##// END OF EJS Templates
patch/diff: move patch.export() to cmdutil.export()...
Benoit Boissinot -
r10611:e764f24a default
parent child Browse files
Show More
@@ -1,251 +1,251 b''
1 # Copyright (C) 2007-8 Brendan Cully <brendan@kublai.com>
1 # Copyright (C) 2007-8 Brendan Cully <brendan@kublai.com>
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 """hooks for integrating with the CIA.vc notification service
6 """hooks for integrating with the CIA.vc notification service
7
7
8 This is meant to be run as a changegroup or incoming hook. To
8 This is meant to be run as a changegroup or incoming hook. To
9 configure it, set the following options in your hgrc::
9 configure it, set the following options in your hgrc::
10
10
11 [cia]
11 [cia]
12 # your registered CIA user name
12 # your registered CIA user name
13 user = foo
13 user = foo
14 # the name of the project in CIA
14 # the name of the project in CIA
15 project = foo
15 project = foo
16 # the module (subproject) (optional)
16 # the module (subproject) (optional)
17 #module = foo
17 #module = foo
18 # Append a diffstat to the log message (optional)
18 # Append a diffstat to the log message (optional)
19 #diffstat = False
19 #diffstat = False
20 # Template to use for log messages (optional)
20 # Template to use for log messages (optional)
21 #template = {desc}\\n{baseurl}/rev/{node}-- {diffstat}
21 #template = {desc}\\n{baseurl}/rev/{node}-- {diffstat}
22 # Style to use (optional)
22 # Style to use (optional)
23 #style = foo
23 #style = foo
24 # The URL of the CIA notification service (optional)
24 # The URL of the CIA notification service (optional)
25 # You can use mailto: URLs to send by email, eg
25 # You can use mailto: URLs to send by email, eg
26 # mailto:cia@cia.vc
26 # mailto:cia@cia.vc
27 # Make sure to set email.from if you do this.
27 # Make sure to set email.from if you do this.
28 #url = http://cia.vc/
28 #url = http://cia.vc/
29 # print message instead of sending it (optional)
29 # print message instead of sending it (optional)
30 #test = False
30 #test = False
31
31
32 [hooks]
32 [hooks]
33 # one of these:
33 # one of these:
34 changegroup.cia = python:hgcia.hook
34 changegroup.cia = python:hgcia.hook
35 #incoming.cia = python:hgcia.hook
35 #incoming.cia = python:hgcia.hook
36
36
37 [web]
37 [web]
38 # If you want hyperlinks (optional)
38 # If you want hyperlinks (optional)
39 baseurl = http://server/path/to/repo
39 baseurl = http://server/path/to/repo
40 """
40 """
41
41
42 from mercurial.i18n import _
42 from mercurial.i18n import _
43 from mercurial.node import *
43 from mercurial.node import *
44 from mercurial import cmdutil, patch, templater, util, mail
44 from mercurial import cmdutil, patch, templater, util, mail
45 import email.Parser
45 import email.Parser
46
46
47 import xmlrpclib
47 import xmlrpclib
48 from xml.sax import saxutils
48 from xml.sax import saxutils
49
49
50 socket_timeout = 30 # seconds
50 socket_timeout = 30 # seconds
51 try:
51 try:
52 # set a timeout for the socket so you don't have to wait so looooong
52 # set a timeout for the socket so you don't have to wait so looooong
53 # when cia.vc is having problems. requires python >= 2.3:
53 # when cia.vc is having problems. requires python >= 2.3:
54 import socket
54 import socket
55 socket.setdefaulttimeout(socket_timeout)
55 socket.setdefaulttimeout(socket_timeout)
56 except:
56 except:
57 pass
57 pass
58
58
59 HGCIA_VERSION = '0.1'
59 HGCIA_VERSION = '0.1'
60 HGCIA_URL = 'http://hg.kublai.com/mercurial/hgcia'
60 HGCIA_URL = 'http://hg.kublai.com/mercurial/hgcia'
61
61
62
62
63 class ciamsg(object):
63 class ciamsg(object):
64 """ A CIA message """
64 """ A CIA message """
65 def __init__(self, cia, ctx):
65 def __init__(self, cia, ctx):
66 self.cia = cia
66 self.cia = cia
67 self.ctx = ctx
67 self.ctx = ctx
68 self.url = self.cia.url
68 self.url = self.cia.url
69
69
70 def fileelem(self, path, uri, action):
70 def fileelem(self, path, uri, action):
71 if uri:
71 if uri:
72 uri = ' uri=%s' % saxutils.quoteattr(uri)
72 uri = ' uri=%s' % saxutils.quoteattr(uri)
73 return '<file%s action=%s>%s</file>' % (
73 return '<file%s action=%s>%s</file>' % (
74 uri, saxutils.quoteattr(action), saxutils.escape(path))
74 uri, saxutils.quoteattr(action), saxutils.escape(path))
75
75
76 def fileelems(self):
76 def fileelems(self):
77 n = self.ctx.node()
77 n = self.ctx.node()
78 f = self.cia.repo.status(self.ctx.parents()[0].node(), n)
78 f = self.cia.repo.status(self.ctx.parents()[0].node(), n)
79 url = self.url or ''
79 url = self.url or ''
80 elems = []
80 elems = []
81 for path in f[0]:
81 for path in f[0]:
82 uri = '%s/diff/%s/%s' % (url, short(n), path)
82 uri = '%s/diff/%s/%s' % (url, short(n), path)
83 elems.append(self.fileelem(path, url and uri, 'modify'))
83 elems.append(self.fileelem(path, url and uri, 'modify'))
84 for path in f[1]:
84 for path in f[1]:
85 # TODO: copy/rename ?
85 # TODO: copy/rename ?
86 uri = '%s/file/%s/%s' % (url, short(n), path)
86 uri = '%s/file/%s/%s' % (url, short(n), path)
87 elems.append(self.fileelem(path, url and uri, 'add'))
87 elems.append(self.fileelem(path, url and uri, 'add'))
88 for path in f[2]:
88 for path in f[2]:
89 elems.append(self.fileelem(path, '', 'remove'))
89 elems.append(self.fileelem(path, '', 'remove'))
90
90
91 return '\n'.join(elems)
91 return '\n'.join(elems)
92
92
93 def sourceelem(self, project, module=None, branch=None):
93 def sourceelem(self, project, module=None, branch=None):
94 msg = ['<source>', '<project>%s</project>' % saxutils.escape(project)]
94 msg = ['<source>', '<project>%s</project>' % saxutils.escape(project)]
95 if module:
95 if module:
96 msg.append('<module>%s</module>' % saxutils.escape(module))
96 msg.append('<module>%s</module>' % saxutils.escape(module))
97 if branch:
97 if branch:
98 msg.append('<branch>%s</branch>' % saxutils.escape(branch))
98 msg.append('<branch>%s</branch>' % saxutils.escape(branch))
99 msg.append('</source>')
99 msg.append('</source>')
100
100
101 return '\n'.join(msg)
101 return '\n'.join(msg)
102
102
103 def diffstat(self):
103 def diffstat(self):
104 class patchbuf(object):
104 class patchbuf(object):
105 def __init__(self):
105 def __init__(self):
106 self.lines = []
106 self.lines = []
107 # diffstat is stupid
107 # diffstat is stupid
108 self.name = 'cia'
108 self.name = 'cia'
109 def write(self, data):
109 def write(self, data):
110 self.lines.append(data)
110 self.lines.append(data)
111 def close(self):
111 def close(self):
112 pass
112 pass
113
113
114 n = self.ctx.node()
114 n = self.ctx.node()
115 pbuf = patchbuf()
115 pbuf = patchbuf()
116 patch.export(self.cia.repo, [n], fp=pbuf)
116 cmdutil.export(self.cia.repo, [n], fp=pbuf)
117 return patch.diffstat(pbuf.lines) or ''
117 return patch.diffstat(pbuf.lines) or ''
118
118
119 def logmsg(self):
119 def logmsg(self):
120 diffstat = self.cia.diffstat and self.diffstat() or ''
120 diffstat = self.cia.diffstat and self.diffstat() or ''
121 self.cia.ui.pushbuffer()
121 self.cia.ui.pushbuffer()
122 self.cia.templater.show(self.ctx, changes=self.ctx.changeset(),
122 self.cia.templater.show(self.ctx, changes=self.ctx.changeset(),
123 url=self.cia.url, diffstat=diffstat)
123 url=self.cia.url, diffstat=diffstat)
124 return self.cia.ui.popbuffer()
124 return self.cia.ui.popbuffer()
125
125
126 def xml(self):
126 def xml(self):
127 n = short(self.ctx.node())
127 n = short(self.ctx.node())
128 src = self.sourceelem(self.cia.project, module=self.cia.module,
128 src = self.sourceelem(self.cia.project, module=self.cia.module,
129 branch=self.ctx.branch())
129 branch=self.ctx.branch())
130 # unix timestamp
130 # unix timestamp
131 dt = self.ctx.date()
131 dt = self.ctx.date()
132 timestamp = dt[0]
132 timestamp = dt[0]
133
133
134 author = saxutils.escape(self.ctx.user())
134 author = saxutils.escape(self.ctx.user())
135 rev = '%d:%s' % (self.ctx.rev(), n)
135 rev = '%d:%s' % (self.ctx.rev(), n)
136 log = saxutils.escape(self.logmsg())
136 log = saxutils.escape(self.logmsg())
137
137
138 url = self.url and '<url>%s/rev/%s</url>' % (saxutils.escape(self.url),
138 url = self.url and '<url>%s/rev/%s</url>' % (saxutils.escape(self.url),
139 n) or ''
139 n) or ''
140
140
141 msg = """
141 msg = """
142 <message>
142 <message>
143 <generator>
143 <generator>
144 <name>Mercurial (hgcia)</name>
144 <name>Mercurial (hgcia)</name>
145 <version>%s</version>
145 <version>%s</version>
146 <url>%s</url>
146 <url>%s</url>
147 <user>%s</user>
147 <user>%s</user>
148 </generator>
148 </generator>
149 %s
149 %s
150 <body>
150 <body>
151 <commit>
151 <commit>
152 <author>%s</author>
152 <author>%s</author>
153 <version>%s</version>
153 <version>%s</version>
154 <log>%s</log>
154 <log>%s</log>
155 %s
155 %s
156 <files>%s</files>
156 <files>%s</files>
157 </commit>
157 </commit>
158 </body>
158 </body>
159 <timestamp>%d</timestamp>
159 <timestamp>%d</timestamp>
160 </message>
160 </message>
161 """ % \
161 """ % \
162 (HGCIA_VERSION, saxutils.escape(HGCIA_URL),
162 (HGCIA_VERSION, saxutils.escape(HGCIA_URL),
163 saxutils.escape(self.cia.user), src, author, rev, log, url,
163 saxutils.escape(self.cia.user), src, author, rev, log, url,
164 self.fileelems(), timestamp)
164 self.fileelems(), timestamp)
165
165
166 return msg
166 return msg
167
167
168
168
169 class hgcia(object):
169 class hgcia(object):
170 """ CIA notification class """
170 """ CIA notification class """
171
171
172 deftemplate = '{desc}'
172 deftemplate = '{desc}'
173 dstemplate = '{desc}\n-- \n{diffstat}'
173 dstemplate = '{desc}\n-- \n{diffstat}'
174
174
175 def __init__(self, ui, repo):
175 def __init__(self, ui, repo):
176 self.ui = ui
176 self.ui = ui
177 self.repo = repo
177 self.repo = repo
178
178
179 self.ciaurl = self.ui.config('cia', 'url', 'http://cia.vc')
179 self.ciaurl = self.ui.config('cia', 'url', 'http://cia.vc')
180 self.user = self.ui.config('cia', 'user')
180 self.user = self.ui.config('cia', 'user')
181 self.project = self.ui.config('cia', 'project')
181 self.project = self.ui.config('cia', 'project')
182 self.module = self.ui.config('cia', 'module')
182 self.module = self.ui.config('cia', 'module')
183 self.diffstat = self.ui.configbool('cia', 'diffstat')
183 self.diffstat = self.ui.configbool('cia', 'diffstat')
184 self.emailfrom = self.ui.config('email', 'from')
184 self.emailfrom = self.ui.config('email', 'from')
185 self.dryrun = self.ui.configbool('cia', 'test')
185 self.dryrun = self.ui.configbool('cia', 'test')
186 self.url = self.ui.config('web', 'baseurl')
186 self.url = self.ui.config('web', 'baseurl')
187
187
188 style = self.ui.config('cia', 'style')
188 style = self.ui.config('cia', 'style')
189 template = self.ui.config('cia', 'template')
189 template = self.ui.config('cia', 'template')
190 if not template:
190 if not template:
191 template = self.diffstat and self.dstemplate or self.deftemplate
191 template = self.diffstat and self.dstemplate or self.deftemplate
192 template = templater.parsestring(template, quoted=False)
192 template = templater.parsestring(template, quoted=False)
193 t = cmdutil.changeset_templater(self.ui, self.repo, False, None,
193 t = cmdutil.changeset_templater(self.ui, self.repo, False, None,
194 style, False)
194 style, False)
195 t.use_template(template)
195 t.use_template(template)
196 self.templater = t
196 self.templater = t
197
197
198 def sendrpc(self, msg):
198 def sendrpc(self, msg):
199 srv = xmlrpclib.Server(self.ciaurl)
199 srv = xmlrpclib.Server(self.ciaurl)
200 res = srv.hub.deliver(msg)
200 res = srv.hub.deliver(msg)
201 if res is not True:
201 if res is not True:
202 raise util.Abort(_('%s returned an error: %s') %
202 raise util.Abort(_('%s returned an error: %s') %
203 (self.ciaurl, res))
203 (self.ciaurl, res))
204
204
205 def sendemail(self, address, data):
205 def sendemail(self, address, data):
206 p = email.Parser.Parser()
206 p = email.Parser.Parser()
207 msg = p.parsestr(data)
207 msg = p.parsestr(data)
208 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
208 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
209 msg['To'] = address
209 msg['To'] = address
210 msg['From'] = self.emailfrom
210 msg['From'] = self.emailfrom
211 msg['Subject'] = 'DeliverXML'
211 msg['Subject'] = 'DeliverXML'
212 msg['Content-type'] = 'text/xml'
212 msg['Content-type'] = 'text/xml'
213 msgtext = msg.as_string()
213 msgtext = msg.as_string()
214
214
215 self.ui.status(_('hgcia: sending update to %s\n') % address)
215 self.ui.status(_('hgcia: sending update to %s\n') % address)
216 mail.sendmail(self.ui, util.email(self.emailfrom),
216 mail.sendmail(self.ui, util.email(self.emailfrom),
217 [address], msgtext)
217 [address], msgtext)
218
218
219
219
220 def hook(ui, repo, hooktype, node=None, url=None, **kwargs):
220 def hook(ui, repo, hooktype, node=None, url=None, **kwargs):
221 """ send CIA notification """
221 """ send CIA notification """
222 def sendmsg(cia, ctx):
222 def sendmsg(cia, ctx):
223 msg = ciamsg(cia, ctx).xml()
223 msg = ciamsg(cia, ctx).xml()
224 if cia.dryrun:
224 if cia.dryrun:
225 ui.write(msg)
225 ui.write(msg)
226 elif cia.ciaurl.startswith('mailto:'):
226 elif cia.ciaurl.startswith('mailto:'):
227 if not cia.emailfrom:
227 if not cia.emailfrom:
228 raise util.Abort(_('email.from must be defined when '
228 raise util.Abort(_('email.from must be defined when '
229 'sending by email'))
229 'sending by email'))
230 cia.sendemail(cia.ciaurl[7:], msg)
230 cia.sendemail(cia.ciaurl[7:], msg)
231 else:
231 else:
232 cia.sendrpc(msg)
232 cia.sendrpc(msg)
233
233
234 n = bin(node)
234 n = bin(node)
235 cia = hgcia(ui, repo)
235 cia = hgcia(ui, repo)
236 if not cia.user:
236 if not cia.user:
237 ui.debug('cia: no user specified')
237 ui.debug('cia: no user specified')
238 return
238 return
239 if not cia.project:
239 if not cia.project:
240 ui.debug('cia: no project specified')
240 ui.debug('cia: no project specified')
241 return
241 return
242 if hooktype == 'changegroup':
242 if hooktype == 'changegroup':
243 start = repo.changelog.rev(n)
243 start = repo.changelog.rev(n)
244 end = len(repo.changelog)
244 end = len(repo.changelog)
245 for rev in xrange(start, end):
245 for rev in xrange(start, end):
246 n = repo.changelog.node(rev)
246 n = repo.changelog.node(rev)
247 ctx = repo.changectx(n)
247 ctx = repo.changectx(n)
248 sendmsg(cia, ctx)
248 sendmsg(cia, ctx)
249 else:
249 else:
250 ctx = repo.changectx(n)
250 ctx = repo.changectx(n)
251 sendmsg(cia, ctx)
251 sendmsg(cia, ctx)
@@ -1,2813 +1,2813 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use "hg help command" for more details)::
17 Common tasks (use "hg help command" for more details)::
18
18
19 prepare repository to work with patches qinit
19 prepare repository to work with patches qinit
20 create new patch qnew
20 create new patch qnew
21 import existing patch qimport
21 import existing patch qimport
22
22
23 print patch series qseries
23 print patch series qseries
24 print applied patches qapplied
24 print applied patches qapplied
25
25
26 add known patch to applied stack qpush
26 add known patch to applied stack qpush
27 remove patch from applied stack qpop
27 remove patch from applied stack qpop
28 refresh contents of top applied patch qrefresh
28 refresh contents of top applied patch qrefresh
29
29
30 By default, mq will automatically use git patches when required to
30 By default, mq will automatically use git patches when required to
31 avoid losing file mode changes, copy records, binary files or empty
31 avoid losing file mode changes, copy records, binary files or empty
32 files creations or deletions. This behaviour can be configured with::
32 files creations or deletions. This behaviour can be configured with::
33
33
34 [mq]
34 [mq]
35 git = auto/keep/yes/no
35 git = auto/keep/yes/no
36
36
37 If set to 'keep', mq will obey the [diff] section configuration while
37 If set to 'keep', mq will obey the [diff] section configuration while
38 preserving existing git patches upon qrefresh. If set to 'yes' or
38 preserving existing git patches upon qrefresh. If set to 'yes' or
39 'no', mq will override the [diff] section and always generate git or
39 'no', mq will override the [diff] section and always generate git or
40 regular patches, possibly losing data in the second case.
40 regular patches, possibly losing data in the second case.
41 '''
41 '''
42
42
43 from mercurial.i18n import _
43 from mercurial.i18n import _
44 from mercurial.node import bin, hex, short, nullid, nullrev
44 from mercurial.node import bin, hex, short, nullid, nullrev
45 from mercurial.lock import release
45 from mercurial.lock import release
46 from mercurial import commands, cmdutil, hg, patch, util
46 from mercurial import commands, cmdutil, hg, patch, util
47 from mercurial import repair, extensions, url, error
47 from mercurial import repair, extensions, url, error
48 import os, sys, re, errno
48 import os, sys, re, errno
49
49
50 commands.norepo += " qclone"
50 commands.norepo += " qclone"
51
51
52 # Patch names looks like unix-file names.
52 # Patch names looks like unix-file names.
53 # They must be joinable with queue directory and result in the patch path.
53 # They must be joinable with queue directory and result in the patch path.
54 normname = util.normpath
54 normname = util.normpath
55
55
56 class statusentry(object):
56 class statusentry(object):
57 def __init__(self, rev, name=None):
57 def __init__(self, rev, name=None):
58 if not name:
58 if not name:
59 fields = rev.split(':', 1)
59 fields = rev.split(':', 1)
60 if len(fields) == 2:
60 if len(fields) == 2:
61 self.rev, self.name = fields
61 self.rev, self.name = fields
62 else:
62 else:
63 self.rev, self.name = None, None
63 self.rev, self.name = None, None
64 else:
64 else:
65 self.rev, self.name = rev, name
65 self.rev, self.name = rev, name
66
66
67 def __str__(self):
67 def __str__(self):
68 return self.rev + ':' + self.name
68 return self.rev + ':' + self.name
69
69
70 class patchheader(object):
70 class patchheader(object):
71 def __init__(self, pf, plainmode=False):
71 def __init__(self, pf, plainmode=False):
72 def eatdiff(lines):
72 def eatdiff(lines):
73 while lines:
73 while lines:
74 l = lines[-1]
74 l = lines[-1]
75 if (l.startswith("diff -") or
75 if (l.startswith("diff -") or
76 l.startswith("Index:") or
76 l.startswith("Index:") or
77 l.startswith("===========")):
77 l.startswith("===========")):
78 del lines[-1]
78 del lines[-1]
79 else:
79 else:
80 break
80 break
81 def eatempty(lines):
81 def eatempty(lines):
82 while lines:
82 while lines:
83 l = lines[-1]
83 l = lines[-1]
84 if re.match('\s*$', l):
84 if re.match('\s*$', l):
85 del lines[-1]
85 del lines[-1]
86 else:
86 else:
87 break
87 break
88
88
89 message = []
89 message = []
90 comments = []
90 comments = []
91 user = None
91 user = None
92 date = None
92 date = None
93 parent = None
93 parent = None
94 format = None
94 format = None
95 subject = None
95 subject = None
96 diffstart = 0
96 diffstart = 0
97
97
98 for line in file(pf):
98 for line in file(pf):
99 line = line.rstrip()
99 line = line.rstrip()
100 if line.startswith('diff --git'):
100 if line.startswith('diff --git'):
101 diffstart = 2
101 diffstart = 2
102 break
102 break
103 if diffstart:
103 if diffstart:
104 if line.startswith('+++ '):
104 if line.startswith('+++ '):
105 diffstart = 2
105 diffstart = 2
106 break
106 break
107 if line.startswith("--- "):
107 if line.startswith("--- "):
108 diffstart = 1
108 diffstart = 1
109 continue
109 continue
110 elif format == "hgpatch":
110 elif format == "hgpatch":
111 # parse values when importing the result of an hg export
111 # parse values when importing the result of an hg export
112 if line.startswith("# User "):
112 if line.startswith("# User "):
113 user = line[7:]
113 user = line[7:]
114 elif line.startswith("# Date "):
114 elif line.startswith("# Date "):
115 date = line[7:]
115 date = line[7:]
116 elif line.startswith("# Parent "):
116 elif line.startswith("# Parent "):
117 parent = line[9:]
117 parent = line[9:]
118 elif not line.startswith("# ") and line:
118 elif not line.startswith("# ") and line:
119 message.append(line)
119 message.append(line)
120 format = None
120 format = None
121 elif line == '# HG changeset patch':
121 elif line == '# HG changeset patch':
122 message = []
122 message = []
123 format = "hgpatch"
123 format = "hgpatch"
124 elif (format != "tagdone" and (line.startswith("Subject: ") or
124 elif (format != "tagdone" and (line.startswith("Subject: ") or
125 line.startswith("subject: "))):
125 line.startswith("subject: "))):
126 subject = line[9:]
126 subject = line[9:]
127 format = "tag"
127 format = "tag"
128 elif (format != "tagdone" and (line.startswith("From: ") or
128 elif (format != "tagdone" and (line.startswith("From: ") or
129 line.startswith("from: "))):
129 line.startswith("from: "))):
130 user = line[6:]
130 user = line[6:]
131 format = "tag"
131 format = "tag"
132 elif (format != "tagdone" and (line.startswith("Date: ") or
132 elif (format != "tagdone" and (line.startswith("Date: ") or
133 line.startswith("date: "))):
133 line.startswith("date: "))):
134 date = line[6:]
134 date = line[6:]
135 format = "tag"
135 format = "tag"
136 elif format == "tag" and line == "":
136 elif format == "tag" and line == "":
137 # when looking for tags (subject: from: etc) they
137 # when looking for tags (subject: from: etc) they
138 # end once you find a blank line in the source
138 # end once you find a blank line in the source
139 format = "tagdone"
139 format = "tagdone"
140 elif message or line:
140 elif message or line:
141 message.append(line)
141 message.append(line)
142 comments.append(line)
142 comments.append(line)
143
143
144 eatdiff(message)
144 eatdiff(message)
145 eatdiff(comments)
145 eatdiff(comments)
146 eatempty(message)
146 eatempty(message)
147 eatempty(comments)
147 eatempty(comments)
148
148
149 # make sure message isn't empty
149 # make sure message isn't empty
150 if format and format.startswith("tag") and subject:
150 if format and format.startswith("tag") and subject:
151 message.insert(0, "")
151 message.insert(0, "")
152 message.insert(0, subject)
152 message.insert(0, subject)
153
153
154 self.message = message
154 self.message = message
155 self.comments = comments
155 self.comments = comments
156 self.user = user
156 self.user = user
157 self.date = date
157 self.date = date
158 self.parent = parent
158 self.parent = parent
159 self.haspatch = diffstart > 1
159 self.haspatch = diffstart > 1
160 self.plainmode = plainmode
160 self.plainmode = plainmode
161
161
162 def setuser(self, user):
162 def setuser(self, user):
163 if not self.updateheader(['From: ', '# User '], user):
163 if not self.updateheader(['From: ', '# User '], user):
164 try:
164 try:
165 patchheaderat = self.comments.index('# HG changeset patch')
165 patchheaderat = self.comments.index('# HG changeset patch')
166 self.comments.insert(patchheaderat + 1, '# User ' + user)
166 self.comments.insert(patchheaderat + 1, '# User ' + user)
167 except ValueError:
167 except ValueError:
168 if self.plainmode or self._hasheader(['Date: ']):
168 if self.plainmode or self._hasheader(['Date: ']):
169 self.comments = ['From: ' + user] + self.comments
169 self.comments = ['From: ' + user] + self.comments
170 else:
170 else:
171 tmp = ['# HG changeset patch', '# User ' + user, '']
171 tmp = ['# HG changeset patch', '# User ' + user, '']
172 self.comments = tmp + self.comments
172 self.comments = tmp + self.comments
173 self.user = user
173 self.user = user
174
174
175 def setdate(self, date):
175 def setdate(self, date):
176 if not self.updateheader(['Date: ', '# Date '], date):
176 if not self.updateheader(['Date: ', '# Date '], date):
177 try:
177 try:
178 patchheaderat = self.comments.index('# HG changeset patch')
178 patchheaderat = self.comments.index('# HG changeset patch')
179 self.comments.insert(patchheaderat + 1, '# Date ' + date)
179 self.comments.insert(patchheaderat + 1, '# Date ' + date)
180 except ValueError:
180 except ValueError:
181 if self.plainmode or self._hasheader(['From: ']):
181 if self.plainmode or self._hasheader(['From: ']):
182 self.comments = ['Date: ' + date] + self.comments
182 self.comments = ['Date: ' + date] + self.comments
183 else:
183 else:
184 tmp = ['# HG changeset patch', '# Date ' + date, '']
184 tmp = ['# HG changeset patch', '# Date ' + date, '']
185 self.comments = tmp + self.comments
185 self.comments = tmp + self.comments
186 self.date = date
186 self.date = date
187
187
188 def setparent(self, parent):
188 def setparent(self, parent):
189 if not self.updateheader(['# Parent '], parent):
189 if not self.updateheader(['# Parent '], parent):
190 try:
190 try:
191 patchheaderat = self.comments.index('# HG changeset patch')
191 patchheaderat = self.comments.index('# HG changeset patch')
192 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
192 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
193 except ValueError:
193 except ValueError:
194 pass
194 pass
195 self.parent = parent
195 self.parent = parent
196
196
197 def setmessage(self, message):
197 def setmessage(self, message):
198 if self.comments:
198 if self.comments:
199 self._delmsg()
199 self._delmsg()
200 self.message = [message]
200 self.message = [message]
201 self.comments += self.message
201 self.comments += self.message
202
202
203 def updateheader(self, prefixes, new):
203 def updateheader(self, prefixes, new):
204 '''Update all references to a field in the patch header.
204 '''Update all references to a field in the patch header.
205 Return whether the field is present.'''
205 Return whether the field is present.'''
206 res = False
206 res = False
207 for prefix in prefixes:
207 for prefix in prefixes:
208 for i in xrange(len(self.comments)):
208 for i in xrange(len(self.comments)):
209 if self.comments[i].startswith(prefix):
209 if self.comments[i].startswith(prefix):
210 self.comments[i] = prefix + new
210 self.comments[i] = prefix + new
211 res = True
211 res = True
212 break
212 break
213 return res
213 return res
214
214
215 def _hasheader(self, prefixes):
215 def _hasheader(self, prefixes):
216 '''Check if a header starts with any of the given prefixes.'''
216 '''Check if a header starts with any of the given prefixes.'''
217 for prefix in prefixes:
217 for prefix in prefixes:
218 for comment in self.comments:
218 for comment in self.comments:
219 if comment.startswith(prefix):
219 if comment.startswith(prefix):
220 return True
220 return True
221 return False
221 return False
222
222
223 def __str__(self):
223 def __str__(self):
224 if not self.comments:
224 if not self.comments:
225 return ''
225 return ''
226 return '\n'.join(self.comments) + '\n\n'
226 return '\n'.join(self.comments) + '\n\n'
227
227
228 def _delmsg(self):
228 def _delmsg(self):
229 '''Remove existing message, keeping the rest of the comments fields.
229 '''Remove existing message, keeping the rest of the comments fields.
230 If comments contains 'subject: ', message will prepend
230 If comments contains 'subject: ', message will prepend
231 the field and a blank line.'''
231 the field and a blank line.'''
232 if self.message:
232 if self.message:
233 subj = 'subject: ' + self.message[0].lower()
233 subj = 'subject: ' + self.message[0].lower()
234 for i in xrange(len(self.comments)):
234 for i in xrange(len(self.comments)):
235 if subj == self.comments[i].lower():
235 if subj == self.comments[i].lower():
236 del self.comments[i]
236 del self.comments[i]
237 self.message = self.message[2:]
237 self.message = self.message[2:]
238 break
238 break
239 ci = 0
239 ci = 0
240 for mi in self.message:
240 for mi in self.message:
241 while mi != self.comments[ci]:
241 while mi != self.comments[ci]:
242 ci += 1
242 ci += 1
243 del self.comments[ci]
243 del self.comments[ci]
244
244
245 class queue(object):
245 class queue(object):
246 def __init__(self, ui, path, patchdir=None):
246 def __init__(self, ui, path, patchdir=None):
247 self.basepath = path
247 self.basepath = path
248 self.path = patchdir or os.path.join(path, "patches")
248 self.path = patchdir or os.path.join(path, "patches")
249 self.opener = util.opener(self.path)
249 self.opener = util.opener(self.path)
250 self.ui = ui
250 self.ui = ui
251 self.applied_dirty = 0
251 self.applied_dirty = 0
252 self.series_dirty = 0
252 self.series_dirty = 0
253 self.series_path = "series"
253 self.series_path = "series"
254 self.status_path = "status"
254 self.status_path = "status"
255 self.guards_path = "guards"
255 self.guards_path = "guards"
256 self.active_guards = None
256 self.active_guards = None
257 self.guards_dirty = False
257 self.guards_dirty = False
258 # Handle mq.git as a bool with extended values
258 # Handle mq.git as a bool with extended values
259 try:
259 try:
260 gitmode = ui.configbool('mq', 'git', None)
260 gitmode = ui.configbool('mq', 'git', None)
261 if gitmode is None:
261 if gitmode is None:
262 raise error.ConfigError()
262 raise error.ConfigError()
263 self.gitmode = gitmode and 'yes' or 'no'
263 self.gitmode = gitmode and 'yes' or 'no'
264 except error.ConfigError:
264 except error.ConfigError:
265 self.gitmode = ui.config('mq', 'git', 'auto').lower()
265 self.gitmode = ui.config('mq', 'git', 'auto').lower()
266 self.plainmode = ui.configbool('mq', 'plain', False)
266 self.plainmode = ui.configbool('mq', 'plain', False)
267
267
268 @util.propertycache
268 @util.propertycache
269 def applied(self):
269 def applied(self):
270 if os.path.exists(self.join(self.status_path)):
270 if os.path.exists(self.join(self.status_path)):
271 lines = self.opener(self.status_path).read().splitlines()
271 lines = self.opener(self.status_path).read().splitlines()
272 return [statusentry(l) for l in lines]
272 return [statusentry(l) for l in lines]
273 return []
273 return []
274
274
275 @util.propertycache
275 @util.propertycache
276 def full_series(self):
276 def full_series(self):
277 if os.path.exists(self.join(self.series_path)):
277 if os.path.exists(self.join(self.series_path)):
278 return self.opener(self.series_path).read().splitlines()
278 return self.opener(self.series_path).read().splitlines()
279 return []
279 return []
280
280
281 @util.propertycache
281 @util.propertycache
282 def series(self):
282 def series(self):
283 self.parse_series()
283 self.parse_series()
284 return self.series
284 return self.series
285
285
286 @util.propertycache
286 @util.propertycache
287 def series_guards(self):
287 def series_guards(self):
288 self.parse_series()
288 self.parse_series()
289 return self.series_guards
289 return self.series_guards
290
290
291 def invalidate(self):
291 def invalidate(self):
292 for a in 'applied full_series series series_guards'.split():
292 for a in 'applied full_series series series_guards'.split():
293 if a in self.__dict__:
293 if a in self.__dict__:
294 delattr(self, a)
294 delattr(self, a)
295 self.applied_dirty = 0
295 self.applied_dirty = 0
296 self.series_dirty = 0
296 self.series_dirty = 0
297 self.guards_dirty = False
297 self.guards_dirty = False
298 self.active_guards = None
298 self.active_guards = None
299
299
300 def diffopts(self, opts={}, patchfn=None):
300 def diffopts(self, opts={}, patchfn=None):
301 diffopts = patch.diffopts(self.ui, opts)
301 diffopts = patch.diffopts(self.ui, opts)
302 if self.gitmode == 'auto':
302 if self.gitmode == 'auto':
303 diffopts.upgrade = True
303 diffopts.upgrade = True
304 elif self.gitmode == 'keep':
304 elif self.gitmode == 'keep':
305 pass
305 pass
306 elif self.gitmode in ('yes', 'no'):
306 elif self.gitmode in ('yes', 'no'):
307 diffopts.git = self.gitmode == 'yes'
307 diffopts.git = self.gitmode == 'yes'
308 else:
308 else:
309 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
309 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
310 ' got %s') % self.gitmode)
310 ' got %s') % self.gitmode)
311 if patchfn:
311 if patchfn:
312 diffopts = self.patchopts(diffopts, patchfn)
312 diffopts = self.patchopts(diffopts, patchfn)
313 return diffopts
313 return diffopts
314
314
315 def patchopts(self, diffopts, *patches):
315 def patchopts(self, diffopts, *patches):
316 """Return a copy of input diff options with git set to true if
316 """Return a copy of input diff options with git set to true if
317 referenced patch is a git patch and should be preserved as such.
317 referenced patch is a git patch and should be preserved as such.
318 """
318 """
319 diffopts = diffopts.copy()
319 diffopts = diffopts.copy()
320 if not diffopts.git and self.gitmode == 'keep':
320 if not diffopts.git and self.gitmode == 'keep':
321 for patchfn in patches:
321 for patchfn in patches:
322 patchf = self.opener(patchfn, 'r')
322 patchf = self.opener(patchfn, 'r')
323 # if the patch was a git patch, refresh it as a git patch
323 # if the patch was a git patch, refresh it as a git patch
324 for line in patchf:
324 for line in patchf:
325 if line.startswith('diff --git'):
325 if line.startswith('diff --git'):
326 diffopts.git = True
326 diffopts.git = True
327 break
327 break
328 patchf.close()
328 patchf.close()
329 return diffopts
329 return diffopts
330
330
331 def join(self, *p):
331 def join(self, *p):
332 return os.path.join(self.path, *p)
332 return os.path.join(self.path, *p)
333
333
334 def find_series(self, patch):
334 def find_series(self, patch):
335 pre = re.compile("(\s*)([^#]+)")
335 pre = re.compile("(\s*)([^#]+)")
336 index = 0
336 index = 0
337 for l in self.full_series:
337 for l in self.full_series:
338 m = pre.match(l)
338 m = pre.match(l)
339 if m:
339 if m:
340 s = m.group(2)
340 s = m.group(2)
341 s = s.rstrip()
341 s = s.rstrip()
342 if s == patch:
342 if s == patch:
343 return index
343 return index
344 index += 1
344 index += 1
345 return None
345 return None
346
346
347 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
347 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
348
348
349 def parse_series(self):
349 def parse_series(self):
350 self.series = []
350 self.series = []
351 self.series_guards = []
351 self.series_guards = []
352 for l in self.full_series:
352 for l in self.full_series:
353 h = l.find('#')
353 h = l.find('#')
354 if h == -1:
354 if h == -1:
355 patch = l
355 patch = l
356 comment = ''
356 comment = ''
357 elif h == 0:
357 elif h == 0:
358 continue
358 continue
359 else:
359 else:
360 patch = l[:h]
360 patch = l[:h]
361 comment = l[h:]
361 comment = l[h:]
362 patch = patch.strip()
362 patch = patch.strip()
363 if patch:
363 if patch:
364 if patch in self.series:
364 if patch in self.series:
365 raise util.Abort(_('%s appears more than once in %s') %
365 raise util.Abort(_('%s appears more than once in %s') %
366 (patch, self.join(self.series_path)))
366 (patch, self.join(self.series_path)))
367 self.series.append(patch)
367 self.series.append(patch)
368 self.series_guards.append(self.guard_re.findall(comment))
368 self.series_guards.append(self.guard_re.findall(comment))
369
369
370 def check_guard(self, guard):
370 def check_guard(self, guard):
371 if not guard:
371 if not guard:
372 return _('guard cannot be an empty string')
372 return _('guard cannot be an empty string')
373 bad_chars = '# \t\r\n\f'
373 bad_chars = '# \t\r\n\f'
374 first = guard[0]
374 first = guard[0]
375 if first in '-+':
375 if first in '-+':
376 return (_('guard %r starts with invalid character: %r') %
376 return (_('guard %r starts with invalid character: %r') %
377 (guard, first))
377 (guard, first))
378 for c in bad_chars:
378 for c in bad_chars:
379 if c in guard:
379 if c in guard:
380 return _('invalid character in guard %r: %r') % (guard, c)
380 return _('invalid character in guard %r: %r') % (guard, c)
381
381
382 def set_active(self, guards):
382 def set_active(self, guards):
383 for guard in guards:
383 for guard in guards:
384 bad = self.check_guard(guard)
384 bad = self.check_guard(guard)
385 if bad:
385 if bad:
386 raise util.Abort(bad)
386 raise util.Abort(bad)
387 guards = sorted(set(guards))
387 guards = sorted(set(guards))
388 self.ui.debug('active guards: %s\n' % ' '.join(guards))
388 self.ui.debug('active guards: %s\n' % ' '.join(guards))
389 self.active_guards = guards
389 self.active_guards = guards
390 self.guards_dirty = True
390 self.guards_dirty = True
391
391
392 def active(self):
392 def active(self):
393 if self.active_guards is None:
393 if self.active_guards is None:
394 self.active_guards = []
394 self.active_guards = []
395 try:
395 try:
396 guards = self.opener(self.guards_path).read().split()
396 guards = self.opener(self.guards_path).read().split()
397 except IOError, err:
397 except IOError, err:
398 if err.errno != errno.ENOENT:
398 if err.errno != errno.ENOENT:
399 raise
399 raise
400 guards = []
400 guards = []
401 for i, guard in enumerate(guards):
401 for i, guard in enumerate(guards):
402 bad = self.check_guard(guard)
402 bad = self.check_guard(guard)
403 if bad:
403 if bad:
404 self.ui.warn('%s:%d: %s\n' %
404 self.ui.warn('%s:%d: %s\n' %
405 (self.join(self.guards_path), i + 1, bad))
405 (self.join(self.guards_path), i + 1, bad))
406 else:
406 else:
407 self.active_guards.append(guard)
407 self.active_guards.append(guard)
408 return self.active_guards
408 return self.active_guards
409
409
410 def set_guards(self, idx, guards):
410 def set_guards(self, idx, guards):
411 for g in guards:
411 for g in guards:
412 if len(g) < 2:
412 if len(g) < 2:
413 raise util.Abort(_('guard %r too short') % g)
413 raise util.Abort(_('guard %r too short') % g)
414 if g[0] not in '-+':
414 if g[0] not in '-+':
415 raise util.Abort(_('guard %r starts with invalid char') % g)
415 raise util.Abort(_('guard %r starts with invalid char') % g)
416 bad = self.check_guard(g[1:])
416 bad = self.check_guard(g[1:])
417 if bad:
417 if bad:
418 raise util.Abort(bad)
418 raise util.Abort(bad)
419 drop = self.guard_re.sub('', self.full_series[idx])
419 drop = self.guard_re.sub('', self.full_series[idx])
420 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
420 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
421 self.parse_series()
421 self.parse_series()
422 self.series_dirty = True
422 self.series_dirty = True
423
423
424 def pushable(self, idx):
424 def pushable(self, idx):
425 if isinstance(idx, str):
425 if isinstance(idx, str):
426 idx = self.series.index(idx)
426 idx = self.series.index(idx)
427 patchguards = self.series_guards[idx]
427 patchguards = self.series_guards[idx]
428 if not patchguards:
428 if not patchguards:
429 return True, None
429 return True, None
430 guards = self.active()
430 guards = self.active()
431 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
431 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
432 if exactneg:
432 if exactneg:
433 return False, exactneg[0]
433 return False, exactneg[0]
434 pos = [g for g in patchguards if g[0] == '+']
434 pos = [g for g in patchguards if g[0] == '+']
435 exactpos = [g for g in pos if g[1:] in guards]
435 exactpos = [g for g in pos if g[1:] in guards]
436 if pos:
436 if pos:
437 if exactpos:
437 if exactpos:
438 return True, exactpos[0]
438 return True, exactpos[0]
439 return False, pos
439 return False, pos
440 return True, ''
440 return True, ''
441
441
442 def explain_pushable(self, idx, all_patches=False):
442 def explain_pushable(self, idx, all_patches=False):
443 write = all_patches and self.ui.write or self.ui.warn
443 write = all_patches and self.ui.write or self.ui.warn
444 if all_patches or self.ui.verbose:
444 if all_patches or self.ui.verbose:
445 if isinstance(idx, str):
445 if isinstance(idx, str):
446 idx = self.series.index(idx)
446 idx = self.series.index(idx)
447 pushable, why = self.pushable(idx)
447 pushable, why = self.pushable(idx)
448 if all_patches and pushable:
448 if all_patches and pushable:
449 if why is None:
449 if why is None:
450 write(_('allowing %s - no guards in effect\n') %
450 write(_('allowing %s - no guards in effect\n') %
451 self.series[idx])
451 self.series[idx])
452 else:
452 else:
453 if not why:
453 if not why:
454 write(_('allowing %s - no matching negative guards\n') %
454 write(_('allowing %s - no matching negative guards\n') %
455 self.series[idx])
455 self.series[idx])
456 else:
456 else:
457 write(_('allowing %s - guarded by %r\n') %
457 write(_('allowing %s - guarded by %r\n') %
458 (self.series[idx], why))
458 (self.series[idx], why))
459 if not pushable:
459 if not pushable:
460 if why:
460 if why:
461 write(_('skipping %s - guarded by %r\n') %
461 write(_('skipping %s - guarded by %r\n') %
462 (self.series[idx], why))
462 (self.series[idx], why))
463 else:
463 else:
464 write(_('skipping %s - no matching guards\n') %
464 write(_('skipping %s - no matching guards\n') %
465 self.series[idx])
465 self.series[idx])
466
466
467 def save_dirty(self):
467 def save_dirty(self):
468 def write_list(items, path):
468 def write_list(items, path):
469 fp = self.opener(path, 'w')
469 fp = self.opener(path, 'w')
470 for i in items:
470 for i in items:
471 fp.write("%s\n" % i)
471 fp.write("%s\n" % i)
472 fp.close()
472 fp.close()
473 if self.applied_dirty:
473 if self.applied_dirty:
474 write_list(map(str, self.applied), self.status_path)
474 write_list(map(str, self.applied), self.status_path)
475 if self.series_dirty:
475 if self.series_dirty:
476 write_list(self.full_series, self.series_path)
476 write_list(self.full_series, self.series_path)
477 if self.guards_dirty:
477 if self.guards_dirty:
478 write_list(self.active_guards, self.guards_path)
478 write_list(self.active_guards, self.guards_path)
479
479
480 def removeundo(self, repo):
480 def removeundo(self, repo):
481 undo = repo.sjoin('undo')
481 undo = repo.sjoin('undo')
482 if not os.path.exists(undo):
482 if not os.path.exists(undo):
483 return
483 return
484 try:
484 try:
485 os.unlink(undo)
485 os.unlink(undo)
486 except OSError, inst:
486 except OSError, inst:
487 self.ui.warn(_('error removing undo: %s\n') % str(inst))
487 self.ui.warn(_('error removing undo: %s\n') % str(inst))
488
488
489 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
489 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
490 fp=None, changes=None, opts={}):
490 fp=None, changes=None, opts={}):
491 stat = opts.get('stat')
491 stat = opts.get('stat')
492 if stat:
492 if stat:
493 opts['unified'] = '0'
493 opts['unified'] = '0'
494
494
495 m = cmdutil.match(repo, files, opts)
495 m = cmdutil.match(repo, files, opts)
496 chunks = patch.diff(repo, node1, node2, m, changes, diffopts)
496 chunks = patch.diff(repo, node1, node2, m, changes, diffopts)
497 write = fp is None and repo.ui.write or fp.write
497 write = fp is None and repo.ui.write or fp.write
498 if stat:
498 if stat:
499 width = self.ui.interactive() and util.termwidth() or 80
499 width = self.ui.interactive() and util.termwidth() or 80
500 write(patch.diffstat(util.iterlines(chunks), width=width,
500 write(patch.diffstat(util.iterlines(chunks), width=width,
501 git=diffopts.git))
501 git=diffopts.git))
502 else:
502 else:
503 for chunk in chunks:
503 for chunk in chunks:
504 write(chunk)
504 write(chunk)
505
505
506 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
506 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
507 # first try just applying the patch
507 # first try just applying the patch
508 (err, n) = self.apply(repo, [patch], update_status=False,
508 (err, n) = self.apply(repo, [patch], update_status=False,
509 strict=True, merge=rev)
509 strict=True, merge=rev)
510
510
511 if err == 0:
511 if err == 0:
512 return (err, n)
512 return (err, n)
513
513
514 if n is None:
514 if n is None:
515 raise util.Abort(_("apply failed for patch %s") % patch)
515 raise util.Abort(_("apply failed for patch %s") % patch)
516
516
517 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
517 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
518
518
519 # apply failed, strip away that rev and merge.
519 # apply failed, strip away that rev and merge.
520 hg.clean(repo, head)
520 hg.clean(repo, head)
521 self.strip(repo, n, update=False, backup='strip')
521 self.strip(repo, n, update=False, backup='strip')
522
522
523 ctx = repo[rev]
523 ctx = repo[rev]
524 ret = hg.merge(repo, rev)
524 ret = hg.merge(repo, rev)
525 if ret:
525 if ret:
526 raise util.Abort(_("update returned %d") % ret)
526 raise util.Abort(_("update returned %d") % ret)
527 n = repo.commit(ctx.description(), ctx.user(), force=True)
527 n = repo.commit(ctx.description(), ctx.user(), force=True)
528 if n is None:
528 if n is None:
529 raise util.Abort(_("repo commit failed"))
529 raise util.Abort(_("repo commit failed"))
530 try:
530 try:
531 ph = patchheader(mergeq.join(patch), self.plainmode)
531 ph = patchheader(mergeq.join(patch), self.plainmode)
532 except:
532 except:
533 raise util.Abort(_("unable to read %s") % patch)
533 raise util.Abort(_("unable to read %s") % patch)
534
534
535 diffopts = self.patchopts(diffopts, patch)
535 diffopts = self.patchopts(diffopts, patch)
536 patchf = self.opener(patch, "w")
536 patchf = self.opener(patch, "w")
537 comments = str(ph)
537 comments = str(ph)
538 if comments:
538 if comments:
539 patchf.write(comments)
539 patchf.write(comments)
540 self.printdiff(repo, diffopts, head, n, fp=patchf)
540 self.printdiff(repo, diffopts, head, n, fp=patchf)
541 patchf.close()
541 patchf.close()
542 self.removeundo(repo)
542 self.removeundo(repo)
543 return (0, n)
543 return (0, n)
544
544
545 def qparents(self, repo, rev=None):
545 def qparents(self, repo, rev=None):
546 if rev is None:
546 if rev is None:
547 (p1, p2) = repo.dirstate.parents()
547 (p1, p2) = repo.dirstate.parents()
548 if p2 == nullid:
548 if p2 == nullid:
549 return p1
549 return p1
550 if len(self.applied) == 0:
550 if len(self.applied) == 0:
551 return None
551 return None
552 return bin(self.applied[-1].rev)
552 return bin(self.applied[-1].rev)
553 pp = repo.changelog.parents(rev)
553 pp = repo.changelog.parents(rev)
554 if pp[1] != nullid:
554 if pp[1] != nullid:
555 arevs = [x.rev for x in self.applied]
555 arevs = [x.rev for x in self.applied]
556 p0 = hex(pp[0])
556 p0 = hex(pp[0])
557 p1 = hex(pp[1])
557 p1 = hex(pp[1])
558 if p0 in arevs:
558 if p0 in arevs:
559 return pp[0]
559 return pp[0]
560 if p1 in arevs:
560 if p1 in arevs:
561 return pp[1]
561 return pp[1]
562 return pp[0]
562 return pp[0]
563
563
564 def mergepatch(self, repo, mergeq, series, diffopts):
564 def mergepatch(self, repo, mergeq, series, diffopts):
565 if len(self.applied) == 0:
565 if len(self.applied) == 0:
566 # each of the patches merged in will have two parents. This
566 # each of the patches merged in will have two parents. This
567 # can confuse the qrefresh, qdiff, and strip code because it
567 # can confuse the qrefresh, qdiff, and strip code because it
568 # needs to know which parent is actually in the patch queue.
568 # needs to know which parent is actually in the patch queue.
569 # so, we insert a merge marker with only one parent. This way
569 # so, we insert a merge marker with only one parent. This way
570 # the first patch in the queue is never a merge patch
570 # the first patch in the queue is never a merge patch
571 #
571 #
572 pname = ".hg.patches.merge.marker"
572 pname = ".hg.patches.merge.marker"
573 n = repo.commit('[mq]: merge marker', force=True)
573 n = repo.commit('[mq]: merge marker', force=True)
574 self.removeundo(repo)
574 self.removeundo(repo)
575 self.applied.append(statusentry(hex(n), pname))
575 self.applied.append(statusentry(hex(n), pname))
576 self.applied_dirty = 1
576 self.applied_dirty = 1
577
577
578 head = self.qparents(repo)
578 head = self.qparents(repo)
579
579
580 for patch in series:
580 for patch in series:
581 patch = mergeq.lookup(patch, strict=True)
581 patch = mergeq.lookup(patch, strict=True)
582 if not patch:
582 if not patch:
583 self.ui.warn(_("patch %s does not exist\n") % patch)
583 self.ui.warn(_("patch %s does not exist\n") % patch)
584 return (1, None)
584 return (1, None)
585 pushable, reason = self.pushable(patch)
585 pushable, reason = self.pushable(patch)
586 if not pushable:
586 if not pushable:
587 self.explain_pushable(patch, all_patches=True)
587 self.explain_pushable(patch, all_patches=True)
588 continue
588 continue
589 info = mergeq.isapplied(patch)
589 info = mergeq.isapplied(patch)
590 if not info:
590 if not info:
591 self.ui.warn(_("patch %s is not applied\n") % patch)
591 self.ui.warn(_("patch %s is not applied\n") % patch)
592 return (1, None)
592 return (1, None)
593 rev = bin(info[1])
593 rev = bin(info[1])
594 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
594 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
595 if head:
595 if head:
596 self.applied.append(statusentry(hex(head), patch))
596 self.applied.append(statusentry(hex(head), patch))
597 self.applied_dirty = 1
597 self.applied_dirty = 1
598 if err:
598 if err:
599 return (err, head)
599 return (err, head)
600 self.save_dirty()
600 self.save_dirty()
601 return (0, head)
601 return (0, head)
602
602
603 def patch(self, repo, patchfile):
603 def patch(self, repo, patchfile):
604 '''Apply patchfile to the working directory.
604 '''Apply patchfile to the working directory.
605 patchfile: name of patch file'''
605 patchfile: name of patch file'''
606 files = {}
606 files = {}
607 try:
607 try:
608 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
608 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
609 files=files, eolmode=None)
609 files=files, eolmode=None)
610 except Exception, inst:
610 except Exception, inst:
611 self.ui.note(str(inst) + '\n')
611 self.ui.note(str(inst) + '\n')
612 if not self.ui.verbose:
612 if not self.ui.verbose:
613 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
613 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
614 return (False, files, False)
614 return (False, files, False)
615
615
616 return (True, files, fuzz)
616 return (True, files, fuzz)
617
617
618 def apply(self, repo, series, list=False, update_status=True,
618 def apply(self, repo, series, list=False, update_status=True,
619 strict=False, patchdir=None, merge=None, all_files={}):
619 strict=False, patchdir=None, merge=None, all_files={}):
620 wlock = lock = tr = None
620 wlock = lock = tr = None
621 try:
621 try:
622 wlock = repo.wlock()
622 wlock = repo.wlock()
623 lock = repo.lock()
623 lock = repo.lock()
624 tr = repo.transaction()
624 tr = repo.transaction()
625 try:
625 try:
626 ret = self._apply(repo, series, list, update_status,
626 ret = self._apply(repo, series, list, update_status,
627 strict, patchdir, merge, all_files=all_files)
627 strict, patchdir, merge, all_files=all_files)
628 tr.close()
628 tr.close()
629 self.save_dirty()
629 self.save_dirty()
630 return ret
630 return ret
631 except:
631 except:
632 try:
632 try:
633 tr.abort()
633 tr.abort()
634 finally:
634 finally:
635 repo.invalidate()
635 repo.invalidate()
636 repo.dirstate.invalidate()
636 repo.dirstate.invalidate()
637 raise
637 raise
638 finally:
638 finally:
639 del tr
639 del tr
640 release(lock, wlock)
640 release(lock, wlock)
641 self.removeundo(repo)
641 self.removeundo(repo)
642
642
643 def _apply(self, repo, series, list=False, update_status=True,
643 def _apply(self, repo, series, list=False, update_status=True,
644 strict=False, patchdir=None, merge=None, all_files={}):
644 strict=False, patchdir=None, merge=None, all_files={}):
645 '''returns (error, hash)
645 '''returns (error, hash)
646 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
646 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
647 # TODO unify with commands.py
647 # TODO unify with commands.py
648 if not patchdir:
648 if not patchdir:
649 patchdir = self.path
649 patchdir = self.path
650 err = 0
650 err = 0
651 n = None
651 n = None
652 for patchname in series:
652 for patchname in series:
653 pushable, reason = self.pushable(patchname)
653 pushable, reason = self.pushable(patchname)
654 if not pushable:
654 if not pushable:
655 self.explain_pushable(patchname, all_patches=True)
655 self.explain_pushable(patchname, all_patches=True)
656 continue
656 continue
657 self.ui.status(_("applying %s\n") % patchname)
657 self.ui.status(_("applying %s\n") % patchname)
658 pf = os.path.join(patchdir, patchname)
658 pf = os.path.join(patchdir, patchname)
659
659
660 try:
660 try:
661 ph = patchheader(self.join(patchname), self.plainmode)
661 ph = patchheader(self.join(patchname), self.plainmode)
662 except:
662 except:
663 self.ui.warn(_("unable to read %s\n") % patchname)
663 self.ui.warn(_("unable to read %s\n") % patchname)
664 err = 1
664 err = 1
665 break
665 break
666
666
667 message = ph.message
667 message = ph.message
668 if not message:
668 if not message:
669 message = "imported patch %s\n" % patchname
669 message = "imported patch %s\n" % patchname
670 else:
670 else:
671 if list:
671 if list:
672 message.append("\nimported patch %s" % patchname)
672 message.append("\nimported patch %s" % patchname)
673 message = '\n'.join(message)
673 message = '\n'.join(message)
674
674
675 if ph.haspatch:
675 if ph.haspatch:
676 (patcherr, files, fuzz) = self.patch(repo, pf)
676 (patcherr, files, fuzz) = self.patch(repo, pf)
677 all_files.update(files)
677 all_files.update(files)
678 patcherr = not patcherr
678 patcherr = not patcherr
679 else:
679 else:
680 self.ui.warn(_("patch %s is empty\n") % patchname)
680 self.ui.warn(_("patch %s is empty\n") % patchname)
681 patcherr, files, fuzz = 0, [], 0
681 patcherr, files, fuzz = 0, [], 0
682
682
683 if merge and files:
683 if merge and files:
684 # Mark as removed/merged and update dirstate parent info
684 # Mark as removed/merged and update dirstate parent info
685 removed = []
685 removed = []
686 merged = []
686 merged = []
687 for f in files:
687 for f in files:
688 if os.path.exists(repo.wjoin(f)):
688 if os.path.exists(repo.wjoin(f)):
689 merged.append(f)
689 merged.append(f)
690 else:
690 else:
691 removed.append(f)
691 removed.append(f)
692 for f in removed:
692 for f in removed:
693 repo.dirstate.remove(f)
693 repo.dirstate.remove(f)
694 for f in merged:
694 for f in merged:
695 repo.dirstate.merge(f)
695 repo.dirstate.merge(f)
696 p1, p2 = repo.dirstate.parents()
696 p1, p2 = repo.dirstate.parents()
697 repo.dirstate.setparents(p1, merge)
697 repo.dirstate.setparents(p1, merge)
698
698
699 files = patch.updatedir(self.ui, repo, files)
699 files = patch.updatedir(self.ui, repo, files)
700 match = cmdutil.matchfiles(repo, files or [])
700 match = cmdutil.matchfiles(repo, files or [])
701 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
701 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
702
702
703 if n is None:
703 if n is None:
704 raise util.Abort(_("repo commit failed"))
704 raise util.Abort(_("repo commit failed"))
705
705
706 if update_status:
706 if update_status:
707 self.applied.append(statusentry(hex(n), patchname))
707 self.applied.append(statusentry(hex(n), patchname))
708
708
709 if patcherr:
709 if patcherr:
710 self.ui.warn(_("patch failed, rejects left in working dir\n"))
710 self.ui.warn(_("patch failed, rejects left in working dir\n"))
711 err = 2
711 err = 2
712 break
712 break
713
713
714 if fuzz and strict:
714 if fuzz and strict:
715 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
715 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
716 err = 3
716 err = 3
717 break
717 break
718 return (err, n)
718 return (err, n)
719
719
720 def _cleanup(self, patches, numrevs, keep=False):
720 def _cleanup(self, patches, numrevs, keep=False):
721 if not keep:
721 if not keep:
722 r = self.qrepo()
722 r = self.qrepo()
723 if r:
723 if r:
724 r.remove(patches, True)
724 r.remove(patches, True)
725 else:
725 else:
726 for p in patches:
726 for p in patches:
727 os.unlink(self.join(p))
727 os.unlink(self.join(p))
728
728
729 if numrevs:
729 if numrevs:
730 del self.applied[:numrevs]
730 del self.applied[:numrevs]
731 self.applied_dirty = 1
731 self.applied_dirty = 1
732
732
733 for i in sorted([self.find_series(p) for p in patches], reverse=True):
733 for i in sorted([self.find_series(p) for p in patches], reverse=True):
734 del self.full_series[i]
734 del self.full_series[i]
735 self.parse_series()
735 self.parse_series()
736 self.series_dirty = 1
736 self.series_dirty = 1
737
737
738 def _revpatches(self, repo, revs):
738 def _revpatches(self, repo, revs):
739 firstrev = repo[self.applied[0].rev].rev()
739 firstrev = repo[self.applied[0].rev].rev()
740 patches = []
740 patches = []
741 for i, rev in enumerate(revs):
741 for i, rev in enumerate(revs):
742
742
743 if rev < firstrev:
743 if rev < firstrev:
744 raise util.Abort(_('revision %d is not managed') % rev)
744 raise util.Abort(_('revision %d is not managed') % rev)
745
745
746 ctx = repo[rev]
746 ctx = repo[rev]
747 base = bin(self.applied[i].rev)
747 base = bin(self.applied[i].rev)
748 if ctx.node() != base:
748 if ctx.node() != base:
749 msg = _('cannot delete revision %d above applied patches')
749 msg = _('cannot delete revision %d above applied patches')
750 raise util.Abort(msg % rev)
750 raise util.Abort(msg % rev)
751
751
752 patch = self.applied[i].name
752 patch = self.applied[i].name
753 for fmt in ('[mq]: %s', 'imported patch %s'):
753 for fmt in ('[mq]: %s', 'imported patch %s'):
754 if ctx.description() == fmt % patch:
754 if ctx.description() == fmt % patch:
755 msg = _('patch %s finalized without changeset message\n')
755 msg = _('patch %s finalized without changeset message\n')
756 repo.ui.status(msg % patch)
756 repo.ui.status(msg % patch)
757 break
757 break
758
758
759 patches.append(patch)
759 patches.append(patch)
760 return patches
760 return patches
761
761
762 def finish(self, repo, revs):
762 def finish(self, repo, revs):
763 patches = self._revpatches(repo, sorted(revs))
763 patches = self._revpatches(repo, sorted(revs))
764 self._cleanup(patches, len(patches))
764 self._cleanup(patches, len(patches))
765
765
766 def delete(self, repo, patches, opts):
766 def delete(self, repo, patches, opts):
767 if not patches and not opts.get('rev'):
767 if not patches and not opts.get('rev'):
768 raise util.Abort(_('qdelete requires at least one revision or '
768 raise util.Abort(_('qdelete requires at least one revision or '
769 'patch name'))
769 'patch name'))
770
770
771 realpatches = []
771 realpatches = []
772 for patch in patches:
772 for patch in patches:
773 patch = self.lookup(patch, strict=True)
773 patch = self.lookup(patch, strict=True)
774 info = self.isapplied(patch)
774 info = self.isapplied(patch)
775 if info:
775 if info:
776 raise util.Abort(_("cannot delete applied patch %s") % patch)
776 raise util.Abort(_("cannot delete applied patch %s") % patch)
777 if patch not in self.series:
777 if patch not in self.series:
778 raise util.Abort(_("patch %s not in series file") % patch)
778 raise util.Abort(_("patch %s not in series file") % patch)
779 realpatches.append(patch)
779 realpatches.append(patch)
780
780
781 numrevs = 0
781 numrevs = 0
782 if opts.get('rev'):
782 if opts.get('rev'):
783 if not self.applied:
783 if not self.applied:
784 raise util.Abort(_('no patches applied'))
784 raise util.Abort(_('no patches applied'))
785 revs = cmdutil.revrange(repo, opts['rev'])
785 revs = cmdutil.revrange(repo, opts['rev'])
786 if len(revs) > 1 and revs[0] > revs[1]:
786 if len(revs) > 1 and revs[0] > revs[1]:
787 revs.reverse()
787 revs.reverse()
788 revpatches = self._revpatches(repo, revs)
788 revpatches = self._revpatches(repo, revs)
789 realpatches += revpatches
789 realpatches += revpatches
790 numrevs = len(revpatches)
790 numrevs = len(revpatches)
791
791
792 self._cleanup(realpatches, numrevs, opts.get('keep'))
792 self._cleanup(realpatches, numrevs, opts.get('keep'))
793
793
794 def check_toppatch(self, repo):
794 def check_toppatch(self, repo):
795 if len(self.applied) > 0:
795 if len(self.applied) > 0:
796 top = bin(self.applied[-1].rev)
796 top = bin(self.applied[-1].rev)
797 patch = self.applied[-1].name
797 patch = self.applied[-1].name
798 pp = repo.dirstate.parents()
798 pp = repo.dirstate.parents()
799 if top not in pp:
799 if top not in pp:
800 raise util.Abort(_("working directory revision is not qtip"))
800 raise util.Abort(_("working directory revision is not qtip"))
801 return top, patch
801 return top, patch
802 return None, None
802 return None, None
803
803
804 def check_localchanges(self, repo, force=False, refresh=True):
804 def check_localchanges(self, repo, force=False, refresh=True):
805 m, a, r, d = repo.status()[:4]
805 m, a, r, d = repo.status()[:4]
806 if (m or a or r or d) and not force:
806 if (m or a or r or d) and not force:
807 if refresh:
807 if refresh:
808 raise util.Abort(_("local changes found, refresh first"))
808 raise util.Abort(_("local changes found, refresh first"))
809 else:
809 else:
810 raise util.Abort(_("local changes found"))
810 raise util.Abort(_("local changes found"))
811 return m, a, r, d
811 return m, a, r, d
812
812
813 _reserved = ('series', 'status', 'guards')
813 _reserved = ('series', 'status', 'guards')
814 def check_reserved_name(self, name):
814 def check_reserved_name(self, name):
815 if (name in self._reserved or name.startswith('.hg')
815 if (name in self._reserved or name.startswith('.hg')
816 or name.startswith('.mq') or '#' in name or ':' in name):
816 or name.startswith('.mq') or '#' in name or ':' in name):
817 raise util.Abort(_('"%s" cannot be used as the name of a patch')
817 raise util.Abort(_('"%s" cannot be used as the name of a patch')
818 % name)
818 % name)
819
819
820 def new(self, repo, patchfn, *pats, **opts):
820 def new(self, repo, patchfn, *pats, **opts):
821 """options:
821 """options:
822 msg: a string or a no-argument function returning a string
822 msg: a string or a no-argument function returning a string
823 """
823 """
824 msg = opts.get('msg')
824 msg = opts.get('msg')
825 user = opts.get('user')
825 user = opts.get('user')
826 date = opts.get('date')
826 date = opts.get('date')
827 if date:
827 if date:
828 date = util.parsedate(date)
828 date = util.parsedate(date)
829 diffopts = self.diffopts({'git': opts.get('git')})
829 diffopts = self.diffopts({'git': opts.get('git')})
830 self.check_reserved_name(patchfn)
830 self.check_reserved_name(patchfn)
831 if os.path.exists(self.join(patchfn)):
831 if os.path.exists(self.join(patchfn)):
832 raise util.Abort(_('patch "%s" already exists') % patchfn)
832 raise util.Abort(_('patch "%s" already exists') % patchfn)
833 if opts.get('include') or opts.get('exclude') or pats:
833 if opts.get('include') or opts.get('exclude') or pats:
834 match = cmdutil.match(repo, pats, opts)
834 match = cmdutil.match(repo, pats, opts)
835 # detect missing files in pats
835 # detect missing files in pats
836 def badfn(f, msg):
836 def badfn(f, msg):
837 raise util.Abort('%s: %s' % (f, msg))
837 raise util.Abort('%s: %s' % (f, msg))
838 match.bad = badfn
838 match.bad = badfn
839 m, a, r, d = repo.status(match=match)[:4]
839 m, a, r, d = repo.status(match=match)[:4]
840 else:
840 else:
841 m, a, r, d = self.check_localchanges(repo, force=True)
841 m, a, r, d = self.check_localchanges(repo, force=True)
842 match = cmdutil.matchfiles(repo, m + a + r)
842 match = cmdutil.matchfiles(repo, m + a + r)
843 if len(repo[None].parents()) > 1:
843 if len(repo[None].parents()) > 1:
844 raise util.Abort(_('cannot manage merge changesets'))
844 raise util.Abort(_('cannot manage merge changesets'))
845 commitfiles = m + a + r
845 commitfiles = m + a + r
846 self.check_toppatch(repo)
846 self.check_toppatch(repo)
847 insert = self.full_series_end()
847 insert = self.full_series_end()
848 wlock = repo.wlock()
848 wlock = repo.wlock()
849 try:
849 try:
850 # if patch file write fails, abort early
850 # if patch file write fails, abort early
851 p = self.opener(patchfn, "w")
851 p = self.opener(patchfn, "w")
852 try:
852 try:
853 if self.plainmode:
853 if self.plainmode:
854 if user:
854 if user:
855 p.write("From: " + user + "\n")
855 p.write("From: " + user + "\n")
856 if not date:
856 if not date:
857 p.write("\n")
857 p.write("\n")
858 if date:
858 if date:
859 p.write("Date: %d %d\n\n" % date)
859 p.write("Date: %d %d\n\n" % date)
860 else:
860 else:
861 p.write("# HG changeset patch\n")
861 p.write("# HG changeset patch\n")
862 p.write("# Parent "
862 p.write("# Parent "
863 + hex(repo[None].parents()[0].node()) + "\n")
863 + hex(repo[None].parents()[0].node()) + "\n")
864 if user:
864 if user:
865 p.write("# User " + user + "\n")
865 p.write("# User " + user + "\n")
866 if date:
866 if date:
867 p.write("# Date %s %s\n\n" % date)
867 p.write("# Date %s %s\n\n" % date)
868 if hasattr(msg, '__call__'):
868 if hasattr(msg, '__call__'):
869 msg = msg()
869 msg = msg()
870 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
870 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
871 n = repo.commit(commitmsg, user, date, match=match, force=True)
871 n = repo.commit(commitmsg, user, date, match=match, force=True)
872 if n is None:
872 if n is None:
873 raise util.Abort(_("repo commit failed"))
873 raise util.Abort(_("repo commit failed"))
874 try:
874 try:
875 self.full_series[insert:insert] = [patchfn]
875 self.full_series[insert:insert] = [patchfn]
876 self.applied.append(statusentry(hex(n), patchfn))
876 self.applied.append(statusentry(hex(n), patchfn))
877 self.parse_series()
877 self.parse_series()
878 self.series_dirty = 1
878 self.series_dirty = 1
879 self.applied_dirty = 1
879 self.applied_dirty = 1
880 if msg:
880 if msg:
881 msg = msg + "\n\n"
881 msg = msg + "\n\n"
882 p.write(msg)
882 p.write(msg)
883 if commitfiles:
883 if commitfiles:
884 parent = self.qparents(repo, n)
884 parent = self.qparents(repo, n)
885 chunks = patch.diff(repo, node1=parent, node2=n,
885 chunks = patch.diff(repo, node1=parent, node2=n,
886 match=match, opts=diffopts)
886 match=match, opts=diffopts)
887 for chunk in chunks:
887 for chunk in chunks:
888 p.write(chunk)
888 p.write(chunk)
889 p.close()
889 p.close()
890 wlock.release()
890 wlock.release()
891 wlock = None
891 wlock = None
892 r = self.qrepo()
892 r = self.qrepo()
893 if r:
893 if r:
894 r.add([patchfn])
894 r.add([patchfn])
895 except:
895 except:
896 repo.rollback()
896 repo.rollback()
897 raise
897 raise
898 except Exception:
898 except Exception:
899 patchpath = self.join(patchfn)
899 patchpath = self.join(patchfn)
900 try:
900 try:
901 os.unlink(patchpath)
901 os.unlink(patchpath)
902 except:
902 except:
903 self.ui.warn(_('error unlinking %s\n') % patchpath)
903 self.ui.warn(_('error unlinking %s\n') % patchpath)
904 raise
904 raise
905 self.removeundo(repo)
905 self.removeundo(repo)
906 finally:
906 finally:
907 release(wlock)
907 release(wlock)
908
908
909 def strip(self, repo, rev, update=True, backup="all", force=None):
909 def strip(self, repo, rev, update=True, backup="all", force=None):
910 wlock = lock = None
910 wlock = lock = None
911 try:
911 try:
912 wlock = repo.wlock()
912 wlock = repo.wlock()
913 lock = repo.lock()
913 lock = repo.lock()
914
914
915 if update:
915 if update:
916 self.check_localchanges(repo, force=force, refresh=False)
916 self.check_localchanges(repo, force=force, refresh=False)
917 urev = self.qparents(repo, rev)
917 urev = self.qparents(repo, rev)
918 hg.clean(repo, urev)
918 hg.clean(repo, urev)
919 repo.dirstate.write()
919 repo.dirstate.write()
920
920
921 self.removeundo(repo)
921 self.removeundo(repo)
922 repair.strip(self.ui, repo, rev, backup)
922 repair.strip(self.ui, repo, rev, backup)
923 # strip may have unbundled a set of backed up revisions after
923 # strip may have unbundled a set of backed up revisions after
924 # the actual strip
924 # the actual strip
925 self.removeundo(repo)
925 self.removeundo(repo)
926 finally:
926 finally:
927 release(lock, wlock)
927 release(lock, wlock)
928
928
929 def isapplied(self, patch):
929 def isapplied(self, patch):
930 """returns (index, rev, patch)"""
930 """returns (index, rev, patch)"""
931 for i, a in enumerate(self.applied):
931 for i, a in enumerate(self.applied):
932 if a.name == patch:
932 if a.name == patch:
933 return (i, a.rev, a.name)
933 return (i, a.rev, a.name)
934 return None
934 return None
935
935
936 # if the exact patch name does not exist, we try a few
936 # if the exact patch name does not exist, we try a few
937 # variations. If strict is passed, we try only #1
937 # variations. If strict is passed, we try only #1
938 #
938 #
939 # 1) a number to indicate an offset in the series file
939 # 1) a number to indicate an offset in the series file
940 # 2) a unique substring of the patch name was given
940 # 2) a unique substring of the patch name was given
941 # 3) patchname[-+]num to indicate an offset in the series file
941 # 3) patchname[-+]num to indicate an offset in the series file
942 def lookup(self, patch, strict=False):
942 def lookup(self, patch, strict=False):
943 patch = patch and str(patch)
943 patch = patch and str(patch)
944
944
945 def partial_name(s):
945 def partial_name(s):
946 if s in self.series:
946 if s in self.series:
947 return s
947 return s
948 matches = [x for x in self.series if s in x]
948 matches = [x for x in self.series if s in x]
949 if len(matches) > 1:
949 if len(matches) > 1:
950 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
950 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
951 for m in matches:
951 for m in matches:
952 self.ui.warn(' %s\n' % m)
952 self.ui.warn(' %s\n' % m)
953 return None
953 return None
954 if matches:
954 if matches:
955 return matches[0]
955 return matches[0]
956 if len(self.series) > 0 and len(self.applied) > 0:
956 if len(self.series) > 0 and len(self.applied) > 0:
957 if s == 'qtip':
957 if s == 'qtip':
958 return self.series[self.series_end(True)-1]
958 return self.series[self.series_end(True)-1]
959 if s == 'qbase':
959 if s == 'qbase':
960 return self.series[0]
960 return self.series[0]
961 return None
961 return None
962
962
963 if patch is None:
963 if patch is None:
964 return None
964 return None
965 if patch in self.series:
965 if patch in self.series:
966 return patch
966 return patch
967
967
968 if not os.path.isfile(self.join(patch)):
968 if not os.path.isfile(self.join(patch)):
969 try:
969 try:
970 sno = int(patch)
970 sno = int(patch)
971 except (ValueError, OverflowError):
971 except (ValueError, OverflowError):
972 pass
972 pass
973 else:
973 else:
974 if -len(self.series) <= sno < len(self.series):
974 if -len(self.series) <= sno < len(self.series):
975 return self.series[sno]
975 return self.series[sno]
976
976
977 if not strict:
977 if not strict:
978 res = partial_name(patch)
978 res = partial_name(patch)
979 if res:
979 if res:
980 return res
980 return res
981 minus = patch.rfind('-')
981 minus = patch.rfind('-')
982 if minus >= 0:
982 if minus >= 0:
983 res = partial_name(patch[:minus])
983 res = partial_name(patch[:minus])
984 if res:
984 if res:
985 i = self.series.index(res)
985 i = self.series.index(res)
986 try:
986 try:
987 off = int(patch[minus + 1:] or 1)
987 off = int(patch[minus + 1:] or 1)
988 except (ValueError, OverflowError):
988 except (ValueError, OverflowError):
989 pass
989 pass
990 else:
990 else:
991 if i - off >= 0:
991 if i - off >= 0:
992 return self.series[i - off]
992 return self.series[i - off]
993 plus = patch.rfind('+')
993 plus = patch.rfind('+')
994 if plus >= 0:
994 if plus >= 0:
995 res = partial_name(patch[:plus])
995 res = partial_name(patch[:plus])
996 if res:
996 if res:
997 i = self.series.index(res)
997 i = self.series.index(res)
998 try:
998 try:
999 off = int(patch[plus + 1:] or 1)
999 off = int(patch[plus + 1:] or 1)
1000 except (ValueError, OverflowError):
1000 except (ValueError, OverflowError):
1001 pass
1001 pass
1002 else:
1002 else:
1003 if i + off < len(self.series):
1003 if i + off < len(self.series):
1004 return self.series[i + off]
1004 return self.series[i + off]
1005 raise util.Abort(_("patch %s not in series") % patch)
1005 raise util.Abort(_("patch %s not in series") % patch)
1006
1006
1007 def push(self, repo, patch=None, force=False, list=False,
1007 def push(self, repo, patch=None, force=False, list=False,
1008 mergeq=None, all=False):
1008 mergeq=None, all=False):
1009 diffopts = self.diffopts()
1009 diffopts = self.diffopts()
1010 wlock = repo.wlock()
1010 wlock = repo.wlock()
1011 try:
1011 try:
1012 heads = []
1012 heads = []
1013 for b, ls in repo.branchmap().iteritems():
1013 for b, ls in repo.branchmap().iteritems():
1014 heads += ls
1014 heads += ls
1015 if not heads:
1015 if not heads:
1016 heads = [nullid]
1016 heads = [nullid]
1017 if repo.dirstate.parents()[0] not in heads:
1017 if repo.dirstate.parents()[0] not in heads:
1018 self.ui.status(_("(working directory not at a head)\n"))
1018 self.ui.status(_("(working directory not at a head)\n"))
1019
1019
1020 if not self.series:
1020 if not self.series:
1021 self.ui.warn(_('no patches in series\n'))
1021 self.ui.warn(_('no patches in series\n'))
1022 return 0
1022 return 0
1023
1023
1024 patch = self.lookup(patch)
1024 patch = self.lookup(patch)
1025 # Suppose our series file is: A B C and the current 'top'
1025 # Suppose our series file is: A B C and the current 'top'
1026 # patch is B. qpush C should be performed (moving forward)
1026 # patch is B. qpush C should be performed (moving forward)
1027 # qpush B is a NOP (no change) qpush A is an error (can't
1027 # qpush B is a NOP (no change) qpush A is an error (can't
1028 # go backwards with qpush)
1028 # go backwards with qpush)
1029 if patch:
1029 if patch:
1030 info = self.isapplied(patch)
1030 info = self.isapplied(patch)
1031 if info:
1031 if info:
1032 if info[0] < len(self.applied) - 1:
1032 if info[0] < len(self.applied) - 1:
1033 raise util.Abort(
1033 raise util.Abort(
1034 _("cannot push to a previous patch: %s") % patch)
1034 _("cannot push to a previous patch: %s") % patch)
1035 self.ui.warn(
1035 self.ui.warn(
1036 _('qpush: %s is already at the top\n') % patch)
1036 _('qpush: %s is already at the top\n') % patch)
1037 return
1037 return
1038 pushable, reason = self.pushable(patch)
1038 pushable, reason = self.pushable(patch)
1039 if not pushable:
1039 if not pushable:
1040 if reason:
1040 if reason:
1041 reason = _('guarded by %r') % reason
1041 reason = _('guarded by %r') % reason
1042 else:
1042 else:
1043 reason = _('no matching guards')
1043 reason = _('no matching guards')
1044 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1044 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1045 return 1
1045 return 1
1046 elif all:
1046 elif all:
1047 patch = self.series[-1]
1047 patch = self.series[-1]
1048 if self.isapplied(patch):
1048 if self.isapplied(patch):
1049 self.ui.warn(_('all patches are currently applied\n'))
1049 self.ui.warn(_('all patches are currently applied\n'))
1050 return 0
1050 return 0
1051
1051
1052 # Following the above example, starting at 'top' of B:
1052 # Following the above example, starting at 'top' of B:
1053 # qpush should be performed (pushes C), but a subsequent
1053 # qpush should be performed (pushes C), but a subsequent
1054 # qpush without an argument is an error (nothing to
1054 # qpush without an argument is an error (nothing to
1055 # apply). This allows a loop of "...while hg qpush..." to
1055 # apply). This allows a loop of "...while hg qpush..." to
1056 # work as it detects an error when done
1056 # work as it detects an error when done
1057 start = self.series_end()
1057 start = self.series_end()
1058 if start == len(self.series):
1058 if start == len(self.series):
1059 self.ui.warn(_('patch series already fully applied\n'))
1059 self.ui.warn(_('patch series already fully applied\n'))
1060 return 1
1060 return 1
1061 if not force:
1061 if not force:
1062 self.check_localchanges(repo)
1062 self.check_localchanges(repo)
1063
1063
1064 self.applied_dirty = 1
1064 self.applied_dirty = 1
1065 if start > 0:
1065 if start > 0:
1066 self.check_toppatch(repo)
1066 self.check_toppatch(repo)
1067 if not patch:
1067 if not patch:
1068 patch = self.series[start]
1068 patch = self.series[start]
1069 end = start + 1
1069 end = start + 1
1070 else:
1070 else:
1071 end = self.series.index(patch, start) + 1
1071 end = self.series.index(patch, start) + 1
1072
1072
1073 s = self.series[start:end]
1073 s = self.series[start:end]
1074 all_files = {}
1074 all_files = {}
1075 try:
1075 try:
1076 if mergeq:
1076 if mergeq:
1077 ret = self.mergepatch(repo, mergeq, s, diffopts)
1077 ret = self.mergepatch(repo, mergeq, s, diffopts)
1078 else:
1078 else:
1079 ret = self.apply(repo, s, list, all_files=all_files)
1079 ret = self.apply(repo, s, list, all_files=all_files)
1080 except:
1080 except:
1081 self.ui.warn(_('cleaning up working directory...'))
1081 self.ui.warn(_('cleaning up working directory...'))
1082 node = repo.dirstate.parents()[0]
1082 node = repo.dirstate.parents()[0]
1083 hg.revert(repo, node, None)
1083 hg.revert(repo, node, None)
1084 unknown = repo.status(unknown=True)[4]
1084 unknown = repo.status(unknown=True)[4]
1085 # only remove unknown files that we know we touched or
1085 # only remove unknown files that we know we touched or
1086 # created while patching
1086 # created while patching
1087 for f in unknown:
1087 for f in unknown:
1088 if f in all_files:
1088 if f in all_files:
1089 util.unlink(repo.wjoin(f))
1089 util.unlink(repo.wjoin(f))
1090 self.ui.warn(_('done\n'))
1090 self.ui.warn(_('done\n'))
1091 raise
1091 raise
1092
1092
1093 if not self.applied:
1093 if not self.applied:
1094 return ret[0]
1094 return ret[0]
1095 top = self.applied[-1].name
1095 top = self.applied[-1].name
1096 if ret[0] and ret[0] > 1:
1096 if ret[0] and ret[0] > 1:
1097 msg = _("errors during apply, please fix and refresh %s\n")
1097 msg = _("errors during apply, please fix and refresh %s\n")
1098 self.ui.write(msg % top)
1098 self.ui.write(msg % top)
1099 else:
1099 else:
1100 self.ui.write(_("now at: %s\n") % top)
1100 self.ui.write(_("now at: %s\n") % top)
1101 return ret[0]
1101 return ret[0]
1102
1102
1103 finally:
1103 finally:
1104 wlock.release()
1104 wlock.release()
1105
1105
1106 def pop(self, repo, patch=None, force=False, update=True, all=False):
1106 def pop(self, repo, patch=None, force=False, update=True, all=False):
1107 def getfile(f, rev, flags):
1107 def getfile(f, rev, flags):
1108 t = repo.file(f).read(rev)
1108 t = repo.file(f).read(rev)
1109 repo.wwrite(f, t, flags)
1109 repo.wwrite(f, t, flags)
1110
1110
1111 wlock = repo.wlock()
1111 wlock = repo.wlock()
1112 try:
1112 try:
1113 if patch:
1113 if patch:
1114 # index, rev, patch
1114 # index, rev, patch
1115 info = self.isapplied(patch)
1115 info = self.isapplied(patch)
1116 if not info:
1116 if not info:
1117 patch = self.lookup(patch)
1117 patch = self.lookup(patch)
1118 info = self.isapplied(patch)
1118 info = self.isapplied(patch)
1119 if not info:
1119 if not info:
1120 raise util.Abort(_("patch %s is not applied") % patch)
1120 raise util.Abort(_("patch %s is not applied") % patch)
1121
1121
1122 if len(self.applied) == 0:
1122 if len(self.applied) == 0:
1123 # Allow qpop -a to work repeatedly,
1123 # Allow qpop -a to work repeatedly,
1124 # but not qpop without an argument
1124 # but not qpop without an argument
1125 self.ui.warn(_("no patches applied\n"))
1125 self.ui.warn(_("no patches applied\n"))
1126 return not all
1126 return not all
1127
1127
1128 if all:
1128 if all:
1129 start = 0
1129 start = 0
1130 elif patch:
1130 elif patch:
1131 start = info[0] + 1
1131 start = info[0] + 1
1132 else:
1132 else:
1133 start = len(self.applied) - 1
1133 start = len(self.applied) - 1
1134
1134
1135 if start >= len(self.applied):
1135 if start >= len(self.applied):
1136 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1136 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1137 return
1137 return
1138
1138
1139 if not update:
1139 if not update:
1140 parents = repo.dirstate.parents()
1140 parents = repo.dirstate.parents()
1141 rr = [bin(x.rev) for x in self.applied]
1141 rr = [bin(x.rev) for x in self.applied]
1142 for p in parents:
1142 for p in parents:
1143 if p in rr:
1143 if p in rr:
1144 self.ui.warn(_("qpop: forcing dirstate update\n"))
1144 self.ui.warn(_("qpop: forcing dirstate update\n"))
1145 update = True
1145 update = True
1146 else:
1146 else:
1147 parents = [p.hex() for p in repo[None].parents()]
1147 parents = [p.hex() for p in repo[None].parents()]
1148 needupdate = False
1148 needupdate = False
1149 for entry in self.applied[start:]:
1149 for entry in self.applied[start:]:
1150 if entry.rev in parents:
1150 if entry.rev in parents:
1151 needupdate = True
1151 needupdate = True
1152 break
1152 break
1153 update = needupdate
1153 update = needupdate
1154
1154
1155 if not force and update:
1155 if not force and update:
1156 self.check_localchanges(repo)
1156 self.check_localchanges(repo)
1157
1157
1158 self.applied_dirty = 1
1158 self.applied_dirty = 1
1159 end = len(self.applied)
1159 end = len(self.applied)
1160 rev = bin(self.applied[start].rev)
1160 rev = bin(self.applied[start].rev)
1161 if update:
1161 if update:
1162 top = self.check_toppatch(repo)[0]
1162 top = self.check_toppatch(repo)[0]
1163
1163
1164 try:
1164 try:
1165 heads = repo.changelog.heads(rev)
1165 heads = repo.changelog.heads(rev)
1166 except error.LookupError:
1166 except error.LookupError:
1167 node = short(rev)
1167 node = short(rev)
1168 raise util.Abort(_('trying to pop unknown node %s') % node)
1168 raise util.Abort(_('trying to pop unknown node %s') % node)
1169
1169
1170 if heads != [bin(self.applied[-1].rev)]:
1170 if heads != [bin(self.applied[-1].rev)]:
1171 raise util.Abort(_("popping would remove a revision not "
1171 raise util.Abort(_("popping would remove a revision not "
1172 "managed by this patch queue"))
1172 "managed by this patch queue"))
1173
1173
1174 # we know there are no local changes, so we can make a simplified
1174 # we know there are no local changes, so we can make a simplified
1175 # form of hg.update.
1175 # form of hg.update.
1176 if update:
1176 if update:
1177 qp = self.qparents(repo, rev)
1177 qp = self.qparents(repo, rev)
1178 changes = repo.changelog.read(qp)
1178 changes = repo.changelog.read(qp)
1179 mmap = repo.manifest.read(changes[0])
1179 mmap = repo.manifest.read(changes[0])
1180 m, a, r, d = repo.status(qp, top)[:4]
1180 m, a, r, d = repo.status(qp, top)[:4]
1181 if d:
1181 if d:
1182 raise util.Abort(_("deletions found between repo revs"))
1182 raise util.Abort(_("deletions found between repo revs"))
1183 for f in a:
1183 for f in a:
1184 try:
1184 try:
1185 os.unlink(repo.wjoin(f))
1185 os.unlink(repo.wjoin(f))
1186 except OSError, e:
1186 except OSError, e:
1187 if e.errno != errno.ENOENT:
1187 if e.errno != errno.ENOENT:
1188 raise
1188 raise
1189 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1189 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1190 except: pass
1190 except: pass
1191 repo.dirstate.forget(f)
1191 repo.dirstate.forget(f)
1192 for f in m:
1192 for f in m:
1193 getfile(f, mmap[f], mmap.flags(f))
1193 getfile(f, mmap[f], mmap.flags(f))
1194 for f in r:
1194 for f in r:
1195 getfile(f, mmap[f], mmap.flags(f))
1195 getfile(f, mmap[f], mmap.flags(f))
1196 for f in m + r:
1196 for f in m + r:
1197 repo.dirstate.normal(f)
1197 repo.dirstate.normal(f)
1198 repo.dirstate.setparents(qp, nullid)
1198 repo.dirstate.setparents(qp, nullid)
1199 for patch in reversed(self.applied[start:end]):
1199 for patch in reversed(self.applied[start:end]):
1200 self.ui.status(_("popping %s\n") % patch.name)
1200 self.ui.status(_("popping %s\n") % patch.name)
1201 del self.applied[start:end]
1201 del self.applied[start:end]
1202 self.strip(repo, rev, update=False, backup='strip')
1202 self.strip(repo, rev, update=False, backup='strip')
1203 if len(self.applied):
1203 if len(self.applied):
1204 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1204 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1205 else:
1205 else:
1206 self.ui.write(_("patch queue now empty\n"))
1206 self.ui.write(_("patch queue now empty\n"))
1207 finally:
1207 finally:
1208 wlock.release()
1208 wlock.release()
1209
1209
1210 def diff(self, repo, pats, opts):
1210 def diff(self, repo, pats, opts):
1211 top, patch = self.check_toppatch(repo)
1211 top, patch = self.check_toppatch(repo)
1212 if not top:
1212 if not top:
1213 self.ui.write(_("no patches applied\n"))
1213 self.ui.write(_("no patches applied\n"))
1214 return
1214 return
1215 qp = self.qparents(repo, top)
1215 qp = self.qparents(repo, top)
1216 if opts.get('reverse'):
1216 if opts.get('reverse'):
1217 node1, node2 = None, qp
1217 node1, node2 = None, qp
1218 else:
1218 else:
1219 node1, node2 = qp, None
1219 node1, node2 = qp, None
1220 diffopts = self.diffopts(opts, patch)
1220 diffopts = self.diffopts(opts, patch)
1221 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1221 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1222
1222
1223 def refresh(self, repo, pats=None, **opts):
1223 def refresh(self, repo, pats=None, **opts):
1224 if len(self.applied) == 0:
1224 if len(self.applied) == 0:
1225 self.ui.write(_("no patches applied\n"))
1225 self.ui.write(_("no patches applied\n"))
1226 return 1
1226 return 1
1227 msg = opts.get('msg', '').rstrip()
1227 msg = opts.get('msg', '').rstrip()
1228 newuser = opts.get('user')
1228 newuser = opts.get('user')
1229 newdate = opts.get('date')
1229 newdate = opts.get('date')
1230 if newdate:
1230 if newdate:
1231 newdate = '%d %d' % util.parsedate(newdate)
1231 newdate = '%d %d' % util.parsedate(newdate)
1232 wlock = repo.wlock()
1232 wlock = repo.wlock()
1233
1233
1234 try:
1234 try:
1235 self.check_toppatch(repo)
1235 self.check_toppatch(repo)
1236 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1236 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1237 top = bin(top)
1237 top = bin(top)
1238 if repo.changelog.heads(top) != [top]:
1238 if repo.changelog.heads(top) != [top]:
1239 raise util.Abort(_("cannot refresh a revision with children"))
1239 raise util.Abort(_("cannot refresh a revision with children"))
1240
1240
1241 cparents = repo.changelog.parents(top)
1241 cparents = repo.changelog.parents(top)
1242 patchparent = self.qparents(repo, top)
1242 patchparent = self.qparents(repo, top)
1243 ph = patchheader(self.join(patchfn), self.plainmode)
1243 ph = patchheader(self.join(patchfn), self.plainmode)
1244 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1244 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1245 if msg:
1245 if msg:
1246 ph.setmessage(msg)
1246 ph.setmessage(msg)
1247 if newuser:
1247 if newuser:
1248 ph.setuser(newuser)
1248 ph.setuser(newuser)
1249 if newdate:
1249 if newdate:
1250 ph.setdate(newdate)
1250 ph.setdate(newdate)
1251 ph.setparent(hex(patchparent))
1251 ph.setparent(hex(patchparent))
1252
1252
1253 # only commit new patch when write is complete
1253 # only commit new patch when write is complete
1254 patchf = self.opener(patchfn, 'w', atomictemp=True)
1254 patchf = self.opener(patchfn, 'w', atomictemp=True)
1255
1255
1256 comments = str(ph)
1256 comments = str(ph)
1257 if comments:
1257 if comments:
1258 patchf.write(comments)
1258 patchf.write(comments)
1259
1259
1260 # update the dirstate in place, strip off the qtip commit
1260 # update the dirstate in place, strip off the qtip commit
1261 # and then commit.
1261 # and then commit.
1262 #
1262 #
1263 # this should really read:
1263 # this should really read:
1264 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1264 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1265 # but we do it backwards to take advantage of manifest/chlog
1265 # but we do it backwards to take advantage of manifest/chlog
1266 # caching against the next repo.status call
1266 # caching against the next repo.status call
1267 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1267 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1268 changes = repo.changelog.read(top)
1268 changes = repo.changelog.read(top)
1269 man = repo.manifest.read(changes[0])
1269 man = repo.manifest.read(changes[0])
1270 aaa = aa[:]
1270 aaa = aa[:]
1271 matchfn = cmdutil.match(repo, pats, opts)
1271 matchfn = cmdutil.match(repo, pats, opts)
1272 # in short mode, we only diff the files included in the
1272 # in short mode, we only diff the files included in the
1273 # patch already plus specified files
1273 # patch already plus specified files
1274 if opts.get('short'):
1274 if opts.get('short'):
1275 # if amending a patch, we start with existing
1275 # if amending a patch, we start with existing
1276 # files plus specified files - unfiltered
1276 # files plus specified files - unfiltered
1277 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1277 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1278 # filter with inc/exl options
1278 # filter with inc/exl options
1279 matchfn = cmdutil.match(repo, opts=opts)
1279 matchfn = cmdutil.match(repo, opts=opts)
1280 else:
1280 else:
1281 match = cmdutil.matchall(repo)
1281 match = cmdutil.matchall(repo)
1282 m, a, r, d = repo.status(match=match)[:4]
1282 m, a, r, d = repo.status(match=match)[:4]
1283
1283
1284 # we might end up with files that were added between
1284 # we might end up with files that were added between
1285 # qtip and the dirstate parent, but then changed in the
1285 # qtip and the dirstate parent, but then changed in the
1286 # local dirstate. in this case, we want them to only
1286 # local dirstate. in this case, we want them to only
1287 # show up in the added section
1287 # show up in the added section
1288 for x in m:
1288 for x in m:
1289 if x not in aa:
1289 if x not in aa:
1290 mm.append(x)
1290 mm.append(x)
1291 # we might end up with files added by the local dirstate that
1291 # we might end up with files added by the local dirstate that
1292 # were deleted by the patch. In this case, they should only
1292 # were deleted by the patch. In this case, they should only
1293 # show up in the changed section.
1293 # show up in the changed section.
1294 for x in a:
1294 for x in a:
1295 if x in dd:
1295 if x in dd:
1296 del dd[dd.index(x)]
1296 del dd[dd.index(x)]
1297 mm.append(x)
1297 mm.append(x)
1298 else:
1298 else:
1299 aa.append(x)
1299 aa.append(x)
1300 # make sure any files deleted in the local dirstate
1300 # make sure any files deleted in the local dirstate
1301 # are not in the add or change column of the patch
1301 # are not in the add or change column of the patch
1302 forget = []
1302 forget = []
1303 for x in d + r:
1303 for x in d + r:
1304 if x in aa:
1304 if x in aa:
1305 del aa[aa.index(x)]
1305 del aa[aa.index(x)]
1306 forget.append(x)
1306 forget.append(x)
1307 continue
1307 continue
1308 elif x in mm:
1308 elif x in mm:
1309 del mm[mm.index(x)]
1309 del mm[mm.index(x)]
1310 dd.append(x)
1310 dd.append(x)
1311
1311
1312 m = list(set(mm))
1312 m = list(set(mm))
1313 r = list(set(dd))
1313 r = list(set(dd))
1314 a = list(set(aa))
1314 a = list(set(aa))
1315 c = [filter(matchfn, l) for l in (m, a, r)]
1315 c = [filter(matchfn, l) for l in (m, a, r)]
1316 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1316 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1317 chunks = patch.diff(repo, patchparent, match=match,
1317 chunks = patch.diff(repo, patchparent, match=match,
1318 changes=c, opts=diffopts)
1318 changes=c, opts=diffopts)
1319 for chunk in chunks:
1319 for chunk in chunks:
1320 patchf.write(chunk)
1320 patchf.write(chunk)
1321
1321
1322 try:
1322 try:
1323 if diffopts.git or diffopts.upgrade:
1323 if diffopts.git or diffopts.upgrade:
1324 copies = {}
1324 copies = {}
1325 for dst in a:
1325 for dst in a:
1326 src = repo.dirstate.copied(dst)
1326 src = repo.dirstate.copied(dst)
1327 # during qfold, the source file for copies may
1327 # during qfold, the source file for copies may
1328 # be removed. Treat this as a simple add.
1328 # be removed. Treat this as a simple add.
1329 if src is not None and src in repo.dirstate:
1329 if src is not None and src in repo.dirstate:
1330 copies.setdefault(src, []).append(dst)
1330 copies.setdefault(src, []).append(dst)
1331 repo.dirstate.add(dst)
1331 repo.dirstate.add(dst)
1332 # remember the copies between patchparent and qtip
1332 # remember the copies between patchparent and qtip
1333 for dst in aaa:
1333 for dst in aaa:
1334 f = repo.file(dst)
1334 f = repo.file(dst)
1335 src = f.renamed(man[dst])
1335 src = f.renamed(man[dst])
1336 if src:
1336 if src:
1337 copies.setdefault(src[0], []).extend(
1337 copies.setdefault(src[0], []).extend(
1338 copies.get(dst, []))
1338 copies.get(dst, []))
1339 if dst in a:
1339 if dst in a:
1340 copies[src[0]].append(dst)
1340 copies[src[0]].append(dst)
1341 # we can't copy a file created by the patch itself
1341 # we can't copy a file created by the patch itself
1342 if dst in copies:
1342 if dst in copies:
1343 del copies[dst]
1343 del copies[dst]
1344 for src, dsts in copies.iteritems():
1344 for src, dsts in copies.iteritems():
1345 for dst in dsts:
1345 for dst in dsts:
1346 repo.dirstate.copy(src, dst)
1346 repo.dirstate.copy(src, dst)
1347 else:
1347 else:
1348 for dst in a:
1348 for dst in a:
1349 repo.dirstate.add(dst)
1349 repo.dirstate.add(dst)
1350 # Drop useless copy information
1350 # Drop useless copy information
1351 for f in list(repo.dirstate.copies()):
1351 for f in list(repo.dirstate.copies()):
1352 repo.dirstate.copy(None, f)
1352 repo.dirstate.copy(None, f)
1353 for f in r:
1353 for f in r:
1354 repo.dirstate.remove(f)
1354 repo.dirstate.remove(f)
1355 # if the patch excludes a modified file, mark that
1355 # if the patch excludes a modified file, mark that
1356 # file with mtime=0 so status can see it.
1356 # file with mtime=0 so status can see it.
1357 mm = []
1357 mm = []
1358 for i in xrange(len(m)-1, -1, -1):
1358 for i in xrange(len(m)-1, -1, -1):
1359 if not matchfn(m[i]):
1359 if not matchfn(m[i]):
1360 mm.append(m[i])
1360 mm.append(m[i])
1361 del m[i]
1361 del m[i]
1362 for f in m:
1362 for f in m:
1363 repo.dirstate.normal(f)
1363 repo.dirstate.normal(f)
1364 for f in mm:
1364 for f in mm:
1365 repo.dirstate.normallookup(f)
1365 repo.dirstate.normallookup(f)
1366 for f in forget:
1366 for f in forget:
1367 repo.dirstate.forget(f)
1367 repo.dirstate.forget(f)
1368
1368
1369 if not msg:
1369 if not msg:
1370 if not ph.message:
1370 if not ph.message:
1371 message = "[mq]: %s\n" % patchfn
1371 message = "[mq]: %s\n" % patchfn
1372 else:
1372 else:
1373 message = "\n".join(ph.message)
1373 message = "\n".join(ph.message)
1374 else:
1374 else:
1375 message = msg
1375 message = msg
1376
1376
1377 user = ph.user or changes[1]
1377 user = ph.user or changes[1]
1378
1378
1379 # assumes strip can roll itself back if interrupted
1379 # assumes strip can roll itself back if interrupted
1380 repo.dirstate.setparents(*cparents)
1380 repo.dirstate.setparents(*cparents)
1381 self.applied.pop()
1381 self.applied.pop()
1382 self.applied_dirty = 1
1382 self.applied_dirty = 1
1383 self.strip(repo, top, update=False,
1383 self.strip(repo, top, update=False,
1384 backup='strip')
1384 backup='strip')
1385 except:
1385 except:
1386 repo.dirstate.invalidate()
1386 repo.dirstate.invalidate()
1387 raise
1387 raise
1388
1388
1389 try:
1389 try:
1390 # might be nice to attempt to roll back strip after this
1390 # might be nice to attempt to roll back strip after this
1391 patchf.rename()
1391 patchf.rename()
1392 n = repo.commit(message, user, ph.date, match=match,
1392 n = repo.commit(message, user, ph.date, match=match,
1393 force=True)
1393 force=True)
1394 self.applied.append(statusentry(hex(n), patchfn))
1394 self.applied.append(statusentry(hex(n), patchfn))
1395 except:
1395 except:
1396 ctx = repo[cparents[0]]
1396 ctx = repo[cparents[0]]
1397 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1397 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1398 self.save_dirty()
1398 self.save_dirty()
1399 self.ui.warn(_('refresh interrupted while patch was popped! '
1399 self.ui.warn(_('refresh interrupted while patch was popped! '
1400 '(revert --all, qpush to recover)\n'))
1400 '(revert --all, qpush to recover)\n'))
1401 raise
1401 raise
1402 finally:
1402 finally:
1403 wlock.release()
1403 wlock.release()
1404 self.removeundo(repo)
1404 self.removeundo(repo)
1405
1405
1406 def init(self, repo, create=False):
1406 def init(self, repo, create=False):
1407 if not create and os.path.isdir(self.path):
1407 if not create and os.path.isdir(self.path):
1408 raise util.Abort(_("patch queue directory already exists"))
1408 raise util.Abort(_("patch queue directory already exists"))
1409 try:
1409 try:
1410 os.mkdir(self.path)
1410 os.mkdir(self.path)
1411 except OSError, inst:
1411 except OSError, inst:
1412 if inst.errno != errno.EEXIST or not create:
1412 if inst.errno != errno.EEXIST or not create:
1413 raise
1413 raise
1414 if create:
1414 if create:
1415 return self.qrepo(create=True)
1415 return self.qrepo(create=True)
1416
1416
1417 def unapplied(self, repo, patch=None):
1417 def unapplied(self, repo, patch=None):
1418 if patch and patch not in self.series:
1418 if patch and patch not in self.series:
1419 raise util.Abort(_("patch %s is not in series file") % patch)
1419 raise util.Abort(_("patch %s is not in series file") % patch)
1420 if not patch:
1420 if not patch:
1421 start = self.series_end()
1421 start = self.series_end()
1422 else:
1422 else:
1423 start = self.series.index(patch) + 1
1423 start = self.series.index(patch) + 1
1424 unapplied = []
1424 unapplied = []
1425 for i in xrange(start, len(self.series)):
1425 for i in xrange(start, len(self.series)):
1426 pushable, reason = self.pushable(i)
1426 pushable, reason = self.pushable(i)
1427 if pushable:
1427 if pushable:
1428 unapplied.append((i, self.series[i]))
1428 unapplied.append((i, self.series[i]))
1429 self.explain_pushable(i)
1429 self.explain_pushable(i)
1430 return unapplied
1430 return unapplied
1431
1431
1432 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1432 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1433 summary=False):
1433 summary=False):
1434 def displayname(pfx, patchname):
1434 def displayname(pfx, patchname):
1435 if summary:
1435 if summary:
1436 ph = patchheader(self.join(patchname), self.plainmode)
1436 ph = patchheader(self.join(patchname), self.plainmode)
1437 msg = ph.message and ph.message[0] or ''
1437 msg = ph.message and ph.message[0] or ''
1438 if self.ui.interactive():
1438 if self.ui.interactive():
1439 width = util.termwidth() - len(pfx) - len(patchname) - 2
1439 width = util.termwidth() - len(pfx) - len(patchname) - 2
1440 if width > 0:
1440 if width > 0:
1441 msg = util.ellipsis(msg, width)
1441 msg = util.ellipsis(msg, width)
1442 else:
1442 else:
1443 msg = ''
1443 msg = ''
1444 msg = "%s%s: %s" % (pfx, patchname, msg)
1444 msg = "%s%s: %s" % (pfx, patchname, msg)
1445 else:
1445 else:
1446 msg = pfx + patchname
1446 msg = pfx + patchname
1447 self.ui.write(msg + '\n')
1447 self.ui.write(msg + '\n')
1448
1448
1449 applied = set([p.name for p in self.applied])
1449 applied = set([p.name for p in self.applied])
1450 if length is None:
1450 if length is None:
1451 length = len(self.series) - start
1451 length = len(self.series) - start
1452 if not missing:
1452 if not missing:
1453 if self.ui.verbose:
1453 if self.ui.verbose:
1454 idxwidth = len(str(start + length - 1))
1454 idxwidth = len(str(start + length - 1))
1455 for i in xrange(start, start + length):
1455 for i in xrange(start, start + length):
1456 patch = self.series[i]
1456 patch = self.series[i]
1457 if patch in applied:
1457 if patch in applied:
1458 stat = 'A'
1458 stat = 'A'
1459 elif self.pushable(i)[0]:
1459 elif self.pushable(i)[0]:
1460 stat = 'U'
1460 stat = 'U'
1461 else:
1461 else:
1462 stat = 'G'
1462 stat = 'G'
1463 pfx = ''
1463 pfx = ''
1464 if self.ui.verbose:
1464 if self.ui.verbose:
1465 pfx = '%*d %s ' % (idxwidth, i, stat)
1465 pfx = '%*d %s ' % (idxwidth, i, stat)
1466 elif status and status != stat:
1466 elif status and status != stat:
1467 continue
1467 continue
1468 displayname(pfx, patch)
1468 displayname(pfx, patch)
1469 else:
1469 else:
1470 msng_list = []
1470 msng_list = []
1471 for root, dirs, files in os.walk(self.path):
1471 for root, dirs, files in os.walk(self.path):
1472 d = root[len(self.path) + 1:]
1472 d = root[len(self.path) + 1:]
1473 for f in files:
1473 for f in files:
1474 fl = os.path.join(d, f)
1474 fl = os.path.join(d, f)
1475 if (fl not in self.series and
1475 if (fl not in self.series and
1476 fl not in (self.status_path, self.series_path,
1476 fl not in (self.status_path, self.series_path,
1477 self.guards_path)
1477 self.guards_path)
1478 and not fl.startswith('.')):
1478 and not fl.startswith('.')):
1479 msng_list.append(fl)
1479 msng_list.append(fl)
1480 for x in sorted(msng_list):
1480 for x in sorted(msng_list):
1481 pfx = self.ui.verbose and ('D ') or ''
1481 pfx = self.ui.verbose and ('D ') or ''
1482 displayname(pfx, x)
1482 displayname(pfx, x)
1483
1483
1484 def issaveline(self, l):
1484 def issaveline(self, l):
1485 if l.name == '.hg.patches.save.line':
1485 if l.name == '.hg.patches.save.line':
1486 return True
1486 return True
1487
1487
1488 def qrepo(self, create=False):
1488 def qrepo(self, create=False):
1489 if create or os.path.isdir(self.join(".hg")):
1489 if create or os.path.isdir(self.join(".hg")):
1490 return hg.repository(self.ui, path=self.path, create=create)
1490 return hg.repository(self.ui, path=self.path, create=create)
1491
1491
1492 def restore(self, repo, rev, delete=None, qupdate=None):
1492 def restore(self, repo, rev, delete=None, qupdate=None):
1493 c = repo.changelog.read(rev)
1493 c = repo.changelog.read(rev)
1494 desc = c[4].strip()
1494 desc = c[4].strip()
1495 lines = desc.splitlines()
1495 lines = desc.splitlines()
1496 i = 0
1496 i = 0
1497 datastart = None
1497 datastart = None
1498 series = []
1498 series = []
1499 applied = []
1499 applied = []
1500 qpp = None
1500 qpp = None
1501 for i, line in enumerate(lines):
1501 for i, line in enumerate(lines):
1502 if line == 'Patch Data:':
1502 if line == 'Patch Data:':
1503 datastart = i + 1
1503 datastart = i + 1
1504 elif line.startswith('Dirstate:'):
1504 elif line.startswith('Dirstate:'):
1505 l = line.rstrip()
1505 l = line.rstrip()
1506 l = l[10:].split(' ')
1506 l = l[10:].split(' ')
1507 qpp = [bin(x) for x in l]
1507 qpp = [bin(x) for x in l]
1508 elif datastart != None:
1508 elif datastart != None:
1509 l = line.rstrip()
1509 l = line.rstrip()
1510 se = statusentry(l)
1510 se = statusentry(l)
1511 file_ = se.name
1511 file_ = se.name
1512 if se.rev:
1512 if se.rev:
1513 applied.append(se)
1513 applied.append(se)
1514 else:
1514 else:
1515 series.append(file_)
1515 series.append(file_)
1516 if datastart is None:
1516 if datastart is None:
1517 self.ui.warn(_("No saved patch data found\n"))
1517 self.ui.warn(_("No saved patch data found\n"))
1518 return 1
1518 return 1
1519 self.ui.warn(_("restoring status: %s\n") % lines[0])
1519 self.ui.warn(_("restoring status: %s\n") % lines[0])
1520 self.full_series = series
1520 self.full_series = series
1521 self.applied = applied
1521 self.applied = applied
1522 self.parse_series()
1522 self.parse_series()
1523 self.series_dirty = 1
1523 self.series_dirty = 1
1524 self.applied_dirty = 1
1524 self.applied_dirty = 1
1525 heads = repo.changelog.heads()
1525 heads = repo.changelog.heads()
1526 if delete:
1526 if delete:
1527 if rev not in heads:
1527 if rev not in heads:
1528 self.ui.warn(_("save entry has children, leaving it alone\n"))
1528 self.ui.warn(_("save entry has children, leaving it alone\n"))
1529 else:
1529 else:
1530 self.ui.warn(_("removing save entry %s\n") % short(rev))
1530 self.ui.warn(_("removing save entry %s\n") % short(rev))
1531 pp = repo.dirstate.parents()
1531 pp = repo.dirstate.parents()
1532 if rev in pp:
1532 if rev in pp:
1533 update = True
1533 update = True
1534 else:
1534 else:
1535 update = False
1535 update = False
1536 self.strip(repo, rev, update=update, backup='strip')
1536 self.strip(repo, rev, update=update, backup='strip')
1537 if qpp:
1537 if qpp:
1538 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1538 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1539 (short(qpp[0]), short(qpp[1])))
1539 (short(qpp[0]), short(qpp[1])))
1540 if qupdate:
1540 if qupdate:
1541 self.ui.status(_("queue directory updating\n"))
1541 self.ui.status(_("queue directory updating\n"))
1542 r = self.qrepo()
1542 r = self.qrepo()
1543 if not r:
1543 if not r:
1544 self.ui.warn(_("Unable to load queue repository\n"))
1544 self.ui.warn(_("Unable to load queue repository\n"))
1545 return 1
1545 return 1
1546 hg.clean(r, qpp[0])
1546 hg.clean(r, qpp[0])
1547
1547
1548 def save(self, repo, msg=None):
1548 def save(self, repo, msg=None):
1549 if len(self.applied) == 0:
1549 if len(self.applied) == 0:
1550 self.ui.warn(_("save: no patches applied, exiting\n"))
1550 self.ui.warn(_("save: no patches applied, exiting\n"))
1551 return 1
1551 return 1
1552 if self.issaveline(self.applied[-1]):
1552 if self.issaveline(self.applied[-1]):
1553 self.ui.warn(_("status is already saved\n"))
1553 self.ui.warn(_("status is already saved\n"))
1554 return 1
1554 return 1
1555
1555
1556 ar = [':' + x for x in self.full_series]
1556 ar = [':' + x for x in self.full_series]
1557 if not msg:
1557 if not msg:
1558 msg = _("hg patches saved state")
1558 msg = _("hg patches saved state")
1559 else:
1559 else:
1560 msg = "hg patches: " + msg.rstrip('\r\n')
1560 msg = "hg patches: " + msg.rstrip('\r\n')
1561 r = self.qrepo()
1561 r = self.qrepo()
1562 if r:
1562 if r:
1563 pp = r.dirstate.parents()
1563 pp = r.dirstate.parents()
1564 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1564 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1565 msg += "\n\nPatch Data:\n"
1565 msg += "\n\nPatch Data:\n"
1566 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1566 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1567 "\n".join(ar) + '\n' or "")
1567 "\n".join(ar) + '\n' or "")
1568 n = repo.commit(text, force=True)
1568 n = repo.commit(text, force=True)
1569 if not n:
1569 if not n:
1570 self.ui.warn(_("repo commit failed\n"))
1570 self.ui.warn(_("repo commit failed\n"))
1571 return 1
1571 return 1
1572 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1572 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1573 self.applied_dirty = 1
1573 self.applied_dirty = 1
1574 self.removeundo(repo)
1574 self.removeundo(repo)
1575
1575
1576 def full_series_end(self):
1576 def full_series_end(self):
1577 if len(self.applied) > 0:
1577 if len(self.applied) > 0:
1578 p = self.applied[-1].name
1578 p = self.applied[-1].name
1579 end = self.find_series(p)
1579 end = self.find_series(p)
1580 if end is None:
1580 if end is None:
1581 return len(self.full_series)
1581 return len(self.full_series)
1582 return end + 1
1582 return end + 1
1583 return 0
1583 return 0
1584
1584
1585 def series_end(self, all_patches=False):
1585 def series_end(self, all_patches=False):
1586 """If all_patches is False, return the index of the next pushable patch
1586 """If all_patches is False, return the index of the next pushable patch
1587 in the series, or the series length. If all_patches is True, return the
1587 in the series, or the series length. If all_patches is True, return the
1588 index of the first patch past the last applied one.
1588 index of the first patch past the last applied one.
1589 """
1589 """
1590 end = 0
1590 end = 0
1591 def next(start):
1591 def next(start):
1592 if all_patches:
1592 if all_patches:
1593 return start
1593 return start
1594 i = start
1594 i = start
1595 while i < len(self.series):
1595 while i < len(self.series):
1596 p, reason = self.pushable(i)
1596 p, reason = self.pushable(i)
1597 if p:
1597 if p:
1598 break
1598 break
1599 self.explain_pushable(i)
1599 self.explain_pushable(i)
1600 i += 1
1600 i += 1
1601 return i
1601 return i
1602 if len(self.applied) > 0:
1602 if len(self.applied) > 0:
1603 p = self.applied[-1].name
1603 p = self.applied[-1].name
1604 try:
1604 try:
1605 end = self.series.index(p)
1605 end = self.series.index(p)
1606 except ValueError:
1606 except ValueError:
1607 return 0
1607 return 0
1608 return next(end + 1)
1608 return next(end + 1)
1609 return next(end)
1609 return next(end)
1610
1610
1611 def appliedname(self, index):
1611 def appliedname(self, index):
1612 pname = self.applied[index].name
1612 pname = self.applied[index].name
1613 if not self.ui.verbose:
1613 if not self.ui.verbose:
1614 p = pname
1614 p = pname
1615 else:
1615 else:
1616 p = str(self.series.index(pname)) + " " + pname
1616 p = str(self.series.index(pname)) + " " + pname
1617 return p
1617 return p
1618
1618
1619 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1619 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1620 force=None, git=False):
1620 force=None, git=False):
1621 def checkseries(patchname):
1621 def checkseries(patchname):
1622 if patchname in self.series:
1622 if patchname in self.series:
1623 raise util.Abort(_('patch %s is already in the series file')
1623 raise util.Abort(_('patch %s is already in the series file')
1624 % patchname)
1624 % patchname)
1625 def checkfile(patchname):
1625 def checkfile(patchname):
1626 if not force and os.path.exists(self.join(patchname)):
1626 if not force and os.path.exists(self.join(patchname)):
1627 raise util.Abort(_('patch "%s" already exists')
1627 raise util.Abort(_('patch "%s" already exists')
1628 % patchname)
1628 % patchname)
1629
1629
1630 if rev:
1630 if rev:
1631 if files:
1631 if files:
1632 raise util.Abort(_('option "-r" not valid when importing '
1632 raise util.Abort(_('option "-r" not valid when importing '
1633 'files'))
1633 'files'))
1634 rev = cmdutil.revrange(repo, rev)
1634 rev = cmdutil.revrange(repo, rev)
1635 rev.sort(reverse=True)
1635 rev.sort(reverse=True)
1636 if (len(files) > 1 or len(rev) > 1) and patchname:
1636 if (len(files) > 1 or len(rev) > 1) and patchname:
1637 raise util.Abort(_('option "-n" not valid when importing multiple '
1637 raise util.Abort(_('option "-n" not valid when importing multiple '
1638 'patches'))
1638 'patches'))
1639 i = 0
1639 i = 0
1640 added = []
1640 added = []
1641 if rev:
1641 if rev:
1642 # If mq patches are applied, we can only import revisions
1642 # If mq patches are applied, we can only import revisions
1643 # that form a linear path to qbase.
1643 # that form a linear path to qbase.
1644 # Otherwise, they should form a linear path to a head.
1644 # Otherwise, they should form a linear path to a head.
1645 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1645 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1646 if len(heads) > 1:
1646 if len(heads) > 1:
1647 raise util.Abort(_('revision %d is the root of more than one '
1647 raise util.Abort(_('revision %d is the root of more than one '
1648 'branch') % rev[-1])
1648 'branch') % rev[-1])
1649 if self.applied:
1649 if self.applied:
1650 base = hex(repo.changelog.node(rev[0]))
1650 base = hex(repo.changelog.node(rev[0]))
1651 if base in [n.rev for n in self.applied]:
1651 if base in [n.rev for n in self.applied]:
1652 raise util.Abort(_('revision %d is already managed')
1652 raise util.Abort(_('revision %d is already managed')
1653 % rev[0])
1653 % rev[0])
1654 if heads != [bin(self.applied[-1].rev)]:
1654 if heads != [bin(self.applied[-1].rev)]:
1655 raise util.Abort(_('revision %d is not the parent of '
1655 raise util.Abort(_('revision %d is not the parent of '
1656 'the queue') % rev[0])
1656 'the queue') % rev[0])
1657 base = repo.changelog.rev(bin(self.applied[0].rev))
1657 base = repo.changelog.rev(bin(self.applied[0].rev))
1658 lastparent = repo.changelog.parentrevs(base)[0]
1658 lastparent = repo.changelog.parentrevs(base)[0]
1659 else:
1659 else:
1660 if heads != [repo.changelog.node(rev[0])]:
1660 if heads != [repo.changelog.node(rev[0])]:
1661 raise util.Abort(_('revision %d has unmanaged children')
1661 raise util.Abort(_('revision %d has unmanaged children')
1662 % rev[0])
1662 % rev[0])
1663 lastparent = None
1663 lastparent = None
1664
1664
1665 diffopts = self.diffopts({'git': git})
1665 diffopts = self.diffopts({'git': git})
1666 for r in rev:
1666 for r in rev:
1667 p1, p2 = repo.changelog.parentrevs(r)
1667 p1, p2 = repo.changelog.parentrevs(r)
1668 n = repo.changelog.node(r)
1668 n = repo.changelog.node(r)
1669 if p2 != nullrev:
1669 if p2 != nullrev:
1670 raise util.Abort(_('cannot import merge revision %d') % r)
1670 raise util.Abort(_('cannot import merge revision %d') % r)
1671 if lastparent and lastparent != r:
1671 if lastparent and lastparent != r:
1672 raise util.Abort(_('revision %d is not the parent of %d')
1672 raise util.Abort(_('revision %d is not the parent of %d')
1673 % (r, lastparent))
1673 % (r, lastparent))
1674 lastparent = p1
1674 lastparent = p1
1675
1675
1676 if not patchname:
1676 if not patchname:
1677 patchname = normname('%d.diff' % r)
1677 patchname = normname('%d.diff' % r)
1678 self.check_reserved_name(patchname)
1678 self.check_reserved_name(patchname)
1679 checkseries(patchname)
1679 checkseries(patchname)
1680 checkfile(patchname)
1680 checkfile(patchname)
1681 self.full_series.insert(0, patchname)
1681 self.full_series.insert(0, patchname)
1682
1682
1683 patchf = self.opener(patchname, "w")
1683 patchf = self.opener(patchname, "w")
1684 patch.export(repo, [n], fp=patchf, opts=diffopts)
1684 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1685 patchf.close()
1685 patchf.close()
1686
1686
1687 se = statusentry(hex(n), patchname)
1687 se = statusentry(hex(n), patchname)
1688 self.applied.insert(0, se)
1688 self.applied.insert(0, se)
1689
1689
1690 added.append(patchname)
1690 added.append(patchname)
1691 patchname = None
1691 patchname = None
1692 self.parse_series()
1692 self.parse_series()
1693 self.applied_dirty = 1
1693 self.applied_dirty = 1
1694
1694
1695 for filename in files:
1695 for filename in files:
1696 if existing:
1696 if existing:
1697 if filename == '-':
1697 if filename == '-':
1698 raise util.Abort(_('-e is incompatible with import from -'))
1698 raise util.Abort(_('-e is incompatible with import from -'))
1699 if not patchname:
1699 if not patchname:
1700 patchname = normname(filename)
1700 patchname = normname(filename)
1701 self.check_reserved_name(patchname)
1701 self.check_reserved_name(patchname)
1702 if not os.path.isfile(self.join(patchname)):
1702 if not os.path.isfile(self.join(patchname)):
1703 raise util.Abort(_("patch %s does not exist") % patchname)
1703 raise util.Abort(_("patch %s does not exist") % patchname)
1704 else:
1704 else:
1705 try:
1705 try:
1706 if filename == '-':
1706 if filename == '-':
1707 if not patchname:
1707 if not patchname:
1708 raise util.Abort(
1708 raise util.Abort(
1709 _('need --name to import a patch from -'))
1709 _('need --name to import a patch from -'))
1710 text = sys.stdin.read()
1710 text = sys.stdin.read()
1711 else:
1711 else:
1712 text = url.open(self.ui, filename).read()
1712 text = url.open(self.ui, filename).read()
1713 except (OSError, IOError):
1713 except (OSError, IOError):
1714 raise util.Abort(_("unable to read %s") % filename)
1714 raise util.Abort(_("unable to read %s") % filename)
1715 if not patchname:
1715 if not patchname:
1716 patchname = normname(os.path.basename(filename))
1716 patchname = normname(os.path.basename(filename))
1717 self.check_reserved_name(patchname)
1717 self.check_reserved_name(patchname)
1718 checkfile(patchname)
1718 checkfile(patchname)
1719 patchf = self.opener(patchname, "w")
1719 patchf = self.opener(patchname, "w")
1720 patchf.write(text)
1720 patchf.write(text)
1721 if not force:
1721 if not force:
1722 checkseries(patchname)
1722 checkseries(patchname)
1723 if patchname not in self.series:
1723 if patchname not in self.series:
1724 index = self.full_series_end() + i
1724 index = self.full_series_end() + i
1725 self.full_series[index:index] = [patchname]
1725 self.full_series[index:index] = [patchname]
1726 self.parse_series()
1726 self.parse_series()
1727 self.ui.warn(_("adding %s to series file\n") % patchname)
1727 self.ui.warn(_("adding %s to series file\n") % patchname)
1728 i += 1
1728 i += 1
1729 added.append(patchname)
1729 added.append(patchname)
1730 patchname = None
1730 patchname = None
1731 self.series_dirty = 1
1731 self.series_dirty = 1
1732 qrepo = self.qrepo()
1732 qrepo = self.qrepo()
1733 if qrepo:
1733 if qrepo:
1734 qrepo.add(added)
1734 qrepo.add(added)
1735
1735
1736 def delete(ui, repo, *patches, **opts):
1736 def delete(ui, repo, *patches, **opts):
1737 """remove patches from queue
1737 """remove patches from queue
1738
1738
1739 The patches must not be applied, and at least one patch is required. With
1739 The patches must not be applied, and at least one patch is required. With
1740 -k/--keep, the patch files are preserved in the patch directory.
1740 -k/--keep, the patch files are preserved in the patch directory.
1741
1741
1742 To stop managing a patch and move it into permanent history,
1742 To stop managing a patch and move it into permanent history,
1743 use the qfinish command."""
1743 use the qfinish command."""
1744 q = repo.mq
1744 q = repo.mq
1745 q.delete(repo, patches, opts)
1745 q.delete(repo, patches, opts)
1746 q.save_dirty()
1746 q.save_dirty()
1747 return 0
1747 return 0
1748
1748
1749 def applied(ui, repo, patch=None, **opts):
1749 def applied(ui, repo, patch=None, **opts):
1750 """print the patches already applied"""
1750 """print the patches already applied"""
1751
1751
1752 q = repo.mq
1752 q = repo.mq
1753 l = len(q.applied)
1753 l = len(q.applied)
1754
1754
1755 if patch:
1755 if patch:
1756 if patch not in q.series:
1756 if patch not in q.series:
1757 raise util.Abort(_("patch %s is not in series file") % patch)
1757 raise util.Abort(_("patch %s is not in series file") % patch)
1758 end = q.series.index(patch) + 1
1758 end = q.series.index(patch) + 1
1759 else:
1759 else:
1760 end = q.series_end(True)
1760 end = q.series_end(True)
1761
1761
1762 if opts.get('last') and not end:
1762 if opts.get('last') and not end:
1763 ui.write(_("no patches applied\n"))
1763 ui.write(_("no patches applied\n"))
1764 return 1
1764 return 1
1765 elif opts.get('last') and end == 1:
1765 elif opts.get('last') and end == 1:
1766 ui.write(_("only one patch applied\n"))
1766 ui.write(_("only one patch applied\n"))
1767 return 1
1767 return 1
1768 elif opts.get('last'):
1768 elif opts.get('last'):
1769 start = end - 2
1769 start = end - 2
1770 end = 1
1770 end = 1
1771 else:
1771 else:
1772 start = 0
1772 start = 0
1773
1773
1774 return q.qseries(repo, length=end, start=start, status='A',
1774 return q.qseries(repo, length=end, start=start, status='A',
1775 summary=opts.get('summary'))
1775 summary=opts.get('summary'))
1776
1776
1777 def unapplied(ui, repo, patch=None, **opts):
1777 def unapplied(ui, repo, patch=None, **opts):
1778 """print the patches not yet applied"""
1778 """print the patches not yet applied"""
1779
1779
1780 q = repo.mq
1780 q = repo.mq
1781 if patch:
1781 if patch:
1782 if patch not in q.series:
1782 if patch not in q.series:
1783 raise util.Abort(_("patch %s is not in series file") % patch)
1783 raise util.Abort(_("patch %s is not in series file") % patch)
1784 start = q.series.index(patch) + 1
1784 start = q.series.index(patch) + 1
1785 else:
1785 else:
1786 start = q.series_end(True)
1786 start = q.series_end(True)
1787
1787
1788 if start == len(q.series) and opts.get('first'):
1788 if start == len(q.series) and opts.get('first'):
1789 ui.write(_("all patches applied\n"))
1789 ui.write(_("all patches applied\n"))
1790 return 1
1790 return 1
1791
1791
1792 length = opts.get('first') and 1 or None
1792 length = opts.get('first') and 1 or None
1793 return q.qseries(repo, start=start, length=length, status='U',
1793 return q.qseries(repo, start=start, length=length, status='U',
1794 summary=opts.get('summary'))
1794 summary=opts.get('summary'))
1795
1795
1796 def qimport(ui, repo, *filename, **opts):
1796 def qimport(ui, repo, *filename, **opts):
1797 """import a patch
1797 """import a patch
1798
1798
1799 The patch is inserted into the series after the last applied
1799 The patch is inserted into the series after the last applied
1800 patch. If no patches have been applied, qimport prepends the patch
1800 patch. If no patches have been applied, qimport prepends the patch
1801 to the series.
1801 to the series.
1802
1802
1803 The patch will have the same name as its source file unless you
1803 The patch will have the same name as its source file unless you
1804 give it a new one with -n/--name.
1804 give it a new one with -n/--name.
1805
1805
1806 You can register an existing patch inside the patch directory with
1806 You can register an existing patch inside the patch directory with
1807 the -e/--existing flag.
1807 the -e/--existing flag.
1808
1808
1809 With -f/--force, an existing patch of the same name will be
1809 With -f/--force, an existing patch of the same name will be
1810 overwritten.
1810 overwritten.
1811
1811
1812 An existing changeset may be placed under mq control with -r/--rev
1812 An existing changeset may be placed under mq control with -r/--rev
1813 (e.g. qimport --rev tip -n patch will place tip under mq control).
1813 (e.g. qimport --rev tip -n patch will place tip under mq control).
1814 With -g/--git, patches imported with --rev will use the git diff
1814 With -g/--git, patches imported with --rev will use the git diff
1815 format. See the diffs help topic for information on why this is
1815 format. See the diffs help topic for information on why this is
1816 important for preserving rename/copy information and permission
1816 important for preserving rename/copy information and permission
1817 changes.
1817 changes.
1818
1818
1819 To import a patch from standard input, pass - as the patch file.
1819 To import a patch from standard input, pass - as the patch file.
1820 When importing from standard input, a patch name must be specified
1820 When importing from standard input, a patch name must be specified
1821 using the --name flag.
1821 using the --name flag.
1822 """
1822 """
1823 q = repo.mq
1823 q = repo.mq
1824 q.qimport(repo, filename, patchname=opts['name'],
1824 q.qimport(repo, filename, patchname=opts['name'],
1825 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1825 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1826 git=opts['git'])
1826 git=opts['git'])
1827 q.save_dirty()
1827 q.save_dirty()
1828
1828
1829 if opts.get('push') and not opts.get('rev'):
1829 if opts.get('push') and not opts.get('rev'):
1830 return q.push(repo, None)
1830 return q.push(repo, None)
1831 return 0
1831 return 0
1832
1832
1833 def qinit(ui, repo, create):
1833 def qinit(ui, repo, create):
1834 """initialize a new queue repository
1834 """initialize a new queue repository
1835
1835
1836 This command also creates a series file for ordering patches, and
1836 This command also creates a series file for ordering patches, and
1837 an mq-specific .hgignore file in the queue repository, to exclude
1837 an mq-specific .hgignore file in the queue repository, to exclude
1838 the status and guards files (these contain mostly transient state)."""
1838 the status and guards files (these contain mostly transient state)."""
1839 q = repo.mq
1839 q = repo.mq
1840 r = q.init(repo, create)
1840 r = q.init(repo, create)
1841 q.save_dirty()
1841 q.save_dirty()
1842 if r:
1842 if r:
1843 if not os.path.exists(r.wjoin('.hgignore')):
1843 if not os.path.exists(r.wjoin('.hgignore')):
1844 fp = r.wopener('.hgignore', 'w')
1844 fp = r.wopener('.hgignore', 'w')
1845 fp.write('^\\.hg\n')
1845 fp.write('^\\.hg\n')
1846 fp.write('^\\.mq\n')
1846 fp.write('^\\.mq\n')
1847 fp.write('syntax: glob\n')
1847 fp.write('syntax: glob\n')
1848 fp.write('status\n')
1848 fp.write('status\n')
1849 fp.write('guards\n')
1849 fp.write('guards\n')
1850 fp.close()
1850 fp.close()
1851 if not os.path.exists(r.wjoin('series')):
1851 if not os.path.exists(r.wjoin('series')):
1852 r.wopener('series', 'w').close()
1852 r.wopener('series', 'w').close()
1853 r.add(['.hgignore', 'series'])
1853 r.add(['.hgignore', 'series'])
1854 commands.add(ui, r)
1854 commands.add(ui, r)
1855 return 0
1855 return 0
1856
1856
1857 def init(ui, repo, **opts):
1857 def init(ui, repo, **opts):
1858 """init a new queue repository (DEPRECATED)
1858 """init a new queue repository (DEPRECATED)
1859
1859
1860 The queue repository is unversioned by default. If
1860 The queue repository is unversioned by default. If
1861 -c/--create-repo is specified, qinit will create a separate nested
1861 -c/--create-repo is specified, qinit will create a separate nested
1862 repository for patches (qinit -c may also be run later to convert
1862 repository for patches (qinit -c may also be run later to convert
1863 an unversioned patch repository into a versioned one). You can use
1863 an unversioned patch repository into a versioned one). You can use
1864 qcommit to commit changes to this queue repository.
1864 qcommit to commit changes to this queue repository.
1865
1865
1866 This command is deprecated. Without -c, it's implied by other relevant
1866 This command is deprecated. Without -c, it's implied by other relevant
1867 commands. With -c, use hg init --mq instead."""
1867 commands. With -c, use hg init --mq instead."""
1868 return qinit(ui, repo, create=opts['create_repo'])
1868 return qinit(ui, repo, create=opts['create_repo'])
1869
1869
1870 def clone(ui, source, dest=None, **opts):
1870 def clone(ui, source, dest=None, **opts):
1871 '''clone main and patch repository at same time
1871 '''clone main and patch repository at same time
1872
1872
1873 If source is local, destination will have no patches applied. If
1873 If source is local, destination will have no patches applied. If
1874 source is remote, this command can not check if patches are
1874 source is remote, this command can not check if patches are
1875 applied in source, so cannot guarantee that patches are not
1875 applied in source, so cannot guarantee that patches are not
1876 applied in destination. If you clone remote repository, be sure
1876 applied in destination. If you clone remote repository, be sure
1877 before that it has no patches applied.
1877 before that it has no patches applied.
1878
1878
1879 Source patch repository is looked for in <src>/.hg/patches by
1879 Source patch repository is looked for in <src>/.hg/patches by
1880 default. Use -p <url> to change.
1880 default. Use -p <url> to change.
1881
1881
1882 The patch directory must be a nested Mercurial repository, as
1882 The patch directory must be a nested Mercurial repository, as
1883 would be created by qinit -c.
1883 would be created by qinit -c.
1884 '''
1884 '''
1885 def patchdir(repo):
1885 def patchdir(repo):
1886 url = repo.url()
1886 url = repo.url()
1887 if url.endswith('/'):
1887 if url.endswith('/'):
1888 url = url[:-1]
1888 url = url[:-1]
1889 return url + '/.hg/patches'
1889 return url + '/.hg/patches'
1890 if dest is None:
1890 if dest is None:
1891 dest = hg.defaultdest(source)
1891 dest = hg.defaultdest(source)
1892 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1892 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1893 if opts['patches']:
1893 if opts['patches']:
1894 patchespath = ui.expandpath(opts['patches'])
1894 patchespath = ui.expandpath(opts['patches'])
1895 else:
1895 else:
1896 patchespath = patchdir(sr)
1896 patchespath = patchdir(sr)
1897 try:
1897 try:
1898 hg.repository(ui, patchespath)
1898 hg.repository(ui, patchespath)
1899 except error.RepoError:
1899 except error.RepoError:
1900 raise util.Abort(_('versioned patch repository not found'
1900 raise util.Abort(_('versioned patch repository not found'
1901 ' (see qinit -c)'))
1901 ' (see qinit -c)'))
1902 qbase, destrev = None, None
1902 qbase, destrev = None, None
1903 if sr.local():
1903 if sr.local():
1904 if sr.mq.applied:
1904 if sr.mq.applied:
1905 qbase = bin(sr.mq.applied[0].rev)
1905 qbase = bin(sr.mq.applied[0].rev)
1906 if not hg.islocal(dest):
1906 if not hg.islocal(dest):
1907 heads = set(sr.heads())
1907 heads = set(sr.heads())
1908 destrev = list(heads.difference(sr.heads(qbase)))
1908 destrev = list(heads.difference(sr.heads(qbase)))
1909 destrev.append(sr.changelog.parents(qbase)[0])
1909 destrev.append(sr.changelog.parents(qbase)[0])
1910 elif sr.capable('lookup'):
1910 elif sr.capable('lookup'):
1911 try:
1911 try:
1912 qbase = sr.lookup('qbase')
1912 qbase = sr.lookup('qbase')
1913 except error.RepoError:
1913 except error.RepoError:
1914 pass
1914 pass
1915 ui.note(_('cloning main repository\n'))
1915 ui.note(_('cloning main repository\n'))
1916 sr, dr = hg.clone(ui, sr.url(), dest,
1916 sr, dr = hg.clone(ui, sr.url(), dest,
1917 pull=opts['pull'],
1917 pull=opts['pull'],
1918 rev=destrev,
1918 rev=destrev,
1919 update=False,
1919 update=False,
1920 stream=opts['uncompressed'])
1920 stream=opts['uncompressed'])
1921 ui.note(_('cloning patch repository\n'))
1921 ui.note(_('cloning patch repository\n'))
1922 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1922 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1923 pull=opts['pull'], update=not opts['noupdate'],
1923 pull=opts['pull'], update=not opts['noupdate'],
1924 stream=opts['uncompressed'])
1924 stream=opts['uncompressed'])
1925 if dr.local():
1925 if dr.local():
1926 if qbase:
1926 if qbase:
1927 ui.note(_('stripping applied patches from destination '
1927 ui.note(_('stripping applied patches from destination '
1928 'repository\n'))
1928 'repository\n'))
1929 dr.mq.strip(dr, qbase, update=False, backup=None)
1929 dr.mq.strip(dr, qbase, update=False, backup=None)
1930 if not opts['noupdate']:
1930 if not opts['noupdate']:
1931 ui.note(_('updating destination repository\n'))
1931 ui.note(_('updating destination repository\n'))
1932 hg.update(dr, dr.changelog.tip())
1932 hg.update(dr, dr.changelog.tip())
1933
1933
1934 def commit(ui, repo, *pats, **opts):
1934 def commit(ui, repo, *pats, **opts):
1935 """commit changes in the queue repository (DEPRECATED)
1935 """commit changes in the queue repository (DEPRECATED)
1936
1936
1937 This command is deprecated; use hg --mq commit instead."""
1937 This command is deprecated; use hg --mq commit instead."""
1938 q = repo.mq
1938 q = repo.mq
1939 r = q.qrepo()
1939 r = q.qrepo()
1940 if not r:
1940 if not r:
1941 raise util.Abort('no queue repository')
1941 raise util.Abort('no queue repository')
1942 commands.commit(r.ui, r, *pats, **opts)
1942 commands.commit(r.ui, r, *pats, **opts)
1943
1943
1944 def series(ui, repo, **opts):
1944 def series(ui, repo, **opts):
1945 """print the entire series file"""
1945 """print the entire series file"""
1946 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1946 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1947 return 0
1947 return 0
1948
1948
1949 def top(ui, repo, **opts):
1949 def top(ui, repo, **opts):
1950 """print the name of the current patch"""
1950 """print the name of the current patch"""
1951 q = repo.mq
1951 q = repo.mq
1952 t = q.applied and q.series_end(True) or 0
1952 t = q.applied and q.series_end(True) or 0
1953 if t:
1953 if t:
1954 return q.qseries(repo, start=t - 1, length=1, status='A',
1954 return q.qseries(repo, start=t - 1, length=1, status='A',
1955 summary=opts.get('summary'))
1955 summary=opts.get('summary'))
1956 else:
1956 else:
1957 ui.write(_("no patches applied\n"))
1957 ui.write(_("no patches applied\n"))
1958 return 1
1958 return 1
1959
1959
1960 def next(ui, repo, **opts):
1960 def next(ui, repo, **opts):
1961 """print the name of the next patch"""
1961 """print the name of the next patch"""
1962 q = repo.mq
1962 q = repo.mq
1963 end = q.series_end()
1963 end = q.series_end()
1964 if end == len(q.series):
1964 if end == len(q.series):
1965 ui.write(_("all patches applied\n"))
1965 ui.write(_("all patches applied\n"))
1966 return 1
1966 return 1
1967 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1967 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1968
1968
1969 def prev(ui, repo, **opts):
1969 def prev(ui, repo, **opts):
1970 """print the name of the previous patch"""
1970 """print the name of the previous patch"""
1971 q = repo.mq
1971 q = repo.mq
1972 l = len(q.applied)
1972 l = len(q.applied)
1973 if l == 1:
1973 if l == 1:
1974 ui.write(_("only one patch applied\n"))
1974 ui.write(_("only one patch applied\n"))
1975 return 1
1975 return 1
1976 if not l:
1976 if not l:
1977 ui.write(_("no patches applied\n"))
1977 ui.write(_("no patches applied\n"))
1978 return 1
1978 return 1
1979 return q.qseries(repo, start=l - 2, length=1, status='A',
1979 return q.qseries(repo, start=l - 2, length=1, status='A',
1980 summary=opts.get('summary'))
1980 summary=opts.get('summary'))
1981
1981
1982 def setupheaderopts(ui, opts):
1982 def setupheaderopts(ui, opts):
1983 if not opts.get('user') and opts.get('currentuser'):
1983 if not opts.get('user') and opts.get('currentuser'):
1984 opts['user'] = ui.username()
1984 opts['user'] = ui.username()
1985 if not opts.get('date') and opts.get('currentdate'):
1985 if not opts.get('date') and opts.get('currentdate'):
1986 opts['date'] = "%d %d" % util.makedate()
1986 opts['date'] = "%d %d" % util.makedate()
1987
1987
1988 def new(ui, repo, patch, *args, **opts):
1988 def new(ui, repo, patch, *args, **opts):
1989 """create a new patch
1989 """create a new patch
1990
1990
1991 qnew creates a new patch on top of the currently-applied patch (if
1991 qnew creates a new patch on top of the currently-applied patch (if
1992 any). It will refuse to run if there are any outstanding changes
1992 any). It will refuse to run if there are any outstanding changes
1993 unless -f/--force is specified, in which case the patch will be
1993 unless -f/--force is specified, in which case the patch will be
1994 initialized with them. You may also use -I/--include,
1994 initialized with them. You may also use -I/--include,
1995 -X/--exclude, and/or a list of files after the patch name to add
1995 -X/--exclude, and/or a list of files after the patch name to add
1996 only changes to matching files to the new patch, leaving the rest
1996 only changes to matching files to the new patch, leaving the rest
1997 as uncommitted modifications.
1997 as uncommitted modifications.
1998
1998
1999 -u/--user and -d/--date can be used to set the (given) user and
1999 -u/--user and -d/--date can be used to set the (given) user and
2000 date, respectively. -U/--currentuser and -D/--currentdate set user
2000 date, respectively. -U/--currentuser and -D/--currentdate set user
2001 to current user and date to current date.
2001 to current user and date to current date.
2002
2002
2003 -e/--edit, -m/--message or -l/--logfile set the patch header as
2003 -e/--edit, -m/--message or -l/--logfile set the patch header as
2004 well as the commit message. If none is specified, the header is
2004 well as the commit message. If none is specified, the header is
2005 empty and the commit message is '[mq]: PATCH'.
2005 empty and the commit message is '[mq]: PATCH'.
2006
2006
2007 Use the -g/--git option to keep the patch in the git extended diff
2007 Use the -g/--git option to keep the patch in the git extended diff
2008 format. Read the diffs help topic for more information on why this
2008 format. Read the diffs help topic for more information on why this
2009 is important for preserving permission changes and copy/rename
2009 is important for preserving permission changes and copy/rename
2010 information.
2010 information.
2011 """
2011 """
2012 msg = cmdutil.logmessage(opts)
2012 msg = cmdutil.logmessage(opts)
2013 def getmsg():
2013 def getmsg():
2014 return ui.edit(msg, ui.username())
2014 return ui.edit(msg, ui.username())
2015 q = repo.mq
2015 q = repo.mq
2016 opts['msg'] = msg
2016 opts['msg'] = msg
2017 if opts.get('edit'):
2017 if opts.get('edit'):
2018 opts['msg'] = getmsg
2018 opts['msg'] = getmsg
2019 else:
2019 else:
2020 opts['msg'] = msg
2020 opts['msg'] = msg
2021 setupheaderopts(ui, opts)
2021 setupheaderopts(ui, opts)
2022 q.new(repo, patch, *args, **opts)
2022 q.new(repo, patch, *args, **opts)
2023 q.save_dirty()
2023 q.save_dirty()
2024 return 0
2024 return 0
2025
2025
2026 def refresh(ui, repo, *pats, **opts):
2026 def refresh(ui, repo, *pats, **opts):
2027 """update the current patch
2027 """update the current patch
2028
2028
2029 If any file patterns are provided, the refreshed patch will
2029 If any file patterns are provided, the refreshed patch will
2030 contain only the modifications that match those patterns; the
2030 contain only the modifications that match those patterns; the
2031 remaining modifications will remain in the working directory.
2031 remaining modifications will remain in the working directory.
2032
2032
2033 If -s/--short is specified, files currently included in the patch
2033 If -s/--short is specified, files currently included in the patch
2034 will be refreshed just like matched files and remain in the patch.
2034 will be refreshed just like matched files and remain in the patch.
2035
2035
2036 hg add/remove/copy/rename work as usual, though you might want to
2036 hg add/remove/copy/rename work as usual, though you might want to
2037 use git-style patches (-g/--git or [diff] git=1) to track copies
2037 use git-style patches (-g/--git or [diff] git=1) to track copies
2038 and renames. See the diffs help topic for more information on the
2038 and renames. See the diffs help topic for more information on the
2039 git diff format.
2039 git diff format.
2040 """
2040 """
2041 q = repo.mq
2041 q = repo.mq
2042 message = cmdutil.logmessage(opts)
2042 message = cmdutil.logmessage(opts)
2043 if opts['edit']:
2043 if opts['edit']:
2044 if not q.applied:
2044 if not q.applied:
2045 ui.write(_("no patches applied\n"))
2045 ui.write(_("no patches applied\n"))
2046 return 1
2046 return 1
2047 if message:
2047 if message:
2048 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2048 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2049 patch = q.applied[-1].name
2049 patch = q.applied[-1].name
2050 ph = patchheader(q.join(patch), q.plainmode)
2050 ph = patchheader(q.join(patch), q.plainmode)
2051 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2051 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2052 setupheaderopts(ui, opts)
2052 setupheaderopts(ui, opts)
2053 ret = q.refresh(repo, pats, msg=message, **opts)
2053 ret = q.refresh(repo, pats, msg=message, **opts)
2054 q.save_dirty()
2054 q.save_dirty()
2055 return ret
2055 return ret
2056
2056
2057 def diff(ui, repo, *pats, **opts):
2057 def diff(ui, repo, *pats, **opts):
2058 """diff of the current patch and subsequent modifications
2058 """diff of the current patch and subsequent modifications
2059
2059
2060 Shows a diff which includes the current patch as well as any
2060 Shows a diff which includes the current patch as well as any
2061 changes which have been made in the working directory since the
2061 changes which have been made in the working directory since the
2062 last refresh (thus showing what the current patch would become
2062 last refresh (thus showing what the current patch would become
2063 after a qrefresh).
2063 after a qrefresh).
2064
2064
2065 Use 'hg diff' if you only want to see the changes made since the
2065 Use 'hg diff' if you only want to see the changes made since the
2066 last qrefresh, or 'hg export qtip' if you want to see changes made
2066 last qrefresh, or 'hg export qtip' if you want to see changes made
2067 by the current patch without including changes made since the
2067 by the current patch without including changes made since the
2068 qrefresh.
2068 qrefresh.
2069 """
2069 """
2070 repo.mq.diff(repo, pats, opts)
2070 repo.mq.diff(repo, pats, opts)
2071 return 0
2071 return 0
2072
2072
2073 def fold(ui, repo, *files, **opts):
2073 def fold(ui, repo, *files, **opts):
2074 """fold the named patches into the current patch
2074 """fold the named patches into the current patch
2075
2075
2076 Patches must not yet be applied. Each patch will be successively
2076 Patches must not yet be applied. Each patch will be successively
2077 applied to the current patch in the order given. If all the
2077 applied to the current patch in the order given. If all the
2078 patches apply successfully, the current patch will be refreshed
2078 patches apply successfully, the current patch will be refreshed
2079 with the new cumulative patch, and the folded patches will be
2079 with the new cumulative patch, and the folded patches will be
2080 deleted. With -k/--keep, the folded patch files will not be
2080 deleted. With -k/--keep, the folded patch files will not be
2081 removed afterwards.
2081 removed afterwards.
2082
2082
2083 The header for each folded patch will be concatenated with the
2083 The header for each folded patch will be concatenated with the
2084 current patch header, separated by a line of '* * *'."""
2084 current patch header, separated by a line of '* * *'."""
2085
2085
2086 q = repo.mq
2086 q = repo.mq
2087
2087
2088 if not files:
2088 if not files:
2089 raise util.Abort(_('qfold requires at least one patch name'))
2089 raise util.Abort(_('qfold requires at least one patch name'))
2090 if not q.check_toppatch(repo)[0]:
2090 if not q.check_toppatch(repo)[0]:
2091 raise util.Abort(_('No patches applied'))
2091 raise util.Abort(_('No patches applied'))
2092 q.check_localchanges(repo)
2092 q.check_localchanges(repo)
2093
2093
2094 message = cmdutil.logmessage(opts)
2094 message = cmdutil.logmessage(opts)
2095 if opts['edit']:
2095 if opts['edit']:
2096 if message:
2096 if message:
2097 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2097 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2098
2098
2099 parent = q.lookup('qtip')
2099 parent = q.lookup('qtip')
2100 patches = []
2100 patches = []
2101 messages = []
2101 messages = []
2102 for f in files:
2102 for f in files:
2103 p = q.lookup(f)
2103 p = q.lookup(f)
2104 if p in patches or p == parent:
2104 if p in patches or p == parent:
2105 ui.warn(_('Skipping already folded patch %s') % p)
2105 ui.warn(_('Skipping already folded patch %s') % p)
2106 if q.isapplied(p):
2106 if q.isapplied(p):
2107 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2107 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2108 patches.append(p)
2108 patches.append(p)
2109
2109
2110 for p in patches:
2110 for p in patches:
2111 if not message:
2111 if not message:
2112 ph = patchheader(q.join(p), q.plainmode)
2112 ph = patchheader(q.join(p), q.plainmode)
2113 if ph.message:
2113 if ph.message:
2114 messages.append(ph.message)
2114 messages.append(ph.message)
2115 pf = q.join(p)
2115 pf = q.join(p)
2116 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2116 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2117 if not patchsuccess:
2117 if not patchsuccess:
2118 raise util.Abort(_('Error folding patch %s') % p)
2118 raise util.Abort(_('Error folding patch %s') % p)
2119 patch.updatedir(ui, repo, files)
2119 patch.updatedir(ui, repo, files)
2120
2120
2121 if not message:
2121 if not message:
2122 ph = patchheader(q.join(parent), q.plainmode)
2122 ph = patchheader(q.join(parent), q.plainmode)
2123 message, user = ph.message, ph.user
2123 message, user = ph.message, ph.user
2124 for msg in messages:
2124 for msg in messages:
2125 message.append('* * *')
2125 message.append('* * *')
2126 message.extend(msg)
2126 message.extend(msg)
2127 message = '\n'.join(message)
2127 message = '\n'.join(message)
2128
2128
2129 if opts['edit']:
2129 if opts['edit']:
2130 message = ui.edit(message, user or ui.username())
2130 message = ui.edit(message, user or ui.username())
2131
2131
2132 diffopts = q.patchopts(q.diffopts(), *patches)
2132 diffopts = q.patchopts(q.diffopts(), *patches)
2133 q.refresh(repo, msg=message, git=diffopts.git)
2133 q.refresh(repo, msg=message, git=diffopts.git)
2134 q.delete(repo, patches, opts)
2134 q.delete(repo, patches, opts)
2135 q.save_dirty()
2135 q.save_dirty()
2136
2136
2137 def goto(ui, repo, patch, **opts):
2137 def goto(ui, repo, patch, **opts):
2138 '''push or pop patches until named patch is at top of stack'''
2138 '''push or pop patches until named patch is at top of stack'''
2139 q = repo.mq
2139 q = repo.mq
2140 patch = q.lookup(patch)
2140 patch = q.lookup(patch)
2141 if q.isapplied(patch):
2141 if q.isapplied(patch):
2142 ret = q.pop(repo, patch, force=opts['force'])
2142 ret = q.pop(repo, patch, force=opts['force'])
2143 else:
2143 else:
2144 ret = q.push(repo, patch, force=opts['force'])
2144 ret = q.push(repo, patch, force=opts['force'])
2145 q.save_dirty()
2145 q.save_dirty()
2146 return ret
2146 return ret
2147
2147
2148 def guard(ui, repo, *args, **opts):
2148 def guard(ui, repo, *args, **opts):
2149 '''set or print guards for a patch
2149 '''set or print guards for a patch
2150
2150
2151 Guards control whether a patch can be pushed. A patch with no
2151 Guards control whether a patch can be pushed. A patch with no
2152 guards is always pushed. A patch with a positive guard ("+foo") is
2152 guards is always pushed. A patch with a positive guard ("+foo") is
2153 pushed only if the qselect command has activated it. A patch with
2153 pushed only if the qselect command has activated it. A patch with
2154 a negative guard ("-foo") is never pushed if the qselect command
2154 a negative guard ("-foo") is never pushed if the qselect command
2155 has activated it.
2155 has activated it.
2156
2156
2157 With no arguments, print the currently active guards.
2157 With no arguments, print the currently active guards.
2158 With arguments, set guards for the named patch.
2158 With arguments, set guards for the named patch.
2159 NOTE: Specifying negative guards now requires '--'.
2159 NOTE: Specifying negative guards now requires '--'.
2160
2160
2161 To set guards on another patch::
2161 To set guards on another patch::
2162
2162
2163 hg qguard other.patch -- +2.6.17 -stable
2163 hg qguard other.patch -- +2.6.17 -stable
2164 '''
2164 '''
2165 def status(idx):
2165 def status(idx):
2166 guards = q.series_guards[idx] or ['unguarded']
2166 guards = q.series_guards[idx] or ['unguarded']
2167 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2167 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2168 q = repo.mq
2168 q = repo.mq
2169 patch = None
2169 patch = None
2170 args = list(args)
2170 args = list(args)
2171 if opts['list']:
2171 if opts['list']:
2172 if args or opts['none']:
2172 if args or opts['none']:
2173 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2173 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2174 for i in xrange(len(q.series)):
2174 for i in xrange(len(q.series)):
2175 status(i)
2175 status(i)
2176 return
2176 return
2177 if not args or args[0][0:1] in '-+':
2177 if not args or args[0][0:1] in '-+':
2178 if not q.applied:
2178 if not q.applied:
2179 raise util.Abort(_('no patches applied'))
2179 raise util.Abort(_('no patches applied'))
2180 patch = q.applied[-1].name
2180 patch = q.applied[-1].name
2181 if patch is None and args[0][0:1] not in '-+':
2181 if patch is None and args[0][0:1] not in '-+':
2182 patch = args.pop(0)
2182 patch = args.pop(0)
2183 if patch is None:
2183 if patch is None:
2184 raise util.Abort(_('no patch to work with'))
2184 raise util.Abort(_('no patch to work with'))
2185 if args or opts['none']:
2185 if args or opts['none']:
2186 idx = q.find_series(patch)
2186 idx = q.find_series(patch)
2187 if idx is None:
2187 if idx is None:
2188 raise util.Abort(_('no patch named %s') % patch)
2188 raise util.Abort(_('no patch named %s') % patch)
2189 q.set_guards(idx, args)
2189 q.set_guards(idx, args)
2190 q.save_dirty()
2190 q.save_dirty()
2191 else:
2191 else:
2192 status(q.series.index(q.lookup(patch)))
2192 status(q.series.index(q.lookup(patch)))
2193
2193
2194 def header(ui, repo, patch=None):
2194 def header(ui, repo, patch=None):
2195 """print the header of the topmost or specified patch"""
2195 """print the header of the topmost or specified patch"""
2196 q = repo.mq
2196 q = repo.mq
2197
2197
2198 if patch:
2198 if patch:
2199 patch = q.lookup(patch)
2199 patch = q.lookup(patch)
2200 else:
2200 else:
2201 if not q.applied:
2201 if not q.applied:
2202 ui.write(_('no patches applied\n'))
2202 ui.write(_('no patches applied\n'))
2203 return 1
2203 return 1
2204 patch = q.lookup('qtip')
2204 patch = q.lookup('qtip')
2205 ph = patchheader(q.join(patch), q.plainmode)
2205 ph = patchheader(q.join(patch), q.plainmode)
2206
2206
2207 ui.write('\n'.join(ph.message) + '\n')
2207 ui.write('\n'.join(ph.message) + '\n')
2208
2208
2209 def lastsavename(path):
2209 def lastsavename(path):
2210 (directory, base) = os.path.split(path)
2210 (directory, base) = os.path.split(path)
2211 names = os.listdir(directory)
2211 names = os.listdir(directory)
2212 namere = re.compile("%s.([0-9]+)" % base)
2212 namere = re.compile("%s.([0-9]+)" % base)
2213 maxindex = None
2213 maxindex = None
2214 maxname = None
2214 maxname = None
2215 for f in names:
2215 for f in names:
2216 m = namere.match(f)
2216 m = namere.match(f)
2217 if m:
2217 if m:
2218 index = int(m.group(1))
2218 index = int(m.group(1))
2219 if maxindex is None or index > maxindex:
2219 if maxindex is None or index > maxindex:
2220 maxindex = index
2220 maxindex = index
2221 maxname = f
2221 maxname = f
2222 if maxname:
2222 if maxname:
2223 return (os.path.join(directory, maxname), maxindex)
2223 return (os.path.join(directory, maxname), maxindex)
2224 return (None, None)
2224 return (None, None)
2225
2225
2226 def savename(path):
2226 def savename(path):
2227 (last, index) = lastsavename(path)
2227 (last, index) = lastsavename(path)
2228 if last is None:
2228 if last is None:
2229 index = 0
2229 index = 0
2230 newpath = path + ".%d" % (index + 1)
2230 newpath = path + ".%d" % (index + 1)
2231 return newpath
2231 return newpath
2232
2232
2233 def push(ui, repo, patch=None, **opts):
2233 def push(ui, repo, patch=None, **opts):
2234 """push the next patch onto the stack
2234 """push the next patch onto the stack
2235
2235
2236 When -f/--force is applied, all local changes in patched files
2236 When -f/--force is applied, all local changes in patched files
2237 will be lost.
2237 will be lost.
2238 """
2238 """
2239 q = repo.mq
2239 q = repo.mq
2240 mergeq = None
2240 mergeq = None
2241
2241
2242 if opts['merge']:
2242 if opts['merge']:
2243 if opts['name']:
2243 if opts['name']:
2244 newpath = repo.join(opts['name'])
2244 newpath = repo.join(opts['name'])
2245 else:
2245 else:
2246 newpath, i = lastsavename(q.path)
2246 newpath, i = lastsavename(q.path)
2247 if not newpath:
2247 if not newpath:
2248 ui.warn(_("no saved queues found, please use -n\n"))
2248 ui.warn(_("no saved queues found, please use -n\n"))
2249 return 1
2249 return 1
2250 mergeq = queue(ui, repo.join(""), newpath)
2250 mergeq = queue(ui, repo.join(""), newpath)
2251 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2251 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2252 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2252 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2253 mergeq=mergeq, all=opts.get('all'))
2253 mergeq=mergeq, all=opts.get('all'))
2254 return ret
2254 return ret
2255
2255
2256 def pop(ui, repo, patch=None, **opts):
2256 def pop(ui, repo, patch=None, **opts):
2257 """pop the current patch off the stack
2257 """pop the current patch off the stack
2258
2258
2259 By default, pops off the top of the patch stack. If given a patch
2259 By default, pops off the top of the patch stack. If given a patch
2260 name, keeps popping off patches until the named patch is at the
2260 name, keeps popping off patches until the named patch is at the
2261 top of the stack.
2261 top of the stack.
2262 """
2262 """
2263 localupdate = True
2263 localupdate = True
2264 if opts['name']:
2264 if opts['name']:
2265 q = queue(ui, repo.join(""), repo.join(opts['name']))
2265 q = queue(ui, repo.join(""), repo.join(opts['name']))
2266 ui.warn(_('using patch queue: %s\n') % q.path)
2266 ui.warn(_('using patch queue: %s\n') % q.path)
2267 localupdate = False
2267 localupdate = False
2268 else:
2268 else:
2269 q = repo.mq
2269 q = repo.mq
2270 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2270 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2271 all=opts['all'])
2271 all=opts['all'])
2272 q.save_dirty()
2272 q.save_dirty()
2273 return ret
2273 return ret
2274
2274
2275 def rename(ui, repo, patch, name=None, **opts):
2275 def rename(ui, repo, patch, name=None, **opts):
2276 """rename a patch
2276 """rename a patch
2277
2277
2278 With one argument, renames the current patch to PATCH1.
2278 With one argument, renames the current patch to PATCH1.
2279 With two arguments, renames PATCH1 to PATCH2."""
2279 With two arguments, renames PATCH1 to PATCH2."""
2280
2280
2281 q = repo.mq
2281 q = repo.mq
2282
2282
2283 if not name:
2283 if not name:
2284 name = patch
2284 name = patch
2285 patch = None
2285 patch = None
2286
2286
2287 if patch:
2287 if patch:
2288 patch = q.lookup(patch)
2288 patch = q.lookup(patch)
2289 else:
2289 else:
2290 if not q.applied:
2290 if not q.applied:
2291 ui.write(_('no patches applied\n'))
2291 ui.write(_('no patches applied\n'))
2292 return
2292 return
2293 patch = q.lookup('qtip')
2293 patch = q.lookup('qtip')
2294 absdest = q.join(name)
2294 absdest = q.join(name)
2295 if os.path.isdir(absdest):
2295 if os.path.isdir(absdest):
2296 name = normname(os.path.join(name, os.path.basename(patch)))
2296 name = normname(os.path.join(name, os.path.basename(patch)))
2297 absdest = q.join(name)
2297 absdest = q.join(name)
2298 if os.path.exists(absdest):
2298 if os.path.exists(absdest):
2299 raise util.Abort(_('%s already exists') % absdest)
2299 raise util.Abort(_('%s already exists') % absdest)
2300
2300
2301 if name in q.series:
2301 if name in q.series:
2302 raise util.Abort(
2302 raise util.Abort(
2303 _('A patch named %s already exists in the series file') % name)
2303 _('A patch named %s already exists in the series file') % name)
2304
2304
2305 ui.note(_('renaming %s to %s\n') % (patch, name))
2305 ui.note(_('renaming %s to %s\n') % (patch, name))
2306 i = q.find_series(patch)
2306 i = q.find_series(patch)
2307 guards = q.guard_re.findall(q.full_series[i])
2307 guards = q.guard_re.findall(q.full_series[i])
2308 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2308 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2309 q.parse_series()
2309 q.parse_series()
2310 q.series_dirty = 1
2310 q.series_dirty = 1
2311
2311
2312 info = q.isapplied(patch)
2312 info = q.isapplied(patch)
2313 if info:
2313 if info:
2314 q.applied[info[0]] = statusentry(info[1], name)
2314 q.applied[info[0]] = statusentry(info[1], name)
2315 q.applied_dirty = 1
2315 q.applied_dirty = 1
2316
2316
2317 util.rename(q.join(patch), absdest)
2317 util.rename(q.join(patch), absdest)
2318 r = q.qrepo()
2318 r = q.qrepo()
2319 if r:
2319 if r:
2320 wlock = r.wlock()
2320 wlock = r.wlock()
2321 try:
2321 try:
2322 if r.dirstate[patch] == 'a':
2322 if r.dirstate[patch] == 'a':
2323 r.dirstate.forget(patch)
2323 r.dirstate.forget(patch)
2324 r.dirstate.add(name)
2324 r.dirstate.add(name)
2325 else:
2325 else:
2326 if r.dirstate[name] == 'r':
2326 if r.dirstate[name] == 'r':
2327 r.undelete([name])
2327 r.undelete([name])
2328 r.copy(patch, name)
2328 r.copy(patch, name)
2329 r.remove([patch], False)
2329 r.remove([patch], False)
2330 finally:
2330 finally:
2331 wlock.release()
2331 wlock.release()
2332
2332
2333 q.save_dirty()
2333 q.save_dirty()
2334
2334
2335 def restore(ui, repo, rev, **opts):
2335 def restore(ui, repo, rev, **opts):
2336 """restore the queue state saved by a revision (DEPRECATED)
2336 """restore the queue state saved by a revision (DEPRECATED)
2337
2337
2338 This command is deprecated, use rebase --mq instead."""
2338 This command is deprecated, use rebase --mq instead."""
2339 rev = repo.lookup(rev)
2339 rev = repo.lookup(rev)
2340 q = repo.mq
2340 q = repo.mq
2341 q.restore(repo, rev, delete=opts['delete'],
2341 q.restore(repo, rev, delete=opts['delete'],
2342 qupdate=opts['update'])
2342 qupdate=opts['update'])
2343 q.save_dirty()
2343 q.save_dirty()
2344 return 0
2344 return 0
2345
2345
2346 def save(ui, repo, **opts):
2346 def save(ui, repo, **opts):
2347 """save current queue state (DEPRECATED)
2347 """save current queue state (DEPRECATED)
2348
2348
2349 This command is deprecated, use rebase --mq instead."""
2349 This command is deprecated, use rebase --mq instead."""
2350 q = repo.mq
2350 q = repo.mq
2351 message = cmdutil.logmessage(opts)
2351 message = cmdutil.logmessage(opts)
2352 ret = q.save(repo, msg=message)
2352 ret = q.save(repo, msg=message)
2353 if ret:
2353 if ret:
2354 return ret
2354 return ret
2355 q.save_dirty()
2355 q.save_dirty()
2356 if opts['copy']:
2356 if opts['copy']:
2357 path = q.path
2357 path = q.path
2358 if opts['name']:
2358 if opts['name']:
2359 newpath = os.path.join(q.basepath, opts['name'])
2359 newpath = os.path.join(q.basepath, opts['name'])
2360 if os.path.exists(newpath):
2360 if os.path.exists(newpath):
2361 if not os.path.isdir(newpath):
2361 if not os.path.isdir(newpath):
2362 raise util.Abort(_('destination %s exists and is not '
2362 raise util.Abort(_('destination %s exists and is not '
2363 'a directory') % newpath)
2363 'a directory') % newpath)
2364 if not opts['force']:
2364 if not opts['force']:
2365 raise util.Abort(_('destination %s exists, '
2365 raise util.Abort(_('destination %s exists, '
2366 'use -f to force') % newpath)
2366 'use -f to force') % newpath)
2367 else:
2367 else:
2368 newpath = savename(path)
2368 newpath = savename(path)
2369 ui.warn(_("copy %s to %s\n") % (path, newpath))
2369 ui.warn(_("copy %s to %s\n") % (path, newpath))
2370 util.copyfiles(path, newpath)
2370 util.copyfiles(path, newpath)
2371 if opts['empty']:
2371 if opts['empty']:
2372 try:
2372 try:
2373 os.unlink(q.join(q.status_path))
2373 os.unlink(q.join(q.status_path))
2374 except:
2374 except:
2375 pass
2375 pass
2376 return 0
2376 return 0
2377
2377
2378 def strip(ui, repo, rev, **opts):
2378 def strip(ui, repo, rev, **opts):
2379 """strip a revision and all its descendants from the repository
2379 """strip a revision and all its descendants from the repository
2380
2380
2381 If one of the working directory's parent revisions is stripped, the
2381 If one of the working directory's parent revisions is stripped, the
2382 working directory will be updated to the parent of the stripped
2382 working directory will be updated to the parent of the stripped
2383 revision.
2383 revision.
2384 """
2384 """
2385 backup = 'all'
2385 backup = 'all'
2386 if opts['backup']:
2386 if opts['backup']:
2387 backup = 'strip'
2387 backup = 'strip'
2388 elif opts['nobackup']:
2388 elif opts['nobackup']:
2389 backup = 'none'
2389 backup = 'none'
2390
2390
2391 rev = repo.lookup(rev)
2391 rev = repo.lookup(rev)
2392 p = repo.dirstate.parents()
2392 p = repo.dirstate.parents()
2393 cl = repo.changelog
2393 cl = repo.changelog
2394 update = True
2394 update = True
2395 if p[0] == nullid:
2395 if p[0] == nullid:
2396 update = False
2396 update = False
2397 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2397 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2398 update = False
2398 update = False
2399 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2399 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2400 update = False
2400 update = False
2401
2401
2402 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2402 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2403 return 0
2403 return 0
2404
2404
2405 def select(ui, repo, *args, **opts):
2405 def select(ui, repo, *args, **opts):
2406 '''set or print guarded patches to push
2406 '''set or print guarded patches to push
2407
2407
2408 Use the qguard command to set or print guards on patch, then use
2408 Use the qguard command to set or print guards on patch, then use
2409 qselect to tell mq which guards to use. A patch will be pushed if
2409 qselect to tell mq which guards to use. A patch will be pushed if
2410 it has no guards or any positive guards match the currently
2410 it has no guards or any positive guards match the currently
2411 selected guard, but will not be pushed if any negative guards
2411 selected guard, but will not be pushed if any negative guards
2412 match the current guard. For example::
2412 match the current guard. For example::
2413
2413
2414 qguard foo.patch -stable (negative guard)
2414 qguard foo.patch -stable (negative guard)
2415 qguard bar.patch +stable (positive guard)
2415 qguard bar.patch +stable (positive guard)
2416 qselect stable
2416 qselect stable
2417
2417
2418 This activates the "stable" guard. mq will skip foo.patch (because
2418 This activates the "stable" guard. mq will skip foo.patch (because
2419 it has a negative match) but push bar.patch (because it has a
2419 it has a negative match) but push bar.patch (because it has a
2420 positive match).
2420 positive match).
2421
2421
2422 With no arguments, prints the currently active guards.
2422 With no arguments, prints the currently active guards.
2423 With one argument, sets the active guard.
2423 With one argument, sets the active guard.
2424
2424
2425 Use -n/--none to deactivate guards (no other arguments needed).
2425 Use -n/--none to deactivate guards (no other arguments needed).
2426 When no guards are active, patches with positive guards are
2426 When no guards are active, patches with positive guards are
2427 skipped and patches with negative guards are pushed.
2427 skipped and patches with negative guards are pushed.
2428
2428
2429 qselect can change the guards on applied patches. It does not pop
2429 qselect can change the guards on applied patches. It does not pop
2430 guarded patches by default. Use --pop to pop back to the last
2430 guarded patches by default. Use --pop to pop back to the last
2431 applied patch that is not guarded. Use --reapply (which implies
2431 applied patch that is not guarded. Use --reapply (which implies
2432 --pop) to push back to the current patch afterwards, but skip
2432 --pop) to push back to the current patch afterwards, but skip
2433 guarded patches.
2433 guarded patches.
2434
2434
2435 Use -s/--series to print a list of all guards in the series file
2435 Use -s/--series to print a list of all guards in the series file
2436 (no other arguments needed). Use -v for more information.'''
2436 (no other arguments needed). Use -v for more information.'''
2437
2437
2438 q = repo.mq
2438 q = repo.mq
2439 guards = q.active()
2439 guards = q.active()
2440 if args or opts['none']:
2440 if args or opts['none']:
2441 old_unapplied = q.unapplied(repo)
2441 old_unapplied = q.unapplied(repo)
2442 old_guarded = [i for i in xrange(len(q.applied)) if
2442 old_guarded = [i for i in xrange(len(q.applied)) if
2443 not q.pushable(i)[0]]
2443 not q.pushable(i)[0]]
2444 q.set_active(args)
2444 q.set_active(args)
2445 q.save_dirty()
2445 q.save_dirty()
2446 if not args:
2446 if not args:
2447 ui.status(_('guards deactivated\n'))
2447 ui.status(_('guards deactivated\n'))
2448 if not opts['pop'] and not opts['reapply']:
2448 if not opts['pop'] and not opts['reapply']:
2449 unapplied = q.unapplied(repo)
2449 unapplied = q.unapplied(repo)
2450 guarded = [i for i in xrange(len(q.applied))
2450 guarded = [i for i in xrange(len(q.applied))
2451 if not q.pushable(i)[0]]
2451 if not q.pushable(i)[0]]
2452 if len(unapplied) != len(old_unapplied):
2452 if len(unapplied) != len(old_unapplied):
2453 ui.status(_('number of unguarded, unapplied patches has '
2453 ui.status(_('number of unguarded, unapplied patches has '
2454 'changed from %d to %d\n') %
2454 'changed from %d to %d\n') %
2455 (len(old_unapplied), len(unapplied)))
2455 (len(old_unapplied), len(unapplied)))
2456 if len(guarded) != len(old_guarded):
2456 if len(guarded) != len(old_guarded):
2457 ui.status(_('number of guarded, applied patches has changed '
2457 ui.status(_('number of guarded, applied patches has changed '
2458 'from %d to %d\n') %
2458 'from %d to %d\n') %
2459 (len(old_guarded), len(guarded)))
2459 (len(old_guarded), len(guarded)))
2460 elif opts['series']:
2460 elif opts['series']:
2461 guards = {}
2461 guards = {}
2462 noguards = 0
2462 noguards = 0
2463 for gs in q.series_guards:
2463 for gs in q.series_guards:
2464 if not gs:
2464 if not gs:
2465 noguards += 1
2465 noguards += 1
2466 for g in gs:
2466 for g in gs:
2467 guards.setdefault(g, 0)
2467 guards.setdefault(g, 0)
2468 guards[g] += 1
2468 guards[g] += 1
2469 if ui.verbose:
2469 if ui.verbose:
2470 guards['NONE'] = noguards
2470 guards['NONE'] = noguards
2471 guards = guards.items()
2471 guards = guards.items()
2472 guards.sort(key=lambda x: x[0][1:])
2472 guards.sort(key=lambda x: x[0][1:])
2473 if guards:
2473 if guards:
2474 ui.note(_('guards in series file:\n'))
2474 ui.note(_('guards in series file:\n'))
2475 for guard, count in guards:
2475 for guard, count in guards:
2476 ui.note('%2d ' % count)
2476 ui.note('%2d ' % count)
2477 ui.write(guard, '\n')
2477 ui.write(guard, '\n')
2478 else:
2478 else:
2479 ui.note(_('no guards in series file\n'))
2479 ui.note(_('no guards in series file\n'))
2480 else:
2480 else:
2481 if guards:
2481 if guards:
2482 ui.note(_('active guards:\n'))
2482 ui.note(_('active guards:\n'))
2483 for g in guards:
2483 for g in guards:
2484 ui.write(g, '\n')
2484 ui.write(g, '\n')
2485 else:
2485 else:
2486 ui.write(_('no active guards\n'))
2486 ui.write(_('no active guards\n'))
2487 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2487 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2488 popped = False
2488 popped = False
2489 if opts['pop'] or opts['reapply']:
2489 if opts['pop'] or opts['reapply']:
2490 for i in xrange(len(q.applied)):
2490 for i in xrange(len(q.applied)):
2491 pushable, reason = q.pushable(i)
2491 pushable, reason = q.pushable(i)
2492 if not pushable:
2492 if not pushable:
2493 ui.status(_('popping guarded patches\n'))
2493 ui.status(_('popping guarded patches\n'))
2494 popped = True
2494 popped = True
2495 if i == 0:
2495 if i == 0:
2496 q.pop(repo, all=True)
2496 q.pop(repo, all=True)
2497 else:
2497 else:
2498 q.pop(repo, i - 1)
2498 q.pop(repo, i - 1)
2499 break
2499 break
2500 if popped:
2500 if popped:
2501 try:
2501 try:
2502 if reapply:
2502 if reapply:
2503 ui.status(_('reapplying unguarded patches\n'))
2503 ui.status(_('reapplying unguarded patches\n'))
2504 q.push(repo, reapply)
2504 q.push(repo, reapply)
2505 finally:
2505 finally:
2506 q.save_dirty()
2506 q.save_dirty()
2507
2507
2508 def finish(ui, repo, *revrange, **opts):
2508 def finish(ui, repo, *revrange, **opts):
2509 """move applied patches into repository history
2509 """move applied patches into repository history
2510
2510
2511 Finishes the specified revisions (corresponding to applied
2511 Finishes the specified revisions (corresponding to applied
2512 patches) by moving them out of mq control into regular repository
2512 patches) by moving them out of mq control into regular repository
2513 history.
2513 history.
2514
2514
2515 Accepts a revision range or the -a/--applied option. If --applied
2515 Accepts a revision range or the -a/--applied option. If --applied
2516 is specified, all applied mq revisions are removed from mq
2516 is specified, all applied mq revisions are removed from mq
2517 control. Otherwise, the given revisions must be at the base of the
2517 control. Otherwise, the given revisions must be at the base of the
2518 stack of applied patches.
2518 stack of applied patches.
2519
2519
2520 This can be especially useful if your changes have been applied to
2520 This can be especially useful if your changes have been applied to
2521 an upstream repository, or if you are about to push your changes
2521 an upstream repository, or if you are about to push your changes
2522 to upstream.
2522 to upstream.
2523 """
2523 """
2524 if not opts['applied'] and not revrange:
2524 if not opts['applied'] and not revrange:
2525 raise util.Abort(_('no revisions specified'))
2525 raise util.Abort(_('no revisions specified'))
2526 elif opts['applied']:
2526 elif opts['applied']:
2527 revrange = ('qbase:qtip',) + revrange
2527 revrange = ('qbase:qtip',) + revrange
2528
2528
2529 q = repo.mq
2529 q = repo.mq
2530 if not q.applied:
2530 if not q.applied:
2531 ui.status(_('no patches applied\n'))
2531 ui.status(_('no patches applied\n'))
2532 return 0
2532 return 0
2533
2533
2534 revs = cmdutil.revrange(repo, revrange)
2534 revs = cmdutil.revrange(repo, revrange)
2535 q.finish(repo, revs)
2535 q.finish(repo, revs)
2536 q.save_dirty()
2536 q.save_dirty()
2537 return 0
2537 return 0
2538
2538
2539 def reposetup(ui, repo):
2539 def reposetup(ui, repo):
2540 class mqrepo(repo.__class__):
2540 class mqrepo(repo.__class__):
2541 @util.propertycache
2541 @util.propertycache
2542 def mq(self):
2542 def mq(self):
2543 return queue(self.ui, self.join(""))
2543 return queue(self.ui, self.join(""))
2544
2544
2545 def abort_if_wdir_patched(self, errmsg, force=False):
2545 def abort_if_wdir_patched(self, errmsg, force=False):
2546 if self.mq.applied and not force:
2546 if self.mq.applied and not force:
2547 parent = hex(self.dirstate.parents()[0])
2547 parent = hex(self.dirstate.parents()[0])
2548 if parent in [s.rev for s in self.mq.applied]:
2548 if parent in [s.rev for s in self.mq.applied]:
2549 raise util.Abort(errmsg)
2549 raise util.Abort(errmsg)
2550
2550
2551 def commit(self, text="", user=None, date=None, match=None,
2551 def commit(self, text="", user=None, date=None, match=None,
2552 force=False, editor=False, extra={}):
2552 force=False, editor=False, extra={}):
2553 self.abort_if_wdir_patched(
2553 self.abort_if_wdir_patched(
2554 _('cannot commit over an applied mq patch'),
2554 _('cannot commit over an applied mq patch'),
2555 force)
2555 force)
2556
2556
2557 return super(mqrepo, self).commit(text, user, date, match, force,
2557 return super(mqrepo, self).commit(text, user, date, match, force,
2558 editor, extra)
2558 editor, extra)
2559
2559
2560 def push(self, remote, force=False, revs=None):
2560 def push(self, remote, force=False, revs=None):
2561 if self.mq.applied and not force and not revs:
2561 if self.mq.applied and not force and not revs:
2562 raise util.Abort(_('source has mq patches applied'))
2562 raise util.Abort(_('source has mq patches applied'))
2563 return super(mqrepo, self).push(remote, force, revs)
2563 return super(mqrepo, self).push(remote, force, revs)
2564
2564
2565 def _findtags(self):
2565 def _findtags(self):
2566 '''augment tags from base class with patch tags'''
2566 '''augment tags from base class with patch tags'''
2567 result = super(mqrepo, self)._findtags()
2567 result = super(mqrepo, self)._findtags()
2568
2568
2569 q = self.mq
2569 q = self.mq
2570 if not q.applied:
2570 if not q.applied:
2571 return result
2571 return result
2572
2572
2573 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2573 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2574
2574
2575 if mqtags[-1][0] not in self.changelog.nodemap:
2575 if mqtags[-1][0] not in self.changelog.nodemap:
2576 self.ui.warn(_('mq status file refers to unknown node %s\n')
2576 self.ui.warn(_('mq status file refers to unknown node %s\n')
2577 % short(mqtags[-1][0]))
2577 % short(mqtags[-1][0]))
2578 return result
2578 return result
2579
2579
2580 mqtags.append((mqtags[-1][0], 'qtip'))
2580 mqtags.append((mqtags[-1][0], 'qtip'))
2581 mqtags.append((mqtags[0][0], 'qbase'))
2581 mqtags.append((mqtags[0][0], 'qbase'))
2582 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2582 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2583 tags = result[0]
2583 tags = result[0]
2584 for patch in mqtags:
2584 for patch in mqtags:
2585 if patch[1] in tags:
2585 if patch[1] in tags:
2586 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2586 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2587 % patch[1])
2587 % patch[1])
2588 else:
2588 else:
2589 tags[patch[1]] = patch[0]
2589 tags[patch[1]] = patch[0]
2590
2590
2591 return result
2591 return result
2592
2592
2593 def _branchtags(self, partial, lrev):
2593 def _branchtags(self, partial, lrev):
2594 q = self.mq
2594 q = self.mq
2595 if not q.applied:
2595 if not q.applied:
2596 return super(mqrepo, self)._branchtags(partial, lrev)
2596 return super(mqrepo, self)._branchtags(partial, lrev)
2597
2597
2598 cl = self.changelog
2598 cl = self.changelog
2599 qbasenode = bin(q.applied[0].rev)
2599 qbasenode = bin(q.applied[0].rev)
2600 if qbasenode not in cl.nodemap:
2600 if qbasenode not in cl.nodemap:
2601 self.ui.warn(_('mq status file refers to unknown node %s\n')
2601 self.ui.warn(_('mq status file refers to unknown node %s\n')
2602 % short(qbasenode))
2602 % short(qbasenode))
2603 return super(mqrepo, self)._branchtags(partial, lrev)
2603 return super(mqrepo, self)._branchtags(partial, lrev)
2604
2604
2605 qbase = cl.rev(qbasenode)
2605 qbase = cl.rev(qbasenode)
2606 start = lrev + 1
2606 start = lrev + 1
2607 if start < qbase:
2607 if start < qbase:
2608 # update the cache (excluding the patches) and save it
2608 # update the cache (excluding the patches) and save it
2609 self._updatebranchcache(partial, lrev + 1, qbase)
2609 self._updatebranchcache(partial, lrev + 1, qbase)
2610 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2610 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2611 start = qbase
2611 start = qbase
2612 # if start = qbase, the cache is as updated as it should be.
2612 # if start = qbase, the cache is as updated as it should be.
2613 # if start > qbase, the cache includes (part of) the patches.
2613 # if start > qbase, the cache includes (part of) the patches.
2614 # we might as well use it, but we won't save it.
2614 # we might as well use it, but we won't save it.
2615
2615
2616 # update the cache up to the tip
2616 # update the cache up to the tip
2617 self._updatebranchcache(partial, start, len(cl))
2617 self._updatebranchcache(partial, start, len(cl))
2618
2618
2619 return partial
2619 return partial
2620
2620
2621 if repo.local():
2621 if repo.local():
2622 repo.__class__ = mqrepo
2622 repo.__class__ = mqrepo
2623
2623
2624 def mqimport(orig, ui, repo, *args, **kwargs):
2624 def mqimport(orig, ui, repo, *args, **kwargs):
2625 if (hasattr(repo, 'abort_if_wdir_patched')
2625 if (hasattr(repo, 'abort_if_wdir_patched')
2626 and not kwargs.get('no_commit', False)):
2626 and not kwargs.get('no_commit', False)):
2627 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2627 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2628 kwargs.get('force'))
2628 kwargs.get('force'))
2629 return orig(ui, repo, *args, **kwargs)
2629 return orig(ui, repo, *args, **kwargs)
2630
2630
2631 def mqinit(orig, ui, *args, **kwargs):
2631 def mqinit(orig, ui, *args, **kwargs):
2632 mq = kwargs.pop('mq', None)
2632 mq = kwargs.pop('mq', None)
2633
2633
2634 if not mq:
2634 if not mq:
2635 return orig(ui, *args, **kwargs)
2635 return orig(ui, *args, **kwargs)
2636
2636
2637 repopath = cmdutil.findrepo(os.getcwd())
2637 repopath = cmdutil.findrepo(os.getcwd())
2638 repo = hg.repository(ui, repopath)
2638 repo = hg.repository(ui, repopath)
2639 return qinit(ui, repo, True)
2639 return qinit(ui, repo, True)
2640
2640
2641 def mqcommand(orig, ui, repo, *args, **kwargs):
2641 def mqcommand(orig, ui, repo, *args, **kwargs):
2642 """Add --mq option to operate on patch repository instead of main"""
2642 """Add --mq option to operate on patch repository instead of main"""
2643
2643
2644 # some commands do not like getting unknown options
2644 # some commands do not like getting unknown options
2645 mq = kwargs.pop('mq', None)
2645 mq = kwargs.pop('mq', None)
2646
2646
2647 if not mq:
2647 if not mq:
2648 return orig(ui, repo, *args, **kwargs)
2648 return orig(ui, repo, *args, **kwargs)
2649
2649
2650 q = repo.mq
2650 q = repo.mq
2651 r = q.qrepo()
2651 r = q.qrepo()
2652 if not r:
2652 if not r:
2653 raise util.Abort('no queue repository')
2653 raise util.Abort('no queue repository')
2654 return orig(r.ui, r, *args, **kwargs)
2654 return orig(r.ui, r, *args, **kwargs)
2655
2655
2656 def uisetup(ui):
2656 def uisetup(ui):
2657 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2657 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2658
2658
2659 extensions.wrapcommand(commands.table, 'import', mqimport)
2659 extensions.wrapcommand(commands.table, 'import', mqimport)
2660
2660
2661 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2661 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2662 entry[1].extend(mqopt)
2662 entry[1].extend(mqopt)
2663
2663
2664 for cmd in commands.table.keys():
2664 for cmd in commands.table.keys():
2665 cmd = cmdutil.parsealiases(cmd)[0]
2665 cmd = cmdutil.parsealiases(cmd)[0]
2666 if cmd in commands.norepo:
2666 if cmd in commands.norepo:
2667 continue
2667 continue
2668 entry = extensions.wrapcommand(commands.table, cmd, mqcommand)
2668 entry = extensions.wrapcommand(commands.table, cmd, mqcommand)
2669 entry[1].extend(mqopt)
2669 entry[1].extend(mqopt)
2670
2670
2671 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2671 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2672
2672
2673 cmdtable = {
2673 cmdtable = {
2674 "qapplied":
2674 "qapplied":
2675 (applied,
2675 (applied,
2676 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2676 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2677 _('hg qapplied [-1] [-s] [PATCH]')),
2677 _('hg qapplied [-1] [-s] [PATCH]')),
2678 "qclone":
2678 "qclone":
2679 (clone,
2679 (clone,
2680 [('', 'pull', None, _('use pull protocol to copy metadata')),
2680 [('', 'pull', None, _('use pull protocol to copy metadata')),
2681 ('U', 'noupdate', None, _('do not update the new working directories')),
2681 ('U', 'noupdate', None, _('do not update the new working directories')),
2682 ('', 'uncompressed', None,
2682 ('', 'uncompressed', None,
2683 _('use uncompressed transfer (fast over LAN)')),
2683 _('use uncompressed transfer (fast over LAN)')),
2684 ('p', 'patches', '', _('location of source patch repository')),
2684 ('p', 'patches', '', _('location of source patch repository')),
2685 ] + commands.remoteopts,
2685 ] + commands.remoteopts,
2686 _('hg qclone [OPTION]... SOURCE [DEST]')),
2686 _('hg qclone [OPTION]... SOURCE [DEST]')),
2687 "qcommit|qci":
2687 "qcommit|qci":
2688 (commit,
2688 (commit,
2689 commands.table["^commit|ci"][1],
2689 commands.table["^commit|ci"][1],
2690 _('hg qcommit [OPTION]... [FILE]...')),
2690 _('hg qcommit [OPTION]... [FILE]...')),
2691 "^qdiff":
2691 "^qdiff":
2692 (diff,
2692 (diff,
2693 commands.diffopts + commands.diffopts2 + commands.walkopts,
2693 commands.diffopts + commands.diffopts2 + commands.walkopts,
2694 _('hg qdiff [OPTION]... [FILE]...')),
2694 _('hg qdiff [OPTION]... [FILE]...')),
2695 "qdelete|qremove|qrm":
2695 "qdelete|qremove|qrm":
2696 (delete,
2696 (delete,
2697 [('k', 'keep', None, _('keep patch file')),
2697 [('k', 'keep', None, _('keep patch file')),
2698 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2698 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2699 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2699 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2700 'qfold':
2700 'qfold':
2701 (fold,
2701 (fold,
2702 [('e', 'edit', None, _('edit patch header')),
2702 [('e', 'edit', None, _('edit patch header')),
2703 ('k', 'keep', None, _('keep folded patch files')),
2703 ('k', 'keep', None, _('keep folded patch files')),
2704 ] + commands.commitopts,
2704 ] + commands.commitopts,
2705 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2705 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2706 'qgoto':
2706 'qgoto':
2707 (goto,
2707 (goto,
2708 [('f', 'force', None, _('overwrite any local changes'))],
2708 [('f', 'force', None, _('overwrite any local changes'))],
2709 _('hg qgoto [OPTION]... PATCH')),
2709 _('hg qgoto [OPTION]... PATCH')),
2710 'qguard':
2710 'qguard':
2711 (guard,
2711 (guard,
2712 [('l', 'list', None, _('list all patches and guards')),
2712 [('l', 'list', None, _('list all patches and guards')),
2713 ('n', 'none', None, _('drop all guards'))],
2713 ('n', 'none', None, _('drop all guards'))],
2714 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
2714 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
2715 'qheader': (header, [], _('hg qheader [PATCH]')),
2715 'qheader': (header, [], _('hg qheader [PATCH]')),
2716 "^qimport":
2716 "^qimport":
2717 (qimport,
2717 (qimport,
2718 [('e', 'existing', None, _('import file in patch directory')),
2718 [('e', 'existing', None, _('import file in patch directory')),
2719 ('n', 'name', '', _('name of patch file')),
2719 ('n', 'name', '', _('name of patch file')),
2720 ('f', 'force', None, _('overwrite existing files')),
2720 ('f', 'force', None, _('overwrite existing files')),
2721 ('r', 'rev', [], _('place existing revisions under mq control')),
2721 ('r', 'rev', [], _('place existing revisions under mq control')),
2722 ('g', 'git', None, _('use git extended diff format')),
2722 ('g', 'git', None, _('use git extended diff format')),
2723 ('P', 'push', None, _('qpush after importing'))],
2723 ('P', 'push', None, _('qpush after importing'))],
2724 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2724 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2725 "^qinit":
2725 "^qinit":
2726 (init,
2726 (init,
2727 [('c', 'create-repo', None, _('create queue repository'))],
2727 [('c', 'create-repo', None, _('create queue repository'))],
2728 _('hg qinit [-c]')),
2728 _('hg qinit [-c]')),
2729 "qnew":
2729 "qnew":
2730 (new,
2730 (new,
2731 [('e', 'edit', None, _('edit commit message')),
2731 [('e', 'edit', None, _('edit commit message')),
2732 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2732 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2733 ('g', 'git', None, _('use git extended diff format')),
2733 ('g', 'git', None, _('use git extended diff format')),
2734 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2734 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2735 ('u', 'user', '', _('add "From: <given user>" to patch')),
2735 ('u', 'user', '', _('add "From: <given user>" to patch')),
2736 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2736 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2737 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2737 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2738 ] + commands.walkopts + commands.commitopts,
2738 ] + commands.walkopts + commands.commitopts,
2739 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2739 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2740 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2740 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2741 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2741 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2742 "^qpop":
2742 "^qpop":
2743 (pop,
2743 (pop,
2744 [('a', 'all', None, _('pop all patches')),
2744 [('a', 'all', None, _('pop all patches')),
2745 ('n', 'name', '', _('queue name to pop (DEPRECATED)')),
2745 ('n', 'name', '', _('queue name to pop (DEPRECATED)')),
2746 ('f', 'force', None, _('forget any local changes to patched files'))],
2746 ('f', 'force', None, _('forget any local changes to patched files'))],
2747 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2747 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2748 "^qpush":
2748 "^qpush":
2749 (push,
2749 (push,
2750 [('f', 'force', None, _('apply if the patch has rejects')),
2750 [('f', 'force', None, _('apply if the patch has rejects')),
2751 ('l', 'list', None, _('list patch name in commit text')),
2751 ('l', 'list', None, _('list patch name in commit text')),
2752 ('a', 'all', None, _('apply all patches')),
2752 ('a', 'all', None, _('apply all patches')),
2753 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2753 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2754 ('n', 'name', '', _('merge queue name (DEPRECATED)'))],
2754 ('n', 'name', '', _('merge queue name (DEPRECATED)'))],
2755 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2755 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2756 "^qrefresh":
2756 "^qrefresh":
2757 (refresh,
2757 (refresh,
2758 [('e', 'edit', None, _('edit commit message')),
2758 [('e', 'edit', None, _('edit commit message')),
2759 ('g', 'git', None, _('use git extended diff format')),
2759 ('g', 'git', None, _('use git extended diff format')),
2760 ('s', 'short', None,
2760 ('s', 'short', None,
2761 _('refresh only files already in the patch and specified files')),
2761 _('refresh only files already in the patch and specified files')),
2762 ('U', 'currentuser', None,
2762 ('U', 'currentuser', None,
2763 _('add/update author field in patch with current user')),
2763 _('add/update author field in patch with current user')),
2764 ('u', 'user', '',
2764 ('u', 'user', '',
2765 _('add/update author field in patch with given user')),
2765 _('add/update author field in patch with given user')),
2766 ('D', 'currentdate', None,
2766 ('D', 'currentdate', None,
2767 _('add/update date field in patch with current date')),
2767 _('add/update date field in patch with current date')),
2768 ('d', 'date', '',
2768 ('d', 'date', '',
2769 _('add/update date field in patch with given date'))
2769 _('add/update date field in patch with given date'))
2770 ] + commands.walkopts + commands.commitopts,
2770 ] + commands.walkopts + commands.commitopts,
2771 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2771 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2772 'qrename|qmv':
2772 'qrename|qmv':
2773 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2773 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2774 "qrestore":
2774 "qrestore":
2775 (restore,
2775 (restore,
2776 [('d', 'delete', None, _('delete save entry')),
2776 [('d', 'delete', None, _('delete save entry')),
2777 ('u', 'update', None, _('update queue working directory'))],
2777 ('u', 'update', None, _('update queue working directory'))],
2778 _('hg qrestore [-d] [-u] REV')),
2778 _('hg qrestore [-d] [-u] REV')),
2779 "qsave":
2779 "qsave":
2780 (save,
2780 (save,
2781 [('c', 'copy', None, _('copy patch directory')),
2781 [('c', 'copy', None, _('copy patch directory')),
2782 ('n', 'name', '', _('copy directory name')),
2782 ('n', 'name', '', _('copy directory name')),
2783 ('e', 'empty', None, _('clear queue status file')),
2783 ('e', 'empty', None, _('clear queue status file')),
2784 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2784 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2785 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2785 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2786 "qselect":
2786 "qselect":
2787 (select,
2787 (select,
2788 [('n', 'none', None, _('disable all guards')),
2788 [('n', 'none', None, _('disable all guards')),
2789 ('s', 'series', None, _('list all guards in series file')),
2789 ('s', 'series', None, _('list all guards in series file')),
2790 ('', 'pop', None, _('pop to before first guarded applied patch')),
2790 ('', 'pop', None, _('pop to before first guarded applied patch')),
2791 ('', 'reapply', None, _('pop, then reapply patches'))],
2791 ('', 'reapply', None, _('pop, then reapply patches'))],
2792 _('hg qselect [OPTION]... [GUARD]...')),
2792 _('hg qselect [OPTION]... [GUARD]...')),
2793 "qseries":
2793 "qseries":
2794 (series,
2794 (series,
2795 [('m', 'missing', None, _('print patches not in series')),
2795 [('m', 'missing', None, _('print patches not in series')),
2796 ] + seriesopts,
2796 ] + seriesopts,
2797 _('hg qseries [-ms]')),
2797 _('hg qseries [-ms]')),
2798 "^strip":
2798 "^strip":
2799 (strip,
2799 (strip,
2800 [('f', 'force', None, _('force removal with local changes')),
2800 [('f', 'force', None, _('force removal with local changes')),
2801 ('b', 'backup', None, _('bundle unrelated changesets')),
2801 ('b', 'backup', None, _('bundle unrelated changesets')),
2802 ('n', 'nobackup', None, _('no backups'))],
2802 ('n', 'nobackup', None, _('no backups'))],
2803 _('hg strip [-f] [-b] [-n] REV')),
2803 _('hg strip [-f] [-b] [-n] REV')),
2804 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2804 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2805 "qunapplied":
2805 "qunapplied":
2806 (unapplied,
2806 (unapplied,
2807 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2807 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2808 _('hg qunapplied [-1] [-s] [PATCH]')),
2808 _('hg qunapplied [-1] [-s] [PATCH]')),
2809 "qfinish":
2809 "qfinish":
2810 (finish,
2810 (finish,
2811 [('a', 'applied', None, _('finish all applied changesets'))],
2811 [('a', 'applied', None, _('finish all applied changesets'))],
2812 _('hg qfinish [-a] [REV]...')),
2812 _('hg qfinish [-a] [REV]...')),
2813 }
2813 }
@@ -1,523 +1,523 b''
1 # patchbomb.py - sending Mercurial changesets as patch emails
1 # patchbomb.py - sending Mercurial changesets as patch emails
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to send changesets as (a series of) patch emails
8 '''command to send changesets as (a series of) patch emails
9
9
10 The series is started off with a "[PATCH 0 of N]" introduction, which
10 The series is started off with a "[PATCH 0 of N]" introduction, which
11 describes the series as a whole.
11 describes the series as a whole.
12
12
13 Each patch email has a Subject line of "[PATCH M of N] ...", using the
13 Each patch email has a Subject line of "[PATCH M of N] ...", using the
14 first line of the changeset description as the subject text. The
14 first line of the changeset description as the subject text. The
15 message contains two or three body parts:
15 message contains two or three body parts:
16
16
17 - The changeset description.
17 - The changeset description.
18 - [Optional] The result of running diffstat on the patch.
18 - [Optional] The result of running diffstat on the patch.
19 - The patch itself, as generated by "hg export".
19 - The patch itself, as generated by "hg export".
20
20
21 Each message refers to the first in the series using the In-Reply-To
21 Each message refers to the first in the series using the In-Reply-To
22 and References headers, so they will show up as a sequence in threaded
22 and References headers, so they will show up as a sequence in threaded
23 mail and news readers, and in mail archives.
23 mail and news readers, and in mail archives.
24
24
25 With the -d/--diffstat option, you will be prompted for each changeset
25 With the -d/--diffstat option, you will be prompted for each changeset
26 with a diffstat summary and the changeset summary, so you can be sure
26 with a diffstat summary and the changeset summary, so you can be sure
27 you are sending the right changes.
27 you are sending the right changes.
28
28
29 To configure other defaults, add a section like this to your hgrc
29 To configure other defaults, add a section like this to your hgrc
30 file::
30 file::
31
31
32 [email]
32 [email]
33 from = My Name <my@email>
33 from = My Name <my@email>
34 to = recipient1, recipient2, ...
34 to = recipient1, recipient2, ...
35 cc = cc1, cc2, ...
35 cc = cc1, cc2, ...
36 bcc = bcc1, bcc2, ...
36 bcc = bcc1, bcc2, ...
37
37
38 Use ``[patchbomb]`` as configuration section name if you need to
38 Use ``[patchbomb]`` as configuration section name if you need to
39 override global ``[email]`` address settings.
39 override global ``[email]`` address settings.
40
40
41 Then you can use the "hg email" command to mail a series of changesets
41 Then you can use the "hg email" command to mail a series of changesets
42 as a patchbomb.
42 as a patchbomb.
43
43
44 To avoid sending patches prematurely, it is a good idea to first run
44 To avoid sending patches prematurely, it is a good idea to first run
45 the "email" command with the "-n" option (test only). You will be
45 the "email" command with the "-n" option (test only). You will be
46 prompted for an email recipient address, a subject and an introductory
46 prompted for an email recipient address, a subject and an introductory
47 message describing the patches of your patchbomb. Then when all is
47 message describing the patches of your patchbomb. Then when all is
48 done, patchbomb messages are displayed. If the PAGER environment
48 done, patchbomb messages are displayed. If the PAGER environment
49 variable is set, your pager will be fired up once for each patchbomb
49 variable is set, your pager will be fired up once for each patchbomb
50 message, so you can verify everything is alright.
50 message, so you can verify everything is alright.
51
51
52 The -m/--mbox option is also very useful. Instead of previewing each
52 The -m/--mbox option is also very useful. Instead of previewing each
53 patchbomb message in a pager or sending the messages directly, it will
53 patchbomb message in a pager or sending the messages directly, it will
54 create a UNIX mailbox file with the patch emails. This mailbox file
54 create a UNIX mailbox file with the patch emails. This mailbox file
55 can be previewed with any mail user agent which supports UNIX mbox
55 can be previewed with any mail user agent which supports UNIX mbox
56 files, e.g. with mutt::
56 files, e.g. with mutt::
57
57
58 % mutt -R -f mbox
58 % mutt -R -f mbox
59
59
60 When you are previewing the patchbomb messages, you can use ``formail``
60 When you are previewing the patchbomb messages, you can use ``formail``
61 (a utility that is commonly installed as part of the procmail
61 (a utility that is commonly installed as part of the procmail
62 package), to send each message out::
62 package), to send each message out::
63
63
64 % formail -s sendmail -bm -t < mbox
64 % formail -s sendmail -bm -t < mbox
65
65
66 That should be all. Now your patchbomb is on its way out.
66 That should be all. Now your patchbomb is on its way out.
67
67
68 You can also either configure the method option in the email section
68 You can also either configure the method option in the email section
69 to be a sendmail compatible mailer or fill out the [smtp] section so
69 to be a sendmail compatible mailer or fill out the [smtp] section so
70 that the patchbomb extension can automatically send patchbombs
70 that the patchbomb extension can automatically send patchbombs
71 directly from the commandline. See the [email] and [smtp] sections in
71 directly from the commandline. See the [email] and [smtp] sections in
72 hgrc(5) for details.
72 hgrc(5) for details.
73 '''
73 '''
74
74
75 import os, errno, socket, tempfile, cStringIO, time
75 import os, errno, socket, tempfile, cStringIO, time
76 import email.MIMEMultipart, email.MIMEBase
76 import email.MIMEMultipart, email.MIMEBase
77 import email.Utils, email.Encoders, email.Generator
77 import email.Utils, email.Encoders, email.Generator
78 from mercurial import cmdutil, commands, hg, mail, patch, util
78 from mercurial import cmdutil, commands, hg, mail, patch, util
79 from mercurial.i18n import _
79 from mercurial.i18n import _
80 from mercurial.node import bin
80 from mercurial.node import bin
81
81
82 def prompt(ui, prompt, default=None, rest=':'):
82 def prompt(ui, prompt, default=None, rest=':'):
83 if not ui.interactive():
83 if not ui.interactive():
84 if default is not None:
84 if default is not None:
85 return default
85 return default
86 raise util.Abort(_("%s Please enter a valid value" % (prompt + rest)))
86 raise util.Abort(_("%s Please enter a valid value" % (prompt + rest)))
87 if default:
87 if default:
88 prompt += ' [%s]' % default
88 prompt += ' [%s]' % default
89 prompt += rest
89 prompt += rest
90 while True:
90 while True:
91 r = ui.prompt(prompt, default=default)
91 r = ui.prompt(prompt, default=default)
92 if r:
92 if r:
93 return r
93 return r
94 if default is not None:
94 if default is not None:
95 return default
95 return default
96 ui.warn(_('Please enter a valid value.\n'))
96 ui.warn(_('Please enter a valid value.\n'))
97
97
98 def cdiffstat(ui, summary, patchlines):
98 def cdiffstat(ui, summary, patchlines):
99 s = patch.diffstat(patchlines)
99 s = patch.diffstat(patchlines)
100 if summary:
100 if summary:
101 ui.write(summary, '\n')
101 ui.write(summary, '\n')
102 ui.write(s, '\n')
102 ui.write(s, '\n')
103 ans = prompt(ui, _('does the diffstat above look okay?'), 'y')
103 ans = prompt(ui, _('does the diffstat above look okay?'), 'y')
104 if not ans.lower().startswith('y'):
104 if not ans.lower().startswith('y'):
105 raise util.Abort(_('diffstat rejected'))
105 raise util.Abort(_('diffstat rejected'))
106 return s
106 return s
107
107
108 def makepatch(ui, repo, patch, opts, _charsets, idx, total, patchname=None):
108 def makepatch(ui, repo, patch, opts, _charsets, idx, total, patchname=None):
109
109
110 desc = []
110 desc = []
111 node = None
111 node = None
112 body = ''
112 body = ''
113
113
114 for line in patch:
114 for line in patch:
115 if line.startswith('#'):
115 if line.startswith('#'):
116 if line.startswith('# Node ID'):
116 if line.startswith('# Node ID'):
117 node = line.split()[-1]
117 node = line.split()[-1]
118 continue
118 continue
119 if line.startswith('diff -r') or line.startswith('diff --git'):
119 if line.startswith('diff -r') or line.startswith('diff --git'):
120 break
120 break
121 desc.append(line)
121 desc.append(line)
122
122
123 if not patchname and not node:
123 if not patchname and not node:
124 raise ValueError
124 raise ValueError
125
125
126 if opts.get('attach'):
126 if opts.get('attach'):
127 body = ('\n'.join(desc[1:]).strip() or
127 body = ('\n'.join(desc[1:]).strip() or
128 'Patch subject is complete summary.')
128 'Patch subject is complete summary.')
129 body += '\n\n\n'
129 body += '\n\n\n'
130
130
131 if opts.get('plain'):
131 if opts.get('plain'):
132 while patch and patch[0].startswith('# '):
132 while patch and patch[0].startswith('# '):
133 patch.pop(0)
133 patch.pop(0)
134 if patch:
134 if patch:
135 patch.pop(0)
135 patch.pop(0)
136 while patch and not patch[0].strip():
136 while patch and not patch[0].strip():
137 patch.pop(0)
137 patch.pop(0)
138
138
139 if opts.get('diffstat'):
139 if opts.get('diffstat'):
140 body += cdiffstat(ui, '\n'.join(desc), patch) + '\n\n'
140 body += cdiffstat(ui, '\n'.join(desc), patch) + '\n\n'
141
141
142 if opts.get('attach') or opts.get('inline'):
142 if opts.get('attach') or opts.get('inline'):
143 msg = email.MIMEMultipart.MIMEMultipart()
143 msg = email.MIMEMultipart.MIMEMultipart()
144 if body:
144 if body:
145 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
145 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
146 p = mail.mimetextpatch('\n'.join(patch), 'x-patch', opts.get('test'))
146 p = mail.mimetextpatch('\n'.join(patch), 'x-patch', opts.get('test'))
147 binnode = bin(node)
147 binnode = bin(node)
148 # if node is mq patch, it will have the patch file's name as a tag
148 # if node is mq patch, it will have the patch file's name as a tag
149 if not patchname:
149 if not patchname:
150 patchtags = [t for t in repo.nodetags(binnode)
150 patchtags = [t for t in repo.nodetags(binnode)
151 if t.endswith('.patch') or t.endswith('.diff')]
151 if t.endswith('.patch') or t.endswith('.diff')]
152 if patchtags:
152 if patchtags:
153 patchname = patchtags[0]
153 patchname = patchtags[0]
154 elif total > 1:
154 elif total > 1:
155 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
155 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
156 binnode, seqno=idx, total=total)
156 binnode, seqno=idx, total=total)
157 else:
157 else:
158 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
158 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
159 disposition = 'inline'
159 disposition = 'inline'
160 if opts.get('attach'):
160 if opts.get('attach'):
161 disposition = 'attachment'
161 disposition = 'attachment'
162 p['Content-Disposition'] = disposition + '; filename=' + patchname
162 p['Content-Disposition'] = disposition + '; filename=' + patchname
163 msg.attach(p)
163 msg.attach(p)
164 else:
164 else:
165 body += '\n'.join(patch)
165 body += '\n'.join(patch)
166 msg = mail.mimetextpatch(body, display=opts.get('test'))
166 msg = mail.mimetextpatch(body, display=opts.get('test'))
167
167
168 flag = ' '.join(opts.get('flag'))
168 flag = ' '.join(opts.get('flag'))
169 if flag:
169 if flag:
170 flag = ' ' + flag
170 flag = ' ' + flag
171
171
172 subj = desc[0].strip().rstrip('. ')
172 subj = desc[0].strip().rstrip('. ')
173 if total == 1 and not opts.get('intro'):
173 if total == 1 and not opts.get('intro'):
174 subj = '[PATCH%s] %s' % (flag, opts.get('subject') or subj)
174 subj = '[PATCH%s] %s' % (flag, opts.get('subject') or subj)
175 else:
175 else:
176 tlen = len(str(total))
176 tlen = len(str(total))
177 subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj)
177 subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj)
178 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
178 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
179 msg['X-Mercurial-Node'] = node
179 msg['X-Mercurial-Node'] = node
180 return msg, subj
180 return msg, subj
181
181
182 def patchbomb(ui, repo, *revs, **opts):
182 def patchbomb(ui, repo, *revs, **opts):
183 '''send changesets by email
183 '''send changesets by email
184
184
185 By default, diffs are sent in the format generated by hg export,
185 By default, diffs are sent in the format generated by hg export,
186 one per message. The series starts with a "[PATCH 0 of N]"
186 one per message. The series starts with a "[PATCH 0 of N]"
187 introduction, which describes the series as a whole.
187 introduction, which describes the series as a whole.
188
188
189 Each patch email has a Subject line of "[PATCH M of N] ...", using
189 Each patch email has a Subject line of "[PATCH M of N] ...", using
190 the first line of the changeset description as the subject text.
190 the first line of the changeset description as the subject text.
191 The message contains two or three parts. First, the changeset
191 The message contains two or three parts. First, the changeset
192 description. Next, (optionally) if the diffstat program is
192 description. Next, (optionally) if the diffstat program is
193 installed and -d/--diffstat is used, the result of running
193 installed and -d/--diffstat is used, the result of running
194 diffstat on the patch. Finally, the patch itself, as generated by
194 diffstat on the patch. Finally, the patch itself, as generated by
195 "hg export".
195 "hg export".
196
196
197 By default the patch is included as text in the email body for
197 By default the patch is included as text in the email body for
198 easy reviewing. Using the -a/--attach option will instead create
198 easy reviewing. Using the -a/--attach option will instead create
199 an attachment for the patch. With -i/--inline an inline attachment
199 an attachment for the patch. With -i/--inline an inline attachment
200 will be created.
200 will be created.
201
201
202 With -o/--outgoing, emails will be generated for patches not found
202 With -o/--outgoing, emails will be generated for patches not found
203 in the destination repository (or only those which are ancestors
203 in the destination repository (or only those which are ancestors
204 of the specified revisions if any are provided)
204 of the specified revisions if any are provided)
205
205
206 With -b/--bundle, changesets are selected as for --outgoing, but a
206 With -b/--bundle, changesets are selected as for --outgoing, but a
207 single email containing a binary Mercurial bundle as an attachment
207 single email containing a binary Mercurial bundle as an attachment
208 will be sent.
208 will be sent.
209
209
210 Examples::
210 Examples::
211
211
212 hg email -r 3000 # send patch 3000 only
212 hg email -r 3000 # send patch 3000 only
213 hg email -r 3000 -r 3001 # send patches 3000 and 3001
213 hg email -r 3000 -r 3001 # send patches 3000 and 3001
214 hg email -r 3000:3005 # send patches 3000 through 3005
214 hg email -r 3000:3005 # send patches 3000 through 3005
215 hg email 3000 # send patch 3000 (deprecated)
215 hg email 3000 # send patch 3000 (deprecated)
216
216
217 hg email -o # send all patches not in default
217 hg email -o # send all patches not in default
218 hg email -o DEST # send all patches not in DEST
218 hg email -o DEST # send all patches not in DEST
219 hg email -o -r 3000 # send all ancestors of 3000 not in default
219 hg email -o -r 3000 # send all ancestors of 3000 not in default
220 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
220 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
221
221
222 hg email -b # send bundle of all patches not in default
222 hg email -b # send bundle of all patches not in default
223 hg email -b DEST # send bundle of all patches not in DEST
223 hg email -b DEST # send bundle of all patches not in DEST
224 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
224 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
225 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
225 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
226
226
227 Before using this command, you will need to enable email in your
227 Before using this command, you will need to enable email in your
228 hgrc. See the [email] section in hgrc(5) for details.
228 hgrc. See the [email] section in hgrc(5) for details.
229 '''
229 '''
230
230
231 _charsets = mail._charsets(ui)
231 _charsets = mail._charsets(ui)
232
232
233 def outgoing(dest, revs):
233 def outgoing(dest, revs):
234 '''Return the revisions present locally but not in dest'''
234 '''Return the revisions present locally but not in dest'''
235 dest = ui.expandpath(dest or 'default-push', dest or 'default')
235 dest = ui.expandpath(dest or 'default-push', dest or 'default')
236 dest, branches = hg.parseurl(dest)
236 dest, branches = hg.parseurl(dest)
237 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
237 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
238 if revs:
238 if revs:
239 revs = [repo.lookup(rev) for rev in revs]
239 revs = [repo.lookup(rev) for rev in revs]
240 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
240 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
241 ui.status(_('comparing with %s\n') % dest)
241 ui.status(_('comparing with %s\n') % dest)
242 o = repo.findoutgoing(other)
242 o = repo.findoutgoing(other)
243 if not o:
243 if not o:
244 ui.status(_("no changes found\n"))
244 ui.status(_("no changes found\n"))
245 return []
245 return []
246 o = repo.changelog.nodesbetween(o, revs)[0]
246 o = repo.changelog.nodesbetween(o, revs)[0]
247 return [str(repo.changelog.rev(r)) for r in o]
247 return [str(repo.changelog.rev(r)) for r in o]
248
248
249 def getpatches(revs):
249 def getpatches(revs):
250 for r in cmdutil.revrange(repo, revs):
250 for r in cmdutil.revrange(repo, revs):
251 output = cStringIO.StringIO()
251 output = cStringIO.StringIO()
252 patch.export(repo, [r], fp=output,
252 cmdutil.export(repo, [r], fp=output,
253 opts=patch.diffopts(ui, opts))
253 opts=patch.diffopts(ui, opts))
254 yield output.getvalue().split('\n')
254 yield output.getvalue().split('\n')
255
255
256 def getbundle(dest):
256 def getbundle(dest):
257 tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
257 tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
258 tmpfn = os.path.join(tmpdir, 'bundle')
258 tmpfn = os.path.join(tmpdir, 'bundle')
259 try:
259 try:
260 commands.bundle(ui, repo, tmpfn, dest, **opts)
260 commands.bundle(ui, repo, tmpfn, dest, **opts)
261 return open(tmpfn, 'rb').read()
261 return open(tmpfn, 'rb').read()
262 finally:
262 finally:
263 try:
263 try:
264 os.unlink(tmpfn)
264 os.unlink(tmpfn)
265 except:
265 except:
266 pass
266 pass
267 os.rmdir(tmpdir)
267 os.rmdir(tmpdir)
268
268
269 if not (opts.get('test') or opts.get('mbox')):
269 if not (opts.get('test') or opts.get('mbox')):
270 # really sending
270 # really sending
271 mail.validateconfig(ui)
271 mail.validateconfig(ui)
272
272
273 if not (revs or opts.get('rev')
273 if not (revs or opts.get('rev')
274 or opts.get('outgoing') or opts.get('bundle')
274 or opts.get('outgoing') or opts.get('bundle')
275 or opts.get('patches')):
275 or opts.get('patches')):
276 raise util.Abort(_('specify at least one changeset with -r or -o'))
276 raise util.Abort(_('specify at least one changeset with -r or -o'))
277
277
278 if opts.get('outgoing') and opts.get('bundle'):
278 if opts.get('outgoing') and opts.get('bundle'):
279 raise util.Abort(_("--outgoing mode always on with --bundle;"
279 raise util.Abort(_("--outgoing mode always on with --bundle;"
280 " do not re-specify --outgoing"))
280 " do not re-specify --outgoing"))
281
281
282 if opts.get('outgoing') or opts.get('bundle'):
282 if opts.get('outgoing') or opts.get('bundle'):
283 if len(revs) > 1:
283 if len(revs) > 1:
284 raise util.Abort(_("too many destinations"))
284 raise util.Abort(_("too many destinations"))
285 dest = revs and revs[0] or None
285 dest = revs and revs[0] or None
286 revs = []
286 revs = []
287
287
288 if opts.get('rev'):
288 if opts.get('rev'):
289 if revs:
289 if revs:
290 raise util.Abort(_('use only one form to specify the revision'))
290 raise util.Abort(_('use only one form to specify the revision'))
291 revs = opts.get('rev')
291 revs = opts.get('rev')
292
292
293 if opts.get('outgoing'):
293 if opts.get('outgoing'):
294 revs = outgoing(dest, opts.get('rev'))
294 revs = outgoing(dest, opts.get('rev'))
295 if opts.get('bundle'):
295 if opts.get('bundle'):
296 opts['revs'] = revs
296 opts['revs'] = revs
297
297
298 # start
298 # start
299 if opts.get('date'):
299 if opts.get('date'):
300 start_time = util.parsedate(opts.get('date'))
300 start_time = util.parsedate(opts.get('date'))
301 else:
301 else:
302 start_time = util.makedate()
302 start_time = util.makedate()
303
303
304 def genmsgid(id):
304 def genmsgid(id):
305 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
305 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
306
306
307 def getdescription(body, sender):
307 def getdescription(body, sender):
308 if opts.get('desc'):
308 if opts.get('desc'):
309 body = open(opts.get('desc')).read()
309 body = open(opts.get('desc')).read()
310 else:
310 else:
311 ui.write(_('\nWrite the introductory message for the '
311 ui.write(_('\nWrite the introductory message for the '
312 'patch series.\n\n'))
312 'patch series.\n\n'))
313 body = ui.edit(body, sender)
313 body = ui.edit(body, sender)
314 return body
314 return body
315
315
316 def getpatchmsgs(patches, patchnames=None):
316 def getpatchmsgs(patches, patchnames=None):
317 jumbo = []
317 jumbo = []
318 msgs = []
318 msgs = []
319
319
320 ui.write(_('This patch series consists of %d patches.\n\n')
320 ui.write(_('This patch series consists of %d patches.\n\n')
321 % len(patches))
321 % len(patches))
322
322
323 name = None
323 name = None
324 for i, p in enumerate(patches):
324 for i, p in enumerate(patches):
325 jumbo.extend(p)
325 jumbo.extend(p)
326 if patchnames:
326 if patchnames:
327 name = patchnames[i]
327 name = patchnames[i]
328 msg = makepatch(ui, repo, p, opts, _charsets, i + 1,
328 msg = makepatch(ui, repo, p, opts, _charsets, i + 1,
329 len(patches), name)
329 len(patches), name)
330 msgs.append(msg)
330 msgs.append(msg)
331
331
332 if len(patches) > 1 or opts.get('intro'):
332 if len(patches) > 1 or opts.get('intro'):
333 tlen = len(str(len(patches)))
333 tlen = len(str(len(patches)))
334
334
335 flag = ' '.join(opts.get('flag'))
335 flag = ' '.join(opts.get('flag'))
336 if flag:
336 if flag:
337 subj = '[PATCH %0*d of %d %s]' % (tlen, 0, len(patches), flag)
337 subj = '[PATCH %0*d of %d %s]' % (tlen, 0, len(patches), flag)
338 else:
338 else:
339 subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches))
339 subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches))
340 subj += ' ' + (opts.get('subject') or
340 subj += ' ' + (opts.get('subject') or
341 prompt(ui, 'Subject: ', rest=subj))
341 prompt(ui, 'Subject: ', rest=subj))
342
342
343 body = ''
343 body = ''
344 if opts.get('diffstat'):
344 if opts.get('diffstat'):
345 d = cdiffstat(ui, _('Final summary:\n'), jumbo)
345 d = cdiffstat(ui, _('Final summary:\n'), jumbo)
346 if d:
346 if d:
347 body = '\n' + d
347 body = '\n' + d
348
348
349 body = getdescription(body, sender)
349 body = getdescription(body, sender)
350 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
350 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
351 msg['Subject'] = mail.headencode(ui, subj, _charsets,
351 msg['Subject'] = mail.headencode(ui, subj, _charsets,
352 opts.get('test'))
352 opts.get('test'))
353
353
354 msgs.insert(0, (msg, subj))
354 msgs.insert(0, (msg, subj))
355 return msgs
355 return msgs
356
356
357 def getbundlemsgs(bundle):
357 def getbundlemsgs(bundle):
358 subj = (opts.get('subject')
358 subj = (opts.get('subject')
359 or prompt(ui, 'Subject:', 'A bundle for your repository'))
359 or prompt(ui, 'Subject:', 'A bundle for your repository'))
360
360
361 body = getdescription('', sender)
361 body = getdescription('', sender)
362 msg = email.MIMEMultipart.MIMEMultipart()
362 msg = email.MIMEMultipart.MIMEMultipart()
363 if body:
363 if body:
364 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
364 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
365 datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
365 datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
366 datapart.set_payload(bundle)
366 datapart.set_payload(bundle)
367 bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
367 bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
368 datapart.add_header('Content-Disposition', 'attachment',
368 datapart.add_header('Content-Disposition', 'attachment',
369 filename=bundlename)
369 filename=bundlename)
370 email.Encoders.encode_base64(datapart)
370 email.Encoders.encode_base64(datapart)
371 msg.attach(datapart)
371 msg.attach(datapart)
372 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
372 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
373 return [(msg, subj)]
373 return [(msg, subj)]
374
374
375 sender = (opts.get('from') or ui.config('email', 'from') or
375 sender = (opts.get('from') or ui.config('email', 'from') or
376 ui.config('patchbomb', 'from') or
376 ui.config('patchbomb', 'from') or
377 prompt(ui, 'From', ui.username()))
377 prompt(ui, 'From', ui.username()))
378
378
379 # internal option used by pbranches
379 # internal option used by pbranches
380 patches = opts.get('patches')
380 patches = opts.get('patches')
381 if patches:
381 if patches:
382 msgs = getpatchmsgs(patches, opts.get('patchnames'))
382 msgs = getpatchmsgs(patches, opts.get('patchnames'))
383 elif opts.get('bundle'):
383 elif opts.get('bundle'):
384 msgs = getbundlemsgs(getbundle(dest))
384 msgs = getbundlemsgs(getbundle(dest))
385 else:
385 else:
386 msgs = getpatchmsgs(list(getpatches(revs)))
386 msgs = getpatchmsgs(list(getpatches(revs)))
387
387
388 def getaddrs(opt, prpt=None, default=None):
388 def getaddrs(opt, prpt=None, default=None):
389 if opts.get(opt):
389 if opts.get(opt):
390 return mail.addrlistencode(ui, opts.get(opt), _charsets,
390 return mail.addrlistencode(ui, opts.get(opt), _charsets,
391 opts.get('test'))
391 opts.get('test'))
392
392
393 addrs = (ui.config('email', opt) or
393 addrs = (ui.config('email', opt) or
394 ui.config('patchbomb', opt) or '')
394 ui.config('patchbomb', opt) or '')
395 if not addrs and prpt:
395 if not addrs and prpt:
396 addrs = prompt(ui, prpt, default)
396 addrs = prompt(ui, prpt, default)
397
397
398 return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test'))
398 return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test'))
399
399
400 to = getaddrs('to', 'To')
400 to = getaddrs('to', 'To')
401 cc = getaddrs('cc', 'Cc', '')
401 cc = getaddrs('cc', 'Cc', '')
402 bcc = getaddrs('bcc')
402 bcc = getaddrs('bcc')
403
403
404 ui.write('\n')
404 ui.write('\n')
405
405
406 parent = opts.get('in_reply_to') or None
406 parent = opts.get('in_reply_to') or None
407 # angle brackets may be omitted, they're not semantically part of the msg-id
407 # angle brackets may be omitted, they're not semantically part of the msg-id
408 if parent is not None:
408 if parent is not None:
409 if not parent.startswith('<'):
409 if not parent.startswith('<'):
410 parent = '<' + parent
410 parent = '<' + parent
411 if not parent.endswith('>'):
411 if not parent.endswith('>'):
412 parent += '>'
412 parent += '>'
413
413
414 first = True
414 first = True
415
415
416 sender_addr = email.Utils.parseaddr(sender)[1]
416 sender_addr = email.Utils.parseaddr(sender)[1]
417 sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
417 sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
418 sendmail = None
418 sendmail = None
419 for m, subj in msgs:
419 for m, subj in msgs:
420 try:
420 try:
421 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
421 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
422 except TypeError:
422 except TypeError:
423 m['Message-Id'] = genmsgid('patchbomb')
423 m['Message-Id'] = genmsgid('patchbomb')
424 if parent:
424 if parent:
425 m['In-Reply-To'] = parent
425 m['In-Reply-To'] = parent
426 m['References'] = parent
426 m['References'] = parent
427 if first:
427 if first:
428 parent = m['Message-Id']
428 parent = m['Message-Id']
429 first = False
429 first = False
430
430
431 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
431 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
432 m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)
432 m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)
433
433
434 start_time = (start_time[0] + 1, start_time[1])
434 start_time = (start_time[0] + 1, start_time[1])
435 m['From'] = sender
435 m['From'] = sender
436 m['To'] = ', '.join(to)
436 m['To'] = ', '.join(to)
437 if cc:
437 if cc:
438 m['Cc'] = ', '.join(cc)
438 m['Cc'] = ', '.join(cc)
439 if bcc:
439 if bcc:
440 m['Bcc'] = ', '.join(bcc)
440 m['Bcc'] = ', '.join(bcc)
441 if opts.get('test'):
441 if opts.get('test'):
442 ui.status(_('Displaying '), subj, ' ...\n')
442 ui.status(_('Displaying '), subj, ' ...\n')
443 ui.flush()
443 ui.flush()
444 if 'PAGER' in os.environ:
444 if 'PAGER' in os.environ:
445 fp = util.popen(os.environ['PAGER'], 'w')
445 fp = util.popen(os.environ['PAGER'], 'w')
446 else:
446 else:
447 fp = ui
447 fp = ui
448 generator = email.Generator.Generator(fp, mangle_from_=False)
448 generator = email.Generator.Generator(fp, mangle_from_=False)
449 try:
449 try:
450 generator.flatten(m, 0)
450 generator.flatten(m, 0)
451 fp.write('\n')
451 fp.write('\n')
452 except IOError, inst:
452 except IOError, inst:
453 if inst.errno != errno.EPIPE:
453 if inst.errno != errno.EPIPE:
454 raise
454 raise
455 if fp is not ui:
455 if fp is not ui:
456 fp.close()
456 fp.close()
457 elif opts.get('mbox'):
457 elif opts.get('mbox'):
458 ui.status(_('Writing '), subj, ' ...\n')
458 ui.status(_('Writing '), subj, ' ...\n')
459 fp = open(opts.get('mbox'), 'In-Reply-To' in m and 'ab+' or 'wb+')
459 fp = open(opts.get('mbox'), 'In-Reply-To' in m and 'ab+' or 'wb+')
460 generator = email.Generator.Generator(fp, mangle_from_=True)
460 generator = email.Generator.Generator(fp, mangle_from_=True)
461 # Should be time.asctime(), but Windows prints 2-characters day
461 # Should be time.asctime(), but Windows prints 2-characters day
462 # of month instead of one. Make them print the same thing.
462 # of month instead of one. Make them print the same thing.
463 date = time.strftime('%a %b %d %H:%M:%S %Y',
463 date = time.strftime('%a %b %d %H:%M:%S %Y',
464 time.localtime(start_time[0]))
464 time.localtime(start_time[0]))
465 fp.write('From %s %s\n' % (sender_addr, date))
465 fp.write('From %s %s\n' % (sender_addr, date))
466 generator.flatten(m, 0)
466 generator.flatten(m, 0)
467 fp.write('\n\n')
467 fp.write('\n\n')
468 fp.close()
468 fp.close()
469 else:
469 else:
470 if not sendmail:
470 if not sendmail:
471 sendmail = mail.connect(ui)
471 sendmail = mail.connect(ui)
472 ui.status(_('Sending '), subj, ' ...\n')
472 ui.status(_('Sending '), subj, ' ...\n')
473 # Exim does not remove the Bcc field
473 # Exim does not remove the Bcc field
474 del m['Bcc']
474 del m['Bcc']
475 fp = cStringIO.StringIO()
475 fp = cStringIO.StringIO()
476 generator = email.Generator.Generator(fp, mangle_from_=False)
476 generator = email.Generator.Generator(fp, mangle_from_=False)
477 generator.flatten(m, 0)
477 generator.flatten(m, 0)
478 sendmail(sender, to + bcc + cc, fp.getvalue())
478 sendmail(sender, to + bcc + cc, fp.getvalue())
479
479
480 emailopts = [
480 emailopts = [
481 ('a', 'attach', None, _('send patches as attachments')),
481 ('a', 'attach', None, _('send patches as attachments')),
482 ('i', 'inline', None, _('send patches as inline attachments')),
482 ('i', 'inline', None, _('send patches as inline attachments')),
483 ('', 'bcc', [], _('email addresses of blind carbon copy recipients')),
483 ('', 'bcc', [], _('email addresses of blind carbon copy recipients')),
484 ('c', 'cc', [], _('email addresses of copy recipients')),
484 ('c', 'cc', [], _('email addresses of copy recipients')),
485 ('d', 'diffstat', None, _('add diffstat output to messages')),
485 ('d', 'diffstat', None, _('add diffstat output to messages')),
486 ('', 'date', '', _('use the given date as the sending date')),
486 ('', 'date', '', _('use the given date as the sending date')),
487 ('', 'desc', '', _('use the given file as the series description')),
487 ('', 'desc', '', _('use the given file as the series description')),
488 ('f', 'from', '', _('email address of sender')),
488 ('f', 'from', '', _('email address of sender')),
489 ('n', 'test', None, _('print messages that would be sent')),
489 ('n', 'test', None, _('print messages that would be sent')),
490 ('m', 'mbox', '',
490 ('m', 'mbox', '',
491 _('write messages to mbox file instead of sending them')),
491 _('write messages to mbox file instead of sending them')),
492 ('s', 'subject', '',
492 ('s', 'subject', '',
493 _('subject of first message (intro or single patch)')),
493 _('subject of first message (intro or single patch)')),
494 ('', 'in-reply-to', '',
494 ('', 'in-reply-to', '',
495 _('message identifier to reply to')),
495 _('message identifier to reply to')),
496 ('', 'flag', [], _('flags to add in subject prefixes')),
496 ('', 'flag', [], _('flags to add in subject prefixes')),
497 ('t', 'to', [], _('email addresses of recipients')),
497 ('t', 'to', [], _('email addresses of recipients')),
498 ]
498 ]
499
499
500
500
501 cmdtable = {
501 cmdtable = {
502 "email":
502 "email":
503 (patchbomb,
503 (patchbomb,
504 [('g', 'git', None, _('use git extended diff format')),
504 [('g', 'git', None, _('use git extended diff format')),
505 ('', 'plain', None, _('omit hg patch header')),
505 ('', 'plain', None, _('omit hg patch header')),
506 ('o', 'outgoing', None,
506 ('o', 'outgoing', None,
507 _('send changes not found in the target repository')),
507 _('send changes not found in the target repository')),
508 ('b', 'bundle', None,
508 ('b', 'bundle', None,
509 _('send changes not in target as a binary bundle')),
509 _('send changes not in target as a binary bundle')),
510 ('', 'bundlename', 'bundle',
510 ('', 'bundlename', 'bundle',
511 _('name of the bundle attachment file')),
511 _('name of the bundle attachment file')),
512 ('r', 'rev', [], _('a revision to send')),
512 ('r', 'rev', [], _('a revision to send')),
513 ('', 'force', None,
513 ('', 'force', None,
514 _('run even when remote repository is unrelated '
514 _('run even when remote repository is unrelated '
515 '(with -b/--bundle)')),
515 '(with -b/--bundle)')),
516 ('', 'base', [],
516 ('', 'base', [],
517 _('a base changeset to specify instead of a destination '
517 _('a base changeset to specify instead of a destination '
518 '(with -b/--bundle)')),
518 '(with -b/--bundle)')),
519 ('', 'intro', None,
519 ('', 'intro', None,
520 _('send an introduction email for a single patch')),
520 _('send an introduction email for a single patch')),
521 ] + emailopts + commands.remoteopts,
521 ] + emailopts + commands.remoteopts,
522 _('hg email [OPTION]... [DEST]...'))
522 _('hg email [OPTION]... [DEST]...'))
523 }
523 }
@@ -1,1205 +1,1245 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, glob, tempfile
10 import os, sys, errno, re, glob, tempfile
11 import mdiff, bdiff, util, templater, patch, error, encoding, templatekw
11 import mdiff, bdiff, util, templater, patch, error, encoding, templatekw
12 import match as _match
12 import match as _match
13
13
14 revrangesep = ':'
14 revrangesep = ':'
15
15
16 def parsealiases(cmd):
16 def parsealiases(cmd):
17 return cmd.lstrip("^").split("|")
17 return cmd.lstrip("^").split("|")
18
18
19 def findpossible(cmd, table, strict=False):
19 def findpossible(cmd, table, strict=False):
20 """
20 """
21 Return cmd -> (aliases, command table entry)
21 Return cmd -> (aliases, command table entry)
22 for each matching command.
22 for each matching command.
23 Return debug commands (or their aliases) only if no normal command matches.
23 Return debug commands (or their aliases) only if no normal command matches.
24 """
24 """
25 choice = {}
25 choice = {}
26 debugchoice = {}
26 debugchoice = {}
27 for e in table.keys():
27 for e in table.keys():
28 aliases = parsealiases(e)
28 aliases = parsealiases(e)
29 found = None
29 found = None
30 if cmd in aliases:
30 if cmd in aliases:
31 found = cmd
31 found = cmd
32 elif not strict:
32 elif not strict:
33 for a in aliases:
33 for a in aliases:
34 if a.startswith(cmd):
34 if a.startswith(cmd):
35 found = a
35 found = a
36 break
36 break
37 if found is not None:
37 if found is not None:
38 if aliases[0].startswith("debug") or found.startswith("debug"):
38 if aliases[0].startswith("debug") or found.startswith("debug"):
39 debugchoice[found] = (aliases, table[e])
39 debugchoice[found] = (aliases, table[e])
40 else:
40 else:
41 choice[found] = (aliases, table[e])
41 choice[found] = (aliases, table[e])
42
42
43 if not choice and debugchoice:
43 if not choice and debugchoice:
44 choice = debugchoice
44 choice = debugchoice
45
45
46 return choice
46 return choice
47
47
48 def findcmd(cmd, table, strict=True):
48 def findcmd(cmd, table, strict=True):
49 """Return (aliases, command table entry) for command string."""
49 """Return (aliases, command table entry) for command string."""
50 choice = findpossible(cmd, table, strict)
50 choice = findpossible(cmd, table, strict)
51
51
52 if cmd in choice:
52 if cmd in choice:
53 return choice[cmd]
53 return choice[cmd]
54
54
55 if len(choice) > 1:
55 if len(choice) > 1:
56 clist = choice.keys()
56 clist = choice.keys()
57 clist.sort()
57 clist.sort()
58 raise error.AmbiguousCommand(cmd, clist)
58 raise error.AmbiguousCommand(cmd, clist)
59
59
60 if choice:
60 if choice:
61 return choice.values()[0]
61 return choice.values()[0]
62
62
63 raise error.UnknownCommand(cmd)
63 raise error.UnknownCommand(cmd)
64
64
65 def findrepo(p):
65 def findrepo(p):
66 while not os.path.isdir(os.path.join(p, ".hg")):
66 while not os.path.isdir(os.path.join(p, ".hg")):
67 oldp, p = p, os.path.dirname(p)
67 oldp, p = p, os.path.dirname(p)
68 if p == oldp:
68 if p == oldp:
69 return None
69 return None
70
70
71 return p
71 return p
72
72
73 def bail_if_changed(repo):
73 def bail_if_changed(repo):
74 if repo.dirstate.parents()[1] != nullid:
74 if repo.dirstate.parents()[1] != nullid:
75 raise util.Abort(_('outstanding uncommitted merge'))
75 raise util.Abort(_('outstanding uncommitted merge'))
76 modified, added, removed, deleted = repo.status()[:4]
76 modified, added, removed, deleted = repo.status()[:4]
77 if modified or added or removed or deleted:
77 if modified or added or removed or deleted:
78 raise util.Abort(_("outstanding uncommitted changes"))
78 raise util.Abort(_("outstanding uncommitted changes"))
79
79
80 def logmessage(opts):
80 def logmessage(opts):
81 """ get the log message according to -m and -l option """
81 """ get the log message according to -m and -l option """
82 message = opts.get('message')
82 message = opts.get('message')
83 logfile = opts.get('logfile')
83 logfile = opts.get('logfile')
84
84
85 if message and logfile:
85 if message and logfile:
86 raise util.Abort(_('options --message and --logfile are mutually '
86 raise util.Abort(_('options --message and --logfile are mutually '
87 'exclusive'))
87 'exclusive'))
88 if not message and logfile:
88 if not message and logfile:
89 try:
89 try:
90 if logfile == '-':
90 if logfile == '-':
91 message = sys.stdin.read()
91 message = sys.stdin.read()
92 else:
92 else:
93 message = open(logfile).read()
93 message = open(logfile).read()
94 except IOError, inst:
94 except IOError, inst:
95 raise util.Abort(_("can't read commit message '%s': %s") %
95 raise util.Abort(_("can't read commit message '%s': %s") %
96 (logfile, inst.strerror))
96 (logfile, inst.strerror))
97 return message
97 return message
98
98
99 def loglimit(opts):
99 def loglimit(opts):
100 """get the log limit according to option -l/--limit"""
100 """get the log limit according to option -l/--limit"""
101 limit = opts.get('limit')
101 limit = opts.get('limit')
102 if limit:
102 if limit:
103 try:
103 try:
104 limit = int(limit)
104 limit = int(limit)
105 except ValueError:
105 except ValueError:
106 raise util.Abort(_('limit must be a positive integer'))
106 raise util.Abort(_('limit must be a positive integer'))
107 if limit <= 0:
107 if limit <= 0:
108 raise util.Abort(_('limit must be positive'))
108 raise util.Abort(_('limit must be positive'))
109 else:
109 else:
110 limit = None
110 limit = None
111 return limit
111 return limit
112
112
113 def remoteui(src, opts):
113 def remoteui(src, opts):
114 'build a remote ui from ui or repo and opts'
114 'build a remote ui from ui or repo and opts'
115 if hasattr(src, 'baseui'): # looks like a repository
115 if hasattr(src, 'baseui'): # looks like a repository
116 dst = src.baseui.copy() # drop repo-specific config
116 dst = src.baseui.copy() # drop repo-specific config
117 src = src.ui # copy target options from repo
117 src = src.ui # copy target options from repo
118 else: # assume it's a global ui object
118 else: # assume it's a global ui object
119 dst = src.copy() # keep all global options
119 dst = src.copy() # keep all global options
120
120
121 # copy ssh-specific options
121 # copy ssh-specific options
122 for o in 'ssh', 'remotecmd':
122 for o in 'ssh', 'remotecmd':
123 v = opts.get(o) or src.config('ui', o)
123 v = opts.get(o) or src.config('ui', o)
124 if v:
124 if v:
125 dst.setconfig("ui", o, v)
125 dst.setconfig("ui", o, v)
126
126
127 # copy bundle-specific options
127 # copy bundle-specific options
128 r = src.config('bundle', 'mainreporoot')
128 r = src.config('bundle', 'mainreporoot')
129 if r:
129 if r:
130 dst.setconfig('bundle', 'mainreporoot', r)
130 dst.setconfig('bundle', 'mainreporoot', r)
131
131
132 # copy auth section settings
132 # copy auth section settings
133 for key, val in src.configitems('auth'):
133 for key, val in src.configitems('auth'):
134 dst.setconfig('auth', key, val)
134 dst.setconfig('auth', key, val)
135
135
136 return dst
136 return dst
137
137
138 def revpair(repo, revs):
138 def revpair(repo, revs):
139 '''return pair of nodes, given list of revisions. second item can
139 '''return pair of nodes, given list of revisions. second item can
140 be None, meaning use working dir.'''
140 be None, meaning use working dir.'''
141
141
142 def revfix(repo, val, defval):
142 def revfix(repo, val, defval):
143 if not val and val != 0 and defval is not None:
143 if not val and val != 0 and defval is not None:
144 val = defval
144 val = defval
145 return repo.lookup(val)
145 return repo.lookup(val)
146
146
147 if not revs:
147 if not revs:
148 return repo.dirstate.parents()[0], None
148 return repo.dirstate.parents()[0], None
149 end = None
149 end = None
150 if len(revs) == 1:
150 if len(revs) == 1:
151 if revrangesep in revs[0]:
151 if revrangesep in revs[0]:
152 start, end = revs[0].split(revrangesep, 1)
152 start, end = revs[0].split(revrangesep, 1)
153 start = revfix(repo, start, 0)
153 start = revfix(repo, start, 0)
154 end = revfix(repo, end, len(repo) - 1)
154 end = revfix(repo, end, len(repo) - 1)
155 else:
155 else:
156 start = revfix(repo, revs[0], None)
156 start = revfix(repo, revs[0], None)
157 elif len(revs) == 2:
157 elif len(revs) == 2:
158 if revrangesep in revs[0] or revrangesep in revs[1]:
158 if revrangesep in revs[0] or revrangesep in revs[1]:
159 raise util.Abort(_('too many revisions specified'))
159 raise util.Abort(_('too many revisions specified'))
160 start = revfix(repo, revs[0], None)
160 start = revfix(repo, revs[0], None)
161 end = revfix(repo, revs[1], None)
161 end = revfix(repo, revs[1], None)
162 else:
162 else:
163 raise util.Abort(_('too many revisions specified'))
163 raise util.Abort(_('too many revisions specified'))
164 return start, end
164 return start, end
165
165
166 def revrange(repo, revs):
166 def revrange(repo, revs):
167 """Yield revision as strings from a list of revision specifications."""
167 """Yield revision as strings from a list of revision specifications."""
168
168
169 def revfix(repo, val, defval):
169 def revfix(repo, val, defval):
170 if not val and val != 0 and defval is not None:
170 if not val and val != 0 and defval is not None:
171 return defval
171 return defval
172 return repo.changelog.rev(repo.lookup(val))
172 return repo.changelog.rev(repo.lookup(val))
173
173
174 seen, l = set(), []
174 seen, l = set(), []
175 for spec in revs:
175 for spec in revs:
176 if revrangesep in spec:
176 if revrangesep in spec:
177 start, end = spec.split(revrangesep, 1)
177 start, end = spec.split(revrangesep, 1)
178 start = revfix(repo, start, 0)
178 start = revfix(repo, start, 0)
179 end = revfix(repo, end, len(repo) - 1)
179 end = revfix(repo, end, len(repo) - 1)
180 step = start > end and -1 or 1
180 step = start > end and -1 or 1
181 for rev in xrange(start, end + step, step):
181 for rev in xrange(start, end + step, step):
182 if rev in seen:
182 if rev in seen:
183 continue
183 continue
184 seen.add(rev)
184 seen.add(rev)
185 l.append(rev)
185 l.append(rev)
186 else:
186 else:
187 rev = revfix(repo, spec, None)
187 rev = revfix(repo, spec, None)
188 if rev in seen:
188 if rev in seen:
189 continue
189 continue
190 seen.add(rev)
190 seen.add(rev)
191 l.append(rev)
191 l.append(rev)
192
192
193 return l
193 return l
194
194
195 def make_filename(repo, pat, node,
195 def make_filename(repo, pat, node,
196 total=None, seqno=None, revwidth=None, pathname=None):
196 total=None, seqno=None, revwidth=None, pathname=None):
197 node_expander = {
197 node_expander = {
198 'H': lambda: hex(node),
198 'H': lambda: hex(node),
199 'R': lambda: str(repo.changelog.rev(node)),
199 'R': lambda: str(repo.changelog.rev(node)),
200 'h': lambda: short(node),
200 'h': lambda: short(node),
201 }
201 }
202 expander = {
202 expander = {
203 '%': lambda: '%',
203 '%': lambda: '%',
204 'b': lambda: os.path.basename(repo.root),
204 'b': lambda: os.path.basename(repo.root),
205 }
205 }
206
206
207 try:
207 try:
208 if node:
208 if node:
209 expander.update(node_expander)
209 expander.update(node_expander)
210 if node:
210 if node:
211 expander['r'] = (lambda:
211 expander['r'] = (lambda:
212 str(repo.changelog.rev(node)).zfill(revwidth or 0))
212 str(repo.changelog.rev(node)).zfill(revwidth or 0))
213 if total is not None:
213 if total is not None:
214 expander['N'] = lambda: str(total)
214 expander['N'] = lambda: str(total)
215 if seqno is not None:
215 if seqno is not None:
216 expander['n'] = lambda: str(seqno)
216 expander['n'] = lambda: str(seqno)
217 if total is not None and seqno is not None:
217 if total is not None and seqno is not None:
218 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
218 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
219 if pathname is not None:
219 if pathname is not None:
220 expander['s'] = lambda: os.path.basename(pathname)
220 expander['s'] = lambda: os.path.basename(pathname)
221 expander['d'] = lambda: os.path.dirname(pathname) or '.'
221 expander['d'] = lambda: os.path.dirname(pathname) or '.'
222 expander['p'] = lambda: pathname
222 expander['p'] = lambda: pathname
223
223
224 newname = []
224 newname = []
225 patlen = len(pat)
225 patlen = len(pat)
226 i = 0
226 i = 0
227 while i < patlen:
227 while i < patlen:
228 c = pat[i]
228 c = pat[i]
229 if c == '%':
229 if c == '%':
230 i += 1
230 i += 1
231 c = pat[i]
231 c = pat[i]
232 c = expander[c]()
232 c = expander[c]()
233 newname.append(c)
233 newname.append(c)
234 i += 1
234 i += 1
235 return ''.join(newname)
235 return ''.join(newname)
236 except KeyError, inst:
236 except KeyError, inst:
237 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
237 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
238 inst.args[0])
238 inst.args[0])
239
239
240 def make_file(repo, pat, node=None,
240 def make_file(repo, pat, node=None,
241 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
241 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
242
242
243 writable = 'w' in mode or 'a' in mode
243 writable = 'w' in mode or 'a' in mode
244
244
245 if not pat or pat == '-':
245 if not pat or pat == '-':
246 return writable and sys.stdout or sys.stdin
246 return writable and sys.stdout or sys.stdin
247 if hasattr(pat, 'write') and writable:
247 if hasattr(pat, 'write') and writable:
248 return pat
248 return pat
249 if hasattr(pat, 'read') and 'r' in mode:
249 if hasattr(pat, 'read') and 'r' in mode:
250 return pat
250 return pat
251 return open(make_filename(repo, pat, node, total, seqno, revwidth,
251 return open(make_filename(repo, pat, node, total, seqno, revwidth,
252 pathname),
252 pathname),
253 mode)
253 mode)
254
254
255 def expandpats(pats):
255 def expandpats(pats):
256 if not util.expandglobs:
256 if not util.expandglobs:
257 return list(pats)
257 return list(pats)
258 ret = []
258 ret = []
259 for p in pats:
259 for p in pats:
260 kind, name = _match._patsplit(p, None)
260 kind, name = _match._patsplit(p, None)
261 if kind is None:
261 if kind is None:
262 try:
262 try:
263 globbed = glob.glob(name)
263 globbed = glob.glob(name)
264 except re.error:
264 except re.error:
265 globbed = [name]
265 globbed = [name]
266 if globbed:
266 if globbed:
267 ret.extend(globbed)
267 ret.extend(globbed)
268 continue
268 continue
269 ret.append(p)
269 ret.append(p)
270 return ret
270 return ret
271
271
272 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
272 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
273 if not globbed and default == 'relpath':
273 if not globbed and default == 'relpath':
274 pats = expandpats(pats or [])
274 pats = expandpats(pats or [])
275 m = _match.match(repo.root, repo.getcwd(), pats,
275 m = _match.match(repo.root, repo.getcwd(), pats,
276 opts.get('include'), opts.get('exclude'), default)
276 opts.get('include'), opts.get('exclude'), default)
277 def badfn(f, msg):
277 def badfn(f, msg):
278 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
278 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
279 m.bad = badfn
279 m.bad = badfn
280 return m
280 return m
281
281
282 def matchall(repo):
282 def matchall(repo):
283 return _match.always(repo.root, repo.getcwd())
283 return _match.always(repo.root, repo.getcwd())
284
284
285 def matchfiles(repo, files):
285 def matchfiles(repo, files):
286 return _match.exact(repo.root, repo.getcwd(), files)
286 return _match.exact(repo.root, repo.getcwd(), files)
287
287
288 def findrenames(repo, added, removed, threshold):
288 def findrenames(repo, added, removed, threshold):
289 '''find renamed files -- yields (before, after, score) tuples'''
289 '''find renamed files -- yields (before, after, score) tuples'''
290 copies = {}
290 copies = {}
291 ctx = repo['.']
291 ctx = repo['.']
292 for i, r in enumerate(removed):
292 for i, r in enumerate(removed):
293 repo.ui.progress(_('looking for similarities'), i, total=len(removed))
293 repo.ui.progress(_('looking for similarities'), i, total=len(removed))
294 if r not in ctx:
294 if r not in ctx:
295 continue
295 continue
296 fctx = ctx.filectx(r)
296 fctx = ctx.filectx(r)
297
297
298 # lazily load text
298 # lazily load text
299 @util.cachefunc
299 @util.cachefunc
300 def data():
300 def data():
301 orig = fctx.data()
301 orig = fctx.data()
302 return orig, mdiff.splitnewlines(orig)
302 return orig, mdiff.splitnewlines(orig)
303
303
304 def score(text):
304 def score(text):
305 if not len(text):
305 if not len(text):
306 return 0.0
306 return 0.0
307 if not fctx.cmp(text):
307 if not fctx.cmp(text):
308 return 1.0
308 return 1.0
309 if threshold == 1.0:
309 if threshold == 1.0:
310 return 0.0
310 return 0.0
311 orig, lines = data()
311 orig, lines = data()
312 # bdiff.blocks() returns blocks of matching lines
312 # bdiff.blocks() returns blocks of matching lines
313 # count the number of bytes in each
313 # count the number of bytes in each
314 equal = 0
314 equal = 0
315 matches = bdiff.blocks(text, orig)
315 matches = bdiff.blocks(text, orig)
316 for x1, x2, y1, y2 in matches:
316 for x1, x2, y1, y2 in matches:
317 for line in lines[y1:y2]:
317 for line in lines[y1:y2]:
318 equal += len(line)
318 equal += len(line)
319
319
320 lengths = len(text) + len(orig)
320 lengths = len(text) + len(orig)
321 return equal * 2.0 / lengths
321 return equal * 2.0 / lengths
322
322
323 for a in added:
323 for a in added:
324 bestscore = copies.get(a, (None, threshold))[1]
324 bestscore = copies.get(a, (None, threshold))[1]
325 myscore = score(repo.wread(a))
325 myscore = score(repo.wread(a))
326 if myscore >= bestscore:
326 if myscore >= bestscore:
327 copies[a] = (r, myscore)
327 copies[a] = (r, myscore)
328 repo.ui.progress(_('looking for similarities'), None, total=len(removed))
328 repo.ui.progress(_('looking for similarities'), None, total=len(removed))
329
329
330 for dest, v in copies.iteritems():
330 for dest, v in copies.iteritems():
331 source, score = v
331 source, score = v
332 yield source, dest, score
332 yield source, dest, score
333
333
334 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
334 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
335 if dry_run is None:
335 if dry_run is None:
336 dry_run = opts.get('dry_run')
336 dry_run = opts.get('dry_run')
337 if similarity is None:
337 if similarity is None:
338 similarity = float(opts.get('similarity') or 0)
338 similarity = float(opts.get('similarity') or 0)
339 # we'd use status here, except handling of symlinks and ignore is tricky
339 # we'd use status here, except handling of symlinks and ignore is tricky
340 added, unknown, deleted, removed = [], [], [], []
340 added, unknown, deleted, removed = [], [], [], []
341 audit_path = util.path_auditor(repo.root)
341 audit_path = util.path_auditor(repo.root)
342 m = match(repo, pats, opts)
342 m = match(repo, pats, opts)
343 for abs in repo.walk(m):
343 for abs in repo.walk(m):
344 target = repo.wjoin(abs)
344 target = repo.wjoin(abs)
345 good = True
345 good = True
346 try:
346 try:
347 audit_path(abs)
347 audit_path(abs)
348 except:
348 except:
349 good = False
349 good = False
350 rel = m.rel(abs)
350 rel = m.rel(abs)
351 exact = m.exact(abs)
351 exact = m.exact(abs)
352 if good and abs not in repo.dirstate:
352 if good and abs not in repo.dirstate:
353 unknown.append(abs)
353 unknown.append(abs)
354 if repo.ui.verbose or not exact:
354 if repo.ui.verbose or not exact:
355 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
355 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
356 elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
356 elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
357 or (os.path.isdir(target) and not os.path.islink(target))):
357 or (os.path.isdir(target) and not os.path.islink(target))):
358 deleted.append(abs)
358 deleted.append(abs)
359 if repo.ui.verbose or not exact:
359 if repo.ui.verbose or not exact:
360 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
360 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
361 # for finding renames
361 # for finding renames
362 elif repo.dirstate[abs] == 'r':
362 elif repo.dirstate[abs] == 'r':
363 removed.append(abs)
363 removed.append(abs)
364 elif repo.dirstate[abs] == 'a':
364 elif repo.dirstate[abs] == 'a':
365 added.append(abs)
365 added.append(abs)
366 copies = {}
366 copies = {}
367 if similarity > 0:
367 if similarity > 0:
368 for old, new, score in findrenames(repo, added + unknown,
368 for old, new, score in findrenames(repo, added + unknown,
369 removed + deleted, similarity):
369 removed + deleted, similarity):
370 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
370 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
371 repo.ui.status(_('recording removal of %s as rename to %s '
371 repo.ui.status(_('recording removal of %s as rename to %s '
372 '(%d%% similar)\n') %
372 '(%d%% similar)\n') %
373 (m.rel(old), m.rel(new), score * 100))
373 (m.rel(old), m.rel(new), score * 100))
374 copies[new] = old
374 copies[new] = old
375
375
376 if not dry_run:
376 if not dry_run:
377 wlock = repo.wlock()
377 wlock = repo.wlock()
378 try:
378 try:
379 repo.remove(deleted)
379 repo.remove(deleted)
380 repo.add(unknown)
380 repo.add(unknown)
381 for new, old in copies.iteritems():
381 for new, old in copies.iteritems():
382 repo.copy(old, new)
382 repo.copy(old, new)
383 finally:
383 finally:
384 wlock.release()
384 wlock.release()
385
385
386 def copy(ui, repo, pats, opts, rename=False):
386 def copy(ui, repo, pats, opts, rename=False):
387 # called with the repo lock held
387 # called with the repo lock held
388 #
388 #
389 # hgsep => pathname that uses "/" to separate directories
389 # hgsep => pathname that uses "/" to separate directories
390 # ossep => pathname that uses os.sep to separate directories
390 # ossep => pathname that uses os.sep to separate directories
391 cwd = repo.getcwd()
391 cwd = repo.getcwd()
392 targets = {}
392 targets = {}
393 after = opts.get("after")
393 after = opts.get("after")
394 dryrun = opts.get("dry_run")
394 dryrun = opts.get("dry_run")
395
395
396 def walkpat(pat):
396 def walkpat(pat):
397 srcs = []
397 srcs = []
398 m = match(repo, [pat], opts, globbed=True)
398 m = match(repo, [pat], opts, globbed=True)
399 for abs in repo.walk(m):
399 for abs in repo.walk(m):
400 state = repo.dirstate[abs]
400 state = repo.dirstate[abs]
401 rel = m.rel(abs)
401 rel = m.rel(abs)
402 exact = m.exact(abs)
402 exact = m.exact(abs)
403 if state in '?r':
403 if state in '?r':
404 if exact and state == '?':
404 if exact and state == '?':
405 ui.warn(_('%s: not copying - file is not managed\n') % rel)
405 ui.warn(_('%s: not copying - file is not managed\n') % rel)
406 if exact and state == 'r':
406 if exact and state == 'r':
407 ui.warn(_('%s: not copying - file has been marked for'
407 ui.warn(_('%s: not copying - file has been marked for'
408 ' remove\n') % rel)
408 ' remove\n') % rel)
409 continue
409 continue
410 # abs: hgsep
410 # abs: hgsep
411 # rel: ossep
411 # rel: ossep
412 srcs.append((abs, rel, exact))
412 srcs.append((abs, rel, exact))
413 return srcs
413 return srcs
414
414
415 # abssrc: hgsep
415 # abssrc: hgsep
416 # relsrc: ossep
416 # relsrc: ossep
417 # otarget: ossep
417 # otarget: ossep
418 def copyfile(abssrc, relsrc, otarget, exact):
418 def copyfile(abssrc, relsrc, otarget, exact):
419 abstarget = util.canonpath(repo.root, cwd, otarget)
419 abstarget = util.canonpath(repo.root, cwd, otarget)
420 reltarget = repo.pathto(abstarget, cwd)
420 reltarget = repo.pathto(abstarget, cwd)
421 target = repo.wjoin(abstarget)
421 target = repo.wjoin(abstarget)
422 src = repo.wjoin(abssrc)
422 src = repo.wjoin(abssrc)
423 state = repo.dirstate[abstarget]
423 state = repo.dirstate[abstarget]
424
424
425 # check for collisions
425 # check for collisions
426 prevsrc = targets.get(abstarget)
426 prevsrc = targets.get(abstarget)
427 if prevsrc is not None:
427 if prevsrc is not None:
428 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
428 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
429 (reltarget, repo.pathto(abssrc, cwd),
429 (reltarget, repo.pathto(abssrc, cwd),
430 repo.pathto(prevsrc, cwd)))
430 repo.pathto(prevsrc, cwd)))
431 return
431 return
432
432
433 # check for overwrites
433 # check for overwrites
434 exists = os.path.exists(target)
434 exists = os.path.exists(target)
435 if not after and exists or after and state in 'mn':
435 if not after and exists or after and state in 'mn':
436 if not opts['force']:
436 if not opts['force']:
437 ui.warn(_('%s: not overwriting - file exists\n') %
437 ui.warn(_('%s: not overwriting - file exists\n') %
438 reltarget)
438 reltarget)
439 return
439 return
440
440
441 if after:
441 if after:
442 if not exists:
442 if not exists:
443 return
443 return
444 elif not dryrun:
444 elif not dryrun:
445 try:
445 try:
446 if exists:
446 if exists:
447 os.unlink(target)
447 os.unlink(target)
448 targetdir = os.path.dirname(target) or '.'
448 targetdir = os.path.dirname(target) or '.'
449 if not os.path.isdir(targetdir):
449 if not os.path.isdir(targetdir):
450 os.makedirs(targetdir)
450 os.makedirs(targetdir)
451 util.copyfile(src, target)
451 util.copyfile(src, target)
452 except IOError, inst:
452 except IOError, inst:
453 if inst.errno == errno.ENOENT:
453 if inst.errno == errno.ENOENT:
454 ui.warn(_('%s: deleted in working copy\n') % relsrc)
454 ui.warn(_('%s: deleted in working copy\n') % relsrc)
455 else:
455 else:
456 ui.warn(_('%s: cannot copy - %s\n') %
456 ui.warn(_('%s: cannot copy - %s\n') %
457 (relsrc, inst.strerror))
457 (relsrc, inst.strerror))
458 return True # report a failure
458 return True # report a failure
459
459
460 if ui.verbose or not exact:
460 if ui.verbose or not exact:
461 if rename:
461 if rename:
462 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
462 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
463 else:
463 else:
464 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
464 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
465
465
466 targets[abstarget] = abssrc
466 targets[abstarget] = abssrc
467
467
468 # fix up dirstate
468 # fix up dirstate
469 origsrc = repo.dirstate.copied(abssrc) or abssrc
469 origsrc = repo.dirstate.copied(abssrc) or abssrc
470 if abstarget == origsrc: # copying back a copy?
470 if abstarget == origsrc: # copying back a copy?
471 if state not in 'mn' and not dryrun:
471 if state not in 'mn' and not dryrun:
472 repo.dirstate.normallookup(abstarget)
472 repo.dirstate.normallookup(abstarget)
473 else:
473 else:
474 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
474 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
475 if not ui.quiet:
475 if not ui.quiet:
476 ui.warn(_("%s has not been committed yet, so no copy "
476 ui.warn(_("%s has not been committed yet, so no copy "
477 "data will be stored for %s.\n")
477 "data will be stored for %s.\n")
478 % (repo.pathto(origsrc, cwd), reltarget))
478 % (repo.pathto(origsrc, cwd), reltarget))
479 if repo.dirstate[abstarget] in '?r' and not dryrun:
479 if repo.dirstate[abstarget] in '?r' and not dryrun:
480 repo.add([abstarget])
480 repo.add([abstarget])
481 elif not dryrun:
481 elif not dryrun:
482 repo.copy(origsrc, abstarget)
482 repo.copy(origsrc, abstarget)
483
483
484 if rename and not dryrun:
484 if rename and not dryrun:
485 repo.remove([abssrc], not after)
485 repo.remove([abssrc], not after)
486
486
487 # pat: ossep
487 # pat: ossep
488 # dest ossep
488 # dest ossep
489 # srcs: list of (hgsep, hgsep, ossep, bool)
489 # srcs: list of (hgsep, hgsep, ossep, bool)
490 # return: function that takes hgsep and returns ossep
490 # return: function that takes hgsep and returns ossep
491 def targetpathfn(pat, dest, srcs):
491 def targetpathfn(pat, dest, srcs):
492 if os.path.isdir(pat):
492 if os.path.isdir(pat):
493 abspfx = util.canonpath(repo.root, cwd, pat)
493 abspfx = util.canonpath(repo.root, cwd, pat)
494 abspfx = util.localpath(abspfx)
494 abspfx = util.localpath(abspfx)
495 if destdirexists:
495 if destdirexists:
496 striplen = len(os.path.split(abspfx)[0])
496 striplen = len(os.path.split(abspfx)[0])
497 else:
497 else:
498 striplen = len(abspfx)
498 striplen = len(abspfx)
499 if striplen:
499 if striplen:
500 striplen += len(os.sep)
500 striplen += len(os.sep)
501 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
501 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
502 elif destdirexists:
502 elif destdirexists:
503 res = lambda p: os.path.join(dest,
503 res = lambda p: os.path.join(dest,
504 os.path.basename(util.localpath(p)))
504 os.path.basename(util.localpath(p)))
505 else:
505 else:
506 res = lambda p: dest
506 res = lambda p: dest
507 return res
507 return res
508
508
509 # pat: ossep
509 # pat: ossep
510 # dest ossep
510 # dest ossep
511 # srcs: list of (hgsep, hgsep, ossep, bool)
511 # srcs: list of (hgsep, hgsep, ossep, bool)
512 # return: function that takes hgsep and returns ossep
512 # return: function that takes hgsep and returns ossep
513 def targetpathafterfn(pat, dest, srcs):
513 def targetpathafterfn(pat, dest, srcs):
514 if _match.patkind(pat):
514 if _match.patkind(pat):
515 # a mercurial pattern
515 # a mercurial pattern
516 res = lambda p: os.path.join(dest,
516 res = lambda p: os.path.join(dest,
517 os.path.basename(util.localpath(p)))
517 os.path.basename(util.localpath(p)))
518 else:
518 else:
519 abspfx = util.canonpath(repo.root, cwd, pat)
519 abspfx = util.canonpath(repo.root, cwd, pat)
520 if len(abspfx) < len(srcs[0][0]):
520 if len(abspfx) < len(srcs[0][0]):
521 # A directory. Either the target path contains the last
521 # A directory. Either the target path contains the last
522 # component of the source path or it does not.
522 # component of the source path or it does not.
523 def evalpath(striplen):
523 def evalpath(striplen):
524 score = 0
524 score = 0
525 for s in srcs:
525 for s in srcs:
526 t = os.path.join(dest, util.localpath(s[0])[striplen:])
526 t = os.path.join(dest, util.localpath(s[0])[striplen:])
527 if os.path.exists(t):
527 if os.path.exists(t):
528 score += 1
528 score += 1
529 return score
529 return score
530
530
531 abspfx = util.localpath(abspfx)
531 abspfx = util.localpath(abspfx)
532 striplen = len(abspfx)
532 striplen = len(abspfx)
533 if striplen:
533 if striplen:
534 striplen += len(os.sep)
534 striplen += len(os.sep)
535 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
535 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
536 score = evalpath(striplen)
536 score = evalpath(striplen)
537 striplen1 = len(os.path.split(abspfx)[0])
537 striplen1 = len(os.path.split(abspfx)[0])
538 if striplen1:
538 if striplen1:
539 striplen1 += len(os.sep)
539 striplen1 += len(os.sep)
540 if evalpath(striplen1) > score:
540 if evalpath(striplen1) > score:
541 striplen = striplen1
541 striplen = striplen1
542 res = lambda p: os.path.join(dest,
542 res = lambda p: os.path.join(dest,
543 util.localpath(p)[striplen:])
543 util.localpath(p)[striplen:])
544 else:
544 else:
545 # a file
545 # a file
546 if destdirexists:
546 if destdirexists:
547 res = lambda p: os.path.join(dest,
547 res = lambda p: os.path.join(dest,
548 os.path.basename(util.localpath(p)))
548 os.path.basename(util.localpath(p)))
549 else:
549 else:
550 res = lambda p: dest
550 res = lambda p: dest
551 return res
551 return res
552
552
553
553
554 pats = expandpats(pats)
554 pats = expandpats(pats)
555 if not pats:
555 if not pats:
556 raise util.Abort(_('no source or destination specified'))
556 raise util.Abort(_('no source or destination specified'))
557 if len(pats) == 1:
557 if len(pats) == 1:
558 raise util.Abort(_('no destination specified'))
558 raise util.Abort(_('no destination specified'))
559 dest = pats.pop()
559 dest = pats.pop()
560 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
560 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
561 if not destdirexists:
561 if not destdirexists:
562 if len(pats) > 1 or _match.patkind(pats[0]):
562 if len(pats) > 1 or _match.patkind(pats[0]):
563 raise util.Abort(_('with multiple sources, destination must be an '
563 raise util.Abort(_('with multiple sources, destination must be an '
564 'existing directory'))
564 'existing directory'))
565 if util.endswithsep(dest):
565 if util.endswithsep(dest):
566 raise util.Abort(_('destination %s is not a directory') % dest)
566 raise util.Abort(_('destination %s is not a directory') % dest)
567
567
568 tfn = targetpathfn
568 tfn = targetpathfn
569 if after:
569 if after:
570 tfn = targetpathafterfn
570 tfn = targetpathafterfn
571 copylist = []
571 copylist = []
572 for pat in pats:
572 for pat in pats:
573 srcs = walkpat(pat)
573 srcs = walkpat(pat)
574 if not srcs:
574 if not srcs:
575 continue
575 continue
576 copylist.append((tfn(pat, dest, srcs), srcs))
576 copylist.append((tfn(pat, dest, srcs), srcs))
577 if not copylist:
577 if not copylist:
578 raise util.Abort(_('no files to copy'))
578 raise util.Abort(_('no files to copy'))
579
579
580 errors = 0
580 errors = 0
581 for targetpath, srcs in copylist:
581 for targetpath, srcs in copylist:
582 for abssrc, relsrc, exact in srcs:
582 for abssrc, relsrc, exact in srcs:
583 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
583 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
584 errors += 1
584 errors += 1
585
585
586 if errors:
586 if errors:
587 ui.warn(_('(consider using --after)\n'))
587 ui.warn(_('(consider using --after)\n'))
588
588
589 return errors
589 return errors
590
590
591 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
591 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
592 runargs=None, appendpid=False):
592 runargs=None, appendpid=False):
593 '''Run a command as a service.'''
593 '''Run a command as a service.'''
594
594
595 if opts['daemon'] and not opts['daemon_pipefds']:
595 if opts['daemon'] and not opts['daemon_pipefds']:
596 # Signal child process startup with file removal
596 # Signal child process startup with file removal
597 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
597 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
598 os.close(lockfd)
598 os.close(lockfd)
599 try:
599 try:
600 if not runargs:
600 if not runargs:
601 runargs = util.hgcmd() + sys.argv[1:]
601 runargs = util.hgcmd() + sys.argv[1:]
602 runargs.append('--daemon-pipefds=%s' % lockpath)
602 runargs.append('--daemon-pipefds=%s' % lockpath)
603 # Don't pass --cwd to the child process, because we've already
603 # Don't pass --cwd to the child process, because we've already
604 # changed directory.
604 # changed directory.
605 for i in xrange(1, len(runargs)):
605 for i in xrange(1, len(runargs)):
606 if runargs[i].startswith('--cwd='):
606 if runargs[i].startswith('--cwd='):
607 del runargs[i]
607 del runargs[i]
608 break
608 break
609 elif runargs[i].startswith('--cwd'):
609 elif runargs[i].startswith('--cwd'):
610 del runargs[i:i + 2]
610 del runargs[i:i + 2]
611 break
611 break
612 def condfn():
612 def condfn():
613 return not os.path.exists(lockpath)
613 return not os.path.exists(lockpath)
614 pid = util.rundetached(runargs, condfn)
614 pid = util.rundetached(runargs, condfn)
615 if pid < 0:
615 if pid < 0:
616 raise util.Abort(_('child process failed to start'))
616 raise util.Abort(_('child process failed to start'))
617 finally:
617 finally:
618 try:
618 try:
619 os.unlink(lockpath)
619 os.unlink(lockpath)
620 except OSError, e:
620 except OSError, e:
621 if e.errno != errno.ENOENT:
621 if e.errno != errno.ENOENT:
622 raise
622 raise
623 if parentfn:
623 if parentfn:
624 return parentfn(pid)
624 return parentfn(pid)
625 else:
625 else:
626 return
626 return
627
627
628 if initfn:
628 if initfn:
629 initfn()
629 initfn()
630
630
631 if opts['pid_file']:
631 if opts['pid_file']:
632 mode = appendpid and 'a' or 'w'
632 mode = appendpid and 'a' or 'w'
633 fp = open(opts['pid_file'], mode)
633 fp = open(opts['pid_file'], mode)
634 fp.write(str(os.getpid()) + '\n')
634 fp.write(str(os.getpid()) + '\n')
635 fp.close()
635 fp.close()
636
636
637 if opts['daemon_pipefds']:
637 if opts['daemon_pipefds']:
638 lockpath = opts['daemon_pipefds']
638 lockpath = opts['daemon_pipefds']
639 try:
639 try:
640 os.setsid()
640 os.setsid()
641 except AttributeError:
641 except AttributeError:
642 pass
642 pass
643 os.unlink(lockpath)
643 os.unlink(lockpath)
644 util.hidewindow()
644 util.hidewindow()
645 sys.stdout.flush()
645 sys.stdout.flush()
646 sys.stderr.flush()
646 sys.stderr.flush()
647
647
648 nullfd = os.open(util.nulldev, os.O_RDWR)
648 nullfd = os.open(util.nulldev, os.O_RDWR)
649 logfilefd = nullfd
649 logfilefd = nullfd
650 if logfile:
650 if logfile:
651 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
651 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
652 os.dup2(nullfd, 0)
652 os.dup2(nullfd, 0)
653 os.dup2(logfilefd, 1)
653 os.dup2(logfilefd, 1)
654 os.dup2(logfilefd, 2)
654 os.dup2(logfilefd, 2)
655 if nullfd not in (0, 1, 2):
655 if nullfd not in (0, 1, 2):
656 os.close(nullfd)
656 os.close(nullfd)
657 if logfile and logfilefd not in (0, 1, 2):
657 if logfile and logfilefd not in (0, 1, 2):
658 os.close(logfilefd)
658 os.close(logfilefd)
659
659
660 if runfn:
660 if runfn:
661 return runfn()
661 return runfn()
662
662
663 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
664 opts=None):
665 '''export changesets as hg patches.'''
666
667 total = len(revs)
668 revwidth = max([len(str(rev)) for rev in revs])
669
670 def single(rev, seqno, fp):
671 ctx = repo[rev]
672 node = ctx.node()
673 parents = [p.node() for p in ctx.parents() if p]
674 branch = ctx.branch()
675 if switch_parent:
676 parents.reverse()
677 prev = (parents and parents[0]) or nullid
678
679 if not fp:
680 fp = make_file(repo, template, node, total=total, seqno=seqno,
681 revwidth=revwidth, mode='ab')
682 if fp != sys.stdout and hasattr(fp, 'name'):
683 repo.ui.note("%s\n" % fp.name)
684
685 fp.write("# HG changeset patch\n")
686 fp.write("# User %s\n" % ctx.user())
687 fp.write("# Date %d %d\n" % ctx.date())
688 if branch and (branch != 'default'):
689 fp.write("# Branch %s\n" % branch)
690 fp.write("# Node ID %s\n" % hex(node))
691 fp.write("# Parent %s\n" % hex(prev))
692 if len(parents) > 1:
693 fp.write("# Parent %s\n" % hex(parents[1]))
694 fp.write(ctx.description().rstrip())
695 fp.write("\n\n")
696
697 for chunk in patch.diff(repo, prev, node, opts=opts):
698 fp.write(chunk)
699
700 for seqno, rev in enumerate(revs):
701 single(rev, seqno + 1, fp)
702
663 class changeset_printer(object):
703 class changeset_printer(object):
664 '''show changeset information when templating not requested.'''
704 '''show changeset information when templating not requested.'''
665
705
666 def __init__(self, ui, repo, patch, diffopts, buffered):
706 def __init__(self, ui, repo, patch, diffopts, buffered):
667 self.ui = ui
707 self.ui = ui
668 self.repo = repo
708 self.repo = repo
669 self.buffered = buffered
709 self.buffered = buffered
670 self.patch = patch
710 self.patch = patch
671 self.diffopts = diffopts
711 self.diffopts = diffopts
672 self.header = {}
712 self.header = {}
673 self.hunk = {}
713 self.hunk = {}
674 self.lastheader = None
714 self.lastheader = None
675 self.footer = None
715 self.footer = None
676
716
677 def flush(self, rev):
717 def flush(self, rev):
678 if rev in self.header:
718 if rev in self.header:
679 h = self.header[rev]
719 h = self.header[rev]
680 if h != self.lastheader:
720 if h != self.lastheader:
681 self.lastheader = h
721 self.lastheader = h
682 self.ui.write(h)
722 self.ui.write(h)
683 del self.header[rev]
723 del self.header[rev]
684 if rev in self.hunk:
724 if rev in self.hunk:
685 self.ui.write(self.hunk[rev])
725 self.ui.write(self.hunk[rev])
686 del self.hunk[rev]
726 del self.hunk[rev]
687 return 1
727 return 1
688 return 0
728 return 0
689
729
690 def close(self):
730 def close(self):
691 if self.footer:
731 if self.footer:
692 self.ui.write(self.footer)
732 self.ui.write(self.footer)
693
733
694 def show(self, ctx, copies=None, **props):
734 def show(self, ctx, copies=None, **props):
695 if self.buffered:
735 if self.buffered:
696 self.ui.pushbuffer()
736 self.ui.pushbuffer()
697 self._show(ctx, copies, props)
737 self._show(ctx, copies, props)
698 self.hunk[ctx.rev()] = self.ui.popbuffer()
738 self.hunk[ctx.rev()] = self.ui.popbuffer()
699 else:
739 else:
700 self._show(ctx, copies, props)
740 self._show(ctx, copies, props)
701
741
702 def _show(self, ctx, copies, props):
742 def _show(self, ctx, copies, props):
703 '''show a single changeset or file revision'''
743 '''show a single changeset or file revision'''
704 changenode = ctx.node()
744 changenode = ctx.node()
705 rev = ctx.rev()
745 rev = ctx.rev()
706
746
707 if self.ui.quiet:
747 if self.ui.quiet:
708 self.ui.write("%d:%s\n" % (rev, short(changenode)))
748 self.ui.write("%d:%s\n" % (rev, short(changenode)))
709 return
749 return
710
750
711 log = self.repo.changelog
751 log = self.repo.changelog
712 date = util.datestr(ctx.date())
752 date = util.datestr(ctx.date())
713
753
714 hexfunc = self.ui.debugflag and hex or short
754 hexfunc = self.ui.debugflag and hex or short
715
755
716 parents = [(p, hexfunc(log.node(p)))
756 parents = [(p, hexfunc(log.node(p)))
717 for p in self._meaningful_parentrevs(log, rev)]
757 for p in self._meaningful_parentrevs(log, rev)]
718
758
719 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
759 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
720
760
721 branch = ctx.branch()
761 branch = ctx.branch()
722 # don't show the default branch name
762 # don't show the default branch name
723 if branch != 'default':
763 if branch != 'default':
724 branch = encoding.tolocal(branch)
764 branch = encoding.tolocal(branch)
725 self.ui.write(_("branch: %s\n") % branch)
765 self.ui.write(_("branch: %s\n") % branch)
726 for tag in self.repo.nodetags(changenode):
766 for tag in self.repo.nodetags(changenode):
727 self.ui.write(_("tag: %s\n") % tag)
767 self.ui.write(_("tag: %s\n") % tag)
728 for parent in parents:
768 for parent in parents:
729 self.ui.write(_("parent: %d:%s\n") % parent)
769 self.ui.write(_("parent: %d:%s\n") % parent)
730
770
731 if self.ui.debugflag:
771 if self.ui.debugflag:
732 mnode = ctx.manifestnode()
772 mnode = ctx.manifestnode()
733 self.ui.write(_("manifest: %d:%s\n") %
773 self.ui.write(_("manifest: %d:%s\n") %
734 (self.repo.manifest.rev(mnode), hex(mnode)))
774 (self.repo.manifest.rev(mnode), hex(mnode)))
735 self.ui.write(_("user: %s\n") % ctx.user())
775 self.ui.write(_("user: %s\n") % ctx.user())
736 self.ui.write(_("date: %s\n") % date)
776 self.ui.write(_("date: %s\n") % date)
737
777
738 if self.ui.debugflag:
778 if self.ui.debugflag:
739 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
779 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
740 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
780 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
741 files):
781 files):
742 if value:
782 if value:
743 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
783 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
744 elif ctx.files() and self.ui.verbose:
784 elif ctx.files() and self.ui.verbose:
745 self.ui.write(_("files: %s\n") % " ".join(ctx.files()))
785 self.ui.write(_("files: %s\n") % " ".join(ctx.files()))
746 if copies and self.ui.verbose:
786 if copies and self.ui.verbose:
747 copies = ['%s (%s)' % c for c in copies]
787 copies = ['%s (%s)' % c for c in copies]
748 self.ui.write(_("copies: %s\n") % ' '.join(copies))
788 self.ui.write(_("copies: %s\n") % ' '.join(copies))
749
789
750 extra = ctx.extra()
790 extra = ctx.extra()
751 if extra and self.ui.debugflag:
791 if extra and self.ui.debugflag:
752 for key, value in sorted(extra.items()):
792 for key, value in sorted(extra.items()):
753 self.ui.write(_("extra: %s=%s\n")
793 self.ui.write(_("extra: %s=%s\n")
754 % (key, value.encode('string_escape')))
794 % (key, value.encode('string_escape')))
755
795
756 description = ctx.description().strip()
796 description = ctx.description().strip()
757 if description:
797 if description:
758 if self.ui.verbose:
798 if self.ui.verbose:
759 self.ui.write(_("description:\n"))
799 self.ui.write(_("description:\n"))
760 self.ui.write(description)
800 self.ui.write(description)
761 self.ui.write("\n\n")
801 self.ui.write("\n\n")
762 else:
802 else:
763 self.ui.write(_("summary: %s\n") %
803 self.ui.write(_("summary: %s\n") %
764 description.splitlines()[0])
804 description.splitlines()[0])
765 self.ui.write("\n")
805 self.ui.write("\n")
766
806
767 self.showpatch(changenode)
807 self.showpatch(changenode)
768
808
769 def showpatch(self, node):
809 def showpatch(self, node):
770 if self.patch:
810 if self.patch:
771 prev = self.repo.changelog.parents(node)[0]
811 prev = self.repo.changelog.parents(node)[0]
772 chunks = patch.diff(self.repo, prev, node, match=self.patch,
812 chunks = patch.diff(self.repo, prev, node, match=self.patch,
773 opts=patch.diffopts(self.ui, self.diffopts))
813 opts=patch.diffopts(self.ui, self.diffopts))
774 for chunk in chunks:
814 for chunk in chunks:
775 self.ui.write(chunk)
815 self.ui.write(chunk)
776 self.ui.write("\n")
816 self.ui.write("\n")
777
817
778 def _meaningful_parentrevs(self, log, rev):
818 def _meaningful_parentrevs(self, log, rev):
779 """Return list of meaningful (or all if debug) parentrevs for rev.
819 """Return list of meaningful (or all if debug) parentrevs for rev.
780
820
781 For merges (two non-nullrev revisions) both parents are meaningful.
821 For merges (two non-nullrev revisions) both parents are meaningful.
782 Otherwise the first parent revision is considered meaningful if it
822 Otherwise the first parent revision is considered meaningful if it
783 is not the preceding revision.
823 is not the preceding revision.
784 """
824 """
785 parents = log.parentrevs(rev)
825 parents = log.parentrevs(rev)
786 if not self.ui.debugflag and parents[1] == nullrev:
826 if not self.ui.debugflag and parents[1] == nullrev:
787 if parents[0] >= rev - 1:
827 if parents[0] >= rev - 1:
788 parents = []
828 parents = []
789 else:
829 else:
790 parents = [parents[0]]
830 parents = [parents[0]]
791 return parents
831 return parents
792
832
793
833
794 class changeset_templater(changeset_printer):
834 class changeset_templater(changeset_printer):
795 '''format changeset information.'''
835 '''format changeset information.'''
796
836
797 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
837 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
798 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
838 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
799 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
839 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
800 defaulttempl = {
840 defaulttempl = {
801 'parent': '{rev}:{node|formatnode} ',
841 'parent': '{rev}:{node|formatnode} ',
802 'manifest': '{rev}:{node|formatnode}',
842 'manifest': '{rev}:{node|formatnode}',
803 'file_copy': '{name} ({source})',
843 'file_copy': '{name} ({source})',
804 'extra': '{key}={value|stringescape}'
844 'extra': '{key}={value|stringescape}'
805 }
845 }
806 # filecopy is preserved for compatibility reasons
846 # filecopy is preserved for compatibility reasons
807 defaulttempl['filecopy'] = defaulttempl['file_copy']
847 defaulttempl['filecopy'] = defaulttempl['file_copy']
808 self.t = templater.templater(mapfile, {'formatnode': formatnode},
848 self.t = templater.templater(mapfile, {'formatnode': formatnode},
809 cache=defaulttempl)
849 cache=defaulttempl)
810 self.cache = {}
850 self.cache = {}
811
851
812 def use_template(self, t):
852 def use_template(self, t):
813 '''set template string to use'''
853 '''set template string to use'''
814 self.t.cache['changeset'] = t
854 self.t.cache['changeset'] = t
815
855
816 def _meaningful_parentrevs(self, ctx):
856 def _meaningful_parentrevs(self, ctx):
817 """Return list of meaningful (or all if debug) parentrevs for rev.
857 """Return list of meaningful (or all if debug) parentrevs for rev.
818 """
858 """
819 parents = ctx.parents()
859 parents = ctx.parents()
820 if len(parents) > 1:
860 if len(parents) > 1:
821 return parents
861 return parents
822 if self.ui.debugflag:
862 if self.ui.debugflag:
823 return [parents[0], self.repo['null']]
863 return [parents[0], self.repo['null']]
824 if parents[0].rev() >= ctx.rev() - 1:
864 if parents[0].rev() >= ctx.rev() - 1:
825 return []
865 return []
826 return parents
866 return parents
827
867
828 def _show(self, ctx, copies, props):
868 def _show(self, ctx, copies, props):
829 '''show a single changeset or file revision'''
869 '''show a single changeset or file revision'''
830
870
831 showlist = templatekw.showlist
871 showlist = templatekw.showlist
832
872
833 # showparents() behaviour depends on ui trace level which
873 # showparents() behaviour depends on ui trace level which
834 # causes unexpected behaviours at templating level and makes
874 # causes unexpected behaviours at templating level and makes
835 # it harder to extract it in a standalone function. Its
875 # it harder to extract it in a standalone function. Its
836 # behaviour cannot be changed so leave it here for now.
876 # behaviour cannot be changed so leave it here for now.
837 def showparents(**args):
877 def showparents(**args):
838 ctx = args['ctx']
878 ctx = args['ctx']
839 parents = [[('rev', p.rev()), ('node', p.hex())]
879 parents = [[('rev', p.rev()), ('node', p.hex())]
840 for p in self._meaningful_parentrevs(ctx)]
880 for p in self._meaningful_parentrevs(ctx)]
841 return showlist('parent', parents, **args)
881 return showlist('parent', parents, **args)
842
882
843 props = props.copy()
883 props = props.copy()
844 props.update(templatekw.keywords)
884 props.update(templatekw.keywords)
845 props['parents'] = showparents
885 props['parents'] = showparents
846 props['templ'] = self.t
886 props['templ'] = self.t
847 props['ctx'] = ctx
887 props['ctx'] = ctx
848 props['repo'] = self.repo
888 props['repo'] = self.repo
849 props['revcache'] = {'copies': copies}
889 props['revcache'] = {'copies': copies}
850 props['cache'] = self.cache
890 props['cache'] = self.cache
851
891
852 # find correct templates for current mode
892 # find correct templates for current mode
853
893
854 tmplmodes = [
894 tmplmodes = [
855 (True, None),
895 (True, None),
856 (self.ui.verbose, 'verbose'),
896 (self.ui.verbose, 'verbose'),
857 (self.ui.quiet, 'quiet'),
897 (self.ui.quiet, 'quiet'),
858 (self.ui.debugflag, 'debug'),
898 (self.ui.debugflag, 'debug'),
859 ]
899 ]
860
900
861 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
901 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
862 for mode, postfix in tmplmodes:
902 for mode, postfix in tmplmodes:
863 for type in types:
903 for type in types:
864 cur = postfix and ('%s_%s' % (type, postfix)) or type
904 cur = postfix and ('%s_%s' % (type, postfix)) or type
865 if mode and cur in self.t:
905 if mode and cur in self.t:
866 types[type] = cur
906 types[type] = cur
867
907
868 try:
908 try:
869
909
870 # write header
910 # write header
871 if types['header']:
911 if types['header']:
872 h = templater.stringify(self.t(types['header'], **props))
912 h = templater.stringify(self.t(types['header'], **props))
873 if self.buffered:
913 if self.buffered:
874 self.header[ctx.rev()] = h
914 self.header[ctx.rev()] = h
875 else:
915 else:
876 self.ui.write(h)
916 self.ui.write(h)
877
917
878 # write changeset metadata, then patch if requested
918 # write changeset metadata, then patch if requested
879 key = types['changeset']
919 key = types['changeset']
880 self.ui.write(templater.stringify(self.t(key, **props)))
920 self.ui.write(templater.stringify(self.t(key, **props)))
881 self.showpatch(ctx.node())
921 self.showpatch(ctx.node())
882
922
883 if types['footer']:
923 if types['footer']:
884 if not self.footer:
924 if not self.footer:
885 self.footer = templater.stringify(self.t(types['footer'],
925 self.footer = templater.stringify(self.t(types['footer'],
886 **props))
926 **props))
887
927
888 except KeyError, inst:
928 except KeyError, inst:
889 msg = _("%s: no key named '%s'")
929 msg = _("%s: no key named '%s'")
890 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
930 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
891 except SyntaxError, inst:
931 except SyntaxError, inst:
892 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
932 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
893
933
894 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
934 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
895 """show one changeset using template or regular display.
935 """show one changeset using template or regular display.
896
936
897 Display format will be the first non-empty hit of:
937 Display format will be the first non-empty hit of:
898 1. option 'template'
938 1. option 'template'
899 2. option 'style'
939 2. option 'style'
900 3. [ui] setting 'logtemplate'
940 3. [ui] setting 'logtemplate'
901 4. [ui] setting 'style'
941 4. [ui] setting 'style'
902 If all of these values are either the unset or the empty string,
942 If all of these values are either the unset or the empty string,
903 regular display via changeset_printer() is done.
943 regular display via changeset_printer() is done.
904 """
944 """
905 # options
945 # options
906 patch = False
946 patch = False
907 if opts.get('patch'):
947 if opts.get('patch'):
908 patch = matchfn or matchall(repo)
948 patch = matchfn or matchall(repo)
909
949
910 tmpl = opts.get('template')
950 tmpl = opts.get('template')
911 style = None
951 style = None
912 if tmpl:
952 if tmpl:
913 tmpl = templater.parsestring(tmpl, quoted=False)
953 tmpl = templater.parsestring(tmpl, quoted=False)
914 else:
954 else:
915 style = opts.get('style')
955 style = opts.get('style')
916
956
917 # ui settings
957 # ui settings
918 if not (tmpl or style):
958 if not (tmpl or style):
919 tmpl = ui.config('ui', 'logtemplate')
959 tmpl = ui.config('ui', 'logtemplate')
920 if tmpl:
960 if tmpl:
921 tmpl = templater.parsestring(tmpl)
961 tmpl = templater.parsestring(tmpl)
922 else:
962 else:
923 style = util.expandpath(ui.config('ui', 'style', ''))
963 style = util.expandpath(ui.config('ui', 'style', ''))
924
964
925 if not (tmpl or style):
965 if not (tmpl or style):
926 return changeset_printer(ui, repo, patch, opts, buffered)
966 return changeset_printer(ui, repo, patch, opts, buffered)
927
967
928 mapfile = None
968 mapfile = None
929 if style and not tmpl:
969 if style and not tmpl:
930 mapfile = style
970 mapfile = style
931 if not os.path.split(mapfile)[0]:
971 if not os.path.split(mapfile)[0]:
932 mapname = (templater.templatepath('map-cmdline.' + mapfile)
972 mapname = (templater.templatepath('map-cmdline.' + mapfile)
933 or templater.templatepath(mapfile))
973 or templater.templatepath(mapfile))
934 if mapname:
974 if mapname:
935 mapfile = mapname
975 mapfile = mapname
936
976
937 try:
977 try:
938 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
978 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
939 except SyntaxError, inst:
979 except SyntaxError, inst:
940 raise util.Abort(inst.args[0])
980 raise util.Abort(inst.args[0])
941 if tmpl:
981 if tmpl:
942 t.use_template(tmpl)
982 t.use_template(tmpl)
943 return t
983 return t
944
984
945 def finddate(ui, repo, date):
985 def finddate(ui, repo, date):
946 """Find the tipmost changeset that matches the given date spec"""
986 """Find the tipmost changeset that matches the given date spec"""
947
987
948 df = util.matchdate(date)
988 df = util.matchdate(date)
949 m = matchall(repo)
989 m = matchall(repo)
950 results = {}
990 results = {}
951
991
952 def prep(ctx, fns):
992 def prep(ctx, fns):
953 d = ctx.date()
993 d = ctx.date()
954 if df(d[0]):
994 if df(d[0]):
955 results[ctx.rev()] = d
995 results[ctx.rev()] = d
956
996
957 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
997 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
958 rev = ctx.rev()
998 rev = ctx.rev()
959 if rev in results:
999 if rev in results:
960 ui.status(_("Found revision %s from %s\n") %
1000 ui.status(_("Found revision %s from %s\n") %
961 (rev, util.datestr(results[rev])))
1001 (rev, util.datestr(results[rev])))
962 return str(rev)
1002 return str(rev)
963
1003
964 raise util.Abort(_("revision matching date not found"))
1004 raise util.Abort(_("revision matching date not found"))
965
1005
966 def walkchangerevs(repo, match, opts, prepare):
1006 def walkchangerevs(repo, match, opts, prepare):
967 '''Iterate over files and the revs in which they changed.
1007 '''Iterate over files and the revs in which they changed.
968
1008
969 Callers most commonly need to iterate backwards over the history
1009 Callers most commonly need to iterate backwards over the history
970 in which they are interested. Doing so has awful (quadratic-looking)
1010 in which they are interested. Doing so has awful (quadratic-looking)
971 performance, so we use iterators in a "windowed" way.
1011 performance, so we use iterators in a "windowed" way.
972
1012
973 We walk a window of revisions in the desired order. Within the
1013 We walk a window of revisions in the desired order. Within the
974 window, we first walk forwards to gather data, then in the desired
1014 window, we first walk forwards to gather data, then in the desired
975 order (usually backwards) to display it.
1015 order (usually backwards) to display it.
976
1016
977 This function returns an iterator yielding contexts. Before
1017 This function returns an iterator yielding contexts. Before
978 yielding each context, the iterator will first call the prepare
1018 yielding each context, the iterator will first call the prepare
979 function on each context in the window in forward order.'''
1019 function on each context in the window in forward order.'''
980
1020
981 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1021 def increasing_windows(start, end, windowsize=8, sizelimit=512):
982 if start < end:
1022 if start < end:
983 while start < end:
1023 while start < end:
984 yield start, min(windowsize, end - start)
1024 yield start, min(windowsize, end - start)
985 start += windowsize
1025 start += windowsize
986 if windowsize < sizelimit:
1026 if windowsize < sizelimit:
987 windowsize *= 2
1027 windowsize *= 2
988 else:
1028 else:
989 while start > end:
1029 while start > end:
990 yield start, min(windowsize, start - end - 1)
1030 yield start, min(windowsize, start - end - 1)
991 start -= windowsize
1031 start -= windowsize
992 if windowsize < sizelimit:
1032 if windowsize < sizelimit:
993 windowsize *= 2
1033 windowsize *= 2
994
1034
995 follow = opts.get('follow') or opts.get('follow_first')
1035 follow = opts.get('follow') or opts.get('follow_first')
996
1036
997 if not len(repo):
1037 if not len(repo):
998 return []
1038 return []
999
1039
1000 if follow:
1040 if follow:
1001 defrange = '%s:0' % repo['.'].rev()
1041 defrange = '%s:0' % repo['.'].rev()
1002 else:
1042 else:
1003 defrange = '-1:0'
1043 defrange = '-1:0'
1004 revs = revrange(repo, opts['rev'] or [defrange])
1044 revs = revrange(repo, opts['rev'] or [defrange])
1005 wanted = set()
1045 wanted = set()
1006 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1046 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1007 fncache = {}
1047 fncache = {}
1008 change = util.cachefunc(repo.changectx)
1048 change = util.cachefunc(repo.changectx)
1009
1049
1010 if not slowpath and not match.files():
1050 if not slowpath and not match.files():
1011 # No files, no patterns. Display all revs.
1051 # No files, no patterns. Display all revs.
1012 wanted = set(revs)
1052 wanted = set(revs)
1013 copies = []
1053 copies = []
1014
1054
1015 if not slowpath:
1055 if not slowpath:
1016 # Only files, no patterns. Check the history of each file.
1056 # Only files, no patterns. Check the history of each file.
1017 def filerevgen(filelog, node):
1057 def filerevgen(filelog, node):
1018 cl_count = len(repo)
1058 cl_count = len(repo)
1019 if node is None:
1059 if node is None:
1020 last = len(filelog) - 1
1060 last = len(filelog) - 1
1021 else:
1061 else:
1022 last = filelog.rev(node)
1062 last = filelog.rev(node)
1023 for i, window in increasing_windows(last, nullrev):
1063 for i, window in increasing_windows(last, nullrev):
1024 revs = []
1064 revs = []
1025 for j in xrange(i - window, i + 1):
1065 for j in xrange(i - window, i + 1):
1026 n = filelog.node(j)
1066 n = filelog.node(j)
1027 revs.append((filelog.linkrev(j),
1067 revs.append((filelog.linkrev(j),
1028 follow and filelog.renamed(n)))
1068 follow and filelog.renamed(n)))
1029 for rev in reversed(revs):
1069 for rev in reversed(revs):
1030 # only yield rev for which we have the changelog, it can
1070 # only yield rev for which we have the changelog, it can
1031 # happen while doing "hg log" during a pull or commit
1071 # happen while doing "hg log" during a pull or commit
1032 if rev[0] < cl_count:
1072 if rev[0] < cl_count:
1033 yield rev
1073 yield rev
1034 def iterfiles():
1074 def iterfiles():
1035 for filename in match.files():
1075 for filename in match.files():
1036 yield filename, None
1076 yield filename, None
1037 for filename_node in copies:
1077 for filename_node in copies:
1038 yield filename_node
1078 yield filename_node
1039 minrev, maxrev = min(revs), max(revs)
1079 minrev, maxrev = min(revs), max(revs)
1040 for file_, node in iterfiles():
1080 for file_, node in iterfiles():
1041 filelog = repo.file(file_)
1081 filelog = repo.file(file_)
1042 if not len(filelog):
1082 if not len(filelog):
1043 if node is None:
1083 if node is None:
1044 # A zero count may be a directory or deleted file, so
1084 # A zero count may be a directory or deleted file, so
1045 # try to find matching entries on the slow path.
1085 # try to find matching entries on the slow path.
1046 if follow:
1086 if follow:
1047 raise util.Abort(
1087 raise util.Abort(
1048 _('cannot follow nonexistent file: "%s"') % file_)
1088 _('cannot follow nonexistent file: "%s"') % file_)
1049 slowpath = True
1089 slowpath = True
1050 break
1090 break
1051 else:
1091 else:
1052 continue
1092 continue
1053 for rev, copied in filerevgen(filelog, node):
1093 for rev, copied in filerevgen(filelog, node):
1054 if rev <= maxrev:
1094 if rev <= maxrev:
1055 if rev < minrev:
1095 if rev < minrev:
1056 break
1096 break
1057 fncache.setdefault(rev, [])
1097 fncache.setdefault(rev, [])
1058 fncache[rev].append(file_)
1098 fncache[rev].append(file_)
1059 wanted.add(rev)
1099 wanted.add(rev)
1060 if follow and copied:
1100 if follow and copied:
1061 copies.append(copied)
1101 copies.append(copied)
1062 if slowpath:
1102 if slowpath:
1063 if follow:
1103 if follow:
1064 raise util.Abort(_('can only follow copies/renames for explicit '
1104 raise util.Abort(_('can only follow copies/renames for explicit '
1065 'filenames'))
1105 'filenames'))
1066
1106
1067 # The slow path checks files modified in every changeset.
1107 # The slow path checks files modified in every changeset.
1068 def changerevgen():
1108 def changerevgen():
1069 for i, window in increasing_windows(len(repo) - 1, nullrev):
1109 for i, window in increasing_windows(len(repo) - 1, nullrev):
1070 for j in xrange(i - window, i + 1):
1110 for j in xrange(i - window, i + 1):
1071 yield change(j)
1111 yield change(j)
1072
1112
1073 for ctx in changerevgen():
1113 for ctx in changerevgen():
1074 matches = filter(match, ctx.files())
1114 matches = filter(match, ctx.files())
1075 if matches:
1115 if matches:
1076 fncache[ctx.rev()] = matches
1116 fncache[ctx.rev()] = matches
1077 wanted.add(ctx.rev())
1117 wanted.add(ctx.rev())
1078
1118
1079 class followfilter(object):
1119 class followfilter(object):
1080 def __init__(self, onlyfirst=False):
1120 def __init__(self, onlyfirst=False):
1081 self.startrev = nullrev
1121 self.startrev = nullrev
1082 self.roots = set()
1122 self.roots = set()
1083 self.onlyfirst = onlyfirst
1123 self.onlyfirst = onlyfirst
1084
1124
1085 def match(self, rev):
1125 def match(self, rev):
1086 def realparents(rev):
1126 def realparents(rev):
1087 if self.onlyfirst:
1127 if self.onlyfirst:
1088 return repo.changelog.parentrevs(rev)[0:1]
1128 return repo.changelog.parentrevs(rev)[0:1]
1089 else:
1129 else:
1090 return filter(lambda x: x != nullrev,
1130 return filter(lambda x: x != nullrev,
1091 repo.changelog.parentrevs(rev))
1131 repo.changelog.parentrevs(rev))
1092
1132
1093 if self.startrev == nullrev:
1133 if self.startrev == nullrev:
1094 self.startrev = rev
1134 self.startrev = rev
1095 return True
1135 return True
1096
1136
1097 if rev > self.startrev:
1137 if rev > self.startrev:
1098 # forward: all descendants
1138 # forward: all descendants
1099 if not self.roots:
1139 if not self.roots:
1100 self.roots.add(self.startrev)
1140 self.roots.add(self.startrev)
1101 for parent in realparents(rev):
1141 for parent in realparents(rev):
1102 if parent in self.roots:
1142 if parent in self.roots:
1103 self.roots.add(rev)
1143 self.roots.add(rev)
1104 return True
1144 return True
1105 else:
1145 else:
1106 # backwards: all parents
1146 # backwards: all parents
1107 if not self.roots:
1147 if not self.roots:
1108 self.roots.update(realparents(self.startrev))
1148 self.roots.update(realparents(self.startrev))
1109 if rev in self.roots:
1149 if rev in self.roots:
1110 self.roots.remove(rev)
1150 self.roots.remove(rev)
1111 self.roots.update(realparents(rev))
1151 self.roots.update(realparents(rev))
1112 return True
1152 return True
1113
1153
1114 return False
1154 return False
1115
1155
1116 # it might be worthwhile to do this in the iterator if the rev range
1156 # it might be worthwhile to do this in the iterator if the rev range
1117 # is descending and the prune args are all within that range
1157 # is descending and the prune args are all within that range
1118 for rev in opts.get('prune', ()):
1158 for rev in opts.get('prune', ()):
1119 rev = repo.changelog.rev(repo.lookup(rev))
1159 rev = repo.changelog.rev(repo.lookup(rev))
1120 ff = followfilter()
1160 ff = followfilter()
1121 stop = min(revs[0], revs[-1])
1161 stop = min(revs[0], revs[-1])
1122 for x in xrange(rev, stop - 1, -1):
1162 for x in xrange(rev, stop - 1, -1):
1123 if ff.match(x):
1163 if ff.match(x):
1124 wanted.discard(x)
1164 wanted.discard(x)
1125
1165
1126 def iterate():
1166 def iterate():
1127 if follow and not match.files():
1167 if follow and not match.files():
1128 ff = followfilter(onlyfirst=opts.get('follow_first'))
1168 ff = followfilter(onlyfirst=opts.get('follow_first'))
1129 def want(rev):
1169 def want(rev):
1130 return ff.match(rev) and rev in wanted
1170 return ff.match(rev) and rev in wanted
1131 else:
1171 else:
1132 def want(rev):
1172 def want(rev):
1133 return rev in wanted
1173 return rev in wanted
1134
1174
1135 for i, window in increasing_windows(0, len(revs)):
1175 for i, window in increasing_windows(0, len(revs)):
1136 change = util.cachefunc(repo.changectx)
1176 change = util.cachefunc(repo.changectx)
1137 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1177 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1138 for rev in sorted(nrevs):
1178 for rev in sorted(nrevs):
1139 fns = fncache.get(rev)
1179 fns = fncache.get(rev)
1140 ctx = change(rev)
1180 ctx = change(rev)
1141 if not fns:
1181 if not fns:
1142 def fns_generator():
1182 def fns_generator():
1143 for f in ctx.files():
1183 for f in ctx.files():
1144 if match(f):
1184 if match(f):
1145 yield f
1185 yield f
1146 fns = fns_generator()
1186 fns = fns_generator()
1147 prepare(ctx, fns)
1187 prepare(ctx, fns)
1148 for rev in nrevs:
1188 for rev in nrevs:
1149 yield change(rev)
1189 yield change(rev)
1150 return iterate()
1190 return iterate()
1151
1191
1152 def commit(ui, repo, commitfunc, pats, opts):
1192 def commit(ui, repo, commitfunc, pats, opts):
1153 '''commit the specified files or all outstanding changes'''
1193 '''commit the specified files or all outstanding changes'''
1154 date = opts.get('date')
1194 date = opts.get('date')
1155 if date:
1195 if date:
1156 opts['date'] = util.parsedate(date)
1196 opts['date'] = util.parsedate(date)
1157 message = logmessage(opts)
1197 message = logmessage(opts)
1158
1198
1159 # extract addremove carefully -- this function can be called from a command
1199 # extract addremove carefully -- this function can be called from a command
1160 # that doesn't support addremove
1200 # that doesn't support addremove
1161 if opts.get('addremove'):
1201 if opts.get('addremove'):
1162 addremove(repo, pats, opts)
1202 addremove(repo, pats, opts)
1163
1203
1164 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1204 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1165
1205
1166 def commiteditor(repo, ctx, subs):
1206 def commiteditor(repo, ctx, subs):
1167 if ctx.description():
1207 if ctx.description():
1168 return ctx.description()
1208 return ctx.description()
1169 return commitforceeditor(repo, ctx, subs)
1209 return commitforceeditor(repo, ctx, subs)
1170
1210
1171 def commitforceeditor(repo, ctx, subs):
1211 def commitforceeditor(repo, ctx, subs):
1172 edittext = []
1212 edittext = []
1173 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1213 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1174 if ctx.description():
1214 if ctx.description():
1175 edittext.append(ctx.description())
1215 edittext.append(ctx.description())
1176 edittext.append("")
1216 edittext.append("")
1177 edittext.append("") # Empty line between message and comments.
1217 edittext.append("") # Empty line between message and comments.
1178 edittext.append(_("HG: Enter commit message."
1218 edittext.append(_("HG: Enter commit message."
1179 " Lines beginning with 'HG:' are removed."))
1219 " Lines beginning with 'HG:' are removed."))
1180 edittext.append(_("HG: Leave message empty to abort commit."))
1220 edittext.append(_("HG: Leave message empty to abort commit."))
1181 edittext.append("HG: --")
1221 edittext.append("HG: --")
1182 edittext.append(_("HG: user: %s") % ctx.user())
1222 edittext.append(_("HG: user: %s") % ctx.user())
1183 if ctx.p2():
1223 if ctx.p2():
1184 edittext.append(_("HG: branch merge"))
1224 edittext.append(_("HG: branch merge"))
1185 if ctx.branch():
1225 if ctx.branch():
1186 edittext.append(_("HG: branch '%s'")
1226 edittext.append(_("HG: branch '%s'")
1187 % encoding.tolocal(ctx.branch()))
1227 % encoding.tolocal(ctx.branch()))
1188 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1228 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1189 edittext.extend([_("HG: added %s") % f for f in added])
1229 edittext.extend([_("HG: added %s") % f for f in added])
1190 edittext.extend([_("HG: changed %s") % f for f in modified])
1230 edittext.extend([_("HG: changed %s") % f for f in modified])
1191 edittext.extend([_("HG: removed %s") % f for f in removed])
1231 edittext.extend([_("HG: removed %s") % f for f in removed])
1192 if not added and not modified and not removed:
1232 if not added and not modified and not removed:
1193 edittext.append(_("HG: no files changed"))
1233 edittext.append(_("HG: no files changed"))
1194 edittext.append("")
1234 edittext.append("")
1195 # run editor in the repository root
1235 # run editor in the repository root
1196 olddir = os.getcwd()
1236 olddir = os.getcwd()
1197 os.chdir(repo.root)
1237 os.chdir(repo.root)
1198 text = repo.ui.edit("\n".join(edittext), ctx.user())
1238 text = repo.ui.edit("\n".join(edittext), ctx.user())
1199 text = re.sub("(?m)^HG:.*\n", "", text)
1239 text = re.sub("(?m)^HG:.*\n", "", text)
1200 os.chdir(olddir)
1240 os.chdir(olddir)
1201
1241
1202 if not text.strip():
1242 if not text.strip():
1203 raise util.Abort(_("empty commit message"))
1243 raise util.Abort(_("empty commit message"))
1204
1244
1205 return text
1245 return text
@@ -1,3846 +1,3846 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from lock import release
9 from lock import release
10 from i18n import _, gettext
10 from i18n import _, gettext
11 import os, re, sys, difflib, time, tempfile
11 import os, re, sys, difflib, time, tempfile
12 import hg, util, revlog, bundlerepo, extensions, copies, error
12 import hg, util, revlog, bundlerepo, extensions, copies, error
13 import patch, help, mdiff, url, encoding, templatekw
13 import patch, help, mdiff, url, encoding, templatekw
14 import archival, changegroup, cmdutil, sshserver, hbisect
14 import archival, changegroup, cmdutil, sshserver, hbisect
15 from hgweb import server
15 from hgweb import server
16 import merge as merge_
16 import merge as merge_
17 import minirst
17 import minirst
18
18
19 # Commands start here, listed alphabetically
19 # Commands start here, listed alphabetically
20
20
21 def add(ui, repo, *pats, **opts):
21 def add(ui, repo, *pats, **opts):
22 """add the specified files on the next commit
22 """add the specified files on the next commit
23
23
24 Schedule files to be version controlled and added to the
24 Schedule files to be version controlled and added to the
25 repository.
25 repository.
26
26
27 The files will be added to the repository at the next commit. To
27 The files will be added to the repository at the next commit. To
28 undo an add before that, see hg forget.
28 undo an add before that, see hg forget.
29
29
30 If no names are given, add all files to the repository.
30 If no names are given, add all files to the repository.
31
31
32 .. container:: verbose
32 .. container:: verbose
33
33
34 An example showing how new (unknown) files are added
34 An example showing how new (unknown) files are added
35 automatically by ``hg add``::
35 automatically by ``hg add``::
36
36
37 $ ls
37 $ ls
38 foo.c
38 foo.c
39 $ hg status
39 $ hg status
40 ? foo.c
40 ? foo.c
41 $ hg add
41 $ hg add
42 adding foo.c
42 adding foo.c
43 $ hg status
43 $ hg status
44 A foo.c
44 A foo.c
45 """
45 """
46
46
47 bad = []
47 bad = []
48 names = []
48 names = []
49 m = cmdutil.match(repo, pats, opts)
49 m = cmdutil.match(repo, pats, opts)
50 oldbad = m.bad
50 oldbad = m.bad
51 m.bad = lambda x, y: bad.append(x) or oldbad(x, y)
51 m.bad = lambda x, y: bad.append(x) or oldbad(x, y)
52
52
53 for f in repo.walk(m):
53 for f in repo.walk(m):
54 exact = m.exact(f)
54 exact = m.exact(f)
55 if exact or f not in repo.dirstate:
55 if exact or f not in repo.dirstate:
56 names.append(f)
56 names.append(f)
57 if ui.verbose or not exact:
57 if ui.verbose or not exact:
58 ui.status(_('adding %s\n') % m.rel(f))
58 ui.status(_('adding %s\n') % m.rel(f))
59 if not opts.get('dry_run'):
59 if not opts.get('dry_run'):
60 bad += [f for f in repo.add(names) if f in m.files()]
60 bad += [f for f in repo.add(names) if f in m.files()]
61 return bad and 1 or 0
61 return bad and 1 or 0
62
62
63 def addremove(ui, repo, *pats, **opts):
63 def addremove(ui, repo, *pats, **opts):
64 """add all new files, delete all missing files
64 """add all new files, delete all missing files
65
65
66 Add all new files and remove all missing files from the
66 Add all new files and remove all missing files from the
67 repository.
67 repository.
68
68
69 New files are ignored if they match any of the patterns in
69 New files are ignored if they match any of the patterns in
70 .hgignore. As with add, these changes take effect at the next
70 .hgignore. As with add, these changes take effect at the next
71 commit.
71 commit.
72
72
73 Use the -s/--similarity option to detect renamed files. With a
73 Use the -s/--similarity option to detect renamed files. With a
74 parameter greater than 0, this compares every removed file with
74 parameter greater than 0, this compares every removed file with
75 every added file and records those similar enough as renames. This
75 every added file and records those similar enough as renames. This
76 option takes a percentage between 0 (disabled) and 100 (files must
76 option takes a percentage between 0 (disabled) and 100 (files must
77 be identical) as its parameter. Detecting renamed files this way
77 be identical) as its parameter. Detecting renamed files this way
78 can be expensive.
78 can be expensive.
79 """
79 """
80 try:
80 try:
81 sim = float(opts.get('similarity') or 0)
81 sim = float(opts.get('similarity') or 0)
82 except ValueError:
82 except ValueError:
83 raise util.Abort(_('similarity must be a number'))
83 raise util.Abort(_('similarity must be a number'))
84 if sim < 0 or sim > 100:
84 if sim < 0 or sim > 100:
85 raise util.Abort(_('similarity must be between 0 and 100'))
85 raise util.Abort(_('similarity must be between 0 and 100'))
86 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
86 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
87
87
88 def annotate(ui, repo, *pats, **opts):
88 def annotate(ui, repo, *pats, **opts):
89 """show changeset information by line for each file
89 """show changeset information by line for each file
90
90
91 List changes in files, showing the revision id responsible for
91 List changes in files, showing the revision id responsible for
92 each line
92 each line
93
93
94 This command is useful for discovering when a change was made and
94 This command is useful for discovering when a change was made and
95 by whom.
95 by whom.
96
96
97 Without the -a/--text option, annotate will avoid processing files
97 Without the -a/--text option, annotate will avoid processing files
98 it detects as binary. With -a, annotate will annotate the file
98 it detects as binary. With -a, annotate will annotate the file
99 anyway, although the results will probably be neither useful
99 anyway, although the results will probably be neither useful
100 nor desirable.
100 nor desirable.
101 """
101 """
102 if opts.get('follow'):
102 if opts.get('follow'):
103 # --follow is deprecated and now just an alias for -f/--file
103 # --follow is deprecated and now just an alias for -f/--file
104 # to mimic the behavior of Mercurial before version 1.5
104 # to mimic the behavior of Mercurial before version 1.5
105 opts['file'] = 1
105 opts['file'] = 1
106
106
107 datefunc = ui.quiet and util.shortdate or util.datestr
107 datefunc = ui.quiet and util.shortdate or util.datestr
108 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
108 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
109
109
110 if not pats:
110 if not pats:
111 raise util.Abort(_('at least one filename or pattern is required'))
111 raise util.Abort(_('at least one filename or pattern is required'))
112
112
113 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
113 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
114 ('number', lambda x: str(x[0].rev())),
114 ('number', lambda x: str(x[0].rev())),
115 ('changeset', lambda x: short(x[0].node())),
115 ('changeset', lambda x: short(x[0].node())),
116 ('date', getdate),
116 ('date', getdate),
117 ('file', lambda x: x[0].path()),
117 ('file', lambda x: x[0].path()),
118 ]
118 ]
119
119
120 if (not opts.get('user') and not opts.get('changeset')
120 if (not opts.get('user') and not opts.get('changeset')
121 and not opts.get('date') and not opts.get('file')):
121 and not opts.get('date') and not opts.get('file')):
122 opts['number'] = 1
122 opts['number'] = 1
123
123
124 linenumber = opts.get('line_number') is not None
124 linenumber = opts.get('line_number') is not None
125 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
125 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
126 raise util.Abort(_('at least one of -n/-c is required for -l'))
126 raise util.Abort(_('at least one of -n/-c is required for -l'))
127
127
128 funcmap = [func for op, func in opmap if opts.get(op)]
128 funcmap = [func for op, func in opmap if opts.get(op)]
129 if linenumber:
129 if linenumber:
130 lastfunc = funcmap[-1]
130 lastfunc = funcmap[-1]
131 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
131 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
132
132
133 ctx = repo[opts.get('rev')]
133 ctx = repo[opts.get('rev')]
134 m = cmdutil.match(repo, pats, opts)
134 m = cmdutil.match(repo, pats, opts)
135 follow = not opts.get('no_follow')
135 follow = not opts.get('no_follow')
136 for abs in ctx.walk(m):
136 for abs in ctx.walk(m):
137 fctx = ctx[abs]
137 fctx = ctx[abs]
138 if not opts.get('text') and util.binary(fctx.data()):
138 if not opts.get('text') and util.binary(fctx.data()):
139 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
139 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
140 continue
140 continue
141
141
142 lines = fctx.annotate(follow=follow, linenumber=linenumber)
142 lines = fctx.annotate(follow=follow, linenumber=linenumber)
143 pieces = []
143 pieces = []
144
144
145 for f in funcmap:
145 for f in funcmap:
146 l = [f(n) for n, dummy in lines]
146 l = [f(n) for n, dummy in lines]
147 if l:
147 if l:
148 ml = max(map(len, l))
148 ml = max(map(len, l))
149 pieces.append(["%*s" % (ml, x) for x in l])
149 pieces.append(["%*s" % (ml, x) for x in l])
150
150
151 if pieces:
151 if pieces:
152 for p, l in zip(zip(*pieces), lines):
152 for p, l in zip(zip(*pieces), lines):
153 ui.write("%s: %s" % (" ".join(p), l[1]))
153 ui.write("%s: %s" % (" ".join(p), l[1]))
154
154
155 def archive(ui, repo, dest, **opts):
155 def archive(ui, repo, dest, **opts):
156 '''create an unversioned archive of a repository revision
156 '''create an unversioned archive of a repository revision
157
157
158 By default, the revision used is the parent of the working
158 By default, the revision used is the parent of the working
159 directory; use -r/--rev to specify a different revision.
159 directory; use -r/--rev to specify a different revision.
160
160
161 To specify the type of archive to create, use -t/--type. Valid
161 To specify the type of archive to create, use -t/--type. Valid
162 types are:
162 types are:
163
163
164 :``files``: a directory full of files (default)
164 :``files``: a directory full of files (default)
165 :``tar``: tar archive, uncompressed
165 :``tar``: tar archive, uncompressed
166 :``tbz2``: tar archive, compressed using bzip2
166 :``tbz2``: tar archive, compressed using bzip2
167 :``tgz``: tar archive, compressed using gzip
167 :``tgz``: tar archive, compressed using gzip
168 :``uzip``: zip archive, uncompressed
168 :``uzip``: zip archive, uncompressed
169 :``zip``: zip archive, compressed using deflate
169 :``zip``: zip archive, compressed using deflate
170
170
171 The exact name of the destination archive or directory is given
171 The exact name of the destination archive or directory is given
172 using a format string; see 'hg help export' for details.
172 using a format string; see 'hg help export' for details.
173
173
174 Each member added to an archive file has a directory prefix
174 Each member added to an archive file has a directory prefix
175 prepended. Use -p/--prefix to specify a format string for the
175 prepended. Use -p/--prefix to specify a format string for the
176 prefix. The default is the basename of the archive, with suffixes
176 prefix. The default is the basename of the archive, with suffixes
177 removed.
177 removed.
178 '''
178 '''
179
179
180 ctx = repo[opts.get('rev')]
180 ctx = repo[opts.get('rev')]
181 if not ctx:
181 if not ctx:
182 raise util.Abort(_('no working directory: please specify a revision'))
182 raise util.Abort(_('no working directory: please specify a revision'))
183 node = ctx.node()
183 node = ctx.node()
184 dest = cmdutil.make_filename(repo, dest, node)
184 dest = cmdutil.make_filename(repo, dest, node)
185 if os.path.realpath(dest) == repo.root:
185 if os.path.realpath(dest) == repo.root:
186 raise util.Abort(_('repository root cannot be destination'))
186 raise util.Abort(_('repository root cannot be destination'))
187 matchfn = cmdutil.match(repo, [], opts)
187 matchfn = cmdutil.match(repo, [], opts)
188 kind = opts.get('type') or 'files'
188 kind = opts.get('type') or 'files'
189 prefix = opts.get('prefix')
189 prefix = opts.get('prefix')
190 if dest == '-':
190 if dest == '-':
191 if kind == 'files':
191 if kind == 'files':
192 raise util.Abort(_('cannot archive plain files to stdout'))
192 raise util.Abort(_('cannot archive plain files to stdout'))
193 dest = sys.stdout
193 dest = sys.stdout
194 if not prefix:
194 if not prefix:
195 prefix = os.path.basename(repo.root) + '-%h'
195 prefix = os.path.basename(repo.root) + '-%h'
196 prefix = cmdutil.make_filename(repo, prefix, node)
196 prefix = cmdutil.make_filename(repo, prefix, node)
197 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
197 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
198 matchfn, prefix)
198 matchfn, prefix)
199
199
200 def backout(ui, repo, node=None, rev=None, **opts):
200 def backout(ui, repo, node=None, rev=None, **opts):
201 '''reverse effect of earlier changeset
201 '''reverse effect of earlier changeset
202
202
203 Commit the backed out changes as a new changeset. The new
203 Commit the backed out changes as a new changeset. The new
204 changeset is a child of the backed out changeset.
204 changeset is a child of the backed out changeset.
205
205
206 If you backout a changeset other than the tip, a new head is
206 If you backout a changeset other than the tip, a new head is
207 created. This head will be the new tip and you should merge this
207 created. This head will be the new tip and you should merge this
208 backout changeset with another head.
208 backout changeset with another head.
209
209
210 The --merge option remembers the parent of the working directory
210 The --merge option remembers the parent of the working directory
211 before starting the backout, then merges the new head with that
211 before starting the backout, then merges the new head with that
212 changeset afterwards. This saves you from doing the merge by hand.
212 changeset afterwards. This saves you from doing the merge by hand.
213 The result of this merge is not committed, as with a normal merge.
213 The result of this merge is not committed, as with a normal merge.
214
214
215 See 'hg help dates' for a list of formats valid for -d/--date.
215 See 'hg help dates' for a list of formats valid for -d/--date.
216 '''
216 '''
217 if rev and node:
217 if rev and node:
218 raise util.Abort(_("please specify just one revision"))
218 raise util.Abort(_("please specify just one revision"))
219
219
220 if not rev:
220 if not rev:
221 rev = node
221 rev = node
222
222
223 if not rev:
223 if not rev:
224 raise util.Abort(_("please specify a revision to backout"))
224 raise util.Abort(_("please specify a revision to backout"))
225
225
226 date = opts.get('date')
226 date = opts.get('date')
227 if date:
227 if date:
228 opts['date'] = util.parsedate(date)
228 opts['date'] = util.parsedate(date)
229
229
230 cmdutil.bail_if_changed(repo)
230 cmdutil.bail_if_changed(repo)
231 node = repo.lookup(rev)
231 node = repo.lookup(rev)
232
232
233 op1, op2 = repo.dirstate.parents()
233 op1, op2 = repo.dirstate.parents()
234 a = repo.changelog.ancestor(op1, node)
234 a = repo.changelog.ancestor(op1, node)
235 if a != node:
235 if a != node:
236 raise util.Abort(_('cannot backout change on a different branch'))
236 raise util.Abort(_('cannot backout change on a different branch'))
237
237
238 p1, p2 = repo.changelog.parents(node)
238 p1, p2 = repo.changelog.parents(node)
239 if p1 == nullid:
239 if p1 == nullid:
240 raise util.Abort(_('cannot backout a change with no parents'))
240 raise util.Abort(_('cannot backout a change with no parents'))
241 if p2 != nullid:
241 if p2 != nullid:
242 if not opts.get('parent'):
242 if not opts.get('parent'):
243 raise util.Abort(_('cannot backout a merge changeset without '
243 raise util.Abort(_('cannot backout a merge changeset without '
244 '--parent'))
244 '--parent'))
245 p = repo.lookup(opts['parent'])
245 p = repo.lookup(opts['parent'])
246 if p not in (p1, p2):
246 if p not in (p1, p2):
247 raise util.Abort(_('%s is not a parent of %s') %
247 raise util.Abort(_('%s is not a parent of %s') %
248 (short(p), short(node)))
248 (short(p), short(node)))
249 parent = p
249 parent = p
250 else:
250 else:
251 if opts.get('parent'):
251 if opts.get('parent'):
252 raise util.Abort(_('cannot use --parent on non-merge changeset'))
252 raise util.Abort(_('cannot use --parent on non-merge changeset'))
253 parent = p1
253 parent = p1
254
254
255 # the backout should appear on the same branch
255 # the backout should appear on the same branch
256 branch = repo.dirstate.branch()
256 branch = repo.dirstate.branch()
257 hg.clean(repo, node, show_stats=False)
257 hg.clean(repo, node, show_stats=False)
258 repo.dirstate.setbranch(branch)
258 repo.dirstate.setbranch(branch)
259 revert_opts = opts.copy()
259 revert_opts = opts.copy()
260 revert_opts['date'] = None
260 revert_opts['date'] = None
261 revert_opts['all'] = True
261 revert_opts['all'] = True
262 revert_opts['rev'] = hex(parent)
262 revert_opts['rev'] = hex(parent)
263 revert_opts['no_backup'] = None
263 revert_opts['no_backup'] = None
264 revert(ui, repo, **revert_opts)
264 revert(ui, repo, **revert_opts)
265 commit_opts = opts.copy()
265 commit_opts = opts.copy()
266 commit_opts['addremove'] = False
266 commit_opts['addremove'] = False
267 if not commit_opts['message'] and not commit_opts['logfile']:
267 if not commit_opts['message'] and not commit_opts['logfile']:
268 # we don't translate commit messages
268 # we don't translate commit messages
269 commit_opts['message'] = "Backed out changeset %s" % short(node)
269 commit_opts['message'] = "Backed out changeset %s" % short(node)
270 commit_opts['force_editor'] = True
270 commit_opts['force_editor'] = True
271 commit(ui, repo, **commit_opts)
271 commit(ui, repo, **commit_opts)
272 def nice(node):
272 def nice(node):
273 return '%d:%s' % (repo.changelog.rev(node), short(node))
273 return '%d:%s' % (repo.changelog.rev(node), short(node))
274 ui.status(_('changeset %s backs out changeset %s\n') %
274 ui.status(_('changeset %s backs out changeset %s\n') %
275 (nice(repo.changelog.tip()), nice(node)))
275 (nice(repo.changelog.tip()), nice(node)))
276 if op1 != node:
276 if op1 != node:
277 hg.clean(repo, op1, show_stats=False)
277 hg.clean(repo, op1, show_stats=False)
278 if opts.get('merge'):
278 if opts.get('merge'):
279 ui.status(_('merging with changeset %s\n')
279 ui.status(_('merging with changeset %s\n')
280 % nice(repo.changelog.tip()))
280 % nice(repo.changelog.tip()))
281 hg.merge(repo, hex(repo.changelog.tip()))
281 hg.merge(repo, hex(repo.changelog.tip()))
282 else:
282 else:
283 ui.status(_('the backout changeset is a new head - '
283 ui.status(_('the backout changeset is a new head - '
284 'do not forget to merge\n'))
284 'do not forget to merge\n'))
285 ui.status(_('(use "backout --merge" '
285 ui.status(_('(use "backout --merge" '
286 'if you want to auto-merge)\n'))
286 'if you want to auto-merge)\n'))
287
287
288 def bisect(ui, repo, rev=None, extra=None, command=None,
288 def bisect(ui, repo, rev=None, extra=None, command=None,
289 reset=None, good=None, bad=None, skip=None, noupdate=None):
289 reset=None, good=None, bad=None, skip=None, noupdate=None):
290 """subdivision search of changesets
290 """subdivision search of changesets
291
291
292 This command helps to find changesets which introduce problems. To
292 This command helps to find changesets which introduce problems. To
293 use, mark the earliest changeset you know exhibits the problem as
293 use, mark the earliest changeset you know exhibits the problem as
294 bad, then mark the latest changeset which is free from the problem
294 bad, then mark the latest changeset which is free from the problem
295 as good. Bisect will update your working directory to a revision
295 as good. Bisect will update your working directory to a revision
296 for testing (unless the -U/--noupdate option is specified). Once
296 for testing (unless the -U/--noupdate option is specified). Once
297 you have performed tests, mark the working directory as good or
297 you have performed tests, mark the working directory as good or
298 bad, and bisect will either update to another candidate changeset
298 bad, and bisect will either update to another candidate changeset
299 or announce that it has found the bad revision.
299 or announce that it has found the bad revision.
300
300
301 As a shortcut, you can also use the revision argument to mark a
301 As a shortcut, you can also use the revision argument to mark a
302 revision as good or bad without checking it out first.
302 revision as good or bad without checking it out first.
303
303
304 If you supply a command, it will be used for automatic bisection.
304 If you supply a command, it will be used for automatic bisection.
305 Its exit status will be used to mark revisions as good or bad:
305 Its exit status will be used to mark revisions as good or bad:
306 status 0 means good, 125 means to skip the revision, 127
306 status 0 means good, 125 means to skip the revision, 127
307 (command not found) will abort the bisection, and any other
307 (command not found) will abort the bisection, and any other
308 non-zero exit status means the revision is bad.
308 non-zero exit status means the revision is bad.
309 """
309 """
310 def print_result(nodes, good):
310 def print_result(nodes, good):
311 displayer = cmdutil.show_changeset(ui, repo, {})
311 displayer = cmdutil.show_changeset(ui, repo, {})
312 if len(nodes) == 1:
312 if len(nodes) == 1:
313 # narrowed it down to a single revision
313 # narrowed it down to a single revision
314 if good:
314 if good:
315 ui.write(_("The first good revision is:\n"))
315 ui.write(_("The first good revision is:\n"))
316 else:
316 else:
317 ui.write(_("The first bad revision is:\n"))
317 ui.write(_("The first bad revision is:\n"))
318 displayer.show(repo[nodes[0]])
318 displayer.show(repo[nodes[0]])
319 else:
319 else:
320 # multiple possible revisions
320 # multiple possible revisions
321 if good:
321 if good:
322 ui.write(_("Due to skipped revisions, the first "
322 ui.write(_("Due to skipped revisions, the first "
323 "good revision could be any of:\n"))
323 "good revision could be any of:\n"))
324 else:
324 else:
325 ui.write(_("Due to skipped revisions, the first "
325 ui.write(_("Due to skipped revisions, the first "
326 "bad revision could be any of:\n"))
326 "bad revision could be any of:\n"))
327 for n in nodes:
327 for n in nodes:
328 displayer.show(repo[n])
328 displayer.show(repo[n])
329 displayer.close()
329 displayer.close()
330
330
331 def check_state(state, interactive=True):
331 def check_state(state, interactive=True):
332 if not state['good'] or not state['bad']:
332 if not state['good'] or not state['bad']:
333 if (good or bad or skip or reset) and interactive:
333 if (good or bad or skip or reset) and interactive:
334 return
334 return
335 if not state['good']:
335 if not state['good']:
336 raise util.Abort(_('cannot bisect (no known good revisions)'))
336 raise util.Abort(_('cannot bisect (no known good revisions)'))
337 else:
337 else:
338 raise util.Abort(_('cannot bisect (no known bad revisions)'))
338 raise util.Abort(_('cannot bisect (no known bad revisions)'))
339 return True
339 return True
340
340
341 # backward compatibility
341 # backward compatibility
342 if rev in "good bad reset init".split():
342 if rev in "good bad reset init".split():
343 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
343 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
344 cmd, rev, extra = rev, extra, None
344 cmd, rev, extra = rev, extra, None
345 if cmd == "good":
345 if cmd == "good":
346 good = True
346 good = True
347 elif cmd == "bad":
347 elif cmd == "bad":
348 bad = True
348 bad = True
349 else:
349 else:
350 reset = True
350 reset = True
351 elif extra or good + bad + skip + reset + bool(command) > 1:
351 elif extra or good + bad + skip + reset + bool(command) > 1:
352 raise util.Abort(_('incompatible arguments'))
352 raise util.Abort(_('incompatible arguments'))
353
353
354 if reset:
354 if reset:
355 p = repo.join("bisect.state")
355 p = repo.join("bisect.state")
356 if os.path.exists(p):
356 if os.path.exists(p):
357 os.unlink(p)
357 os.unlink(p)
358 return
358 return
359
359
360 state = hbisect.load_state(repo)
360 state = hbisect.load_state(repo)
361
361
362 if command:
362 if command:
363 changesets = 1
363 changesets = 1
364 try:
364 try:
365 while changesets:
365 while changesets:
366 # update state
366 # update state
367 status = util.system(command)
367 status = util.system(command)
368 if status == 125:
368 if status == 125:
369 transition = "skip"
369 transition = "skip"
370 elif status == 0:
370 elif status == 0:
371 transition = "good"
371 transition = "good"
372 # status < 0 means process was killed
372 # status < 0 means process was killed
373 elif status == 127:
373 elif status == 127:
374 raise util.Abort(_("failed to execute %s") % command)
374 raise util.Abort(_("failed to execute %s") % command)
375 elif status < 0:
375 elif status < 0:
376 raise util.Abort(_("%s killed") % command)
376 raise util.Abort(_("%s killed") % command)
377 else:
377 else:
378 transition = "bad"
378 transition = "bad"
379 ctx = repo[rev or '.']
379 ctx = repo[rev or '.']
380 state[transition].append(ctx.node())
380 state[transition].append(ctx.node())
381 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
381 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
382 check_state(state, interactive=False)
382 check_state(state, interactive=False)
383 # bisect
383 # bisect
384 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
384 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
385 # update to next check
385 # update to next check
386 cmdutil.bail_if_changed(repo)
386 cmdutil.bail_if_changed(repo)
387 hg.clean(repo, nodes[0], show_stats=False)
387 hg.clean(repo, nodes[0], show_stats=False)
388 finally:
388 finally:
389 hbisect.save_state(repo, state)
389 hbisect.save_state(repo, state)
390 return print_result(nodes, good)
390 return print_result(nodes, good)
391
391
392 # update state
392 # update state
393 node = repo.lookup(rev or '.')
393 node = repo.lookup(rev or '.')
394 if good or bad or skip:
394 if good or bad or skip:
395 if good:
395 if good:
396 state['good'].append(node)
396 state['good'].append(node)
397 elif bad:
397 elif bad:
398 state['bad'].append(node)
398 state['bad'].append(node)
399 elif skip:
399 elif skip:
400 state['skip'].append(node)
400 state['skip'].append(node)
401 hbisect.save_state(repo, state)
401 hbisect.save_state(repo, state)
402
402
403 if not check_state(state):
403 if not check_state(state):
404 return
404 return
405
405
406 # actually bisect
406 # actually bisect
407 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
407 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
408 if changesets == 0:
408 if changesets == 0:
409 print_result(nodes, good)
409 print_result(nodes, good)
410 else:
410 else:
411 assert len(nodes) == 1 # only a single node can be tested next
411 assert len(nodes) == 1 # only a single node can be tested next
412 node = nodes[0]
412 node = nodes[0]
413 # compute the approximate number of remaining tests
413 # compute the approximate number of remaining tests
414 tests, size = 0, 2
414 tests, size = 0, 2
415 while size <= changesets:
415 while size <= changesets:
416 tests, size = tests + 1, size * 2
416 tests, size = tests + 1, size * 2
417 rev = repo.changelog.rev(node)
417 rev = repo.changelog.rev(node)
418 ui.write(_("Testing changeset %d:%s "
418 ui.write(_("Testing changeset %d:%s "
419 "(%d changesets remaining, ~%d tests)\n")
419 "(%d changesets remaining, ~%d tests)\n")
420 % (rev, short(node), changesets, tests))
420 % (rev, short(node), changesets, tests))
421 if not noupdate:
421 if not noupdate:
422 cmdutil.bail_if_changed(repo)
422 cmdutil.bail_if_changed(repo)
423 return hg.clean(repo, node)
423 return hg.clean(repo, node)
424
424
425 def branch(ui, repo, label=None, **opts):
425 def branch(ui, repo, label=None, **opts):
426 """set or show the current branch name
426 """set or show the current branch name
427
427
428 With no argument, show the current branch name. With one argument,
428 With no argument, show the current branch name. With one argument,
429 set the working directory branch name (the branch will not exist
429 set the working directory branch name (the branch will not exist
430 in the repository until the next commit). Standard practice
430 in the repository until the next commit). Standard practice
431 recommends that primary development take place on the 'default'
431 recommends that primary development take place on the 'default'
432 branch.
432 branch.
433
433
434 Unless -f/--force is specified, branch will not let you set a
434 Unless -f/--force is specified, branch will not let you set a
435 branch name that already exists, even if it's inactive.
435 branch name that already exists, even if it's inactive.
436
436
437 Use -C/--clean to reset the working directory branch to that of
437 Use -C/--clean to reset the working directory branch to that of
438 the parent of the working directory, negating a previous branch
438 the parent of the working directory, negating a previous branch
439 change.
439 change.
440
440
441 Use the command 'hg update' to switch to an existing branch. Use
441 Use the command 'hg update' to switch to an existing branch. Use
442 'hg commit --close-branch' to mark this branch as closed.
442 'hg commit --close-branch' to mark this branch as closed.
443 """
443 """
444
444
445 if opts.get('clean'):
445 if opts.get('clean'):
446 label = repo[None].parents()[0].branch()
446 label = repo[None].parents()[0].branch()
447 repo.dirstate.setbranch(label)
447 repo.dirstate.setbranch(label)
448 ui.status(_('reset working directory to branch %s\n') % label)
448 ui.status(_('reset working directory to branch %s\n') % label)
449 elif label:
449 elif label:
450 utflabel = encoding.fromlocal(label)
450 utflabel = encoding.fromlocal(label)
451 if not opts.get('force') and utflabel in repo.branchtags():
451 if not opts.get('force') and utflabel in repo.branchtags():
452 if label not in [p.branch() for p in repo.parents()]:
452 if label not in [p.branch() for p in repo.parents()]:
453 raise util.Abort(_('a branch of the same name already exists'
453 raise util.Abort(_('a branch of the same name already exists'
454 " (use 'hg update' to switch to it)"))
454 " (use 'hg update' to switch to it)"))
455 repo.dirstate.setbranch(utflabel)
455 repo.dirstate.setbranch(utflabel)
456 ui.status(_('marked working directory as branch %s\n') % label)
456 ui.status(_('marked working directory as branch %s\n') % label)
457 else:
457 else:
458 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
458 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
459
459
460 def branches(ui, repo, active=False, closed=False):
460 def branches(ui, repo, active=False, closed=False):
461 """list repository named branches
461 """list repository named branches
462
462
463 List the repository's named branches, indicating which ones are
463 List the repository's named branches, indicating which ones are
464 inactive. If -c/--closed is specified, also list branches which have
464 inactive. If -c/--closed is specified, also list branches which have
465 been marked closed (see hg commit --close-branch).
465 been marked closed (see hg commit --close-branch).
466
466
467 If -a/--active is specified, only show active branches. A branch
467 If -a/--active is specified, only show active branches. A branch
468 is considered active if it contains repository heads.
468 is considered active if it contains repository heads.
469
469
470 Use the command 'hg update' to switch to an existing branch.
470 Use the command 'hg update' to switch to an existing branch.
471 """
471 """
472
472
473 hexfunc = ui.debugflag and hex or short
473 hexfunc = ui.debugflag and hex or short
474 activebranches = [repo[n].branch() for n in repo.heads()]
474 activebranches = [repo[n].branch() for n in repo.heads()]
475 def testactive(tag, node):
475 def testactive(tag, node):
476 realhead = tag in activebranches
476 realhead = tag in activebranches
477 open = node in repo.branchheads(tag, closed=False)
477 open = node in repo.branchheads(tag, closed=False)
478 return realhead and open
478 return realhead and open
479 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
479 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
480 for tag, node in repo.branchtags().items()],
480 for tag, node in repo.branchtags().items()],
481 reverse=True)
481 reverse=True)
482
482
483 for isactive, node, tag in branches:
483 for isactive, node, tag in branches:
484 if (not active) or isactive:
484 if (not active) or isactive:
485 encodedtag = encoding.tolocal(tag)
485 encodedtag = encoding.tolocal(tag)
486 if ui.quiet:
486 if ui.quiet:
487 ui.write("%s\n" % encodedtag)
487 ui.write("%s\n" % encodedtag)
488 else:
488 else:
489 hn = repo.lookup(node)
489 hn = repo.lookup(node)
490 if isactive:
490 if isactive:
491 notice = ''
491 notice = ''
492 elif hn not in repo.branchheads(tag, closed=False):
492 elif hn not in repo.branchheads(tag, closed=False):
493 if not closed:
493 if not closed:
494 continue
494 continue
495 notice = _(' (closed)')
495 notice = _(' (closed)')
496 else:
496 else:
497 notice = _(' (inactive)')
497 notice = _(' (inactive)')
498 rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
498 rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
499 data = encodedtag, rev, hexfunc(hn), notice
499 data = encodedtag, rev, hexfunc(hn), notice
500 ui.write("%s %s:%s%s\n" % data)
500 ui.write("%s %s:%s%s\n" % data)
501
501
502 def bundle(ui, repo, fname, dest=None, **opts):
502 def bundle(ui, repo, fname, dest=None, **opts):
503 """create a changegroup file
503 """create a changegroup file
504
504
505 Generate a compressed changegroup file collecting changesets not
505 Generate a compressed changegroup file collecting changesets not
506 known to be in another repository.
506 known to be in another repository.
507
507
508 If you omit the destination repository, then hg assumes the
508 If you omit the destination repository, then hg assumes the
509 destination will have all the nodes you specify with --base
509 destination will have all the nodes you specify with --base
510 parameters. To create a bundle containing all changesets, use
510 parameters. To create a bundle containing all changesets, use
511 -a/--all (or --base null).
511 -a/--all (or --base null).
512
512
513 You can change compression method with the -t/--type option.
513 You can change compression method with the -t/--type option.
514 The available compression methods are: none, bzip2, and
514 The available compression methods are: none, bzip2, and
515 gzip (by default, bundles are compressed using bzip2).
515 gzip (by default, bundles are compressed using bzip2).
516
516
517 The bundle file can then be transferred using conventional means
517 The bundle file can then be transferred using conventional means
518 and applied to another repository with the unbundle or pull
518 and applied to another repository with the unbundle or pull
519 command. This is useful when direct push and pull are not
519 command. This is useful when direct push and pull are not
520 available or when exporting an entire repository is undesirable.
520 available or when exporting an entire repository is undesirable.
521
521
522 Applying bundles preserves all changeset contents including
522 Applying bundles preserves all changeset contents including
523 permissions, copy/rename information, and revision history.
523 permissions, copy/rename information, and revision history.
524 """
524 """
525 revs = opts.get('rev') or None
525 revs = opts.get('rev') or None
526 if revs:
526 if revs:
527 revs = [repo.lookup(rev) for rev in revs]
527 revs = [repo.lookup(rev) for rev in revs]
528 if opts.get('all'):
528 if opts.get('all'):
529 base = ['null']
529 base = ['null']
530 else:
530 else:
531 base = opts.get('base')
531 base = opts.get('base')
532 if base:
532 if base:
533 if dest:
533 if dest:
534 raise util.Abort(_("--base is incompatible with specifying "
534 raise util.Abort(_("--base is incompatible with specifying "
535 "a destination"))
535 "a destination"))
536 base = [repo.lookup(rev) for rev in base]
536 base = [repo.lookup(rev) for rev in base]
537 # create the right base
537 # create the right base
538 # XXX: nodesbetween / changegroup* should be "fixed" instead
538 # XXX: nodesbetween / changegroup* should be "fixed" instead
539 o = []
539 o = []
540 has = set((nullid,))
540 has = set((nullid,))
541 for n in base:
541 for n in base:
542 has.update(repo.changelog.reachable(n))
542 has.update(repo.changelog.reachable(n))
543 if revs:
543 if revs:
544 visit = list(revs)
544 visit = list(revs)
545 has.difference_update(revs)
545 has.difference_update(revs)
546 else:
546 else:
547 visit = repo.changelog.heads()
547 visit = repo.changelog.heads()
548 seen = {}
548 seen = {}
549 while visit:
549 while visit:
550 n = visit.pop(0)
550 n = visit.pop(0)
551 parents = [p for p in repo.changelog.parents(n) if p not in has]
551 parents = [p for p in repo.changelog.parents(n) if p not in has]
552 if len(parents) == 0:
552 if len(parents) == 0:
553 if n not in has:
553 if n not in has:
554 o.append(n)
554 o.append(n)
555 else:
555 else:
556 for p in parents:
556 for p in parents:
557 if p not in seen:
557 if p not in seen:
558 seen[p] = 1
558 seen[p] = 1
559 visit.append(p)
559 visit.append(p)
560 else:
560 else:
561 dest = ui.expandpath(dest or 'default-push', dest or 'default')
561 dest = ui.expandpath(dest or 'default-push', dest or 'default')
562 dest, branches = hg.parseurl(dest, opts.get('branch'))
562 dest, branches = hg.parseurl(dest, opts.get('branch'))
563 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
563 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
564 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
564 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
565 o = repo.findoutgoing(other, force=opts.get('force'))
565 o = repo.findoutgoing(other, force=opts.get('force'))
566
566
567 if revs:
567 if revs:
568 cg = repo.changegroupsubset(o, revs, 'bundle')
568 cg = repo.changegroupsubset(o, revs, 'bundle')
569 else:
569 else:
570 cg = repo.changegroup(o, 'bundle')
570 cg = repo.changegroup(o, 'bundle')
571
571
572 bundletype = opts.get('type', 'bzip2').lower()
572 bundletype = opts.get('type', 'bzip2').lower()
573 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
573 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
574 bundletype = btypes.get(bundletype)
574 bundletype = btypes.get(bundletype)
575 if bundletype not in changegroup.bundletypes:
575 if bundletype not in changegroup.bundletypes:
576 raise util.Abort(_('unknown bundle type specified with --type'))
576 raise util.Abort(_('unknown bundle type specified with --type'))
577
577
578 changegroup.writebundle(cg, fname, bundletype)
578 changegroup.writebundle(cg, fname, bundletype)
579
579
580 def cat(ui, repo, file1, *pats, **opts):
580 def cat(ui, repo, file1, *pats, **opts):
581 """output the current or given revision of files
581 """output the current or given revision of files
582
582
583 Print the specified files as they were at the given revision. If
583 Print the specified files as they were at the given revision. If
584 no revision is given, the parent of the working directory is used,
584 no revision is given, the parent of the working directory is used,
585 or tip if no revision is checked out.
585 or tip if no revision is checked out.
586
586
587 Output may be to a file, in which case the name of the file is
587 Output may be to a file, in which case the name of the file is
588 given using a format string. The formatting rules are the same as
588 given using a format string. The formatting rules are the same as
589 for the export command, with the following additions:
589 for the export command, with the following additions:
590
590
591 :``%s``: basename of file being printed
591 :``%s``: basename of file being printed
592 :``%d``: dirname of file being printed, or '.' if in repository root
592 :``%d``: dirname of file being printed, or '.' if in repository root
593 :``%p``: root-relative path name of file being printed
593 :``%p``: root-relative path name of file being printed
594 """
594 """
595 ctx = repo[opts.get('rev')]
595 ctx = repo[opts.get('rev')]
596 err = 1
596 err = 1
597 m = cmdutil.match(repo, (file1,) + pats, opts)
597 m = cmdutil.match(repo, (file1,) + pats, opts)
598 for abs in ctx.walk(m):
598 for abs in ctx.walk(m):
599 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
599 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
600 data = ctx[abs].data()
600 data = ctx[abs].data()
601 if opts.get('decode'):
601 if opts.get('decode'):
602 data = repo.wwritedata(abs, data)
602 data = repo.wwritedata(abs, data)
603 fp.write(data)
603 fp.write(data)
604 err = 0
604 err = 0
605 return err
605 return err
606
606
607 def clone(ui, source, dest=None, **opts):
607 def clone(ui, source, dest=None, **opts):
608 """make a copy of an existing repository
608 """make a copy of an existing repository
609
609
610 Create a copy of an existing repository in a new directory.
610 Create a copy of an existing repository in a new directory.
611
611
612 If no destination directory name is specified, it defaults to the
612 If no destination directory name is specified, it defaults to the
613 basename of the source.
613 basename of the source.
614
614
615 The location of the source is added to the new repository's
615 The location of the source is added to the new repository's
616 .hg/hgrc file, as the default to be used for future pulls.
616 .hg/hgrc file, as the default to be used for future pulls.
617
617
618 See 'hg help urls' for valid source format details.
618 See 'hg help urls' for valid source format details.
619
619
620 It is possible to specify an ``ssh://`` URL as the destination, but no
620 It is possible to specify an ``ssh://`` URL as the destination, but no
621 .hg/hgrc and working directory will be created on the remote side.
621 .hg/hgrc and working directory will be created on the remote side.
622 Please see 'hg help urls' for important details about ``ssh://`` URLs.
622 Please see 'hg help urls' for important details about ``ssh://`` URLs.
623
623
624 If the -U/--noupdate option is specified, the new clone will contain
624 If the -U/--noupdate option is specified, the new clone will contain
625 only a repository (.hg) and no working copy (the working copy parent
625 only a repository (.hg) and no working copy (the working copy parent
626 will be the null changeset). Otherwise, clone will initially check
626 will be the null changeset). Otherwise, clone will initially check
627 out (in order of precedence):
627 out (in order of precedence):
628
628
629 a) the changeset, tag or branch specified with -u/--updaterev
629 a) the changeset, tag or branch specified with -u/--updaterev
630 b) the changeset, tag or branch given with the first -r/--rev
630 b) the changeset, tag or branch given with the first -r/--rev
631 c) the branch given with the first -b/--branch
631 c) the branch given with the first -b/--branch
632 d) the branch given with the url#branch source syntax
632 d) the branch given with the url#branch source syntax
633 e) the head of the default branch
633 e) the head of the default branch
634
634
635 Use 'hg clone -u . src dst' to checkout the source repository's
635 Use 'hg clone -u . src dst' to checkout the source repository's
636 parent changeset (applicable for local source repositories only).
636 parent changeset (applicable for local source repositories only).
637
637
638 A set of changesets (tags, or branch names) to pull may be specified
638 A set of changesets (tags, or branch names) to pull may be specified
639 by listing each changeset (tag, or branch name) with -r/--rev.
639 by listing each changeset (tag, or branch name) with -r/--rev.
640 If -r/--rev is used, the cloned repository will contain only a subset
640 If -r/--rev is used, the cloned repository will contain only a subset
641 of the changesets of the source repository. Only the set of changesets
641 of the changesets of the source repository. Only the set of changesets
642 defined by all -r/--rev options (including all their ancestors)
642 defined by all -r/--rev options (including all their ancestors)
643 will be pulled into the destination repository.
643 will be pulled into the destination repository.
644 No subsequent changesets (including subsequent tags) will be present
644 No subsequent changesets (including subsequent tags) will be present
645 in the destination.
645 in the destination.
646
646
647 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
647 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
648 local source repositories.
648 local source repositories.
649
649
650 For efficiency, hardlinks are used for cloning whenever the source
650 For efficiency, hardlinks are used for cloning whenever the source
651 and destination are on the same filesystem (note this applies only
651 and destination are on the same filesystem (note this applies only
652 to the repository data, not to the checked out files). Some
652 to the repository data, not to the checked out files). Some
653 filesystems, such as AFS, implement hardlinking incorrectly, but
653 filesystems, such as AFS, implement hardlinking incorrectly, but
654 do not report errors. In these cases, use the --pull option to
654 do not report errors. In these cases, use the --pull option to
655 avoid hardlinking.
655 avoid hardlinking.
656
656
657 In some cases, you can clone repositories and checked out files
657 In some cases, you can clone repositories and checked out files
658 using full hardlinks with ::
658 using full hardlinks with ::
659
659
660 $ cp -al REPO REPOCLONE
660 $ cp -al REPO REPOCLONE
661
661
662 This is the fastest way to clone, but it is not always safe. The
662 This is the fastest way to clone, but it is not always safe. The
663 operation is not atomic (making sure REPO is not modified during
663 operation is not atomic (making sure REPO is not modified during
664 the operation is up to you) and you have to make sure your editor
664 the operation is up to you) and you have to make sure your editor
665 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
665 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
666 this is not compatible with certain extensions that place their
666 this is not compatible with certain extensions that place their
667 metadata under the .hg directory, such as mq.
667 metadata under the .hg directory, such as mq.
668 """
668 """
669 if opts.get('noupdate') and opts.get('updaterev'):
669 if opts.get('noupdate') and opts.get('updaterev'):
670 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
670 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
671
671
672 hg.clone(cmdutil.remoteui(ui, opts), source, dest,
672 hg.clone(cmdutil.remoteui(ui, opts), source, dest,
673 pull=opts.get('pull'),
673 pull=opts.get('pull'),
674 stream=opts.get('uncompressed'),
674 stream=opts.get('uncompressed'),
675 rev=opts.get('rev'),
675 rev=opts.get('rev'),
676 update=opts.get('updaterev') or not opts.get('noupdate'),
676 update=opts.get('updaterev') or not opts.get('noupdate'),
677 branch=opts.get('branch'))
677 branch=opts.get('branch'))
678
678
679 def commit(ui, repo, *pats, **opts):
679 def commit(ui, repo, *pats, **opts):
680 """commit the specified files or all outstanding changes
680 """commit the specified files or all outstanding changes
681
681
682 Commit changes to the given files into the repository. Unlike a
682 Commit changes to the given files into the repository. Unlike a
683 centralized RCS, this operation is a local operation. See hg push
683 centralized RCS, this operation is a local operation. See hg push
684 for a way to actively distribute your changes.
684 for a way to actively distribute your changes.
685
685
686 If a list of files is omitted, all changes reported by "hg status"
686 If a list of files is omitted, all changes reported by "hg status"
687 will be committed.
687 will be committed.
688
688
689 If you are committing the result of a merge, do not provide any
689 If you are committing the result of a merge, do not provide any
690 filenames or -I/-X filters.
690 filenames or -I/-X filters.
691
691
692 If no commit message is specified, the configured editor is
692 If no commit message is specified, the configured editor is
693 started to prompt you for a message.
693 started to prompt you for a message.
694
694
695 See 'hg help dates' for a list of formats valid for -d/--date.
695 See 'hg help dates' for a list of formats valid for -d/--date.
696 """
696 """
697 extra = {}
697 extra = {}
698 if opts.get('close_branch'):
698 if opts.get('close_branch'):
699 extra['close'] = 1
699 extra['close'] = 1
700 e = cmdutil.commiteditor
700 e = cmdutil.commiteditor
701 if opts.get('force_editor'):
701 if opts.get('force_editor'):
702 e = cmdutil.commitforceeditor
702 e = cmdutil.commitforceeditor
703
703
704 def commitfunc(ui, repo, message, match, opts):
704 def commitfunc(ui, repo, message, match, opts):
705 return repo.commit(message, opts.get('user'), opts.get('date'), match,
705 return repo.commit(message, opts.get('user'), opts.get('date'), match,
706 editor=e, extra=extra)
706 editor=e, extra=extra)
707
707
708 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
708 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
709 if not node:
709 if not node:
710 ui.status(_("nothing changed\n"))
710 ui.status(_("nothing changed\n"))
711 return
711 return
712 cl = repo.changelog
712 cl = repo.changelog
713 rev = cl.rev(node)
713 rev = cl.rev(node)
714 parents = cl.parentrevs(rev)
714 parents = cl.parentrevs(rev)
715 if rev - 1 in parents:
715 if rev - 1 in parents:
716 # one of the parents was the old tip
716 # one of the parents was the old tip
717 pass
717 pass
718 elif (parents == (nullrev, nullrev) or
718 elif (parents == (nullrev, nullrev) or
719 len(cl.heads(cl.node(parents[0]))) > 1 and
719 len(cl.heads(cl.node(parents[0]))) > 1 and
720 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
720 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
721 ui.status(_('created new head\n'))
721 ui.status(_('created new head\n'))
722
722
723 if ui.debugflag:
723 if ui.debugflag:
724 ui.write(_('committed changeset %d:%s\n') % (rev, hex(node)))
724 ui.write(_('committed changeset %d:%s\n') % (rev, hex(node)))
725 elif ui.verbose:
725 elif ui.verbose:
726 ui.write(_('committed changeset %d:%s\n') % (rev, short(node)))
726 ui.write(_('committed changeset %d:%s\n') % (rev, short(node)))
727
727
728 def copy(ui, repo, *pats, **opts):
728 def copy(ui, repo, *pats, **opts):
729 """mark files as copied for the next commit
729 """mark files as copied for the next commit
730
730
731 Mark dest as having copies of source files. If dest is a
731 Mark dest as having copies of source files. If dest is a
732 directory, copies are put in that directory. If dest is a file,
732 directory, copies are put in that directory. If dest is a file,
733 the source must be a single file.
733 the source must be a single file.
734
734
735 By default, this command copies the contents of files as they
735 By default, this command copies the contents of files as they
736 exist in the working directory. If invoked with -A/--after, the
736 exist in the working directory. If invoked with -A/--after, the
737 operation is recorded, but no copying is performed.
737 operation is recorded, but no copying is performed.
738
738
739 This command takes effect with the next commit. To undo a copy
739 This command takes effect with the next commit. To undo a copy
740 before that, see hg revert.
740 before that, see hg revert.
741 """
741 """
742 wlock = repo.wlock(False)
742 wlock = repo.wlock(False)
743 try:
743 try:
744 return cmdutil.copy(ui, repo, pats, opts)
744 return cmdutil.copy(ui, repo, pats, opts)
745 finally:
745 finally:
746 wlock.release()
746 wlock.release()
747
747
748 def debugancestor(ui, repo, *args):
748 def debugancestor(ui, repo, *args):
749 """find the ancestor revision of two revisions in a given index"""
749 """find the ancestor revision of two revisions in a given index"""
750 if len(args) == 3:
750 if len(args) == 3:
751 index, rev1, rev2 = args
751 index, rev1, rev2 = args
752 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
752 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
753 lookup = r.lookup
753 lookup = r.lookup
754 elif len(args) == 2:
754 elif len(args) == 2:
755 if not repo:
755 if not repo:
756 raise util.Abort(_("There is no Mercurial repository here "
756 raise util.Abort(_("There is no Mercurial repository here "
757 "(.hg not found)"))
757 "(.hg not found)"))
758 rev1, rev2 = args
758 rev1, rev2 = args
759 r = repo.changelog
759 r = repo.changelog
760 lookup = repo.lookup
760 lookup = repo.lookup
761 else:
761 else:
762 raise util.Abort(_('either two or three arguments required'))
762 raise util.Abort(_('either two or three arguments required'))
763 a = r.ancestor(lookup(rev1), lookup(rev2))
763 a = r.ancestor(lookup(rev1), lookup(rev2))
764 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
764 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
765
765
766 def debugcommands(ui, cmd='', *args):
766 def debugcommands(ui, cmd='', *args):
767 for cmd, vals in sorted(table.iteritems()):
767 for cmd, vals in sorted(table.iteritems()):
768 cmd = cmd.split('|')[0].strip('^')
768 cmd = cmd.split('|')[0].strip('^')
769 opts = ', '.join([i[1] for i in vals[1]])
769 opts = ', '.join([i[1] for i in vals[1]])
770 ui.write('%s: %s\n' % (cmd, opts))
770 ui.write('%s: %s\n' % (cmd, opts))
771
771
772 def debugcomplete(ui, cmd='', **opts):
772 def debugcomplete(ui, cmd='', **opts):
773 """returns the completion list associated with the given command"""
773 """returns the completion list associated with the given command"""
774
774
775 if opts.get('options'):
775 if opts.get('options'):
776 options = []
776 options = []
777 otables = [globalopts]
777 otables = [globalopts]
778 if cmd:
778 if cmd:
779 aliases, entry = cmdutil.findcmd(cmd, table, False)
779 aliases, entry = cmdutil.findcmd(cmd, table, False)
780 otables.append(entry[1])
780 otables.append(entry[1])
781 for t in otables:
781 for t in otables:
782 for o in t:
782 for o in t:
783 if o[0]:
783 if o[0]:
784 options.append('-%s' % o[0])
784 options.append('-%s' % o[0])
785 options.append('--%s' % o[1])
785 options.append('--%s' % o[1])
786 ui.write("%s\n" % "\n".join(options))
786 ui.write("%s\n" % "\n".join(options))
787 return
787 return
788
788
789 cmdlist = cmdutil.findpossible(cmd, table)
789 cmdlist = cmdutil.findpossible(cmd, table)
790 if ui.verbose:
790 if ui.verbose:
791 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
791 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
792 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
792 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
793
793
794 def debugfsinfo(ui, path = "."):
794 def debugfsinfo(ui, path = "."):
795 open('.debugfsinfo', 'w').write('')
795 open('.debugfsinfo', 'w').write('')
796 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
796 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
797 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
797 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
798 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
798 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
799 and 'yes' or 'no'))
799 and 'yes' or 'no'))
800 os.unlink('.debugfsinfo')
800 os.unlink('.debugfsinfo')
801
801
802 def debugrebuildstate(ui, repo, rev="tip"):
802 def debugrebuildstate(ui, repo, rev="tip"):
803 """rebuild the dirstate as it would look like for the given revision"""
803 """rebuild the dirstate as it would look like for the given revision"""
804 ctx = repo[rev]
804 ctx = repo[rev]
805 wlock = repo.wlock()
805 wlock = repo.wlock()
806 try:
806 try:
807 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
807 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
808 finally:
808 finally:
809 wlock.release()
809 wlock.release()
810
810
811 def debugcheckstate(ui, repo):
811 def debugcheckstate(ui, repo):
812 """validate the correctness of the current dirstate"""
812 """validate the correctness of the current dirstate"""
813 parent1, parent2 = repo.dirstate.parents()
813 parent1, parent2 = repo.dirstate.parents()
814 m1 = repo[parent1].manifest()
814 m1 = repo[parent1].manifest()
815 m2 = repo[parent2].manifest()
815 m2 = repo[parent2].manifest()
816 errors = 0
816 errors = 0
817 for f in repo.dirstate:
817 for f in repo.dirstate:
818 state = repo.dirstate[f]
818 state = repo.dirstate[f]
819 if state in "nr" and f not in m1:
819 if state in "nr" and f not in m1:
820 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
820 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
821 errors += 1
821 errors += 1
822 if state in "a" and f in m1:
822 if state in "a" and f in m1:
823 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
823 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
824 errors += 1
824 errors += 1
825 if state in "m" and f not in m1 and f not in m2:
825 if state in "m" and f not in m1 and f not in m2:
826 ui.warn(_("%s in state %s, but not in either manifest\n") %
826 ui.warn(_("%s in state %s, but not in either manifest\n") %
827 (f, state))
827 (f, state))
828 errors += 1
828 errors += 1
829 for f in m1:
829 for f in m1:
830 state = repo.dirstate[f]
830 state = repo.dirstate[f]
831 if state not in "nrm":
831 if state not in "nrm":
832 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
832 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
833 errors += 1
833 errors += 1
834 if errors:
834 if errors:
835 error = _(".hg/dirstate inconsistent with current parent's manifest")
835 error = _(".hg/dirstate inconsistent with current parent's manifest")
836 raise util.Abort(error)
836 raise util.Abort(error)
837
837
838 def showconfig(ui, repo, *values, **opts):
838 def showconfig(ui, repo, *values, **opts):
839 """show combined config settings from all hgrc files
839 """show combined config settings from all hgrc files
840
840
841 With no arguments, print names and values of all config items.
841 With no arguments, print names and values of all config items.
842
842
843 With one argument of the form section.name, print just the value
843 With one argument of the form section.name, print just the value
844 of that config item.
844 of that config item.
845
845
846 With multiple arguments, print names and values of all config
846 With multiple arguments, print names and values of all config
847 items with matching section names.
847 items with matching section names.
848
848
849 With --debug, the source (filename and line number) is printed
849 With --debug, the source (filename and line number) is printed
850 for each config item.
850 for each config item.
851 """
851 """
852
852
853 untrusted = bool(opts.get('untrusted'))
853 untrusted = bool(opts.get('untrusted'))
854 if values:
854 if values:
855 if len([v for v in values if '.' in v]) > 1:
855 if len([v for v in values if '.' in v]) > 1:
856 raise util.Abort(_('only one config item permitted'))
856 raise util.Abort(_('only one config item permitted'))
857 for section, name, value in ui.walkconfig(untrusted=untrusted):
857 for section, name, value in ui.walkconfig(untrusted=untrusted):
858 sectname = section + '.' + name
858 sectname = section + '.' + name
859 if values:
859 if values:
860 for v in values:
860 for v in values:
861 if v == section:
861 if v == section:
862 ui.debug('%s: ' %
862 ui.debug('%s: ' %
863 ui.configsource(section, name, untrusted))
863 ui.configsource(section, name, untrusted))
864 ui.write('%s=%s\n' % (sectname, value))
864 ui.write('%s=%s\n' % (sectname, value))
865 elif v == sectname:
865 elif v == sectname:
866 ui.debug('%s: ' %
866 ui.debug('%s: ' %
867 ui.configsource(section, name, untrusted))
867 ui.configsource(section, name, untrusted))
868 ui.write(value, '\n')
868 ui.write(value, '\n')
869 else:
869 else:
870 ui.debug('%s: ' %
870 ui.debug('%s: ' %
871 ui.configsource(section, name, untrusted))
871 ui.configsource(section, name, untrusted))
872 ui.write('%s=%s\n' % (sectname, value))
872 ui.write('%s=%s\n' % (sectname, value))
873
873
874 def debugsetparents(ui, repo, rev1, rev2=None):
874 def debugsetparents(ui, repo, rev1, rev2=None):
875 """manually set the parents of the current working directory
875 """manually set the parents of the current working directory
876
876
877 This is useful for writing repository conversion tools, but should
877 This is useful for writing repository conversion tools, but should
878 be used with care.
878 be used with care.
879 """
879 """
880
880
881 if not rev2:
881 if not rev2:
882 rev2 = hex(nullid)
882 rev2 = hex(nullid)
883
883
884 wlock = repo.wlock()
884 wlock = repo.wlock()
885 try:
885 try:
886 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
886 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
887 finally:
887 finally:
888 wlock.release()
888 wlock.release()
889
889
890 def debugstate(ui, repo, nodates=None):
890 def debugstate(ui, repo, nodates=None):
891 """show the contents of the current dirstate"""
891 """show the contents of the current dirstate"""
892 timestr = ""
892 timestr = ""
893 showdate = not nodates
893 showdate = not nodates
894 for file_, ent in sorted(repo.dirstate._map.iteritems()):
894 for file_, ent in sorted(repo.dirstate._map.iteritems()):
895 if showdate:
895 if showdate:
896 if ent[3] == -1:
896 if ent[3] == -1:
897 # Pad or slice to locale representation
897 # Pad or slice to locale representation
898 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
898 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
899 time.localtime(0)))
899 time.localtime(0)))
900 timestr = 'unset'
900 timestr = 'unset'
901 timestr = (timestr[:locale_len] +
901 timestr = (timestr[:locale_len] +
902 ' ' * (locale_len - len(timestr)))
902 ' ' * (locale_len - len(timestr)))
903 else:
903 else:
904 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
904 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
905 time.localtime(ent[3]))
905 time.localtime(ent[3]))
906 if ent[1] & 020000:
906 if ent[1] & 020000:
907 mode = 'lnk'
907 mode = 'lnk'
908 else:
908 else:
909 mode = '%3o' % (ent[1] & 0777)
909 mode = '%3o' % (ent[1] & 0777)
910 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
910 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
911 for f in repo.dirstate.copies():
911 for f in repo.dirstate.copies():
912 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
912 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
913
913
914 def debugsub(ui, repo, rev=None):
914 def debugsub(ui, repo, rev=None):
915 if rev == '':
915 if rev == '':
916 rev = None
916 rev = None
917 for k, v in sorted(repo[rev].substate.items()):
917 for k, v in sorted(repo[rev].substate.items()):
918 ui.write('path %s\n' % k)
918 ui.write('path %s\n' % k)
919 ui.write(' source %s\n' % v[0])
919 ui.write(' source %s\n' % v[0])
920 ui.write(' revision %s\n' % v[1])
920 ui.write(' revision %s\n' % v[1])
921
921
922 def debugdata(ui, file_, rev):
922 def debugdata(ui, file_, rev):
923 """dump the contents of a data file revision"""
923 """dump the contents of a data file revision"""
924 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
924 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
925 try:
925 try:
926 ui.write(r.revision(r.lookup(rev)))
926 ui.write(r.revision(r.lookup(rev)))
927 except KeyError:
927 except KeyError:
928 raise util.Abort(_('invalid revision identifier %s') % rev)
928 raise util.Abort(_('invalid revision identifier %s') % rev)
929
929
930 def debugdate(ui, date, range=None, **opts):
930 def debugdate(ui, date, range=None, **opts):
931 """parse and display a date"""
931 """parse and display a date"""
932 if opts["extended"]:
932 if opts["extended"]:
933 d = util.parsedate(date, util.extendeddateformats)
933 d = util.parsedate(date, util.extendeddateformats)
934 else:
934 else:
935 d = util.parsedate(date)
935 d = util.parsedate(date)
936 ui.write("internal: %s %s\n" % d)
936 ui.write("internal: %s %s\n" % d)
937 ui.write("standard: %s\n" % util.datestr(d))
937 ui.write("standard: %s\n" % util.datestr(d))
938 if range:
938 if range:
939 m = util.matchdate(range)
939 m = util.matchdate(range)
940 ui.write("match: %s\n" % m(d[0]))
940 ui.write("match: %s\n" % m(d[0]))
941
941
942 def debugindex(ui, file_):
942 def debugindex(ui, file_):
943 """dump the contents of an index file"""
943 """dump the contents of an index file"""
944 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
944 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
945 ui.write(" rev offset length base linkrev"
945 ui.write(" rev offset length base linkrev"
946 " nodeid p1 p2\n")
946 " nodeid p1 p2\n")
947 for i in r:
947 for i in r:
948 node = r.node(i)
948 node = r.node(i)
949 try:
949 try:
950 pp = r.parents(node)
950 pp = r.parents(node)
951 except:
951 except:
952 pp = [nullid, nullid]
952 pp = [nullid, nullid]
953 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
953 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
954 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
954 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
955 short(node), short(pp[0]), short(pp[1])))
955 short(node), short(pp[0]), short(pp[1])))
956
956
957 def debugindexdot(ui, file_):
957 def debugindexdot(ui, file_):
958 """dump an index DAG as a graphviz dot file"""
958 """dump an index DAG as a graphviz dot file"""
959 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
959 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
960 ui.write("digraph G {\n")
960 ui.write("digraph G {\n")
961 for i in r:
961 for i in r:
962 node = r.node(i)
962 node = r.node(i)
963 pp = r.parents(node)
963 pp = r.parents(node)
964 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
964 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
965 if pp[1] != nullid:
965 if pp[1] != nullid:
966 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
966 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
967 ui.write("}\n")
967 ui.write("}\n")
968
968
969 def debuginstall(ui):
969 def debuginstall(ui):
970 '''test Mercurial installation'''
970 '''test Mercurial installation'''
971
971
972 def writetemp(contents):
972 def writetemp(contents):
973 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
973 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
974 f = os.fdopen(fd, "wb")
974 f = os.fdopen(fd, "wb")
975 f.write(contents)
975 f.write(contents)
976 f.close()
976 f.close()
977 return name
977 return name
978
978
979 problems = 0
979 problems = 0
980
980
981 # encoding
981 # encoding
982 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
982 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
983 try:
983 try:
984 encoding.fromlocal("test")
984 encoding.fromlocal("test")
985 except util.Abort, inst:
985 except util.Abort, inst:
986 ui.write(" %s\n" % inst)
986 ui.write(" %s\n" % inst)
987 ui.write(_(" (check that your locale is properly set)\n"))
987 ui.write(_(" (check that your locale is properly set)\n"))
988 problems += 1
988 problems += 1
989
989
990 # compiled modules
990 # compiled modules
991 ui.status(_("Checking extensions...\n"))
991 ui.status(_("Checking extensions...\n"))
992 try:
992 try:
993 import bdiff, mpatch, base85
993 import bdiff, mpatch, base85
994 except Exception, inst:
994 except Exception, inst:
995 ui.write(" %s\n" % inst)
995 ui.write(" %s\n" % inst)
996 ui.write(_(" One or more extensions could not be found"))
996 ui.write(_(" One or more extensions could not be found"))
997 ui.write(_(" (check that you compiled the extensions)\n"))
997 ui.write(_(" (check that you compiled the extensions)\n"))
998 problems += 1
998 problems += 1
999
999
1000 # templates
1000 # templates
1001 ui.status(_("Checking templates...\n"))
1001 ui.status(_("Checking templates...\n"))
1002 try:
1002 try:
1003 import templater
1003 import templater
1004 templater.templater(templater.templatepath("map-cmdline.default"))
1004 templater.templater(templater.templatepath("map-cmdline.default"))
1005 except Exception, inst:
1005 except Exception, inst:
1006 ui.write(" %s\n" % inst)
1006 ui.write(" %s\n" % inst)
1007 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1007 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1008 problems += 1
1008 problems += 1
1009
1009
1010 # patch
1010 # patch
1011 ui.status(_("Checking patch...\n"))
1011 ui.status(_("Checking patch...\n"))
1012 patchproblems = 0
1012 patchproblems = 0
1013 a = "1\n2\n3\n4\n"
1013 a = "1\n2\n3\n4\n"
1014 b = "1\n2\n3\ninsert\n4\n"
1014 b = "1\n2\n3\ninsert\n4\n"
1015 fa = writetemp(a)
1015 fa = writetemp(a)
1016 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1016 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1017 os.path.basename(fa))
1017 os.path.basename(fa))
1018 fd = writetemp(d)
1018 fd = writetemp(d)
1019
1019
1020 files = {}
1020 files = {}
1021 try:
1021 try:
1022 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1022 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1023 except util.Abort, e:
1023 except util.Abort, e:
1024 ui.write(_(" patch call failed:\n"))
1024 ui.write(_(" patch call failed:\n"))
1025 ui.write(" " + str(e) + "\n")
1025 ui.write(" " + str(e) + "\n")
1026 patchproblems += 1
1026 patchproblems += 1
1027 else:
1027 else:
1028 if list(files) != [os.path.basename(fa)]:
1028 if list(files) != [os.path.basename(fa)]:
1029 ui.write(_(" unexpected patch output!\n"))
1029 ui.write(_(" unexpected patch output!\n"))
1030 patchproblems += 1
1030 patchproblems += 1
1031 a = open(fa).read()
1031 a = open(fa).read()
1032 if a != b:
1032 if a != b:
1033 ui.write(_(" patch test failed!\n"))
1033 ui.write(_(" patch test failed!\n"))
1034 patchproblems += 1
1034 patchproblems += 1
1035
1035
1036 if patchproblems:
1036 if patchproblems:
1037 if ui.config('ui', 'patch'):
1037 if ui.config('ui', 'patch'):
1038 ui.write(_(" (Current patch tool may be incompatible with patch,"
1038 ui.write(_(" (Current patch tool may be incompatible with patch,"
1039 " or misconfigured. Please check your .hgrc file)\n"))
1039 " or misconfigured. Please check your .hgrc file)\n"))
1040 else:
1040 else:
1041 ui.write(_(" Internal patcher failure, please report this error"
1041 ui.write(_(" Internal patcher failure, please report this error"
1042 " to http://mercurial.selenic.com/bts/\n"))
1042 " to http://mercurial.selenic.com/bts/\n"))
1043 problems += patchproblems
1043 problems += patchproblems
1044
1044
1045 os.unlink(fa)
1045 os.unlink(fa)
1046 os.unlink(fd)
1046 os.unlink(fd)
1047
1047
1048 # editor
1048 # editor
1049 ui.status(_("Checking commit editor...\n"))
1049 ui.status(_("Checking commit editor...\n"))
1050 editor = ui.geteditor()
1050 editor = ui.geteditor()
1051 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1051 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1052 if not cmdpath:
1052 if not cmdpath:
1053 if editor == 'vi':
1053 if editor == 'vi':
1054 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1054 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1055 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1055 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1056 else:
1056 else:
1057 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1057 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1058 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1058 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1059 problems += 1
1059 problems += 1
1060
1060
1061 # check username
1061 # check username
1062 ui.status(_("Checking username...\n"))
1062 ui.status(_("Checking username...\n"))
1063 try:
1063 try:
1064 user = ui.username()
1064 user = ui.username()
1065 except util.Abort, e:
1065 except util.Abort, e:
1066 ui.write(" %s\n" % e)
1066 ui.write(" %s\n" % e)
1067 ui.write(_(" (specify a username in your .hgrc file)\n"))
1067 ui.write(_(" (specify a username in your .hgrc file)\n"))
1068 problems += 1
1068 problems += 1
1069
1069
1070 if not problems:
1070 if not problems:
1071 ui.status(_("No problems detected\n"))
1071 ui.status(_("No problems detected\n"))
1072 else:
1072 else:
1073 ui.write(_("%s problems detected,"
1073 ui.write(_("%s problems detected,"
1074 " please check your install!\n") % problems)
1074 " please check your install!\n") % problems)
1075
1075
1076 return problems
1076 return problems
1077
1077
1078 def debugrename(ui, repo, file1, *pats, **opts):
1078 def debugrename(ui, repo, file1, *pats, **opts):
1079 """dump rename information"""
1079 """dump rename information"""
1080
1080
1081 ctx = repo[opts.get('rev')]
1081 ctx = repo[opts.get('rev')]
1082 m = cmdutil.match(repo, (file1,) + pats, opts)
1082 m = cmdutil.match(repo, (file1,) + pats, opts)
1083 for abs in ctx.walk(m):
1083 for abs in ctx.walk(m):
1084 fctx = ctx[abs]
1084 fctx = ctx[abs]
1085 o = fctx.filelog().renamed(fctx.filenode())
1085 o = fctx.filelog().renamed(fctx.filenode())
1086 rel = m.rel(abs)
1086 rel = m.rel(abs)
1087 if o:
1087 if o:
1088 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1088 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1089 else:
1089 else:
1090 ui.write(_("%s not renamed\n") % rel)
1090 ui.write(_("%s not renamed\n") % rel)
1091
1091
1092 def debugwalk(ui, repo, *pats, **opts):
1092 def debugwalk(ui, repo, *pats, **opts):
1093 """show how files match on given patterns"""
1093 """show how files match on given patterns"""
1094 m = cmdutil.match(repo, pats, opts)
1094 m = cmdutil.match(repo, pats, opts)
1095 items = list(repo.walk(m))
1095 items = list(repo.walk(m))
1096 if not items:
1096 if not items:
1097 return
1097 return
1098 fmt = 'f %%-%ds %%-%ds %%s' % (
1098 fmt = 'f %%-%ds %%-%ds %%s' % (
1099 max([len(abs) for abs in items]),
1099 max([len(abs) for abs in items]),
1100 max([len(m.rel(abs)) for abs in items]))
1100 max([len(m.rel(abs)) for abs in items]))
1101 for abs in items:
1101 for abs in items:
1102 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1102 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1103 ui.write("%s\n" % line.rstrip())
1103 ui.write("%s\n" % line.rstrip())
1104
1104
1105 def diff(ui, repo, *pats, **opts):
1105 def diff(ui, repo, *pats, **opts):
1106 """diff repository (or selected files)
1106 """diff repository (or selected files)
1107
1107
1108 Show differences between revisions for the specified files.
1108 Show differences between revisions for the specified files.
1109
1109
1110 Differences between files are shown using the unified diff format.
1110 Differences between files are shown using the unified diff format.
1111
1111
1112 NOTE: diff may generate unexpected results for merges, as it will
1112 NOTE: diff may generate unexpected results for merges, as it will
1113 default to comparing against the working directory's first parent
1113 default to comparing against the working directory's first parent
1114 changeset if no revisions are specified.
1114 changeset if no revisions are specified.
1115
1115
1116 When two revision arguments are given, then changes are shown
1116 When two revision arguments are given, then changes are shown
1117 between those revisions. If only one revision is specified then
1117 between those revisions. If only one revision is specified then
1118 that revision is compared to the working directory, and, when no
1118 that revision is compared to the working directory, and, when no
1119 revisions are specified, the working directory files are compared
1119 revisions are specified, the working directory files are compared
1120 to its parent.
1120 to its parent.
1121
1121
1122 Alternatively you can specify -c/--change with a revision to see
1122 Alternatively you can specify -c/--change with a revision to see
1123 the changes in that changeset relative to its first parent.
1123 the changes in that changeset relative to its first parent.
1124
1124
1125 Without the -a/--text option, diff will avoid generating diffs of
1125 Without the -a/--text option, diff will avoid generating diffs of
1126 files it detects as binary. With -a, diff will generate a diff
1126 files it detects as binary. With -a, diff will generate a diff
1127 anyway, probably with undesirable results.
1127 anyway, probably with undesirable results.
1128
1128
1129 Use the -g/--git option to generate diffs in the git extended diff
1129 Use the -g/--git option to generate diffs in the git extended diff
1130 format. For more information, read 'hg help diffs'.
1130 format. For more information, read 'hg help diffs'.
1131 """
1131 """
1132
1132
1133 revs = opts.get('rev')
1133 revs = opts.get('rev')
1134 change = opts.get('change')
1134 change = opts.get('change')
1135 stat = opts.get('stat')
1135 stat = opts.get('stat')
1136 reverse = opts.get('reverse')
1136 reverse = opts.get('reverse')
1137
1137
1138 if revs and change:
1138 if revs and change:
1139 msg = _('cannot specify --rev and --change at the same time')
1139 msg = _('cannot specify --rev and --change at the same time')
1140 raise util.Abort(msg)
1140 raise util.Abort(msg)
1141 elif change:
1141 elif change:
1142 node2 = repo.lookup(change)
1142 node2 = repo.lookup(change)
1143 node1 = repo[node2].parents()[0].node()
1143 node1 = repo[node2].parents()[0].node()
1144 else:
1144 else:
1145 node1, node2 = cmdutil.revpair(repo, revs)
1145 node1, node2 = cmdutil.revpair(repo, revs)
1146
1146
1147 if reverse:
1147 if reverse:
1148 node1, node2 = node2, node1
1148 node1, node2 = node2, node1
1149
1149
1150 if stat:
1150 if stat:
1151 opts['unified'] = '0'
1151 opts['unified'] = '0'
1152 diffopts = patch.diffopts(ui, opts)
1152 diffopts = patch.diffopts(ui, opts)
1153
1153
1154 m = cmdutil.match(repo, pats, opts)
1154 m = cmdutil.match(repo, pats, opts)
1155 it = patch.diff(repo, node1, node2, match=m, opts=diffopts)
1155 it = patch.diff(repo, node1, node2, match=m, opts=diffopts)
1156 if stat:
1156 if stat:
1157 width = ui.interactive() and util.termwidth() or 80
1157 width = ui.interactive() and util.termwidth() or 80
1158 ui.write(patch.diffstat(util.iterlines(it), width=width,
1158 ui.write(patch.diffstat(util.iterlines(it), width=width,
1159 git=diffopts.git))
1159 git=diffopts.git))
1160 else:
1160 else:
1161 for chunk in it:
1161 for chunk in it:
1162 ui.write(chunk)
1162 ui.write(chunk)
1163
1163
1164 def export(ui, repo, *changesets, **opts):
1164 def export(ui, repo, *changesets, **opts):
1165 """dump the header and diffs for one or more changesets
1165 """dump the header and diffs for one or more changesets
1166
1166
1167 Print the changeset header and diffs for one or more revisions.
1167 Print the changeset header and diffs for one or more revisions.
1168
1168
1169 The information shown in the changeset header is: author, date,
1169 The information shown in the changeset header is: author, date,
1170 branch name (if non-default), changeset hash, parent(s) and commit
1170 branch name (if non-default), changeset hash, parent(s) and commit
1171 comment.
1171 comment.
1172
1172
1173 NOTE: export may generate unexpected diff output for merge
1173 NOTE: export may generate unexpected diff output for merge
1174 changesets, as it will compare the merge changeset against its
1174 changesets, as it will compare the merge changeset against its
1175 first parent only.
1175 first parent only.
1176
1176
1177 Output may be to a file, in which case the name of the file is
1177 Output may be to a file, in which case the name of the file is
1178 given using a format string. The formatting rules are as follows:
1178 given using a format string. The formatting rules are as follows:
1179
1179
1180 :``%%``: literal "%" character
1180 :``%%``: literal "%" character
1181 :``%H``: changeset hash (40 bytes of hexadecimal)
1181 :``%H``: changeset hash (40 bytes of hexadecimal)
1182 :``%N``: number of patches being generated
1182 :``%N``: number of patches being generated
1183 :``%R``: changeset revision number
1183 :``%R``: changeset revision number
1184 :``%b``: basename of the exporting repository
1184 :``%b``: basename of the exporting repository
1185 :``%h``: short-form changeset hash (12 bytes of hexadecimal)
1185 :``%h``: short-form changeset hash (12 bytes of hexadecimal)
1186 :``%n``: zero-padded sequence number, starting at 1
1186 :``%n``: zero-padded sequence number, starting at 1
1187 :``%r``: zero-padded changeset revision number
1187 :``%r``: zero-padded changeset revision number
1188
1188
1189 Without the -a/--text option, export will avoid generating diffs
1189 Without the -a/--text option, export will avoid generating diffs
1190 of files it detects as binary. With -a, export will generate a
1190 of files it detects as binary. With -a, export will generate a
1191 diff anyway, probably with undesirable results.
1191 diff anyway, probably with undesirable results.
1192
1192
1193 Use the -g/--git option to generate diffs in the git extended diff
1193 Use the -g/--git option to generate diffs in the git extended diff
1194 format. See 'hg help diffs' for more information.
1194 format. See 'hg help diffs' for more information.
1195
1195
1196 With the --switch-parent option, the diff will be against the
1196 With the --switch-parent option, the diff will be against the
1197 second parent. It can be useful to review a merge.
1197 second parent. It can be useful to review a merge.
1198 """
1198 """
1199 changesets += tuple(opts.get('rev', []))
1199 changesets += tuple(opts.get('rev', []))
1200 if not changesets:
1200 if not changesets:
1201 raise util.Abort(_("export requires at least one changeset"))
1201 raise util.Abort(_("export requires at least one changeset"))
1202 revs = cmdutil.revrange(repo, changesets)
1202 revs = cmdutil.revrange(repo, changesets)
1203 if len(revs) > 1:
1203 if len(revs) > 1:
1204 ui.note(_('exporting patches:\n'))
1204 ui.note(_('exporting patches:\n'))
1205 else:
1205 else:
1206 ui.note(_('exporting patch:\n'))
1206 ui.note(_('exporting patch:\n'))
1207 patch.export(repo, revs, template=opts.get('output'),
1207 cmdutil.export(repo, revs, template=opts.get('output'),
1208 switch_parent=opts.get('switch_parent'),
1208 switch_parent=opts.get('switch_parent'),
1209 opts=patch.diffopts(ui, opts))
1209 opts=patch.diffopts(ui, opts))
1210
1210
1211 def forget(ui, repo, *pats, **opts):
1211 def forget(ui, repo, *pats, **opts):
1212 """forget the specified files on the next commit
1212 """forget the specified files on the next commit
1213
1213
1214 Mark the specified files so they will no longer be tracked
1214 Mark the specified files so they will no longer be tracked
1215 after the next commit.
1215 after the next commit.
1216
1216
1217 This only removes files from the current branch, not from the
1217 This only removes files from the current branch, not from the
1218 entire project history, and it does not delete them from the
1218 entire project history, and it does not delete them from the
1219 working directory.
1219 working directory.
1220
1220
1221 To undo a forget before the next commit, see hg add.
1221 To undo a forget before the next commit, see hg add.
1222 """
1222 """
1223
1223
1224 if not pats:
1224 if not pats:
1225 raise util.Abort(_('no files specified'))
1225 raise util.Abort(_('no files specified'))
1226
1226
1227 m = cmdutil.match(repo, pats, opts)
1227 m = cmdutil.match(repo, pats, opts)
1228 s = repo.status(match=m, clean=True)
1228 s = repo.status(match=m, clean=True)
1229 forget = sorted(s[0] + s[1] + s[3] + s[6])
1229 forget = sorted(s[0] + s[1] + s[3] + s[6])
1230
1230
1231 for f in m.files():
1231 for f in m.files():
1232 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1232 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1233 ui.warn(_('not removing %s: file is already untracked\n')
1233 ui.warn(_('not removing %s: file is already untracked\n')
1234 % m.rel(f))
1234 % m.rel(f))
1235
1235
1236 for f in forget:
1236 for f in forget:
1237 if ui.verbose or not m.exact(f):
1237 if ui.verbose or not m.exact(f):
1238 ui.status(_('removing %s\n') % m.rel(f))
1238 ui.status(_('removing %s\n') % m.rel(f))
1239
1239
1240 repo.remove(forget, unlink=False)
1240 repo.remove(forget, unlink=False)
1241
1241
1242 def grep(ui, repo, pattern, *pats, **opts):
1242 def grep(ui, repo, pattern, *pats, **opts):
1243 """search for a pattern in specified files and revisions
1243 """search for a pattern in specified files and revisions
1244
1244
1245 Search revisions of files for a regular expression.
1245 Search revisions of files for a regular expression.
1246
1246
1247 This command behaves differently than Unix grep. It only accepts
1247 This command behaves differently than Unix grep. It only accepts
1248 Python/Perl regexps. It searches repository history, not the
1248 Python/Perl regexps. It searches repository history, not the
1249 working directory. It always prints the revision number in which a
1249 working directory. It always prints the revision number in which a
1250 match appears.
1250 match appears.
1251
1251
1252 By default, grep only prints output for the first revision of a
1252 By default, grep only prints output for the first revision of a
1253 file in which it finds a match. To get it to print every revision
1253 file in which it finds a match. To get it to print every revision
1254 that contains a change in match status ("-" for a match that
1254 that contains a change in match status ("-" for a match that
1255 becomes a non-match, or "+" for a non-match that becomes a match),
1255 becomes a non-match, or "+" for a non-match that becomes a match),
1256 use the --all flag.
1256 use the --all flag.
1257 """
1257 """
1258 reflags = 0
1258 reflags = 0
1259 if opts.get('ignore_case'):
1259 if opts.get('ignore_case'):
1260 reflags |= re.I
1260 reflags |= re.I
1261 try:
1261 try:
1262 regexp = re.compile(pattern, reflags)
1262 regexp = re.compile(pattern, reflags)
1263 except Exception, inst:
1263 except Exception, inst:
1264 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1264 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1265 return None
1265 return None
1266 sep, eol = ':', '\n'
1266 sep, eol = ':', '\n'
1267 if opts.get('print0'):
1267 if opts.get('print0'):
1268 sep = eol = '\0'
1268 sep = eol = '\0'
1269
1269
1270 getfile = util.lrucachefunc(repo.file)
1270 getfile = util.lrucachefunc(repo.file)
1271
1271
1272 def matchlines(body):
1272 def matchlines(body):
1273 begin = 0
1273 begin = 0
1274 linenum = 0
1274 linenum = 0
1275 while True:
1275 while True:
1276 match = regexp.search(body, begin)
1276 match = regexp.search(body, begin)
1277 if not match:
1277 if not match:
1278 break
1278 break
1279 mstart, mend = match.span()
1279 mstart, mend = match.span()
1280 linenum += body.count('\n', begin, mstart) + 1
1280 linenum += body.count('\n', begin, mstart) + 1
1281 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1281 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1282 begin = body.find('\n', mend) + 1 or len(body)
1282 begin = body.find('\n', mend) + 1 or len(body)
1283 lend = begin - 1
1283 lend = begin - 1
1284 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1284 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1285
1285
1286 class linestate(object):
1286 class linestate(object):
1287 def __init__(self, line, linenum, colstart, colend):
1287 def __init__(self, line, linenum, colstart, colend):
1288 self.line = line
1288 self.line = line
1289 self.linenum = linenum
1289 self.linenum = linenum
1290 self.colstart = colstart
1290 self.colstart = colstart
1291 self.colend = colend
1291 self.colend = colend
1292
1292
1293 def __hash__(self):
1293 def __hash__(self):
1294 return hash((self.linenum, self.line))
1294 return hash((self.linenum, self.line))
1295
1295
1296 def __eq__(self, other):
1296 def __eq__(self, other):
1297 return self.line == other.line
1297 return self.line == other.line
1298
1298
1299 matches = {}
1299 matches = {}
1300 copies = {}
1300 copies = {}
1301 def grepbody(fn, rev, body):
1301 def grepbody(fn, rev, body):
1302 matches[rev].setdefault(fn, [])
1302 matches[rev].setdefault(fn, [])
1303 m = matches[rev][fn]
1303 m = matches[rev][fn]
1304 for lnum, cstart, cend, line in matchlines(body):
1304 for lnum, cstart, cend, line in matchlines(body):
1305 s = linestate(line, lnum, cstart, cend)
1305 s = linestate(line, lnum, cstart, cend)
1306 m.append(s)
1306 m.append(s)
1307
1307
1308 def difflinestates(a, b):
1308 def difflinestates(a, b):
1309 sm = difflib.SequenceMatcher(None, a, b)
1309 sm = difflib.SequenceMatcher(None, a, b)
1310 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1310 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1311 if tag == 'insert':
1311 if tag == 'insert':
1312 for i in xrange(blo, bhi):
1312 for i in xrange(blo, bhi):
1313 yield ('+', b[i])
1313 yield ('+', b[i])
1314 elif tag == 'delete':
1314 elif tag == 'delete':
1315 for i in xrange(alo, ahi):
1315 for i in xrange(alo, ahi):
1316 yield ('-', a[i])
1316 yield ('-', a[i])
1317 elif tag == 'replace':
1317 elif tag == 'replace':
1318 for i in xrange(alo, ahi):
1318 for i in xrange(alo, ahi):
1319 yield ('-', a[i])
1319 yield ('-', a[i])
1320 for i in xrange(blo, bhi):
1320 for i in xrange(blo, bhi):
1321 yield ('+', b[i])
1321 yield ('+', b[i])
1322
1322
1323 def display(fn, ctx, pstates, states):
1323 def display(fn, ctx, pstates, states):
1324 rev = ctx.rev()
1324 rev = ctx.rev()
1325 datefunc = ui.quiet and util.shortdate or util.datestr
1325 datefunc = ui.quiet and util.shortdate or util.datestr
1326 found = False
1326 found = False
1327 filerevmatches = {}
1327 filerevmatches = {}
1328 if opts.get('all'):
1328 if opts.get('all'):
1329 iter = difflinestates(pstates, states)
1329 iter = difflinestates(pstates, states)
1330 else:
1330 else:
1331 iter = [('', l) for l in states]
1331 iter = [('', l) for l in states]
1332 for change, l in iter:
1332 for change, l in iter:
1333 cols = [fn, str(rev)]
1333 cols = [fn, str(rev)]
1334 if opts.get('line_number'):
1334 if opts.get('line_number'):
1335 cols.append(str(l.linenum))
1335 cols.append(str(l.linenum))
1336 if opts.get('all'):
1336 if opts.get('all'):
1337 cols.append(change)
1337 cols.append(change)
1338 if opts.get('user'):
1338 if opts.get('user'):
1339 cols.append(ui.shortuser(ctx.user()))
1339 cols.append(ui.shortuser(ctx.user()))
1340 if opts.get('date'):
1340 if opts.get('date'):
1341 cols.append(datefunc(ctx.date()))
1341 cols.append(datefunc(ctx.date()))
1342 if opts.get('files_with_matches'):
1342 if opts.get('files_with_matches'):
1343 c = (fn, rev)
1343 c = (fn, rev)
1344 if c in filerevmatches:
1344 if c in filerevmatches:
1345 continue
1345 continue
1346 filerevmatches[c] = 1
1346 filerevmatches[c] = 1
1347 else:
1347 else:
1348 cols.append(l.line)
1348 cols.append(l.line)
1349 ui.write(sep.join(cols), eol)
1349 ui.write(sep.join(cols), eol)
1350 found = True
1350 found = True
1351 return found
1351 return found
1352
1352
1353 skip = {}
1353 skip = {}
1354 revfiles = {}
1354 revfiles = {}
1355 matchfn = cmdutil.match(repo, pats, opts)
1355 matchfn = cmdutil.match(repo, pats, opts)
1356 found = False
1356 found = False
1357 follow = opts.get('follow')
1357 follow = opts.get('follow')
1358
1358
1359 def prep(ctx, fns):
1359 def prep(ctx, fns):
1360 rev = ctx.rev()
1360 rev = ctx.rev()
1361 pctx = ctx.parents()[0]
1361 pctx = ctx.parents()[0]
1362 parent = pctx.rev()
1362 parent = pctx.rev()
1363 matches.setdefault(rev, {})
1363 matches.setdefault(rev, {})
1364 matches.setdefault(parent, {})
1364 matches.setdefault(parent, {})
1365 files = revfiles.setdefault(rev, [])
1365 files = revfiles.setdefault(rev, [])
1366 for fn in fns:
1366 for fn in fns:
1367 flog = getfile(fn)
1367 flog = getfile(fn)
1368 try:
1368 try:
1369 fnode = ctx.filenode(fn)
1369 fnode = ctx.filenode(fn)
1370 except error.LookupError:
1370 except error.LookupError:
1371 continue
1371 continue
1372
1372
1373 copied = flog.renamed(fnode)
1373 copied = flog.renamed(fnode)
1374 copy = follow and copied and copied[0]
1374 copy = follow and copied and copied[0]
1375 if copy:
1375 if copy:
1376 copies.setdefault(rev, {})[fn] = copy
1376 copies.setdefault(rev, {})[fn] = copy
1377 if fn in skip:
1377 if fn in skip:
1378 if copy:
1378 if copy:
1379 skip[copy] = True
1379 skip[copy] = True
1380 continue
1380 continue
1381 files.append(fn)
1381 files.append(fn)
1382
1382
1383 if fn not in matches[rev]:
1383 if fn not in matches[rev]:
1384 grepbody(fn, rev, flog.read(fnode))
1384 grepbody(fn, rev, flog.read(fnode))
1385
1385
1386 pfn = copy or fn
1386 pfn = copy or fn
1387 if pfn not in matches[parent]:
1387 if pfn not in matches[parent]:
1388 try:
1388 try:
1389 fnode = pctx.filenode(pfn)
1389 fnode = pctx.filenode(pfn)
1390 grepbody(pfn, parent, flog.read(fnode))
1390 grepbody(pfn, parent, flog.read(fnode))
1391 except error.LookupError:
1391 except error.LookupError:
1392 pass
1392 pass
1393
1393
1394 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1394 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1395 rev = ctx.rev()
1395 rev = ctx.rev()
1396 parent = ctx.parents()[0].rev()
1396 parent = ctx.parents()[0].rev()
1397 for fn in sorted(revfiles.get(rev, [])):
1397 for fn in sorted(revfiles.get(rev, [])):
1398 states = matches[rev][fn]
1398 states = matches[rev][fn]
1399 copy = copies.get(rev, {}).get(fn)
1399 copy = copies.get(rev, {}).get(fn)
1400 if fn in skip:
1400 if fn in skip:
1401 if copy:
1401 if copy:
1402 skip[copy] = True
1402 skip[copy] = True
1403 continue
1403 continue
1404 pstates = matches.get(parent, {}).get(copy or fn, [])
1404 pstates = matches.get(parent, {}).get(copy or fn, [])
1405 if pstates or states:
1405 if pstates or states:
1406 r = display(fn, ctx, pstates, states)
1406 r = display(fn, ctx, pstates, states)
1407 found = found or r
1407 found = found or r
1408 if r and not opts.get('all'):
1408 if r and not opts.get('all'):
1409 skip[fn] = True
1409 skip[fn] = True
1410 if copy:
1410 if copy:
1411 skip[copy] = True
1411 skip[copy] = True
1412 del matches[rev]
1412 del matches[rev]
1413 del revfiles[rev]
1413 del revfiles[rev]
1414
1414
1415 def heads(ui, repo, *branchrevs, **opts):
1415 def heads(ui, repo, *branchrevs, **opts):
1416 """show current repository heads or show branch heads
1416 """show current repository heads or show branch heads
1417
1417
1418 With no arguments, show all repository branch heads.
1418 With no arguments, show all repository branch heads.
1419
1419
1420 Repository "heads" are changesets with no child changesets. They are
1420 Repository "heads" are changesets with no child changesets. They are
1421 where development generally takes place and are the usual targets
1421 where development generally takes place and are the usual targets
1422 for update and merge operations. Branch heads are changesets that have
1422 for update and merge operations. Branch heads are changesets that have
1423 no child changeset on the same branch.
1423 no child changeset on the same branch.
1424
1424
1425 If one or more REVs are given, only branch heads on the branches
1425 If one or more REVs are given, only branch heads on the branches
1426 associated with the specified changesets are shown.
1426 associated with the specified changesets are shown.
1427
1427
1428 If -c/--closed is specified, also show branch heads marked closed
1428 If -c/--closed is specified, also show branch heads marked closed
1429 (see hg commit --close-branch).
1429 (see hg commit --close-branch).
1430
1430
1431 If STARTREV is specified, only those heads that are descendants of
1431 If STARTREV is specified, only those heads that are descendants of
1432 STARTREV will be displayed.
1432 STARTREV will be displayed.
1433
1433
1434 If -t/--topo is specified, named branch mechanics will be ignored and only
1434 If -t/--topo is specified, named branch mechanics will be ignored and only
1435 changesets without children will be shown.
1435 changesets without children will be shown.
1436 """
1436 """
1437
1437
1438 if opts.get('rev'):
1438 if opts.get('rev'):
1439 start = repo.lookup(opts['rev'])
1439 start = repo.lookup(opts['rev'])
1440 else:
1440 else:
1441 start = None
1441 start = None
1442
1442
1443 if opts.get('topo'):
1443 if opts.get('topo'):
1444 heads = [repo[h] for h in repo.heads(start)]
1444 heads = [repo[h] for h in repo.heads(start)]
1445 else:
1445 else:
1446 heads = []
1446 heads = []
1447 for b, ls in repo.branchmap().iteritems():
1447 for b, ls in repo.branchmap().iteritems():
1448 if start is None:
1448 if start is None:
1449 heads += [repo[h] for h in ls]
1449 heads += [repo[h] for h in ls]
1450 continue
1450 continue
1451 startrev = repo.changelog.rev(start)
1451 startrev = repo.changelog.rev(start)
1452 descendants = set(repo.changelog.descendants(startrev))
1452 descendants = set(repo.changelog.descendants(startrev))
1453 descendants.add(startrev)
1453 descendants.add(startrev)
1454 rev = repo.changelog.rev
1454 rev = repo.changelog.rev
1455 heads += [repo[h] for h in ls if rev(h) in descendants]
1455 heads += [repo[h] for h in ls if rev(h) in descendants]
1456
1456
1457 if branchrevs:
1457 if branchrevs:
1458 decode, encode = encoding.fromlocal, encoding.tolocal
1458 decode, encode = encoding.fromlocal, encoding.tolocal
1459 branches = set(repo[decode(br)].branch() for br in branchrevs)
1459 branches = set(repo[decode(br)].branch() for br in branchrevs)
1460 heads = [h for h in heads if h.branch() in branches]
1460 heads = [h for h in heads if h.branch() in branches]
1461
1461
1462 if not opts.get('closed'):
1462 if not opts.get('closed'):
1463 heads = [h for h in heads if not h.extra().get('close')]
1463 heads = [h for h in heads if not h.extra().get('close')]
1464
1464
1465 if opts.get('active') and branchrevs:
1465 if opts.get('active') and branchrevs:
1466 dagheads = repo.heads(start)
1466 dagheads = repo.heads(start)
1467 heads = [h for h in heads if h.node() in dagheads]
1467 heads = [h for h in heads if h.node() in dagheads]
1468
1468
1469 if branchrevs:
1469 if branchrevs:
1470 haveheads = set(h.branch() for h in heads)
1470 haveheads = set(h.branch() for h in heads)
1471 if branches - haveheads:
1471 if branches - haveheads:
1472 headless = ', '.join(encode(b) for b in branches - haveheads)
1472 headless = ', '.join(encode(b) for b in branches - haveheads)
1473 msg = _('no open branch heads found on branches %s')
1473 msg = _('no open branch heads found on branches %s')
1474 if opts.get('rev'):
1474 if opts.get('rev'):
1475 msg += _(' (started at %s)' % opts['rev'])
1475 msg += _(' (started at %s)' % opts['rev'])
1476 ui.warn((msg + '\n') % headless)
1476 ui.warn((msg + '\n') % headless)
1477
1477
1478 if not heads:
1478 if not heads:
1479 return 1
1479 return 1
1480
1480
1481 heads = sorted(heads, key=lambda x: -x.rev())
1481 heads = sorted(heads, key=lambda x: -x.rev())
1482 displayer = cmdutil.show_changeset(ui, repo, opts)
1482 displayer = cmdutil.show_changeset(ui, repo, opts)
1483 for ctx in heads:
1483 for ctx in heads:
1484 displayer.show(ctx)
1484 displayer.show(ctx)
1485 displayer.close()
1485 displayer.close()
1486
1486
1487 def help_(ui, name=None, with_version=False, unknowncmd=False):
1487 def help_(ui, name=None, with_version=False, unknowncmd=False):
1488 """show help for a given topic or a help overview
1488 """show help for a given topic or a help overview
1489
1489
1490 With no arguments, print a list of commands with short help messages.
1490 With no arguments, print a list of commands with short help messages.
1491
1491
1492 Given a topic, extension, or command name, print help for that
1492 Given a topic, extension, or command name, print help for that
1493 topic."""
1493 topic."""
1494 option_lists = []
1494 option_lists = []
1495 textwidth = util.termwidth() - 2
1495 textwidth = util.termwidth() - 2
1496
1496
1497 def addglobalopts(aliases):
1497 def addglobalopts(aliases):
1498 if ui.verbose:
1498 if ui.verbose:
1499 option_lists.append((_("global options:"), globalopts))
1499 option_lists.append((_("global options:"), globalopts))
1500 if name == 'shortlist':
1500 if name == 'shortlist':
1501 option_lists.append((_('use "hg help" for the full list '
1501 option_lists.append((_('use "hg help" for the full list '
1502 'of commands'), ()))
1502 'of commands'), ()))
1503 else:
1503 else:
1504 if name == 'shortlist':
1504 if name == 'shortlist':
1505 msg = _('use "hg help" for the full list of commands '
1505 msg = _('use "hg help" for the full list of commands '
1506 'or "hg -v" for details')
1506 'or "hg -v" for details')
1507 elif aliases:
1507 elif aliases:
1508 msg = _('use "hg -v help%s" to show aliases and '
1508 msg = _('use "hg -v help%s" to show aliases and '
1509 'global options') % (name and " " + name or "")
1509 'global options') % (name and " " + name or "")
1510 else:
1510 else:
1511 msg = _('use "hg -v help %s" to show global options') % name
1511 msg = _('use "hg -v help %s" to show global options') % name
1512 option_lists.append((msg, ()))
1512 option_lists.append((msg, ()))
1513
1513
1514 def helpcmd(name):
1514 def helpcmd(name):
1515 if with_version:
1515 if with_version:
1516 version_(ui)
1516 version_(ui)
1517 ui.write('\n')
1517 ui.write('\n')
1518
1518
1519 try:
1519 try:
1520 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1520 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1521 except error.AmbiguousCommand, inst:
1521 except error.AmbiguousCommand, inst:
1522 # py3k fix: except vars can't be used outside the scope of the
1522 # py3k fix: except vars can't be used outside the scope of the
1523 # except block, nor can be used inside a lambda. python issue4617
1523 # except block, nor can be used inside a lambda. python issue4617
1524 prefix = inst.args[0]
1524 prefix = inst.args[0]
1525 select = lambda c: c.lstrip('^').startswith(prefix)
1525 select = lambda c: c.lstrip('^').startswith(prefix)
1526 helplist(_('list of commands:\n\n'), select)
1526 helplist(_('list of commands:\n\n'), select)
1527 return
1527 return
1528
1528
1529 # check if it's an invalid alias and display its error if it is
1529 # check if it's an invalid alias and display its error if it is
1530 if getattr(entry[0], 'badalias', False):
1530 if getattr(entry[0], 'badalias', False):
1531 if not unknowncmd:
1531 if not unknowncmd:
1532 entry[0](ui)
1532 entry[0](ui)
1533 return
1533 return
1534
1534
1535 # synopsis
1535 # synopsis
1536 if len(entry) > 2:
1536 if len(entry) > 2:
1537 if entry[2].startswith('hg'):
1537 if entry[2].startswith('hg'):
1538 ui.write("%s\n" % entry[2])
1538 ui.write("%s\n" % entry[2])
1539 else:
1539 else:
1540 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
1540 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
1541 else:
1541 else:
1542 ui.write('hg %s\n' % aliases[0])
1542 ui.write('hg %s\n' % aliases[0])
1543
1543
1544 # aliases
1544 # aliases
1545 if not ui.quiet and len(aliases) > 1:
1545 if not ui.quiet and len(aliases) > 1:
1546 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1546 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1547
1547
1548 # description
1548 # description
1549 doc = gettext(entry[0].__doc__)
1549 doc = gettext(entry[0].__doc__)
1550 if not doc:
1550 if not doc:
1551 doc = _("(no help text available)")
1551 doc = _("(no help text available)")
1552 if hasattr(entry[0], 'definition'): # aliased command
1552 if hasattr(entry[0], 'definition'): # aliased command
1553 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
1553 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
1554 if ui.quiet:
1554 if ui.quiet:
1555 doc = doc.splitlines()[0]
1555 doc = doc.splitlines()[0]
1556 keep = ui.verbose and ['verbose'] or []
1556 keep = ui.verbose and ['verbose'] or []
1557 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
1557 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
1558 ui.write("\n%s\n" % formatted)
1558 ui.write("\n%s\n" % formatted)
1559 if pruned:
1559 if pruned:
1560 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
1560 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
1561
1561
1562 if not ui.quiet:
1562 if not ui.quiet:
1563 # options
1563 # options
1564 if entry[1]:
1564 if entry[1]:
1565 option_lists.append((_("options:\n"), entry[1]))
1565 option_lists.append((_("options:\n"), entry[1]))
1566
1566
1567 addglobalopts(False)
1567 addglobalopts(False)
1568
1568
1569 def helplist(header, select=None):
1569 def helplist(header, select=None):
1570 h = {}
1570 h = {}
1571 cmds = {}
1571 cmds = {}
1572 for c, e in table.iteritems():
1572 for c, e in table.iteritems():
1573 f = c.split("|", 1)[0]
1573 f = c.split("|", 1)[0]
1574 if select and not select(f):
1574 if select and not select(f):
1575 continue
1575 continue
1576 if (not select and name != 'shortlist' and
1576 if (not select and name != 'shortlist' and
1577 e[0].__module__ != __name__):
1577 e[0].__module__ != __name__):
1578 continue
1578 continue
1579 if name == "shortlist" and not f.startswith("^"):
1579 if name == "shortlist" and not f.startswith("^"):
1580 continue
1580 continue
1581 f = f.lstrip("^")
1581 f = f.lstrip("^")
1582 if not ui.debugflag and f.startswith("debug"):
1582 if not ui.debugflag and f.startswith("debug"):
1583 continue
1583 continue
1584 doc = e[0].__doc__
1584 doc = e[0].__doc__
1585 if doc and 'DEPRECATED' in doc and not ui.verbose:
1585 if doc and 'DEPRECATED' in doc and not ui.verbose:
1586 continue
1586 continue
1587 doc = gettext(doc)
1587 doc = gettext(doc)
1588 if not doc:
1588 if not doc:
1589 doc = _("(no help text available)")
1589 doc = _("(no help text available)")
1590 h[f] = doc.splitlines()[0].rstrip()
1590 h[f] = doc.splitlines()[0].rstrip()
1591 cmds[f] = c.lstrip("^")
1591 cmds[f] = c.lstrip("^")
1592
1592
1593 if not h:
1593 if not h:
1594 ui.status(_('no commands defined\n'))
1594 ui.status(_('no commands defined\n'))
1595 return
1595 return
1596
1596
1597 ui.status(header)
1597 ui.status(header)
1598 fns = sorted(h)
1598 fns = sorted(h)
1599 m = max(map(len, fns))
1599 m = max(map(len, fns))
1600 for f in fns:
1600 for f in fns:
1601 if ui.verbose:
1601 if ui.verbose:
1602 commands = cmds[f].replace("|",", ")
1602 commands = cmds[f].replace("|",", ")
1603 ui.write(" %s:\n %s\n"%(commands, h[f]))
1603 ui.write(" %s:\n %s\n"%(commands, h[f]))
1604 else:
1604 else:
1605 ui.write(' %-*s %s\n' % (m, f, util.wrap(h[f], m + 4)))
1605 ui.write(' %-*s %s\n' % (m, f, util.wrap(h[f], m + 4)))
1606
1606
1607 if not ui.quiet:
1607 if not ui.quiet:
1608 addglobalopts(True)
1608 addglobalopts(True)
1609
1609
1610 def helptopic(name):
1610 def helptopic(name):
1611 for names, header, doc in help.helptable:
1611 for names, header, doc in help.helptable:
1612 if name in names:
1612 if name in names:
1613 break
1613 break
1614 else:
1614 else:
1615 raise error.UnknownCommand(name)
1615 raise error.UnknownCommand(name)
1616
1616
1617 # description
1617 # description
1618 if not doc:
1618 if not doc:
1619 doc = _("(no help text available)")
1619 doc = _("(no help text available)")
1620 if hasattr(doc, '__call__'):
1620 if hasattr(doc, '__call__'):
1621 doc = doc()
1621 doc = doc()
1622
1622
1623 ui.write("%s\n\n" % header)
1623 ui.write("%s\n\n" % header)
1624 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1624 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1625
1625
1626 def helpext(name):
1626 def helpext(name):
1627 try:
1627 try:
1628 mod = extensions.find(name)
1628 mod = extensions.find(name)
1629 doc = gettext(mod.__doc__) or _('no help text available')
1629 doc = gettext(mod.__doc__) or _('no help text available')
1630 except KeyError:
1630 except KeyError:
1631 mod = None
1631 mod = None
1632 doc = extensions.disabledext(name)
1632 doc = extensions.disabledext(name)
1633 if not doc:
1633 if not doc:
1634 raise error.UnknownCommand(name)
1634 raise error.UnknownCommand(name)
1635
1635
1636 if '\n' not in doc:
1636 if '\n' not in doc:
1637 head, tail = doc, ""
1637 head, tail = doc, ""
1638 else:
1638 else:
1639 head, tail = doc.split('\n', 1)
1639 head, tail = doc.split('\n', 1)
1640 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1640 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1641 if tail:
1641 if tail:
1642 ui.write(minirst.format(tail, textwidth))
1642 ui.write(minirst.format(tail, textwidth))
1643 ui.status('\n\n')
1643 ui.status('\n\n')
1644
1644
1645 if mod:
1645 if mod:
1646 try:
1646 try:
1647 ct = mod.cmdtable
1647 ct = mod.cmdtable
1648 except AttributeError:
1648 except AttributeError:
1649 ct = {}
1649 ct = {}
1650 modcmds = set([c.split('|', 1)[0] for c in ct])
1650 modcmds = set([c.split('|', 1)[0] for c in ct])
1651 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1651 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1652 else:
1652 else:
1653 ui.write(_('use "hg help extensions" for information on enabling '
1653 ui.write(_('use "hg help extensions" for information on enabling '
1654 'extensions\n'))
1654 'extensions\n'))
1655
1655
1656 def helpextcmd(name):
1656 def helpextcmd(name):
1657 cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict'))
1657 cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict'))
1658 doc = gettext(mod.__doc__).splitlines()[0]
1658 doc = gettext(mod.__doc__).splitlines()[0]
1659
1659
1660 msg = help.listexts(_("'%s' is provided by the following "
1660 msg = help.listexts(_("'%s' is provided by the following "
1661 "extension:") % cmd, {ext: doc}, len(ext),
1661 "extension:") % cmd, {ext: doc}, len(ext),
1662 indent=4)
1662 indent=4)
1663 ui.write(minirst.format(msg, textwidth))
1663 ui.write(minirst.format(msg, textwidth))
1664 ui.write('\n\n')
1664 ui.write('\n\n')
1665 ui.write(_('use "hg help extensions" for information on enabling '
1665 ui.write(_('use "hg help extensions" for information on enabling '
1666 'extensions\n'))
1666 'extensions\n'))
1667
1667
1668 if name and name != 'shortlist':
1668 if name and name != 'shortlist':
1669 i = None
1669 i = None
1670 if unknowncmd:
1670 if unknowncmd:
1671 queries = (helpextcmd,)
1671 queries = (helpextcmd,)
1672 else:
1672 else:
1673 queries = (helptopic, helpcmd, helpext, helpextcmd)
1673 queries = (helptopic, helpcmd, helpext, helpextcmd)
1674 for f in queries:
1674 for f in queries:
1675 try:
1675 try:
1676 f(name)
1676 f(name)
1677 i = None
1677 i = None
1678 break
1678 break
1679 except error.UnknownCommand, inst:
1679 except error.UnknownCommand, inst:
1680 i = inst
1680 i = inst
1681 if i:
1681 if i:
1682 raise i
1682 raise i
1683
1683
1684 else:
1684 else:
1685 # program name
1685 # program name
1686 if ui.verbose or with_version:
1686 if ui.verbose or with_version:
1687 version_(ui)
1687 version_(ui)
1688 else:
1688 else:
1689 ui.status(_("Mercurial Distributed SCM\n"))
1689 ui.status(_("Mercurial Distributed SCM\n"))
1690 ui.status('\n')
1690 ui.status('\n')
1691
1691
1692 # list of commands
1692 # list of commands
1693 if name == "shortlist":
1693 if name == "shortlist":
1694 header = _('basic commands:\n\n')
1694 header = _('basic commands:\n\n')
1695 else:
1695 else:
1696 header = _('list of commands:\n\n')
1696 header = _('list of commands:\n\n')
1697
1697
1698 helplist(header)
1698 helplist(header)
1699 if name != 'shortlist':
1699 if name != 'shortlist':
1700 exts, maxlength = extensions.enabled()
1700 exts, maxlength = extensions.enabled()
1701 text = help.listexts(_('enabled extensions:'), exts, maxlength)
1701 text = help.listexts(_('enabled extensions:'), exts, maxlength)
1702 if text:
1702 if text:
1703 ui.write("\n%s\n" % minirst.format(text, textwidth))
1703 ui.write("\n%s\n" % minirst.format(text, textwidth))
1704
1704
1705 # list all option lists
1705 # list all option lists
1706 opt_output = []
1706 opt_output = []
1707 for title, options in option_lists:
1707 for title, options in option_lists:
1708 opt_output.append(("\n%s" % title, None))
1708 opt_output.append(("\n%s" % title, None))
1709 for shortopt, longopt, default, desc in options:
1709 for shortopt, longopt, default, desc in options:
1710 if _("DEPRECATED") in desc and not ui.verbose:
1710 if _("DEPRECATED") in desc and not ui.verbose:
1711 continue
1711 continue
1712 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1712 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1713 longopt and " --%s" % longopt),
1713 longopt and " --%s" % longopt),
1714 "%s%s" % (desc,
1714 "%s%s" % (desc,
1715 default
1715 default
1716 and _(" (default: %s)") % default
1716 and _(" (default: %s)") % default
1717 or "")))
1717 or "")))
1718
1718
1719 if not name:
1719 if not name:
1720 ui.write(_("\nadditional help topics:\n\n"))
1720 ui.write(_("\nadditional help topics:\n\n"))
1721 topics = []
1721 topics = []
1722 for names, header, doc in help.helptable:
1722 for names, header, doc in help.helptable:
1723 topics.append((sorted(names, key=len, reverse=True)[0], header))
1723 topics.append((sorted(names, key=len, reverse=True)[0], header))
1724 topics_len = max([len(s[0]) for s in topics])
1724 topics_len = max([len(s[0]) for s in topics])
1725 for t, desc in topics:
1725 for t, desc in topics:
1726 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1726 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1727
1727
1728 if opt_output:
1728 if opt_output:
1729 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1729 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1730 for first, second in opt_output:
1730 for first, second in opt_output:
1731 if second:
1731 if second:
1732 second = util.wrap(second, opts_len + 3)
1732 second = util.wrap(second, opts_len + 3)
1733 ui.write(" %-*s %s\n" % (opts_len, first, second))
1733 ui.write(" %-*s %s\n" % (opts_len, first, second))
1734 else:
1734 else:
1735 ui.write("%s\n" % first)
1735 ui.write("%s\n" % first)
1736
1736
1737 def identify(ui, repo, source=None,
1737 def identify(ui, repo, source=None,
1738 rev=None, num=None, id=None, branch=None, tags=None):
1738 rev=None, num=None, id=None, branch=None, tags=None):
1739 """identify the working copy or specified revision
1739 """identify the working copy or specified revision
1740
1740
1741 With no revision, print a summary of the current state of the
1741 With no revision, print a summary of the current state of the
1742 repository.
1742 repository.
1743
1743
1744 Specifying a path to a repository root or Mercurial bundle will
1744 Specifying a path to a repository root or Mercurial bundle will
1745 cause lookup to operate on that repository/bundle.
1745 cause lookup to operate on that repository/bundle.
1746
1746
1747 This summary identifies the repository state using one or two
1747 This summary identifies the repository state using one or two
1748 parent hash identifiers, followed by a "+" if there are
1748 parent hash identifiers, followed by a "+" if there are
1749 uncommitted changes in the working directory, a list of tags for
1749 uncommitted changes in the working directory, a list of tags for
1750 this revision and a branch name for non-default branches.
1750 this revision and a branch name for non-default branches.
1751 """
1751 """
1752
1752
1753 if not repo and not source:
1753 if not repo and not source:
1754 raise util.Abort(_("There is no Mercurial repository here "
1754 raise util.Abort(_("There is no Mercurial repository here "
1755 "(.hg not found)"))
1755 "(.hg not found)"))
1756
1756
1757 hexfunc = ui.debugflag and hex or short
1757 hexfunc = ui.debugflag and hex or short
1758 default = not (num or id or branch or tags)
1758 default = not (num or id or branch or tags)
1759 output = []
1759 output = []
1760
1760
1761 revs = []
1761 revs = []
1762 if source:
1762 if source:
1763 source, branches = hg.parseurl(ui.expandpath(source))
1763 source, branches = hg.parseurl(ui.expandpath(source))
1764 repo = hg.repository(ui, source)
1764 repo = hg.repository(ui, source)
1765 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
1765 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
1766
1766
1767 if not repo.local():
1767 if not repo.local():
1768 if not rev and revs:
1768 if not rev and revs:
1769 rev = revs[0]
1769 rev = revs[0]
1770 if not rev:
1770 if not rev:
1771 rev = "tip"
1771 rev = "tip"
1772 if num or branch or tags:
1772 if num or branch or tags:
1773 raise util.Abort(
1773 raise util.Abort(
1774 "can't query remote revision number, branch, or tags")
1774 "can't query remote revision number, branch, or tags")
1775 output = [hexfunc(repo.lookup(rev))]
1775 output = [hexfunc(repo.lookup(rev))]
1776 elif not rev:
1776 elif not rev:
1777 ctx = repo[None]
1777 ctx = repo[None]
1778 parents = ctx.parents()
1778 parents = ctx.parents()
1779 changed = False
1779 changed = False
1780 if default or id or num:
1780 if default or id or num:
1781 changed = util.any(repo.status())
1781 changed = util.any(repo.status())
1782 if default or id:
1782 if default or id:
1783 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1783 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1784 (changed) and "+" or "")]
1784 (changed) and "+" or "")]
1785 if num:
1785 if num:
1786 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1786 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1787 (changed) and "+" or ""))
1787 (changed) and "+" or ""))
1788 else:
1788 else:
1789 ctx = repo[rev]
1789 ctx = repo[rev]
1790 if default or id:
1790 if default or id:
1791 output = [hexfunc(ctx.node())]
1791 output = [hexfunc(ctx.node())]
1792 if num:
1792 if num:
1793 output.append(str(ctx.rev()))
1793 output.append(str(ctx.rev()))
1794
1794
1795 if repo.local() and default and not ui.quiet:
1795 if repo.local() and default and not ui.quiet:
1796 b = encoding.tolocal(ctx.branch())
1796 b = encoding.tolocal(ctx.branch())
1797 if b != 'default':
1797 if b != 'default':
1798 output.append("(%s)" % b)
1798 output.append("(%s)" % b)
1799
1799
1800 # multiple tags for a single parent separated by '/'
1800 # multiple tags for a single parent separated by '/'
1801 t = "/".join(ctx.tags())
1801 t = "/".join(ctx.tags())
1802 if t:
1802 if t:
1803 output.append(t)
1803 output.append(t)
1804
1804
1805 if branch:
1805 if branch:
1806 output.append(encoding.tolocal(ctx.branch()))
1806 output.append(encoding.tolocal(ctx.branch()))
1807
1807
1808 if tags:
1808 if tags:
1809 output.extend(ctx.tags())
1809 output.extend(ctx.tags())
1810
1810
1811 ui.write("%s\n" % ' '.join(output))
1811 ui.write("%s\n" % ' '.join(output))
1812
1812
1813 def import_(ui, repo, patch1, *patches, **opts):
1813 def import_(ui, repo, patch1, *patches, **opts):
1814 """import an ordered set of patches
1814 """import an ordered set of patches
1815
1815
1816 Import a list of patches and commit them individually (unless
1816 Import a list of patches and commit them individually (unless
1817 --no-commit is specified).
1817 --no-commit is specified).
1818
1818
1819 If there are outstanding changes in the working directory, import
1819 If there are outstanding changes in the working directory, import
1820 will abort unless given the -f/--force flag.
1820 will abort unless given the -f/--force flag.
1821
1821
1822 You can import a patch straight from a mail message. Even patches
1822 You can import a patch straight from a mail message. Even patches
1823 as attachments work (to use the body part, it must have type
1823 as attachments work (to use the body part, it must have type
1824 text/plain or text/x-patch). From and Subject headers of email
1824 text/plain or text/x-patch). From and Subject headers of email
1825 message are used as default committer and commit message. All
1825 message are used as default committer and commit message. All
1826 text/plain body parts before first diff are added to commit
1826 text/plain body parts before first diff are added to commit
1827 message.
1827 message.
1828
1828
1829 If the imported patch was generated by hg export, user and
1829 If the imported patch was generated by hg export, user and
1830 description from patch override values from message headers and
1830 description from patch override values from message headers and
1831 body. Values given on command line with -m/--message and -u/--user
1831 body. Values given on command line with -m/--message and -u/--user
1832 override these.
1832 override these.
1833
1833
1834 If --exact is specified, import will set the working directory to
1834 If --exact is specified, import will set the working directory to
1835 the parent of each patch before applying it, and will abort if the
1835 the parent of each patch before applying it, and will abort if the
1836 resulting changeset has a different ID than the one recorded in
1836 resulting changeset has a different ID than the one recorded in
1837 the patch. This may happen due to character set problems or other
1837 the patch. This may happen due to character set problems or other
1838 deficiencies in the text patch format.
1838 deficiencies in the text patch format.
1839
1839
1840 With -s/--similarity, hg will attempt to discover renames and
1840 With -s/--similarity, hg will attempt to discover renames and
1841 copies in the patch in the same way as 'addremove'.
1841 copies in the patch in the same way as 'addremove'.
1842
1842
1843 To read a patch from standard input, use "-" as the patch name. If
1843 To read a patch from standard input, use "-" as the patch name. If
1844 a URL is specified, the patch will be downloaded from it.
1844 a URL is specified, the patch will be downloaded from it.
1845 See 'hg help dates' for a list of formats valid for -d/--date.
1845 See 'hg help dates' for a list of formats valid for -d/--date.
1846 """
1846 """
1847 patches = (patch1,) + patches
1847 patches = (patch1,) + patches
1848
1848
1849 date = opts.get('date')
1849 date = opts.get('date')
1850 if date:
1850 if date:
1851 opts['date'] = util.parsedate(date)
1851 opts['date'] = util.parsedate(date)
1852
1852
1853 try:
1853 try:
1854 sim = float(opts.get('similarity') or 0)
1854 sim = float(opts.get('similarity') or 0)
1855 except ValueError:
1855 except ValueError:
1856 raise util.Abort(_('similarity must be a number'))
1856 raise util.Abort(_('similarity must be a number'))
1857 if sim < 0 or sim > 100:
1857 if sim < 0 or sim > 100:
1858 raise util.Abort(_('similarity must be between 0 and 100'))
1858 raise util.Abort(_('similarity must be between 0 and 100'))
1859
1859
1860 if opts.get('exact') or not opts.get('force'):
1860 if opts.get('exact') or not opts.get('force'):
1861 cmdutil.bail_if_changed(repo)
1861 cmdutil.bail_if_changed(repo)
1862
1862
1863 d = opts["base"]
1863 d = opts["base"]
1864 strip = opts["strip"]
1864 strip = opts["strip"]
1865 wlock = lock = None
1865 wlock = lock = None
1866
1866
1867 def tryone(ui, hunk):
1867 def tryone(ui, hunk):
1868 tmpname, message, user, date, branch, nodeid, p1, p2 = \
1868 tmpname, message, user, date, branch, nodeid, p1, p2 = \
1869 patch.extract(ui, hunk)
1869 patch.extract(ui, hunk)
1870
1870
1871 if not tmpname:
1871 if not tmpname:
1872 return None
1872 return None
1873 commitid = _('to working directory')
1873 commitid = _('to working directory')
1874
1874
1875 try:
1875 try:
1876 cmdline_message = cmdutil.logmessage(opts)
1876 cmdline_message = cmdutil.logmessage(opts)
1877 if cmdline_message:
1877 if cmdline_message:
1878 # pickup the cmdline msg
1878 # pickup the cmdline msg
1879 message = cmdline_message
1879 message = cmdline_message
1880 elif message:
1880 elif message:
1881 # pickup the patch msg
1881 # pickup the patch msg
1882 message = message.strip()
1882 message = message.strip()
1883 else:
1883 else:
1884 # launch the editor
1884 # launch the editor
1885 message = None
1885 message = None
1886 ui.debug('message:\n%s\n' % message)
1886 ui.debug('message:\n%s\n' % message)
1887
1887
1888 wp = repo.parents()
1888 wp = repo.parents()
1889 if opts.get('exact'):
1889 if opts.get('exact'):
1890 if not nodeid or not p1:
1890 if not nodeid or not p1:
1891 raise util.Abort(_('not a Mercurial patch'))
1891 raise util.Abort(_('not a Mercurial patch'))
1892 p1 = repo.lookup(p1)
1892 p1 = repo.lookup(p1)
1893 p2 = repo.lookup(p2 or hex(nullid))
1893 p2 = repo.lookup(p2 or hex(nullid))
1894
1894
1895 if p1 != wp[0].node():
1895 if p1 != wp[0].node():
1896 hg.clean(repo, p1)
1896 hg.clean(repo, p1)
1897 repo.dirstate.setparents(p1, p2)
1897 repo.dirstate.setparents(p1, p2)
1898 elif p2:
1898 elif p2:
1899 try:
1899 try:
1900 p1 = repo.lookup(p1)
1900 p1 = repo.lookup(p1)
1901 p2 = repo.lookup(p2)
1901 p2 = repo.lookup(p2)
1902 if p1 == wp[0].node():
1902 if p1 == wp[0].node():
1903 repo.dirstate.setparents(p1, p2)
1903 repo.dirstate.setparents(p1, p2)
1904 except error.RepoError:
1904 except error.RepoError:
1905 pass
1905 pass
1906 if opts.get('exact') or opts.get('import_branch'):
1906 if opts.get('exact') or opts.get('import_branch'):
1907 repo.dirstate.setbranch(branch or 'default')
1907 repo.dirstate.setbranch(branch or 'default')
1908
1908
1909 files = {}
1909 files = {}
1910 try:
1910 try:
1911 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1911 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1912 files=files, eolmode=None)
1912 files=files, eolmode=None)
1913 finally:
1913 finally:
1914 files = patch.updatedir(ui, repo, files,
1914 files = patch.updatedir(ui, repo, files,
1915 similarity=sim / 100.0)
1915 similarity=sim / 100.0)
1916 if not opts.get('no_commit'):
1916 if not opts.get('no_commit'):
1917 if opts.get('exact'):
1917 if opts.get('exact'):
1918 m = None
1918 m = None
1919 else:
1919 else:
1920 m = cmdutil.matchfiles(repo, files or [])
1920 m = cmdutil.matchfiles(repo, files or [])
1921 n = repo.commit(message, opts.get('user') or user,
1921 n = repo.commit(message, opts.get('user') or user,
1922 opts.get('date') or date, match=m,
1922 opts.get('date') or date, match=m,
1923 editor=cmdutil.commiteditor)
1923 editor=cmdutil.commiteditor)
1924 if opts.get('exact'):
1924 if opts.get('exact'):
1925 if hex(n) != nodeid:
1925 if hex(n) != nodeid:
1926 repo.rollback()
1926 repo.rollback()
1927 raise util.Abort(_('patch is damaged'
1927 raise util.Abort(_('patch is damaged'
1928 ' or loses information'))
1928 ' or loses information'))
1929 # Force a dirstate write so that the next transaction
1929 # Force a dirstate write so that the next transaction
1930 # backups an up-do-date file.
1930 # backups an up-do-date file.
1931 repo.dirstate.write()
1931 repo.dirstate.write()
1932 if n:
1932 if n:
1933 commitid = short(n)
1933 commitid = short(n)
1934
1934
1935 return commitid
1935 return commitid
1936 finally:
1936 finally:
1937 os.unlink(tmpname)
1937 os.unlink(tmpname)
1938
1938
1939 try:
1939 try:
1940 wlock = repo.wlock()
1940 wlock = repo.wlock()
1941 lock = repo.lock()
1941 lock = repo.lock()
1942 lastcommit = None
1942 lastcommit = None
1943 for p in patches:
1943 for p in patches:
1944 pf = os.path.join(d, p)
1944 pf = os.path.join(d, p)
1945
1945
1946 if pf == '-':
1946 if pf == '-':
1947 ui.status(_("applying patch from stdin\n"))
1947 ui.status(_("applying patch from stdin\n"))
1948 pf = sys.stdin
1948 pf = sys.stdin
1949 else:
1949 else:
1950 ui.status(_("applying %s\n") % p)
1950 ui.status(_("applying %s\n") % p)
1951 pf = url.open(ui, pf)
1951 pf = url.open(ui, pf)
1952
1952
1953 haspatch = False
1953 haspatch = False
1954 for hunk in patch.split(pf):
1954 for hunk in patch.split(pf):
1955 commitid = tryone(ui, hunk)
1955 commitid = tryone(ui, hunk)
1956 if commitid:
1956 if commitid:
1957 haspatch = True
1957 haspatch = True
1958 if lastcommit:
1958 if lastcommit:
1959 ui.status(_('applied %s\n') % lastcommit)
1959 ui.status(_('applied %s\n') % lastcommit)
1960 lastcommit = commitid
1960 lastcommit = commitid
1961
1961
1962 if not haspatch:
1962 if not haspatch:
1963 raise util.Abort(_('no diffs found'))
1963 raise util.Abort(_('no diffs found'))
1964
1964
1965 finally:
1965 finally:
1966 release(lock, wlock)
1966 release(lock, wlock)
1967
1967
1968 def incoming(ui, repo, source="default", **opts):
1968 def incoming(ui, repo, source="default", **opts):
1969 """show new changesets found in source
1969 """show new changesets found in source
1970
1970
1971 Show new changesets found in the specified path/URL or the default
1971 Show new changesets found in the specified path/URL or the default
1972 pull location. These are the changesets that would have been pulled
1972 pull location. These are the changesets that would have been pulled
1973 if a pull at the time you issued this command.
1973 if a pull at the time you issued this command.
1974
1974
1975 For remote repository, using --bundle avoids downloading the
1975 For remote repository, using --bundle avoids downloading the
1976 changesets twice if the incoming is followed by a pull.
1976 changesets twice if the incoming is followed by a pull.
1977
1977
1978 See pull for valid source format details.
1978 See pull for valid source format details.
1979 """
1979 """
1980 limit = cmdutil.loglimit(opts)
1980 limit = cmdutil.loglimit(opts)
1981 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
1981 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
1982 other = hg.repository(cmdutil.remoteui(repo, opts), source)
1982 other = hg.repository(cmdutil.remoteui(repo, opts), source)
1983 ui.status(_('comparing with %s\n') % url.hidepassword(source))
1983 ui.status(_('comparing with %s\n') % url.hidepassword(source))
1984 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
1984 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
1985 if revs:
1985 if revs:
1986 revs = [other.lookup(rev) for rev in revs]
1986 revs = [other.lookup(rev) for rev in revs]
1987 common, incoming, rheads = repo.findcommonincoming(other, heads=revs,
1987 common, incoming, rheads = repo.findcommonincoming(other, heads=revs,
1988 force=opts["force"])
1988 force=opts["force"])
1989 if not incoming:
1989 if not incoming:
1990 try:
1990 try:
1991 os.unlink(opts["bundle"])
1991 os.unlink(opts["bundle"])
1992 except:
1992 except:
1993 pass
1993 pass
1994 ui.status(_("no changes found\n"))
1994 ui.status(_("no changes found\n"))
1995 return 1
1995 return 1
1996
1996
1997 cleanup = None
1997 cleanup = None
1998 try:
1998 try:
1999 fname = opts["bundle"]
1999 fname = opts["bundle"]
2000 if fname or not other.local():
2000 if fname or not other.local():
2001 # create a bundle (uncompressed if other repo is not local)
2001 # create a bundle (uncompressed if other repo is not local)
2002
2002
2003 if revs is None and other.capable('changegroupsubset'):
2003 if revs is None and other.capable('changegroupsubset'):
2004 revs = rheads
2004 revs = rheads
2005
2005
2006 if revs is None:
2006 if revs is None:
2007 cg = other.changegroup(incoming, "incoming")
2007 cg = other.changegroup(incoming, "incoming")
2008 else:
2008 else:
2009 cg = other.changegroupsubset(incoming, revs, 'incoming')
2009 cg = other.changegroupsubset(incoming, revs, 'incoming')
2010 bundletype = other.local() and "HG10BZ" or "HG10UN"
2010 bundletype = other.local() and "HG10BZ" or "HG10UN"
2011 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
2011 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
2012 # keep written bundle?
2012 # keep written bundle?
2013 if opts["bundle"]:
2013 if opts["bundle"]:
2014 cleanup = None
2014 cleanup = None
2015 if not other.local():
2015 if not other.local():
2016 # use the created uncompressed bundlerepo
2016 # use the created uncompressed bundlerepo
2017 other = bundlerepo.bundlerepository(ui, repo.root, fname)
2017 other = bundlerepo.bundlerepository(ui, repo.root, fname)
2018
2018
2019 o = other.changelog.nodesbetween(incoming, revs)[0]
2019 o = other.changelog.nodesbetween(incoming, revs)[0]
2020 if opts.get('newest_first'):
2020 if opts.get('newest_first'):
2021 o.reverse()
2021 o.reverse()
2022 displayer = cmdutil.show_changeset(ui, other, opts)
2022 displayer = cmdutil.show_changeset(ui, other, opts)
2023 count = 0
2023 count = 0
2024 for n in o:
2024 for n in o:
2025 if limit is not None and count >= limit:
2025 if limit is not None and count >= limit:
2026 break
2026 break
2027 parents = [p for p in other.changelog.parents(n) if p != nullid]
2027 parents = [p for p in other.changelog.parents(n) if p != nullid]
2028 if opts.get('no_merges') and len(parents) == 2:
2028 if opts.get('no_merges') and len(parents) == 2:
2029 continue
2029 continue
2030 count += 1
2030 count += 1
2031 displayer.show(other[n])
2031 displayer.show(other[n])
2032 displayer.close()
2032 displayer.close()
2033 finally:
2033 finally:
2034 if hasattr(other, 'close'):
2034 if hasattr(other, 'close'):
2035 other.close()
2035 other.close()
2036 if cleanup:
2036 if cleanup:
2037 os.unlink(cleanup)
2037 os.unlink(cleanup)
2038
2038
2039 def init(ui, dest=".", **opts):
2039 def init(ui, dest=".", **opts):
2040 """create a new repository in the given directory
2040 """create a new repository in the given directory
2041
2041
2042 Initialize a new repository in the given directory. If the given
2042 Initialize a new repository in the given directory. If the given
2043 directory does not exist, it will be created.
2043 directory does not exist, it will be created.
2044
2044
2045 If no directory is given, the current directory is used.
2045 If no directory is given, the current directory is used.
2046
2046
2047 It is possible to specify an ``ssh://`` URL as the destination.
2047 It is possible to specify an ``ssh://`` URL as the destination.
2048 See 'hg help urls' for more information.
2048 See 'hg help urls' for more information.
2049 """
2049 """
2050 hg.repository(cmdutil.remoteui(ui, opts), dest, create=1)
2050 hg.repository(cmdutil.remoteui(ui, opts), dest, create=1)
2051
2051
2052 def locate(ui, repo, *pats, **opts):
2052 def locate(ui, repo, *pats, **opts):
2053 """locate files matching specific patterns
2053 """locate files matching specific patterns
2054
2054
2055 Print files under Mercurial control in the working directory whose
2055 Print files under Mercurial control in the working directory whose
2056 names match the given patterns.
2056 names match the given patterns.
2057
2057
2058 By default, this command searches all directories in the working
2058 By default, this command searches all directories in the working
2059 directory. To search just the current directory and its
2059 directory. To search just the current directory and its
2060 subdirectories, use "--include .".
2060 subdirectories, use "--include .".
2061
2061
2062 If no patterns are given to match, this command prints the names
2062 If no patterns are given to match, this command prints the names
2063 of all files under Mercurial control in the working directory.
2063 of all files under Mercurial control in the working directory.
2064
2064
2065 If you want to feed the output of this command into the "xargs"
2065 If you want to feed the output of this command into the "xargs"
2066 command, use the -0 option to both this command and "xargs". This
2066 command, use the -0 option to both this command and "xargs". This
2067 will avoid the problem of "xargs" treating single filenames that
2067 will avoid the problem of "xargs" treating single filenames that
2068 contain whitespace as multiple filenames.
2068 contain whitespace as multiple filenames.
2069 """
2069 """
2070 end = opts.get('print0') and '\0' or '\n'
2070 end = opts.get('print0') and '\0' or '\n'
2071 rev = opts.get('rev') or None
2071 rev = opts.get('rev') or None
2072
2072
2073 ret = 1
2073 ret = 1
2074 m = cmdutil.match(repo, pats, opts, default='relglob')
2074 m = cmdutil.match(repo, pats, opts, default='relglob')
2075 m.bad = lambda x, y: False
2075 m.bad = lambda x, y: False
2076 for abs in repo[rev].walk(m):
2076 for abs in repo[rev].walk(m):
2077 if not rev and abs not in repo.dirstate:
2077 if not rev and abs not in repo.dirstate:
2078 continue
2078 continue
2079 if opts.get('fullpath'):
2079 if opts.get('fullpath'):
2080 ui.write(repo.wjoin(abs), end)
2080 ui.write(repo.wjoin(abs), end)
2081 else:
2081 else:
2082 ui.write(((pats and m.rel(abs)) or abs), end)
2082 ui.write(((pats and m.rel(abs)) or abs), end)
2083 ret = 0
2083 ret = 0
2084
2084
2085 return ret
2085 return ret
2086
2086
2087 def log(ui, repo, *pats, **opts):
2087 def log(ui, repo, *pats, **opts):
2088 """show revision history of entire repository or files
2088 """show revision history of entire repository or files
2089
2089
2090 Print the revision history of the specified files or the entire
2090 Print the revision history of the specified files or the entire
2091 project.
2091 project.
2092
2092
2093 File history is shown without following rename or copy history of
2093 File history is shown without following rename or copy history of
2094 files. Use -f/--follow with a filename to follow history across
2094 files. Use -f/--follow with a filename to follow history across
2095 renames and copies. --follow without a filename will only show
2095 renames and copies. --follow without a filename will only show
2096 ancestors or descendants of the starting revision. --follow-first
2096 ancestors or descendants of the starting revision. --follow-first
2097 only follows the first parent of merge revisions.
2097 only follows the first parent of merge revisions.
2098
2098
2099 If no revision range is specified, the default is tip:0 unless
2099 If no revision range is specified, the default is tip:0 unless
2100 --follow is set, in which case the working directory parent is
2100 --follow is set, in which case the working directory parent is
2101 used as the starting revision.
2101 used as the starting revision.
2102
2102
2103 See 'hg help dates' for a list of formats valid for -d/--date.
2103 See 'hg help dates' for a list of formats valid for -d/--date.
2104
2104
2105 By default this command prints revision number and changeset id,
2105 By default this command prints revision number and changeset id,
2106 tags, non-trivial parents, user, date and time, and a summary for
2106 tags, non-trivial parents, user, date and time, and a summary for
2107 each commit. When the -v/--verbose switch is used, the list of
2107 each commit. When the -v/--verbose switch is used, the list of
2108 changed files and full commit message are shown.
2108 changed files and full commit message are shown.
2109
2109
2110 NOTE: log -p/--patch may generate unexpected diff output for merge
2110 NOTE: log -p/--patch may generate unexpected diff output for merge
2111 changesets, as it will only compare the merge changeset against
2111 changesets, as it will only compare the merge changeset against
2112 its first parent. Also, only files different from BOTH parents
2112 its first parent. Also, only files different from BOTH parents
2113 will appear in files:.
2113 will appear in files:.
2114 """
2114 """
2115
2115
2116 matchfn = cmdutil.match(repo, pats, opts)
2116 matchfn = cmdutil.match(repo, pats, opts)
2117 limit = cmdutil.loglimit(opts)
2117 limit = cmdutil.loglimit(opts)
2118 count = 0
2118 count = 0
2119
2119
2120 endrev = None
2120 endrev = None
2121 if opts.get('copies') and opts.get('rev'):
2121 if opts.get('copies') and opts.get('rev'):
2122 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2122 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2123
2123
2124 df = False
2124 df = False
2125 if opts["date"]:
2125 if opts["date"]:
2126 df = util.matchdate(opts["date"])
2126 df = util.matchdate(opts["date"])
2127
2127
2128 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
2128 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
2129 def prep(ctx, fns):
2129 def prep(ctx, fns):
2130 rev = ctx.rev()
2130 rev = ctx.rev()
2131 parents = [p for p in repo.changelog.parentrevs(rev)
2131 parents = [p for p in repo.changelog.parentrevs(rev)
2132 if p != nullrev]
2132 if p != nullrev]
2133 if opts.get('no_merges') and len(parents) == 2:
2133 if opts.get('no_merges') and len(parents) == 2:
2134 return
2134 return
2135 if opts.get('only_merges') and len(parents) != 2:
2135 if opts.get('only_merges') and len(parents) != 2:
2136 return
2136 return
2137 if opts.get('only_branch') and ctx.branch() not in opts['only_branch']:
2137 if opts.get('only_branch') and ctx.branch() not in opts['only_branch']:
2138 return
2138 return
2139 if df and not df(ctx.date()[0]):
2139 if df and not df(ctx.date()[0]):
2140 return
2140 return
2141 if opts['user'] and not [k for k in opts['user'] if k in ctx.user()]:
2141 if opts['user'] and not [k for k in opts['user'] if k in ctx.user()]:
2142 return
2142 return
2143 if opts.get('keyword'):
2143 if opts.get('keyword'):
2144 for k in [kw.lower() for kw in opts['keyword']]:
2144 for k in [kw.lower() for kw in opts['keyword']]:
2145 if (k in ctx.user().lower() or
2145 if (k in ctx.user().lower() or
2146 k in ctx.description().lower() or
2146 k in ctx.description().lower() or
2147 k in " ".join(ctx.files()).lower()):
2147 k in " ".join(ctx.files()).lower()):
2148 break
2148 break
2149 else:
2149 else:
2150 return
2150 return
2151
2151
2152 copies = None
2152 copies = None
2153 if opts.get('copies') and rev:
2153 if opts.get('copies') and rev:
2154 copies = []
2154 copies = []
2155 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2155 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2156 for fn in ctx.files():
2156 for fn in ctx.files():
2157 rename = getrenamed(fn, rev)
2157 rename = getrenamed(fn, rev)
2158 if rename:
2158 if rename:
2159 copies.append((fn, rename[0]))
2159 copies.append((fn, rename[0]))
2160
2160
2161 displayer.show(ctx, copies=copies)
2161 displayer.show(ctx, copies=copies)
2162
2162
2163 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2163 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2164 if count == limit:
2164 if count == limit:
2165 break
2165 break
2166 if displayer.flush(ctx.rev()):
2166 if displayer.flush(ctx.rev()):
2167 count += 1
2167 count += 1
2168 displayer.close()
2168 displayer.close()
2169
2169
2170 def manifest(ui, repo, node=None, rev=None):
2170 def manifest(ui, repo, node=None, rev=None):
2171 """output the current or given revision of the project manifest
2171 """output the current or given revision of the project manifest
2172
2172
2173 Print a list of version controlled files for the given revision.
2173 Print a list of version controlled files for the given revision.
2174 If no revision is given, the first parent of the working directory
2174 If no revision is given, the first parent of the working directory
2175 is used, or the null revision if no revision is checked out.
2175 is used, or the null revision if no revision is checked out.
2176
2176
2177 With -v, print file permissions, symlink and executable bits.
2177 With -v, print file permissions, symlink and executable bits.
2178 With --debug, print file revision hashes.
2178 With --debug, print file revision hashes.
2179 """
2179 """
2180
2180
2181 if rev and node:
2181 if rev and node:
2182 raise util.Abort(_("please specify just one revision"))
2182 raise util.Abort(_("please specify just one revision"))
2183
2183
2184 if not node:
2184 if not node:
2185 node = rev
2185 node = rev
2186
2186
2187 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2187 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2188 ctx = repo[node]
2188 ctx = repo[node]
2189 for f in ctx:
2189 for f in ctx:
2190 if ui.debugflag:
2190 if ui.debugflag:
2191 ui.write("%40s " % hex(ctx.manifest()[f]))
2191 ui.write("%40s " % hex(ctx.manifest()[f]))
2192 if ui.verbose:
2192 if ui.verbose:
2193 ui.write(decor[ctx.flags(f)])
2193 ui.write(decor[ctx.flags(f)])
2194 ui.write("%s\n" % f)
2194 ui.write("%s\n" % f)
2195
2195
2196 def merge(ui, repo, node=None, **opts):
2196 def merge(ui, repo, node=None, **opts):
2197 """merge working directory with another revision
2197 """merge working directory with another revision
2198
2198
2199 The current working directory is updated with all changes made in
2199 The current working directory is updated with all changes made in
2200 the requested revision since the last common predecessor revision.
2200 the requested revision since the last common predecessor revision.
2201
2201
2202 Files that changed between either parent are marked as changed for
2202 Files that changed between either parent are marked as changed for
2203 the next commit and a commit must be performed before any further
2203 the next commit and a commit must be performed before any further
2204 updates to the repository are allowed. The next commit will have
2204 updates to the repository are allowed. The next commit will have
2205 two parents.
2205 two parents.
2206
2206
2207 If no revision is specified, the working directory's parent is a
2207 If no revision is specified, the working directory's parent is a
2208 head revision, and the current branch contains exactly one other
2208 head revision, and the current branch contains exactly one other
2209 head, the other head is merged with by default. Otherwise, an
2209 head, the other head is merged with by default. Otherwise, an
2210 explicit revision with which to merge with must be provided.
2210 explicit revision with which to merge with must be provided.
2211 """
2211 """
2212
2212
2213 if opts.get('rev') and node:
2213 if opts.get('rev') and node:
2214 raise util.Abort(_("please specify just one revision"))
2214 raise util.Abort(_("please specify just one revision"))
2215 if not node:
2215 if not node:
2216 node = opts.get('rev')
2216 node = opts.get('rev')
2217
2217
2218 if not node:
2218 if not node:
2219 branch = repo.changectx(None).branch()
2219 branch = repo.changectx(None).branch()
2220 bheads = repo.branchheads(branch)
2220 bheads = repo.branchheads(branch)
2221 if len(bheads) > 2:
2221 if len(bheads) > 2:
2222 ui.warn(_("abort: branch '%s' has %d heads - "
2222 ui.warn(_("abort: branch '%s' has %d heads - "
2223 "please merge with an explicit rev\n")
2223 "please merge with an explicit rev\n")
2224 % (branch, len(bheads)))
2224 % (branch, len(bheads)))
2225 ui.status(_("(run 'hg heads .' to see heads)\n"))
2225 ui.status(_("(run 'hg heads .' to see heads)\n"))
2226 return False
2226 return False
2227
2227
2228 parent = repo.dirstate.parents()[0]
2228 parent = repo.dirstate.parents()[0]
2229 if len(bheads) == 1:
2229 if len(bheads) == 1:
2230 if len(repo.heads()) > 1:
2230 if len(repo.heads()) > 1:
2231 ui.warn(_("abort: branch '%s' has one head - "
2231 ui.warn(_("abort: branch '%s' has one head - "
2232 "please merge with an explicit rev\n" % branch))
2232 "please merge with an explicit rev\n" % branch))
2233 ui.status(_("(run 'hg heads' to see all heads)\n"))
2233 ui.status(_("(run 'hg heads' to see all heads)\n"))
2234 return False
2234 return False
2235 msg = _('there is nothing to merge')
2235 msg = _('there is nothing to merge')
2236 if parent != repo.lookup(repo[None].branch()):
2236 if parent != repo.lookup(repo[None].branch()):
2237 msg = _('%s - use "hg update" instead') % msg
2237 msg = _('%s - use "hg update" instead') % msg
2238 raise util.Abort(msg)
2238 raise util.Abort(msg)
2239
2239
2240 if parent not in bheads:
2240 if parent not in bheads:
2241 raise util.Abort(_('working dir not at a head rev - '
2241 raise util.Abort(_('working dir not at a head rev - '
2242 'use "hg update" or merge with an explicit rev'))
2242 'use "hg update" or merge with an explicit rev'))
2243 node = parent == bheads[0] and bheads[-1] or bheads[0]
2243 node = parent == bheads[0] and bheads[-1] or bheads[0]
2244
2244
2245 if opts.get('preview'):
2245 if opts.get('preview'):
2246 # find nodes that are ancestors of p2 but not of p1
2246 # find nodes that are ancestors of p2 but not of p1
2247 p1 = repo.lookup('.')
2247 p1 = repo.lookup('.')
2248 p2 = repo.lookup(node)
2248 p2 = repo.lookup(node)
2249 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2249 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2250
2250
2251 displayer = cmdutil.show_changeset(ui, repo, opts)
2251 displayer = cmdutil.show_changeset(ui, repo, opts)
2252 for node in nodes:
2252 for node in nodes:
2253 displayer.show(repo[node])
2253 displayer.show(repo[node])
2254 displayer.close()
2254 displayer.close()
2255 return 0
2255 return 0
2256
2256
2257 return hg.merge(repo, node, force=opts.get('force'))
2257 return hg.merge(repo, node, force=opts.get('force'))
2258
2258
2259 def outgoing(ui, repo, dest=None, **opts):
2259 def outgoing(ui, repo, dest=None, **opts):
2260 """show changesets not found in the destination
2260 """show changesets not found in the destination
2261
2261
2262 Show changesets not found in the specified destination repository
2262 Show changesets not found in the specified destination repository
2263 or the default push location. These are the changesets that would
2263 or the default push location. These are the changesets that would
2264 be pushed if a push was requested.
2264 be pushed if a push was requested.
2265
2265
2266 See pull for details of valid destination formats.
2266 See pull for details of valid destination formats.
2267 """
2267 """
2268 limit = cmdutil.loglimit(opts)
2268 limit = cmdutil.loglimit(opts)
2269 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2269 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2270 dest, branches = hg.parseurl(dest, opts.get('branch'))
2270 dest, branches = hg.parseurl(dest, opts.get('branch'))
2271 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2271 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2272 if revs:
2272 if revs:
2273 revs = [repo.lookup(rev) for rev in revs]
2273 revs = [repo.lookup(rev) for rev in revs]
2274
2274
2275 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2275 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2276 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2276 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2277 o = repo.findoutgoing(other, force=opts.get('force'))
2277 o = repo.findoutgoing(other, force=opts.get('force'))
2278 if not o:
2278 if not o:
2279 ui.status(_("no changes found\n"))
2279 ui.status(_("no changes found\n"))
2280 return 1
2280 return 1
2281 o = repo.changelog.nodesbetween(o, revs)[0]
2281 o = repo.changelog.nodesbetween(o, revs)[0]
2282 if opts.get('newest_first'):
2282 if opts.get('newest_first'):
2283 o.reverse()
2283 o.reverse()
2284 displayer = cmdutil.show_changeset(ui, repo, opts)
2284 displayer = cmdutil.show_changeset(ui, repo, opts)
2285 count = 0
2285 count = 0
2286 for n in o:
2286 for n in o:
2287 if limit is not None and count >= limit:
2287 if limit is not None and count >= limit:
2288 break
2288 break
2289 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2289 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2290 if opts.get('no_merges') and len(parents) == 2:
2290 if opts.get('no_merges') and len(parents) == 2:
2291 continue
2291 continue
2292 count += 1
2292 count += 1
2293 displayer.show(repo[n])
2293 displayer.show(repo[n])
2294 displayer.close()
2294 displayer.close()
2295
2295
2296 def parents(ui, repo, file_=None, **opts):
2296 def parents(ui, repo, file_=None, **opts):
2297 """show the parents of the working directory or revision
2297 """show the parents of the working directory or revision
2298
2298
2299 Print the working directory's parent revisions. If a revision is
2299 Print the working directory's parent revisions. If a revision is
2300 given via -r/--rev, the parent of that revision will be printed.
2300 given via -r/--rev, the parent of that revision will be printed.
2301 If a file argument is given, the revision in which the file was
2301 If a file argument is given, the revision in which the file was
2302 last changed (before the working directory revision or the
2302 last changed (before the working directory revision or the
2303 argument to --rev if given) is printed.
2303 argument to --rev if given) is printed.
2304 """
2304 """
2305 rev = opts.get('rev')
2305 rev = opts.get('rev')
2306 if rev:
2306 if rev:
2307 ctx = repo[rev]
2307 ctx = repo[rev]
2308 else:
2308 else:
2309 ctx = repo[None]
2309 ctx = repo[None]
2310
2310
2311 if file_:
2311 if file_:
2312 m = cmdutil.match(repo, (file_,), opts)
2312 m = cmdutil.match(repo, (file_,), opts)
2313 if m.anypats() or len(m.files()) != 1:
2313 if m.anypats() or len(m.files()) != 1:
2314 raise util.Abort(_('can only specify an explicit filename'))
2314 raise util.Abort(_('can only specify an explicit filename'))
2315 file_ = m.files()[0]
2315 file_ = m.files()[0]
2316 filenodes = []
2316 filenodes = []
2317 for cp in ctx.parents():
2317 for cp in ctx.parents():
2318 if not cp:
2318 if not cp:
2319 continue
2319 continue
2320 try:
2320 try:
2321 filenodes.append(cp.filenode(file_))
2321 filenodes.append(cp.filenode(file_))
2322 except error.LookupError:
2322 except error.LookupError:
2323 pass
2323 pass
2324 if not filenodes:
2324 if not filenodes:
2325 raise util.Abort(_("'%s' not found in manifest!") % file_)
2325 raise util.Abort(_("'%s' not found in manifest!") % file_)
2326 fl = repo.file(file_)
2326 fl = repo.file(file_)
2327 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2327 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2328 else:
2328 else:
2329 p = [cp.node() for cp in ctx.parents()]
2329 p = [cp.node() for cp in ctx.parents()]
2330
2330
2331 displayer = cmdutil.show_changeset(ui, repo, opts)
2331 displayer = cmdutil.show_changeset(ui, repo, opts)
2332 for n in p:
2332 for n in p:
2333 if n != nullid:
2333 if n != nullid:
2334 displayer.show(repo[n])
2334 displayer.show(repo[n])
2335 displayer.close()
2335 displayer.close()
2336
2336
2337 def paths(ui, repo, search=None):
2337 def paths(ui, repo, search=None):
2338 """show aliases for remote repositories
2338 """show aliases for remote repositories
2339
2339
2340 Show definition of symbolic path name NAME. If no name is given,
2340 Show definition of symbolic path name NAME. If no name is given,
2341 show definition of all available names.
2341 show definition of all available names.
2342
2342
2343 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2343 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2344 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2344 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2345
2345
2346 See 'hg help urls' for more information.
2346 See 'hg help urls' for more information.
2347 """
2347 """
2348 if search:
2348 if search:
2349 for name, path in ui.configitems("paths"):
2349 for name, path in ui.configitems("paths"):
2350 if name == search:
2350 if name == search:
2351 ui.write("%s\n" % url.hidepassword(path))
2351 ui.write("%s\n" % url.hidepassword(path))
2352 return
2352 return
2353 ui.warn(_("not found!\n"))
2353 ui.warn(_("not found!\n"))
2354 return 1
2354 return 1
2355 else:
2355 else:
2356 for name, path in ui.configitems("paths"):
2356 for name, path in ui.configitems("paths"):
2357 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2357 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2358
2358
2359 def postincoming(ui, repo, modheads, optupdate, checkout):
2359 def postincoming(ui, repo, modheads, optupdate, checkout):
2360 if modheads == 0:
2360 if modheads == 0:
2361 return
2361 return
2362 if optupdate:
2362 if optupdate:
2363 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2363 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2364 return hg.update(repo, checkout)
2364 return hg.update(repo, checkout)
2365 else:
2365 else:
2366 ui.status(_("not updating, since new heads added\n"))
2366 ui.status(_("not updating, since new heads added\n"))
2367 if modheads > 1:
2367 if modheads > 1:
2368 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2368 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2369 else:
2369 else:
2370 ui.status(_("(run 'hg update' to get a working copy)\n"))
2370 ui.status(_("(run 'hg update' to get a working copy)\n"))
2371
2371
2372 def pull(ui, repo, source="default", **opts):
2372 def pull(ui, repo, source="default", **opts):
2373 """pull changes from the specified source
2373 """pull changes from the specified source
2374
2374
2375 Pull changes from a remote repository to a local one.
2375 Pull changes from a remote repository to a local one.
2376
2376
2377 This finds all changes from the repository at the specified path
2377 This finds all changes from the repository at the specified path
2378 or URL and adds them to a local repository (the current one unless
2378 or URL and adds them to a local repository (the current one unless
2379 -R is specified). By default, this does not update the copy of the
2379 -R is specified). By default, this does not update the copy of the
2380 project in the working directory.
2380 project in the working directory.
2381
2381
2382 Use hg incoming if you want to see what would have been added by a
2382 Use hg incoming if you want to see what would have been added by a
2383 pull at the time you issued this command. If you then decide to
2383 pull at the time you issued this command. If you then decide to
2384 added those changes to the repository, you should use pull -r X
2384 added those changes to the repository, you should use pull -r X
2385 where X is the last changeset listed by hg incoming.
2385 where X is the last changeset listed by hg incoming.
2386
2386
2387 If SOURCE is omitted, the 'default' path will be used.
2387 If SOURCE is omitted, the 'default' path will be used.
2388 See 'hg help urls' for more information.
2388 See 'hg help urls' for more information.
2389 """
2389 """
2390 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2390 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2391 other = hg.repository(cmdutil.remoteui(repo, opts), source)
2391 other = hg.repository(cmdutil.remoteui(repo, opts), source)
2392 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2392 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2393 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2393 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2394 if revs:
2394 if revs:
2395 try:
2395 try:
2396 revs = [other.lookup(rev) for rev in revs]
2396 revs = [other.lookup(rev) for rev in revs]
2397 except error.CapabilityError:
2397 except error.CapabilityError:
2398 err = _("Other repository doesn't support revision lookup, "
2398 err = _("Other repository doesn't support revision lookup, "
2399 "so a rev cannot be specified.")
2399 "so a rev cannot be specified.")
2400 raise util.Abort(err)
2400 raise util.Abort(err)
2401
2401
2402 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2402 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2403 if checkout:
2403 if checkout:
2404 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2404 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2405 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2405 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2406
2406
2407 def push(ui, repo, dest=None, **opts):
2407 def push(ui, repo, dest=None, **opts):
2408 """push changes to the specified destination
2408 """push changes to the specified destination
2409
2409
2410 Push changes from the local repository to the specified destination.
2410 Push changes from the local repository to the specified destination.
2411
2411
2412 This is the symmetrical operation for pull. It moves changes from
2412 This is the symmetrical operation for pull. It moves changes from
2413 the current repository to a different one. If the destination is
2413 the current repository to a different one. If the destination is
2414 local this is identical to a pull in that directory from the
2414 local this is identical to a pull in that directory from the
2415 current one.
2415 current one.
2416
2416
2417 By default, push will refuse to run if it detects the result would
2417 By default, push will refuse to run if it detects the result would
2418 increase the number of remote heads. This generally indicates the
2418 increase the number of remote heads. This generally indicates the
2419 user forgot to pull and merge before pushing.
2419 user forgot to pull and merge before pushing.
2420
2420
2421 If -r/--rev is used, the named revision and all its ancestors will
2421 If -r/--rev is used, the named revision and all its ancestors will
2422 be pushed to the remote repository.
2422 be pushed to the remote repository.
2423
2423
2424 Please see 'hg help urls' for important details about ``ssh://``
2424 Please see 'hg help urls' for important details about ``ssh://``
2425 URLs. If DESTINATION is omitted, a default path will be used.
2425 URLs. If DESTINATION is omitted, a default path will be used.
2426 """
2426 """
2427 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2427 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2428 dest, branches = hg.parseurl(dest, opts.get('branch'))
2428 dest, branches = hg.parseurl(dest, opts.get('branch'))
2429 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2429 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2430 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2430 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2431 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2431 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2432 if revs:
2432 if revs:
2433 revs = [repo.lookup(rev) for rev in revs]
2433 revs = [repo.lookup(rev) for rev in revs]
2434
2434
2435 # push subrepos depth-first for coherent ordering
2435 # push subrepos depth-first for coherent ordering
2436 c = repo['']
2436 c = repo['']
2437 subs = c.substate # only repos that are committed
2437 subs = c.substate # only repos that are committed
2438 for s in sorted(subs):
2438 for s in sorted(subs):
2439 c.sub(s).push(opts.get('force'))
2439 c.sub(s).push(opts.get('force'))
2440
2440
2441 r = repo.push(other, opts.get('force'), revs=revs)
2441 r = repo.push(other, opts.get('force'), revs=revs)
2442 return r == 0
2442 return r == 0
2443
2443
2444 def recover(ui, repo):
2444 def recover(ui, repo):
2445 """roll back an interrupted transaction
2445 """roll back an interrupted transaction
2446
2446
2447 Recover from an interrupted commit or pull.
2447 Recover from an interrupted commit or pull.
2448
2448
2449 This command tries to fix the repository status after an
2449 This command tries to fix the repository status after an
2450 interrupted operation. It should only be necessary when Mercurial
2450 interrupted operation. It should only be necessary when Mercurial
2451 suggests it.
2451 suggests it.
2452 """
2452 """
2453 if repo.recover():
2453 if repo.recover():
2454 return hg.verify(repo)
2454 return hg.verify(repo)
2455 return 1
2455 return 1
2456
2456
2457 def remove(ui, repo, *pats, **opts):
2457 def remove(ui, repo, *pats, **opts):
2458 """remove the specified files on the next commit
2458 """remove the specified files on the next commit
2459
2459
2460 Schedule the indicated files for removal from the repository.
2460 Schedule the indicated files for removal from the repository.
2461
2461
2462 This only removes files from the current branch, not from the
2462 This only removes files from the current branch, not from the
2463 entire project history. -A/--after can be used to remove only
2463 entire project history. -A/--after can be used to remove only
2464 files that have already been deleted, -f/--force can be used to
2464 files that have already been deleted, -f/--force can be used to
2465 force deletion, and -Af can be used to remove files from the next
2465 force deletion, and -Af can be used to remove files from the next
2466 revision without deleting them from the working directory.
2466 revision without deleting them from the working directory.
2467
2467
2468 The following table details the behavior of remove for different
2468 The following table details the behavior of remove for different
2469 file states (columns) and option combinations (rows). The file
2469 file states (columns) and option combinations (rows). The file
2470 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2470 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2471 reported by hg status). The actions are Warn, Remove (from branch)
2471 reported by hg status). The actions are Warn, Remove (from branch)
2472 and Delete (from disk)::
2472 and Delete (from disk)::
2473
2473
2474 A C M !
2474 A C M !
2475 none W RD W R
2475 none W RD W R
2476 -f R RD RD R
2476 -f R RD RD R
2477 -A W W W R
2477 -A W W W R
2478 -Af R R R R
2478 -Af R R R R
2479
2479
2480 This command schedules the files to be removed at the next commit.
2480 This command schedules the files to be removed at the next commit.
2481 To undo a remove before that, see hg revert.
2481 To undo a remove before that, see hg revert.
2482 """
2482 """
2483
2483
2484 after, force = opts.get('after'), opts.get('force')
2484 after, force = opts.get('after'), opts.get('force')
2485 if not pats and not after:
2485 if not pats and not after:
2486 raise util.Abort(_('no files specified'))
2486 raise util.Abort(_('no files specified'))
2487
2487
2488 m = cmdutil.match(repo, pats, opts)
2488 m = cmdutil.match(repo, pats, opts)
2489 s = repo.status(match=m, clean=True)
2489 s = repo.status(match=m, clean=True)
2490 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2490 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2491
2491
2492 for f in m.files():
2492 for f in m.files():
2493 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2493 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2494 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2494 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2495
2495
2496 def warn(files, reason):
2496 def warn(files, reason):
2497 for f in files:
2497 for f in files:
2498 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2498 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2499 % (m.rel(f), reason))
2499 % (m.rel(f), reason))
2500
2500
2501 if force:
2501 if force:
2502 remove, forget = modified + deleted + clean, added
2502 remove, forget = modified + deleted + clean, added
2503 elif after:
2503 elif after:
2504 remove, forget = deleted, []
2504 remove, forget = deleted, []
2505 warn(modified + added + clean, _('still exists'))
2505 warn(modified + added + clean, _('still exists'))
2506 else:
2506 else:
2507 remove, forget = deleted + clean, []
2507 remove, forget = deleted + clean, []
2508 warn(modified, _('is modified'))
2508 warn(modified, _('is modified'))
2509 warn(added, _('has been marked for add'))
2509 warn(added, _('has been marked for add'))
2510
2510
2511 for f in sorted(remove + forget):
2511 for f in sorted(remove + forget):
2512 if ui.verbose or not m.exact(f):
2512 if ui.verbose or not m.exact(f):
2513 ui.status(_('removing %s\n') % m.rel(f))
2513 ui.status(_('removing %s\n') % m.rel(f))
2514
2514
2515 repo.forget(forget)
2515 repo.forget(forget)
2516 repo.remove(remove, unlink=not after)
2516 repo.remove(remove, unlink=not after)
2517
2517
2518 def rename(ui, repo, *pats, **opts):
2518 def rename(ui, repo, *pats, **opts):
2519 """rename files; equivalent of copy + remove
2519 """rename files; equivalent of copy + remove
2520
2520
2521 Mark dest as copies of sources; mark sources for deletion. If dest
2521 Mark dest as copies of sources; mark sources for deletion. If dest
2522 is a directory, copies are put in that directory. If dest is a
2522 is a directory, copies are put in that directory. If dest is a
2523 file, there can only be one source.
2523 file, there can only be one source.
2524
2524
2525 By default, this command copies the contents of files as they
2525 By default, this command copies the contents of files as they
2526 exist in the working directory. If invoked with -A/--after, the
2526 exist in the working directory. If invoked with -A/--after, the
2527 operation is recorded, but no copying is performed.
2527 operation is recorded, but no copying is performed.
2528
2528
2529 This command takes effect at the next commit. To undo a rename
2529 This command takes effect at the next commit. To undo a rename
2530 before that, see hg revert.
2530 before that, see hg revert.
2531 """
2531 """
2532 wlock = repo.wlock(False)
2532 wlock = repo.wlock(False)
2533 try:
2533 try:
2534 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2534 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2535 finally:
2535 finally:
2536 wlock.release()
2536 wlock.release()
2537
2537
2538 def resolve(ui, repo, *pats, **opts):
2538 def resolve(ui, repo, *pats, **opts):
2539 """various operations to help finish a merge
2539 """various operations to help finish a merge
2540
2540
2541 This command includes several actions that are often useful while
2541 This command includes several actions that are often useful while
2542 performing a merge, after running ``merge`` but before running
2542 performing a merge, after running ``merge`` but before running
2543 ``commit``. (It is only meaningful if your working directory has
2543 ``commit``. (It is only meaningful if your working directory has
2544 two parents.) It is most relevant for merges with unresolved
2544 two parents.) It is most relevant for merges with unresolved
2545 conflicts, which are typically a result of non-interactive merging with
2545 conflicts, which are typically a result of non-interactive merging with
2546 ``internal:merge`` or a command-line merge tool like ``diff3``.
2546 ``internal:merge`` or a command-line merge tool like ``diff3``.
2547
2547
2548 The available actions are:
2548 The available actions are:
2549
2549
2550 1) list files that were merged with conflicts (U, for unresolved)
2550 1) list files that were merged with conflicts (U, for unresolved)
2551 and without conflicts (R, for resolved): ``hg resolve -l``
2551 and without conflicts (R, for resolved): ``hg resolve -l``
2552 (this is like ``status`` for merges)
2552 (this is like ``status`` for merges)
2553 2) record that you have resolved conflicts in certain files:
2553 2) record that you have resolved conflicts in certain files:
2554 ``hg resolve -m [file ...]`` (default: mark all unresolved files)
2554 ``hg resolve -m [file ...]`` (default: mark all unresolved files)
2555 3) forget that you have resolved conflicts in certain files:
2555 3) forget that you have resolved conflicts in certain files:
2556 ``hg resolve -u [file ...]`` (default: unmark all resolved files)
2556 ``hg resolve -u [file ...]`` (default: unmark all resolved files)
2557 4) discard your current attempt(s) at resolving conflicts and
2557 4) discard your current attempt(s) at resolving conflicts and
2558 restart the merge from scratch: ``hg resolve file...``
2558 restart the merge from scratch: ``hg resolve file...``
2559 (or ``-a`` for all unresolved files)
2559 (or ``-a`` for all unresolved files)
2560
2560
2561 Note that Mercurial will not let you commit files with unresolved merge
2561 Note that Mercurial will not let you commit files with unresolved merge
2562 conflicts. You must use ``hg resolve -m ...`` before you can commit
2562 conflicts. You must use ``hg resolve -m ...`` before you can commit
2563 after a conflicting merge.
2563 after a conflicting merge.
2564 """
2564 """
2565
2565
2566 all, mark, unmark, show, nostatus = \
2566 all, mark, unmark, show, nostatus = \
2567 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
2567 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
2568
2568
2569 if (show and (mark or unmark)) or (mark and unmark):
2569 if (show and (mark or unmark)) or (mark and unmark):
2570 raise util.Abort(_("too many options specified"))
2570 raise util.Abort(_("too many options specified"))
2571 if pats and all:
2571 if pats and all:
2572 raise util.Abort(_("can't specify --all and patterns"))
2572 raise util.Abort(_("can't specify --all and patterns"))
2573 if not (all or pats or show or mark or unmark):
2573 if not (all or pats or show or mark or unmark):
2574 raise util.Abort(_('no files or directories specified; '
2574 raise util.Abort(_('no files or directories specified; '
2575 'use --all to remerge all files'))
2575 'use --all to remerge all files'))
2576
2576
2577 ms = merge_.mergestate(repo)
2577 ms = merge_.mergestate(repo)
2578 m = cmdutil.match(repo, pats, opts)
2578 m = cmdutil.match(repo, pats, opts)
2579
2579
2580 for f in ms:
2580 for f in ms:
2581 if m(f):
2581 if m(f):
2582 if show:
2582 if show:
2583 if nostatus:
2583 if nostatus:
2584 ui.write("%s\n" % f)
2584 ui.write("%s\n" % f)
2585 else:
2585 else:
2586 ui.write("%s %s\n" % (ms[f].upper(), f))
2586 ui.write("%s %s\n" % (ms[f].upper(), f))
2587 elif mark:
2587 elif mark:
2588 ms.mark(f, "r")
2588 ms.mark(f, "r")
2589 elif unmark:
2589 elif unmark:
2590 ms.mark(f, "u")
2590 ms.mark(f, "u")
2591 else:
2591 else:
2592 wctx = repo[None]
2592 wctx = repo[None]
2593 mctx = wctx.parents()[-1]
2593 mctx = wctx.parents()[-1]
2594
2594
2595 # backup pre-resolve (merge uses .orig for its own purposes)
2595 # backup pre-resolve (merge uses .orig for its own purposes)
2596 a = repo.wjoin(f)
2596 a = repo.wjoin(f)
2597 util.copyfile(a, a + ".resolve")
2597 util.copyfile(a, a + ".resolve")
2598
2598
2599 # resolve file
2599 # resolve file
2600 ms.resolve(f, wctx, mctx)
2600 ms.resolve(f, wctx, mctx)
2601
2601
2602 # replace filemerge's .orig file with our resolve file
2602 # replace filemerge's .orig file with our resolve file
2603 util.rename(a + ".resolve", a + ".orig")
2603 util.rename(a + ".resolve", a + ".orig")
2604
2604
2605 def revert(ui, repo, *pats, **opts):
2605 def revert(ui, repo, *pats, **opts):
2606 """restore individual files or directories to an earlier state
2606 """restore individual files or directories to an earlier state
2607
2607
2608 (Use update -r to check out earlier revisions, revert does not
2608 (Use update -r to check out earlier revisions, revert does not
2609 change the working directory parents.)
2609 change the working directory parents.)
2610
2610
2611 With no revision specified, revert the named files or directories
2611 With no revision specified, revert the named files or directories
2612 to the contents they had in the parent of the working directory.
2612 to the contents they had in the parent of the working directory.
2613 This restores the contents of the affected files to an unmodified
2613 This restores the contents of the affected files to an unmodified
2614 state and unschedules adds, removes, copies, and renames. If the
2614 state and unschedules adds, removes, copies, and renames. If the
2615 working directory has two parents, you must explicitly specify a
2615 working directory has two parents, you must explicitly specify a
2616 revision.
2616 revision.
2617
2617
2618 Using the -r/--rev option, revert the given files or directories
2618 Using the -r/--rev option, revert the given files or directories
2619 to their contents as of a specific revision. This can be helpful
2619 to their contents as of a specific revision. This can be helpful
2620 to "roll back" some or all of an earlier change. See 'hg help
2620 to "roll back" some or all of an earlier change. See 'hg help
2621 dates' for a list of formats valid for -d/--date.
2621 dates' for a list of formats valid for -d/--date.
2622
2622
2623 Revert modifies the working directory. It does not commit any
2623 Revert modifies the working directory. It does not commit any
2624 changes, or change the parent of the working directory. If you
2624 changes, or change the parent of the working directory. If you
2625 revert to a revision other than the parent of the working
2625 revert to a revision other than the parent of the working
2626 directory, the reverted files will thus appear modified
2626 directory, the reverted files will thus appear modified
2627 afterwards.
2627 afterwards.
2628
2628
2629 If a file has been deleted, it is restored. If the executable mode
2629 If a file has been deleted, it is restored. If the executable mode
2630 of a file was changed, it is reset.
2630 of a file was changed, it is reset.
2631
2631
2632 If names are given, all files matching the names are reverted.
2632 If names are given, all files matching the names are reverted.
2633 If no arguments are given, no files are reverted.
2633 If no arguments are given, no files are reverted.
2634
2634
2635 Modified files are saved with a .orig suffix before reverting.
2635 Modified files are saved with a .orig suffix before reverting.
2636 To disable these backups, use --no-backup.
2636 To disable these backups, use --no-backup.
2637 """
2637 """
2638
2638
2639 if opts["date"]:
2639 if opts["date"]:
2640 if opts["rev"]:
2640 if opts["rev"]:
2641 raise util.Abort(_("you can't specify a revision and a date"))
2641 raise util.Abort(_("you can't specify a revision and a date"))
2642 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2642 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2643
2643
2644 if not pats and not opts.get('all'):
2644 if not pats and not opts.get('all'):
2645 raise util.Abort(_('no files or directories specified; '
2645 raise util.Abort(_('no files or directories specified; '
2646 'use --all to revert the whole repo'))
2646 'use --all to revert the whole repo'))
2647
2647
2648 parent, p2 = repo.dirstate.parents()
2648 parent, p2 = repo.dirstate.parents()
2649 if not opts.get('rev') and p2 != nullid:
2649 if not opts.get('rev') and p2 != nullid:
2650 raise util.Abort(_('uncommitted merge - please provide a '
2650 raise util.Abort(_('uncommitted merge - please provide a '
2651 'specific revision'))
2651 'specific revision'))
2652 ctx = repo[opts.get('rev')]
2652 ctx = repo[opts.get('rev')]
2653 node = ctx.node()
2653 node = ctx.node()
2654 mf = ctx.manifest()
2654 mf = ctx.manifest()
2655 if node == parent:
2655 if node == parent:
2656 pmf = mf
2656 pmf = mf
2657 else:
2657 else:
2658 pmf = None
2658 pmf = None
2659
2659
2660 # need all matching names in dirstate and manifest of target rev,
2660 # need all matching names in dirstate and manifest of target rev,
2661 # so have to walk both. do not print errors if files exist in one
2661 # so have to walk both. do not print errors if files exist in one
2662 # but not other.
2662 # but not other.
2663
2663
2664 names = {}
2664 names = {}
2665
2665
2666 wlock = repo.wlock()
2666 wlock = repo.wlock()
2667 try:
2667 try:
2668 # walk dirstate.
2668 # walk dirstate.
2669
2669
2670 m = cmdutil.match(repo, pats, opts)
2670 m = cmdutil.match(repo, pats, opts)
2671 m.bad = lambda x, y: False
2671 m.bad = lambda x, y: False
2672 for abs in repo.walk(m):
2672 for abs in repo.walk(m):
2673 names[abs] = m.rel(abs), m.exact(abs)
2673 names[abs] = m.rel(abs), m.exact(abs)
2674
2674
2675 # walk target manifest.
2675 # walk target manifest.
2676
2676
2677 def badfn(path, msg):
2677 def badfn(path, msg):
2678 if path in names:
2678 if path in names:
2679 return
2679 return
2680 path_ = path + '/'
2680 path_ = path + '/'
2681 for f in names:
2681 for f in names:
2682 if f.startswith(path_):
2682 if f.startswith(path_):
2683 return
2683 return
2684 ui.warn("%s: %s\n" % (m.rel(path), msg))
2684 ui.warn("%s: %s\n" % (m.rel(path), msg))
2685
2685
2686 m = cmdutil.match(repo, pats, opts)
2686 m = cmdutil.match(repo, pats, opts)
2687 m.bad = badfn
2687 m.bad = badfn
2688 for abs in repo[node].walk(m):
2688 for abs in repo[node].walk(m):
2689 if abs not in names:
2689 if abs not in names:
2690 names[abs] = m.rel(abs), m.exact(abs)
2690 names[abs] = m.rel(abs), m.exact(abs)
2691
2691
2692 m = cmdutil.matchfiles(repo, names)
2692 m = cmdutil.matchfiles(repo, names)
2693 changes = repo.status(match=m)[:4]
2693 changes = repo.status(match=m)[:4]
2694 modified, added, removed, deleted = map(set, changes)
2694 modified, added, removed, deleted = map(set, changes)
2695
2695
2696 # if f is a rename, also revert the source
2696 # if f is a rename, also revert the source
2697 cwd = repo.getcwd()
2697 cwd = repo.getcwd()
2698 for f in added:
2698 for f in added:
2699 src = repo.dirstate.copied(f)
2699 src = repo.dirstate.copied(f)
2700 if src and src not in names and repo.dirstate[src] == 'r':
2700 if src and src not in names and repo.dirstate[src] == 'r':
2701 removed.add(src)
2701 removed.add(src)
2702 names[src] = (repo.pathto(src, cwd), True)
2702 names[src] = (repo.pathto(src, cwd), True)
2703
2703
2704 def removeforget(abs):
2704 def removeforget(abs):
2705 if repo.dirstate[abs] == 'a':
2705 if repo.dirstate[abs] == 'a':
2706 return _('forgetting %s\n')
2706 return _('forgetting %s\n')
2707 return _('removing %s\n')
2707 return _('removing %s\n')
2708
2708
2709 revert = ([], _('reverting %s\n'))
2709 revert = ([], _('reverting %s\n'))
2710 add = ([], _('adding %s\n'))
2710 add = ([], _('adding %s\n'))
2711 remove = ([], removeforget)
2711 remove = ([], removeforget)
2712 undelete = ([], _('undeleting %s\n'))
2712 undelete = ([], _('undeleting %s\n'))
2713
2713
2714 disptable = (
2714 disptable = (
2715 # dispatch table:
2715 # dispatch table:
2716 # file state
2716 # file state
2717 # action if in target manifest
2717 # action if in target manifest
2718 # action if not in target manifest
2718 # action if not in target manifest
2719 # make backup if in target manifest
2719 # make backup if in target manifest
2720 # make backup if not in target manifest
2720 # make backup if not in target manifest
2721 (modified, revert, remove, True, True),
2721 (modified, revert, remove, True, True),
2722 (added, revert, remove, True, False),
2722 (added, revert, remove, True, False),
2723 (removed, undelete, None, False, False),
2723 (removed, undelete, None, False, False),
2724 (deleted, revert, remove, False, False),
2724 (deleted, revert, remove, False, False),
2725 )
2725 )
2726
2726
2727 for abs, (rel, exact) in sorted(names.items()):
2727 for abs, (rel, exact) in sorted(names.items()):
2728 mfentry = mf.get(abs)
2728 mfentry = mf.get(abs)
2729 target = repo.wjoin(abs)
2729 target = repo.wjoin(abs)
2730 def handle(xlist, dobackup):
2730 def handle(xlist, dobackup):
2731 xlist[0].append(abs)
2731 xlist[0].append(abs)
2732 if dobackup and not opts.get('no_backup') and util.lexists(target):
2732 if dobackup and not opts.get('no_backup') and util.lexists(target):
2733 bakname = "%s.orig" % rel
2733 bakname = "%s.orig" % rel
2734 ui.note(_('saving current version of %s as %s\n') %
2734 ui.note(_('saving current version of %s as %s\n') %
2735 (rel, bakname))
2735 (rel, bakname))
2736 if not opts.get('dry_run'):
2736 if not opts.get('dry_run'):
2737 util.copyfile(target, bakname)
2737 util.copyfile(target, bakname)
2738 if ui.verbose or not exact:
2738 if ui.verbose or not exact:
2739 msg = xlist[1]
2739 msg = xlist[1]
2740 if not isinstance(msg, basestring):
2740 if not isinstance(msg, basestring):
2741 msg = msg(abs)
2741 msg = msg(abs)
2742 ui.status(msg % rel)
2742 ui.status(msg % rel)
2743 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2743 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2744 if abs not in table:
2744 if abs not in table:
2745 continue
2745 continue
2746 # file has changed in dirstate
2746 # file has changed in dirstate
2747 if mfentry:
2747 if mfentry:
2748 handle(hitlist, backuphit)
2748 handle(hitlist, backuphit)
2749 elif misslist is not None:
2749 elif misslist is not None:
2750 handle(misslist, backupmiss)
2750 handle(misslist, backupmiss)
2751 break
2751 break
2752 else:
2752 else:
2753 if abs not in repo.dirstate:
2753 if abs not in repo.dirstate:
2754 if mfentry:
2754 if mfentry:
2755 handle(add, True)
2755 handle(add, True)
2756 elif exact:
2756 elif exact:
2757 ui.warn(_('file not managed: %s\n') % rel)
2757 ui.warn(_('file not managed: %s\n') % rel)
2758 continue
2758 continue
2759 # file has not changed in dirstate
2759 # file has not changed in dirstate
2760 if node == parent:
2760 if node == parent:
2761 if exact:
2761 if exact:
2762 ui.warn(_('no changes needed to %s\n') % rel)
2762 ui.warn(_('no changes needed to %s\n') % rel)
2763 continue
2763 continue
2764 if pmf is None:
2764 if pmf is None:
2765 # only need parent manifest in this unlikely case,
2765 # only need parent manifest in this unlikely case,
2766 # so do not read by default
2766 # so do not read by default
2767 pmf = repo[parent].manifest()
2767 pmf = repo[parent].manifest()
2768 if abs in pmf:
2768 if abs in pmf:
2769 if mfentry:
2769 if mfentry:
2770 # if version of file is same in parent and target
2770 # if version of file is same in parent and target
2771 # manifests, do nothing
2771 # manifests, do nothing
2772 if (pmf[abs] != mfentry or
2772 if (pmf[abs] != mfentry or
2773 pmf.flags(abs) != mf.flags(abs)):
2773 pmf.flags(abs) != mf.flags(abs)):
2774 handle(revert, False)
2774 handle(revert, False)
2775 else:
2775 else:
2776 handle(remove, False)
2776 handle(remove, False)
2777
2777
2778 if not opts.get('dry_run'):
2778 if not opts.get('dry_run'):
2779 def checkout(f):
2779 def checkout(f):
2780 fc = ctx[f]
2780 fc = ctx[f]
2781 repo.wwrite(f, fc.data(), fc.flags())
2781 repo.wwrite(f, fc.data(), fc.flags())
2782
2782
2783 audit_path = util.path_auditor(repo.root)
2783 audit_path = util.path_auditor(repo.root)
2784 for f in remove[0]:
2784 for f in remove[0]:
2785 if repo.dirstate[f] == 'a':
2785 if repo.dirstate[f] == 'a':
2786 repo.dirstate.forget(f)
2786 repo.dirstate.forget(f)
2787 continue
2787 continue
2788 audit_path(f)
2788 audit_path(f)
2789 try:
2789 try:
2790 util.unlink(repo.wjoin(f))
2790 util.unlink(repo.wjoin(f))
2791 except OSError:
2791 except OSError:
2792 pass
2792 pass
2793 repo.dirstate.remove(f)
2793 repo.dirstate.remove(f)
2794
2794
2795 normal = None
2795 normal = None
2796 if node == parent:
2796 if node == parent:
2797 # We're reverting to our parent. If possible, we'd like status
2797 # We're reverting to our parent. If possible, we'd like status
2798 # to report the file as clean. We have to use normallookup for
2798 # to report the file as clean. We have to use normallookup for
2799 # merges to avoid losing information about merged/dirty files.
2799 # merges to avoid losing information about merged/dirty files.
2800 if p2 != nullid:
2800 if p2 != nullid:
2801 normal = repo.dirstate.normallookup
2801 normal = repo.dirstate.normallookup
2802 else:
2802 else:
2803 normal = repo.dirstate.normal
2803 normal = repo.dirstate.normal
2804 for f in revert[0]:
2804 for f in revert[0]:
2805 checkout(f)
2805 checkout(f)
2806 if normal:
2806 if normal:
2807 normal(f)
2807 normal(f)
2808
2808
2809 for f in add[0]:
2809 for f in add[0]:
2810 checkout(f)
2810 checkout(f)
2811 repo.dirstate.add(f)
2811 repo.dirstate.add(f)
2812
2812
2813 normal = repo.dirstate.normallookup
2813 normal = repo.dirstate.normallookup
2814 if node == parent and p2 == nullid:
2814 if node == parent and p2 == nullid:
2815 normal = repo.dirstate.normal
2815 normal = repo.dirstate.normal
2816 for f in undelete[0]:
2816 for f in undelete[0]:
2817 checkout(f)
2817 checkout(f)
2818 normal(f)
2818 normal(f)
2819
2819
2820 finally:
2820 finally:
2821 wlock.release()
2821 wlock.release()
2822
2822
2823 def rollback(ui, repo):
2823 def rollback(ui, repo):
2824 """roll back the last transaction
2824 """roll back the last transaction
2825
2825
2826 This command should be used with care. There is only one level of
2826 This command should be used with care. There is only one level of
2827 rollback, and there is no way to undo a rollback. It will also
2827 rollback, and there is no way to undo a rollback. It will also
2828 restore the dirstate at the time of the last transaction, losing
2828 restore the dirstate at the time of the last transaction, losing
2829 any dirstate changes since that time. This command does not alter
2829 any dirstate changes since that time. This command does not alter
2830 the working directory.
2830 the working directory.
2831
2831
2832 Transactions are used to encapsulate the effects of all commands
2832 Transactions are used to encapsulate the effects of all commands
2833 that create new changesets or propagate existing changesets into a
2833 that create new changesets or propagate existing changesets into a
2834 repository. For example, the following commands are transactional,
2834 repository. For example, the following commands are transactional,
2835 and their effects can be rolled back:
2835 and their effects can be rolled back:
2836
2836
2837 - commit
2837 - commit
2838 - import
2838 - import
2839 - pull
2839 - pull
2840 - push (with this repository as the destination)
2840 - push (with this repository as the destination)
2841 - unbundle
2841 - unbundle
2842
2842
2843 This command is not intended for use on public repositories. Once
2843 This command is not intended for use on public repositories. Once
2844 changes are visible for pull by other users, rolling a transaction
2844 changes are visible for pull by other users, rolling a transaction
2845 back locally is ineffective (someone else may already have pulled
2845 back locally is ineffective (someone else may already have pulled
2846 the changes). Furthermore, a race is possible with readers of the
2846 the changes). Furthermore, a race is possible with readers of the
2847 repository; for example an in-progress pull from the repository
2847 repository; for example an in-progress pull from the repository
2848 may fail if a rollback is performed.
2848 may fail if a rollback is performed.
2849 """
2849 """
2850 repo.rollback()
2850 repo.rollback()
2851
2851
2852 def root(ui, repo):
2852 def root(ui, repo):
2853 """print the root (top) of the current working directory
2853 """print the root (top) of the current working directory
2854
2854
2855 Print the root directory of the current repository.
2855 Print the root directory of the current repository.
2856 """
2856 """
2857 ui.write(repo.root + "\n")
2857 ui.write(repo.root + "\n")
2858
2858
2859 def serve(ui, repo, **opts):
2859 def serve(ui, repo, **opts):
2860 """export the repository via HTTP
2860 """export the repository via HTTP
2861
2861
2862 Start a local HTTP repository browser and pull server.
2862 Start a local HTTP repository browser and pull server.
2863
2863
2864 By default, the server logs accesses to stdout and errors to
2864 By default, the server logs accesses to stdout and errors to
2865 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
2865 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
2866 files.
2866 files.
2867 """
2867 """
2868
2868
2869 if opts["stdio"]:
2869 if opts["stdio"]:
2870 if repo is None:
2870 if repo is None:
2871 raise error.RepoError(_("There is no Mercurial repository here"
2871 raise error.RepoError(_("There is no Mercurial repository here"
2872 " (.hg not found)"))
2872 " (.hg not found)"))
2873 s = sshserver.sshserver(ui, repo)
2873 s = sshserver.sshserver(ui, repo)
2874 s.serve_forever()
2874 s.serve_forever()
2875
2875
2876 baseui = repo and repo.baseui or ui
2876 baseui = repo and repo.baseui or ui
2877 optlist = ("name templates style address port prefix ipv6"
2877 optlist = ("name templates style address port prefix ipv6"
2878 " accesslog errorlog webdir_conf certificate encoding")
2878 " accesslog errorlog webdir_conf certificate encoding")
2879 for o in optlist.split():
2879 for o in optlist.split():
2880 if opts.get(o, None):
2880 if opts.get(o, None):
2881 baseui.setconfig("web", o, str(opts[o]))
2881 baseui.setconfig("web", o, str(opts[o]))
2882 if (repo is not None) and (repo.ui != baseui):
2882 if (repo is not None) and (repo.ui != baseui):
2883 repo.ui.setconfig("web", o, str(opts[o]))
2883 repo.ui.setconfig("web", o, str(opts[o]))
2884
2884
2885 if repo is None and not ui.config("web", "webdir_conf"):
2885 if repo is None and not ui.config("web", "webdir_conf"):
2886 raise error.RepoError(_("There is no Mercurial repository here"
2886 raise error.RepoError(_("There is no Mercurial repository here"
2887 " (.hg not found)"))
2887 " (.hg not found)"))
2888
2888
2889 class service(object):
2889 class service(object):
2890 def init(self):
2890 def init(self):
2891 util.set_signal_handler()
2891 util.set_signal_handler()
2892 self.httpd = server.create_server(baseui, repo)
2892 self.httpd = server.create_server(baseui, repo)
2893
2893
2894 if not ui.verbose:
2894 if not ui.verbose:
2895 return
2895 return
2896
2896
2897 if self.httpd.prefix:
2897 if self.httpd.prefix:
2898 prefix = self.httpd.prefix.strip('/') + '/'
2898 prefix = self.httpd.prefix.strip('/') + '/'
2899 else:
2899 else:
2900 prefix = ''
2900 prefix = ''
2901
2901
2902 port = ':%d' % self.httpd.port
2902 port = ':%d' % self.httpd.port
2903 if port == ':80':
2903 if port == ':80':
2904 port = ''
2904 port = ''
2905
2905
2906 bindaddr = self.httpd.addr
2906 bindaddr = self.httpd.addr
2907 if bindaddr == '0.0.0.0':
2907 if bindaddr == '0.0.0.0':
2908 bindaddr = '*'
2908 bindaddr = '*'
2909 elif ':' in bindaddr: # IPv6
2909 elif ':' in bindaddr: # IPv6
2910 bindaddr = '[%s]' % bindaddr
2910 bindaddr = '[%s]' % bindaddr
2911
2911
2912 fqaddr = self.httpd.fqaddr
2912 fqaddr = self.httpd.fqaddr
2913 if ':' in fqaddr:
2913 if ':' in fqaddr:
2914 fqaddr = '[%s]' % fqaddr
2914 fqaddr = '[%s]' % fqaddr
2915 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2915 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2916 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2916 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2917
2917
2918 def run(self):
2918 def run(self):
2919 self.httpd.serve_forever()
2919 self.httpd.serve_forever()
2920
2920
2921 service = service()
2921 service = service()
2922
2922
2923 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2923 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2924
2924
2925 def status(ui, repo, *pats, **opts):
2925 def status(ui, repo, *pats, **opts):
2926 """show changed files in the working directory
2926 """show changed files in the working directory
2927
2927
2928 Show status of files in the repository. If names are given, only
2928 Show status of files in the repository. If names are given, only
2929 files that match are shown. Files that are clean or ignored or
2929 files that match are shown. Files that are clean or ignored or
2930 the source of a copy/move operation, are not listed unless
2930 the source of a copy/move operation, are not listed unless
2931 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
2931 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
2932 Unless options described with "show only ..." are given, the
2932 Unless options described with "show only ..." are given, the
2933 options -mardu are used.
2933 options -mardu are used.
2934
2934
2935 Option -q/--quiet hides untracked (unknown and ignored) files
2935 Option -q/--quiet hides untracked (unknown and ignored) files
2936 unless explicitly requested with -u/--unknown or -i/--ignored.
2936 unless explicitly requested with -u/--unknown or -i/--ignored.
2937
2937
2938 NOTE: status may appear to disagree with diff if permissions have
2938 NOTE: status may appear to disagree with diff if permissions have
2939 changed or a merge has occurred. The standard diff format does not
2939 changed or a merge has occurred. The standard diff format does not
2940 report permission changes and diff only reports changes relative
2940 report permission changes and diff only reports changes relative
2941 to one merge parent.
2941 to one merge parent.
2942
2942
2943 If one revision is given, it is used as the base revision.
2943 If one revision is given, it is used as the base revision.
2944 If two revisions are given, the differences between them are
2944 If two revisions are given, the differences between them are
2945 shown. The --change option can also be used as a shortcut to list
2945 shown. The --change option can also be used as a shortcut to list
2946 the changed files of a revision from its first parent.
2946 the changed files of a revision from its first parent.
2947
2947
2948 The codes used to show the status of files are::
2948 The codes used to show the status of files are::
2949
2949
2950 M = modified
2950 M = modified
2951 A = added
2951 A = added
2952 R = removed
2952 R = removed
2953 C = clean
2953 C = clean
2954 ! = missing (deleted by non-hg command, but still tracked)
2954 ! = missing (deleted by non-hg command, but still tracked)
2955 ? = not tracked
2955 ? = not tracked
2956 I = ignored
2956 I = ignored
2957 = origin of the previous file listed as A (added)
2957 = origin of the previous file listed as A (added)
2958 """
2958 """
2959
2959
2960 revs = opts.get('rev')
2960 revs = opts.get('rev')
2961 change = opts.get('change')
2961 change = opts.get('change')
2962
2962
2963 if revs and change:
2963 if revs and change:
2964 msg = _('cannot specify --rev and --change at the same time')
2964 msg = _('cannot specify --rev and --change at the same time')
2965 raise util.Abort(msg)
2965 raise util.Abort(msg)
2966 elif change:
2966 elif change:
2967 node2 = repo.lookup(change)
2967 node2 = repo.lookup(change)
2968 node1 = repo[node2].parents()[0].node()
2968 node1 = repo[node2].parents()[0].node()
2969 else:
2969 else:
2970 node1, node2 = cmdutil.revpair(repo, revs)
2970 node1, node2 = cmdutil.revpair(repo, revs)
2971
2971
2972 cwd = (pats and repo.getcwd()) or ''
2972 cwd = (pats and repo.getcwd()) or ''
2973 end = opts.get('print0') and '\0' or '\n'
2973 end = opts.get('print0') and '\0' or '\n'
2974 copy = {}
2974 copy = {}
2975 states = 'modified added removed deleted unknown ignored clean'.split()
2975 states = 'modified added removed deleted unknown ignored clean'.split()
2976 show = [k for k in states if opts.get(k)]
2976 show = [k for k in states if opts.get(k)]
2977 if opts.get('all'):
2977 if opts.get('all'):
2978 show += ui.quiet and (states[:4] + ['clean']) or states
2978 show += ui.quiet and (states[:4] + ['clean']) or states
2979 if not show:
2979 if not show:
2980 show = ui.quiet and states[:4] or states[:5]
2980 show = ui.quiet and states[:4] or states[:5]
2981
2981
2982 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2982 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2983 'ignored' in show, 'clean' in show, 'unknown' in show)
2983 'ignored' in show, 'clean' in show, 'unknown' in show)
2984 changestates = zip(states, 'MAR!?IC', stat)
2984 changestates = zip(states, 'MAR!?IC', stat)
2985
2985
2986 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
2986 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
2987 ctxn = repo[nullid]
2987 ctxn = repo[nullid]
2988 ctx1 = repo[node1]
2988 ctx1 = repo[node1]
2989 ctx2 = repo[node2]
2989 ctx2 = repo[node2]
2990 added = stat[1]
2990 added = stat[1]
2991 if node2 is None:
2991 if node2 is None:
2992 added = stat[0] + stat[1] # merged?
2992 added = stat[0] + stat[1] # merged?
2993
2993
2994 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
2994 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
2995 if k in added:
2995 if k in added:
2996 copy[k] = v
2996 copy[k] = v
2997 elif v in added:
2997 elif v in added:
2998 copy[v] = k
2998 copy[v] = k
2999
2999
3000 for state, char, files in changestates:
3000 for state, char, files in changestates:
3001 if state in show:
3001 if state in show:
3002 format = "%s %%s%s" % (char, end)
3002 format = "%s %%s%s" % (char, end)
3003 if opts.get('no_status'):
3003 if opts.get('no_status'):
3004 format = "%%s%s" % end
3004 format = "%%s%s" % end
3005
3005
3006 for f in files:
3006 for f in files:
3007 ui.write(format % repo.pathto(f, cwd))
3007 ui.write(format % repo.pathto(f, cwd))
3008 if f in copy:
3008 if f in copy:
3009 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
3009 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
3010
3010
3011 def summary(ui, repo, **opts):
3011 def summary(ui, repo, **opts):
3012 """summarize working directory state
3012 """summarize working directory state
3013
3013
3014 This generates a brief summary of the working directory state,
3014 This generates a brief summary of the working directory state,
3015 including parents, branch, commit status, and available updates.
3015 including parents, branch, commit status, and available updates.
3016
3016
3017 With the --remote option, this will check the default paths for
3017 With the --remote option, this will check the default paths for
3018 incoming and outgoing changes. This can be time-consuming.
3018 incoming and outgoing changes. This can be time-consuming.
3019 """
3019 """
3020
3020
3021 ctx = repo[None]
3021 ctx = repo[None]
3022 parents = ctx.parents()
3022 parents = ctx.parents()
3023 pnode = parents[0].node()
3023 pnode = parents[0].node()
3024 tags = repo.tags()
3024 tags = repo.tags()
3025
3025
3026 for p in parents:
3026 for p in parents:
3027 t = ' '.join([t for t in tags if tags[t] == p.node()])
3027 t = ' '.join([t for t in tags if tags[t] == p.node()])
3028 if p.rev() == -1:
3028 if p.rev() == -1:
3029 if not len(repo):
3029 if not len(repo):
3030 t += _(' (empty repository)')
3030 t += _(' (empty repository)')
3031 else:
3031 else:
3032 t += _(' (no revision checked out)')
3032 t += _(' (no revision checked out)')
3033 ui.write(_('parent: %d:%s %s\n') % (p.rev(), str(p), t))
3033 ui.write(_('parent: %d:%s %s\n') % (p.rev(), str(p), t))
3034 if p.description():
3034 if p.description():
3035 ui.status(' ' + p.description().splitlines()[0].strip() + '\n')
3035 ui.status(' ' + p.description().splitlines()[0].strip() + '\n')
3036
3036
3037 branch = ctx.branch()
3037 branch = ctx.branch()
3038 bheads = repo.branchheads(branch)
3038 bheads = repo.branchheads(branch)
3039 m = _('branch: %s\n') % branch
3039 m = _('branch: %s\n') % branch
3040 if branch != 'default':
3040 if branch != 'default':
3041 ui.write(m)
3041 ui.write(m)
3042 else:
3042 else:
3043 ui.status(m)
3043 ui.status(m)
3044
3044
3045 st = list(repo.status(unknown=True))[:6]
3045 st = list(repo.status(unknown=True))[:6]
3046 ms = merge_.mergestate(repo)
3046 ms = merge_.mergestate(repo)
3047 st.append([f for f in ms if ms[f] == 'u'])
3047 st.append([f for f in ms if ms[f] == 'u'])
3048 labels = [_('%d modified'), _('%d added'), _('%d removed'),
3048 labels = [_('%d modified'), _('%d added'), _('%d removed'),
3049 _('%d deleted'), _('%d unknown'), _('%d ignored'),
3049 _('%d deleted'), _('%d unknown'), _('%d ignored'),
3050 _('%d unresolved')]
3050 _('%d unresolved')]
3051 t = []
3051 t = []
3052 for s, l in zip(st, labels):
3052 for s, l in zip(st, labels):
3053 if s:
3053 if s:
3054 t.append(l % len(s))
3054 t.append(l % len(s))
3055
3055
3056 t = ', '.join(t)
3056 t = ', '.join(t)
3057 cleanworkdir = False
3057 cleanworkdir = False
3058
3058
3059 if len(parents) > 1:
3059 if len(parents) > 1:
3060 t += _(' (merge)')
3060 t += _(' (merge)')
3061 elif branch != parents[0].branch():
3061 elif branch != parents[0].branch():
3062 t += _(' (new branch)')
3062 t += _(' (new branch)')
3063 elif (not st[0] and not st[1] and not st[2]):
3063 elif (not st[0] and not st[1] and not st[2]):
3064 t += _(' (clean)')
3064 t += _(' (clean)')
3065 cleanworkdir = True
3065 cleanworkdir = True
3066 elif pnode not in bheads:
3066 elif pnode not in bheads:
3067 t += _(' (new branch head)')
3067 t += _(' (new branch head)')
3068
3068
3069 if cleanworkdir:
3069 if cleanworkdir:
3070 ui.status(_('commit: %s\n') % t.strip())
3070 ui.status(_('commit: %s\n') % t.strip())
3071 else:
3071 else:
3072 ui.write(_('commit: %s\n') % t.strip())
3072 ui.write(_('commit: %s\n') % t.strip())
3073
3073
3074 # all ancestors of branch heads - all ancestors of parent = new csets
3074 # all ancestors of branch heads - all ancestors of parent = new csets
3075 new = [0] * len(repo)
3075 new = [0] * len(repo)
3076 cl = repo.changelog
3076 cl = repo.changelog
3077 for a in [cl.rev(n) for n in bheads]:
3077 for a in [cl.rev(n) for n in bheads]:
3078 new[a] = 1
3078 new[a] = 1
3079 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3079 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3080 new[a] = 1
3080 new[a] = 1
3081 for a in [p.rev() for p in parents]:
3081 for a in [p.rev() for p in parents]:
3082 if a >= 0:
3082 if a >= 0:
3083 new[a] = 0
3083 new[a] = 0
3084 for a in cl.ancestors(*[p.rev() for p in parents]):
3084 for a in cl.ancestors(*[p.rev() for p in parents]):
3085 new[a] = 0
3085 new[a] = 0
3086 new = sum(new)
3086 new = sum(new)
3087
3087
3088 if new == 0:
3088 if new == 0:
3089 ui.status(_('update: (current)\n'))
3089 ui.status(_('update: (current)\n'))
3090 elif pnode not in bheads:
3090 elif pnode not in bheads:
3091 ui.write(_('update: %d new changesets (update)\n') % new)
3091 ui.write(_('update: %d new changesets (update)\n') % new)
3092 else:
3092 else:
3093 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3093 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3094 (new, len(bheads)))
3094 (new, len(bheads)))
3095
3095
3096 if opts.get('remote'):
3096 if opts.get('remote'):
3097 t = []
3097 t = []
3098 source, branches = hg.parseurl(ui.expandpath('default'))
3098 source, branches = hg.parseurl(ui.expandpath('default'))
3099 other = hg.repository(cmdutil.remoteui(repo, {}), source)
3099 other = hg.repository(cmdutil.remoteui(repo, {}), source)
3100 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3100 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3101 ui.debug('comparing with %s\n' % url.hidepassword(source))
3101 ui.debug('comparing with %s\n' % url.hidepassword(source))
3102 repo.ui.pushbuffer()
3102 repo.ui.pushbuffer()
3103 common, incoming, rheads = repo.findcommonincoming(other)
3103 common, incoming, rheads = repo.findcommonincoming(other)
3104 repo.ui.popbuffer()
3104 repo.ui.popbuffer()
3105 if incoming:
3105 if incoming:
3106 t.append(_('1 or more incoming'))
3106 t.append(_('1 or more incoming'))
3107
3107
3108 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3108 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3109 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3109 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3110 other = hg.repository(cmdutil.remoteui(repo, {}), dest)
3110 other = hg.repository(cmdutil.remoteui(repo, {}), dest)
3111 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3111 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3112 repo.ui.pushbuffer()
3112 repo.ui.pushbuffer()
3113 o = repo.findoutgoing(other)
3113 o = repo.findoutgoing(other)
3114 repo.ui.popbuffer()
3114 repo.ui.popbuffer()
3115 o = repo.changelog.nodesbetween(o, None)[0]
3115 o = repo.changelog.nodesbetween(o, None)[0]
3116 if o:
3116 if o:
3117 t.append(_('%d outgoing') % len(o))
3117 t.append(_('%d outgoing') % len(o))
3118
3118
3119 if t:
3119 if t:
3120 ui.write(_('remote: %s\n') % (', '.join(t)))
3120 ui.write(_('remote: %s\n') % (', '.join(t)))
3121 else:
3121 else:
3122 ui.status(_('remote: (synced)\n'))
3122 ui.status(_('remote: (synced)\n'))
3123
3123
3124 def tag(ui, repo, name1, *names, **opts):
3124 def tag(ui, repo, name1, *names, **opts):
3125 """add one or more tags for the current or given revision
3125 """add one or more tags for the current or given revision
3126
3126
3127 Name a particular revision using <name>.
3127 Name a particular revision using <name>.
3128
3128
3129 Tags are used to name particular revisions of the repository and are
3129 Tags are used to name particular revisions of the repository and are
3130 very useful to compare different revisions, to go back to significant
3130 very useful to compare different revisions, to go back to significant
3131 earlier versions or to mark branch points as releases, etc.
3131 earlier versions or to mark branch points as releases, etc.
3132
3132
3133 If no revision is given, the parent of the working directory is
3133 If no revision is given, the parent of the working directory is
3134 used, or tip if no revision is checked out.
3134 used, or tip if no revision is checked out.
3135
3135
3136 To facilitate version control, distribution, and merging of tags,
3136 To facilitate version control, distribution, and merging of tags,
3137 they are stored as a file named ".hgtags" which is managed
3137 they are stored as a file named ".hgtags" which is managed
3138 similarly to other project files and can be hand-edited if
3138 similarly to other project files and can be hand-edited if
3139 necessary. The file '.hg/localtags' is used for local tags (not
3139 necessary. The file '.hg/localtags' is used for local tags (not
3140 shared among repositories).
3140 shared among repositories).
3141
3141
3142 See 'hg help dates' for a list of formats valid for -d/--date.
3142 See 'hg help dates' for a list of formats valid for -d/--date.
3143 """
3143 """
3144
3144
3145 rev_ = "."
3145 rev_ = "."
3146 names = (name1,) + names
3146 names = (name1,) + names
3147 if len(names) != len(set(names)):
3147 if len(names) != len(set(names)):
3148 raise util.Abort(_('tag names must be unique'))
3148 raise util.Abort(_('tag names must be unique'))
3149 for n in names:
3149 for n in names:
3150 if n in ['tip', '.', 'null']:
3150 if n in ['tip', '.', 'null']:
3151 raise util.Abort(_('the name \'%s\' is reserved') % n)
3151 raise util.Abort(_('the name \'%s\' is reserved') % n)
3152 if opts.get('rev') and opts.get('remove'):
3152 if opts.get('rev') and opts.get('remove'):
3153 raise util.Abort(_("--rev and --remove are incompatible"))
3153 raise util.Abort(_("--rev and --remove are incompatible"))
3154 if opts.get('rev'):
3154 if opts.get('rev'):
3155 rev_ = opts['rev']
3155 rev_ = opts['rev']
3156 message = opts.get('message')
3156 message = opts.get('message')
3157 if opts.get('remove'):
3157 if opts.get('remove'):
3158 expectedtype = opts.get('local') and 'local' or 'global'
3158 expectedtype = opts.get('local') and 'local' or 'global'
3159 for n in names:
3159 for n in names:
3160 if not repo.tagtype(n):
3160 if not repo.tagtype(n):
3161 raise util.Abort(_('tag \'%s\' does not exist') % n)
3161 raise util.Abort(_('tag \'%s\' does not exist') % n)
3162 if repo.tagtype(n) != expectedtype:
3162 if repo.tagtype(n) != expectedtype:
3163 if expectedtype == 'global':
3163 if expectedtype == 'global':
3164 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3164 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3165 else:
3165 else:
3166 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3166 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3167 rev_ = nullid
3167 rev_ = nullid
3168 if not message:
3168 if not message:
3169 # we don't translate commit messages
3169 # we don't translate commit messages
3170 message = 'Removed tag %s' % ', '.join(names)
3170 message = 'Removed tag %s' % ', '.join(names)
3171 elif not opts.get('force'):
3171 elif not opts.get('force'):
3172 for n in names:
3172 for n in names:
3173 if n in repo.tags():
3173 if n in repo.tags():
3174 raise util.Abort(_('tag \'%s\' already exists '
3174 raise util.Abort(_('tag \'%s\' already exists '
3175 '(use -f to force)') % n)
3175 '(use -f to force)') % n)
3176 if not rev_ and repo.dirstate.parents()[1] != nullid:
3176 if not rev_ and repo.dirstate.parents()[1] != nullid:
3177 raise util.Abort(_('uncommitted merge - please provide a '
3177 raise util.Abort(_('uncommitted merge - please provide a '
3178 'specific revision'))
3178 'specific revision'))
3179 r = repo[rev_].node()
3179 r = repo[rev_].node()
3180
3180
3181 if not message:
3181 if not message:
3182 # we don't translate commit messages
3182 # we don't translate commit messages
3183 message = ('Added tag %s for changeset %s' %
3183 message = ('Added tag %s for changeset %s' %
3184 (', '.join(names), short(r)))
3184 (', '.join(names), short(r)))
3185
3185
3186 date = opts.get('date')
3186 date = opts.get('date')
3187 if date:
3187 if date:
3188 date = util.parsedate(date)
3188 date = util.parsedate(date)
3189
3189
3190 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3190 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3191
3191
3192 def tags(ui, repo):
3192 def tags(ui, repo):
3193 """list repository tags
3193 """list repository tags
3194
3194
3195 This lists both regular and local tags. When the -v/--verbose
3195 This lists both regular and local tags. When the -v/--verbose
3196 switch is used, a third column "local" is printed for local tags.
3196 switch is used, a third column "local" is printed for local tags.
3197 """
3197 """
3198
3198
3199 hexfunc = ui.debugflag and hex or short
3199 hexfunc = ui.debugflag and hex or short
3200 tagtype = ""
3200 tagtype = ""
3201
3201
3202 for t, n in reversed(repo.tagslist()):
3202 for t, n in reversed(repo.tagslist()):
3203 if ui.quiet:
3203 if ui.quiet:
3204 ui.write("%s\n" % t)
3204 ui.write("%s\n" % t)
3205 continue
3205 continue
3206
3206
3207 try:
3207 try:
3208 hn = hexfunc(n)
3208 hn = hexfunc(n)
3209 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3209 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3210 except error.LookupError:
3210 except error.LookupError:
3211 r = " ?:%s" % hn
3211 r = " ?:%s" % hn
3212 else:
3212 else:
3213 spaces = " " * (30 - encoding.colwidth(t))
3213 spaces = " " * (30 - encoding.colwidth(t))
3214 if ui.verbose:
3214 if ui.verbose:
3215 if repo.tagtype(t) == 'local':
3215 if repo.tagtype(t) == 'local':
3216 tagtype = " local"
3216 tagtype = " local"
3217 else:
3217 else:
3218 tagtype = ""
3218 tagtype = ""
3219 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3219 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3220
3220
3221 def tip(ui, repo, **opts):
3221 def tip(ui, repo, **opts):
3222 """show the tip revision
3222 """show the tip revision
3223
3223
3224 The tip revision (usually just called the tip) is the changeset
3224 The tip revision (usually just called the tip) is the changeset
3225 most recently added to the repository (and therefore the most
3225 most recently added to the repository (and therefore the most
3226 recently changed head).
3226 recently changed head).
3227
3227
3228 If you have just made a commit, that commit will be the tip. If
3228 If you have just made a commit, that commit will be the tip. If
3229 you have just pulled changes from another repository, the tip of
3229 you have just pulled changes from another repository, the tip of
3230 that repository becomes the current tip. The "tip" tag is special
3230 that repository becomes the current tip. The "tip" tag is special
3231 and cannot be renamed or assigned to a different changeset.
3231 and cannot be renamed or assigned to a different changeset.
3232 """
3232 """
3233 displayer = cmdutil.show_changeset(ui, repo, opts)
3233 displayer = cmdutil.show_changeset(ui, repo, opts)
3234 displayer.show(repo[len(repo) - 1])
3234 displayer.show(repo[len(repo) - 1])
3235 displayer.close()
3235 displayer.close()
3236
3236
3237 def unbundle(ui, repo, fname1, *fnames, **opts):
3237 def unbundle(ui, repo, fname1, *fnames, **opts):
3238 """apply one or more changegroup files
3238 """apply one or more changegroup files
3239
3239
3240 Apply one or more compressed changegroup files generated by the
3240 Apply one or more compressed changegroup files generated by the
3241 bundle command.
3241 bundle command.
3242 """
3242 """
3243 fnames = (fname1,) + fnames
3243 fnames = (fname1,) + fnames
3244
3244
3245 lock = repo.lock()
3245 lock = repo.lock()
3246 try:
3246 try:
3247 for fname in fnames:
3247 for fname in fnames:
3248 f = url.open(ui, fname)
3248 f = url.open(ui, fname)
3249 gen = changegroup.readbundle(f, fname)
3249 gen = changegroup.readbundle(f, fname)
3250 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
3250 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
3251 finally:
3251 finally:
3252 lock.release()
3252 lock.release()
3253
3253
3254 return postincoming(ui, repo, modheads, opts.get('update'), None)
3254 return postincoming(ui, repo, modheads, opts.get('update'), None)
3255
3255
3256 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3256 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3257 """update working directory
3257 """update working directory
3258
3258
3259 Update the repository's working directory to the specified
3259 Update the repository's working directory to the specified
3260 changeset.
3260 changeset.
3261
3261
3262 If no changeset is specified, attempt to update to the head of the
3262 If no changeset is specified, attempt to update to the head of the
3263 current branch. If this head is a descendant of the working
3263 current branch. If this head is a descendant of the working
3264 directory's parent, update to it, otherwise abort.
3264 directory's parent, update to it, otherwise abort.
3265
3265
3266 The following rules apply when the working directory contains
3266 The following rules apply when the working directory contains
3267 uncommitted changes:
3267 uncommitted changes:
3268
3268
3269 1. If neither -c/--check nor -C/--clean is specified, and if
3269 1. If neither -c/--check nor -C/--clean is specified, and if
3270 the requested changeset is an ancestor or descendant of
3270 the requested changeset is an ancestor or descendant of
3271 the working directory's parent, the uncommitted changes
3271 the working directory's parent, the uncommitted changes
3272 are merged into the requested changeset and the merged
3272 are merged into the requested changeset and the merged
3273 result is left uncommitted. If the requested changeset is
3273 result is left uncommitted. If the requested changeset is
3274 not an ancestor or descendant (that is, it is on another
3274 not an ancestor or descendant (that is, it is on another
3275 branch), the update is aborted and the uncommitted changes
3275 branch), the update is aborted and the uncommitted changes
3276 are preserved.
3276 are preserved.
3277
3277
3278 2. With the -c/--check option, the update is aborted and the
3278 2. With the -c/--check option, the update is aborted and the
3279 uncommitted changes are preserved.
3279 uncommitted changes are preserved.
3280
3280
3281 3. With the -C/--clean option, uncommitted changes are discarded and
3281 3. With the -C/--clean option, uncommitted changes are discarded and
3282 the working directory is updated to the requested changeset.
3282 the working directory is updated to the requested changeset.
3283
3283
3284 Use null as the changeset to remove the working directory (like 'hg
3284 Use null as the changeset to remove the working directory (like 'hg
3285 clone -U').
3285 clone -U').
3286
3286
3287 If you want to update just one file to an older changeset, use 'hg revert'.
3287 If you want to update just one file to an older changeset, use 'hg revert'.
3288
3288
3289 See 'hg help dates' for a list of formats valid for -d/--date.
3289 See 'hg help dates' for a list of formats valid for -d/--date.
3290 """
3290 """
3291 if rev and node:
3291 if rev and node:
3292 raise util.Abort(_("please specify just one revision"))
3292 raise util.Abort(_("please specify just one revision"))
3293
3293
3294 if not rev:
3294 if not rev:
3295 rev = node
3295 rev = node
3296
3296
3297 if check and clean:
3297 if check and clean:
3298 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3298 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3299
3299
3300 if check:
3300 if check:
3301 # we could use dirty() but we can ignore merge and branch trivia
3301 # we could use dirty() but we can ignore merge and branch trivia
3302 c = repo[None]
3302 c = repo[None]
3303 if c.modified() or c.added() or c.removed():
3303 if c.modified() or c.added() or c.removed():
3304 raise util.Abort(_("uncommitted local changes"))
3304 raise util.Abort(_("uncommitted local changes"))
3305
3305
3306 if date:
3306 if date:
3307 if rev:
3307 if rev:
3308 raise util.Abort(_("you can't specify a revision and a date"))
3308 raise util.Abort(_("you can't specify a revision and a date"))
3309 rev = cmdutil.finddate(ui, repo, date)
3309 rev = cmdutil.finddate(ui, repo, date)
3310
3310
3311 if clean or check:
3311 if clean or check:
3312 return hg.clean(repo, rev)
3312 return hg.clean(repo, rev)
3313 else:
3313 else:
3314 return hg.update(repo, rev)
3314 return hg.update(repo, rev)
3315
3315
3316 def verify(ui, repo):
3316 def verify(ui, repo):
3317 """verify the integrity of the repository
3317 """verify the integrity of the repository
3318
3318
3319 Verify the integrity of the current repository.
3319 Verify the integrity of the current repository.
3320
3320
3321 This will perform an extensive check of the repository's
3321 This will perform an extensive check of the repository's
3322 integrity, validating the hashes and checksums of each entry in
3322 integrity, validating the hashes and checksums of each entry in
3323 the changelog, manifest, and tracked files, as well as the
3323 the changelog, manifest, and tracked files, as well as the
3324 integrity of their crosslinks and indices.
3324 integrity of their crosslinks and indices.
3325 """
3325 """
3326 return hg.verify(repo)
3326 return hg.verify(repo)
3327
3327
3328 def version_(ui):
3328 def version_(ui):
3329 """output version and copyright information"""
3329 """output version and copyright information"""
3330 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3330 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3331 % util.version())
3331 % util.version())
3332 ui.status(_(
3332 ui.status(_(
3333 "\nCopyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others\n"
3333 "\nCopyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others\n"
3334 "This is free software; see the source for copying conditions. "
3334 "This is free software; see the source for copying conditions. "
3335 "There is NO\nwarranty; "
3335 "There is NO\nwarranty; "
3336 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3336 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3337 ))
3337 ))
3338
3338
3339 # Command options and aliases are listed here, alphabetically
3339 # Command options and aliases are listed here, alphabetically
3340
3340
3341 globalopts = [
3341 globalopts = [
3342 ('R', 'repository', '',
3342 ('R', 'repository', '',
3343 _('repository root directory or name of overlay bundle file')),
3343 _('repository root directory or name of overlay bundle file')),
3344 ('', 'cwd', '', _('change working directory')),
3344 ('', 'cwd', '', _('change working directory')),
3345 ('y', 'noninteractive', None,
3345 ('y', 'noninteractive', None,
3346 _('do not prompt, assume \'yes\' for any required answers')),
3346 _('do not prompt, assume \'yes\' for any required answers')),
3347 ('q', 'quiet', None, _('suppress output')),
3347 ('q', 'quiet', None, _('suppress output')),
3348 ('v', 'verbose', None, _('enable additional output')),
3348 ('v', 'verbose', None, _('enable additional output')),
3349 ('', 'config', [],
3349 ('', 'config', [],
3350 _('set/override config option (use \'section.name=value\')')),
3350 _('set/override config option (use \'section.name=value\')')),
3351 ('', 'debug', None, _('enable debugging output')),
3351 ('', 'debug', None, _('enable debugging output')),
3352 ('', 'debugger', None, _('start debugger')),
3352 ('', 'debugger', None, _('start debugger')),
3353 ('', 'encoding', encoding.encoding, _('set the charset encoding')),
3353 ('', 'encoding', encoding.encoding, _('set the charset encoding')),
3354 ('', 'encodingmode', encoding.encodingmode,
3354 ('', 'encodingmode', encoding.encodingmode,
3355 _('set the charset encoding mode')),
3355 _('set the charset encoding mode')),
3356 ('', 'traceback', None, _('always print a traceback on exception')),
3356 ('', 'traceback', None, _('always print a traceback on exception')),
3357 ('', 'time', None, _('time how long the command takes')),
3357 ('', 'time', None, _('time how long the command takes')),
3358 ('', 'profile', None, _('print command execution profile')),
3358 ('', 'profile', None, _('print command execution profile')),
3359 ('', 'version', None, _('output version information and exit')),
3359 ('', 'version', None, _('output version information and exit')),
3360 ('h', 'help', None, _('display help and exit')),
3360 ('h', 'help', None, _('display help and exit')),
3361 ]
3361 ]
3362
3362
3363 dryrunopts = [('n', 'dry-run', None,
3363 dryrunopts = [('n', 'dry-run', None,
3364 _('do not perform actions, just print output'))]
3364 _('do not perform actions, just print output'))]
3365
3365
3366 remoteopts = [
3366 remoteopts = [
3367 ('e', 'ssh', '', _('specify ssh command to use')),
3367 ('e', 'ssh', '', _('specify ssh command to use')),
3368 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
3368 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
3369 ]
3369 ]
3370
3370
3371 walkopts = [
3371 walkopts = [
3372 ('I', 'include', [], _('include names matching the given patterns')),
3372 ('I', 'include', [], _('include names matching the given patterns')),
3373 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3373 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3374 ]
3374 ]
3375
3375
3376 commitopts = [
3376 commitopts = [
3377 ('m', 'message', '', _('use <text> as commit message')),
3377 ('m', 'message', '', _('use <text> as commit message')),
3378 ('l', 'logfile', '', _('read commit message from <file>')),
3378 ('l', 'logfile', '', _('read commit message from <file>')),
3379 ]
3379 ]
3380
3380
3381 commitopts2 = [
3381 commitopts2 = [
3382 ('d', 'date', '', _('record datecode as commit date')),
3382 ('d', 'date', '', _('record datecode as commit date')),
3383 ('u', 'user', '', _('record the specified user as committer')),
3383 ('u', 'user', '', _('record the specified user as committer')),
3384 ]
3384 ]
3385
3385
3386 templateopts = [
3386 templateopts = [
3387 ('', 'style', '', _('display using template map file')),
3387 ('', 'style', '', _('display using template map file')),
3388 ('', 'template', '', _('display with template')),
3388 ('', 'template', '', _('display with template')),
3389 ]
3389 ]
3390
3390
3391 logopts = [
3391 logopts = [
3392 ('p', 'patch', None, _('show patch')),
3392 ('p', 'patch', None, _('show patch')),
3393 ('g', 'git', None, _('use git extended diff format')),
3393 ('g', 'git', None, _('use git extended diff format')),
3394 ('l', 'limit', '', _('limit number of changes displayed')),
3394 ('l', 'limit', '', _('limit number of changes displayed')),
3395 ('M', 'no-merges', None, _('do not show merges')),
3395 ('M', 'no-merges', None, _('do not show merges')),
3396 ] + templateopts
3396 ] + templateopts
3397
3397
3398 diffopts = [
3398 diffopts = [
3399 ('a', 'text', None, _('treat all files as text')),
3399 ('a', 'text', None, _('treat all files as text')),
3400 ('g', 'git', None, _('use git extended diff format')),
3400 ('g', 'git', None, _('use git extended diff format')),
3401 ('', 'nodates', None, _('omit dates from diff headers'))
3401 ('', 'nodates', None, _('omit dates from diff headers'))
3402 ]
3402 ]
3403
3403
3404 diffopts2 = [
3404 diffopts2 = [
3405 ('p', 'show-function', None, _('show which function each change is in')),
3405 ('p', 'show-function', None, _('show which function each change is in')),
3406 ('', 'reverse', None, _('produce a diff that undoes the changes')),
3406 ('', 'reverse', None, _('produce a diff that undoes the changes')),
3407 ('w', 'ignore-all-space', None,
3407 ('w', 'ignore-all-space', None,
3408 _('ignore white space when comparing lines')),
3408 _('ignore white space when comparing lines')),
3409 ('b', 'ignore-space-change', None,
3409 ('b', 'ignore-space-change', None,
3410 _('ignore changes in the amount of white space')),
3410 _('ignore changes in the amount of white space')),
3411 ('B', 'ignore-blank-lines', None,
3411 ('B', 'ignore-blank-lines', None,
3412 _('ignore changes whose lines are all blank')),
3412 _('ignore changes whose lines are all blank')),
3413 ('U', 'unified', '', _('number of lines of context to show')),
3413 ('U', 'unified', '', _('number of lines of context to show')),
3414 ('', 'stat', None, _('output diffstat-style summary of changes')),
3414 ('', 'stat', None, _('output diffstat-style summary of changes')),
3415 ]
3415 ]
3416
3416
3417 similarityopts = [
3417 similarityopts = [
3418 ('s', 'similarity', '',
3418 ('s', 'similarity', '',
3419 _('guess renamed files by similarity (0<=s<=100)'))
3419 _('guess renamed files by similarity (0<=s<=100)'))
3420 ]
3420 ]
3421
3421
3422 table = {
3422 table = {
3423 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3423 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3424 "addremove":
3424 "addremove":
3425 (addremove, similarityopts + walkopts + dryrunopts,
3425 (addremove, similarityopts + walkopts + dryrunopts,
3426 _('[OPTION]... [FILE]...')),
3426 _('[OPTION]... [FILE]...')),
3427 "^annotate|blame":
3427 "^annotate|blame":
3428 (annotate,
3428 (annotate,
3429 [('r', 'rev', '', _('annotate the specified revision')),
3429 [('r', 'rev', '', _('annotate the specified revision')),
3430 ('', 'follow', None,
3430 ('', 'follow', None,
3431 _('follow copies/renames and list the filename (DEPRECATED)')),
3431 _('follow copies/renames and list the filename (DEPRECATED)')),
3432 ('', 'no-follow', None, _("don't follow copies and renames")),
3432 ('', 'no-follow', None, _("don't follow copies and renames")),
3433 ('a', 'text', None, _('treat all files as text')),
3433 ('a', 'text', None, _('treat all files as text')),
3434 ('u', 'user', None, _('list the author (long with -v)')),
3434 ('u', 'user', None, _('list the author (long with -v)')),
3435 ('f', 'file', None, _('list the filename')),
3435 ('f', 'file', None, _('list the filename')),
3436 ('d', 'date', None, _('list the date (short with -q)')),
3436 ('d', 'date', None, _('list the date (short with -q)')),
3437 ('n', 'number', None, _('list the revision number (default)')),
3437 ('n', 'number', None, _('list the revision number (default)')),
3438 ('c', 'changeset', None, _('list the changeset')),
3438 ('c', 'changeset', None, _('list the changeset')),
3439 ('l', 'line-number', None,
3439 ('l', 'line-number', None,
3440 _('show line number at the first appearance'))
3440 _('show line number at the first appearance'))
3441 ] + walkopts,
3441 ] + walkopts,
3442 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3442 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3443 "archive":
3443 "archive":
3444 (archive,
3444 (archive,
3445 [('', 'no-decode', None, _('do not pass files through decoders')),
3445 [('', 'no-decode', None, _('do not pass files through decoders')),
3446 ('p', 'prefix', '', _('directory prefix for files in archive')),
3446 ('p', 'prefix', '', _('directory prefix for files in archive')),
3447 ('r', 'rev', '', _('revision to distribute')),
3447 ('r', 'rev', '', _('revision to distribute')),
3448 ('t', 'type', '', _('type of distribution to create')),
3448 ('t', 'type', '', _('type of distribution to create')),
3449 ] + walkopts,
3449 ] + walkopts,
3450 _('[OPTION]... DEST')),
3450 _('[OPTION]... DEST')),
3451 "backout":
3451 "backout":
3452 (backout,
3452 (backout,
3453 [('', 'merge', None,
3453 [('', 'merge', None,
3454 _('merge with old dirstate parent after backout')),
3454 _('merge with old dirstate parent after backout')),
3455 ('', 'parent', '', _('parent to choose when backing out merge')),
3455 ('', 'parent', '', _('parent to choose when backing out merge')),
3456 ('r', 'rev', '', _('revision to backout')),
3456 ('r', 'rev', '', _('revision to backout')),
3457 ] + walkopts + commitopts + commitopts2,
3457 ] + walkopts + commitopts + commitopts2,
3458 _('[OPTION]... [-r] REV')),
3458 _('[OPTION]... [-r] REV')),
3459 "bisect":
3459 "bisect":
3460 (bisect,
3460 (bisect,
3461 [('r', 'reset', False, _('reset bisect state')),
3461 [('r', 'reset', False, _('reset bisect state')),
3462 ('g', 'good', False, _('mark changeset good')),
3462 ('g', 'good', False, _('mark changeset good')),
3463 ('b', 'bad', False, _('mark changeset bad')),
3463 ('b', 'bad', False, _('mark changeset bad')),
3464 ('s', 'skip', False, _('skip testing changeset')),
3464 ('s', 'skip', False, _('skip testing changeset')),
3465 ('c', 'command', '', _('use command to check changeset state')),
3465 ('c', 'command', '', _('use command to check changeset state')),
3466 ('U', 'noupdate', False, _('do not update to target'))],
3466 ('U', 'noupdate', False, _('do not update to target'))],
3467 _("[-gbsr] [-U] [-c CMD] [REV]")),
3467 _("[-gbsr] [-U] [-c CMD] [REV]")),
3468 "branch":
3468 "branch":
3469 (branch,
3469 (branch,
3470 [('f', 'force', None,
3470 [('f', 'force', None,
3471 _('set branch name even if it shadows an existing branch')),
3471 _('set branch name even if it shadows an existing branch')),
3472 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3472 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3473 _('[-fC] [NAME]')),
3473 _('[-fC] [NAME]')),
3474 "branches":
3474 "branches":
3475 (branches,
3475 (branches,
3476 [('a', 'active', False,
3476 [('a', 'active', False,
3477 _('show only branches that have unmerged heads')),
3477 _('show only branches that have unmerged heads')),
3478 ('c', 'closed', False,
3478 ('c', 'closed', False,
3479 _('show normal and closed branches'))],
3479 _('show normal and closed branches'))],
3480 _('[-ac]')),
3480 _('[-ac]')),
3481 "bundle":
3481 "bundle":
3482 (bundle,
3482 (bundle,
3483 [('f', 'force', None,
3483 [('f', 'force', None,
3484 _('run even when the destination is unrelated')),
3484 _('run even when the destination is unrelated')),
3485 ('r', 'rev', [],
3485 ('r', 'rev', [],
3486 _('a changeset intended to be added to the destination')),
3486 _('a changeset intended to be added to the destination')),
3487 ('b', 'branch', [],
3487 ('b', 'branch', [],
3488 _('a specific branch you would like to bundle')),
3488 _('a specific branch you would like to bundle')),
3489 ('', 'base', [],
3489 ('', 'base', [],
3490 _('a base changeset assumed to be available at the destination')),
3490 _('a base changeset assumed to be available at the destination')),
3491 ('a', 'all', None, _('bundle all changesets in the repository')),
3491 ('a', 'all', None, _('bundle all changesets in the repository')),
3492 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3492 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3493 ] + remoteopts,
3493 ] + remoteopts,
3494 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3494 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3495 "cat":
3495 "cat":
3496 (cat,
3496 (cat,
3497 [('o', 'output', '', _('print output to file with formatted name')),
3497 [('o', 'output', '', _('print output to file with formatted name')),
3498 ('r', 'rev', '', _('print the given revision')),
3498 ('r', 'rev', '', _('print the given revision')),
3499 ('', 'decode', None, _('apply any matching decode filter')),
3499 ('', 'decode', None, _('apply any matching decode filter')),
3500 ] + walkopts,
3500 ] + walkopts,
3501 _('[OPTION]... FILE...')),
3501 _('[OPTION]... FILE...')),
3502 "^clone":
3502 "^clone":
3503 (clone,
3503 (clone,
3504 [('U', 'noupdate', None,
3504 [('U', 'noupdate', None,
3505 _('the clone will include an empty working copy (only a repository)')),
3505 _('the clone will include an empty working copy (only a repository)')),
3506 ('u', 'updaterev', '',
3506 ('u', 'updaterev', '',
3507 _('revision, tag or branch to check out')),
3507 _('revision, tag or branch to check out')),
3508 ('r', 'rev', [],
3508 ('r', 'rev', [],
3509 _('include the specified changeset')),
3509 _('include the specified changeset')),
3510 ('b', 'branch', [],
3510 ('b', 'branch', [],
3511 _('clone only the specified branch')),
3511 _('clone only the specified branch')),
3512 ('', 'pull', None, _('use pull protocol to copy metadata')),
3512 ('', 'pull', None, _('use pull protocol to copy metadata')),
3513 ('', 'uncompressed', None,
3513 ('', 'uncompressed', None,
3514 _('use uncompressed transfer (fast over LAN)')),
3514 _('use uncompressed transfer (fast over LAN)')),
3515 ] + remoteopts,
3515 ] + remoteopts,
3516 _('[OPTION]... SOURCE [DEST]')),
3516 _('[OPTION]... SOURCE [DEST]')),
3517 "^commit|ci":
3517 "^commit|ci":
3518 (commit,
3518 (commit,
3519 [('A', 'addremove', None,
3519 [('A', 'addremove', None,
3520 _('mark new/missing files as added/removed before committing')),
3520 _('mark new/missing files as added/removed before committing')),
3521 ('', 'close-branch', None,
3521 ('', 'close-branch', None,
3522 _('mark a branch as closed, hiding it from the branch list')),
3522 _('mark a branch as closed, hiding it from the branch list')),
3523 ] + walkopts + commitopts + commitopts2,
3523 ] + walkopts + commitopts + commitopts2,
3524 _('[OPTION]... [FILE]...')),
3524 _('[OPTION]... [FILE]...')),
3525 "copy|cp":
3525 "copy|cp":
3526 (copy,
3526 (copy,
3527 [('A', 'after', None, _('record a copy that has already occurred')),
3527 [('A', 'after', None, _('record a copy that has already occurred')),
3528 ('f', 'force', None,
3528 ('f', 'force', None,
3529 _('forcibly copy over an existing managed file')),
3529 _('forcibly copy over an existing managed file')),
3530 ] + walkopts + dryrunopts,
3530 ] + walkopts + dryrunopts,
3531 _('[OPTION]... [SOURCE]... DEST')),
3531 _('[OPTION]... [SOURCE]... DEST')),
3532 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
3532 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
3533 "debugcheckstate": (debugcheckstate, [], ''),
3533 "debugcheckstate": (debugcheckstate, [], ''),
3534 "debugcommands": (debugcommands, [], _('[COMMAND]')),
3534 "debugcommands": (debugcommands, [], _('[COMMAND]')),
3535 "debugcomplete":
3535 "debugcomplete":
3536 (debugcomplete,
3536 (debugcomplete,
3537 [('o', 'options', None, _('show the command options'))],
3537 [('o', 'options', None, _('show the command options'))],
3538 _('[-o] CMD')),
3538 _('[-o] CMD')),
3539 "debugdate":
3539 "debugdate":
3540 (debugdate,
3540 (debugdate,
3541 [('e', 'extended', None, _('try extended date formats'))],
3541 [('e', 'extended', None, _('try extended date formats'))],
3542 _('[-e] DATE [RANGE]')),
3542 _('[-e] DATE [RANGE]')),
3543 "debugdata": (debugdata, [], _('FILE REV')),
3543 "debugdata": (debugdata, [], _('FILE REV')),
3544 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
3544 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
3545 "debugindex": (debugindex, [], _('FILE')),
3545 "debugindex": (debugindex, [], _('FILE')),
3546 "debugindexdot": (debugindexdot, [], _('FILE')),
3546 "debugindexdot": (debugindexdot, [], _('FILE')),
3547 "debuginstall": (debuginstall, [], ''),
3547 "debuginstall": (debuginstall, [], ''),
3548 "debugrebuildstate":
3548 "debugrebuildstate":
3549 (debugrebuildstate,
3549 (debugrebuildstate,
3550 [('r', 'rev', '', _('revision to rebuild to'))],
3550 [('r', 'rev', '', _('revision to rebuild to'))],
3551 _('[-r REV] [REV]')),
3551 _('[-r REV] [REV]')),
3552 "debugrename":
3552 "debugrename":
3553 (debugrename,
3553 (debugrename,
3554 [('r', 'rev', '', _('revision to debug'))],
3554 [('r', 'rev', '', _('revision to debug'))],
3555 _('[-r REV] FILE')),
3555 _('[-r REV] FILE')),
3556 "debugsetparents":
3556 "debugsetparents":
3557 (debugsetparents, [], _('REV1 [REV2]')),
3557 (debugsetparents, [], _('REV1 [REV2]')),
3558 "debugstate":
3558 "debugstate":
3559 (debugstate,
3559 (debugstate,
3560 [('', 'nodates', None, _('do not display the saved mtime'))],
3560 [('', 'nodates', None, _('do not display the saved mtime'))],
3561 _('[OPTION]...')),
3561 _('[OPTION]...')),
3562 "debugsub":
3562 "debugsub":
3563 (debugsub,
3563 (debugsub,
3564 [('r', 'rev', '', _('revision to check'))],
3564 [('r', 'rev', '', _('revision to check'))],
3565 _('[-r REV] [REV]')),
3565 _('[-r REV] [REV]')),
3566 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
3566 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
3567 "^diff":
3567 "^diff":
3568 (diff,
3568 (diff,
3569 [('r', 'rev', [], _('revision')),
3569 [('r', 'rev', [], _('revision')),
3570 ('c', 'change', '', _('change made by revision'))
3570 ('c', 'change', '', _('change made by revision'))
3571 ] + diffopts + diffopts2 + walkopts,
3571 ] + diffopts + diffopts2 + walkopts,
3572 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
3572 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
3573 "^export":
3573 "^export":
3574 (export,
3574 (export,
3575 [('o', 'output', '', _('print output to file with formatted name')),
3575 [('o', 'output', '', _('print output to file with formatted name')),
3576 ('', 'switch-parent', None, _('diff against the second parent')),
3576 ('', 'switch-parent', None, _('diff against the second parent')),
3577 ('r', 'rev', [], _('revisions to export')),
3577 ('r', 'rev', [], _('revisions to export')),
3578 ] + diffopts,
3578 ] + diffopts,
3579 _('[OPTION]... [-o OUTFILESPEC] REV...')),
3579 _('[OPTION]... [-o OUTFILESPEC] REV...')),
3580 "^forget":
3580 "^forget":
3581 (forget,
3581 (forget,
3582 [] + walkopts,
3582 [] + walkopts,
3583 _('[OPTION]... FILE...')),
3583 _('[OPTION]... FILE...')),
3584 "grep":
3584 "grep":
3585 (grep,
3585 (grep,
3586 [('0', 'print0', None, _('end fields with NUL')),
3586 [('0', 'print0', None, _('end fields with NUL')),
3587 ('', 'all', None, _('print all revisions that match')),
3587 ('', 'all', None, _('print all revisions that match')),
3588 ('f', 'follow', None,
3588 ('f', 'follow', None,
3589 _('follow changeset history,'
3589 _('follow changeset history,'
3590 ' or file history across copies and renames')),
3590 ' or file history across copies and renames')),
3591 ('i', 'ignore-case', None, _('ignore case when matching')),
3591 ('i', 'ignore-case', None, _('ignore case when matching')),
3592 ('l', 'files-with-matches', None,
3592 ('l', 'files-with-matches', None,
3593 _('print only filenames and revisions that match')),
3593 _('print only filenames and revisions that match')),
3594 ('n', 'line-number', None, _('print matching line numbers')),
3594 ('n', 'line-number', None, _('print matching line numbers')),
3595 ('r', 'rev', [], _('search in given revision range')),
3595 ('r', 'rev', [], _('search in given revision range')),
3596 ('u', 'user', None, _('list the author (long with -v)')),
3596 ('u', 'user', None, _('list the author (long with -v)')),
3597 ('d', 'date', None, _('list the date (short with -q)')),
3597 ('d', 'date', None, _('list the date (short with -q)')),
3598 ] + walkopts,
3598 ] + walkopts,
3599 _('[OPTION]... PATTERN [FILE]...')),
3599 _('[OPTION]... PATTERN [FILE]...')),
3600 "heads":
3600 "heads":
3601 (heads,
3601 (heads,
3602 [('r', 'rev', '', _('show only heads which are descendants of REV')),
3602 [('r', 'rev', '', _('show only heads which are descendants of REV')),
3603 ('t', 'topo', False, _('show topological heads only')),
3603 ('t', 'topo', False, _('show topological heads only')),
3604 ('a', 'active', False,
3604 ('a', 'active', False,
3605 _('show active branchheads only [DEPRECATED]')),
3605 _('show active branchheads only [DEPRECATED]')),
3606 ('c', 'closed', False,
3606 ('c', 'closed', False,
3607 _('show normal and closed branch heads')),
3607 _('show normal and closed branch heads')),
3608 ] + templateopts,
3608 ] + templateopts,
3609 _('[-ac] [-r STARTREV] [REV]...')),
3609 _('[-ac] [-r STARTREV] [REV]...')),
3610 "help": (help_, [], _('[TOPIC]')),
3610 "help": (help_, [], _('[TOPIC]')),
3611 "identify|id":
3611 "identify|id":
3612 (identify,
3612 (identify,
3613 [('r', 'rev', '', _('identify the specified revision')),
3613 [('r', 'rev', '', _('identify the specified revision')),
3614 ('n', 'num', None, _('show local revision number')),
3614 ('n', 'num', None, _('show local revision number')),
3615 ('i', 'id', None, _('show global revision id')),
3615 ('i', 'id', None, _('show global revision id')),
3616 ('b', 'branch', None, _('show branch')),
3616 ('b', 'branch', None, _('show branch')),
3617 ('t', 'tags', None, _('show tags'))],
3617 ('t', 'tags', None, _('show tags'))],
3618 _('[-nibt] [-r REV] [SOURCE]')),
3618 _('[-nibt] [-r REV] [SOURCE]')),
3619 "import|patch":
3619 "import|patch":
3620 (import_,
3620 (import_,
3621 [('p', 'strip', 1,
3621 [('p', 'strip', 1,
3622 _('directory strip option for patch. This has the same '
3622 _('directory strip option for patch. This has the same '
3623 'meaning as the corresponding patch option')),
3623 'meaning as the corresponding patch option')),
3624 ('b', 'base', '', _('base path')),
3624 ('b', 'base', '', _('base path')),
3625 ('f', 'force', None,
3625 ('f', 'force', None,
3626 _('skip check for outstanding uncommitted changes')),
3626 _('skip check for outstanding uncommitted changes')),
3627 ('', 'no-commit', None,
3627 ('', 'no-commit', None,
3628 _("don't commit, just update the working directory")),
3628 _("don't commit, just update the working directory")),
3629 ('', 'exact', None,
3629 ('', 'exact', None,
3630 _('apply patch to the nodes from which it was generated')),
3630 _('apply patch to the nodes from which it was generated')),
3631 ('', 'import-branch', None,
3631 ('', 'import-branch', None,
3632 _('use any branch information in patch (implied by --exact)'))] +
3632 _('use any branch information in patch (implied by --exact)'))] +
3633 commitopts + commitopts2 + similarityopts,
3633 commitopts + commitopts2 + similarityopts,
3634 _('[OPTION]... PATCH...')),
3634 _('[OPTION]... PATCH...')),
3635 "incoming|in":
3635 "incoming|in":
3636 (incoming,
3636 (incoming,
3637 [('f', 'force', None,
3637 [('f', 'force', None,
3638 _('run even if remote repository is unrelated')),
3638 _('run even if remote repository is unrelated')),
3639 ('n', 'newest-first', None, _('show newest record first')),
3639 ('n', 'newest-first', None, _('show newest record first')),
3640 ('', 'bundle', '', _('file to store the bundles into')),
3640 ('', 'bundle', '', _('file to store the bundles into')),
3641 ('r', 'rev', [],
3641 ('r', 'rev', [],
3642 _('a remote changeset intended to be added')),
3642 _('a remote changeset intended to be added')),
3643 ('b', 'branch', [],
3643 ('b', 'branch', [],
3644 _('a specific branch you would like to pull')),
3644 _('a specific branch you would like to pull')),
3645 ] + logopts + remoteopts,
3645 ] + logopts + remoteopts,
3646 _('[-p] [-n] [-M] [-f] [-r REV]...'
3646 _('[-p] [-n] [-M] [-f] [-r REV]...'
3647 ' [--bundle FILENAME] [SOURCE]')),
3647 ' [--bundle FILENAME] [SOURCE]')),
3648 "^init":
3648 "^init":
3649 (init,
3649 (init,
3650 remoteopts,
3650 remoteopts,
3651 _('[-e CMD] [--remotecmd CMD] [DEST]')),
3651 _('[-e CMD] [--remotecmd CMD] [DEST]')),
3652 "locate":
3652 "locate":
3653 (locate,
3653 (locate,
3654 [('r', 'rev', '', _('search the repository as it is in REV')),
3654 [('r', 'rev', '', _('search the repository as it is in REV')),
3655 ('0', 'print0', None,
3655 ('0', 'print0', None,
3656 _('end filenames with NUL, for use with xargs')),
3656 _('end filenames with NUL, for use with xargs')),
3657 ('f', 'fullpath', None,
3657 ('f', 'fullpath', None,
3658 _('print complete paths from the filesystem root')),
3658 _('print complete paths from the filesystem root')),
3659 ] + walkopts,
3659 ] + walkopts,
3660 _('[OPTION]... [PATTERN]...')),
3660 _('[OPTION]... [PATTERN]...')),
3661 "^log|history":
3661 "^log|history":
3662 (log,
3662 (log,
3663 [('f', 'follow', None,
3663 [('f', 'follow', None,
3664 _('follow changeset history,'
3664 _('follow changeset history,'
3665 ' or file history across copies and renames')),
3665 ' or file history across copies and renames')),
3666 ('', 'follow-first', None,
3666 ('', 'follow-first', None,
3667 _('only follow the first parent of merge changesets')),
3667 _('only follow the first parent of merge changesets')),
3668 ('d', 'date', '', _('show revisions matching date spec')),
3668 ('d', 'date', '', _('show revisions matching date spec')),
3669 ('C', 'copies', None, _('show copied files')),
3669 ('C', 'copies', None, _('show copied files')),
3670 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3670 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3671 ('r', 'rev', [], _('show the specified revision or range')),
3671 ('r', 'rev', [], _('show the specified revision or range')),
3672 ('', 'removed', None, _('include revisions where files were removed')),
3672 ('', 'removed', None, _('include revisions where files were removed')),
3673 ('m', 'only-merges', None, _('show only merges')),
3673 ('m', 'only-merges', None, _('show only merges')),
3674 ('u', 'user', [], _('revisions committed by user')),
3674 ('u', 'user', [], _('revisions committed by user')),
3675 ('b', 'only-branch', [],
3675 ('b', 'only-branch', [],
3676 _('show only changesets within the given named branch')),
3676 _('show only changesets within the given named branch')),
3677 ('P', 'prune', [],
3677 ('P', 'prune', [],
3678 _('do not display revision or any of its ancestors')),
3678 _('do not display revision or any of its ancestors')),
3679 ] + logopts + walkopts,
3679 ] + logopts + walkopts,
3680 _('[OPTION]... [FILE]')),
3680 _('[OPTION]... [FILE]')),
3681 "manifest":
3681 "manifest":
3682 (manifest,
3682 (manifest,
3683 [('r', 'rev', '', _('revision to display'))],
3683 [('r', 'rev', '', _('revision to display'))],
3684 _('[-r REV]')),
3684 _('[-r REV]')),
3685 "^merge":
3685 "^merge":
3686 (merge,
3686 (merge,
3687 [('f', 'force', None, _('force a merge with outstanding changes')),
3687 [('f', 'force', None, _('force a merge with outstanding changes')),
3688 ('r', 'rev', '', _('revision to merge')),
3688 ('r', 'rev', '', _('revision to merge')),
3689 ('P', 'preview', None,
3689 ('P', 'preview', None,
3690 _('review revisions to merge (no merge is performed)'))],
3690 _('review revisions to merge (no merge is performed)'))],
3691 _('[-P] [-f] [[-r] REV]')),
3691 _('[-P] [-f] [[-r] REV]')),
3692 "outgoing|out":
3692 "outgoing|out":
3693 (outgoing,
3693 (outgoing,
3694 [('f', 'force', None,
3694 [('f', 'force', None,
3695 _('run even when the destination is unrelated')),
3695 _('run even when the destination is unrelated')),
3696 ('r', 'rev', [],
3696 ('r', 'rev', [],
3697 _('a changeset intended to be included in the destination')),
3697 _('a changeset intended to be included in the destination')),
3698 ('n', 'newest-first', None, _('show newest record first')),
3698 ('n', 'newest-first', None, _('show newest record first')),
3699 ('b', 'branch', [],
3699 ('b', 'branch', [],
3700 _('a specific branch you would like to push')),
3700 _('a specific branch you would like to push')),
3701 ] + logopts + remoteopts,
3701 ] + logopts + remoteopts,
3702 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3702 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3703 "parents":
3703 "parents":
3704 (parents,
3704 (parents,
3705 [('r', 'rev', '', _('show parents of the specified revision')),
3705 [('r', 'rev', '', _('show parents of the specified revision')),
3706 ] + templateopts,
3706 ] + templateopts,
3707 _('[-r REV] [FILE]')),
3707 _('[-r REV] [FILE]')),
3708 "paths": (paths, [], _('[NAME]')),
3708 "paths": (paths, [], _('[NAME]')),
3709 "^pull":
3709 "^pull":
3710 (pull,
3710 (pull,
3711 [('u', 'update', None,
3711 [('u', 'update', None,
3712 _('update to new branch head if changesets were pulled')),
3712 _('update to new branch head if changesets were pulled')),
3713 ('f', 'force', None,
3713 ('f', 'force', None,
3714 _('run even when remote repository is unrelated')),
3714 _('run even when remote repository is unrelated')),
3715 ('r', 'rev', [],
3715 ('r', 'rev', [],
3716 _('a remote changeset intended to be added')),
3716 _('a remote changeset intended to be added')),
3717 ('b', 'branch', [],
3717 ('b', 'branch', [],
3718 _('a specific branch you would like to pull')),
3718 _('a specific branch you would like to pull')),
3719 ] + remoteopts,
3719 ] + remoteopts,
3720 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3720 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3721 "^push":
3721 "^push":
3722 (push,
3722 (push,
3723 [('f', 'force', None, _('force push')),
3723 [('f', 'force', None, _('force push')),
3724 ('r', 'rev', [],
3724 ('r', 'rev', [],
3725 _('a changeset intended to be included in the destination')),
3725 _('a changeset intended to be included in the destination')),
3726 ('b', 'branch', [],
3726 ('b', 'branch', [],
3727 _('a specific branch you would like to push')),
3727 _('a specific branch you would like to push')),
3728 ] + remoteopts,
3728 ] + remoteopts,
3729 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3729 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3730 "recover": (recover, []),
3730 "recover": (recover, []),
3731 "^remove|rm":
3731 "^remove|rm":
3732 (remove,
3732 (remove,
3733 [('A', 'after', None, _('record delete for missing files')),
3733 [('A', 'after', None, _('record delete for missing files')),
3734 ('f', 'force', None,
3734 ('f', 'force', None,
3735 _('remove (and delete) file even if added or modified')),
3735 _('remove (and delete) file even if added or modified')),
3736 ] + walkopts,
3736 ] + walkopts,
3737 _('[OPTION]... FILE...')),
3737 _('[OPTION]... FILE...')),
3738 "rename|mv":
3738 "rename|mv":
3739 (rename,
3739 (rename,
3740 [('A', 'after', None, _('record a rename that has already occurred')),
3740 [('A', 'after', None, _('record a rename that has already occurred')),
3741 ('f', 'force', None,
3741 ('f', 'force', None,
3742 _('forcibly copy over an existing managed file')),
3742 _('forcibly copy over an existing managed file')),
3743 ] + walkopts + dryrunopts,
3743 ] + walkopts + dryrunopts,
3744 _('[OPTION]... SOURCE... DEST')),
3744 _('[OPTION]... SOURCE... DEST')),
3745 "resolve":
3745 "resolve":
3746 (resolve,
3746 (resolve,
3747 [('a', 'all', None, _('select all unresolved files')),
3747 [('a', 'all', None, _('select all unresolved files')),
3748 ('l', 'list', None, _('list state of files needing merge')),
3748 ('l', 'list', None, _('list state of files needing merge')),
3749 ('m', 'mark', None, _('mark files as resolved')),
3749 ('m', 'mark', None, _('mark files as resolved')),
3750 ('u', 'unmark', None, _('unmark files as resolved')),
3750 ('u', 'unmark', None, _('unmark files as resolved')),
3751 ('n', 'no-status', None, _('hide status prefix'))]
3751 ('n', 'no-status', None, _('hide status prefix'))]
3752 + walkopts,
3752 + walkopts,
3753 _('[OPTION]... [FILE]...')),
3753 _('[OPTION]... [FILE]...')),
3754 "revert":
3754 "revert":
3755 (revert,
3755 (revert,
3756 [('a', 'all', None, _('revert all changes when no arguments given')),
3756 [('a', 'all', None, _('revert all changes when no arguments given')),
3757 ('d', 'date', '', _('tipmost revision matching date')),
3757 ('d', 'date', '', _('tipmost revision matching date')),
3758 ('r', 'rev', '', _('revert to the specified revision')),
3758 ('r', 'rev', '', _('revert to the specified revision')),
3759 ('', 'no-backup', None, _('do not save backup copies of files')),
3759 ('', 'no-backup', None, _('do not save backup copies of files')),
3760 ] + walkopts + dryrunopts,
3760 ] + walkopts + dryrunopts,
3761 _('[OPTION]... [-r REV] [NAME]...')),
3761 _('[OPTION]... [-r REV] [NAME]...')),
3762 "rollback": (rollback, []),
3762 "rollback": (rollback, []),
3763 "root": (root, []),
3763 "root": (root, []),
3764 "^serve":
3764 "^serve":
3765 (serve,
3765 (serve,
3766 [('A', 'accesslog', '', _('name of access log file to write to')),
3766 [('A', 'accesslog', '', _('name of access log file to write to')),
3767 ('d', 'daemon', None, _('run server in background')),
3767 ('d', 'daemon', None, _('run server in background')),
3768 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3768 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3769 ('E', 'errorlog', '', _('name of error log file to write to')),
3769 ('E', 'errorlog', '', _('name of error log file to write to')),
3770 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3770 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3771 ('a', 'address', '',
3771 ('a', 'address', '',
3772 _('address to listen on (default: all interfaces)')),
3772 _('address to listen on (default: all interfaces)')),
3773 ('', 'prefix', '',
3773 ('', 'prefix', '',
3774 _('prefix path to serve from (default: server root)')),
3774 _('prefix path to serve from (default: server root)')),
3775 ('n', 'name', '',
3775 ('n', 'name', '',
3776 _('name to show in web pages (default: working directory)')),
3776 _('name to show in web pages (default: working directory)')),
3777 ('', 'webdir-conf', '', _('name of the webdir config file'
3777 ('', 'webdir-conf', '', _('name of the webdir config file'
3778 ' (serve more than one repository)')),
3778 ' (serve more than one repository)')),
3779 ('', 'pid-file', '', _('name of file to write process ID to')),
3779 ('', 'pid-file', '', _('name of file to write process ID to')),
3780 ('', 'stdio', None, _('for remote clients')),
3780 ('', 'stdio', None, _('for remote clients')),
3781 ('t', 'templates', '', _('web templates to use')),
3781 ('t', 'templates', '', _('web templates to use')),
3782 ('', 'style', '', _('template style to use')),
3782 ('', 'style', '', _('template style to use')),
3783 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3783 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3784 ('', 'certificate', '', _('SSL certificate file'))],
3784 ('', 'certificate', '', _('SSL certificate file'))],
3785 _('[OPTION]...')),
3785 _('[OPTION]...')),
3786 "showconfig|debugconfig":
3786 "showconfig|debugconfig":
3787 (showconfig,
3787 (showconfig,
3788 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3788 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3789 _('[-u] [NAME]...')),
3789 _('[-u] [NAME]...')),
3790 "^summary|sum":
3790 "^summary|sum":
3791 (summary,
3791 (summary,
3792 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
3792 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
3793 "^status|st":
3793 "^status|st":
3794 (status,
3794 (status,
3795 [('A', 'all', None, _('show status of all files')),
3795 [('A', 'all', None, _('show status of all files')),
3796 ('m', 'modified', None, _('show only modified files')),
3796 ('m', 'modified', None, _('show only modified files')),
3797 ('a', 'added', None, _('show only added files')),
3797 ('a', 'added', None, _('show only added files')),
3798 ('r', 'removed', None, _('show only removed files')),
3798 ('r', 'removed', None, _('show only removed files')),
3799 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3799 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3800 ('c', 'clean', None, _('show only files without changes')),
3800 ('c', 'clean', None, _('show only files without changes')),
3801 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3801 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3802 ('i', 'ignored', None, _('show only ignored files')),
3802 ('i', 'ignored', None, _('show only ignored files')),
3803 ('n', 'no-status', None, _('hide status prefix')),
3803 ('n', 'no-status', None, _('hide status prefix')),
3804 ('C', 'copies', None, _('show source of copied files')),
3804 ('C', 'copies', None, _('show source of copied files')),
3805 ('0', 'print0', None,
3805 ('0', 'print0', None,
3806 _('end filenames with NUL, for use with xargs')),
3806 _('end filenames with NUL, for use with xargs')),
3807 ('', 'rev', [], _('show difference from revision')),
3807 ('', 'rev', [], _('show difference from revision')),
3808 ('', 'change', '', _('list the changed files of a revision')),
3808 ('', 'change', '', _('list the changed files of a revision')),
3809 ] + walkopts,
3809 ] + walkopts,
3810 _('[OPTION]... [FILE]...')),
3810 _('[OPTION]... [FILE]...')),
3811 "tag":
3811 "tag":
3812 (tag,
3812 (tag,
3813 [('f', 'force', None, _('replace existing tag')),
3813 [('f', 'force', None, _('replace existing tag')),
3814 ('l', 'local', None, _('make the tag local')),
3814 ('l', 'local', None, _('make the tag local')),
3815 ('r', 'rev', '', _('revision to tag')),
3815 ('r', 'rev', '', _('revision to tag')),
3816 ('', 'remove', None, _('remove a tag')),
3816 ('', 'remove', None, _('remove a tag')),
3817 # -l/--local is already there, commitopts cannot be used
3817 # -l/--local is already there, commitopts cannot be used
3818 ('m', 'message', '', _('use <text> as commit message')),
3818 ('m', 'message', '', _('use <text> as commit message')),
3819 ] + commitopts2,
3819 ] + commitopts2,
3820 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3820 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3821 "tags": (tags, [], ''),
3821 "tags": (tags, [], ''),
3822 "tip":
3822 "tip":
3823 (tip,
3823 (tip,
3824 [('p', 'patch', None, _('show patch')),
3824 [('p', 'patch', None, _('show patch')),
3825 ('g', 'git', None, _('use git extended diff format')),
3825 ('g', 'git', None, _('use git extended diff format')),
3826 ] + templateopts,
3826 ] + templateopts,
3827 _('[-p] [-g]')),
3827 _('[-p] [-g]')),
3828 "unbundle":
3828 "unbundle":
3829 (unbundle,
3829 (unbundle,
3830 [('u', 'update', None,
3830 [('u', 'update', None,
3831 _('update to new branch head if changesets were unbundled'))],
3831 _('update to new branch head if changesets were unbundled'))],
3832 _('[-u] FILE...')),
3832 _('[-u] FILE...')),
3833 "^update|up|checkout|co":
3833 "^update|up|checkout|co":
3834 (update,
3834 (update,
3835 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
3835 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
3836 ('c', 'check', None, _('check for uncommitted changes')),
3836 ('c', 'check', None, _('check for uncommitted changes')),
3837 ('d', 'date', '', _('tipmost revision matching date')),
3837 ('d', 'date', '', _('tipmost revision matching date')),
3838 ('r', 'rev', '', _('revision'))],
3838 ('r', 'rev', '', _('revision'))],
3839 _('[-c] [-C] [-d DATE] [[-r] REV]')),
3839 _('[-c] [-C] [-d DATE] [[-r] REV]')),
3840 "verify": (verify, []),
3840 "verify": (verify, []),
3841 "version": (version_, []),
3841 "version": (version_, []),
3842 }
3842 }
3843
3843
3844 norepo = ("clone init version help debugcommands debugcomplete debugdata"
3844 norepo = ("clone init version help debugcommands debugcomplete debugdata"
3845 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3845 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3846 optionalrepo = ("identify paths serve showconfig debugancestor")
3846 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,1661 +1,1620 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from i18n import _
9 from i18n import _
10 from node import hex, nullid, short
10 from node import hex, nullid, short
11 import base85, cmdutil, mdiff, util, diffhelpers, copies
11 import base85, cmdutil, mdiff, util, diffhelpers, copies
12 import cStringIO, email.Parser, os, re
12 import cStringIO, email.Parser, os, re
13 import sys, tempfile, zlib
13 import sys, tempfile, zlib
14
14
15 gitre = re.compile('diff --git a/(.*) b/(.*)')
15 gitre = re.compile('diff --git a/(.*) b/(.*)')
16
16
17 class PatchError(Exception):
17 class PatchError(Exception):
18 pass
18 pass
19
19
20 class NoHunks(PatchError):
20 class NoHunks(PatchError):
21 pass
21 pass
22
22
23 # helper functions
23 # helper functions
24
24
25 def copyfile(src, dst, basedir):
25 def copyfile(src, dst, basedir):
26 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
26 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
27 if os.path.exists(absdst):
27 if os.path.exists(absdst):
28 raise util.Abort(_("cannot create %s: destination already exists") %
28 raise util.Abort(_("cannot create %s: destination already exists") %
29 dst)
29 dst)
30
30
31 dstdir = os.path.dirname(absdst)
31 dstdir = os.path.dirname(absdst)
32 if dstdir and not os.path.isdir(dstdir):
32 if dstdir and not os.path.isdir(dstdir):
33 try:
33 try:
34 os.makedirs(dstdir)
34 os.makedirs(dstdir)
35 except IOError:
35 except IOError:
36 raise util.Abort(
36 raise util.Abort(
37 _("cannot create %s: unable to create destination directory")
37 _("cannot create %s: unable to create destination directory")
38 % dst)
38 % dst)
39
39
40 util.copyfile(abssrc, absdst)
40 util.copyfile(abssrc, absdst)
41
41
42 # public functions
42 # public functions
43
43
44 def split(stream):
44 def split(stream):
45 '''return an iterator of individual patches from a stream'''
45 '''return an iterator of individual patches from a stream'''
46 def isheader(line, inheader):
46 def isheader(line, inheader):
47 if inheader and line[0] in (' ', '\t'):
47 if inheader and line[0] in (' ', '\t'):
48 # continuation
48 # continuation
49 return True
49 return True
50 l = line.split(': ', 1)
50 l = line.split(': ', 1)
51 return len(l) == 2 and ' ' not in l[0]
51 return len(l) == 2 and ' ' not in l[0]
52
52
53 def chunk(lines):
53 def chunk(lines):
54 return cStringIO.StringIO(''.join(lines))
54 return cStringIO.StringIO(''.join(lines))
55
55
56 def hgsplit(stream, cur):
56 def hgsplit(stream, cur):
57 inheader = True
57 inheader = True
58
58
59 for line in stream:
59 for line in stream:
60 if not line.strip():
60 if not line.strip():
61 inheader = False
61 inheader = False
62 if not inheader and line.startswith('# HG changeset patch'):
62 if not inheader and line.startswith('# HG changeset patch'):
63 yield chunk(cur)
63 yield chunk(cur)
64 cur = []
64 cur = []
65 inheader = True
65 inheader = True
66
66
67 cur.append(line)
67 cur.append(line)
68
68
69 if cur:
69 if cur:
70 yield chunk(cur)
70 yield chunk(cur)
71
71
72 def mboxsplit(stream, cur):
72 def mboxsplit(stream, cur):
73 for line in stream:
73 for line in stream:
74 if line.startswith('From '):
74 if line.startswith('From '):
75 for c in split(chunk(cur[1:])):
75 for c in split(chunk(cur[1:])):
76 yield c
76 yield c
77 cur = []
77 cur = []
78
78
79 cur.append(line)
79 cur.append(line)
80
80
81 if cur:
81 if cur:
82 for c in split(chunk(cur[1:])):
82 for c in split(chunk(cur[1:])):
83 yield c
83 yield c
84
84
85 def mimesplit(stream, cur):
85 def mimesplit(stream, cur):
86 def msgfp(m):
86 def msgfp(m):
87 fp = cStringIO.StringIO()
87 fp = cStringIO.StringIO()
88 g = email.Generator.Generator(fp, mangle_from_=False)
88 g = email.Generator.Generator(fp, mangle_from_=False)
89 g.flatten(m)
89 g.flatten(m)
90 fp.seek(0)
90 fp.seek(0)
91 return fp
91 return fp
92
92
93 for line in stream:
93 for line in stream:
94 cur.append(line)
94 cur.append(line)
95 c = chunk(cur)
95 c = chunk(cur)
96
96
97 m = email.Parser.Parser().parse(c)
97 m = email.Parser.Parser().parse(c)
98 if not m.is_multipart():
98 if not m.is_multipart():
99 yield msgfp(m)
99 yield msgfp(m)
100 else:
100 else:
101 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
101 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
102 for part in m.walk():
102 for part in m.walk():
103 ct = part.get_content_type()
103 ct = part.get_content_type()
104 if ct not in ok_types:
104 if ct not in ok_types:
105 continue
105 continue
106 yield msgfp(part)
106 yield msgfp(part)
107
107
108 def headersplit(stream, cur):
108 def headersplit(stream, cur):
109 inheader = False
109 inheader = False
110
110
111 for line in stream:
111 for line in stream:
112 if not inheader and isheader(line, inheader):
112 if not inheader and isheader(line, inheader):
113 yield chunk(cur)
113 yield chunk(cur)
114 cur = []
114 cur = []
115 inheader = True
115 inheader = True
116 if inheader and not isheader(line, inheader):
116 if inheader and not isheader(line, inheader):
117 inheader = False
117 inheader = False
118
118
119 cur.append(line)
119 cur.append(line)
120
120
121 if cur:
121 if cur:
122 yield chunk(cur)
122 yield chunk(cur)
123
123
124 def remainder(cur):
124 def remainder(cur):
125 yield chunk(cur)
125 yield chunk(cur)
126
126
127 class fiter(object):
127 class fiter(object):
128 def __init__(self, fp):
128 def __init__(self, fp):
129 self.fp = fp
129 self.fp = fp
130
130
131 def __iter__(self):
131 def __iter__(self):
132 return self
132 return self
133
133
134 def next(self):
134 def next(self):
135 l = self.fp.readline()
135 l = self.fp.readline()
136 if not l:
136 if not l:
137 raise StopIteration
137 raise StopIteration
138 return l
138 return l
139
139
140 inheader = False
140 inheader = False
141 cur = []
141 cur = []
142
142
143 mimeheaders = ['content-type']
143 mimeheaders = ['content-type']
144
144
145 if not hasattr(stream, 'next'):
145 if not hasattr(stream, 'next'):
146 # http responses, for example, have readline but not next
146 # http responses, for example, have readline but not next
147 stream = fiter(stream)
147 stream = fiter(stream)
148
148
149 for line in stream:
149 for line in stream:
150 cur.append(line)
150 cur.append(line)
151 if line.startswith('# HG changeset patch'):
151 if line.startswith('# HG changeset patch'):
152 return hgsplit(stream, cur)
152 return hgsplit(stream, cur)
153 elif line.startswith('From '):
153 elif line.startswith('From '):
154 return mboxsplit(stream, cur)
154 return mboxsplit(stream, cur)
155 elif isheader(line, inheader):
155 elif isheader(line, inheader):
156 inheader = True
156 inheader = True
157 if line.split(':', 1)[0].lower() in mimeheaders:
157 if line.split(':', 1)[0].lower() in mimeheaders:
158 # let email parser handle this
158 # let email parser handle this
159 return mimesplit(stream, cur)
159 return mimesplit(stream, cur)
160 elif line.startswith('--- ') and inheader:
160 elif line.startswith('--- ') and inheader:
161 # No evil headers seen by diff start, split by hand
161 # No evil headers seen by diff start, split by hand
162 return headersplit(stream, cur)
162 return headersplit(stream, cur)
163 # Not enough info, keep reading
163 # Not enough info, keep reading
164
164
165 # if we are here, we have a very plain patch
165 # if we are here, we have a very plain patch
166 return remainder(cur)
166 return remainder(cur)
167
167
168 def extract(ui, fileobj):
168 def extract(ui, fileobj):
169 '''extract patch from data read from fileobj.
169 '''extract patch from data read from fileobj.
170
170
171 patch can be a normal patch or contained in an email message.
171 patch can be a normal patch or contained in an email message.
172
172
173 return tuple (filename, message, user, date, node, p1, p2).
173 return tuple (filename, message, user, date, node, p1, p2).
174 Any item in the returned tuple can be None. If filename is None,
174 Any item in the returned tuple can be None. If filename is None,
175 fileobj did not contain a patch. Caller must unlink filename when done.'''
175 fileobj did not contain a patch. Caller must unlink filename when done.'''
176
176
177 # attempt to detect the start of a patch
177 # attempt to detect the start of a patch
178 # (this heuristic is borrowed from quilt)
178 # (this heuristic is borrowed from quilt)
179 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
179 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
180 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
180 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
181 r'(---|\*\*\*)[ \t])', re.MULTILINE)
181 r'(---|\*\*\*)[ \t])', re.MULTILINE)
182
182
183 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
183 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
184 tmpfp = os.fdopen(fd, 'w')
184 tmpfp = os.fdopen(fd, 'w')
185 try:
185 try:
186 msg = email.Parser.Parser().parse(fileobj)
186 msg = email.Parser.Parser().parse(fileobj)
187
187
188 subject = msg['Subject']
188 subject = msg['Subject']
189 user = msg['From']
189 user = msg['From']
190 if not subject and not user:
190 if not subject and not user:
191 # Not an email, restore parsed headers if any
191 # Not an email, restore parsed headers if any
192 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
192 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
193
193
194 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
194 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
195 # should try to parse msg['Date']
195 # should try to parse msg['Date']
196 date = None
196 date = None
197 nodeid = None
197 nodeid = None
198 branch = None
198 branch = None
199 parents = []
199 parents = []
200
200
201 if subject:
201 if subject:
202 if subject.startswith('[PATCH'):
202 if subject.startswith('[PATCH'):
203 pend = subject.find(']')
203 pend = subject.find(']')
204 if pend >= 0:
204 if pend >= 0:
205 subject = subject[pend + 1:].lstrip()
205 subject = subject[pend + 1:].lstrip()
206 subject = subject.replace('\n\t', ' ')
206 subject = subject.replace('\n\t', ' ')
207 ui.debug('Subject: %s\n' % subject)
207 ui.debug('Subject: %s\n' % subject)
208 if user:
208 if user:
209 ui.debug('From: %s\n' % user)
209 ui.debug('From: %s\n' % user)
210 diffs_seen = 0
210 diffs_seen = 0
211 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
211 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
212 message = ''
212 message = ''
213 for part in msg.walk():
213 for part in msg.walk():
214 content_type = part.get_content_type()
214 content_type = part.get_content_type()
215 ui.debug('Content-Type: %s\n' % content_type)
215 ui.debug('Content-Type: %s\n' % content_type)
216 if content_type not in ok_types:
216 if content_type not in ok_types:
217 continue
217 continue
218 payload = part.get_payload(decode=True)
218 payload = part.get_payload(decode=True)
219 m = diffre.search(payload)
219 m = diffre.search(payload)
220 if m:
220 if m:
221 hgpatch = False
221 hgpatch = False
222 ignoretext = False
222 ignoretext = False
223
223
224 ui.debug('found patch at byte %d\n' % m.start(0))
224 ui.debug('found patch at byte %d\n' % m.start(0))
225 diffs_seen += 1
225 diffs_seen += 1
226 cfp = cStringIO.StringIO()
226 cfp = cStringIO.StringIO()
227 for line in payload[:m.start(0)].splitlines():
227 for line in payload[:m.start(0)].splitlines():
228 if line.startswith('# HG changeset patch'):
228 if line.startswith('# HG changeset patch'):
229 ui.debug('patch generated by hg export\n')
229 ui.debug('patch generated by hg export\n')
230 hgpatch = True
230 hgpatch = True
231 # drop earlier commit message content
231 # drop earlier commit message content
232 cfp.seek(0)
232 cfp.seek(0)
233 cfp.truncate()
233 cfp.truncate()
234 subject = None
234 subject = None
235 elif hgpatch:
235 elif hgpatch:
236 if line.startswith('# User '):
236 if line.startswith('# User '):
237 user = line[7:]
237 user = line[7:]
238 ui.debug('From: %s\n' % user)
238 ui.debug('From: %s\n' % user)
239 elif line.startswith("# Date "):
239 elif line.startswith("# Date "):
240 date = line[7:]
240 date = line[7:]
241 elif line.startswith("# Branch "):
241 elif line.startswith("# Branch "):
242 branch = line[9:]
242 branch = line[9:]
243 elif line.startswith("# Node ID "):
243 elif line.startswith("# Node ID "):
244 nodeid = line[10:]
244 nodeid = line[10:]
245 elif line.startswith("# Parent "):
245 elif line.startswith("# Parent "):
246 parents.append(line[10:])
246 parents.append(line[10:])
247 elif line == '---' and gitsendmail:
247 elif line == '---' and gitsendmail:
248 ignoretext = True
248 ignoretext = True
249 if not line.startswith('# ') and not ignoretext:
249 if not line.startswith('# ') and not ignoretext:
250 cfp.write(line)
250 cfp.write(line)
251 cfp.write('\n')
251 cfp.write('\n')
252 message = cfp.getvalue()
252 message = cfp.getvalue()
253 if tmpfp:
253 if tmpfp:
254 tmpfp.write(payload)
254 tmpfp.write(payload)
255 if not payload.endswith('\n'):
255 if not payload.endswith('\n'):
256 tmpfp.write('\n')
256 tmpfp.write('\n')
257 elif not diffs_seen and message and content_type == 'text/plain':
257 elif not diffs_seen and message and content_type == 'text/plain':
258 message += '\n' + payload
258 message += '\n' + payload
259 except:
259 except:
260 tmpfp.close()
260 tmpfp.close()
261 os.unlink(tmpname)
261 os.unlink(tmpname)
262 raise
262 raise
263
263
264 if subject and not message.startswith(subject):
264 if subject and not message.startswith(subject):
265 message = '%s\n%s' % (subject, message)
265 message = '%s\n%s' % (subject, message)
266 tmpfp.close()
266 tmpfp.close()
267 if not diffs_seen:
267 if not diffs_seen:
268 os.unlink(tmpname)
268 os.unlink(tmpname)
269 return None, message, user, date, branch, None, None, None
269 return None, message, user, date, branch, None, None, None
270 p1 = parents and parents.pop(0) or None
270 p1 = parents and parents.pop(0) or None
271 p2 = parents and parents.pop(0) or None
271 p2 = parents and parents.pop(0) or None
272 return tmpname, message, user, date, branch, nodeid, p1, p2
272 return tmpname, message, user, date, branch, nodeid, p1, p2
273
273
274 GP_PATCH = 1 << 0 # we have to run patch
274 GP_PATCH = 1 << 0 # we have to run patch
275 GP_FILTER = 1 << 1 # there's some copy/rename operation
275 GP_FILTER = 1 << 1 # there's some copy/rename operation
276 GP_BINARY = 1 << 2 # there's a binary patch
276 GP_BINARY = 1 << 2 # there's a binary patch
277
277
278 class patchmeta(object):
278 class patchmeta(object):
279 """Patched file metadata
279 """Patched file metadata
280
280
281 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
281 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
282 or COPY. 'path' is patched file path. 'oldpath' is set to the
282 or COPY. 'path' is patched file path. 'oldpath' is set to the
283 origin file when 'op' is either COPY or RENAME, None otherwise. If
283 origin file when 'op' is either COPY or RENAME, None otherwise. If
284 file mode is changed, 'mode' is a tuple (islink, isexec) where
284 file mode is changed, 'mode' is a tuple (islink, isexec) where
285 'islink' is True if the file is a symlink and 'isexec' is True if
285 'islink' is True if the file is a symlink and 'isexec' is True if
286 the file is executable. Otherwise, 'mode' is None.
286 the file is executable. Otherwise, 'mode' is None.
287 """
287 """
288 def __init__(self, path):
288 def __init__(self, path):
289 self.path = path
289 self.path = path
290 self.oldpath = None
290 self.oldpath = None
291 self.mode = None
291 self.mode = None
292 self.op = 'MODIFY'
292 self.op = 'MODIFY'
293 self.lineno = 0
293 self.lineno = 0
294 self.binary = False
294 self.binary = False
295
295
296 def setmode(self, mode):
296 def setmode(self, mode):
297 islink = mode & 020000
297 islink = mode & 020000
298 isexec = mode & 0100
298 isexec = mode & 0100
299 self.mode = (islink, isexec)
299 self.mode = (islink, isexec)
300
300
301 def readgitpatch(lr):
301 def readgitpatch(lr):
302 """extract git-style metadata about patches from <patchname>"""
302 """extract git-style metadata about patches from <patchname>"""
303
303
304 # Filter patch for git information
304 # Filter patch for git information
305 gp = None
305 gp = None
306 gitpatches = []
306 gitpatches = []
307 # Can have a git patch with only metadata, causing patch to complain
307 # Can have a git patch with only metadata, causing patch to complain
308 dopatch = 0
308 dopatch = 0
309
309
310 lineno = 0
310 lineno = 0
311 for line in lr:
311 for line in lr:
312 lineno += 1
312 lineno += 1
313 line = line.rstrip(' \r\n')
313 line = line.rstrip(' \r\n')
314 if line.startswith('diff --git'):
314 if line.startswith('diff --git'):
315 m = gitre.match(line)
315 m = gitre.match(line)
316 if m:
316 if m:
317 if gp:
317 if gp:
318 gitpatches.append(gp)
318 gitpatches.append(gp)
319 dst = m.group(2)
319 dst = m.group(2)
320 gp = patchmeta(dst)
320 gp = patchmeta(dst)
321 gp.lineno = lineno
321 gp.lineno = lineno
322 elif gp:
322 elif gp:
323 if line.startswith('--- '):
323 if line.startswith('--- '):
324 if gp.op in ('COPY', 'RENAME'):
324 if gp.op in ('COPY', 'RENAME'):
325 dopatch |= GP_FILTER
325 dopatch |= GP_FILTER
326 gitpatches.append(gp)
326 gitpatches.append(gp)
327 gp = None
327 gp = None
328 dopatch |= GP_PATCH
328 dopatch |= GP_PATCH
329 continue
329 continue
330 if line.startswith('rename from '):
330 if line.startswith('rename from '):
331 gp.op = 'RENAME'
331 gp.op = 'RENAME'
332 gp.oldpath = line[12:]
332 gp.oldpath = line[12:]
333 elif line.startswith('rename to '):
333 elif line.startswith('rename to '):
334 gp.path = line[10:]
334 gp.path = line[10:]
335 elif line.startswith('copy from '):
335 elif line.startswith('copy from '):
336 gp.op = 'COPY'
336 gp.op = 'COPY'
337 gp.oldpath = line[10:]
337 gp.oldpath = line[10:]
338 elif line.startswith('copy to '):
338 elif line.startswith('copy to '):
339 gp.path = line[8:]
339 gp.path = line[8:]
340 elif line.startswith('deleted file'):
340 elif line.startswith('deleted file'):
341 gp.op = 'DELETE'
341 gp.op = 'DELETE'
342 # is the deleted file a symlink?
342 # is the deleted file a symlink?
343 gp.setmode(int(line[-6:], 8))
343 gp.setmode(int(line[-6:], 8))
344 elif line.startswith('new file mode '):
344 elif line.startswith('new file mode '):
345 gp.op = 'ADD'
345 gp.op = 'ADD'
346 gp.setmode(int(line[-6:], 8))
346 gp.setmode(int(line[-6:], 8))
347 elif line.startswith('new mode '):
347 elif line.startswith('new mode '):
348 gp.setmode(int(line[-6:], 8))
348 gp.setmode(int(line[-6:], 8))
349 elif line.startswith('GIT binary patch'):
349 elif line.startswith('GIT binary patch'):
350 dopatch |= GP_BINARY
350 dopatch |= GP_BINARY
351 gp.binary = True
351 gp.binary = True
352 if gp:
352 if gp:
353 gitpatches.append(gp)
353 gitpatches.append(gp)
354
354
355 if not gitpatches:
355 if not gitpatches:
356 dopatch = GP_PATCH
356 dopatch = GP_PATCH
357
357
358 return (dopatch, gitpatches)
358 return (dopatch, gitpatches)
359
359
360 class linereader(object):
360 class linereader(object):
361 # simple class to allow pushing lines back into the input stream
361 # simple class to allow pushing lines back into the input stream
362 def __init__(self, fp, textmode=False):
362 def __init__(self, fp, textmode=False):
363 self.fp = fp
363 self.fp = fp
364 self.buf = []
364 self.buf = []
365 self.textmode = textmode
365 self.textmode = textmode
366 self.eol = None
366 self.eol = None
367
367
368 def push(self, line):
368 def push(self, line):
369 if line is not None:
369 if line is not None:
370 self.buf.append(line)
370 self.buf.append(line)
371
371
372 def readline(self):
372 def readline(self):
373 if self.buf:
373 if self.buf:
374 l = self.buf[0]
374 l = self.buf[0]
375 del self.buf[0]
375 del self.buf[0]
376 return l
376 return l
377 l = self.fp.readline()
377 l = self.fp.readline()
378 if not self.eol:
378 if not self.eol:
379 if l.endswith('\r\n'):
379 if l.endswith('\r\n'):
380 self.eol = '\r\n'
380 self.eol = '\r\n'
381 elif l.endswith('\n'):
381 elif l.endswith('\n'):
382 self.eol = '\n'
382 self.eol = '\n'
383 if self.textmode and l.endswith('\r\n'):
383 if self.textmode and l.endswith('\r\n'):
384 l = l[:-2] + '\n'
384 l = l[:-2] + '\n'
385 return l
385 return l
386
386
387 def __iter__(self):
387 def __iter__(self):
388 while 1:
388 while 1:
389 l = self.readline()
389 l = self.readline()
390 if not l:
390 if not l:
391 break
391 break
392 yield l
392 yield l
393
393
394 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
394 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
395 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
395 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
396 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
396 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
397 eolmodes = ['strict', 'crlf', 'lf', 'auto']
397 eolmodes = ['strict', 'crlf', 'lf', 'auto']
398
398
399 class patchfile(object):
399 class patchfile(object):
400 def __init__(self, ui, fname, opener, missing=False, eolmode='strict'):
400 def __init__(self, ui, fname, opener, missing=False, eolmode='strict'):
401 self.fname = fname
401 self.fname = fname
402 self.eolmode = eolmode
402 self.eolmode = eolmode
403 self.eol = None
403 self.eol = None
404 self.opener = opener
404 self.opener = opener
405 self.ui = ui
405 self.ui = ui
406 self.lines = []
406 self.lines = []
407 self.exists = False
407 self.exists = False
408 self.missing = missing
408 self.missing = missing
409 if not missing:
409 if not missing:
410 try:
410 try:
411 self.lines = self.readlines(fname)
411 self.lines = self.readlines(fname)
412 self.exists = True
412 self.exists = True
413 except IOError:
413 except IOError:
414 pass
414 pass
415 else:
415 else:
416 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
416 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
417
417
418 self.hash = {}
418 self.hash = {}
419 self.dirty = 0
419 self.dirty = 0
420 self.offset = 0
420 self.offset = 0
421 self.skew = 0
421 self.skew = 0
422 self.rej = []
422 self.rej = []
423 self.fileprinted = False
423 self.fileprinted = False
424 self.printfile(False)
424 self.printfile(False)
425 self.hunks = 0
425 self.hunks = 0
426
426
427 def readlines(self, fname):
427 def readlines(self, fname):
428 if os.path.islink(fname):
428 if os.path.islink(fname):
429 return [os.readlink(fname)]
429 return [os.readlink(fname)]
430 fp = self.opener(fname, 'r')
430 fp = self.opener(fname, 'r')
431 try:
431 try:
432 lr = linereader(fp, self.eolmode != 'strict')
432 lr = linereader(fp, self.eolmode != 'strict')
433 lines = list(lr)
433 lines = list(lr)
434 self.eol = lr.eol
434 self.eol = lr.eol
435 return lines
435 return lines
436 finally:
436 finally:
437 fp.close()
437 fp.close()
438
438
439 def writelines(self, fname, lines):
439 def writelines(self, fname, lines):
440 # Ensure supplied data ends in fname, being a regular file or
440 # Ensure supplied data ends in fname, being a regular file or
441 # a symlink. updatedir() will -too magically- take care of
441 # a symlink. updatedir() will -too magically- take care of
442 # setting it to the proper type afterwards.
442 # setting it to the proper type afterwards.
443 islink = os.path.islink(fname)
443 islink = os.path.islink(fname)
444 if islink:
444 if islink:
445 fp = cStringIO.StringIO()
445 fp = cStringIO.StringIO()
446 else:
446 else:
447 fp = self.opener(fname, 'w')
447 fp = self.opener(fname, 'w')
448 try:
448 try:
449 if self.eolmode == 'auto':
449 if self.eolmode == 'auto':
450 eol = self.eol
450 eol = self.eol
451 elif self.eolmode == 'crlf':
451 elif self.eolmode == 'crlf':
452 eol = '\r\n'
452 eol = '\r\n'
453 else:
453 else:
454 eol = '\n'
454 eol = '\n'
455
455
456 if self.eolmode != 'strict' and eol and eol != '\n':
456 if self.eolmode != 'strict' and eol and eol != '\n':
457 for l in lines:
457 for l in lines:
458 if l and l[-1] == '\n':
458 if l and l[-1] == '\n':
459 l = l[:-1] + eol
459 l = l[:-1] + eol
460 fp.write(l)
460 fp.write(l)
461 else:
461 else:
462 fp.writelines(lines)
462 fp.writelines(lines)
463 if islink:
463 if islink:
464 self.opener.symlink(fp.getvalue(), fname)
464 self.opener.symlink(fp.getvalue(), fname)
465 finally:
465 finally:
466 fp.close()
466 fp.close()
467
467
468 def unlink(self, fname):
468 def unlink(self, fname):
469 os.unlink(fname)
469 os.unlink(fname)
470
470
471 def printfile(self, warn):
471 def printfile(self, warn):
472 if self.fileprinted:
472 if self.fileprinted:
473 return
473 return
474 if warn or self.ui.verbose:
474 if warn or self.ui.verbose:
475 self.fileprinted = True
475 self.fileprinted = True
476 s = _("patching file %s\n") % self.fname
476 s = _("patching file %s\n") % self.fname
477 if warn:
477 if warn:
478 self.ui.warn(s)
478 self.ui.warn(s)
479 else:
479 else:
480 self.ui.note(s)
480 self.ui.note(s)
481
481
482
482
483 def findlines(self, l, linenum):
483 def findlines(self, l, linenum):
484 # looks through the hash and finds candidate lines. The
484 # looks through the hash and finds candidate lines. The
485 # result is a list of line numbers sorted based on distance
485 # result is a list of line numbers sorted based on distance
486 # from linenum
486 # from linenum
487
487
488 cand = self.hash.get(l, [])
488 cand = self.hash.get(l, [])
489 if len(cand) > 1:
489 if len(cand) > 1:
490 # resort our list of potentials forward then back.
490 # resort our list of potentials forward then back.
491 cand.sort(key=lambda x: abs(x - linenum))
491 cand.sort(key=lambda x: abs(x - linenum))
492 return cand
492 return cand
493
493
494 def hashlines(self):
494 def hashlines(self):
495 self.hash = {}
495 self.hash = {}
496 for x, s in enumerate(self.lines):
496 for x, s in enumerate(self.lines):
497 self.hash.setdefault(s, []).append(x)
497 self.hash.setdefault(s, []).append(x)
498
498
499 def write_rej(self):
499 def write_rej(self):
500 # our rejects are a little different from patch(1). This always
500 # our rejects are a little different from patch(1). This always
501 # creates rejects in the same form as the original patch. A file
501 # creates rejects in the same form as the original patch. A file
502 # header is inserted so that you can run the reject through patch again
502 # header is inserted so that you can run the reject through patch again
503 # without having to type the filename.
503 # without having to type the filename.
504
504
505 if not self.rej:
505 if not self.rej:
506 return
506 return
507
507
508 fname = self.fname + ".rej"
508 fname = self.fname + ".rej"
509 self.ui.warn(
509 self.ui.warn(
510 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
510 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
511 (len(self.rej), self.hunks, fname))
511 (len(self.rej), self.hunks, fname))
512
512
513 def rejlines():
513 def rejlines():
514 base = os.path.basename(self.fname)
514 base = os.path.basename(self.fname)
515 yield "--- %s\n+++ %s\n" % (base, base)
515 yield "--- %s\n+++ %s\n" % (base, base)
516 for x in self.rej:
516 for x in self.rej:
517 for l in x.hunk:
517 for l in x.hunk:
518 yield l
518 yield l
519 if l[-1] != '\n':
519 if l[-1] != '\n':
520 yield "\n\ No newline at end of file\n"
520 yield "\n\ No newline at end of file\n"
521
521
522 self.writelines(fname, rejlines())
522 self.writelines(fname, rejlines())
523
523
524 def write(self, dest=None):
524 def write(self, dest=None):
525 if not self.dirty:
525 if not self.dirty:
526 return
526 return
527 if not dest:
527 if not dest:
528 dest = self.fname
528 dest = self.fname
529 self.writelines(dest, self.lines)
529 self.writelines(dest, self.lines)
530
530
531 def close(self):
531 def close(self):
532 self.write()
532 self.write()
533 self.write_rej()
533 self.write_rej()
534
534
535 def apply(self, h):
535 def apply(self, h):
536 if not h.complete():
536 if not h.complete():
537 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
537 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
538 (h.number, h.desc, len(h.a), h.lena, len(h.b),
538 (h.number, h.desc, len(h.a), h.lena, len(h.b),
539 h.lenb))
539 h.lenb))
540
540
541 self.hunks += 1
541 self.hunks += 1
542
542
543 if self.missing:
543 if self.missing:
544 self.rej.append(h)
544 self.rej.append(h)
545 return -1
545 return -1
546
546
547 if self.exists and h.createfile():
547 if self.exists and h.createfile():
548 self.ui.warn(_("file %s already exists\n") % self.fname)
548 self.ui.warn(_("file %s already exists\n") % self.fname)
549 self.rej.append(h)
549 self.rej.append(h)
550 return -1
550 return -1
551
551
552 if isinstance(h, binhunk):
552 if isinstance(h, binhunk):
553 if h.rmfile():
553 if h.rmfile():
554 self.unlink(self.fname)
554 self.unlink(self.fname)
555 else:
555 else:
556 self.lines[:] = h.new()
556 self.lines[:] = h.new()
557 self.offset += len(h.new())
557 self.offset += len(h.new())
558 self.dirty = 1
558 self.dirty = 1
559 return 0
559 return 0
560
560
561 horig = h
561 horig = h
562 if (self.eolmode in ('crlf', 'lf')
562 if (self.eolmode in ('crlf', 'lf')
563 or self.eolmode == 'auto' and self.eol):
563 or self.eolmode == 'auto' and self.eol):
564 # If new eols are going to be normalized, then normalize
564 # If new eols are going to be normalized, then normalize
565 # hunk data before patching. Otherwise, preserve input
565 # hunk data before patching. Otherwise, preserve input
566 # line-endings.
566 # line-endings.
567 h = h.getnormalized()
567 h = h.getnormalized()
568
568
569 # fast case first, no offsets, no fuzz
569 # fast case first, no offsets, no fuzz
570 old = h.old()
570 old = h.old()
571 # patch starts counting at 1 unless we are adding the file
571 # patch starts counting at 1 unless we are adding the file
572 if h.starta == 0:
572 if h.starta == 0:
573 start = 0
573 start = 0
574 else:
574 else:
575 start = h.starta + self.offset - 1
575 start = h.starta + self.offset - 1
576 orig_start = start
576 orig_start = start
577 # if there's skew we want to emit the "(offset %d lines)" even
577 # if there's skew we want to emit the "(offset %d lines)" even
578 # when the hunk cleanly applies at start + skew, so skip the
578 # when the hunk cleanly applies at start + skew, so skip the
579 # fast case code
579 # fast case code
580 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
580 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
581 if h.rmfile():
581 if h.rmfile():
582 self.unlink(self.fname)
582 self.unlink(self.fname)
583 else:
583 else:
584 self.lines[start : start + h.lena] = h.new()
584 self.lines[start : start + h.lena] = h.new()
585 self.offset += h.lenb - h.lena
585 self.offset += h.lenb - h.lena
586 self.dirty = 1
586 self.dirty = 1
587 return 0
587 return 0
588
588
589 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
589 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
590 self.hashlines()
590 self.hashlines()
591 if h.hunk[-1][0] != ' ':
591 if h.hunk[-1][0] != ' ':
592 # if the hunk tried to put something at the bottom of the file
592 # if the hunk tried to put something at the bottom of the file
593 # override the start line and use eof here
593 # override the start line and use eof here
594 search_start = len(self.lines)
594 search_start = len(self.lines)
595 else:
595 else:
596 search_start = orig_start + self.skew
596 search_start = orig_start + self.skew
597
597
598 for fuzzlen in xrange(3):
598 for fuzzlen in xrange(3):
599 for toponly in [True, False]:
599 for toponly in [True, False]:
600 old = h.old(fuzzlen, toponly)
600 old = h.old(fuzzlen, toponly)
601
601
602 cand = self.findlines(old[0][1:], search_start)
602 cand = self.findlines(old[0][1:], search_start)
603 for l in cand:
603 for l in cand:
604 if diffhelpers.testhunk(old, self.lines, l) == 0:
604 if diffhelpers.testhunk(old, self.lines, l) == 0:
605 newlines = h.new(fuzzlen, toponly)
605 newlines = h.new(fuzzlen, toponly)
606 self.lines[l : l + len(old)] = newlines
606 self.lines[l : l + len(old)] = newlines
607 self.offset += len(newlines) - len(old)
607 self.offset += len(newlines) - len(old)
608 self.skew = l - orig_start
608 self.skew = l - orig_start
609 self.dirty = 1
609 self.dirty = 1
610 offset = l - orig_start - fuzzlen
610 offset = l - orig_start - fuzzlen
611 if fuzzlen:
611 if fuzzlen:
612 msg = _("Hunk #%d succeeded at %d "
612 msg = _("Hunk #%d succeeded at %d "
613 "with fuzz %d "
613 "with fuzz %d "
614 "(offset %d lines).\n")
614 "(offset %d lines).\n")
615 self.printfile(True)
615 self.printfile(True)
616 self.ui.warn(msg %
616 self.ui.warn(msg %
617 (h.number, l + 1, fuzzlen, offset))
617 (h.number, l + 1, fuzzlen, offset))
618 else:
618 else:
619 msg = _("Hunk #%d succeeded at %d "
619 msg = _("Hunk #%d succeeded at %d "
620 "(offset %d lines).\n")
620 "(offset %d lines).\n")
621 self.ui.note(msg % (h.number, l + 1, offset))
621 self.ui.note(msg % (h.number, l + 1, offset))
622 return fuzzlen
622 return fuzzlen
623 self.printfile(True)
623 self.printfile(True)
624 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
624 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
625 self.rej.append(horig)
625 self.rej.append(horig)
626 return -1
626 return -1
627
627
628 class hunk(object):
628 class hunk(object):
629 def __init__(self, desc, num, lr, context, create=False, remove=False):
629 def __init__(self, desc, num, lr, context, create=False, remove=False):
630 self.number = num
630 self.number = num
631 self.desc = desc
631 self.desc = desc
632 self.hunk = [desc]
632 self.hunk = [desc]
633 self.a = []
633 self.a = []
634 self.b = []
634 self.b = []
635 self.starta = self.lena = None
635 self.starta = self.lena = None
636 self.startb = self.lenb = None
636 self.startb = self.lenb = None
637 if lr is not None:
637 if lr is not None:
638 if context:
638 if context:
639 self.read_context_hunk(lr)
639 self.read_context_hunk(lr)
640 else:
640 else:
641 self.read_unified_hunk(lr)
641 self.read_unified_hunk(lr)
642 self.create = create
642 self.create = create
643 self.remove = remove and not create
643 self.remove = remove and not create
644
644
645 def getnormalized(self):
645 def getnormalized(self):
646 """Return a copy with line endings normalized to LF."""
646 """Return a copy with line endings normalized to LF."""
647
647
648 def normalize(lines):
648 def normalize(lines):
649 nlines = []
649 nlines = []
650 for line in lines:
650 for line in lines:
651 if line.endswith('\r\n'):
651 if line.endswith('\r\n'):
652 line = line[:-2] + '\n'
652 line = line[:-2] + '\n'
653 nlines.append(line)
653 nlines.append(line)
654 return nlines
654 return nlines
655
655
656 # Dummy object, it is rebuilt manually
656 # Dummy object, it is rebuilt manually
657 nh = hunk(self.desc, self.number, None, None, False, False)
657 nh = hunk(self.desc, self.number, None, None, False, False)
658 nh.number = self.number
658 nh.number = self.number
659 nh.desc = self.desc
659 nh.desc = self.desc
660 nh.hunk = self.hunk
660 nh.hunk = self.hunk
661 nh.a = normalize(self.a)
661 nh.a = normalize(self.a)
662 nh.b = normalize(self.b)
662 nh.b = normalize(self.b)
663 nh.starta = self.starta
663 nh.starta = self.starta
664 nh.startb = self.startb
664 nh.startb = self.startb
665 nh.lena = self.lena
665 nh.lena = self.lena
666 nh.lenb = self.lenb
666 nh.lenb = self.lenb
667 nh.create = self.create
667 nh.create = self.create
668 nh.remove = self.remove
668 nh.remove = self.remove
669 return nh
669 return nh
670
670
671 def read_unified_hunk(self, lr):
671 def read_unified_hunk(self, lr):
672 m = unidesc.match(self.desc)
672 m = unidesc.match(self.desc)
673 if not m:
673 if not m:
674 raise PatchError(_("bad hunk #%d") % self.number)
674 raise PatchError(_("bad hunk #%d") % self.number)
675 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
675 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
676 if self.lena is None:
676 if self.lena is None:
677 self.lena = 1
677 self.lena = 1
678 else:
678 else:
679 self.lena = int(self.lena)
679 self.lena = int(self.lena)
680 if self.lenb is None:
680 if self.lenb is None:
681 self.lenb = 1
681 self.lenb = 1
682 else:
682 else:
683 self.lenb = int(self.lenb)
683 self.lenb = int(self.lenb)
684 self.starta = int(self.starta)
684 self.starta = int(self.starta)
685 self.startb = int(self.startb)
685 self.startb = int(self.startb)
686 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
686 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
687 # if we hit eof before finishing out the hunk, the last line will
687 # if we hit eof before finishing out the hunk, the last line will
688 # be zero length. Lets try to fix it up.
688 # be zero length. Lets try to fix it up.
689 while len(self.hunk[-1]) == 0:
689 while len(self.hunk[-1]) == 0:
690 del self.hunk[-1]
690 del self.hunk[-1]
691 del self.a[-1]
691 del self.a[-1]
692 del self.b[-1]
692 del self.b[-1]
693 self.lena -= 1
693 self.lena -= 1
694 self.lenb -= 1
694 self.lenb -= 1
695
695
696 def read_context_hunk(self, lr):
696 def read_context_hunk(self, lr):
697 self.desc = lr.readline()
697 self.desc = lr.readline()
698 m = contextdesc.match(self.desc)
698 m = contextdesc.match(self.desc)
699 if not m:
699 if not m:
700 raise PatchError(_("bad hunk #%d") % self.number)
700 raise PatchError(_("bad hunk #%d") % self.number)
701 foo, self.starta, foo2, aend, foo3 = m.groups()
701 foo, self.starta, foo2, aend, foo3 = m.groups()
702 self.starta = int(self.starta)
702 self.starta = int(self.starta)
703 if aend is None:
703 if aend is None:
704 aend = self.starta
704 aend = self.starta
705 self.lena = int(aend) - self.starta
705 self.lena = int(aend) - self.starta
706 if self.starta:
706 if self.starta:
707 self.lena += 1
707 self.lena += 1
708 for x in xrange(self.lena):
708 for x in xrange(self.lena):
709 l = lr.readline()
709 l = lr.readline()
710 if l.startswith('---'):
710 if l.startswith('---'):
711 lr.push(l)
711 lr.push(l)
712 break
712 break
713 s = l[2:]
713 s = l[2:]
714 if l.startswith('- ') or l.startswith('! '):
714 if l.startswith('- ') or l.startswith('! '):
715 u = '-' + s
715 u = '-' + s
716 elif l.startswith(' '):
716 elif l.startswith(' '):
717 u = ' ' + s
717 u = ' ' + s
718 else:
718 else:
719 raise PatchError(_("bad hunk #%d old text line %d") %
719 raise PatchError(_("bad hunk #%d old text line %d") %
720 (self.number, x))
720 (self.number, x))
721 self.a.append(u)
721 self.a.append(u)
722 self.hunk.append(u)
722 self.hunk.append(u)
723
723
724 l = lr.readline()
724 l = lr.readline()
725 if l.startswith('\ '):
725 if l.startswith('\ '):
726 s = self.a[-1][:-1]
726 s = self.a[-1][:-1]
727 self.a[-1] = s
727 self.a[-1] = s
728 self.hunk[-1] = s
728 self.hunk[-1] = s
729 l = lr.readline()
729 l = lr.readline()
730 m = contextdesc.match(l)
730 m = contextdesc.match(l)
731 if not m:
731 if not m:
732 raise PatchError(_("bad hunk #%d") % self.number)
732 raise PatchError(_("bad hunk #%d") % self.number)
733 foo, self.startb, foo2, bend, foo3 = m.groups()
733 foo, self.startb, foo2, bend, foo3 = m.groups()
734 self.startb = int(self.startb)
734 self.startb = int(self.startb)
735 if bend is None:
735 if bend is None:
736 bend = self.startb
736 bend = self.startb
737 self.lenb = int(bend) - self.startb
737 self.lenb = int(bend) - self.startb
738 if self.startb:
738 if self.startb:
739 self.lenb += 1
739 self.lenb += 1
740 hunki = 1
740 hunki = 1
741 for x in xrange(self.lenb):
741 for x in xrange(self.lenb):
742 l = lr.readline()
742 l = lr.readline()
743 if l.startswith('\ '):
743 if l.startswith('\ '):
744 s = self.b[-1][:-1]
744 s = self.b[-1][:-1]
745 self.b[-1] = s
745 self.b[-1] = s
746 self.hunk[hunki - 1] = s
746 self.hunk[hunki - 1] = s
747 continue
747 continue
748 if not l:
748 if not l:
749 lr.push(l)
749 lr.push(l)
750 break
750 break
751 s = l[2:]
751 s = l[2:]
752 if l.startswith('+ ') or l.startswith('! '):
752 if l.startswith('+ ') or l.startswith('! '):
753 u = '+' + s
753 u = '+' + s
754 elif l.startswith(' '):
754 elif l.startswith(' '):
755 u = ' ' + s
755 u = ' ' + s
756 elif len(self.b) == 0:
756 elif len(self.b) == 0:
757 # this can happen when the hunk does not add any lines
757 # this can happen when the hunk does not add any lines
758 lr.push(l)
758 lr.push(l)
759 break
759 break
760 else:
760 else:
761 raise PatchError(_("bad hunk #%d old text line %d") %
761 raise PatchError(_("bad hunk #%d old text line %d") %
762 (self.number, x))
762 (self.number, x))
763 self.b.append(s)
763 self.b.append(s)
764 while True:
764 while True:
765 if hunki >= len(self.hunk):
765 if hunki >= len(self.hunk):
766 h = ""
766 h = ""
767 else:
767 else:
768 h = self.hunk[hunki]
768 h = self.hunk[hunki]
769 hunki += 1
769 hunki += 1
770 if h == u:
770 if h == u:
771 break
771 break
772 elif h.startswith('-'):
772 elif h.startswith('-'):
773 continue
773 continue
774 else:
774 else:
775 self.hunk.insert(hunki - 1, u)
775 self.hunk.insert(hunki - 1, u)
776 break
776 break
777
777
778 if not self.a:
778 if not self.a:
779 # this happens when lines were only added to the hunk
779 # this happens when lines were only added to the hunk
780 for x in self.hunk:
780 for x in self.hunk:
781 if x.startswith('-') or x.startswith(' '):
781 if x.startswith('-') or x.startswith(' '):
782 self.a.append(x)
782 self.a.append(x)
783 if not self.b:
783 if not self.b:
784 # this happens when lines were only deleted from the hunk
784 # this happens when lines were only deleted from the hunk
785 for x in self.hunk:
785 for x in self.hunk:
786 if x.startswith('+') or x.startswith(' '):
786 if x.startswith('+') or x.startswith(' '):
787 self.b.append(x[1:])
787 self.b.append(x[1:])
788 # @@ -start,len +start,len @@
788 # @@ -start,len +start,len @@
789 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
789 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
790 self.startb, self.lenb)
790 self.startb, self.lenb)
791 self.hunk[0] = self.desc
791 self.hunk[0] = self.desc
792
792
793 def fix_newline(self):
793 def fix_newline(self):
794 diffhelpers.fix_newline(self.hunk, self.a, self.b)
794 diffhelpers.fix_newline(self.hunk, self.a, self.b)
795
795
796 def complete(self):
796 def complete(self):
797 return len(self.a) == self.lena and len(self.b) == self.lenb
797 return len(self.a) == self.lena and len(self.b) == self.lenb
798
798
799 def createfile(self):
799 def createfile(self):
800 return self.starta == 0 and self.lena == 0 and self.create
800 return self.starta == 0 and self.lena == 0 and self.create
801
801
802 def rmfile(self):
802 def rmfile(self):
803 return self.startb == 0 and self.lenb == 0 and self.remove
803 return self.startb == 0 and self.lenb == 0 and self.remove
804
804
805 def fuzzit(self, l, fuzz, toponly):
805 def fuzzit(self, l, fuzz, toponly):
806 # this removes context lines from the top and bottom of list 'l'. It
806 # this removes context lines from the top and bottom of list 'l'. It
807 # checks the hunk to make sure only context lines are removed, and then
807 # checks the hunk to make sure only context lines are removed, and then
808 # returns a new shortened list of lines.
808 # returns a new shortened list of lines.
809 fuzz = min(fuzz, len(l)-1)
809 fuzz = min(fuzz, len(l)-1)
810 if fuzz:
810 if fuzz:
811 top = 0
811 top = 0
812 bot = 0
812 bot = 0
813 hlen = len(self.hunk)
813 hlen = len(self.hunk)
814 for x in xrange(hlen - 1):
814 for x in xrange(hlen - 1):
815 # the hunk starts with the @@ line, so use x+1
815 # the hunk starts with the @@ line, so use x+1
816 if self.hunk[x + 1][0] == ' ':
816 if self.hunk[x + 1][0] == ' ':
817 top += 1
817 top += 1
818 else:
818 else:
819 break
819 break
820 if not toponly:
820 if not toponly:
821 for x in xrange(hlen - 1):
821 for x in xrange(hlen - 1):
822 if self.hunk[hlen - bot - 1][0] == ' ':
822 if self.hunk[hlen - bot - 1][0] == ' ':
823 bot += 1
823 bot += 1
824 else:
824 else:
825 break
825 break
826
826
827 # top and bot now count context in the hunk
827 # top and bot now count context in the hunk
828 # adjust them if either one is short
828 # adjust them if either one is short
829 context = max(top, bot, 3)
829 context = max(top, bot, 3)
830 if bot < context:
830 if bot < context:
831 bot = max(0, fuzz - (context - bot))
831 bot = max(0, fuzz - (context - bot))
832 else:
832 else:
833 bot = min(fuzz, bot)
833 bot = min(fuzz, bot)
834 if top < context:
834 if top < context:
835 top = max(0, fuzz - (context - top))
835 top = max(0, fuzz - (context - top))
836 else:
836 else:
837 top = min(fuzz, top)
837 top = min(fuzz, top)
838
838
839 return l[top:len(l)-bot]
839 return l[top:len(l)-bot]
840 return l
840 return l
841
841
842 def old(self, fuzz=0, toponly=False):
842 def old(self, fuzz=0, toponly=False):
843 return self.fuzzit(self.a, fuzz, toponly)
843 return self.fuzzit(self.a, fuzz, toponly)
844
844
845 def new(self, fuzz=0, toponly=False):
845 def new(self, fuzz=0, toponly=False):
846 return self.fuzzit(self.b, fuzz, toponly)
846 return self.fuzzit(self.b, fuzz, toponly)
847
847
848 class binhunk:
848 class binhunk:
849 'A binary patch file. Only understands literals so far.'
849 'A binary patch file. Only understands literals so far.'
850 def __init__(self, gitpatch):
850 def __init__(self, gitpatch):
851 self.gitpatch = gitpatch
851 self.gitpatch = gitpatch
852 self.text = None
852 self.text = None
853 self.hunk = ['GIT binary patch\n']
853 self.hunk = ['GIT binary patch\n']
854
854
855 def createfile(self):
855 def createfile(self):
856 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
856 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
857
857
858 def rmfile(self):
858 def rmfile(self):
859 return self.gitpatch.op == 'DELETE'
859 return self.gitpatch.op == 'DELETE'
860
860
861 def complete(self):
861 def complete(self):
862 return self.text is not None
862 return self.text is not None
863
863
864 def new(self):
864 def new(self):
865 return [self.text]
865 return [self.text]
866
866
867 def extract(self, lr):
867 def extract(self, lr):
868 line = lr.readline()
868 line = lr.readline()
869 self.hunk.append(line)
869 self.hunk.append(line)
870 while line and not line.startswith('literal '):
870 while line and not line.startswith('literal '):
871 line = lr.readline()
871 line = lr.readline()
872 self.hunk.append(line)
872 self.hunk.append(line)
873 if not line:
873 if not line:
874 raise PatchError(_('could not extract binary patch'))
874 raise PatchError(_('could not extract binary patch'))
875 size = int(line[8:].rstrip())
875 size = int(line[8:].rstrip())
876 dec = []
876 dec = []
877 line = lr.readline()
877 line = lr.readline()
878 self.hunk.append(line)
878 self.hunk.append(line)
879 while len(line) > 1:
879 while len(line) > 1:
880 l = line[0]
880 l = line[0]
881 if l <= 'Z' and l >= 'A':
881 if l <= 'Z' and l >= 'A':
882 l = ord(l) - ord('A') + 1
882 l = ord(l) - ord('A') + 1
883 else:
883 else:
884 l = ord(l) - ord('a') + 27
884 l = ord(l) - ord('a') + 27
885 dec.append(base85.b85decode(line[1:-1])[:l])
885 dec.append(base85.b85decode(line[1:-1])[:l])
886 line = lr.readline()
886 line = lr.readline()
887 self.hunk.append(line)
887 self.hunk.append(line)
888 text = zlib.decompress(''.join(dec))
888 text = zlib.decompress(''.join(dec))
889 if len(text) != size:
889 if len(text) != size:
890 raise PatchError(_('binary patch is %d bytes, not %d') %
890 raise PatchError(_('binary patch is %d bytes, not %d') %
891 len(text), size)
891 len(text), size)
892 self.text = text
892 self.text = text
893
893
894 def parsefilename(str):
894 def parsefilename(str):
895 # --- filename \t|space stuff
895 # --- filename \t|space stuff
896 s = str[4:].rstrip('\r\n')
896 s = str[4:].rstrip('\r\n')
897 i = s.find('\t')
897 i = s.find('\t')
898 if i < 0:
898 if i < 0:
899 i = s.find(' ')
899 i = s.find(' ')
900 if i < 0:
900 if i < 0:
901 return s
901 return s
902 return s[:i]
902 return s[:i]
903
903
904 def selectfile(afile_orig, bfile_orig, hunk, strip):
904 def selectfile(afile_orig, bfile_orig, hunk, strip):
905 def pathstrip(path, count=1):
905 def pathstrip(path, count=1):
906 pathlen = len(path)
906 pathlen = len(path)
907 i = 0
907 i = 0
908 if count == 0:
908 if count == 0:
909 return '', path.rstrip()
909 return '', path.rstrip()
910 while count > 0:
910 while count > 0:
911 i = path.find('/', i)
911 i = path.find('/', i)
912 if i == -1:
912 if i == -1:
913 raise PatchError(_("unable to strip away %d dirs from %s") %
913 raise PatchError(_("unable to strip away %d dirs from %s") %
914 (count, path))
914 (count, path))
915 i += 1
915 i += 1
916 # consume '//' in the path
916 # consume '//' in the path
917 while i < pathlen - 1 and path[i] == '/':
917 while i < pathlen - 1 and path[i] == '/':
918 i += 1
918 i += 1
919 count -= 1
919 count -= 1
920 return path[:i].lstrip(), path[i:].rstrip()
920 return path[:i].lstrip(), path[i:].rstrip()
921
921
922 nulla = afile_orig == "/dev/null"
922 nulla = afile_orig == "/dev/null"
923 nullb = bfile_orig == "/dev/null"
923 nullb = bfile_orig == "/dev/null"
924 abase, afile = pathstrip(afile_orig, strip)
924 abase, afile = pathstrip(afile_orig, strip)
925 gooda = not nulla and util.lexists(afile)
925 gooda = not nulla and util.lexists(afile)
926 bbase, bfile = pathstrip(bfile_orig, strip)
926 bbase, bfile = pathstrip(bfile_orig, strip)
927 if afile == bfile:
927 if afile == bfile:
928 goodb = gooda
928 goodb = gooda
929 else:
929 else:
930 goodb = not nullb and os.path.exists(bfile)
930 goodb = not nullb and os.path.exists(bfile)
931 createfunc = hunk.createfile
931 createfunc = hunk.createfile
932 missing = not goodb and not gooda and not createfunc()
932 missing = not goodb and not gooda and not createfunc()
933
933
934 # some diff programs apparently produce create patches where the
934 # some diff programs apparently produce create patches where the
935 # afile is not /dev/null, but rather the same name as the bfile
935 # afile is not /dev/null, but rather the same name as the bfile
936 if missing and afile == bfile:
936 if missing and afile == bfile:
937 # this isn't very pretty
937 # this isn't very pretty
938 hunk.create = True
938 hunk.create = True
939 if createfunc():
939 if createfunc():
940 missing = False
940 missing = False
941 else:
941 else:
942 hunk.create = False
942 hunk.create = False
943
943
944 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
944 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
945 # diff is between a file and its backup. In this case, the original
945 # diff is between a file and its backup. In this case, the original
946 # file should be patched (see original mpatch code).
946 # file should be patched (see original mpatch code).
947 isbackup = (abase == bbase and bfile.startswith(afile))
947 isbackup = (abase == bbase and bfile.startswith(afile))
948 fname = None
948 fname = None
949 if not missing:
949 if not missing:
950 if gooda and goodb:
950 if gooda and goodb:
951 fname = isbackup and afile or bfile
951 fname = isbackup and afile or bfile
952 elif gooda:
952 elif gooda:
953 fname = afile
953 fname = afile
954
954
955 if not fname:
955 if not fname:
956 if not nullb:
956 if not nullb:
957 fname = isbackup and afile or bfile
957 fname = isbackup and afile or bfile
958 elif not nulla:
958 elif not nulla:
959 fname = afile
959 fname = afile
960 else:
960 else:
961 raise PatchError(_("undefined source and destination files"))
961 raise PatchError(_("undefined source and destination files"))
962
962
963 return fname, missing
963 return fname, missing
964
964
965 def scangitpatch(lr, firstline):
965 def scangitpatch(lr, firstline):
966 """
966 """
967 Git patches can emit:
967 Git patches can emit:
968 - rename a to b
968 - rename a to b
969 - change b
969 - change b
970 - copy a to c
970 - copy a to c
971 - change c
971 - change c
972
972
973 We cannot apply this sequence as-is, the renamed 'a' could not be
973 We cannot apply this sequence as-is, the renamed 'a' could not be
974 found for it would have been renamed already. And we cannot copy
974 found for it would have been renamed already. And we cannot copy
975 from 'b' instead because 'b' would have been changed already. So
975 from 'b' instead because 'b' would have been changed already. So
976 we scan the git patch for copy and rename commands so we can
976 we scan the git patch for copy and rename commands so we can
977 perform the copies ahead of time.
977 perform the copies ahead of time.
978 """
978 """
979 pos = 0
979 pos = 0
980 try:
980 try:
981 pos = lr.fp.tell()
981 pos = lr.fp.tell()
982 fp = lr.fp
982 fp = lr.fp
983 except IOError:
983 except IOError:
984 fp = cStringIO.StringIO(lr.fp.read())
984 fp = cStringIO.StringIO(lr.fp.read())
985 gitlr = linereader(fp, lr.textmode)
985 gitlr = linereader(fp, lr.textmode)
986 gitlr.push(firstline)
986 gitlr.push(firstline)
987 (dopatch, gitpatches) = readgitpatch(gitlr)
987 (dopatch, gitpatches) = readgitpatch(gitlr)
988 fp.seek(pos)
988 fp.seek(pos)
989 return dopatch, gitpatches
989 return dopatch, gitpatches
990
990
991 def iterhunks(ui, fp, sourcefile=None):
991 def iterhunks(ui, fp, sourcefile=None):
992 """Read a patch and yield the following events:
992 """Read a patch and yield the following events:
993 - ("file", afile, bfile, firsthunk): select a new target file.
993 - ("file", afile, bfile, firsthunk): select a new target file.
994 - ("hunk", hunk): a new hunk is ready to be applied, follows a
994 - ("hunk", hunk): a new hunk is ready to be applied, follows a
995 "file" event.
995 "file" event.
996 - ("git", gitchanges): current diff is in git format, gitchanges
996 - ("git", gitchanges): current diff is in git format, gitchanges
997 maps filenames to gitpatch records. Unique event.
997 maps filenames to gitpatch records. Unique event.
998 """
998 """
999 changed = {}
999 changed = {}
1000 current_hunk = None
1000 current_hunk = None
1001 afile = ""
1001 afile = ""
1002 bfile = ""
1002 bfile = ""
1003 state = None
1003 state = None
1004 hunknum = 0
1004 hunknum = 0
1005 emitfile = False
1005 emitfile = False
1006 git = False
1006 git = False
1007
1007
1008 # our states
1008 # our states
1009 BFILE = 1
1009 BFILE = 1
1010 context = None
1010 context = None
1011 lr = linereader(fp)
1011 lr = linereader(fp)
1012 dopatch = True
1012 dopatch = True
1013 # gitworkdone is True if a git operation (copy, rename, ...) was
1013 # gitworkdone is True if a git operation (copy, rename, ...) was
1014 # performed already for the current file. Useful when the file
1014 # performed already for the current file. Useful when the file
1015 # section may have no hunk.
1015 # section may have no hunk.
1016 gitworkdone = False
1016 gitworkdone = False
1017
1017
1018 while True:
1018 while True:
1019 newfile = False
1019 newfile = False
1020 x = lr.readline()
1020 x = lr.readline()
1021 if not x:
1021 if not x:
1022 break
1022 break
1023 if current_hunk:
1023 if current_hunk:
1024 if x.startswith('\ '):
1024 if x.startswith('\ '):
1025 current_hunk.fix_newline()
1025 current_hunk.fix_newline()
1026 yield 'hunk', current_hunk
1026 yield 'hunk', current_hunk
1027 current_hunk = None
1027 current_hunk = None
1028 gitworkdone = False
1028 gitworkdone = False
1029 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
1029 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
1030 ((context is not False) and x.startswith('***************')))):
1030 ((context is not False) and x.startswith('***************')))):
1031 try:
1031 try:
1032 if context is None and x.startswith('***************'):
1032 if context is None and x.startswith('***************'):
1033 context = True
1033 context = True
1034 gpatch = changed.get(bfile)
1034 gpatch = changed.get(bfile)
1035 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
1035 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
1036 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
1036 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
1037 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
1037 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
1038 except PatchError, err:
1038 except PatchError, err:
1039 ui.debug(err)
1039 ui.debug(err)
1040 current_hunk = None
1040 current_hunk = None
1041 continue
1041 continue
1042 hunknum += 1
1042 hunknum += 1
1043 if emitfile:
1043 if emitfile:
1044 emitfile = False
1044 emitfile = False
1045 yield 'file', (afile, bfile, current_hunk)
1045 yield 'file', (afile, bfile, current_hunk)
1046 elif state == BFILE and x.startswith('GIT binary patch'):
1046 elif state == BFILE and x.startswith('GIT binary patch'):
1047 current_hunk = binhunk(changed[bfile])
1047 current_hunk = binhunk(changed[bfile])
1048 hunknum += 1
1048 hunknum += 1
1049 if emitfile:
1049 if emitfile:
1050 emitfile = False
1050 emitfile = False
1051 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
1051 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
1052 current_hunk.extract(lr)
1052 current_hunk.extract(lr)
1053 elif x.startswith('diff --git'):
1053 elif x.startswith('diff --git'):
1054 # check for git diff, scanning the whole patch file if needed
1054 # check for git diff, scanning the whole patch file if needed
1055 m = gitre.match(x)
1055 m = gitre.match(x)
1056 if m:
1056 if m:
1057 afile, bfile = m.group(1, 2)
1057 afile, bfile = m.group(1, 2)
1058 if not git:
1058 if not git:
1059 git = True
1059 git = True
1060 dopatch, gitpatches = scangitpatch(lr, x)
1060 dopatch, gitpatches = scangitpatch(lr, x)
1061 yield 'git', gitpatches
1061 yield 'git', gitpatches
1062 for gp in gitpatches:
1062 for gp in gitpatches:
1063 changed[gp.path] = gp
1063 changed[gp.path] = gp
1064 # else error?
1064 # else error?
1065 # copy/rename + modify should modify target, not source
1065 # copy/rename + modify should modify target, not source
1066 gp = changed.get(bfile)
1066 gp = changed.get(bfile)
1067 if gp and gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD'):
1067 if gp and gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD'):
1068 afile = bfile
1068 afile = bfile
1069 gitworkdone = True
1069 gitworkdone = True
1070 newfile = True
1070 newfile = True
1071 elif x.startswith('---'):
1071 elif x.startswith('---'):
1072 # check for a unified diff
1072 # check for a unified diff
1073 l2 = lr.readline()
1073 l2 = lr.readline()
1074 if not l2.startswith('+++'):
1074 if not l2.startswith('+++'):
1075 lr.push(l2)
1075 lr.push(l2)
1076 continue
1076 continue
1077 newfile = True
1077 newfile = True
1078 context = False
1078 context = False
1079 afile = parsefilename(x)
1079 afile = parsefilename(x)
1080 bfile = parsefilename(l2)
1080 bfile = parsefilename(l2)
1081 elif x.startswith('***'):
1081 elif x.startswith('***'):
1082 # check for a context diff
1082 # check for a context diff
1083 l2 = lr.readline()
1083 l2 = lr.readline()
1084 if not l2.startswith('---'):
1084 if not l2.startswith('---'):
1085 lr.push(l2)
1085 lr.push(l2)
1086 continue
1086 continue
1087 l3 = lr.readline()
1087 l3 = lr.readline()
1088 lr.push(l3)
1088 lr.push(l3)
1089 if not l3.startswith("***************"):
1089 if not l3.startswith("***************"):
1090 lr.push(l2)
1090 lr.push(l2)
1091 continue
1091 continue
1092 newfile = True
1092 newfile = True
1093 context = True
1093 context = True
1094 afile = parsefilename(x)
1094 afile = parsefilename(x)
1095 bfile = parsefilename(l2)
1095 bfile = parsefilename(l2)
1096
1096
1097 if newfile:
1097 if newfile:
1098 emitfile = True
1098 emitfile = True
1099 state = BFILE
1099 state = BFILE
1100 hunknum = 0
1100 hunknum = 0
1101 if current_hunk:
1101 if current_hunk:
1102 if current_hunk.complete():
1102 if current_hunk.complete():
1103 yield 'hunk', current_hunk
1103 yield 'hunk', current_hunk
1104 else:
1104 else:
1105 raise PatchError(_("malformed patch %s %s") % (afile,
1105 raise PatchError(_("malformed patch %s %s") % (afile,
1106 current_hunk.desc))
1106 current_hunk.desc))
1107
1107
1108 if hunknum == 0 and dopatch and not gitworkdone:
1108 if hunknum == 0 and dopatch and not gitworkdone:
1109 raise NoHunks
1109 raise NoHunks
1110
1110
1111 def applydiff(ui, fp, changed, strip=1, sourcefile=None, eolmode='strict'):
1111 def applydiff(ui, fp, changed, strip=1, sourcefile=None, eolmode='strict'):
1112 """
1112 """
1113 Reads a patch from fp and tries to apply it.
1113 Reads a patch from fp and tries to apply it.
1114
1114
1115 The dict 'changed' is filled in with all of the filenames changed
1115 The dict 'changed' is filled in with all of the filenames changed
1116 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1116 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1117 found and 1 if there was any fuzz.
1117 found and 1 if there was any fuzz.
1118
1118
1119 If 'eolmode' is 'strict', the patch content and patched file are
1119 If 'eolmode' is 'strict', the patch content and patched file are
1120 read in binary mode. Otherwise, line endings are ignored when
1120 read in binary mode. Otherwise, line endings are ignored when
1121 patching then normalized according to 'eolmode'.
1121 patching then normalized according to 'eolmode'.
1122 """
1122 """
1123 rejects = 0
1123 rejects = 0
1124 err = 0
1124 err = 0
1125 current_file = None
1125 current_file = None
1126 gitpatches = None
1126 gitpatches = None
1127 opener = util.opener(os.getcwd())
1127 opener = util.opener(os.getcwd())
1128
1128
1129 def closefile():
1129 def closefile():
1130 if not current_file:
1130 if not current_file:
1131 return 0
1131 return 0
1132 current_file.close()
1132 current_file.close()
1133 return len(current_file.rej)
1133 return len(current_file.rej)
1134
1134
1135 for state, values in iterhunks(ui, fp, sourcefile):
1135 for state, values in iterhunks(ui, fp, sourcefile):
1136 if state == 'hunk':
1136 if state == 'hunk':
1137 if not current_file:
1137 if not current_file:
1138 continue
1138 continue
1139 current_hunk = values
1139 current_hunk = values
1140 ret = current_file.apply(current_hunk)
1140 ret = current_file.apply(current_hunk)
1141 if ret >= 0:
1141 if ret >= 0:
1142 changed.setdefault(current_file.fname, None)
1142 changed.setdefault(current_file.fname, None)
1143 if ret > 0:
1143 if ret > 0:
1144 err = 1
1144 err = 1
1145 elif state == 'file':
1145 elif state == 'file':
1146 rejects += closefile()
1146 rejects += closefile()
1147 afile, bfile, first_hunk = values
1147 afile, bfile, first_hunk = values
1148 try:
1148 try:
1149 if sourcefile:
1149 if sourcefile:
1150 current_file = patchfile(ui, sourcefile, opener,
1150 current_file = patchfile(ui, sourcefile, opener,
1151 eolmode=eolmode)
1151 eolmode=eolmode)
1152 else:
1152 else:
1153 current_file, missing = selectfile(afile, bfile,
1153 current_file, missing = selectfile(afile, bfile,
1154 first_hunk, strip)
1154 first_hunk, strip)
1155 current_file = patchfile(ui, current_file, opener,
1155 current_file = patchfile(ui, current_file, opener,
1156 missing, eolmode)
1156 missing, eolmode)
1157 except PatchError, err:
1157 except PatchError, err:
1158 ui.warn(str(err) + '\n')
1158 ui.warn(str(err) + '\n')
1159 current_file, current_hunk = None, None
1159 current_file, current_hunk = None, None
1160 rejects += 1
1160 rejects += 1
1161 continue
1161 continue
1162 elif state == 'git':
1162 elif state == 'git':
1163 gitpatches = values
1163 gitpatches = values
1164 cwd = os.getcwd()
1164 cwd = os.getcwd()
1165 for gp in gitpatches:
1165 for gp in gitpatches:
1166 if gp.op in ('COPY', 'RENAME'):
1166 if gp.op in ('COPY', 'RENAME'):
1167 copyfile(gp.oldpath, gp.path, cwd)
1167 copyfile(gp.oldpath, gp.path, cwd)
1168 changed[gp.path] = gp
1168 changed[gp.path] = gp
1169 else:
1169 else:
1170 raise util.Abort(_('unsupported parser state: %s') % state)
1170 raise util.Abort(_('unsupported parser state: %s') % state)
1171
1171
1172 rejects += closefile()
1172 rejects += closefile()
1173
1173
1174 if rejects:
1174 if rejects:
1175 return -1
1175 return -1
1176 return err
1176 return err
1177
1177
1178 def diffopts(ui, opts=None, untrusted=False):
1178 def diffopts(ui, opts=None, untrusted=False):
1179 def get(key, name=None, getter=ui.configbool):
1179 def get(key, name=None, getter=ui.configbool):
1180 return ((opts and opts.get(key)) or
1180 return ((opts and opts.get(key)) or
1181 getter('diff', name or key, None, untrusted=untrusted))
1181 getter('diff', name or key, None, untrusted=untrusted))
1182 return mdiff.diffopts(
1182 return mdiff.diffopts(
1183 text=opts and opts.get('text'),
1183 text=opts and opts.get('text'),
1184 git=get('git'),
1184 git=get('git'),
1185 nodates=get('nodates'),
1185 nodates=get('nodates'),
1186 showfunc=get('show_function', 'showfunc'),
1186 showfunc=get('show_function', 'showfunc'),
1187 ignorews=get('ignore_all_space', 'ignorews'),
1187 ignorews=get('ignore_all_space', 'ignorews'),
1188 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1188 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1189 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1189 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1190 context=get('unified', getter=ui.config))
1190 context=get('unified', getter=ui.config))
1191
1191
1192 def updatedir(ui, repo, patches, similarity=0):
1192 def updatedir(ui, repo, patches, similarity=0):
1193 '''Update dirstate after patch application according to metadata'''
1193 '''Update dirstate after patch application according to metadata'''
1194 if not patches:
1194 if not patches:
1195 return
1195 return
1196 copies = []
1196 copies = []
1197 removes = set()
1197 removes = set()
1198 cfiles = patches.keys()
1198 cfiles = patches.keys()
1199 cwd = repo.getcwd()
1199 cwd = repo.getcwd()
1200 if cwd:
1200 if cwd:
1201 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1201 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1202 for f in patches:
1202 for f in patches:
1203 gp = patches[f]
1203 gp = patches[f]
1204 if not gp:
1204 if not gp:
1205 continue
1205 continue
1206 if gp.op == 'RENAME':
1206 if gp.op == 'RENAME':
1207 copies.append((gp.oldpath, gp.path))
1207 copies.append((gp.oldpath, gp.path))
1208 removes.add(gp.oldpath)
1208 removes.add(gp.oldpath)
1209 elif gp.op == 'COPY':
1209 elif gp.op == 'COPY':
1210 copies.append((gp.oldpath, gp.path))
1210 copies.append((gp.oldpath, gp.path))
1211 elif gp.op == 'DELETE':
1211 elif gp.op == 'DELETE':
1212 removes.add(gp.path)
1212 removes.add(gp.path)
1213 for src, dst in copies:
1213 for src, dst in copies:
1214 repo.copy(src, dst)
1214 repo.copy(src, dst)
1215 if (not similarity) and removes:
1215 if (not similarity) and removes:
1216 repo.remove(sorted(removes), True)
1216 repo.remove(sorted(removes), True)
1217 for f in patches:
1217 for f in patches:
1218 gp = patches[f]
1218 gp = patches[f]
1219 if gp and gp.mode:
1219 if gp and gp.mode:
1220 islink, isexec = gp.mode
1220 islink, isexec = gp.mode
1221 dst = repo.wjoin(gp.path)
1221 dst = repo.wjoin(gp.path)
1222 # patch won't create empty files
1222 # patch won't create empty files
1223 if gp.op == 'ADD' and not os.path.exists(dst):
1223 if gp.op == 'ADD' and not os.path.exists(dst):
1224 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1224 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1225 repo.wwrite(gp.path, '', flags)
1225 repo.wwrite(gp.path, '', flags)
1226 elif gp.op != 'DELETE':
1226 elif gp.op != 'DELETE':
1227 util.set_flags(dst, islink, isexec)
1227 util.set_flags(dst, islink, isexec)
1228 cmdutil.addremove(repo, cfiles, similarity=similarity)
1228 cmdutil.addremove(repo, cfiles, similarity=similarity)
1229 files = patches.keys()
1229 files = patches.keys()
1230 files.extend([r for r in removes if r not in files])
1230 files.extend([r for r in removes if r not in files])
1231 return sorted(files)
1231 return sorted(files)
1232
1232
1233 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1233 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1234 """use <patcher> to apply <patchname> to the working directory.
1234 """use <patcher> to apply <patchname> to the working directory.
1235 returns whether patch was applied with fuzz factor."""
1235 returns whether patch was applied with fuzz factor."""
1236
1236
1237 fuzz = False
1237 fuzz = False
1238 if cwd:
1238 if cwd:
1239 args.append('-d %s' % util.shellquote(cwd))
1239 args.append('-d %s' % util.shellquote(cwd))
1240 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1240 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1241 util.shellquote(patchname)))
1241 util.shellquote(patchname)))
1242
1242
1243 for line in fp:
1243 for line in fp:
1244 line = line.rstrip()
1244 line = line.rstrip()
1245 ui.note(line + '\n')
1245 ui.note(line + '\n')
1246 if line.startswith('patching file '):
1246 if line.startswith('patching file '):
1247 pf = util.parse_patch_output(line)
1247 pf = util.parse_patch_output(line)
1248 printed_file = False
1248 printed_file = False
1249 files.setdefault(pf, None)
1249 files.setdefault(pf, None)
1250 elif line.find('with fuzz') >= 0:
1250 elif line.find('with fuzz') >= 0:
1251 fuzz = True
1251 fuzz = True
1252 if not printed_file:
1252 if not printed_file:
1253 ui.warn(pf + '\n')
1253 ui.warn(pf + '\n')
1254 printed_file = True
1254 printed_file = True
1255 ui.warn(line + '\n')
1255 ui.warn(line + '\n')
1256 elif line.find('saving rejects to file') >= 0:
1256 elif line.find('saving rejects to file') >= 0:
1257 ui.warn(line + '\n')
1257 ui.warn(line + '\n')
1258 elif line.find('FAILED') >= 0:
1258 elif line.find('FAILED') >= 0:
1259 if not printed_file:
1259 if not printed_file:
1260 ui.warn(pf + '\n')
1260 ui.warn(pf + '\n')
1261 printed_file = True
1261 printed_file = True
1262 ui.warn(line + '\n')
1262 ui.warn(line + '\n')
1263 code = fp.close()
1263 code = fp.close()
1264 if code:
1264 if code:
1265 raise PatchError(_("patch command failed: %s") %
1265 raise PatchError(_("patch command failed: %s") %
1266 util.explain_exit(code)[0])
1266 util.explain_exit(code)[0])
1267 return fuzz
1267 return fuzz
1268
1268
1269 def internalpatch(patchobj, ui, strip, cwd, files=None, eolmode='strict'):
1269 def internalpatch(patchobj, ui, strip, cwd, files=None, eolmode='strict'):
1270 """use builtin patch to apply <patchobj> to the working directory.
1270 """use builtin patch to apply <patchobj> to the working directory.
1271 returns whether patch was applied with fuzz factor."""
1271 returns whether patch was applied with fuzz factor."""
1272
1272
1273 if files is None:
1273 if files is None:
1274 files = {}
1274 files = {}
1275 if eolmode is None:
1275 if eolmode is None:
1276 eolmode = ui.config('patch', 'eol', 'strict')
1276 eolmode = ui.config('patch', 'eol', 'strict')
1277 if eolmode.lower() not in eolmodes:
1277 if eolmode.lower() not in eolmodes:
1278 raise util.Abort(_('Unsupported line endings type: %s') % eolmode)
1278 raise util.Abort(_('Unsupported line endings type: %s') % eolmode)
1279 eolmode = eolmode.lower()
1279 eolmode = eolmode.lower()
1280
1280
1281 try:
1281 try:
1282 fp = open(patchobj, 'rb')
1282 fp = open(patchobj, 'rb')
1283 except TypeError:
1283 except TypeError:
1284 fp = patchobj
1284 fp = patchobj
1285 if cwd:
1285 if cwd:
1286 curdir = os.getcwd()
1286 curdir = os.getcwd()
1287 os.chdir(cwd)
1287 os.chdir(cwd)
1288 try:
1288 try:
1289 ret = applydiff(ui, fp, files, strip=strip, eolmode=eolmode)
1289 ret = applydiff(ui, fp, files, strip=strip, eolmode=eolmode)
1290 finally:
1290 finally:
1291 if cwd:
1291 if cwd:
1292 os.chdir(curdir)
1292 os.chdir(curdir)
1293 if fp != patchobj:
1293 if fp != patchobj:
1294 fp.close()
1294 fp.close()
1295 if ret < 0:
1295 if ret < 0:
1296 raise PatchError
1296 raise PatchError
1297 return ret > 0
1297 return ret > 0
1298
1298
1299 def patch(patchname, ui, strip=1, cwd=None, files=None, eolmode='strict'):
1299 def patch(patchname, ui, strip=1, cwd=None, files=None, eolmode='strict'):
1300 """Apply <patchname> to the working directory.
1300 """Apply <patchname> to the working directory.
1301
1301
1302 'eolmode' specifies how end of lines should be handled. It can be:
1302 'eolmode' specifies how end of lines should be handled. It can be:
1303 - 'strict': inputs are read in binary mode, EOLs are preserved
1303 - 'strict': inputs are read in binary mode, EOLs are preserved
1304 - 'crlf': EOLs are ignored when patching and reset to CRLF
1304 - 'crlf': EOLs are ignored when patching and reset to CRLF
1305 - 'lf': EOLs are ignored when patching and reset to LF
1305 - 'lf': EOLs are ignored when patching and reset to LF
1306 - None: get it from user settings, default to 'strict'
1306 - None: get it from user settings, default to 'strict'
1307 'eolmode' is ignored when using an external patcher program.
1307 'eolmode' is ignored when using an external patcher program.
1308
1308
1309 Returns whether patch was applied with fuzz factor.
1309 Returns whether patch was applied with fuzz factor.
1310 """
1310 """
1311 patcher = ui.config('ui', 'patch')
1311 patcher = ui.config('ui', 'patch')
1312 args = []
1312 args = []
1313 if files is None:
1313 if files is None:
1314 files = {}
1314 files = {}
1315 try:
1315 try:
1316 if patcher:
1316 if patcher:
1317 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1317 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1318 files)
1318 files)
1319 else:
1319 else:
1320 try:
1320 try:
1321 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1321 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1322 except NoHunks:
1322 except NoHunks:
1323 patcher = (util.find_exe('gpatch') or util.find_exe('patch')
1323 patcher = (util.find_exe('gpatch') or util.find_exe('patch')
1324 or 'patch')
1324 or 'patch')
1325 ui.debug('no valid hunks found; trying with %r instead\n' %
1325 ui.debug('no valid hunks found; trying with %r instead\n' %
1326 patcher)
1326 patcher)
1327 if util.needbinarypatch():
1327 if util.needbinarypatch():
1328 args.append('--binary')
1328 args.append('--binary')
1329 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1329 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1330 files)
1330 files)
1331 except PatchError, err:
1331 except PatchError, err:
1332 s = str(err)
1332 s = str(err)
1333 if s:
1333 if s:
1334 raise util.Abort(s)
1334 raise util.Abort(s)
1335 else:
1335 else:
1336 raise util.Abort(_('patch failed to apply'))
1336 raise util.Abort(_('patch failed to apply'))
1337
1337
1338 def b85diff(to, tn):
1338 def b85diff(to, tn):
1339 '''print base85-encoded binary diff'''
1339 '''print base85-encoded binary diff'''
1340 def gitindex(text):
1340 def gitindex(text):
1341 if not text:
1341 if not text:
1342 return '0' * 40
1342 return '0' * 40
1343 l = len(text)
1343 l = len(text)
1344 s = util.sha1('blob %d\0' % l)
1344 s = util.sha1('blob %d\0' % l)
1345 s.update(text)
1345 s.update(text)
1346 return s.hexdigest()
1346 return s.hexdigest()
1347
1347
1348 def fmtline(line):
1348 def fmtline(line):
1349 l = len(line)
1349 l = len(line)
1350 if l <= 26:
1350 if l <= 26:
1351 l = chr(ord('A') + l - 1)
1351 l = chr(ord('A') + l - 1)
1352 else:
1352 else:
1353 l = chr(l - 26 + ord('a') - 1)
1353 l = chr(l - 26 + ord('a') - 1)
1354 return '%c%s\n' % (l, base85.b85encode(line, True))
1354 return '%c%s\n' % (l, base85.b85encode(line, True))
1355
1355
1356 def chunk(text, csize=52):
1356 def chunk(text, csize=52):
1357 l = len(text)
1357 l = len(text)
1358 i = 0
1358 i = 0
1359 while i < l:
1359 while i < l:
1360 yield text[i:i + csize]
1360 yield text[i:i + csize]
1361 i += csize
1361 i += csize
1362
1362
1363 tohash = gitindex(to)
1363 tohash = gitindex(to)
1364 tnhash = gitindex(tn)
1364 tnhash = gitindex(tn)
1365 if tohash == tnhash:
1365 if tohash == tnhash:
1366 return ""
1366 return ""
1367
1367
1368 # TODO: deltas
1368 # TODO: deltas
1369 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1369 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1370 (tohash, tnhash, len(tn))]
1370 (tohash, tnhash, len(tn))]
1371 for l in chunk(zlib.compress(tn)):
1371 for l in chunk(zlib.compress(tn)):
1372 ret.append(fmtline(l))
1372 ret.append(fmtline(l))
1373 ret.append('\n')
1373 ret.append('\n')
1374 return ''.join(ret)
1374 return ''.join(ret)
1375
1375
1376 class GitDiffRequired(Exception):
1376 class GitDiffRequired(Exception):
1377 pass
1377 pass
1378
1378
1379 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1379 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1380 losedatafn=None):
1380 losedatafn=None):
1381 '''yields diff of changes to files between two nodes, or node and
1381 '''yields diff of changes to files between two nodes, or node and
1382 working directory.
1382 working directory.
1383
1383
1384 if node1 is None, use first dirstate parent instead.
1384 if node1 is None, use first dirstate parent instead.
1385 if node2 is None, compare node1 with working directory.
1385 if node2 is None, compare node1 with working directory.
1386
1386
1387 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1387 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1388 every time some change cannot be represented with the current
1388 every time some change cannot be represented with the current
1389 patch format. Return False to upgrade to git patch format, True to
1389 patch format. Return False to upgrade to git patch format, True to
1390 accept the loss or raise an exception to abort the diff. It is
1390 accept the loss or raise an exception to abort the diff. It is
1391 called with the name of current file being diffed as 'fn'. If set
1391 called with the name of current file being diffed as 'fn'. If set
1392 to None, patches will always be upgraded to git format when
1392 to None, patches will always be upgraded to git format when
1393 necessary.
1393 necessary.
1394 '''
1394 '''
1395
1395
1396 if opts is None:
1396 if opts is None:
1397 opts = mdiff.defaultopts
1397 opts = mdiff.defaultopts
1398
1398
1399 if not node1 and not node2:
1399 if not node1 and not node2:
1400 node1 = repo.dirstate.parents()[0]
1400 node1 = repo.dirstate.parents()[0]
1401
1401
1402 def lrugetfilectx():
1402 def lrugetfilectx():
1403 cache = {}
1403 cache = {}
1404 order = []
1404 order = []
1405 def getfilectx(f, ctx):
1405 def getfilectx(f, ctx):
1406 fctx = ctx.filectx(f, filelog=cache.get(f))
1406 fctx = ctx.filectx(f, filelog=cache.get(f))
1407 if f not in cache:
1407 if f not in cache:
1408 if len(cache) > 20:
1408 if len(cache) > 20:
1409 del cache[order.pop(0)]
1409 del cache[order.pop(0)]
1410 cache[f] = fctx.filelog()
1410 cache[f] = fctx.filelog()
1411 else:
1411 else:
1412 order.remove(f)
1412 order.remove(f)
1413 order.append(f)
1413 order.append(f)
1414 return fctx
1414 return fctx
1415 return getfilectx
1415 return getfilectx
1416 getfilectx = lrugetfilectx()
1416 getfilectx = lrugetfilectx()
1417
1417
1418 ctx1 = repo[node1]
1418 ctx1 = repo[node1]
1419 ctx2 = repo[node2]
1419 ctx2 = repo[node2]
1420
1420
1421 if not changes:
1421 if not changes:
1422 changes = repo.status(ctx1, ctx2, match=match)
1422 changes = repo.status(ctx1, ctx2, match=match)
1423 modified, added, removed = changes[:3]
1423 modified, added, removed = changes[:3]
1424
1424
1425 if not modified and not added and not removed:
1425 if not modified and not added and not removed:
1426 return []
1426 return []
1427
1427
1428 revs = None
1428 revs = None
1429 if not repo.ui.quiet:
1429 if not repo.ui.quiet:
1430 hexfunc = repo.ui.debugflag and hex or short
1430 hexfunc = repo.ui.debugflag and hex or short
1431 revs = [hexfunc(node) for node in [node1, node2] if node]
1431 revs = [hexfunc(node) for node in [node1, node2] if node]
1432
1432
1433 copy = {}
1433 copy = {}
1434 if opts.git or opts.upgrade:
1434 if opts.git or opts.upgrade:
1435 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1435 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1436
1436
1437 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1437 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1438 modified, added, removed, copy, getfilectx, opts, losedata)
1438 modified, added, removed, copy, getfilectx, opts, losedata)
1439 if opts.upgrade and not opts.git:
1439 if opts.upgrade and not opts.git:
1440 try:
1440 try:
1441 def losedata(fn):
1441 def losedata(fn):
1442 if not losedatafn or not losedatafn(fn=fn):
1442 if not losedatafn or not losedatafn(fn=fn):
1443 raise GitDiffRequired()
1443 raise GitDiffRequired()
1444 # Buffer the whole output until we are sure it can be generated
1444 # Buffer the whole output until we are sure it can be generated
1445 return list(difffn(opts.copy(git=False), losedata))
1445 return list(difffn(opts.copy(git=False), losedata))
1446 except GitDiffRequired:
1446 except GitDiffRequired:
1447 return difffn(opts.copy(git=True), None)
1447 return difffn(opts.copy(git=True), None)
1448 else:
1448 else:
1449 return difffn(opts, None)
1449 return difffn(opts, None)
1450
1450
1451 def _addmodehdr(header, omode, nmode):
1451 def _addmodehdr(header, omode, nmode):
1452 if omode != nmode:
1452 if omode != nmode:
1453 header.append('old mode %s\n' % omode)
1453 header.append('old mode %s\n' % omode)
1454 header.append('new mode %s\n' % nmode)
1454 header.append('new mode %s\n' % nmode)
1455
1455
1456 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1456 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1457 copy, getfilectx, opts, losedatafn):
1457 copy, getfilectx, opts, losedatafn):
1458
1458
1459 date1 = util.datestr(ctx1.date())
1459 date1 = util.datestr(ctx1.date())
1460 man1 = ctx1.manifest()
1460 man1 = ctx1.manifest()
1461
1461
1462 gone = set()
1462 gone = set()
1463 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1463 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1464
1464
1465 copyto = dict([(v, k) for k, v in copy.items()])
1465 copyto = dict([(v, k) for k, v in copy.items()])
1466
1466
1467 if opts.git:
1467 if opts.git:
1468 revs = None
1468 revs = None
1469
1469
1470 for f in sorted(modified + added + removed):
1470 for f in sorted(modified + added + removed):
1471 to = None
1471 to = None
1472 tn = None
1472 tn = None
1473 dodiff = True
1473 dodiff = True
1474 header = []
1474 header = []
1475 if f in man1:
1475 if f in man1:
1476 to = getfilectx(f, ctx1).data()
1476 to = getfilectx(f, ctx1).data()
1477 if f not in removed:
1477 if f not in removed:
1478 tn = getfilectx(f, ctx2).data()
1478 tn = getfilectx(f, ctx2).data()
1479 a, b = f, f
1479 a, b = f, f
1480 if opts.git or losedatafn:
1480 if opts.git or losedatafn:
1481 if f in added:
1481 if f in added:
1482 mode = gitmode[ctx2.flags(f)]
1482 mode = gitmode[ctx2.flags(f)]
1483 if f in copy or f in copyto:
1483 if f in copy or f in copyto:
1484 if opts.git:
1484 if opts.git:
1485 if f in copy:
1485 if f in copy:
1486 a = copy[f]
1486 a = copy[f]
1487 else:
1487 else:
1488 a = copyto[f]
1488 a = copyto[f]
1489 omode = gitmode[man1.flags(a)]
1489 omode = gitmode[man1.flags(a)]
1490 _addmodehdr(header, omode, mode)
1490 _addmodehdr(header, omode, mode)
1491 if a in removed and a not in gone:
1491 if a in removed and a not in gone:
1492 op = 'rename'
1492 op = 'rename'
1493 gone.add(a)
1493 gone.add(a)
1494 else:
1494 else:
1495 op = 'copy'
1495 op = 'copy'
1496 header.append('%s from %s\n' % (op, a))
1496 header.append('%s from %s\n' % (op, a))
1497 header.append('%s to %s\n' % (op, f))
1497 header.append('%s to %s\n' % (op, f))
1498 to = getfilectx(a, ctx1).data()
1498 to = getfilectx(a, ctx1).data()
1499 else:
1499 else:
1500 losedatafn(f)
1500 losedatafn(f)
1501 else:
1501 else:
1502 if opts.git:
1502 if opts.git:
1503 header.append('new file mode %s\n' % mode)
1503 header.append('new file mode %s\n' % mode)
1504 elif ctx2.flags(f):
1504 elif ctx2.flags(f):
1505 losedatafn(f)
1505 losedatafn(f)
1506 if util.binary(tn):
1506 if util.binary(tn):
1507 if opts.git:
1507 if opts.git:
1508 dodiff = 'binary'
1508 dodiff = 'binary'
1509 else:
1509 else:
1510 losedatafn(f)
1510 losedatafn(f)
1511 if not opts.git and not tn:
1511 if not opts.git and not tn:
1512 # regular diffs cannot represent new empty file
1512 # regular diffs cannot represent new empty file
1513 losedatafn(f)
1513 losedatafn(f)
1514 elif f in removed:
1514 elif f in removed:
1515 if opts.git:
1515 if opts.git:
1516 # have we already reported a copy above?
1516 # have we already reported a copy above?
1517 if ((f in copy and copy[f] in added
1517 if ((f in copy and copy[f] in added
1518 and copyto[copy[f]] == f) or
1518 and copyto[copy[f]] == f) or
1519 (f in copyto and copyto[f] in added
1519 (f in copyto and copyto[f] in added
1520 and copy[copyto[f]] == f)):
1520 and copy[copyto[f]] == f)):
1521 dodiff = False
1521 dodiff = False
1522 else:
1522 else:
1523 header.append('deleted file mode %s\n' %
1523 header.append('deleted file mode %s\n' %
1524 gitmode[man1.flags(f)])
1524 gitmode[man1.flags(f)])
1525 elif not to:
1525 elif not to:
1526 # regular diffs cannot represent empty file deletion
1526 # regular diffs cannot represent empty file deletion
1527 losedatafn(f)
1527 losedatafn(f)
1528 else:
1528 else:
1529 oflag = man1.flags(f)
1529 oflag = man1.flags(f)
1530 nflag = ctx2.flags(f)
1530 nflag = ctx2.flags(f)
1531 binary = util.binary(to) or util.binary(tn)
1531 binary = util.binary(to) or util.binary(tn)
1532 if opts.git:
1532 if opts.git:
1533 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1533 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1534 if binary:
1534 if binary:
1535 dodiff = 'binary'
1535 dodiff = 'binary'
1536 elif binary or nflag != oflag:
1536 elif binary or nflag != oflag:
1537 losedatafn(f)
1537 losedatafn(f)
1538 if opts.git:
1538 if opts.git:
1539 header.insert(0, mdiff.diffline(revs, a, b, opts))
1539 header.insert(0, mdiff.diffline(revs, a, b, opts))
1540
1540
1541 if dodiff:
1541 if dodiff:
1542 if dodiff == 'binary':
1542 if dodiff == 'binary':
1543 text = b85diff(to, tn)
1543 text = b85diff(to, tn)
1544 else:
1544 else:
1545 text = mdiff.unidiff(to, date1,
1545 text = mdiff.unidiff(to, date1,
1546 # ctx2 date may be dynamic
1546 # ctx2 date may be dynamic
1547 tn, util.datestr(ctx2.date()),
1547 tn, util.datestr(ctx2.date()),
1548 a, b, revs, opts=opts)
1548 a, b, revs, opts=opts)
1549 if header and (text or len(header) > 1):
1549 if header and (text or len(header) > 1):
1550 yield ''.join(header)
1550 yield ''.join(header)
1551 if text:
1551 if text:
1552 yield text
1552 yield text
1553
1553
1554 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1555 opts=None):
1556 '''export changesets as hg patches.'''
1557
1558 total = len(revs)
1559 revwidth = max([len(str(rev)) for rev in revs])
1560
1561 def single(rev, seqno, fp):
1562 ctx = repo[rev]
1563 node = ctx.node()
1564 parents = [p.node() for p in ctx.parents() if p]
1565 branch = ctx.branch()
1566 if switch_parent:
1567 parents.reverse()
1568 prev = (parents and parents[0]) or nullid
1569
1570 if not fp:
1571 fp = cmdutil.make_file(repo, template, node, total=total,
1572 seqno=seqno, revwidth=revwidth,
1573 mode='ab')
1574 if fp != sys.stdout and hasattr(fp, 'name'):
1575 repo.ui.note("%s\n" % fp.name)
1576
1577 fp.write("# HG changeset patch\n")
1578 fp.write("# User %s\n" % ctx.user())
1579 fp.write("# Date %d %d\n" % ctx.date())
1580 if branch and (branch != 'default'):
1581 fp.write("# Branch %s\n" % branch)
1582 fp.write("# Node ID %s\n" % hex(node))
1583 fp.write("# Parent %s\n" % hex(prev))
1584 if len(parents) > 1:
1585 fp.write("# Parent %s\n" % hex(parents[1]))
1586 fp.write(ctx.description().rstrip())
1587 fp.write("\n\n")
1588
1589 for chunk in diff(repo, prev, node, opts=opts):
1590 fp.write(chunk)
1591
1592 for seqno, rev in enumerate(revs):
1593 single(rev, seqno + 1, fp)
1594
1595 def diffstatdata(lines):
1554 def diffstatdata(lines):
1596 filename, adds, removes = None, 0, 0
1555 filename, adds, removes = None, 0, 0
1597 for line in lines:
1556 for line in lines:
1598 if line.startswith('diff'):
1557 if line.startswith('diff'):
1599 if filename:
1558 if filename:
1600 isbinary = adds == 0 and removes == 0
1559 isbinary = adds == 0 and removes == 0
1601 yield (filename, adds, removes, isbinary)
1560 yield (filename, adds, removes, isbinary)
1602 # set numbers to 0 anyway when starting new file
1561 # set numbers to 0 anyway when starting new file
1603 adds, removes = 0, 0
1562 adds, removes = 0, 0
1604 if line.startswith('diff --git'):
1563 if line.startswith('diff --git'):
1605 filename = gitre.search(line).group(1)
1564 filename = gitre.search(line).group(1)
1606 else:
1565 else:
1607 # format: "diff -r ... -r ... filename"
1566 # format: "diff -r ... -r ... filename"
1608 filename = line.split(None, 5)[-1]
1567 filename = line.split(None, 5)[-1]
1609 elif line.startswith('+') and not line.startswith('+++'):
1568 elif line.startswith('+') and not line.startswith('+++'):
1610 adds += 1
1569 adds += 1
1611 elif line.startswith('-') and not line.startswith('---'):
1570 elif line.startswith('-') and not line.startswith('---'):
1612 removes += 1
1571 removes += 1
1613 if filename:
1572 if filename:
1614 isbinary = adds == 0 and removes == 0
1573 isbinary = adds == 0 and removes == 0
1615 yield (filename, adds, removes, isbinary)
1574 yield (filename, adds, removes, isbinary)
1616
1575
1617 def diffstat(lines, width=80, git=False):
1576 def diffstat(lines, width=80, git=False):
1618 output = []
1577 output = []
1619 stats = list(diffstatdata(lines))
1578 stats = list(diffstatdata(lines))
1620
1579
1621 maxtotal, maxname = 0, 0
1580 maxtotal, maxname = 0, 0
1622 totaladds, totalremoves = 0, 0
1581 totaladds, totalremoves = 0, 0
1623 hasbinary = False
1582 hasbinary = False
1624 for filename, adds, removes, isbinary in stats:
1583 for filename, adds, removes, isbinary in stats:
1625 totaladds += adds
1584 totaladds += adds
1626 totalremoves += removes
1585 totalremoves += removes
1627 maxname = max(maxname, len(filename))
1586 maxname = max(maxname, len(filename))
1628 maxtotal = max(maxtotal, adds + removes)
1587 maxtotal = max(maxtotal, adds + removes)
1629 if isbinary:
1588 if isbinary:
1630 hasbinary = True
1589 hasbinary = True
1631
1590
1632 countwidth = len(str(maxtotal))
1591 countwidth = len(str(maxtotal))
1633 if hasbinary and countwidth < 3:
1592 if hasbinary and countwidth < 3:
1634 countwidth = 3
1593 countwidth = 3
1635 graphwidth = width - countwidth - maxname - 6
1594 graphwidth = width - countwidth - maxname - 6
1636 if graphwidth < 10:
1595 if graphwidth < 10:
1637 graphwidth = 10
1596 graphwidth = 10
1638
1597
1639 def scale(i):
1598 def scale(i):
1640 if maxtotal <= graphwidth:
1599 if maxtotal <= graphwidth:
1641 return i
1600 return i
1642 # If diffstat runs out of room it doesn't print anything,
1601 # If diffstat runs out of room it doesn't print anything,
1643 # which isn't very useful, so always print at least one + or -
1602 # which isn't very useful, so always print at least one + or -
1644 # if there were at least some changes.
1603 # if there were at least some changes.
1645 return max(i * graphwidth // maxtotal, int(bool(i)))
1604 return max(i * graphwidth // maxtotal, int(bool(i)))
1646
1605
1647 for filename, adds, removes, isbinary in stats:
1606 for filename, adds, removes, isbinary in stats:
1648 if git and isbinary:
1607 if git and isbinary:
1649 count = 'Bin'
1608 count = 'Bin'
1650 else:
1609 else:
1651 count = adds + removes
1610 count = adds + removes
1652 pluses = '+' * scale(adds)
1611 pluses = '+' * scale(adds)
1653 minuses = '-' * scale(removes)
1612 minuses = '-' * scale(removes)
1654 output.append(' %-*s | %*s %s%s\n' % (maxname, filename, countwidth,
1613 output.append(' %-*s | %*s %s%s\n' % (maxname, filename, countwidth,
1655 count, pluses, minuses))
1614 count, pluses, minuses))
1656
1615
1657 if stats:
1616 if stats:
1658 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1617 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1659 % (len(stats), totaladds, totalremoves))
1618 % (len(stats), totaladds, totalremoves))
1660
1619
1661 return ''.join(output)
1620 return ''.join(output)
General Comments 0
You need to be logged in to leave comments. Login now