##// END OF EJS Templates
Avoid importing mercurial.node/mercurial.repo stuff from mercurial.hg
Joel Rosdahl -
r6217:fe8dbbe9 default
parent child Browse files
Show More
@@ -1,301 +1,302 b''
1 # hg backend for convert extension
1 # hg backend for convert extension
2
2
3 # Notes for hg->hg conversion:
3 # Notes for hg->hg conversion:
4 #
4 #
5 # * Old versions of Mercurial didn't trim the whitespace from the ends
5 # * Old versions of Mercurial didn't trim the whitespace from the ends
6 # of commit messages, but new versions do. Changesets created by
6 # of commit messages, but new versions do. Changesets created by
7 # those older versions, then converted, may thus have different
7 # those older versions, then converted, may thus have different
8 # hashes for changesets that are otherwise identical.
8 # hashes for changesets that are otherwise identical.
9 #
9 #
10 # * By default, the source revision is stored in the converted
10 # * By default, the source revision is stored in the converted
11 # revision. This will cause the converted revision to have a
11 # revision. This will cause the converted revision to have a
12 # different identity than the source. To avoid this, use the
12 # different identity than the source. To avoid this, use the
13 # following option: "--config convert.hg.saverev=false"
13 # following option: "--config convert.hg.saverev=false"
14
14
15
15
16 import os, time
16 import os, time
17 from mercurial.i18n import _
17 from mercurial.i18n import _
18 from mercurial.repo import RepoError
18 from mercurial.node import bin, hex, nullid
19 from mercurial.node import bin, hex, nullid
19 from mercurial import hg, revlog, util
20 from mercurial import hg, revlog, util
20
21
21 from common import NoRepo, commit, converter_source, converter_sink
22 from common import NoRepo, commit, converter_source, converter_sink
22
23
23 class mercurial_sink(converter_sink):
24 class mercurial_sink(converter_sink):
24 def __init__(self, ui, path):
25 def __init__(self, ui, path):
25 converter_sink.__init__(self, ui, path)
26 converter_sink.__init__(self, ui, path)
26 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
27 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
27 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
28 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
28 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
29 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
29 self.lastbranch = None
30 self.lastbranch = None
30 if os.path.isdir(path) and len(os.listdir(path)) > 0:
31 if os.path.isdir(path) and len(os.listdir(path)) > 0:
31 try:
32 try:
32 self.repo = hg.repository(self.ui, path)
33 self.repo = hg.repository(self.ui, path)
33 if not self.repo.local():
34 if not self.repo.local():
34 raise NoRepo(_('%s is not a local Mercurial repo') % path)
35 raise NoRepo(_('%s is not a local Mercurial repo') % path)
35 except hg.RepoError, err:
36 except RepoError, err:
36 ui.print_exc()
37 ui.print_exc()
37 raise NoRepo(err.args[0])
38 raise NoRepo(err.args[0])
38 else:
39 else:
39 try:
40 try:
40 ui.status(_('initializing destination %s repository\n') % path)
41 ui.status(_('initializing destination %s repository\n') % path)
41 self.repo = hg.repository(self.ui, path, create=True)
42 self.repo = hg.repository(self.ui, path, create=True)
42 if not self.repo.local():
43 if not self.repo.local():
43 raise NoRepo(_('%s is not a local Mercurial repo') % path)
44 raise NoRepo(_('%s is not a local Mercurial repo') % path)
44 self.created.append(path)
45 self.created.append(path)
45 except hg.RepoError, err:
46 except RepoError, err:
46 ui.print_exc()
47 ui.print_exc()
47 raise NoRepo("could not create hg repo %s as sink" % path)
48 raise NoRepo("could not create hg repo %s as sink" % path)
48 self.lock = None
49 self.lock = None
49 self.wlock = None
50 self.wlock = None
50 self.filemapmode = False
51 self.filemapmode = False
51
52
52 def before(self):
53 def before(self):
53 self.ui.debug(_('run hg sink pre-conversion action\n'))
54 self.ui.debug(_('run hg sink pre-conversion action\n'))
54 self.wlock = self.repo.wlock()
55 self.wlock = self.repo.wlock()
55 self.lock = self.repo.lock()
56 self.lock = self.repo.lock()
56 self.repo.dirstate.clear()
57 self.repo.dirstate.clear()
57
58
58 def after(self):
59 def after(self):
59 self.ui.debug(_('run hg sink post-conversion action\n'))
60 self.ui.debug(_('run hg sink post-conversion action\n'))
60 self.repo.dirstate.invalidate()
61 self.repo.dirstate.invalidate()
61 self.lock = None
62 self.lock = None
62 self.wlock = None
63 self.wlock = None
63
64
64 def revmapfile(self):
65 def revmapfile(self):
65 return os.path.join(self.path, ".hg", "shamap")
66 return os.path.join(self.path, ".hg", "shamap")
66
67
67 def authorfile(self):
68 def authorfile(self):
68 return os.path.join(self.path, ".hg", "authormap")
69 return os.path.join(self.path, ".hg", "authormap")
69
70
70 def getheads(self):
71 def getheads(self):
71 h = self.repo.changelog.heads()
72 h = self.repo.changelog.heads()
72 return [ hex(x) for x in h ]
73 return [ hex(x) for x in h ]
73
74
74 def putfile(self, f, e, data):
75 def putfile(self, f, e, data):
75 self.repo.wwrite(f, data, e)
76 self.repo.wwrite(f, data, e)
76 if f not in self.repo.dirstate:
77 if f not in self.repo.dirstate:
77 self.repo.dirstate.normallookup(f)
78 self.repo.dirstate.normallookup(f)
78
79
79 def copyfile(self, source, dest):
80 def copyfile(self, source, dest):
80 self.repo.copy(source, dest)
81 self.repo.copy(source, dest)
81
82
82 def delfile(self, f):
83 def delfile(self, f):
83 try:
84 try:
84 util.unlink(self.repo.wjoin(f))
85 util.unlink(self.repo.wjoin(f))
85 #self.repo.remove([f])
86 #self.repo.remove([f])
86 except OSError:
87 except OSError:
87 pass
88 pass
88
89
89 def setbranch(self, branch, pbranches):
90 def setbranch(self, branch, pbranches):
90 if not self.clonebranches:
91 if not self.clonebranches:
91 return
92 return
92
93
93 setbranch = (branch != self.lastbranch)
94 setbranch = (branch != self.lastbranch)
94 self.lastbranch = branch
95 self.lastbranch = branch
95 if not branch:
96 if not branch:
96 branch = 'default'
97 branch = 'default'
97 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
98 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
98 pbranch = pbranches and pbranches[0][1] or 'default'
99 pbranch = pbranches and pbranches[0][1] or 'default'
99
100
100 branchpath = os.path.join(self.path, branch)
101 branchpath = os.path.join(self.path, branch)
101 if setbranch:
102 if setbranch:
102 self.after()
103 self.after()
103 try:
104 try:
104 self.repo = hg.repository(self.ui, branchpath)
105 self.repo = hg.repository(self.ui, branchpath)
105 except:
106 except:
106 self.repo = hg.repository(self.ui, branchpath, create=True)
107 self.repo = hg.repository(self.ui, branchpath, create=True)
107 self.before()
108 self.before()
108
109
109 # pbranches may bring revisions from other branches (merge parents)
110 # pbranches may bring revisions from other branches (merge parents)
110 # Make sure we have them, or pull them.
111 # Make sure we have them, or pull them.
111 missings = {}
112 missings = {}
112 for b in pbranches:
113 for b in pbranches:
113 try:
114 try:
114 self.repo.lookup(b[0])
115 self.repo.lookup(b[0])
115 except:
116 except:
116 missings.setdefault(b[1], []).append(b[0])
117 missings.setdefault(b[1], []).append(b[0])
117
118
118 if missings:
119 if missings:
119 self.after()
120 self.after()
120 for pbranch, heads in missings.iteritems():
121 for pbranch, heads in missings.iteritems():
121 pbranchpath = os.path.join(self.path, pbranch)
122 pbranchpath = os.path.join(self.path, pbranch)
122 prepo = hg.repository(self.ui, pbranchpath)
123 prepo = hg.repository(self.ui, pbranchpath)
123 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
124 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
124 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
125 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
125 self.before()
126 self.before()
126
127
127 def putcommit(self, files, parents, commit):
128 def putcommit(self, files, parents, commit):
128 seen = {}
129 seen = {}
129 pl = []
130 pl = []
130 for p in parents:
131 for p in parents:
131 if p not in seen:
132 if p not in seen:
132 pl.append(p)
133 pl.append(p)
133 seen[p] = 1
134 seen[p] = 1
134 parents = pl
135 parents = pl
135 nparents = len(parents)
136 nparents = len(parents)
136 if self.filemapmode and nparents == 1:
137 if self.filemapmode and nparents == 1:
137 m1node = self.repo.changelog.read(bin(parents[0]))[0]
138 m1node = self.repo.changelog.read(bin(parents[0]))[0]
138 parent = parents[0]
139 parent = parents[0]
139
140
140 if len(parents) < 2: parents.append("0" * 40)
141 if len(parents) < 2: parents.append("0" * 40)
141 if len(parents) < 2: parents.append("0" * 40)
142 if len(parents) < 2: parents.append("0" * 40)
142 p2 = parents.pop(0)
143 p2 = parents.pop(0)
143
144
144 text = commit.desc
145 text = commit.desc
145 extra = commit.extra.copy()
146 extra = commit.extra.copy()
146 if self.branchnames and commit.branch:
147 if self.branchnames and commit.branch:
147 extra['branch'] = commit.branch
148 extra['branch'] = commit.branch
148 if commit.rev:
149 if commit.rev:
149 extra['convert_revision'] = commit.rev
150 extra['convert_revision'] = commit.rev
150
151
151 while parents:
152 while parents:
152 p1 = p2
153 p1 = p2
153 p2 = parents.pop(0)
154 p2 = parents.pop(0)
154 a = self.repo.rawcommit(files, text, commit.author, commit.date,
155 a = self.repo.rawcommit(files, text, commit.author, commit.date,
155 bin(p1), bin(p2), extra=extra)
156 bin(p1), bin(p2), extra=extra)
156 self.repo.dirstate.clear()
157 self.repo.dirstate.clear()
157 text = "(octopus merge fixup)\n"
158 text = "(octopus merge fixup)\n"
158 p2 = hg.hex(self.repo.changelog.tip())
159 p2 = hex(self.repo.changelog.tip())
159
160
160 if self.filemapmode and nparents == 1:
161 if self.filemapmode and nparents == 1:
161 man = self.repo.manifest
162 man = self.repo.manifest
162 mnode = self.repo.changelog.read(bin(p2))[0]
163 mnode = self.repo.changelog.read(bin(p2))[0]
163 if not man.cmp(m1node, man.revision(mnode)):
164 if not man.cmp(m1node, man.revision(mnode)):
164 self.repo.rollback()
165 self.repo.rollback()
165 self.repo.dirstate.clear()
166 self.repo.dirstate.clear()
166 return parent
167 return parent
167 return p2
168 return p2
168
169
169 def puttags(self, tags):
170 def puttags(self, tags):
170 try:
171 try:
171 old = self.repo.wfile(".hgtags").read()
172 old = self.repo.wfile(".hgtags").read()
172 oldlines = old.splitlines(1)
173 oldlines = old.splitlines(1)
173 oldlines.sort()
174 oldlines.sort()
174 except:
175 except:
175 oldlines = []
176 oldlines = []
176
177
177 k = tags.keys()
178 k = tags.keys()
178 k.sort()
179 k.sort()
179 newlines = []
180 newlines = []
180 for tag in k:
181 for tag in k:
181 newlines.append("%s %s\n" % (tags[tag], tag))
182 newlines.append("%s %s\n" % (tags[tag], tag))
182
183
183 newlines.sort()
184 newlines.sort()
184
185
185 if newlines != oldlines:
186 if newlines != oldlines:
186 self.ui.status("updating tags\n")
187 self.ui.status("updating tags\n")
187 f = self.repo.wfile(".hgtags", "w")
188 f = self.repo.wfile(".hgtags", "w")
188 f.write("".join(newlines))
189 f.write("".join(newlines))
189 f.close()
190 f.close()
190 if not oldlines: self.repo.add([".hgtags"])
191 if not oldlines: self.repo.add([".hgtags"])
191 date = "%s 0" % int(time.mktime(time.gmtime()))
192 date = "%s 0" % int(time.mktime(time.gmtime()))
192 extra = {}
193 extra = {}
193 if self.tagsbranch != 'default':
194 if self.tagsbranch != 'default':
194 extra['branch'] = self.tagsbranch
195 extra['branch'] = self.tagsbranch
195 try:
196 try:
196 tagparent = self.repo.changectx(self.tagsbranch).node()
197 tagparent = self.repo.changectx(self.tagsbranch).node()
197 except hg.RepoError, inst:
198 except RepoError, inst:
198 tagparent = nullid
199 tagparent = nullid
199 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
200 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
200 date, tagparent, nullid, extra=extra)
201 date, tagparent, nullid, extra=extra)
201 return hex(self.repo.changelog.tip())
202 return hex(self.repo.changelog.tip())
202
203
203 def setfilemapmode(self, active):
204 def setfilemapmode(self, active):
204 self.filemapmode = active
205 self.filemapmode = active
205
206
206 class mercurial_source(converter_source):
207 class mercurial_source(converter_source):
207 def __init__(self, ui, path, rev=None):
208 def __init__(self, ui, path, rev=None):
208 converter_source.__init__(self, ui, path, rev)
209 converter_source.__init__(self, ui, path, rev)
209 self.saverev = ui.configbool('convert', 'hg.saverev', True)
210 self.saverev = ui.configbool('convert', 'hg.saverev', True)
210 try:
211 try:
211 self.repo = hg.repository(self.ui, path)
212 self.repo = hg.repository(self.ui, path)
212 # try to provoke an exception if this isn't really a hg
213 # try to provoke an exception if this isn't really a hg
213 # repo, but some other bogus compatible-looking url
214 # repo, but some other bogus compatible-looking url
214 if not self.repo.local():
215 if not self.repo.local():
215 raise hg.RepoError()
216 raise RepoError()
216 except hg.RepoError:
217 except RepoError:
217 ui.print_exc()
218 ui.print_exc()
218 raise NoRepo("%s is not a local Mercurial repo" % path)
219 raise NoRepo("%s is not a local Mercurial repo" % path)
219 self.lastrev = None
220 self.lastrev = None
220 self.lastctx = None
221 self.lastctx = None
221 self._changescache = None
222 self._changescache = None
222 self.convertfp = None
223 self.convertfp = None
223
224
224 def changectx(self, rev):
225 def changectx(self, rev):
225 if self.lastrev != rev:
226 if self.lastrev != rev:
226 self.lastctx = self.repo.changectx(rev)
227 self.lastctx = self.repo.changectx(rev)
227 self.lastrev = rev
228 self.lastrev = rev
228 return self.lastctx
229 return self.lastctx
229
230
230 def getheads(self):
231 def getheads(self):
231 if self.rev:
232 if self.rev:
232 return [hex(self.repo.changectx(self.rev).node())]
233 return [hex(self.repo.changectx(self.rev).node())]
233 else:
234 else:
234 return [hex(node) for node in self.repo.heads()]
235 return [hex(node) for node in self.repo.heads()]
235
236
236 def getfile(self, name, rev):
237 def getfile(self, name, rev):
237 try:
238 try:
238 return self.changectx(rev).filectx(name).data()
239 return self.changectx(rev).filectx(name).data()
239 except revlog.LookupError, err:
240 except revlog.LookupError, err:
240 raise IOError(err)
241 raise IOError(err)
241
242
242 def getmode(self, name, rev):
243 def getmode(self, name, rev):
243 m = self.changectx(rev).manifest()
244 m = self.changectx(rev).manifest()
244 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
245 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
245
246
246 def getchanges(self, rev):
247 def getchanges(self, rev):
247 ctx = self.changectx(rev)
248 ctx = self.changectx(rev)
248 if self._changescache and self._changescache[0] == rev:
249 if self._changescache and self._changescache[0] == rev:
249 m, a, r = self._changescache[1]
250 m, a, r = self._changescache[1]
250 else:
251 else:
251 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
252 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
252 changes = [(name, rev) for name in m + a + r]
253 changes = [(name, rev) for name in m + a + r]
253 changes.sort()
254 changes.sort()
254 return (changes, self.getcopies(ctx, m + a))
255 return (changes, self.getcopies(ctx, m + a))
255
256
256 def getcopies(self, ctx, files):
257 def getcopies(self, ctx, files):
257 copies = {}
258 copies = {}
258 for name in files:
259 for name in files:
259 try:
260 try:
260 copies[name] = ctx.filectx(name).renamed()[0]
261 copies[name] = ctx.filectx(name).renamed()[0]
261 except TypeError:
262 except TypeError:
262 pass
263 pass
263 return copies
264 return copies
264
265
265 def getcommit(self, rev):
266 def getcommit(self, rev):
266 ctx = self.changectx(rev)
267 ctx = self.changectx(rev)
267 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
268 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
268 if self.saverev:
269 if self.saverev:
269 crev = rev
270 crev = rev
270 else:
271 else:
271 crev = None
272 crev = None
272 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
273 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
273 desc=ctx.description(), rev=crev, parents=parents,
274 desc=ctx.description(), rev=crev, parents=parents,
274 branch=ctx.branch(), extra=ctx.extra())
275 branch=ctx.branch(), extra=ctx.extra())
275
276
276 def gettags(self):
277 def gettags(self):
277 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
278 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
278 return dict([(name, hex(node)) for name, node in tags])
279 return dict([(name, hex(node)) for name, node in tags])
279
280
280 def getchangedfiles(self, rev, i):
281 def getchangedfiles(self, rev, i):
281 ctx = self.changectx(rev)
282 ctx = self.changectx(rev)
282 i = i or 0
283 i = i or 0
283 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
284 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
284
285
285 if i == 0:
286 if i == 0:
286 self._changescache = (rev, changes)
287 self._changescache = (rev, changes)
287
288
288 return changes[0] + changes[1] + changes[2]
289 return changes[0] + changes[1] + changes[2]
289
290
290 def converted(self, rev, destrev):
291 def converted(self, rev, destrev):
291 if self.convertfp is None:
292 if self.convertfp is None:
292 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
293 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
293 'a')
294 'a')
294 self.convertfp.write('%s %s\n' % (destrev, rev))
295 self.convertfp.write('%s %s\n' % (destrev, rev))
295 self.convertfp.flush()
296 self.convertfp.flush()
296
297
297 def before(self):
298 def before(self):
298 self.ui.debug(_('run hg source pre-conversion action\n'))
299 self.ui.debug(_('run hg source pre-conversion action\n'))
299
300
300 def after(self):
301 def after(self):
301 self.ui.debug(_('run hg source post-conversion action\n'))
302 self.ui.debug(_('run hg source post-conversion action\n'))
@@ -1,356 +1,357 b''
1 # Minimal support for git commands on an hg repository
1 # Minimal support for git commands on an hg repository
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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 #
7 #
8 # The hgk extension allows browsing the history of a repository in a
8 # The hgk extension allows browsing the history of a repository in a
9 # graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is
9 # graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is
10 # not distributed with Mercurial.)
10 # not distributed with Mercurial.)
11 #
11 #
12 # hgk consists of two parts: a Tcl script that does the displaying and
12 # hgk consists of two parts: a Tcl script that does the displaying and
13 # querying of information, and an extension to mercurial named hgk.py,
13 # querying of information, and an extension to mercurial named hgk.py,
14 # which provides hooks for hgk to get information. hgk can be found in
14 # which provides hooks for hgk to get information. hgk can be found in
15 # the contrib directory, and hgk.py can be found in the hgext
15 # the contrib directory, and hgk.py can be found in the hgext
16 # directory.
16 # directory.
17 #
17 #
18 # To load the hgext.py extension, add it to your .hgrc file (you have
18 # To load the hgext.py extension, add it to your .hgrc file (you have
19 # to use your global $HOME/.hgrc file, not one in a repository). You
19 # to use your global $HOME/.hgrc file, not one in a repository). You
20 # can specify an absolute path:
20 # can specify an absolute path:
21 #
21 #
22 # [extensions]
22 # [extensions]
23 # hgk=/usr/local/lib/hgk.py
23 # hgk=/usr/local/lib/hgk.py
24 #
24 #
25 # Mercurial can also scan the default python library path for a file
25 # Mercurial can also scan the default python library path for a file
26 # named 'hgk.py' if you set hgk empty:
26 # named 'hgk.py' if you set hgk empty:
27 #
27 #
28 # [extensions]
28 # [extensions]
29 # hgk=
29 # hgk=
30 #
30 #
31 # The hg view command will launch the hgk Tcl script. For this command
31 # The hg view command will launch the hgk Tcl script. For this command
32 # to work, hgk must be in your search path. Alternately, you can
32 # to work, hgk must be in your search path. Alternately, you can
33 # specify the path to hgk in your .hgrc file:
33 # specify the path to hgk in your .hgrc file:
34 #
34 #
35 # [hgk]
35 # [hgk]
36 # path=/location/of/hgk
36 # path=/location/of/hgk
37 #
37 #
38 # hgk can make use of the extdiff extension to visualize
38 # hgk can make use of the extdiff extension to visualize
39 # revisions. Assuming you had already configured extdiff vdiff
39 # revisions. Assuming you had already configured extdiff vdiff
40 # command, just add:
40 # command, just add:
41 #
41 #
42 # [hgk]
42 # [hgk]
43 # vdiff=vdiff
43 # vdiff=vdiff
44 #
44 #
45 # Revisions context menu will now display additional entries to fire
45 # Revisions context menu will now display additional entries to fire
46 # vdiff on hovered and selected revisions.
46 # vdiff on hovered and selected revisions.
47
47
48 import os
48 import os
49 from mercurial import hg, commands, util, patch, revlog
49 from mercurial import commands, util, patch, revlog
50 from mercurial.node import nullid, nullrev, short
50
51
51 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
52 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
52 """diff trees from two commits"""
53 """diff trees from two commits"""
53 def __difftree(repo, node1, node2, files=[]):
54 def __difftree(repo, node1, node2, files=[]):
54 assert node2 is not None
55 assert node2 is not None
55 mmap = repo.changectx(node1).manifest()
56 mmap = repo.changectx(node1).manifest()
56 mmap2 = repo.changectx(node2).manifest()
57 mmap2 = repo.changectx(node2).manifest()
57 status = repo.status(node1, node2, files=files)[:5]
58 status = repo.status(node1, node2, files=files)[:5]
58 modified, added, removed, deleted, unknown = status
59 modified, added, removed, deleted, unknown = status
59
60
60 empty = hg.short(hg.nullid)
61 empty = short(nullid)
61
62
62 for f in modified:
63 for f in modified:
63 # TODO get file permissions
64 # TODO get file permissions
64 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
65 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
65 (hg.short(mmap[f]), hg.short(mmap2[f]), f, f))
66 (short(mmap[f]), short(mmap2[f]), f, f))
66 for f in added:
67 for f in added:
67 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
68 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
68 (empty, hg.short(mmap2[f]), f, f))
69 (empty, short(mmap2[f]), f, f))
69 for f in removed:
70 for f in removed:
70 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
71 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
71 (hg.short(mmap[f]), empty, f, f))
72 (short(mmap[f]), empty, f, f))
72 ##
73 ##
73
74
74 while True:
75 while True:
75 if opts['stdin']:
76 if opts['stdin']:
76 try:
77 try:
77 line = raw_input().split(' ')
78 line = raw_input().split(' ')
78 node1 = line[0]
79 node1 = line[0]
79 if len(line) > 1:
80 if len(line) > 1:
80 node2 = line[1]
81 node2 = line[1]
81 else:
82 else:
82 node2 = None
83 node2 = None
83 except EOFError:
84 except EOFError:
84 break
85 break
85 node1 = repo.lookup(node1)
86 node1 = repo.lookup(node1)
86 if node2:
87 if node2:
87 node2 = repo.lookup(node2)
88 node2 = repo.lookup(node2)
88 else:
89 else:
89 node2 = node1
90 node2 = node1
90 node1 = repo.changelog.parents(node1)[0]
91 node1 = repo.changelog.parents(node1)[0]
91 if opts['patch']:
92 if opts['patch']:
92 if opts['pretty']:
93 if opts['pretty']:
93 catcommit(ui, repo, node2, "")
94 catcommit(ui, repo, node2, "")
94 patch.diff(repo, node1, node2,
95 patch.diff(repo, node1, node2,
95 files=files,
96 files=files,
96 opts=patch.diffopts(ui, {'git': True}))
97 opts=patch.diffopts(ui, {'git': True}))
97 else:
98 else:
98 __difftree(repo, node1, node2, files=files)
99 __difftree(repo, node1, node2, files=files)
99 if not opts['stdin']:
100 if not opts['stdin']:
100 break
101 break
101
102
102 def catcommit(ui, repo, n, prefix, ctx=None):
103 def catcommit(ui, repo, n, prefix, ctx=None):
103 nlprefix = '\n' + prefix;
104 nlprefix = '\n' + prefix;
104 if ctx is None:
105 if ctx is None:
105 ctx = repo.changectx(n)
106 ctx = repo.changectx(n)
106 (p1, p2) = ctx.parents()
107 (p1, p2) = ctx.parents()
107 ui.write("tree %s\n" % hg.short(ctx.changeset()[0])) # use ctx.node() instead ??
108 ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
108 if p1: ui.write("parent %s\n" % hg.short(p1.node()))
109 if p1: ui.write("parent %s\n" % short(p1.node()))
109 if p2: ui.write("parent %s\n" % hg.short(p2.node()))
110 if p2: ui.write("parent %s\n" % short(p2.node()))
110 date = ctx.date()
111 date = ctx.date()
111 description = ctx.description().replace("\0", "")
112 description = ctx.description().replace("\0", "")
112 lines = description.splitlines()
113 lines = description.splitlines()
113 if lines and lines[-1].startswith('committer:'):
114 if lines and lines[-1].startswith('committer:'):
114 committer = lines[-1].split(': ')[1].rstrip()
115 committer = lines[-1].split(': ')[1].rstrip()
115 else:
116 else:
116 committer = ctx.user()
117 committer = ctx.user()
117
118
118 ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
119 ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
119 ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
120 ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
120 ui.write("revision %d\n" % ctx.rev())
121 ui.write("revision %d\n" % ctx.rev())
121 ui.write("branch %s\n\n" % ctx.branch())
122 ui.write("branch %s\n\n" % ctx.branch())
122
123
123 if prefix != "":
124 if prefix != "":
124 ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
125 ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
125 else:
126 else:
126 ui.write(description + "\n")
127 ui.write(description + "\n")
127 if prefix:
128 if prefix:
128 ui.write('\0')
129 ui.write('\0')
129
130
130 def base(ui, repo, node1, node2):
131 def base(ui, repo, node1, node2):
131 """Output common ancestor information"""
132 """Output common ancestor information"""
132 node1 = repo.lookup(node1)
133 node1 = repo.lookup(node1)
133 node2 = repo.lookup(node2)
134 node2 = repo.lookup(node2)
134 n = repo.changelog.ancestor(node1, node2)
135 n = repo.changelog.ancestor(node1, node2)
135 ui.write(hg.short(n) + "\n")
136 ui.write(short(n) + "\n")
136
137
137 def catfile(ui, repo, type=None, r=None, **opts):
138 def catfile(ui, repo, type=None, r=None, **opts):
138 """cat a specific revision"""
139 """cat a specific revision"""
139 # in stdin mode, every line except the commit is prefixed with two
140 # in stdin mode, every line except the commit is prefixed with two
140 # spaces. This way the our caller can find the commit without magic
141 # spaces. This way the our caller can find the commit without magic
141 # strings
142 # strings
142 #
143 #
143 prefix = ""
144 prefix = ""
144 if opts['stdin']:
145 if opts['stdin']:
145 try:
146 try:
146 (type, r) = raw_input().split(' ');
147 (type, r) = raw_input().split(' ');
147 prefix = " "
148 prefix = " "
148 except EOFError:
149 except EOFError:
149 return
150 return
150
151
151 else:
152 else:
152 if not type or not r:
153 if not type or not r:
153 ui.warn("cat-file: type or revision not supplied\n")
154 ui.warn("cat-file: type or revision not supplied\n")
154 commands.help_(ui, 'cat-file')
155 commands.help_(ui, 'cat-file')
155
156
156 while r:
157 while r:
157 if type != "commit":
158 if type != "commit":
158 ui.warn("aborting hg cat-file only understands commits\n")
159 ui.warn("aborting hg cat-file only understands commits\n")
159 return 1;
160 return 1;
160 n = repo.lookup(r)
161 n = repo.lookup(r)
161 catcommit(ui, repo, n, prefix)
162 catcommit(ui, repo, n, prefix)
162 if opts['stdin']:
163 if opts['stdin']:
163 try:
164 try:
164 (type, r) = raw_input().split(' ');
165 (type, r) = raw_input().split(' ');
165 except EOFError:
166 except EOFError:
166 break
167 break
167 else:
168 else:
168 break
169 break
169
170
170 # git rev-tree is a confusing thing. You can supply a number of
171 # git rev-tree is a confusing thing. You can supply a number of
171 # commit sha1s on the command line, and it walks the commit history
172 # commit sha1s on the command line, and it walks the commit history
172 # telling you which commits are reachable from the supplied ones via
173 # telling you which commits are reachable from the supplied ones via
173 # a bitmask based on arg position.
174 # a bitmask based on arg position.
174 # you can specify a commit to stop at by starting the sha1 with ^
175 # you can specify a commit to stop at by starting the sha1 with ^
175 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
176 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
176 def chlogwalk():
177 def chlogwalk():
177 count = repo.changelog.count()
178 count = repo.changelog.count()
178 i = count
179 i = count
179 l = [0] * 100
180 l = [0] * 100
180 chunk = 100
181 chunk = 100
181 while True:
182 while True:
182 if chunk > i:
183 if chunk > i:
183 chunk = i
184 chunk = i
184 i = 0
185 i = 0
185 else:
186 else:
186 i -= chunk
187 i -= chunk
187
188
188 for x in xrange(0, chunk):
189 for x in xrange(0, chunk):
189 if i + x >= count:
190 if i + x >= count:
190 l[chunk - x:] = [0] * (chunk - x)
191 l[chunk - x:] = [0] * (chunk - x)
191 break
192 break
192 if full != None:
193 if full != None:
193 l[x] = repo.changectx(i + x)
194 l[x] = repo.changectx(i + x)
194 l[x].changeset() # force reading
195 l[x].changeset() # force reading
195 else:
196 else:
196 l[x] = 1
197 l[x] = 1
197 for x in xrange(chunk-1, -1, -1):
198 for x in xrange(chunk-1, -1, -1):
198 if l[x] != 0:
199 if l[x] != 0:
199 yield (i + x, full != None and l[x] or None)
200 yield (i + x, full != None and l[x] or None)
200 if i == 0:
201 if i == 0:
201 break
202 break
202
203
203 # calculate and return the reachability bitmask for sha
204 # calculate and return the reachability bitmask for sha
204 def is_reachable(ar, reachable, sha):
205 def is_reachable(ar, reachable, sha):
205 if len(ar) == 0:
206 if len(ar) == 0:
206 return 1
207 return 1
207 mask = 0
208 mask = 0
208 for i in xrange(len(ar)):
209 for i in xrange(len(ar)):
209 if sha in reachable[i]:
210 if sha in reachable[i]:
210 mask |= 1 << i
211 mask |= 1 << i
211
212
212 return mask
213 return mask
213
214
214 reachable = []
215 reachable = []
215 stop_sha1 = []
216 stop_sha1 = []
216 want_sha1 = []
217 want_sha1 = []
217 count = 0
218 count = 0
218
219
219 # figure out which commits they are asking for and which ones they
220 # figure out which commits they are asking for and which ones they
220 # want us to stop on
221 # want us to stop on
221 for i in xrange(len(args)):
222 for i in xrange(len(args)):
222 if args[i].startswith('^'):
223 if args[i].startswith('^'):
223 s = repo.lookup(args[i][1:])
224 s = repo.lookup(args[i][1:])
224 stop_sha1.append(s)
225 stop_sha1.append(s)
225 want_sha1.append(s)
226 want_sha1.append(s)
226 elif args[i] != 'HEAD':
227 elif args[i] != 'HEAD':
227 want_sha1.append(repo.lookup(args[i]))
228 want_sha1.append(repo.lookup(args[i]))
228
229
229 # calculate the graph for the supplied commits
230 # calculate the graph for the supplied commits
230 for i in xrange(len(want_sha1)):
231 for i in xrange(len(want_sha1)):
231 reachable.append({});
232 reachable.append({});
232 n = want_sha1[i];
233 n = want_sha1[i];
233 visit = [n];
234 visit = [n];
234 reachable[i][n] = 1
235 reachable[i][n] = 1
235 while visit:
236 while visit:
236 n = visit.pop(0)
237 n = visit.pop(0)
237 if n in stop_sha1:
238 if n in stop_sha1:
238 continue
239 continue
239 for p in repo.changelog.parents(n):
240 for p in repo.changelog.parents(n):
240 if p not in reachable[i]:
241 if p not in reachable[i]:
241 reachable[i][p] = 1
242 reachable[i][p] = 1
242 visit.append(p)
243 visit.append(p)
243 if p in stop_sha1:
244 if p in stop_sha1:
244 continue
245 continue
245
246
246 # walk the repository looking for commits that are in our
247 # walk the repository looking for commits that are in our
247 # reachability graph
248 # reachability graph
248 for i, ctx in chlogwalk():
249 for i, ctx in chlogwalk():
249 n = repo.changelog.node(i)
250 n = repo.changelog.node(i)
250 mask = is_reachable(want_sha1, reachable, n)
251 mask = is_reachable(want_sha1, reachable, n)
251 if mask:
252 if mask:
252 parentstr = ""
253 parentstr = ""
253 if parents:
254 if parents:
254 pp = repo.changelog.parents(n)
255 pp = repo.changelog.parents(n)
255 if pp[0] != hg.nullid:
256 if pp[0] != nullid:
256 parentstr += " " + hg.short(pp[0])
257 parentstr += " " + short(pp[0])
257 if pp[1] != hg.nullid:
258 if pp[1] != nullid:
258 parentstr += " " + hg.short(pp[1])
259 parentstr += " " + short(pp[1])
259 if not full:
260 if not full:
260 ui.write("%s%s\n" % (hg.short(n), parentstr))
261 ui.write("%s%s\n" % (short(n), parentstr))
261 elif full == "commit":
262 elif full == "commit":
262 ui.write("%s%s\n" % (hg.short(n), parentstr))
263 ui.write("%s%s\n" % (short(n), parentstr))
263 catcommit(ui, repo, n, ' ', ctx)
264 catcommit(ui, repo, n, ' ', ctx)
264 else:
265 else:
265 (p1, p2) = repo.changelog.parents(n)
266 (p1, p2) = repo.changelog.parents(n)
266 (h, h1, h2) = map(hg.short, (n, p1, p2))
267 (h, h1, h2) = map(short, (n, p1, p2))
267 (i1, i2) = map(repo.changelog.rev, (p1, p2))
268 (i1, i2) = map(repo.changelog.rev, (p1, p2))
268
269
269 date = ctx.date()[0]
270 date = ctx.date()[0]
270 ui.write("%s %s:%s" % (date, h, mask))
271 ui.write("%s %s:%s" % (date, h, mask))
271 mask = is_reachable(want_sha1, reachable, p1)
272 mask = is_reachable(want_sha1, reachable, p1)
272 if i1 != hg.nullrev and mask > 0:
273 if i1 != nullrev and mask > 0:
273 ui.write("%s:%s " % (h1, mask)),
274 ui.write("%s:%s " % (h1, mask)),
274 mask = is_reachable(want_sha1, reachable, p2)
275 mask = is_reachable(want_sha1, reachable, p2)
275 if i2 != hg.nullrev and mask > 0:
276 if i2 != nullrev and mask > 0:
276 ui.write("%s:%s " % (h2, mask))
277 ui.write("%s:%s " % (h2, mask))
277 ui.write("\n")
278 ui.write("\n")
278 if maxnr and count >= maxnr:
279 if maxnr and count >= maxnr:
279 break
280 break
280 count += 1
281 count += 1
281
282
282 def revparse(ui, repo, *revs, **opts):
283 def revparse(ui, repo, *revs, **opts):
283 """Parse given revisions"""
284 """Parse given revisions"""
284 def revstr(rev):
285 def revstr(rev):
285 if rev == 'HEAD':
286 if rev == 'HEAD':
286 rev = 'tip'
287 rev = 'tip'
287 return revlog.hex(repo.lookup(rev))
288 return revlog.hex(repo.lookup(rev))
288
289
289 for r in revs:
290 for r in revs:
290 revrange = r.split(':', 1)
291 revrange = r.split(':', 1)
291 ui.write('%s\n' % revstr(revrange[0]))
292 ui.write('%s\n' % revstr(revrange[0]))
292 if len(revrange) == 2:
293 if len(revrange) == 2:
293 ui.write('^%s\n' % revstr(revrange[1]))
294 ui.write('^%s\n' % revstr(revrange[1]))
294
295
295 # git rev-list tries to order things by date, and has the ability to stop
296 # git rev-list tries to order things by date, and has the ability to stop
296 # at a given commit without walking the whole repo. TODO add the stop
297 # at a given commit without walking the whole repo. TODO add the stop
297 # parameter
298 # parameter
298 def revlist(ui, repo, *revs, **opts):
299 def revlist(ui, repo, *revs, **opts):
299 """print revisions"""
300 """print revisions"""
300 if opts['header']:
301 if opts['header']:
301 full = "commit"
302 full = "commit"
302 else:
303 else:
303 full = None
304 full = None
304 copy = [x for x in revs]
305 copy = [x for x in revs]
305 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
306 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
306
307
307 def config(ui, repo, **opts):
308 def config(ui, repo, **opts):
308 """print extension options"""
309 """print extension options"""
309 def writeopt(name, value):
310 def writeopt(name, value):
310 ui.write('k=%s\nv=%s\n' % (name, value))
311 ui.write('k=%s\nv=%s\n' % (name, value))
311
312
312 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
313 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
313
314
314
315
315 def view(ui, repo, *etc, **opts):
316 def view(ui, repo, *etc, **opts):
316 "start interactive history viewer"
317 "start interactive history viewer"
317 os.chdir(repo.root)
318 os.chdir(repo.root)
318 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
319 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
319 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
320 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
320 ui.debug("running %s\n" % cmd)
321 ui.debug("running %s\n" % cmd)
321 util.system(cmd)
322 util.system(cmd)
322
323
323 cmdtable = {
324 cmdtable = {
324 "^view":
325 "^view":
325 (view,
326 (view,
326 [('l', 'limit', '', 'limit number of changes displayed')],
327 [('l', 'limit', '', 'limit number of changes displayed')],
327 'hg view [-l LIMIT] [REVRANGE]'),
328 'hg view [-l LIMIT] [REVRANGE]'),
328 "debug-diff-tree":
329 "debug-diff-tree":
329 (difftree,
330 (difftree,
330 [('p', 'patch', None, 'generate patch'),
331 [('p', 'patch', None, 'generate patch'),
331 ('r', 'recursive', None, 'recursive'),
332 ('r', 'recursive', None, 'recursive'),
332 ('P', 'pretty', None, 'pretty'),
333 ('P', 'pretty', None, 'pretty'),
333 ('s', 'stdin', None, 'stdin'),
334 ('s', 'stdin', None, 'stdin'),
334 ('C', 'copy', None, 'detect copies'),
335 ('C', 'copy', None, 'detect copies'),
335 ('S', 'search', "", 'search')],
336 ('S', 'search', "", 'search')],
336 'hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...'),
337 'hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...'),
337 "debug-cat-file":
338 "debug-cat-file":
338 (catfile,
339 (catfile,
339 [('s', 'stdin', None, 'stdin')],
340 [('s', 'stdin', None, 'stdin')],
340 'hg debug-cat-file [OPTION]... TYPE FILE'),
341 'hg debug-cat-file [OPTION]... TYPE FILE'),
341 "debug-config":
342 "debug-config":
342 (config, [], 'hg debug-config'),
343 (config, [], 'hg debug-config'),
343 "debug-merge-base":
344 "debug-merge-base":
344 (base, [], 'hg debug-merge-base node node'),
345 (base, [], 'hg debug-merge-base node node'),
345 "debug-rev-parse":
346 "debug-rev-parse":
346 (revparse,
347 (revparse,
347 [('', 'default', '', 'ignored')],
348 [('', 'default', '', 'ignored')],
348 'hg debug-rev-parse REV'),
349 'hg debug-rev-parse REV'),
349 "debug-rev-list":
350 "debug-rev-list":
350 (revlist,
351 (revlist,
351 [('H', 'header', None, 'header'),
352 [('H', 'header', None, 'header'),
352 ('t', 'topo-order', None, 'topo-order'),
353 ('t', 'topo-order', None, 'topo-order'),
353 ('p', 'parents', None, 'parents'),
354 ('p', 'parents', None, 'parents'),
354 ('n', 'max-count', 0, 'max-count')],
355 ('n', 'max-count', 0, 'max-count')],
355 'hg debug-rev-list [options] revs'),
356 'hg debug-rev-list [options] revs'),
356 }
357 }
@@ -1,2352 +1,2354 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 '''patch management and development
8 '''patch management and development
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 print name of top applied patch qtop
25 print name of top applied patch qtop
26
26
27 add known patch to applied stack qpush
27 add known patch to applied stack qpush
28 remove patch from applied stack qpop
28 remove patch from applied stack qpop
29 refresh contents of top applied patch qrefresh
29 refresh contents of top applied patch qrefresh
30 '''
30 '''
31
31
32 from mercurial.i18n import _
32 from mercurial.i18n import _
33 from mercurial.node import bin, hex, short
34 from mercurial.repo import RepoError
33 from mercurial import commands, cmdutil, hg, patch, revlog, util
35 from mercurial import commands, cmdutil, hg, patch, revlog, util
34 from mercurial import repair
36 from mercurial import repair
35 import os, sys, re, errno
37 import os, sys, re, errno
36
38
37 commands.norepo += " qclone"
39 commands.norepo += " qclone"
38
40
39 # Patch names looks like unix-file names.
41 # Patch names looks like unix-file names.
40 # They must be joinable with queue directory and result in the patch path.
42 # They must be joinable with queue directory and result in the patch path.
41 normname = util.normpath
43 normname = util.normpath
42
44
43 class statusentry:
45 class statusentry:
44 def __init__(self, rev, name=None):
46 def __init__(self, rev, name=None):
45 if not name:
47 if not name:
46 fields = rev.split(':', 1)
48 fields = rev.split(':', 1)
47 if len(fields) == 2:
49 if len(fields) == 2:
48 self.rev, self.name = fields
50 self.rev, self.name = fields
49 else:
51 else:
50 self.rev, self.name = None, None
52 self.rev, self.name = None, None
51 else:
53 else:
52 self.rev, self.name = rev, name
54 self.rev, self.name = rev, name
53
55
54 def __str__(self):
56 def __str__(self):
55 return self.rev + ':' + self.name
57 return self.rev + ':' + self.name
56
58
57 class queue:
59 class queue:
58 def __init__(self, ui, path, patchdir=None):
60 def __init__(self, ui, path, patchdir=None):
59 self.basepath = path
61 self.basepath = path
60 self.path = patchdir or os.path.join(path, "patches")
62 self.path = patchdir or os.path.join(path, "patches")
61 self.opener = util.opener(self.path)
63 self.opener = util.opener(self.path)
62 self.ui = ui
64 self.ui = ui
63 self.applied = []
65 self.applied = []
64 self.full_series = []
66 self.full_series = []
65 self.applied_dirty = 0
67 self.applied_dirty = 0
66 self.series_dirty = 0
68 self.series_dirty = 0
67 self.series_path = "series"
69 self.series_path = "series"
68 self.status_path = "status"
70 self.status_path = "status"
69 self.guards_path = "guards"
71 self.guards_path = "guards"
70 self.active_guards = None
72 self.active_guards = None
71 self.guards_dirty = False
73 self.guards_dirty = False
72 self._diffopts = None
74 self._diffopts = None
73
75
74 if os.path.exists(self.join(self.series_path)):
76 if os.path.exists(self.join(self.series_path)):
75 self.full_series = self.opener(self.series_path).read().splitlines()
77 self.full_series = self.opener(self.series_path).read().splitlines()
76 self.parse_series()
78 self.parse_series()
77
79
78 if os.path.exists(self.join(self.status_path)):
80 if os.path.exists(self.join(self.status_path)):
79 lines = self.opener(self.status_path).read().splitlines()
81 lines = self.opener(self.status_path).read().splitlines()
80 self.applied = [statusentry(l) for l in lines]
82 self.applied = [statusentry(l) for l in lines]
81
83
82 def diffopts(self):
84 def diffopts(self):
83 if self._diffopts is None:
85 if self._diffopts is None:
84 self._diffopts = patch.diffopts(self.ui)
86 self._diffopts = patch.diffopts(self.ui)
85 return self._diffopts
87 return self._diffopts
86
88
87 def join(self, *p):
89 def join(self, *p):
88 return os.path.join(self.path, *p)
90 return os.path.join(self.path, *p)
89
91
90 def find_series(self, patch):
92 def find_series(self, patch):
91 pre = re.compile("(\s*)([^#]+)")
93 pre = re.compile("(\s*)([^#]+)")
92 index = 0
94 index = 0
93 for l in self.full_series:
95 for l in self.full_series:
94 m = pre.match(l)
96 m = pre.match(l)
95 if m:
97 if m:
96 s = m.group(2)
98 s = m.group(2)
97 s = s.rstrip()
99 s = s.rstrip()
98 if s == patch:
100 if s == patch:
99 return index
101 return index
100 index += 1
102 index += 1
101 return None
103 return None
102
104
103 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
105 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
104
106
105 def parse_series(self):
107 def parse_series(self):
106 self.series = []
108 self.series = []
107 self.series_guards = []
109 self.series_guards = []
108 for l in self.full_series:
110 for l in self.full_series:
109 h = l.find('#')
111 h = l.find('#')
110 if h == -1:
112 if h == -1:
111 patch = l
113 patch = l
112 comment = ''
114 comment = ''
113 elif h == 0:
115 elif h == 0:
114 continue
116 continue
115 else:
117 else:
116 patch = l[:h]
118 patch = l[:h]
117 comment = l[h:]
119 comment = l[h:]
118 patch = patch.strip()
120 patch = patch.strip()
119 if patch:
121 if patch:
120 if patch in self.series:
122 if patch in self.series:
121 raise util.Abort(_('%s appears more than once in %s') %
123 raise util.Abort(_('%s appears more than once in %s') %
122 (patch, self.join(self.series_path)))
124 (patch, self.join(self.series_path)))
123 self.series.append(patch)
125 self.series.append(patch)
124 self.series_guards.append(self.guard_re.findall(comment))
126 self.series_guards.append(self.guard_re.findall(comment))
125
127
126 def check_guard(self, guard):
128 def check_guard(self, guard):
127 bad_chars = '# \t\r\n\f'
129 bad_chars = '# \t\r\n\f'
128 first = guard[0]
130 first = guard[0]
129 for c in '-+':
131 for c in '-+':
130 if first == c:
132 if first == c:
131 return (_('guard %r starts with invalid character: %r') %
133 return (_('guard %r starts with invalid character: %r') %
132 (guard, c))
134 (guard, c))
133 for c in bad_chars:
135 for c in bad_chars:
134 if c in guard:
136 if c in guard:
135 return _('invalid character in guard %r: %r') % (guard, c)
137 return _('invalid character in guard %r: %r') % (guard, c)
136
138
137 def set_active(self, guards):
139 def set_active(self, guards):
138 for guard in guards:
140 for guard in guards:
139 bad = self.check_guard(guard)
141 bad = self.check_guard(guard)
140 if bad:
142 if bad:
141 raise util.Abort(bad)
143 raise util.Abort(bad)
142 guards = dict.fromkeys(guards).keys()
144 guards = dict.fromkeys(guards).keys()
143 guards.sort()
145 guards.sort()
144 self.ui.debug('active guards: %s\n' % ' '.join(guards))
146 self.ui.debug('active guards: %s\n' % ' '.join(guards))
145 self.active_guards = guards
147 self.active_guards = guards
146 self.guards_dirty = True
148 self.guards_dirty = True
147
149
148 def active(self):
150 def active(self):
149 if self.active_guards is None:
151 if self.active_guards is None:
150 self.active_guards = []
152 self.active_guards = []
151 try:
153 try:
152 guards = self.opener(self.guards_path).read().split()
154 guards = self.opener(self.guards_path).read().split()
153 except IOError, err:
155 except IOError, err:
154 if err.errno != errno.ENOENT: raise
156 if err.errno != errno.ENOENT: raise
155 guards = []
157 guards = []
156 for i, guard in enumerate(guards):
158 for i, guard in enumerate(guards):
157 bad = self.check_guard(guard)
159 bad = self.check_guard(guard)
158 if bad:
160 if bad:
159 self.ui.warn('%s:%d: %s\n' %
161 self.ui.warn('%s:%d: %s\n' %
160 (self.join(self.guards_path), i + 1, bad))
162 (self.join(self.guards_path), i + 1, bad))
161 else:
163 else:
162 self.active_guards.append(guard)
164 self.active_guards.append(guard)
163 return self.active_guards
165 return self.active_guards
164
166
165 def set_guards(self, idx, guards):
167 def set_guards(self, idx, guards):
166 for g in guards:
168 for g in guards:
167 if len(g) < 2:
169 if len(g) < 2:
168 raise util.Abort(_('guard %r too short') % g)
170 raise util.Abort(_('guard %r too short') % g)
169 if g[0] not in '-+':
171 if g[0] not in '-+':
170 raise util.Abort(_('guard %r starts with invalid char') % g)
172 raise util.Abort(_('guard %r starts with invalid char') % g)
171 bad = self.check_guard(g[1:])
173 bad = self.check_guard(g[1:])
172 if bad:
174 if bad:
173 raise util.Abort(bad)
175 raise util.Abort(bad)
174 drop = self.guard_re.sub('', self.full_series[idx])
176 drop = self.guard_re.sub('', self.full_series[idx])
175 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
177 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
176 self.parse_series()
178 self.parse_series()
177 self.series_dirty = True
179 self.series_dirty = True
178
180
179 def pushable(self, idx):
181 def pushable(self, idx):
180 if isinstance(idx, str):
182 if isinstance(idx, str):
181 idx = self.series.index(idx)
183 idx = self.series.index(idx)
182 patchguards = self.series_guards[idx]
184 patchguards = self.series_guards[idx]
183 if not patchguards:
185 if not patchguards:
184 return True, None
186 return True, None
185 default = False
187 default = False
186 guards = self.active()
188 guards = self.active()
187 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
189 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
188 if exactneg:
190 if exactneg:
189 return False, exactneg[0]
191 return False, exactneg[0]
190 pos = [g for g in patchguards if g[0] == '+']
192 pos = [g for g in patchguards if g[0] == '+']
191 exactpos = [g for g in pos if g[1:] in guards]
193 exactpos = [g for g in pos if g[1:] in guards]
192 if pos:
194 if pos:
193 if exactpos:
195 if exactpos:
194 return True, exactpos[0]
196 return True, exactpos[0]
195 return False, pos
197 return False, pos
196 return True, ''
198 return True, ''
197
199
198 def explain_pushable(self, idx, all_patches=False):
200 def explain_pushable(self, idx, all_patches=False):
199 write = all_patches and self.ui.write or self.ui.warn
201 write = all_patches and self.ui.write or self.ui.warn
200 if all_patches or self.ui.verbose:
202 if all_patches or self.ui.verbose:
201 if isinstance(idx, str):
203 if isinstance(idx, str):
202 idx = self.series.index(idx)
204 idx = self.series.index(idx)
203 pushable, why = self.pushable(idx)
205 pushable, why = self.pushable(idx)
204 if all_patches and pushable:
206 if all_patches and pushable:
205 if why is None:
207 if why is None:
206 write(_('allowing %s - no guards in effect\n') %
208 write(_('allowing %s - no guards in effect\n') %
207 self.series[idx])
209 self.series[idx])
208 else:
210 else:
209 if not why:
211 if not why:
210 write(_('allowing %s - no matching negative guards\n') %
212 write(_('allowing %s - no matching negative guards\n') %
211 self.series[idx])
213 self.series[idx])
212 else:
214 else:
213 write(_('allowing %s - guarded by %r\n') %
215 write(_('allowing %s - guarded by %r\n') %
214 (self.series[idx], why))
216 (self.series[idx], why))
215 if not pushable:
217 if not pushable:
216 if why:
218 if why:
217 write(_('skipping %s - guarded by %r\n') %
219 write(_('skipping %s - guarded by %r\n') %
218 (self.series[idx], why))
220 (self.series[idx], why))
219 else:
221 else:
220 write(_('skipping %s - no matching guards\n') %
222 write(_('skipping %s - no matching guards\n') %
221 self.series[idx])
223 self.series[idx])
222
224
223 def save_dirty(self):
225 def save_dirty(self):
224 def write_list(items, path):
226 def write_list(items, path):
225 fp = self.opener(path, 'w')
227 fp = self.opener(path, 'w')
226 for i in items:
228 for i in items:
227 fp.write("%s\n" % i)
229 fp.write("%s\n" % i)
228 fp.close()
230 fp.close()
229 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
231 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
230 if self.series_dirty: write_list(self.full_series, self.series_path)
232 if self.series_dirty: write_list(self.full_series, self.series_path)
231 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
233 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
232
234
233 def readheaders(self, patch):
235 def readheaders(self, patch):
234 def eatdiff(lines):
236 def eatdiff(lines):
235 while lines:
237 while lines:
236 l = lines[-1]
238 l = lines[-1]
237 if (l.startswith("diff -") or
239 if (l.startswith("diff -") or
238 l.startswith("Index:") or
240 l.startswith("Index:") or
239 l.startswith("===========")):
241 l.startswith("===========")):
240 del lines[-1]
242 del lines[-1]
241 else:
243 else:
242 break
244 break
243 def eatempty(lines):
245 def eatempty(lines):
244 while lines:
246 while lines:
245 l = lines[-1]
247 l = lines[-1]
246 if re.match('\s*$', l):
248 if re.match('\s*$', l):
247 del lines[-1]
249 del lines[-1]
248 else:
250 else:
249 break
251 break
250
252
251 pf = self.join(patch)
253 pf = self.join(patch)
252 message = []
254 message = []
253 comments = []
255 comments = []
254 user = None
256 user = None
255 date = None
257 date = None
256 format = None
258 format = None
257 subject = None
259 subject = None
258 diffstart = 0
260 diffstart = 0
259
261
260 for line in file(pf):
262 for line in file(pf):
261 line = line.rstrip()
263 line = line.rstrip()
262 if line.startswith('diff --git'):
264 if line.startswith('diff --git'):
263 diffstart = 2
265 diffstart = 2
264 break
266 break
265 if diffstart:
267 if diffstart:
266 if line.startswith('+++ '):
268 if line.startswith('+++ '):
267 diffstart = 2
269 diffstart = 2
268 break
270 break
269 if line.startswith("--- "):
271 if line.startswith("--- "):
270 diffstart = 1
272 diffstart = 1
271 continue
273 continue
272 elif format == "hgpatch":
274 elif format == "hgpatch":
273 # parse values when importing the result of an hg export
275 # parse values when importing the result of an hg export
274 if line.startswith("# User "):
276 if line.startswith("# User "):
275 user = line[7:]
277 user = line[7:]
276 elif line.startswith("# Date "):
278 elif line.startswith("# Date "):
277 date = line[7:]
279 date = line[7:]
278 elif not line.startswith("# ") and line:
280 elif not line.startswith("# ") and line:
279 message.append(line)
281 message.append(line)
280 format = None
282 format = None
281 elif line == '# HG changeset patch':
283 elif line == '# HG changeset patch':
282 format = "hgpatch"
284 format = "hgpatch"
283 elif (format != "tagdone" and (line.startswith("Subject: ") or
285 elif (format != "tagdone" and (line.startswith("Subject: ") or
284 line.startswith("subject: "))):
286 line.startswith("subject: "))):
285 subject = line[9:]
287 subject = line[9:]
286 format = "tag"
288 format = "tag"
287 elif (format != "tagdone" and (line.startswith("From: ") or
289 elif (format != "tagdone" and (line.startswith("From: ") or
288 line.startswith("from: "))):
290 line.startswith("from: "))):
289 user = line[6:]
291 user = line[6:]
290 format = "tag"
292 format = "tag"
291 elif format == "tag" and line == "":
293 elif format == "tag" and line == "":
292 # when looking for tags (subject: from: etc) they
294 # when looking for tags (subject: from: etc) they
293 # end once you find a blank line in the source
295 # end once you find a blank line in the source
294 format = "tagdone"
296 format = "tagdone"
295 elif message or line:
297 elif message or line:
296 message.append(line)
298 message.append(line)
297 comments.append(line)
299 comments.append(line)
298
300
299 eatdiff(message)
301 eatdiff(message)
300 eatdiff(comments)
302 eatdiff(comments)
301 eatempty(message)
303 eatempty(message)
302 eatempty(comments)
304 eatempty(comments)
303
305
304 # make sure message isn't empty
306 # make sure message isn't empty
305 if format and format.startswith("tag") and subject:
307 if format and format.startswith("tag") and subject:
306 message.insert(0, "")
308 message.insert(0, "")
307 message.insert(0, subject)
309 message.insert(0, subject)
308 return (message, comments, user, date, diffstart > 1)
310 return (message, comments, user, date, diffstart > 1)
309
311
310 def removeundo(self, repo):
312 def removeundo(self, repo):
311 undo = repo.sjoin('undo')
313 undo = repo.sjoin('undo')
312 if not os.path.exists(undo):
314 if not os.path.exists(undo):
313 return
315 return
314 try:
316 try:
315 os.unlink(undo)
317 os.unlink(undo)
316 except OSError, inst:
318 except OSError, inst:
317 self.ui.warn('error removing undo: %s\n' % str(inst))
319 self.ui.warn('error removing undo: %s\n' % str(inst))
318
320
319 def printdiff(self, repo, node1, node2=None, files=None,
321 def printdiff(self, repo, node1, node2=None, files=None,
320 fp=None, changes=None, opts={}):
322 fp=None, changes=None, opts={}):
321 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
323 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
322
324
323 patch.diff(repo, node1, node2, fns, match=matchfn,
325 patch.diff(repo, node1, node2, fns, match=matchfn,
324 fp=fp, changes=changes, opts=self.diffopts())
326 fp=fp, changes=changes, opts=self.diffopts())
325
327
326 def mergeone(self, repo, mergeq, head, patch, rev):
328 def mergeone(self, repo, mergeq, head, patch, rev):
327 # first try just applying the patch
329 # first try just applying the patch
328 (err, n) = self.apply(repo, [ patch ], update_status=False,
330 (err, n) = self.apply(repo, [ patch ], update_status=False,
329 strict=True, merge=rev)
331 strict=True, merge=rev)
330
332
331 if err == 0:
333 if err == 0:
332 return (err, n)
334 return (err, n)
333
335
334 if n is None:
336 if n is None:
335 raise util.Abort(_("apply failed for patch %s") % patch)
337 raise util.Abort(_("apply failed for patch %s") % patch)
336
338
337 self.ui.warn("patch didn't work out, merging %s\n" % patch)
339 self.ui.warn("patch didn't work out, merging %s\n" % patch)
338
340
339 # apply failed, strip away that rev and merge.
341 # apply failed, strip away that rev and merge.
340 hg.clean(repo, head)
342 hg.clean(repo, head)
341 self.strip(repo, n, update=False, backup='strip')
343 self.strip(repo, n, update=False, backup='strip')
342
344
343 ctx = repo.changectx(rev)
345 ctx = repo.changectx(rev)
344 ret = hg.merge(repo, rev)
346 ret = hg.merge(repo, rev)
345 if ret:
347 if ret:
346 raise util.Abort(_("update returned %d") % ret)
348 raise util.Abort(_("update returned %d") % ret)
347 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
349 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
348 if n == None:
350 if n == None:
349 raise util.Abort(_("repo commit failed"))
351 raise util.Abort(_("repo commit failed"))
350 try:
352 try:
351 message, comments, user, date, patchfound = mergeq.readheaders(patch)
353 message, comments, user, date, patchfound = mergeq.readheaders(patch)
352 except:
354 except:
353 raise util.Abort(_("unable to read %s") % patch)
355 raise util.Abort(_("unable to read %s") % patch)
354
356
355 patchf = self.opener(patch, "w")
357 patchf = self.opener(patch, "w")
356 if comments:
358 if comments:
357 comments = "\n".join(comments) + '\n\n'
359 comments = "\n".join(comments) + '\n\n'
358 patchf.write(comments)
360 patchf.write(comments)
359 self.printdiff(repo, head, n, fp=patchf)
361 self.printdiff(repo, head, n, fp=patchf)
360 patchf.close()
362 patchf.close()
361 self.removeundo(repo)
363 self.removeundo(repo)
362 return (0, n)
364 return (0, n)
363
365
364 def qparents(self, repo, rev=None):
366 def qparents(self, repo, rev=None):
365 if rev is None:
367 if rev is None:
366 (p1, p2) = repo.dirstate.parents()
368 (p1, p2) = repo.dirstate.parents()
367 if p2 == revlog.nullid:
369 if p2 == revlog.nullid:
368 return p1
370 return p1
369 if len(self.applied) == 0:
371 if len(self.applied) == 0:
370 return None
372 return None
371 return revlog.bin(self.applied[-1].rev)
373 return revlog.bin(self.applied[-1].rev)
372 pp = repo.changelog.parents(rev)
374 pp = repo.changelog.parents(rev)
373 if pp[1] != revlog.nullid:
375 if pp[1] != revlog.nullid:
374 arevs = [ x.rev for x in self.applied ]
376 arevs = [ x.rev for x in self.applied ]
375 p0 = revlog.hex(pp[0])
377 p0 = revlog.hex(pp[0])
376 p1 = revlog.hex(pp[1])
378 p1 = revlog.hex(pp[1])
377 if p0 in arevs:
379 if p0 in arevs:
378 return pp[0]
380 return pp[0]
379 if p1 in arevs:
381 if p1 in arevs:
380 return pp[1]
382 return pp[1]
381 return pp[0]
383 return pp[0]
382
384
383 def mergepatch(self, repo, mergeq, series):
385 def mergepatch(self, repo, mergeq, series):
384 if len(self.applied) == 0:
386 if len(self.applied) == 0:
385 # each of the patches merged in will have two parents. This
387 # each of the patches merged in will have two parents. This
386 # can confuse the qrefresh, qdiff, and strip code because it
388 # can confuse the qrefresh, qdiff, and strip code because it
387 # needs to know which parent is actually in the patch queue.
389 # needs to know which parent is actually in the patch queue.
388 # so, we insert a merge marker with only one parent. This way
390 # so, we insert a merge marker with only one parent. This way
389 # the first patch in the queue is never a merge patch
391 # the first patch in the queue is never a merge patch
390 #
392 #
391 pname = ".hg.patches.merge.marker"
393 pname = ".hg.patches.merge.marker"
392 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
394 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
393 self.removeundo(repo)
395 self.removeundo(repo)
394 self.applied.append(statusentry(revlog.hex(n), pname))
396 self.applied.append(statusentry(revlog.hex(n), pname))
395 self.applied_dirty = 1
397 self.applied_dirty = 1
396
398
397 head = self.qparents(repo)
399 head = self.qparents(repo)
398
400
399 for patch in series:
401 for patch in series:
400 patch = mergeq.lookup(patch, strict=True)
402 patch = mergeq.lookup(patch, strict=True)
401 if not patch:
403 if not patch:
402 self.ui.warn("patch %s does not exist\n" % patch)
404 self.ui.warn("patch %s does not exist\n" % patch)
403 return (1, None)
405 return (1, None)
404 pushable, reason = self.pushable(patch)
406 pushable, reason = self.pushable(patch)
405 if not pushable:
407 if not pushable:
406 self.explain_pushable(patch, all_patches=True)
408 self.explain_pushable(patch, all_patches=True)
407 continue
409 continue
408 info = mergeq.isapplied(patch)
410 info = mergeq.isapplied(patch)
409 if not info:
411 if not info:
410 self.ui.warn("patch %s is not applied\n" % patch)
412 self.ui.warn("patch %s is not applied\n" % patch)
411 return (1, None)
413 return (1, None)
412 rev = revlog.bin(info[1])
414 rev = revlog.bin(info[1])
413 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
415 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
414 if head:
416 if head:
415 self.applied.append(statusentry(revlog.hex(head), patch))
417 self.applied.append(statusentry(revlog.hex(head), patch))
416 self.applied_dirty = 1
418 self.applied_dirty = 1
417 if err:
419 if err:
418 return (err, head)
420 return (err, head)
419 self.save_dirty()
421 self.save_dirty()
420 return (0, head)
422 return (0, head)
421
423
422 def patch(self, repo, patchfile):
424 def patch(self, repo, patchfile):
423 '''Apply patchfile to the working directory.
425 '''Apply patchfile to the working directory.
424 patchfile: file name of patch'''
426 patchfile: file name of patch'''
425 files = {}
427 files = {}
426 try:
428 try:
427 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
429 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
428 files=files)
430 files=files)
429 except Exception, inst:
431 except Exception, inst:
430 self.ui.note(str(inst) + '\n')
432 self.ui.note(str(inst) + '\n')
431 if not self.ui.verbose:
433 if not self.ui.verbose:
432 self.ui.warn("patch failed, unable to continue (try -v)\n")
434 self.ui.warn("patch failed, unable to continue (try -v)\n")
433 return (False, files, False)
435 return (False, files, False)
434
436
435 return (True, files, fuzz)
437 return (True, files, fuzz)
436
438
437 def apply(self, repo, series, list=False, update_status=True,
439 def apply(self, repo, series, list=False, update_status=True,
438 strict=False, patchdir=None, merge=None, all_files={}):
440 strict=False, patchdir=None, merge=None, all_files={}):
439 wlock = lock = tr = None
441 wlock = lock = tr = None
440 try:
442 try:
441 wlock = repo.wlock()
443 wlock = repo.wlock()
442 lock = repo.lock()
444 lock = repo.lock()
443 tr = repo.transaction()
445 tr = repo.transaction()
444 try:
446 try:
445 ret = self._apply(repo, series, list, update_status,
447 ret = self._apply(repo, series, list, update_status,
446 strict, patchdir, merge, all_files=all_files)
448 strict, patchdir, merge, all_files=all_files)
447 tr.close()
449 tr.close()
448 self.save_dirty()
450 self.save_dirty()
449 return ret
451 return ret
450 except:
452 except:
451 try:
453 try:
452 tr.abort()
454 tr.abort()
453 finally:
455 finally:
454 repo.invalidate()
456 repo.invalidate()
455 repo.dirstate.invalidate()
457 repo.dirstate.invalidate()
456 raise
458 raise
457 finally:
459 finally:
458 del tr, lock, wlock
460 del tr, lock, wlock
459 self.removeundo(repo)
461 self.removeundo(repo)
460
462
461 def _apply(self, repo, series, list=False, update_status=True,
463 def _apply(self, repo, series, list=False, update_status=True,
462 strict=False, patchdir=None, merge=None, all_files={}):
464 strict=False, patchdir=None, merge=None, all_files={}):
463 # TODO unify with commands.py
465 # TODO unify with commands.py
464 if not patchdir:
466 if not patchdir:
465 patchdir = self.path
467 patchdir = self.path
466 err = 0
468 err = 0
467 n = None
469 n = None
468 for patchname in series:
470 for patchname in series:
469 pushable, reason = self.pushable(patchname)
471 pushable, reason = self.pushable(patchname)
470 if not pushable:
472 if not pushable:
471 self.explain_pushable(patchname, all_patches=True)
473 self.explain_pushable(patchname, all_patches=True)
472 continue
474 continue
473 self.ui.warn("applying %s\n" % patchname)
475 self.ui.warn("applying %s\n" % patchname)
474 pf = os.path.join(patchdir, patchname)
476 pf = os.path.join(patchdir, patchname)
475
477
476 try:
478 try:
477 message, comments, user, date, patchfound = self.readheaders(patchname)
479 message, comments, user, date, patchfound = self.readheaders(patchname)
478 except:
480 except:
479 self.ui.warn("Unable to read %s\n" % patchname)
481 self.ui.warn("Unable to read %s\n" % patchname)
480 err = 1
482 err = 1
481 break
483 break
482
484
483 if not message:
485 if not message:
484 message = "imported patch %s\n" % patchname
486 message = "imported patch %s\n" % patchname
485 else:
487 else:
486 if list:
488 if list:
487 message.append("\nimported patch %s" % patchname)
489 message.append("\nimported patch %s" % patchname)
488 message = '\n'.join(message)
490 message = '\n'.join(message)
489
491
490 (patcherr, files, fuzz) = self.patch(repo, pf)
492 (patcherr, files, fuzz) = self.patch(repo, pf)
491 all_files.update(files)
493 all_files.update(files)
492 patcherr = not patcherr
494 patcherr = not patcherr
493
495
494 if merge and files:
496 if merge and files:
495 # Mark as removed/merged and update dirstate parent info
497 # Mark as removed/merged and update dirstate parent info
496 removed = []
498 removed = []
497 merged = []
499 merged = []
498 for f in files:
500 for f in files:
499 if os.path.exists(repo.wjoin(f)):
501 if os.path.exists(repo.wjoin(f)):
500 merged.append(f)
502 merged.append(f)
501 else:
503 else:
502 removed.append(f)
504 removed.append(f)
503 for f in removed:
505 for f in removed:
504 repo.dirstate.remove(f)
506 repo.dirstate.remove(f)
505 for f in merged:
507 for f in merged:
506 repo.dirstate.merge(f)
508 repo.dirstate.merge(f)
507 p1, p2 = repo.dirstate.parents()
509 p1, p2 = repo.dirstate.parents()
508 repo.dirstate.setparents(p1, merge)
510 repo.dirstate.setparents(p1, merge)
509 files = patch.updatedir(self.ui, repo, files)
511 files = patch.updatedir(self.ui, repo, files)
510 n = repo.commit(files, message, user, date, force=1)
512 n = repo.commit(files, message, user, date, force=1)
511
513
512 if n == None:
514 if n == None:
513 raise util.Abort(_("repo commit failed"))
515 raise util.Abort(_("repo commit failed"))
514
516
515 if update_status:
517 if update_status:
516 self.applied.append(statusentry(revlog.hex(n), patchname))
518 self.applied.append(statusentry(revlog.hex(n), patchname))
517
519
518 if patcherr:
520 if patcherr:
519 if not patchfound:
521 if not patchfound:
520 self.ui.warn("patch %s is empty\n" % patchname)
522 self.ui.warn("patch %s is empty\n" % patchname)
521 err = 0
523 err = 0
522 else:
524 else:
523 self.ui.warn("patch failed, rejects left in working dir\n")
525 self.ui.warn("patch failed, rejects left in working dir\n")
524 err = 1
526 err = 1
525 break
527 break
526
528
527 if fuzz and strict:
529 if fuzz and strict:
528 self.ui.warn("fuzz found when applying patch, stopping\n")
530 self.ui.warn("fuzz found when applying patch, stopping\n")
529 err = 1
531 err = 1
530 break
532 break
531 return (err, n)
533 return (err, n)
532
534
533 def delete(self, repo, patches, opts):
535 def delete(self, repo, patches, opts):
534 if not patches and not opts.get('rev'):
536 if not patches and not opts.get('rev'):
535 raise util.Abort(_('qdelete requires at least one revision or '
537 raise util.Abort(_('qdelete requires at least one revision or '
536 'patch name'))
538 'patch name'))
537
539
538 realpatches = []
540 realpatches = []
539 for patch in patches:
541 for patch in patches:
540 patch = self.lookup(patch, strict=True)
542 patch = self.lookup(patch, strict=True)
541 info = self.isapplied(patch)
543 info = self.isapplied(patch)
542 if info:
544 if info:
543 raise util.Abort(_("cannot delete applied patch %s") % patch)
545 raise util.Abort(_("cannot delete applied patch %s") % patch)
544 if patch not in self.series:
546 if patch not in self.series:
545 raise util.Abort(_("patch %s not in series file") % patch)
547 raise util.Abort(_("patch %s not in series file") % patch)
546 realpatches.append(patch)
548 realpatches.append(patch)
547
549
548 appliedbase = 0
550 appliedbase = 0
549 if opts.get('rev'):
551 if opts.get('rev'):
550 if not self.applied:
552 if not self.applied:
551 raise util.Abort(_('no patches applied'))
553 raise util.Abort(_('no patches applied'))
552 revs = cmdutil.revrange(repo, opts['rev'])
554 revs = cmdutil.revrange(repo, opts['rev'])
553 if len(revs) > 1 and revs[0] > revs[1]:
555 if len(revs) > 1 and revs[0] > revs[1]:
554 revs.reverse()
556 revs.reverse()
555 for rev in revs:
557 for rev in revs:
556 if appliedbase >= len(self.applied):
558 if appliedbase >= len(self.applied):
557 raise util.Abort(_("revision %d is not managed") % rev)
559 raise util.Abort(_("revision %d is not managed") % rev)
558
560
559 base = revlog.bin(self.applied[appliedbase].rev)
561 base = revlog.bin(self.applied[appliedbase].rev)
560 node = repo.changelog.node(rev)
562 node = repo.changelog.node(rev)
561 if node != base:
563 if node != base:
562 raise util.Abort(_("cannot delete revision %d above "
564 raise util.Abort(_("cannot delete revision %d above "
563 "applied patches") % rev)
565 "applied patches") % rev)
564 realpatches.append(self.applied[appliedbase].name)
566 realpatches.append(self.applied[appliedbase].name)
565 appliedbase += 1
567 appliedbase += 1
566
568
567 if not opts.get('keep'):
569 if not opts.get('keep'):
568 r = self.qrepo()
570 r = self.qrepo()
569 if r:
571 if r:
570 r.remove(realpatches, True)
572 r.remove(realpatches, True)
571 else:
573 else:
572 for p in realpatches:
574 for p in realpatches:
573 os.unlink(self.join(p))
575 os.unlink(self.join(p))
574
576
575 if appliedbase:
577 if appliedbase:
576 del self.applied[:appliedbase]
578 del self.applied[:appliedbase]
577 self.applied_dirty = 1
579 self.applied_dirty = 1
578 indices = [self.find_series(p) for p in realpatches]
580 indices = [self.find_series(p) for p in realpatches]
579 indices.sort()
581 indices.sort()
580 for i in indices[-1::-1]:
582 for i in indices[-1::-1]:
581 del self.full_series[i]
583 del self.full_series[i]
582 self.parse_series()
584 self.parse_series()
583 self.series_dirty = 1
585 self.series_dirty = 1
584
586
585 def check_toppatch(self, repo):
587 def check_toppatch(self, repo):
586 if len(self.applied) > 0:
588 if len(self.applied) > 0:
587 top = revlog.bin(self.applied[-1].rev)
589 top = revlog.bin(self.applied[-1].rev)
588 pp = repo.dirstate.parents()
590 pp = repo.dirstate.parents()
589 if top not in pp:
591 if top not in pp:
590 raise util.Abort(_("working directory revision is not qtip"))
592 raise util.Abort(_("working directory revision is not qtip"))
591 return top
593 return top
592 return None
594 return None
593 def check_localchanges(self, repo, force=False, refresh=True):
595 def check_localchanges(self, repo, force=False, refresh=True):
594 m, a, r, d = repo.status()[:4]
596 m, a, r, d = repo.status()[:4]
595 if m or a or r or d:
597 if m or a or r or d:
596 if not force:
598 if not force:
597 if refresh:
599 if refresh:
598 raise util.Abort(_("local changes found, refresh first"))
600 raise util.Abort(_("local changes found, refresh first"))
599 else:
601 else:
600 raise util.Abort(_("local changes found"))
602 raise util.Abort(_("local changes found"))
601 return m, a, r, d
603 return m, a, r, d
602
604
603 _reserved = ('series', 'status', 'guards')
605 _reserved = ('series', 'status', 'guards')
604 def check_reserved_name(self, name):
606 def check_reserved_name(self, name):
605 if (name in self._reserved or name.startswith('.hg')
607 if (name in self._reserved or name.startswith('.hg')
606 or name.startswith('.mq')):
608 or name.startswith('.mq')):
607 raise util.Abort(_('"%s" cannot be used as the name of a patch')
609 raise util.Abort(_('"%s" cannot be used as the name of a patch')
608 % name)
610 % name)
609
611
610 def new(self, repo, patch, *pats, **opts):
612 def new(self, repo, patch, *pats, **opts):
611 msg = opts.get('msg')
613 msg = opts.get('msg')
612 force = opts.get('force')
614 force = opts.get('force')
613 user = opts.get('user')
615 user = opts.get('user')
614 date = opts.get('date')
616 date = opts.get('date')
615 if date:
617 if date:
616 date = util.parsedate(date)
618 date = util.parsedate(date)
617 self.check_reserved_name(patch)
619 self.check_reserved_name(patch)
618 if os.path.exists(self.join(patch)):
620 if os.path.exists(self.join(patch)):
619 raise util.Abort(_('patch "%s" already exists') % patch)
621 raise util.Abort(_('patch "%s" already exists') % patch)
620 if opts.get('include') or opts.get('exclude') or pats:
622 if opts.get('include') or opts.get('exclude') or pats:
621 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
623 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
622 m, a, r, d = repo.status(files=fns, match=match)[:4]
624 m, a, r, d = repo.status(files=fns, match=match)[:4]
623 else:
625 else:
624 m, a, r, d = self.check_localchanges(repo, force)
626 m, a, r, d = self.check_localchanges(repo, force)
625 fns, match, anypats = cmdutil.matchpats(repo, m + a + r)
627 fns, match, anypats = cmdutil.matchpats(repo, m + a + r)
626 commitfiles = m + a + r
628 commitfiles = m + a + r
627 self.check_toppatch(repo)
629 self.check_toppatch(repo)
628 wlock = repo.wlock()
630 wlock = repo.wlock()
629 try:
631 try:
630 insert = self.full_series_end()
632 insert = self.full_series_end()
631 commitmsg = msg and msg or ("[mq]: %s" % patch)
633 commitmsg = msg and msg or ("[mq]: %s" % patch)
632 n = repo.commit(commitfiles, commitmsg, user, date, match=match, force=True)
634 n = repo.commit(commitfiles, commitmsg, user, date, match=match, force=True)
633 if n == None:
635 if n == None:
634 raise util.Abort(_("repo commit failed"))
636 raise util.Abort(_("repo commit failed"))
635 self.full_series[insert:insert] = [patch]
637 self.full_series[insert:insert] = [patch]
636 self.applied.append(statusentry(revlog.hex(n), patch))
638 self.applied.append(statusentry(revlog.hex(n), patch))
637 self.parse_series()
639 self.parse_series()
638 self.series_dirty = 1
640 self.series_dirty = 1
639 self.applied_dirty = 1
641 self.applied_dirty = 1
640 p = self.opener(patch, "w")
642 p = self.opener(patch, "w")
641 if date:
643 if date:
642 p.write("# HG changeset patch\n")
644 p.write("# HG changeset patch\n")
643 if user:
645 if user:
644 p.write("# User " + user + "\n")
646 p.write("# User " + user + "\n")
645 p.write("# Date %d %d\n" % date)
647 p.write("# Date %d %d\n" % date)
646 p.write("\n")
648 p.write("\n")
647 elif user:
649 elif user:
648 p.write("From: " + user + "\n")
650 p.write("From: " + user + "\n")
649 p.write("\n")
651 p.write("\n")
650 if msg:
652 if msg:
651 msg = msg + "\n"
653 msg = msg + "\n"
652 p.write(msg)
654 p.write(msg)
653 p.close()
655 p.close()
654 wlock = None
656 wlock = None
655 r = self.qrepo()
657 r = self.qrepo()
656 if r: r.add([patch])
658 if r: r.add([patch])
657 if commitfiles:
659 if commitfiles:
658 self.refresh(repo, short=True, git=opts.get('git'))
660 self.refresh(repo, short=True, git=opts.get('git'))
659 self.removeundo(repo)
661 self.removeundo(repo)
660 finally:
662 finally:
661 del wlock
663 del wlock
662
664
663 def strip(self, repo, rev, update=True, backup="all"):
665 def strip(self, repo, rev, update=True, backup="all"):
664 wlock = lock = None
666 wlock = lock = None
665 try:
667 try:
666 wlock = repo.wlock()
668 wlock = repo.wlock()
667 lock = repo.lock()
669 lock = repo.lock()
668
670
669 if update:
671 if update:
670 self.check_localchanges(repo, refresh=False)
672 self.check_localchanges(repo, refresh=False)
671 urev = self.qparents(repo, rev)
673 urev = self.qparents(repo, rev)
672 hg.clean(repo, urev)
674 hg.clean(repo, urev)
673 repo.dirstate.write()
675 repo.dirstate.write()
674
676
675 self.removeundo(repo)
677 self.removeundo(repo)
676 repair.strip(self.ui, repo, rev, backup)
678 repair.strip(self.ui, repo, rev, backup)
677 # strip may have unbundled a set of backed up revisions after
679 # strip may have unbundled a set of backed up revisions after
678 # the actual strip
680 # the actual strip
679 self.removeundo(repo)
681 self.removeundo(repo)
680 finally:
682 finally:
681 del lock, wlock
683 del lock, wlock
682
684
683 def isapplied(self, patch):
685 def isapplied(self, patch):
684 """returns (index, rev, patch)"""
686 """returns (index, rev, patch)"""
685 for i in xrange(len(self.applied)):
687 for i in xrange(len(self.applied)):
686 a = self.applied[i]
688 a = self.applied[i]
687 if a.name == patch:
689 if a.name == patch:
688 return (i, a.rev, a.name)
690 return (i, a.rev, a.name)
689 return None
691 return None
690
692
691 # if the exact patch name does not exist, we try a few
693 # if the exact patch name does not exist, we try a few
692 # variations. If strict is passed, we try only #1
694 # variations. If strict is passed, we try only #1
693 #
695 #
694 # 1) a number to indicate an offset in the series file
696 # 1) a number to indicate an offset in the series file
695 # 2) a unique substring of the patch name was given
697 # 2) a unique substring of the patch name was given
696 # 3) patchname[-+]num to indicate an offset in the series file
698 # 3) patchname[-+]num to indicate an offset in the series file
697 def lookup(self, patch, strict=False):
699 def lookup(self, patch, strict=False):
698 patch = patch and str(patch)
700 patch = patch and str(patch)
699
701
700 def partial_name(s):
702 def partial_name(s):
701 if s in self.series:
703 if s in self.series:
702 return s
704 return s
703 matches = [x for x in self.series if s in x]
705 matches = [x for x in self.series if s in x]
704 if len(matches) > 1:
706 if len(matches) > 1:
705 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
707 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
706 for m in matches:
708 for m in matches:
707 self.ui.warn(' %s\n' % m)
709 self.ui.warn(' %s\n' % m)
708 return None
710 return None
709 if matches:
711 if matches:
710 return matches[0]
712 return matches[0]
711 if len(self.series) > 0 and len(self.applied) > 0:
713 if len(self.series) > 0 and len(self.applied) > 0:
712 if s == 'qtip':
714 if s == 'qtip':
713 return self.series[self.series_end(True)-1]
715 return self.series[self.series_end(True)-1]
714 if s == 'qbase':
716 if s == 'qbase':
715 return self.series[0]
717 return self.series[0]
716 return None
718 return None
717 if patch == None:
719 if patch == None:
718 return None
720 return None
719
721
720 # we don't want to return a partial match until we make
722 # we don't want to return a partial match until we make
721 # sure the file name passed in does not exist (checked below)
723 # sure the file name passed in does not exist (checked below)
722 res = partial_name(patch)
724 res = partial_name(patch)
723 if res and res == patch:
725 if res and res == patch:
724 return res
726 return res
725
727
726 if not os.path.isfile(self.join(patch)):
728 if not os.path.isfile(self.join(patch)):
727 try:
729 try:
728 sno = int(patch)
730 sno = int(patch)
729 except(ValueError, OverflowError):
731 except(ValueError, OverflowError):
730 pass
732 pass
731 else:
733 else:
732 if sno < len(self.series):
734 if sno < len(self.series):
733 return self.series[sno]
735 return self.series[sno]
734 if not strict:
736 if not strict:
735 # return any partial match made above
737 # return any partial match made above
736 if res:
738 if res:
737 return res
739 return res
738 minus = patch.rfind('-')
740 minus = patch.rfind('-')
739 if minus >= 0:
741 if minus >= 0:
740 res = partial_name(patch[:minus])
742 res = partial_name(patch[:minus])
741 if res:
743 if res:
742 i = self.series.index(res)
744 i = self.series.index(res)
743 try:
745 try:
744 off = int(patch[minus+1:] or 1)
746 off = int(patch[minus+1:] or 1)
745 except(ValueError, OverflowError):
747 except(ValueError, OverflowError):
746 pass
748 pass
747 else:
749 else:
748 if i - off >= 0:
750 if i - off >= 0:
749 return self.series[i - off]
751 return self.series[i - off]
750 plus = patch.rfind('+')
752 plus = patch.rfind('+')
751 if plus >= 0:
753 if plus >= 0:
752 res = partial_name(patch[:plus])
754 res = partial_name(patch[:plus])
753 if res:
755 if res:
754 i = self.series.index(res)
756 i = self.series.index(res)
755 try:
757 try:
756 off = int(patch[plus+1:] or 1)
758 off = int(patch[plus+1:] or 1)
757 except(ValueError, OverflowError):
759 except(ValueError, OverflowError):
758 pass
760 pass
759 else:
761 else:
760 if i + off < len(self.series):
762 if i + off < len(self.series):
761 return self.series[i + off]
763 return self.series[i + off]
762 raise util.Abort(_("patch %s not in series") % patch)
764 raise util.Abort(_("patch %s not in series") % patch)
763
765
764 def push(self, repo, patch=None, force=False, list=False,
766 def push(self, repo, patch=None, force=False, list=False,
765 mergeq=None):
767 mergeq=None):
766 wlock = repo.wlock()
768 wlock = repo.wlock()
767 try:
769 try:
768 patch = self.lookup(patch)
770 patch = self.lookup(patch)
769 # Suppose our series file is: A B C and the current 'top'
771 # Suppose our series file is: A B C and the current 'top'
770 # patch is B. qpush C should be performed (moving forward)
772 # patch is B. qpush C should be performed (moving forward)
771 # qpush B is a NOP (no change) qpush A is an error (can't
773 # qpush B is a NOP (no change) qpush A is an error (can't
772 # go backwards with qpush)
774 # go backwards with qpush)
773 if patch:
775 if patch:
774 info = self.isapplied(patch)
776 info = self.isapplied(patch)
775 if info:
777 if info:
776 if info[0] < len(self.applied) - 1:
778 if info[0] < len(self.applied) - 1:
777 raise util.Abort(
779 raise util.Abort(
778 _("cannot push to a previous patch: %s") % patch)
780 _("cannot push to a previous patch: %s") % patch)
779 if info[0] < len(self.series) - 1:
781 if info[0] < len(self.series) - 1:
780 self.ui.warn(
782 self.ui.warn(
781 _('qpush: %s is already at the top\n') % patch)
783 _('qpush: %s is already at the top\n') % patch)
782 else:
784 else:
783 self.ui.warn(_('all patches are currently applied\n'))
785 self.ui.warn(_('all patches are currently applied\n'))
784 return
786 return
785
787
786 # Following the above example, starting at 'top' of B:
788 # Following the above example, starting at 'top' of B:
787 # qpush should be performed (pushes C), but a subsequent
789 # qpush should be performed (pushes C), but a subsequent
788 # qpush without an argument is an error (nothing to
790 # qpush without an argument is an error (nothing to
789 # apply). This allows a loop of "...while hg qpush..." to
791 # apply). This allows a loop of "...while hg qpush..." to
790 # work as it detects an error when done
792 # work as it detects an error when done
791 if self.series_end() == len(self.series):
793 if self.series_end() == len(self.series):
792 self.ui.warn(_('patch series already fully applied\n'))
794 self.ui.warn(_('patch series already fully applied\n'))
793 return 1
795 return 1
794 if not force:
796 if not force:
795 self.check_localchanges(repo)
797 self.check_localchanges(repo)
796
798
797 self.applied_dirty = 1;
799 self.applied_dirty = 1;
798 start = self.series_end()
800 start = self.series_end()
799 if start > 0:
801 if start > 0:
800 self.check_toppatch(repo)
802 self.check_toppatch(repo)
801 if not patch:
803 if not patch:
802 patch = self.series[start]
804 patch = self.series[start]
803 end = start + 1
805 end = start + 1
804 else:
806 else:
805 end = self.series.index(patch, start) + 1
807 end = self.series.index(patch, start) + 1
806 s = self.series[start:end]
808 s = self.series[start:end]
807 all_files = {}
809 all_files = {}
808 try:
810 try:
809 if mergeq:
811 if mergeq:
810 ret = self.mergepatch(repo, mergeq, s)
812 ret = self.mergepatch(repo, mergeq, s)
811 else:
813 else:
812 ret = self.apply(repo, s, list, all_files=all_files)
814 ret = self.apply(repo, s, list, all_files=all_files)
813 except:
815 except:
814 self.ui.warn(_('cleaning up working directory...'))
816 self.ui.warn(_('cleaning up working directory...'))
815 node = repo.dirstate.parents()[0]
817 node = repo.dirstate.parents()[0]
816 hg.revert(repo, node, None)
818 hg.revert(repo, node, None)
817 unknown = repo.status()[4]
819 unknown = repo.status()[4]
818 # only remove unknown files that we know we touched or
820 # only remove unknown files that we know we touched or
819 # created while patching
821 # created while patching
820 for f in unknown:
822 for f in unknown:
821 if f in all_files:
823 if f in all_files:
822 util.unlink(repo.wjoin(f))
824 util.unlink(repo.wjoin(f))
823 self.ui.warn(_('done\n'))
825 self.ui.warn(_('done\n'))
824 raise
826 raise
825 top = self.applied[-1].name
827 top = self.applied[-1].name
826 if ret[0]:
828 if ret[0]:
827 self.ui.write(
829 self.ui.write(
828 "Errors during apply, please fix and refresh %s\n" % top)
830 "Errors during apply, please fix and refresh %s\n" % top)
829 else:
831 else:
830 self.ui.write("Now at: %s\n" % top)
832 self.ui.write("Now at: %s\n" % top)
831 return ret[0]
833 return ret[0]
832 finally:
834 finally:
833 del wlock
835 del wlock
834
836
835 def pop(self, repo, patch=None, force=False, update=True, all=False):
837 def pop(self, repo, patch=None, force=False, update=True, all=False):
836 def getfile(f, rev, flags):
838 def getfile(f, rev, flags):
837 t = repo.file(f).read(rev)
839 t = repo.file(f).read(rev)
838 repo.wwrite(f, t, flags)
840 repo.wwrite(f, t, flags)
839
841
840 wlock = repo.wlock()
842 wlock = repo.wlock()
841 try:
843 try:
842 if patch:
844 if patch:
843 # index, rev, patch
845 # index, rev, patch
844 info = self.isapplied(patch)
846 info = self.isapplied(patch)
845 if not info:
847 if not info:
846 patch = self.lookup(patch)
848 patch = self.lookup(patch)
847 info = self.isapplied(patch)
849 info = self.isapplied(patch)
848 if not info:
850 if not info:
849 raise util.Abort(_("patch %s is not applied") % patch)
851 raise util.Abort(_("patch %s is not applied") % patch)
850
852
851 if len(self.applied) == 0:
853 if len(self.applied) == 0:
852 # Allow qpop -a to work repeatedly,
854 # Allow qpop -a to work repeatedly,
853 # but not qpop without an argument
855 # but not qpop without an argument
854 self.ui.warn(_("no patches applied\n"))
856 self.ui.warn(_("no patches applied\n"))
855 return not all
857 return not all
856
858
857 if not update:
859 if not update:
858 parents = repo.dirstate.parents()
860 parents = repo.dirstate.parents()
859 rr = [ revlog.bin(x.rev) for x in self.applied ]
861 rr = [ revlog.bin(x.rev) for x in self.applied ]
860 for p in parents:
862 for p in parents:
861 if p in rr:
863 if p in rr:
862 self.ui.warn("qpop: forcing dirstate update\n")
864 self.ui.warn("qpop: forcing dirstate update\n")
863 update = True
865 update = True
864
866
865 if not force and update:
867 if not force and update:
866 self.check_localchanges(repo)
868 self.check_localchanges(repo)
867
869
868 self.applied_dirty = 1;
870 self.applied_dirty = 1;
869 end = len(self.applied)
871 end = len(self.applied)
870 if not patch:
872 if not patch:
871 if all:
873 if all:
872 popi = 0
874 popi = 0
873 else:
875 else:
874 popi = len(self.applied) - 1
876 popi = len(self.applied) - 1
875 else:
877 else:
876 popi = info[0] + 1
878 popi = info[0] + 1
877 if popi >= end:
879 if popi >= end:
878 self.ui.warn("qpop: %s is already at the top\n" % patch)
880 self.ui.warn("qpop: %s is already at the top\n" % patch)
879 return
881 return
880 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
882 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
881
883
882 start = info[0]
884 start = info[0]
883 rev = revlog.bin(info[1])
885 rev = revlog.bin(info[1])
884
886
885 if update:
887 if update:
886 top = self.check_toppatch(repo)
888 top = self.check_toppatch(repo)
887
889
888 if repo.changelog.heads(rev) != [revlog.bin(self.applied[-1].rev)]:
890 if repo.changelog.heads(rev) != [revlog.bin(self.applied[-1].rev)]:
889 raise util.Abort("popping would remove a revision not "
891 raise util.Abort("popping would remove a revision not "
890 "managed by this patch queue")
892 "managed by this patch queue")
891
893
892 # we know there are no local changes, so we can make a simplified
894 # we know there are no local changes, so we can make a simplified
893 # form of hg.update.
895 # form of hg.update.
894 if update:
896 if update:
895 qp = self.qparents(repo, rev)
897 qp = self.qparents(repo, rev)
896 changes = repo.changelog.read(qp)
898 changes = repo.changelog.read(qp)
897 mmap = repo.manifest.read(changes[0])
899 mmap = repo.manifest.read(changes[0])
898 m, a, r, d, u = repo.status(qp, top)[:5]
900 m, a, r, d, u = repo.status(qp, top)[:5]
899 if d:
901 if d:
900 raise util.Abort("deletions found between repo revs")
902 raise util.Abort("deletions found between repo revs")
901 for f in m:
903 for f in m:
902 getfile(f, mmap[f], mmap.flags(f))
904 getfile(f, mmap[f], mmap.flags(f))
903 for f in r:
905 for f in r:
904 getfile(f, mmap[f], mmap.flags(f))
906 getfile(f, mmap[f], mmap.flags(f))
905 for f in m + r:
907 for f in m + r:
906 repo.dirstate.normal(f)
908 repo.dirstate.normal(f)
907 for f in a:
909 for f in a:
908 try:
910 try:
909 os.unlink(repo.wjoin(f))
911 os.unlink(repo.wjoin(f))
910 except OSError, e:
912 except OSError, e:
911 if e.errno != errno.ENOENT:
913 if e.errno != errno.ENOENT:
912 raise
914 raise
913 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
915 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
914 except: pass
916 except: pass
915 repo.dirstate.forget(f)
917 repo.dirstate.forget(f)
916 repo.dirstate.setparents(qp, revlog.nullid)
918 repo.dirstate.setparents(qp, revlog.nullid)
917 del self.applied[start:end]
919 del self.applied[start:end]
918 self.strip(repo, rev, update=False, backup='strip')
920 self.strip(repo, rev, update=False, backup='strip')
919 if len(self.applied):
921 if len(self.applied):
920 self.ui.write("Now at: %s\n" % self.applied[-1].name)
922 self.ui.write("Now at: %s\n" % self.applied[-1].name)
921 else:
923 else:
922 self.ui.write("Patch queue now empty\n")
924 self.ui.write("Patch queue now empty\n")
923 finally:
925 finally:
924 del wlock
926 del wlock
925
927
926 def diff(self, repo, pats, opts):
928 def diff(self, repo, pats, opts):
927 top = self.check_toppatch(repo)
929 top = self.check_toppatch(repo)
928 if not top:
930 if not top:
929 self.ui.write("No patches applied\n")
931 self.ui.write("No patches applied\n")
930 return
932 return
931 qp = self.qparents(repo, top)
933 qp = self.qparents(repo, top)
932 if opts.get('git'):
934 if opts.get('git'):
933 self.diffopts().git = True
935 self.diffopts().git = True
934 self.printdiff(repo, qp, files=pats, opts=opts)
936 self.printdiff(repo, qp, files=pats, opts=opts)
935
937
936 def refresh(self, repo, pats=None, **opts):
938 def refresh(self, repo, pats=None, **opts):
937 if len(self.applied) == 0:
939 if len(self.applied) == 0:
938 self.ui.write("No patches applied\n")
940 self.ui.write("No patches applied\n")
939 return 1
941 return 1
940 newdate = opts.get('date')
942 newdate = opts.get('date')
941 if newdate:
943 if newdate:
942 newdate = '%d %d' % util.parsedate(newdate)
944 newdate = '%d %d' % util.parsedate(newdate)
943 wlock = repo.wlock()
945 wlock = repo.wlock()
944 try:
946 try:
945 self.check_toppatch(repo)
947 self.check_toppatch(repo)
946 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
948 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
947 top = revlog.bin(top)
949 top = revlog.bin(top)
948 if repo.changelog.heads(top) != [top]:
950 if repo.changelog.heads(top) != [top]:
949 raise util.Abort("cannot refresh a revision with children")
951 raise util.Abort("cannot refresh a revision with children")
950 cparents = repo.changelog.parents(top)
952 cparents = repo.changelog.parents(top)
951 patchparent = self.qparents(repo, top)
953 patchparent = self.qparents(repo, top)
952 message, comments, user, date, patchfound = self.readheaders(patchfn)
954 message, comments, user, date, patchfound = self.readheaders(patchfn)
953
955
954 patchf = self.opener(patchfn, 'r+')
956 patchf = self.opener(patchfn, 'r+')
955
957
956 # if the patch was a git patch, refresh it as a git patch
958 # if the patch was a git patch, refresh it as a git patch
957 for line in patchf:
959 for line in patchf:
958 if line.startswith('diff --git'):
960 if line.startswith('diff --git'):
959 self.diffopts().git = True
961 self.diffopts().git = True
960 break
962 break
961
963
962 msg = opts.get('msg', '').rstrip()
964 msg = opts.get('msg', '').rstrip()
963 if msg and comments:
965 if msg and comments:
964 # Remove existing message, keeping the rest of the comments
966 # Remove existing message, keeping the rest of the comments
965 # fields.
967 # fields.
966 # If comments contains 'subject: ', message will prepend
968 # If comments contains 'subject: ', message will prepend
967 # the field and a blank line.
969 # the field and a blank line.
968 if message:
970 if message:
969 subj = 'subject: ' + message[0].lower()
971 subj = 'subject: ' + message[0].lower()
970 for i in xrange(len(comments)):
972 for i in xrange(len(comments)):
971 if subj == comments[i].lower():
973 if subj == comments[i].lower():
972 del comments[i]
974 del comments[i]
973 message = message[2:]
975 message = message[2:]
974 break
976 break
975 ci = 0
977 ci = 0
976 for mi in xrange(len(message)):
978 for mi in xrange(len(message)):
977 while message[mi] != comments[ci]:
979 while message[mi] != comments[ci]:
978 ci += 1
980 ci += 1
979 del comments[ci]
981 del comments[ci]
980
982
981 def setheaderfield(comments, prefixes, new):
983 def setheaderfield(comments, prefixes, new):
982 # Update all references to a field in the patch header.
984 # Update all references to a field in the patch header.
983 # If none found, add it email style.
985 # If none found, add it email style.
984 res = False
986 res = False
985 for prefix in prefixes:
987 for prefix in prefixes:
986 for i in xrange(len(comments)):
988 for i in xrange(len(comments)):
987 if comments[i].startswith(prefix):
989 if comments[i].startswith(prefix):
988 comments[i] = prefix + new
990 comments[i] = prefix + new
989 res = True
991 res = True
990 break
992 break
991 return res
993 return res
992
994
993 newuser = opts.get('user')
995 newuser = opts.get('user')
994 if newuser:
996 if newuser:
995 if not setheaderfield(comments, ['From: ', '# User '], newuser):
997 if not setheaderfield(comments, ['From: ', '# User '], newuser):
996 try:
998 try:
997 patchheaderat = comments.index('# HG changeset patch')
999 patchheaderat = comments.index('# HG changeset patch')
998 comments.insert(patchheaderat + 1,'# User ' + newuser)
1000 comments.insert(patchheaderat + 1,'# User ' + newuser)
999 except ValueError:
1001 except ValueError:
1000 comments = ['From: ' + newuser, ''] + comments
1002 comments = ['From: ' + newuser, ''] + comments
1001 user = newuser
1003 user = newuser
1002
1004
1003 if newdate:
1005 if newdate:
1004 if setheaderfield(comments, ['# Date '], newdate):
1006 if setheaderfield(comments, ['# Date '], newdate):
1005 date = newdate
1007 date = newdate
1006
1008
1007 if msg:
1009 if msg:
1008 comments.append(msg)
1010 comments.append(msg)
1009
1011
1010 patchf.seek(0)
1012 patchf.seek(0)
1011 patchf.truncate()
1013 patchf.truncate()
1012
1014
1013 if comments:
1015 if comments:
1014 comments = "\n".join(comments) + '\n\n'
1016 comments = "\n".join(comments) + '\n\n'
1015 patchf.write(comments)
1017 patchf.write(comments)
1016
1018
1017 if opts.get('git'):
1019 if opts.get('git'):
1018 self.diffopts().git = True
1020 self.diffopts().git = True
1019 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1021 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1020 tip = repo.changelog.tip()
1022 tip = repo.changelog.tip()
1021 if top == tip:
1023 if top == tip:
1022 # if the top of our patch queue is also the tip, there is an
1024 # if the top of our patch queue is also the tip, there is an
1023 # optimization here. We update the dirstate in place and strip
1025 # optimization here. We update the dirstate in place and strip
1024 # off the tip commit. Then just commit the current directory
1026 # off the tip commit. Then just commit the current directory
1025 # tree. We can also send repo.commit the list of files
1027 # tree. We can also send repo.commit the list of files
1026 # changed to speed up the diff
1028 # changed to speed up the diff
1027 #
1029 #
1028 # in short mode, we only diff the files included in the
1030 # in short mode, we only diff the files included in the
1029 # patch already
1031 # patch already
1030 #
1032 #
1031 # this should really read:
1033 # this should really read:
1032 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
1034 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
1033 # but we do it backwards to take advantage of manifest/chlog
1035 # but we do it backwards to take advantage of manifest/chlog
1034 # caching against the next repo.status call
1036 # caching against the next repo.status call
1035 #
1037 #
1036 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
1038 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
1037 changes = repo.changelog.read(tip)
1039 changes = repo.changelog.read(tip)
1038 man = repo.manifest.read(changes[0])
1040 man = repo.manifest.read(changes[0])
1039 aaa = aa[:]
1041 aaa = aa[:]
1040 if opts.get('short'):
1042 if opts.get('short'):
1041 filelist = mm + aa + dd
1043 filelist = mm + aa + dd
1042 match = dict.fromkeys(filelist).__contains__
1044 match = dict.fromkeys(filelist).__contains__
1043 else:
1045 else:
1044 filelist = None
1046 filelist = None
1045 match = util.always
1047 match = util.always
1046 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
1048 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
1047
1049
1048 # we might end up with files that were added between
1050 # we might end up with files that were added between
1049 # tip and the dirstate parent, but then changed in the
1051 # tip and the dirstate parent, but then changed in the
1050 # local dirstate. in this case, we want them to only
1052 # local dirstate. in this case, we want them to only
1051 # show up in the added section
1053 # show up in the added section
1052 for x in m:
1054 for x in m:
1053 if x not in aa:
1055 if x not in aa:
1054 mm.append(x)
1056 mm.append(x)
1055 # we might end up with files added by the local dirstate that
1057 # we might end up with files added by the local dirstate that
1056 # were deleted by the patch. In this case, they should only
1058 # were deleted by the patch. In this case, they should only
1057 # show up in the changed section.
1059 # show up in the changed section.
1058 for x in a:
1060 for x in a:
1059 if x in dd:
1061 if x in dd:
1060 del dd[dd.index(x)]
1062 del dd[dd.index(x)]
1061 mm.append(x)
1063 mm.append(x)
1062 else:
1064 else:
1063 aa.append(x)
1065 aa.append(x)
1064 # make sure any files deleted in the local dirstate
1066 # make sure any files deleted in the local dirstate
1065 # are not in the add or change column of the patch
1067 # are not in the add or change column of the patch
1066 forget = []
1068 forget = []
1067 for x in d + r:
1069 for x in d + r:
1068 if x in aa:
1070 if x in aa:
1069 del aa[aa.index(x)]
1071 del aa[aa.index(x)]
1070 forget.append(x)
1072 forget.append(x)
1071 continue
1073 continue
1072 elif x in mm:
1074 elif x in mm:
1073 del mm[mm.index(x)]
1075 del mm[mm.index(x)]
1074 dd.append(x)
1076 dd.append(x)
1075
1077
1076 m = util.unique(mm)
1078 m = util.unique(mm)
1077 r = util.unique(dd)
1079 r = util.unique(dd)
1078 a = util.unique(aa)
1080 a = util.unique(aa)
1079 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1081 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1080 filelist = util.unique(c[0] + c[1] + c[2])
1082 filelist = util.unique(c[0] + c[1] + c[2])
1081 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1083 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1082 fp=patchf, changes=c, opts=self.diffopts())
1084 fp=patchf, changes=c, opts=self.diffopts())
1083 patchf.close()
1085 patchf.close()
1084
1086
1085 repo.dirstate.setparents(*cparents)
1087 repo.dirstate.setparents(*cparents)
1086 copies = {}
1088 copies = {}
1087 for dst in a:
1089 for dst in a:
1088 src = repo.dirstate.copied(dst)
1090 src = repo.dirstate.copied(dst)
1089 if src is not None:
1091 if src is not None:
1090 copies.setdefault(src, []).append(dst)
1092 copies.setdefault(src, []).append(dst)
1091 repo.dirstate.add(dst)
1093 repo.dirstate.add(dst)
1092 # remember the copies between patchparent and tip
1094 # remember the copies between patchparent and tip
1093 # this may be slow, so don't do it if we're not tracking copies
1095 # this may be slow, so don't do it if we're not tracking copies
1094 if self.diffopts().git:
1096 if self.diffopts().git:
1095 for dst in aaa:
1097 for dst in aaa:
1096 f = repo.file(dst)
1098 f = repo.file(dst)
1097 src = f.renamed(man[dst])
1099 src = f.renamed(man[dst])
1098 if src:
1100 if src:
1099 copies[src[0]] = copies.get(dst, [])
1101 copies[src[0]] = copies.get(dst, [])
1100 if dst in a:
1102 if dst in a:
1101 copies[src[0]].append(dst)
1103 copies[src[0]].append(dst)
1102 # we can't copy a file created by the patch itself
1104 # we can't copy a file created by the patch itself
1103 if dst in copies:
1105 if dst in copies:
1104 del copies[dst]
1106 del copies[dst]
1105 for src, dsts in copies.iteritems():
1107 for src, dsts in copies.iteritems():
1106 for dst in dsts:
1108 for dst in dsts:
1107 repo.dirstate.copy(src, dst)
1109 repo.dirstate.copy(src, dst)
1108 for f in r:
1110 for f in r:
1109 repo.dirstate.remove(f)
1111 repo.dirstate.remove(f)
1110 # if the patch excludes a modified file, mark that
1112 # if the patch excludes a modified file, mark that
1111 # file with mtime=0 so status can see it.
1113 # file with mtime=0 so status can see it.
1112 mm = []
1114 mm = []
1113 for i in xrange(len(m)-1, -1, -1):
1115 for i in xrange(len(m)-1, -1, -1):
1114 if not matchfn(m[i]):
1116 if not matchfn(m[i]):
1115 mm.append(m[i])
1117 mm.append(m[i])
1116 del m[i]
1118 del m[i]
1117 for f in m:
1119 for f in m:
1118 repo.dirstate.normal(f)
1120 repo.dirstate.normal(f)
1119 for f in mm:
1121 for f in mm:
1120 repo.dirstate.normallookup(f)
1122 repo.dirstate.normallookup(f)
1121 for f in forget:
1123 for f in forget:
1122 repo.dirstate.forget(f)
1124 repo.dirstate.forget(f)
1123
1125
1124 if not msg:
1126 if not msg:
1125 if not message:
1127 if not message:
1126 message = "[mq]: %s\n" % patchfn
1128 message = "[mq]: %s\n" % patchfn
1127 else:
1129 else:
1128 message = "\n".join(message)
1130 message = "\n".join(message)
1129 else:
1131 else:
1130 message = msg
1132 message = msg
1131
1133
1132 if not user:
1134 if not user:
1133 user = changes[1]
1135 user = changes[1]
1134
1136
1135 self.applied.pop()
1137 self.applied.pop()
1136 self.applied_dirty = 1
1138 self.applied_dirty = 1
1137 self.strip(repo, top, update=False,
1139 self.strip(repo, top, update=False,
1138 backup='strip')
1140 backup='strip')
1139 n = repo.commit(filelist, message, user, date, match=matchfn,
1141 n = repo.commit(filelist, message, user, date, match=matchfn,
1140 force=1)
1142 force=1)
1141 self.applied.append(statusentry(revlog.hex(n), patchfn))
1143 self.applied.append(statusentry(revlog.hex(n), patchfn))
1142 self.removeundo(repo)
1144 self.removeundo(repo)
1143 else:
1145 else:
1144 self.printdiff(repo, patchparent, fp=patchf)
1146 self.printdiff(repo, patchparent, fp=patchf)
1145 patchf.close()
1147 patchf.close()
1146 added = repo.status()[1]
1148 added = repo.status()[1]
1147 for a in added:
1149 for a in added:
1148 f = repo.wjoin(a)
1150 f = repo.wjoin(a)
1149 try:
1151 try:
1150 os.unlink(f)
1152 os.unlink(f)
1151 except OSError, e:
1153 except OSError, e:
1152 if e.errno != errno.ENOENT:
1154 if e.errno != errno.ENOENT:
1153 raise
1155 raise
1154 try: os.removedirs(os.path.dirname(f))
1156 try: os.removedirs(os.path.dirname(f))
1155 except: pass
1157 except: pass
1156 # forget the file copies in the dirstate
1158 # forget the file copies in the dirstate
1157 # push should readd the files later on
1159 # push should readd the files later on
1158 repo.dirstate.forget(a)
1160 repo.dirstate.forget(a)
1159 self.pop(repo, force=True)
1161 self.pop(repo, force=True)
1160 self.push(repo, force=True)
1162 self.push(repo, force=True)
1161 finally:
1163 finally:
1162 del wlock
1164 del wlock
1163
1165
1164 def init(self, repo, create=False):
1166 def init(self, repo, create=False):
1165 if not create and os.path.isdir(self.path):
1167 if not create and os.path.isdir(self.path):
1166 raise util.Abort(_("patch queue directory already exists"))
1168 raise util.Abort(_("patch queue directory already exists"))
1167 try:
1169 try:
1168 os.mkdir(self.path)
1170 os.mkdir(self.path)
1169 except OSError, inst:
1171 except OSError, inst:
1170 if inst.errno != errno.EEXIST or not create:
1172 if inst.errno != errno.EEXIST or not create:
1171 raise
1173 raise
1172 if create:
1174 if create:
1173 return self.qrepo(create=True)
1175 return self.qrepo(create=True)
1174
1176
1175 def unapplied(self, repo, patch=None):
1177 def unapplied(self, repo, patch=None):
1176 if patch and patch not in self.series:
1178 if patch and patch not in self.series:
1177 raise util.Abort(_("patch %s is not in series file") % patch)
1179 raise util.Abort(_("patch %s is not in series file") % patch)
1178 if not patch:
1180 if not patch:
1179 start = self.series_end()
1181 start = self.series_end()
1180 else:
1182 else:
1181 start = self.series.index(patch) + 1
1183 start = self.series.index(patch) + 1
1182 unapplied = []
1184 unapplied = []
1183 for i in xrange(start, len(self.series)):
1185 for i in xrange(start, len(self.series)):
1184 pushable, reason = self.pushable(i)
1186 pushable, reason = self.pushable(i)
1185 if pushable:
1187 if pushable:
1186 unapplied.append((i, self.series[i]))
1188 unapplied.append((i, self.series[i]))
1187 self.explain_pushable(i)
1189 self.explain_pushable(i)
1188 return unapplied
1190 return unapplied
1189
1191
1190 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1192 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1191 summary=False):
1193 summary=False):
1192 def displayname(patchname):
1194 def displayname(patchname):
1193 if summary:
1195 if summary:
1194 msg = self.readheaders(patchname)[0]
1196 msg = self.readheaders(patchname)[0]
1195 msg = msg and ': ' + msg[0] or ': '
1197 msg = msg and ': ' + msg[0] or ': '
1196 else:
1198 else:
1197 msg = ''
1199 msg = ''
1198 return '%s%s' % (patchname, msg)
1200 return '%s%s' % (patchname, msg)
1199
1201
1200 applied = dict.fromkeys([p.name for p in self.applied])
1202 applied = dict.fromkeys([p.name for p in self.applied])
1201 if length is None:
1203 if length is None:
1202 length = len(self.series) - start
1204 length = len(self.series) - start
1203 if not missing:
1205 if not missing:
1204 for i in xrange(start, start+length):
1206 for i in xrange(start, start+length):
1205 patch = self.series[i]
1207 patch = self.series[i]
1206 if patch in applied:
1208 if patch in applied:
1207 stat = 'A'
1209 stat = 'A'
1208 elif self.pushable(i)[0]:
1210 elif self.pushable(i)[0]:
1209 stat = 'U'
1211 stat = 'U'
1210 else:
1212 else:
1211 stat = 'G'
1213 stat = 'G'
1212 pfx = ''
1214 pfx = ''
1213 if self.ui.verbose:
1215 if self.ui.verbose:
1214 pfx = '%d %s ' % (i, stat)
1216 pfx = '%d %s ' % (i, stat)
1215 elif status and status != stat:
1217 elif status and status != stat:
1216 continue
1218 continue
1217 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1219 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1218 else:
1220 else:
1219 msng_list = []
1221 msng_list = []
1220 for root, dirs, files in os.walk(self.path):
1222 for root, dirs, files in os.walk(self.path):
1221 d = root[len(self.path) + 1:]
1223 d = root[len(self.path) + 1:]
1222 for f in files:
1224 for f in files:
1223 fl = os.path.join(d, f)
1225 fl = os.path.join(d, f)
1224 if (fl not in self.series and
1226 if (fl not in self.series and
1225 fl not in (self.status_path, self.series_path,
1227 fl not in (self.status_path, self.series_path,
1226 self.guards_path)
1228 self.guards_path)
1227 and not fl.startswith('.')):
1229 and not fl.startswith('.')):
1228 msng_list.append(fl)
1230 msng_list.append(fl)
1229 msng_list.sort()
1231 msng_list.sort()
1230 for x in msng_list:
1232 for x in msng_list:
1231 pfx = self.ui.verbose and ('D ') or ''
1233 pfx = self.ui.verbose and ('D ') or ''
1232 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1234 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1233
1235
1234 def issaveline(self, l):
1236 def issaveline(self, l):
1235 if l.name == '.hg.patches.save.line':
1237 if l.name == '.hg.patches.save.line':
1236 return True
1238 return True
1237
1239
1238 def qrepo(self, create=False):
1240 def qrepo(self, create=False):
1239 if create or os.path.isdir(self.join(".hg")):
1241 if create or os.path.isdir(self.join(".hg")):
1240 return hg.repository(self.ui, path=self.path, create=create)
1242 return hg.repository(self.ui, path=self.path, create=create)
1241
1243
1242 def restore(self, repo, rev, delete=None, qupdate=None):
1244 def restore(self, repo, rev, delete=None, qupdate=None):
1243 c = repo.changelog.read(rev)
1245 c = repo.changelog.read(rev)
1244 desc = c[4].strip()
1246 desc = c[4].strip()
1245 lines = desc.splitlines()
1247 lines = desc.splitlines()
1246 i = 0
1248 i = 0
1247 datastart = None
1249 datastart = None
1248 series = []
1250 series = []
1249 applied = []
1251 applied = []
1250 qpp = None
1252 qpp = None
1251 for i in xrange(0, len(lines)):
1253 for i in xrange(0, len(lines)):
1252 if lines[i] == 'Patch Data:':
1254 if lines[i] == 'Patch Data:':
1253 datastart = i + 1
1255 datastart = i + 1
1254 elif lines[i].startswith('Dirstate:'):
1256 elif lines[i].startswith('Dirstate:'):
1255 l = lines[i].rstrip()
1257 l = lines[i].rstrip()
1256 l = l[10:].split(' ')
1258 l = l[10:].split(' ')
1257 qpp = [ hg.bin(x) for x in l ]
1259 qpp = [ bin(x) for x in l ]
1258 elif datastart != None:
1260 elif datastart != None:
1259 l = lines[i].rstrip()
1261 l = lines[i].rstrip()
1260 se = statusentry(l)
1262 se = statusentry(l)
1261 file_ = se.name
1263 file_ = se.name
1262 if se.rev:
1264 if se.rev:
1263 applied.append(se)
1265 applied.append(se)
1264 else:
1266 else:
1265 series.append(file_)
1267 series.append(file_)
1266 if datastart == None:
1268 if datastart == None:
1267 self.ui.warn("No saved patch data found\n")
1269 self.ui.warn("No saved patch data found\n")
1268 return 1
1270 return 1
1269 self.ui.warn("restoring status: %s\n" % lines[0])
1271 self.ui.warn("restoring status: %s\n" % lines[0])
1270 self.full_series = series
1272 self.full_series = series
1271 self.applied = applied
1273 self.applied = applied
1272 self.parse_series()
1274 self.parse_series()
1273 self.series_dirty = 1
1275 self.series_dirty = 1
1274 self.applied_dirty = 1
1276 self.applied_dirty = 1
1275 heads = repo.changelog.heads()
1277 heads = repo.changelog.heads()
1276 if delete:
1278 if delete:
1277 if rev not in heads:
1279 if rev not in heads:
1278 self.ui.warn("save entry has children, leaving it alone\n")
1280 self.ui.warn("save entry has children, leaving it alone\n")
1279 else:
1281 else:
1280 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1282 self.ui.warn("removing save entry %s\n" % short(rev))
1281 pp = repo.dirstate.parents()
1283 pp = repo.dirstate.parents()
1282 if rev in pp:
1284 if rev in pp:
1283 update = True
1285 update = True
1284 else:
1286 else:
1285 update = False
1287 update = False
1286 self.strip(repo, rev, update=update, backup='strip')
1288 self.strip(repo, rev, update=update, backup='strip')
1287 if qpp:
1289 if qpp:
1288 self.ui.warn("saved queue repository parents: %s %s\n" %
1290 self.ui.warn("saved queue repository parents: %s %s\n" %
1289 (hg.short(qpp[0]), hg.short(qpp[1])))
1291 (short(qpp[0]), short(qpp[1])))
1290 if qupdate:
1292 if qupdate:
1291 self.ui.status(_("queue directory updating\n"))
1293 self.ui.status(_("queue directory updating\n"))
1292 r = self.qrepo()
1294 r = self.qrepo()
1293 if not r:
1295 if not r:
1294 self.ui.warn("Unable to load queue repository\n")
1296 self.ui.warn("Unable to load queue repository\n")
1295 return 1
1297 return 1
1296 hg.clean(r, qpp[0])
1298 hg.clean(r, qpp[0])
1297
1299
1298 def save(self, repo, msg=None):
1300 def save(self, repo, msg=None):
1299 if len(self.applied) == 0:
1301 if len(self.applied) == 0:
1300 self.ui.warn("save: no patches applied, exiting\n")
1302 self.ui.warn("save: no patches applied, exiting\n")
1301 return 1
1303 return 1
1302 if self.issaveline(self.applied[-1]):
1304 if self.issaveline(self.applied[-1]):
1303 self.ui.warn("status is already saved\n")
1305 self.ui.warn("status is already saved\n")
1304 return 1
1306 return 1
1305
1307
1306 ar = [ ':' + x for x in self.full_series ]
1308 ar = [ ':' + x for x in self.full_series ]
1307 if not msg:
1309 if not msg:
1308 msg = "hg patches saved state"
1310 msg = "hg patches saved state"
1309 else:
1311 else:
1310 msg = "hg patches: " + msg.rstrip('\r\n')
1312 msg = "hg patches: " + msg.rstrip('\r\n')
1311 r = self.qrepo()
1313 r = self.qrepo()
1312 if r:
1314 if r:
1313 pp = r.dirstate.parents()
1315 pp = r.dirstate.parents()
1314 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1316 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1315 msg += "\n\nPatch Data:\n"
1317 msg += "\n\nPatch Data:\n"
1316 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1318 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1317 "\n".join(ar) + '\n' or "")
1319 "\n".join(ar) + '\n' or "")
1318 n = repo.commit(None, text, user=None, force=1)
1320 n = repo.commit(None, text, user=None, force=1)
1319 if not n:
1321 if not n:
1320 self.ui.warn("repo commit failed\n")
1322 self.ui.warn("repo commit failed\n")
1321 return 1
1323 return 1
1322 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1324 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1323 self.applied_dirty = 1
1325 self.applied_dirty = 1
1324 self.removeundo(repo)
1326 self.removeundo(repo)
1325
1327
1326 def full_series_end(self):
1328 def full_series_end(self):
1327 if len(self.applied) > 0:
1329 if len(self.applied) > 0:
1328 p = self.applied[-1].name
1330 p = self.applied[-1].name
1329 end = self.find_series(p)
1331 end = self.find_series(p)
1330 if end == None:
1332 if end == None:
1331 return len(self.full_series)
1333 return len(self.full_series)
1332 return end + 1
1334 return end + 1
1333 return 0
1335 return 0
1334
1336
1335 def series_end(self, all_patches=False):
1337 def series_end(self, all_patches=False):
1336 """If all_patches is False, return the index of the next pushable patch
1338 """If all_patches is False, return the index of the next pushable patch
1337 in the series, or the series length. If all_patches is True, return the
1339 in the series, or the series length. If all_patches is True, return the
1338 index of the first patch past the last applied one.
1340 index of the first patch past the last applied one.
1339 """
1341 """
1340 end = 0
1342 end = 0
1341 def next(start):
1343 def next(start):
1342 if all_patches:
1344 if all_patches:
1343 return start
1345 return start
1344 i = start
1346 i = start
1345 while i < len(self.series):
1347 while i < len(self.series):
1346 p, reason = self.pushable(i)
1348 p, reason = self.pushable(i)
1347 if p:
1349 if p:
1348 break
1350 break
1349 self.explain_pushable(i)
1351 self.explain_pushable(i)
1350 i += 1
1352 i += 1
1351 return i
1353 return i
1352 if len(self.applied) > 0:
1354 if len(self.applied) > 0:
1353 p = self.applied[-1].name
1355 p = self.applied[-1].name
1354 try:
1356 try:
1355 end = self.series.index(p)
1357 end = self.series.index(p)
1356 except ValueError:
1358 except ValueError:
1357 return 0
1359 return 0
1358 return next(end + 1)
1360 return next(end + 1)
1359 return next(end)
1361 return next(end)
1360
1362
1361 def appliedname(self, index):
1363 def appliedname(self, index):
1362 pname = self.applied[index].name
1364 pname = self.applied[index].name
1363 if not self.ui.verbose:
1365 if not self.ui.verbose:
1364 p = pname
1366 p = pname
1365 else:
1367 else:
1366 p = str(self.series.index(pname)) + " " + pname
1368 p = str(self.series.index(pname)) + " " + pname
1367 return p
1369 return p
1368
1370
1369 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1371 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1370 force=None, git=False):
1372 force=None, git=False):
1371 def checkseries(patchname):
1373 def checkseries(patchname):
1372 if patchname in self.series:
1374 if patchname in self.series:
1373 raise util.Abort(_('patch %s is already in the series file')
1375 raise util.Abort(_('patch %s is already in the series file')
1374 % patchname)
1376 % patchname)
1375 def checkfile(patchname):
1377 def checkfile(patchname):
1376 if not force and os.path.exists(self.join(patchname)):
1378 if not force and os.path.exists(self.join(patchname)):
1377 raise util.Abort(_('patch "%s" already exists')
1379 raise util.Abort(_('patch "%s" already exists')
1378 % patchname)
1380 % patchname)
1379
1381
1380 if rev:
1382 if rev:
1381 if files:
1383 if files:
1382 raise util.Abort(_('option "-r" not valid when importing '
1384 raise util.Abort(_('option "-r" not valid when importing '
1383 'files'))
1385 'files'))
1384 rev = cmdutil.revrange(repo, rev)
1386 rev = cmdutil.revrange(repo, rev)
1385 rev.sort(lambda x, y: cmp(y, x))
1387 rev.sort(lambda x, y: cmp(y, x))
1386 if (len(files) > 1 or len(rev) > 1) and patchname:
1388 if (len(files) > 1 or len(rev) > 1) and patchname:
1387 raise util.Abort(_('option "-n" not valid when importing multiple '
1389 raise util.Abort(_('option "-n" not valid when importing multiple '
1388 'patches'))
1390 'patches'))
1389 i = 0
1391 i = 0
1390 added = []
1392 added = []
1391 if rev:
1393 if rev:
1392 # If mq patches are applied, we can only import revisions
1394 # If mq patches are applied, we can only import revisions
1393 # that form a linear path to qbase.
1395 # that form a linear path to qbase.
1394 # Otherwise, they should form a linear path to a head.
1396 # Otherwise, they should form a linear path to a head.
1395 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1397 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1396 if len(heads) > 1:
1398 if len(heads) > 1:
1397 raise util.Abort(_('revision %d is the root of more than one '
1399 raise util.Abort(_('revision %d is the root of more than one '
1398 'branch') % rev[-1])
1400 'branch') % rev[-1])
1399 if self.applied:
1401 if self.applied:
1400 base = revlog.hex(repo.changelog.node(rev[0]))
1402 base = revlog.hex(repo.changelog.node(rev[0]))
1401 if base in [n.rev for n in self.applied]:
1403 if base in [n.rev for n in self.applied]:
1402 raise util.Abort(_('revision %d is already managed')
1404 raise util.Abort(_('revision %d is already managed')
1403 % rev[0])
1405 % rev[0])
1404 if heads != [revlog.bin(self.applied[-1].rev)]:
1406 if heads != [revlog.bin(self.applied[-1].rev)]:
1405 raise util.Abort(_('revision %d is not the parent of '
1407 raise util.Abort(_('revision %d is not the parent of '
1406 'the queue') % rev[0])
1408 'the queue') % rev[0])
1407 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1409 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1408 lastparent = repo.changelog.parentrevs(base)[0]
1410 lastparent = repo.changelog.parentrevs(base)[0]
1409 else:
1411 else:
1410 if heads != [repo.changelog.node(rev[0])]:
1412 if heads != [repo.changelog.node(rev[0])]:
1411 raise util.Abort(_('revision %d has unmanaged children')
1413 raise util.Abort(_('revision %d has unmanaged children')
1412 % rev[0])
1414 % rev[0])
1413 lastparent = None
1415 lastparent = None
1414
1416
1415 if git:
1417 if git:
1416 self.diffopts().git = True
1418 self.diffopts().git = True
1417
1419
1418 for r in rev:
1420 for r in rev:
1419 p1, p2 = repo.changelog.parentrevs(r)
1421 p1, p2 = repo.changelog.parentrevs(r)
1420 n = repo.changelog.node(r)
1422 n = repo.changelog.node(r)
1421 if p2 != revlog.nullrev:
1423 if p2 != revlog.nullrev:
1422 raise util.Abort(_('cannot import merge revision %d') % r)
1424 raise util.Abort(_('cannot import merge revision %d') % r)
1423 if lastparent and lastparent != r:
1425 if lastparent and lastparent != r:
1424 raise util.Abort(_('revision %d is not the parent of %d')
1426 raise util.Abort(_('revision %d is not the parent of %d')
1425 % (r, lastparent))
1427 % (r, lastparent))
1426 lastparent = p1
1428 lastparent = p1
1427
1429
1428 if not patchname:
1430 if not patchname:
1429 patchname = normname('%d.diff' % r)
1431 patchname = normname('%d.diff' % r)
1430 self.check_reserved_name(patchname)
1432 self.check_reserved_name(patchname)
1431 checkseries(patchname)
1433 checkseries(patchname)
1432 checkfile(patchname)
1434 checkfile(patchname)
1433 self.full_series.insert(0, patchname)
1435 self.full_series.insert(0, patchname)
1434
1436
1435 patchf = self.opener(patchname, "w")
1437 patchf = self.opener(patchname, "w")
1436 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1438 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1437 patchf.close()
1439 patchf.close()
1438
1440
1439 se = statusentry(revlog.hex(n), patchname)
1441 se = statusentry(revlog.hex(n), patchname)
1440 self.applied.insert(0, se)
1442 self.applied.insert(0, se)
1441
1443
1442 added.append(patchname)
1444 added.append(patchname)
1443 patchname = None
1445 patchname = None
1444 self.parse_series()
1446 self.parse_series()
1445 self.applied_dirty = 1
1447 self.applied_dirty = 1
1446
1448
1447 for filename in files:
1449 for filename in files:
1448 if existing:
1450 if existing:
1449 if filename == '-':
1451 if filename == '-':
1450 raise util.Abort(_('-e is incompatible with import from -'))
1452 raise util.Abort(_('-e is incompatible with import from -'))
1451 if not patchname:
1453 if not patchname:
1452 patchname = normname(filename)
1454 patchname = normname(filename)
1453 self.check_reserved_name(patchname)
1455 self.check_reserved_name(patchname)
1454 if not os.path.isfile(self.join(patchname)):
1456 if not os.path.isfile(self.join(patchname)):
1455 raise util.Abort(_("patch %s does not exist") % patchname)
1457 raise util.Abort(_("patch %s does not exist") % patchname)
1456 else:
1458 else:
1457 try:
1459 try:
1458 if filename == '-':
1460 if filename == '-':
1459 if not patchname:
1461 if not patchname:
1460 raise util.Abort(_('need --name to import a patch from -'))
1462 raise util.Abort(_('need --name to import a patch from -'))
1461 text = sys.stdin.read()
1463 text = sys.stdin.read()
1462 else:
1464 else:
1463 text = file(filename, 'rb').read()
1465 text = file(filename, 'rb').read()
1464 except IOError:
1466 except IOError:
1465 raise util.Abort(_("unable to read %s") % patchname)
1467 raise util.Abort(_("unable to read %s") % patchname)
1466 if not patchname:
1468 if not patchname:
1467 patchname = normname(os.path.basename(filename))
1469 patchname = normname(os.path.basename(filename))
1468 self.check_reserved_name(patchname)
1470 self.check_reserved_name(patchname)
1469 checkfile(patchname)
1471 checkfile(patchname)
1470 patchf = self.opener(patchname, "w")
1472 patchf = self.opener(patchname, "w")
1471 patchf.write(text)
1473 patchf.write(text)
1472 checkseries(patchname)
1474 checkseries(patchname)
1473 index = self.full_series_end() + i
1475 index = self.full_series_end() + i
1474 self.full_series[index:index] = [patchname]
1476 self.full_series[index:index] = [patchname]
1475 self.parse_series()
1477 self.parse_series()
1476 self.ui.warn("adding %s to series file\n" % patchname)
1478 self.ui.warn("adding %s to series file\n" % patchname)
1477 i += 1
1479 i += 1
1478 added.append(patchname)
1480 added.append(patchname)
1479 patchname = None
1481 patchname = None
1480 self.series_dirty = 1
1482 self.series_dirty = 1
1481 qrepo = self.qrepo()
1483 qrepo = self.qrepo()
1482 if qrepo:
1484 if qrepo:
1483 qrepo.add(added)
1485 qrepo.add(added)
1484
1486
1485 def delete(ui, repo, *patches, **opts):
1487 def delete(ui, repo, *patches, **opts):
1486 """remove patches from queue
1488 """remove patches from queue
1487
1489
1488 The patches must not be applied, unless they are arguments to
1490 The patches must not be applied, unless they are arguments to
1489 the --rev parameter. At least one patch or revision is required.
1491 the --rev parameter. At least one patch or revision is required.
1490
1492
1491 With --rev, mq will stop managing the named revisions (converting
1493 With --rev, mq will stop managing the named revisions (converting
1492 them to regular mercurial changesets). The patches must be applied
1494 them to regular mercurial changesets). The patches must be applied
1493 and at the base of the stack. This option is useful when the patches
1495 and at the base of the stack. This option is useful when the patches
1494 have been applied upstream.
1496 have been applied upstream.
1495
1497
1496 With --keep, the patch files are preserved in the patch directory."""
1498 With --keep, the patch files are preserved in the patch directory."""
1497 q = repo.mq
1499 q = repo.mq
1498 q.delete(repo, patches, opts)
1500 q.delete(repo, patches, opts)
1499 q.save_dirty()
1501 q.save_dirty()
1500 return 0
1502 return 0
1501
1503
1502 def applied(ui, repo, patch=None, **opts):
1504 def applied(ui, repo, patch=None, **opts):
1503 """print the patches already applied"""
1505 """print the patches already applied"""
1504 q = repo.mq
1506 q = repo.mq
1505 if patch:
1507 if patch:
1506 if patch not in q.series:
1508 if patch not in q.series:
1507 raise util.Abort(_("patch %s is not in series file") % patch)
1509 raise util.Abort(_("patch %s is not in series file") % patch)
1508 end = q.series.index(patch) + 1
1510 end = q.series.index(patch) + 1
1509 else:
1511 else:
1510 end = q.series_end(True)
1512 end = q.series_end(True)
1511 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1513 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1512
1514
1513 def unapplied(ui, repo, patch=None, **opts):
1515 def unapplied(ui, repo, patch=None, **opts):
1514 """print the patches not yet applied"""
1516 """print the patches not yet applied"""
1515 q = repo.mq
1517 q = repo.mq
1516 if patch:
1518 if patch:
1517 if patch not in q.series:
1519 if patch not in q.series:
1518 raise util.Abort(_("patch %s is not in series file") % patch)
1520 raise util.Abort(_("patch %s is not in series file") % patch)
1519 start = q.series.index(patch) + 1
1521 start = q.series.index(patch) + 1
1520 else:
1522 else:
1521 start = q.series_end(True)
1523 start = q.series_end(True)
1522 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1524 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1523
1525
1524 def qimport(ui, repo, *filename, **opts):
1526 def qimport(ui, repo, *filename, **opts):
1525 """import a patch
1527 """import a patch
1526
1528
1527 The patch will have the same name as its source file unless you
1529 The patch will have the same name as its source file unless you
1528 give it a new one with --name.
1530 give it a new one with --name.
1529
1531
1530 You can register an existing patch inside the patch directory
1532 You can register an existing patch inside the patch directory
1531 with the --existing flag.
1533 with the --existing flag.
1532
1534
1533 With --force, an existing patch of the same name will be overwritten.
1535 With --force, an existing patch of the same name will be overwritten.
1534
1536
1535 An existing changeset may be placed under mq control with --rev
1537 An existing changeset may be placed under mq control with --rev
1536 (e.g. qimport --rev tip -n patch will place tip under mq control).
1538 (e.g. qimport --rev tip -n patch will place tip under mq control).
1537 With --git, patches imported with --rev will use the git diff
1539 With --git, patches imported with --rev will use the git diff
1538 format.
1540 format.
1539 """
1541 """
1540 q = repo.mq
1542 q = repo.mq
1541 q.qimport(repo, filename, patchname=opts['name'],
1543 q.qimport(repo, filename, patchname=opts['name'],
1542 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1544 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1543 git=opts['git'])
1545 git=opts['git'])
1544 q.save_dirty()
1546 q.save_dirty()
1545 return 0
1547 return 0
1546
1548
1547 def init(ui, repo, **opts):
1549 def init(ui, repo, **opts):
1548 """init a new queue repository
1550 """init a new queue repository
1549
1551
1550 The queue repository is unversioned by default. If -c is
1552 The queue repository is unversioned by default. If -c is
1551 specified, qinit will create a separate nested repository
1553 specified, qinit will create a separate nested repository
1552 for patches (qinit -c may also be run later to convert
1554 for patches (qinit -c may also be run later to convert
1553 an unversioned patch repository into a versioned one).
1555 an unversioned patch repository into a versioned one).
1554 You can use qcommit to commit changes to this queue repository."""
1556 You can use qcommit to commit changes to this queue repository."""
1555 q = repo.mq
1557 q = repo.mq
1556 r = q.init(repo, create=opts['create_repo'])
1558 r = q.init(repo, create=opts['create_repo'])
1557 q.save_dirty()
1559 q.save_dirty()
1558 if r:
1560 if r:
1559 if not os.path.exists(r.wjoin('.hgignore')):
1561 if not os.path.exists(r.wjoin('.hgignore')):
1560 fp = r.wopener('.hgignore', 'w')
1562 fp = r.wopener('.hgignore', 'w')
1561 fp.write('^\\.hg\n')
1563 fp.write('^\\.hg\n')
1562 fp.write('^\\.mq\n')
1564 fp.write('^\\.mq\n')
1563 fp.write('syntax: glob\n')
1565 fp.write('syntax: glob\n')
1564 fp.write('status\n')
1566 fp.write('status\n')
1565 fp.write('guards\n')
1567 fp.write('guards\n')
1566 fp.close()
1568 fp.close()
1567 if not os.path.exists(r.wjoin('series')):
1569 if not os.path.exists(r.wjoin('series')):
1568 r.wopener('series', 'w').close()
1570 r.wopener('series', 'w').close()
1569 r.add(['.hgignore', 'series'])
1571 r.add(['.hgignore', 'series'])
1570 commands.add(ui, r)
1572 commands.add(ui, r)
1571 return 0
1573 return 0
1572
1574
1573 def clone(ui, source, dest=None, **opts):
1575 def clone(ui, source, dest=None, **opts):
1574 '''clone main and patch repository at same time
1576 '''clone main and patch repository at same time
1575
1577
1576 If source is local, destination will have no patches applied. If
1578 If source is local, destination will have no patches applied. If
1577 source is remote, this command can not check if patches are
1579 source is remote, this command can not check if patches are
1578 applied in source, so cannot guarantee that patches are not
1580 applied in source, so cannot guarantee that patches are not
1579 applied in destination. If you clone remote repository, be sure
1581 applied in destination. If you clone remote repository, be sure
1580 before that it has no patches applied.
1582 before that it has no patches applied.
1581
1583
1582 Source patch repository is looked for in <src>/.hg/patches by
1584 Source patch repository is looked for in <src>/.hg/patches by
1583 default. Use -p <url> to change.
1585 default. Use -p <url> to change.
1584
1586
1585 The patch directory must be a nested mercurial repository, as
1587 The patch directory must be a nested mercurial repository, as
1586 would be created by qinit -c.
1588 would be created by qinit -c.
1587 '''
1589 '''
1588 def patchdir(repo):
1590 def patchdir(repo):
1589 url = repo.url()
1591 url = repo.url()
1590 if url.endswith('/'):
1592 if url.endswith('/'):
1591 url = url[:-1]
1593 url = url[:-1]
1592 return url + '/.hg/patches'
1594 return url + '/.hg/patches'
1593 cmdutil.setremoteconfig(ui, opts)
1595 cmdutil.setremoteconfig(ui, opts)
1594 if dest is None:
1596 if dest is None:
1595 dest = hg.defaultdest(source)
1597 dest = hg.defaultdest(source)
1596 sr = hg.repository(ui, ui.expandpath(source))
1598 sr = hg.repository(ui, ui.expandpath(source))
1597 patchespath = opts['patches'] or patchdir(sr)
1599 patchespath = opts['patches'] or patchdir(sr)
1598 try:
1600 try:
1599 pr = hg.repository(ui, patchespath)
1601 pr = hg.repository(ui, patchespath)
1600 except hg.RepoError:
1602 except RepoError:
1601 raise util.Abort(_('versioned patch repository not found'
1603 raise util.Abort(_('versioned patch repository not found'
1602 ' (see qinit -c)'))
1604 ' (see qinit -c)'))
1603 qbase, destrev = None, None
1605 qbase, destrev = None, None
1604 if sr.local():
1606 if sr.local():
1605 if sr.mq.applied:
1607 if sr.mq.applied:
1606 qbase = revlog.bin(sr.mq.applied[0].rev)
1608 qbase = revlog.bin(sr.mq.applied[0].rev)
1607 if not hg.islocal(dest):
1609 if not hg.islocal(dest):
1608 heads = dict.fromkeys(sr.heads())
1610 heads = dict.fromkeys(sr.heads())
1609 for h in sr.heads(qbase):
1611 for h in sr.heads(qbase):
1610 del heads[h]
1612 del heads[h]
1611 destrev = heads.keys()
1613 destrev = heads.keys()
1612 destrev.append(sr.changelog.parents(qbase)[0])
1614 destrev.append(sr.changelog.parents(qbase)[0])
1613 elif sr.capable('lookup'):
1615 elif sr.capable('lookup'):
1614 qbase = sr.lookup('qbase')
1616 qbase = sr.lookup('qbase')
1615 ui.note(_('cloning main repo\n'))
1617 ui.note(_('cloning main repo\n'))
1616 sr, dr = hg.clone(ui, sr.url(), dest,
1618 sr, dr = hg.clone(ui, sr.url(), dest,
1617 pull=opts['pull'],
1619 pull=opts['pull'],
1618 rev=destrev,
1620 rev=destrev,
1619 update=False,
1621 update=False,
1620 stream=opts['uncompressed'])
1622 stream=opts['uncompressed'])
1621 ui.note(_('cloning patch repo\n'))
1623 ui.note(_('cloning patch repo\n'))
1622 spr, dpr = hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1624 spr, dpr = hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1623 pull=opts['pull'], update=not opts['noupdate'],
1625 pull=opts['pull'], update=not opts['noupdate'],
1624 stream=opts['uncompressed'])
1626 stream=opts['uncompressed'])
1625 if dr.local():
1627 if dr.local():
1626 if qbase:
1628 if qbase:
1627 ui.note(_('stripping applied patches from destination repo\n'))
1629 ui.note(_('stripping applied patches from destination repo\n'))
1628 dr.mq.strip(dr, qbase, update=False, backup=None)
1630 dr.mq.strip(dr, qbase, update=False, backup=None)
1629 if not opts['noupdate']:
1631 if not opts['noupdate']:
1630 ui.note(_('updating destination repo\n'))
1632 ui.note(_('updating destination repo\n'))
1631 hg.update(dr, dr.changelog.tip())
1633 hg.update(dr, dr.changelog.tip())
1632
1634
1633 def commit(ui, repo, *pats, **opts):
1635 def commit(ui, repo, *pats, **opts):
1634 """commit changes in the queue repository"""
1636 """commit changes in the queue repository"""
1635 q = repo.mq
1637 q = repo.mq
1636 r = q.qrepo()
1638 r = q.qrepo()
1637 if not r: raise util.Abort('no queue repository')
1639 if not r: raise util.Abort('no queue repository')
1638 commands.commit(r.ui, r, *pats, **opts)
1640 commands.commit(r.ui, r, *pats, **opts)
1639
1641
1640 def series(ui, repo, **opts):
1642 def series(ui, repo, **opts):
1641 """print the entire series file"""
1643 """print the entire series file"""
1642 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1644 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1643 return 0
1645 return 0
1644
1646
1645 def top(ui, repo, **opts):
1647 def top(ui, repo, **opts):
1646 """print the name of the current patch"""
1648 """print the name of the current patch"""
1647 q = repo.mq
1649 q = repo.mq
1648 t = q.applied and q.series_end(True) or 0
1650 t = q.applied and q.series_end(True) or 0
1649 if t:
1651 if t:
1650 return q.qseries(repo, start=t-1, length=1, status='A',
1652 return q.qseries(repo, start=t-1, length=1, status='A',
1651 summary=opts.get('summary'))
1653 summary=opts.get('summary'))
1652 else:
1654 else:
1653 ui.write("No patches applied\n")
1655 ui.write("No patches applied\n")
1654 return 1
1656 return 1
1655
1657
1656 def next(ui, repo, **opts):
1658 def next(ui, repo, **opts):
1657 """print the name of the next patch"""
1659 """print the name of the next patch"""
1658 q = repo.mq
1660 q = repo.mq
1659 end = q.series_end()
1661 end = q.series_end()
1660 if end == len(q.series):
1662 if end == len(q.series):
1661 ui.write("All patches applied\n")
1663 ui.write("All patches applied\n")
1662 return 1
1664 return 1
1663 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1665 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1664
1666
1665 def prev(ui, repo, **opts):
1667 def prev(ui, repo, **opts):
1666 """print the name of the previous patch"""
1668 """print the name of the previous patch"""
1667 q = repo.mq
1669 q = repo.mq
1668 l = len(q.applied)
1670 l = len(q.applied)
1669 if l == 1:
1671 if l == 1:
1670 ui.write("Only one patch applied\n")
1672 ui.write("Only one patch applied\n")
1671 return 1
1673 return 1
1672 if not l:
1674 if not l:
1673 ui.write("No patches applied\n")
1675 ui.write("No patches applied\n")
1674 return 1
1676 return 1
1675 return q.qseries(repo, start=l-2, length=1, status='A',
1677 return q.qseries(repo, start=l-2, length=1, status='A',
1676 summary=opts.get('summary'))
1678 summary=opts.get('summary'))
1677
1679
1678 def setupheaderopts(ui, opts):
1680 def setupheaderopts(ui, opts):
1679 def do(opt,val):
1681 def do(opt,val):
1680 if not opts[opt] and opts['current' + opt]:
1682 if not opts[opt] and opts['current' + opt]:
1681 opts[opt] = val
1683 opts[opt] = val
1682 do('user', ui.username())
1684 do('user', ui.username())
1683 do('date', "%d %d" % util.makedate())
1685 do('date', "%d %d" % util.makedate())
1684
1686
1685 def new(ui, repo, patch, *args, **opts):
1687 def new(ui, repo, patch, *args, **opts):
1686 """create a new patch
1688 """create a new patch
1687
1689
1688 qnew creates a new patch on top of the currently-applied patch
1690 qnew creates a new patch on top of the currently-applied patch
1689 (if any). It will refuse to run if there are any outstanding
1691 (if any). It will refuse to run if there are any outstanding
1690 changes unless -f is specified, in which case the patch will
1692 changes unless -f is specified, in which case the patch will
1691 be initialised with them. You may also use -I, -X, and/or a list of
1693 be initialised with them. You may also use -I, -X, and/or a list of
1692 files after the patch name to add only changes to matching files
1694 files after the patch name to add only changes to matching files
1693 to the new patch, leaving the rest as uncommitted modifications.
1695 to the new patch, leaving the rest as uncommitted modifications.
1694
1696
1695 -e, -m or -l set the patch header as well as the commit message.
1697 -e, -m or -l set the patch header as well as the commit message.
1696 If none is specified, the patch header is empty and the
1698 If none is specified, the patch header is empty and the
1697 commit message is '[mq]: PATCH'"""
1699 commit message is '[mq]: PATCH'"""
1698 q = repo.mq
1700 q = repo.mq
1699 message = cmdutil.logmessage(opts)
1701 message = cmdutil.logmessage(opts)
1700 if opts['edit']:
1702 if opts['edit']:
1701 message = ui.edit(message, ui.username())
1703 message = ui.edit(message, ui.username())
1702 opts['msg'] = message
1704 opts['msg'] = message
1703 setupheaderopts(ui, opts)
1705 setupheaderopts(ui, opts)
1704 q.new(repo, patch, *args, **opts)
1706 q.new(repo, patch, *args, **opts)
1705 q.save_dirty()
1707 q.save_dirty()
1706 return 0
1708 return 0
1707
1709
1708 def refresh(ui, repo, *pats, **opts):
1710 def refresh(ui, repo, *pats, **opts):
1709 """update the current patch
1711 """update the current patch
1710
1712
1711 If any file patterns are provided, the refreshed patch will contain only
1713 If any file patterns are provided, the refreshed patch will contain only
1712 the modifications that match those patterns; the remaining modifications
1714 the modifications that match those patterns; the remaining modifications
1713 will remain in the working directory.
1715 will remain in the working directory.
1714
1716
1715 hg add/remove/copy/rename work as usual, though you might want to use
1717 hg add/remove/copy/rename work as usual, though you might want to use
1716 git-style patches (--git or [diff] git=1) to track copies and renames.
1718 git-style patches (--git or [diff] git=1) to track copies and renames.
1717 """
1719 """
1718 q = repo.mq
1720 q = repo.mq
1719 message = cmdutil.logmessage(opts)
1721 message = cmdutil.logmessage(opts)
1720 if opts['edit']:
1722 if opts['edit']:
1721 if not q.applied:
1723 if not q.applied:
1722 ui.write(_("No patches applied\n"))
1724 ui.write(_("No patches applied\n"))
1723 return 1
1725 return 1
1724 if message:
1726 if message:
1725 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1727 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1726 patch = q.applied[-1].name
1728 patch = q.applied[-1].name
1727 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1729 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1728 message = ui.edit('\n'.join(message), user or ui.username())
1730 message = ui.edit('\n'.join(message), user or ui.username())
1729 setupheaderopts(ui, opts)
1731 setupheaderopts(ui, opts)
1730 ret = q.refresh(repo, pats, msg=message, **opts)
1732 ret = q.refresh(repo, pats, msg=message, **opts)
1731 q.save_dirty()
1733 q.save_dirty()
1732 return ret
1734 return ret
1733
1735
1734 def diff(ui, repo, *pats, **opts):
1736 def diff(ui, repo, *pats, **opts):
1735 """diff of the current patch"""
1737 """diff of the current patch"""
1736 repo.mq.diff(repo, pats, opts)
1738 repo.mq.diff(repo, pats, opts)
1737 return 0
1739 return 0
1738
1740
1739 def fold(ui, repo, *files, **opts):
1741 def fold(ui, repo, *files, **opts):
1740 """fold the named patches into the current patch
1742 """fold the named patches into the current patch
1741
1743
1742 Patches must not yet be applied. Each patch will be successively
1744 Patches must not yet be applied. Each patch will be successively
1743 applied to the current patch in the order given. If all the
1745 applied to the current patch in the order given. If all the
1744 patches apply successfully, the current patch will be refreshed
1746 patches apply successfully, the current patch will be refreshed
1745 with the new cumulative patch, and the folded patches will
1747 with the new cumulative patch, and the folded patches will
1746 be deleted. With -k/--keep, the folded patch files will not
1748 be deleted. With -k/--keep, the folded patch files will not
1747 be removed afterwards.
1749 be removed afterwards.
1748
1750
1749 The header for each folded patch will be concatenated with
1751 The header for each folded patch will be concatenated with
1750 the current patch header, separated by a line of '* * *'."""
1752 the current patch header, separated by a line of '* * *'."""
1751
1753
1752 q = repo.mq
1754 q = repo.mq
1753
1755
1754 if not files:
1756 if not files:
1755 raise util.Abort(_('qfold requires at least one patch name'))
1757 raise util.Abort(_('qfold requires at least one patch name'))
1756 if not q.check_toppatch(repo):
1758 if not q.check_toppatch(repo):
1757 raise util.Abort(_('No patches applied'))
1759 raise util.Abort(_('No patches applied'))
1758
1760
1759 message = cmdutil.logmessage(opts)
1761 message = cmdutil.logmessage(opts)
1760 if opts['edit']:
1762 if opts['edit']:
1761 if message:
1763 if message:
1762 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1764 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1763
1765
1764 parent = q.lookup('qtip')
1766 parent = q.lookup('qtip')
1765 patches = []
1767 patches = []
1766 messages = []
1768 messages = []
1767 for f in files:
1769 for f in files:
1768 p = q.lookup(f)
1770 p = q.lookup(f)
1769 if p in patches or p == parent:
1771 if p in patches or p == parent:
1770 ui.warn(_('Skipping already folded patch %s') % p)
1772 ui.warn(_('Skipping already folded patch %s') % p)
1771 if q.isapplied(p):
1773 if q.isapplied(p):
1772 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1774 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1773 patches.append(p)
1775 patches.append(p)
1774
1776
1775 for p in patches:
1777 for p in patches:
1776 if not message:
1778 if not message:
1777 messages.append(q.readheaders(p)[0])
1779 messages.append(q.readheaders(p)[0])
1778 pf = q.join(p)
1780 pf = q.join(p)
1779 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1781 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1780 if not patchsuccess:
1782 if not patchsuccess:
1781 raise util.Abort(_('Error folding patch %s') % p)
1783 raise util.Abort(_('Error folding patch %s') % p)
1782 patch.updatedir(ui, repo, files)
1784 patch.updatedir(ui, repo, files)
1783
1785
1784 if not message:
1786 if not message:
1785 message, comments, user = q.readheaders(parent)[0:3]
1787 message, comments, user = q.readheaders(parent)[0:3]
1786 for msg in messages:
1788 for msg in messages:
1787 message.append('* * *')
1789 message.append('* * *')
1788 message.extend(msg)
1790 message.extend(msg)
1789 message = '\n'.join(message)
1791 message = '\n'.join(message)
1790
1792
1791 if opts['edit']:
1793 if opts['edit']:
1792 message = ui.edit(message, user or ui.username())
1794 message = ui.edit(message, user or ui.username())
1793
1795
1794 q.refresh(repo, msg=message)
1796 q.refresh(repo, msg=message)
1795 q.delete(repo, patches, opts)
1797 q.delete(repo, patches, opts)
1796 q.save_dirty()
1798 q.save_dirty()
1797
1799
1798 def goto(ui, repo, patch, **opts):
1800 def goto(ui, repo, patch, **opts):
1799 '''push or pop patches until named patch is at top of stack'''
1801 '''push or pop patches until named patch is at top of stack'''
1800 q = repo.mq
1802 q = repo.mq
1801 patch = q.lookup(patch)
1803 patch = q.lookup(patch)
1802 if q.isapplied(patch):
1804 if q.isapplied(patch):
1803 ret = q.pop(repo, patch, force=opts['force'])
1805 ret = q.pop(repo, patch, force=opts['force'])
1804 else:
1806 else:
1805 ret = q.push(repo, patch, force=opts['force'])
1807 ret = q.push(repo, patch, force=opts['force'])
1806 q.save_dirty()
1808 q.save_dirty()
1807 return ret
1809 return ret
1808
1810
1809 def guard(ui, repo, *args, **opts):
1811 def guard(ui, repo, *args, **opts):
1810 '''set or print guards for a patch
1812 '''set or print guards for a patch
1811
1813
1812 Guards control whether a patch can be pushed. A patch with no
1814 Guards control whether a patch can be pushed. A patch with no
1813 guards is always pushed. A patch with a positive guard ("+foo") is
1815 guards is always pushed. A patch with a positive guard ("+foo") is
1814 pushed only if the qselect command has activated it. A patch with
1816 pushed only if the qselect command has activated it. A patch with
1815 a negative guard ("-foo") is never pushed if the qselect command
1817 a negative guard ("-foo") is never pushed if the qselect command
1816 has activated it.
1818 has activated it.
1817
1819
1818 With no arguments, print the currently active guards.
1820 With no arguments, print the currently active guards.
1819 With arguments, set guards for the named patch.
1821 With arguments, set guards for the named patch.
1820
1822
1821 To set a negative guard "-foo" on topmost patch ("--" is needed so
1823 To set a negative guard "-foo" on topmost patch ("--" is needed so
1822 hg will not interpret "-foo" as an option):
1824 hg will not interpret "-foo" as an option):
1823 hg qguard -- -foo
1825 hg qguard -- -foo
1824
1826
1825 To set guards on another patch:
1827 To set guards on another patch:
1826 hg qguard other.patch +2.6.17 -stable
1828 hg qguard other.patch +2.6.17 -stable
1827 '''
1829 '''
1828 def status(idx):
1830 def status(idx):
1829 guards = q.series_guards[idx] or ['unguarded']
1831 guards = q.series_guards[idx] or ['unguarded']
1830 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1832 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1831 q = repo.mq
1833 q = repo.mq
1832 patch = None
1834 patch = None
1833 args = list(args)
1835 args = list(args)
1834 if opts['list']:
1836 if opts['list']:
1835 if args or opts['none']:
1837 if args or opts['none']:
1836 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1838 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1837 for i in xrange(len(q.series)):
1839 for i in xrange(len(q.series)):
1838 status(i)
1840 status(i)
1839 return
1841 return
1840 if not args or args[0][0:1] in '-+':
1842 if not args or args[0][0:1] in '-+':
1841 if not q.applied:
1843 if not q.applied:
1842 raise util.Abort(_('no patches applied'))
1844 raise util.Abort(_('no patches applied'))
1843 patch = q.applied[-1].name
1845 patch = q.applied[-1].name
1844 if patch is None and args[0][0:1] not in '-+':
1846 if patch is None and args[0][0:1] not in '-+':
1845 patch = args.pop(0)
1847 patch = args.pop(0)
1846 if patch is None:
1848 if patch is None:
1847 raise util.Abort(_('no patch to work with'))
1849 raise util.Abort(_('no patch to work with'))
1848 if args or opts['none']:
1850 if args or opts['none']:
1849 idx = q.find_series(patch)
1851 idx = q.find_series(patch)
1850 if idx is None:
1852 if idx is None:
1851 raise util.Abort(_('no patch named %s') % patch)
1853 raise util.Abort(_('no patch named %s') % patch)
1852 q.set_guards(idx, args)
1854 q.set_guards(idx, args)
1853 q.save_dirty()
1855 q.save_dirty()
1854 else:
1856 else:
1855 status(q.series.index(q.lookup(patch)))
1857 status(q.series.index(q.lookup(patch)))
1856
1858
1857 def header(ui, repo, patch=None):
1859 def header(ui, repo, patch=None):
1858 """Print the header of the topmost or specified patch"""
1860 """Print the header of the topmost or specified patch"""
1859 q = repo.mq
1861 q = repo.mq
1860
1862
1861 if patch:
1863 if patch:
1862 patch = q.lookup(patch)
1864 patch = q.lookup(patch)
1863 else:
1865 else:
1864 if not q.applied:
1866 if not q.applied:
1865 ui.write('No patches applied\n')
1867 ui.write('No patches applied\n')
1866 return 1
1868 return 1
1867 patch = q.lookup('qtip')
1869 patch = q.lookup('qtip')
1868 message = repo.mq.readheaders(patch)[0]
1870 message = repo.mq.readheaders(patch)[0]
1869
1871
1870 ui.write('\n'.join(message) + '\n')
1872 ui.write('\n'.join(message) + '\n')
1871
1873
1872 def lastsavename(path):
1874 def lastsavename(path):
1873 (directory, base) = os.path.split(path)
1875 (directory, base) = os.path.split(path)
1874 names = os.listdir(directory)
1876 names = os.listdir(directory)
1875 namere = re.compile("%s.([0-9]+)" % base)
1877 namere = re.compile("%s.([0-9]+)" % base)
1876 maxindex = None
1878 maxindex = None
1877 maxname = None
1879 maxname = None
1878 for f in names:
1880 for f in names:
1879 m = namere.match(f)
1881 m = namere.match(f)
1880 if m:
1882 if m:
1881 index = int(m.group(1))
1883 index = int(m.group(1))
1882 if maxindex == None or index > maxindex:
1884 if maxindex == None or index > maxindex:
1883 maxindex = index
1885 maxindex = index
1884 maxname = f
1886 maxname = f
1885 if maxname:
1887 if maxname:
1886 return (os.path.join(directory, maxname), maxindex)
1888 return (os.path.join(directory, maxname), maxindex)
1887 return (None, None)
1889 return (None, None)
1888
1890
1889 def savename(path):
1891 def savename(path):
1890 (last, index) = lastsavename(path)
1892 (last, index) = lastsavename(path)
1891 if last is None:
1893 if last is None:
1892 index = 0
1894 index = 0
1893 newpath = path + ".%d" % (index + 1)
1895 newpath = path + ".%d" % (index + 1)
1894 return newpath
1896 return newpath
1895
1897
1896 def push(ui, repo, patch=None, **opts):
1898 def push(ui, repo, patch=None, **opts):
1897 """push the next patch onto the stack"""
1899 """push the next patch onto the stack"""
1898 q = repo.mq
1900 q = repo.mq
1899 mergeq = None
1901 mergeq = None
1900
1902
1901 if opts['all']:
1903 if opts['all']:
1902 if not q.series:
1904 if not q.series:
1903 ui.warn(_('no patches in series\n'))
1905 ui.warn(_('no patches in series\n'))
1904 return 0
1906 return 0
1905 patch = q.series[-1]
1907 patch = q.series[-1]
1906 if opts['merge']:
1908 if opts['merge']:
1907 if opts['name']:
1909 if opts['name']:
1908 newpath = opts['name']
1910 newpath = opts['name']
1909 else:
1911 else:
1910 newpath, i = lastsavename(q.path)
1912 newpath, i = lastsavename(q.path)
1911 if not newpath:
1913 if not newpath:
1912 ui.warn("no saved queues found, please use -n\n")
1914 ui.warn("no saved queues found, please use -n\n")
1913 return 1
1915 return 1
1914 mergeq = queue(ui, repo.join(""), newpath)
1916 mergeq = queue(ui, repo.join(""), newpath)
1915 ui.warn("merging with queue at: %s\n" % mergeq.path)
1917 ui.warn("merging with queue at: %s\n" % mergeq.path)
1916 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1918 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1917 mergeq=mergeq)
1919 mergeq=mergeq)
1918 return ret
1920 return ret
1919
1921
1920 def pop(ui, repo, patch=None, **opts):
1922 def pop(ui, repo, patch=None, **opts):
1921 """pop the current patch off the stack"""
1923 """pop the current patch off the stack"""
1922 localupdate = True
1924 localupdate = True
1923 if opts['name']:
1925 if opts['name']:
1924 q = queue(ui, repo.join(""), repo.join(opts['name']))
1926 q = queue(ui, repo.join(""), repo.join(opts['name']))
1925 ui.warn('using patch queue: %s\n' % q.path)
1927 ui.warn('using patch queue: %s\n' % q.path)
1926 localupdate = False
1928 localupdate = False
1927 else:
1929 else:
1928 q = repo.mq
1930 q = repo.mq
1929 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1931 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1930 all=opts['all'])
1932 all=opts['all'])
1931 q.save_dirty()
1933 q.save_dirty()
1932 return ret
1934 return ret
1933
1935
1934 def rename(ui, repo, patch, name=None, **opts):
1936 def rename(ui, repo, patch, name=None, **opts):
1935 """rename a patch
1937 """rename a patch
1936
1938
1937 With one argument, renames the current patch to PATCH1.
1939 With one argument, renames the current patch to PATCH1.
1938 With two arguments, renames PATCH1 to PATCH2."""
1940 With two arguments, renames PATCH1 to PATCH2."""
1939
1941
1940 q = repo.mq
1942 q = repo.mq
1941
1943
1942 if not name:
1944 if not name:
1943 name = patch
1945 name = patch
1944 patch = None
1946 patch = None
1945
1947
1946 if patch:
1948 if patch:
1947 patch = q.lookup(patch)
1949 patch = q.lookup(patch)
1948 else:
1950 else:
1949 if not q.applied:
1951 if not q.applied:
1950 ui.write(_('No patches applied\n'))
1952 ui.write(_('No patches applied\n'))
1951 return
1953 return
1952 patch = q.lookup('qtip')
1954 patch = q.lookup('qtip')
1953 absdest = q.join(name)
1955 absdest = q.join(name)
1954 if os.path.isdir(absdest):
1956 if os.path.isdir(absdest):
1955 name = normname(os.path.join(name, os.path.basename(patch)))
1957 name = normname(os.path.join(name, os.path.basename(patch)))
1956 absdest = q.join(name)
1958 absdest = q.join(name)
1957 if os.path.exists(absdest):
1959 if os.path.exists(absdest):
1958 raise util.Abort(_('%s already exists') % absdest)
1960 raise util.Abort(_('%s already exists') % absdest)
1959
1961
1960 if name in q.series:
1962 if name in q.series:
1961 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1963 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1962
1964
1963 if ui.verbose:
1965 if ui.verbose:
1964 ui.write('Renaming %s to %s\n' % (patch, name))
1966 ui.write('Renaming %s to %s\n' % (patch, name))
1965 i = q.find_series(patch)
1967 i = q.find_series(patch)
1966 guards = q.guard_re.findall(q.full_series[i])
1968 guards = q.guard_re.findall(q.full_series[i])
1967 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1969 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1968 q.parse_series()
1970 q.parse_series()
1969 q.series_dirty = 1
1971 q.series_dirty = 1
1970
1972
1971 info = q.isapplied(patch)
1973 info = q.isapplied(patch)
1972 if info:
1974 if info:
1973 q.applied[info[0]] = statusentry(info[1], name)
1975 q.applied[info[0]] = statusentry(info[1], name)
1974 q.applied_dirty = 1
1976 q.applied_dirty = 1
1975
1977
1976 util.rename(q.join(patch), absdest)
1978 util.rename(q.join(patch), absdest)
1977 r = q.qrepo()
1979 r = q.qrepo()
1978 if r:
1980 if r:
1979 wlock = r.wlock()
1981 wlock = r.wlock()
1980 try:
1982 try:
1981 if r.dirstate[name] == 'r':
1983 if r.dirstate[name] == 'r':
1982 r.undelete([name])
1984 r.undelete([name])
1983 r.copy(patch, name)
1985 r.copy(patch, name)
1984 r.remove([patch], False)
1986 r.remove([patch], False)
1985 finally:
1987 finally:
1986 del wlock
1988 del wlock
1987
1989
1988 q.save_dirty()
1990 q.save_dirty()
1989
1991
1990 def restore(ui, repo, rev, **opts):
1992 def restore(ui, repo, rev, **opts):
1991 """restore the queue state saved by a rev"""
1993 """restore the queue state saved by a rev"""
1992 rev = repo.lookup(rev)
1994 rev = repo.lookup(rev)
1993 q = repo.mq
1995 q = repo.mq
1994 q.restore(repo, rev, delete=opts['delete'],
1996 q.restore(repo, rev, delete=opts['delete'],
1995 qupdate=opts['update'])
1997 qupdate=opts['update'])
1996 q.save_dirty()
1998 q.save_dirty()
1997 return 0
1999 return 0
1998
2000
1999 def save(ui, repo, **opts):
2001 def save(ui, repo, **opts):
2000 """save current queue state"""
2002 """save current queue state"""
2001 q = repo.mq
2003 q = repo.mq
2002 message = cmdutil.logmessage(opts)
2004 message = cmdutil.logmessage(opts)
2003 ret = q.save(repo, msg=message)
2005 ret = q.save(repo, msg=message)
2004 if ret:
2006 if ret:
2005 return ret
2007 return ret
2006 q.save_dirty()
2008 q.save_dirty()
2007 if opts['copy']:
2009 if opts['copy']:
2008 path = q.path
2010 path = q.path
2009 if opts['name']:
2011 if opts['name']:
2010 newpath = os.path.join(q.basepath, opts['name'])
2012 newpath = os.path.join(q.basepath, opts['name'])
2011 if os.path.exists(newpath):
2013 if os.path.exists(newpath):
2012 if not os.path.isdir(newpath):
2014 if not os.path.isdir(newpath):
2013 raise util.Abort(_('destination %s exists and is not '
2015 raise util.Abort(_('destination %s exists and is not '
2014 'a directory') % newpath)
2016 'a directory') % newpath)
2015 if not opts['force']:
2017 if not opts['force']:
2016 raise util.Abort(_('destination %s exists, '
2018 raise util.Abort(_('destination %s exists, '
2017 'use -f to force') % newpath)
2019 'use -f to force') % newpath)
2018 else:
2020 else:
2019 newpath = savename(path)
2021 newpath = savename(path)
2020 ui.warn("copy %s to %s\n" % (path, newpath))
2022 ui.warn("copy %s to %s\n" % (path, newpath))
2021 util.copyfiles(path, newpath)
2023 util.copyfiles(path, newpath)
2022 if opts['empty']:
2024 if opts['empty']:
2023 try:
2025 try:
2024 os.unlink(q.join(q.status_path))
2026 os.unlink(q.join(q.status_path))
2025 except:
2027 except:
2026 pass
2028 pass
2027 return 0
2029 return 0
2028
2030
2029 def strip(ui, repo, rev, **opts):
2031 def strip(ui, repo, rev, **opts):
2030 """strip a revision and all later revs on the same branch"""
2032 """strip a revision and all later revs on the same branch"""
2031 rev = repo.lookup(rev)
2033 rev = repo.lookup(rev)
2032 backup = 'all'
2034 backup = 'all'
2033 if opts['backup']:
2035 if opts['backup']:
2034 backup = 'strip'
2036 backup = 'strip'
2035 elif opts['nobackup']:
2037 elif opts['nobackup']:
2036 backup = 'none'
2038 backup = 'none'
2037 update = repo.dirstate.parents()[0] != revlog.nullid
2039 update = repo.dirstate.parents()[0] != revlog.nullid
2038 repo.mq.strip(repo, rev, backup=backup, update=update)
2040 repo.mq.strip(repo, rev, backup=backup, update=update)
2039 return 0
2041 return 0
2040
2042
2041 def select(ui, repo, *args, **opts):
2043 def select(ui, repo, *args, **opts):
2042 '''set or print guarded patches to push
2044 '''set or print guarded patches to push
2043
2045
2044 Use the qguard command to set or print guards on patch, then use
2046 Use the qguard command to set or print guards on patch, then use
2045 qselect to tell mq which guards to use. A patch will be pushed if it
2047 qselect to tell mq which guards to use. A patch will be pushed if it
2046 has no guards or any positive guards match the currently selected guard,
2048 has no guards or any positive guards match the currently selected guard,
2047 but will not be pushed if any negative guards match the current guard.
2049 but will not be pushed if any negative guards match the current guard.
2048 For example:
2050 For example:
2049
2051
2050 qguard foo.patch -stable (negative guard)
2052 qguard foo.patch -stable (negative guard)
2051 qguard bar.patch +stable (positive guard)
2053 qguard bar.patch +stable (positive guard)
2052 qselect stable
2054 qselect stable
2053
2055
2054 This activates the "stable" guard. mq will skip foo.patch (because
2056 This activates the "stable" guard. mq will skip foo.patch (because
2055 it has a negative match) but push bar.patch (because it
2057 it has a negative match) but push bar.patch (because it
2056 has a positive match).
2058 has a positive match).
2057
2059
2058 With no arguments, prints the currently active guards.
2060 With no arguments, prints the currently active guards.
2059 With one argument, sets the active guard.
2061 With one argument, sets the active guard.
2060
2062
2061 Use -n/--none to deactivate guards (no other arguments needed).
2063 Use -n/--none to deactivate guards (no other arguments needed).
2062 When no guards are active, patches with positive guards are skipped
2064 When no guards are active, patches with positive guards are skipped
2063 and patches with negative guards are pushed.
2065 and patches with negative guards are pushed.
2064
2066
2065 qselect can change the guards on applied patches. It does not pop
2067 qselect can change the guards on applied patches. It does not pop
2066 guarded patches by default. Use --pop to pop back to the last applied
2068 guarded patches by default. Use --pop to pop back to the last applied
2067 patch that is not guarded. Use --reapply (which implies --pop) to push
2069 patch that is not guarded. Use --reapply (which implies --pop) to push
2068 back to the current patch afterwards, but skip guarded patches.
2070 back to the current patch afterwards, but skip guarded patches.
2069
2071
2070 Use -s/--series to print a list of all guards in the series file (no
2072 Use -s/--series to print a list of all guards in the series file (no
2071 other arguments needed). Use -v for more information.'''
2073 other arguments needed). Use -v for more information.'''
2072
2074
2073 q = repo.mq
2075 q = repo.mq
2074 guards = q.active()
2076 guards = q.active()
2075 if args or opts['none']:
2077 if args or opts['none']:
2076 old_unapplied = q.unapplied(repo)
2078 old_unapplied = q.unapplied(repo)
2077 old_guarded = [i for i in xrange(len(q.applied)) if
2079 old_guarded = [i for i in xrange(len(q.applied)) if
2078 not q.pushable(i)[0]]
2080 not q.pushable(i)[0]]
2079 q.set_active(args)
2081 q.set_active(args)
2080 q.save_dirty()
2082 q.save_dirty()
2081 if not args:
2083 if not args:
2082 ui.status(_('guards deactivated\n'))
2084 ui.status(_('guards deactivated\n'))
2083 if not opts['pop'] and not opts['reapply']:
2085 if not opts['pop'] and not opts['reapply']:
2084 unapplied = q.unapplied(repo)
2086 unapplied = q.unapplied(repo)
2085 guarded = [i for i in xrange(len(q.applied))
2087 guarded = [i for i in xrange(len(q.applied))
2086 if not q.pushable(i)[0]]
2088 if not q.pushable(i)[0]]
2087 if len(unapplied) != len(old_unapplied):
2089 if len(unapplied) != len(old_unapplied):
2088 ui.status(_('number of unguarded, unapplied patches has '
2090 ui.status(_('number of unguarded, unapplied patches has '
2089 'changed from %d to %d\n') %
2091 'changed from %d to %d\n') %
2090 (len(old_unapplied), len(unapplied)))
2092 (len(old_unapplied), len(unapplied)))
2091 if len(guarded) != len(old_guarded):
2093 if len(guarded) != len(old_guarded):
2092 ui.status(_('number of guarded, applied patches has changed '
2094 ui.status(_('number of guarded, applied patches has changed '
2093 'from %d to %d\n') %
2095 'from %d to %d\n') %
2094 (len(old_guarded), len(guarded)))
2096 (len(old_guarded), len(guarded)))
2095 elif opts['series']:
2097 elif opts['series']:
2096 guards = {}
2098 guards = {}
2097 noguards = 0
2099 noguards = 0
2098 for gs in q.series_guards:
2100 for gs in q.series_guards:
2099 if not gs:
2101 if not gs:
2100 noguards += 1
2102 noguards += 1
2101 for g in gs:
2103 for g in gs:
2102 guards.setdefault(g, 0)
2104 guards.setdefault(g, 0)
2103 guards[g] += 1
2105 guards[g] += 1
2104 if ui.verbose:
2106 if ui.verbose:
2105 guards['NONE'] = noguards
2107 guards['NONE'] = noguards
2106 guards = guards.items()
2108 guards = guards.items()
2107 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2109 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2108 if guards:
2110 if guards:
2109 ui.note(_('guards in series file:\n'))
2111 ui.note(_('guards in series file:\n'))
2110 for guard, count in guards:
2112 for guard, count in guards:
2111 ui.note('%2d ' % count)
2113 ui.note('%2d ' % count)
2112 ui.write(guard, '\n')
2114 ui.write(guard, '\n')
2113 else:
2115 else:
2114 ui.note(_('no guards in series file\n'))
2116 ui.note(_('no guards in series file\n'))
2115 else:
2117 else:
2116 if guards:
2118 if guards:
2117 ui.note(_('active guards:\n'))
2119 ui.note(_('active guards:\n'))
2118 for g in guards:
2120 for g in guards:
2119 ui.write(g, '\n')
2121 ui.write(g, '\n')
2120 else:
2122 else:
2121 ui.write(_('no active guards\n'))
2123 ui.write(_('no active guards\n'))
2122 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2124 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2123 popped = False
2125 popped = False
2124 if opts['pop'] or opts['reapply']:
2126 if opts['pop'] or opts['reapply']:
2125 for i in xrange(len(q.applied)):
2127 for i in xrange(len(q.applied)):
2126 pushable, reason = q.pushable(i)
2128 pushable, reason = q.pushable(i)
2127 if not pushable:
2129 if not pushable:
2128 ui.status(_('popping guarded patches\n'))
2130 ui.status(_('popping guarded patches\n'))
2129 popped = True
2131 popped = True
2130 if i == 0:
2132 if i == 0:
2131 q.pop(repo, all=True)
2133 q.pop(repo, all=True)
2132 else:
2134 else:
2133 q.pop(repo, i-1)
2135 q.pop(repo, i-1)
2134 break
2136 break
2135 if popped:
2137 if popped:
2136 try:
2138 try:
2137 if reapply:
2139 if reapply:
2138 ui.status(_('reapplying unguarded patches\n'))
2140 ui.status(_('reapplying unguarded patches\n'))
2139 q.push(repo, reapply)
2141 q.push(repo, reapply)
2140 finally:
2142 finally:
2141 q.save_dirty()
2143 q.save_dirty()
2142
2144
2143 def reposetup(ui, repo):
2145 def reposetup(ui, repo):
2144 class mqrepo(repo.__class__):
2146 class mqrepo(repo.__class__):
2145 def abort_if_wdir_patched(self, errmsg, force=False):
2147 def abort_if_wdir_patched(self, errmsg, force=False):
2146 if self.mq.applied and not force:
2148 if self.mq.applied and not force:
2147 parent = revlog.hex(self.dirstate.parents()[0])
2149 parent = revlog.hex(self.dirstate.parents()[0])
2148 if parent in [s.rev for s in self.mq.applied]:
2150 if parent in [s.rev for s in self.mq.applied]:
2149 raise util.Abort(errmsg)
2151 raise util.Abort(errmsg)
2150
2152
2151 def commit(self, *args, **opts):
2153 def commit(self, *args, **opts):
2152 if len(args) >= 6:
2154 if len(args) >= 6:
2153 force = args[5]
2155 force = args[5]
2154 else:
2156 else:
2155 force = opts.get('force')
2157 force = opts.get('force')
2156 self.abort_if_wdir_patched(
2158 self.abort_if_wdir_patched(
2157 _('cannot commit over an applied mq patch'),
2159 _('cannot commit over an applied mq patch'),
2158 force)
2160 force)
2159
2161
2160 return super(mqrepo, self).commit(*args, **opts)
2162 return super(mqrepo, self).commit(*args, **opts)
2161
2163
2162 def push(self, remote, force=False, revs=None):
2164 def push(self, remote, force=False, revs=None):
2163 if self.mq.applied and not force and not revs:
2165 if self.mq.applied and not force and not revs:
2164 raise util.Abort(_('source has mq patches applied'))
2166 raise util.Abort(_('source has mq patches applied'))
2165 return super(mqrepo, self).push(remote, force, revs)
2167 return super(mqrepo, self).push(remote, force, revs)
2166
2168
2167 def tags(self):
2169 def tags(self):
2168 if self.tagscache:
2170 if self.tagscache:
2169 return self.tagscache
2171 return self.tagscache
2170
2172
2171 tagscache = super(mqrepo, self).tags()
2173 tagscache = super(mqrepo, self).tags()
2172
2174
2173 q = self.mq
2175 q = self.mq
2174 if not q.applied:
2176 if not q.applied:
2175 return tagscache
2177 return tagscache
2176
2178
2177 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2179 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2178
2180
2179 if mqtags[-1][0] not in self.changelog.nodemap:
2181 if mqtags[-1][0] not in self.changelog.nodemap:
2180 self.ui.warn('mq status file refers to unknown node %s\n'
2182 self.ui.warn('mq status file refers to unknown node %s\n'
2181 % revlog.short(mqtags[-1][0]))
2183 % revlog.short(mqtags[-1][0]))
2182 return tagscache
2184 return tagscache
2183
2185
2184 mqtags.append((mqtags[-1][0], 'qtip'))
2186 mqtags.append((mqtags[-1][0], 'qtip'))
2185 mqtags.append((mqtags[0][0], 'qbase'))
2187 mqtags.append((mqtags[0][0], 'qbase'))
2186 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2188 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2187 for patch in mqtags:
2189 for patch in mqtags:
2188 if patch[1] in tagscache:
2190 if patch[1] in tagscache:
2189 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2191 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2190 else:
2192 else:
2191 tagscache[patch[1]] = patch[0]
2193 tagscache[patch[1]] = patch[0]
2192
2194
2193 return tagscache
2195 return tagscache
2194
2196
2195 def _branchtags(self, partial, lrev):
2197 def _branchtags(self, partial, lrev):
2196 q = self.mq
2198 q = self.mq
2197 if not q.applied:
2199 if not q.applied:
2198 return super(mqrepo, self)._branchtags(partial, lrev)
2200 return super(mqrepo, self)._branchtags(partial, lrev)
2199
2201
2200 cl = self.changelog
2202 cl = self.changelog
2201 qbasenode = revlog.bin(q.applied[0].rev)
2203 qbasenode = revlog.bin(q.applied[0].rev)
2202 if qbasenode not in cl.nodemap:
2204 if qbasenode not in cl.nodemap:
2203 self.ui.warn('mq status file refers to unknown node %s\n'
2205 self.ui.warn('mq status file refers to unknown node %s\n'
2204 % revlog.short(qbasenode))
2206 % revlog.short(qbasenode))
2205 return super(mqrepo, self)._branchtags(partial, lrev)
2207 return super(mqrepo, self)._branchtags(partial, lrev)
2206
2208
2207 qbase = cl.rev(qbasenode)
2209 qbase = cl.rev(qbasenode)
2208 start = lrev + 1
2210 start = lrev + 1
2209 if start < qbase:
2211 if start < qbase:
2210 # update the cache (excluding the patches) and save it
2212 # update the cache (excluding the patches) and save it
2211 self._updatebranchcache(partial, lrev+1, qbase)
2213 self._updatebranchcache(partial, lrev+1, qbase)
2212 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2214 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2213 start = qbase
2215 start = qbase
2214 # if start = qbase, the cache is as updated as it should be.
2216 # if start = qbase, the cache is as updated as it should be.
2215 # if start > qbase, the cache includes (part of) the patches.
2217 # if start > qbase, the cache includes (part of) the patches.
2216 # we might as well use it, but we won't save it.
2218 # we might as well use it, but we won't save it.
2217
2219
2218 # update the cache up to the tip
2220 # update the cache up to the tip
2219 self._updatebranchcache(partial, start, cl.count())
2221 self._updatebranchcache(partial, start, cl.count())
2220
2222
2221 return partial
2223 return partial
2222
2224
2223 if repo.local():
2225 if repo.local():
2224 repo.__class__ = mqrepo
2226 repo.__class__ = mqrepo
2225 repo.mq = queue(ui, repo.join(""))
2227 repo.mq = queue(ui, repo.join(""))
2226
2228
2227 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2229 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2228
2230
2229 headeropts = [
2231 headeropts = [
2230 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2232 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2231 ('u', 'user', '', _('add "From: <given user>" to patch')),
2233 ('u', 'user', '', _('add "From: <given user>" to patch')),
2232 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2234 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2233 ('d', 'date', '', _('add "Date: <given date>" to patch'))]
2235 ('d', 'date', '', _('add "Date: <given date>" to patch'))]
2234
2236
2235 cmdtable = {
2237 cmdtable = {
2236 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2238 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2237 "qclone":
2239 "qclone":
2238 (clone,
2240 (clone,
2239 [('', 'pull', None, _('use pull protocol to copy metadata')),
2241 [('', 'pull', None, _('use pull protocol to copy metadata')),
2240 ('U', 'noupdate', None, _('do not update the new working directories')),
2242 ('U', 'noupdate', None, _('do not update the new working directories')),
2241 ('', 'uncompressed', None,
2243 ('', 'uncompressed', None,
2242 _('use uncompressed transfer (fast over LAN)')),
2244 _('use uncompressed transfer (fast over LAN)')),
2243 ('p', 'patches', '', _('location of source patch repo')),
2245 ('p', 'patches', '', _('location of source patch repo')),
2244 ] + commands.remoteopts,
2246 ] + commands.remoteopts,
2245 _('hg qclone [OPTION]... SOURCE [DEST]')),
2247 _('hg qclone [OPTION]... SOURCE [DEST]')),
2246 "qcommit|qci":
2248 "qcommit|qci":
2247 (commit,
2249 (commit,
2248 commands.table["^commit|ci"][1],
2250 commands.table["^commit|ci"][1],
2249 _('hg qcommit [OPTION]... [FILE]...')),
2251 _('hg qcommit [OPTION]... [FILE]...')),
2250 "^qdiff":
2252 "^qdiff":
2251 (diff,
2253 (diff,
2252 [('g', 'git', None, _('use git extended diff format')),
2254 [('g', 'git', None, _('use git extended diff format')),
2253 ('U', 'unified', 3, _('number of lines of context to show')),
2255 ('U', 'unified', 3, _('number of lines of context to show')),
2254 ] + commands.walkopts,
2256 ] + commands.walkopts,
2255 _('hg qdiff [-I] [-X] [-U NUM] [-g] [FILE]...')),
2257 _('hg qdiff [-I] [-X] [-U NUM] [-g] [FILE]...')),
2256 "qdelete|qremove|qrm":
2258 "qdelete|qremove|qrm":
2257 (delete,
2259 (delete,
2258 [('k', 'keep', None, _('keep patch file')),
2260 [('k', 'keep', None, _('keep patch file')),
2259 ('r', 'rev', [], _('stop managing a revision'))],
2261 ('r', 'rev', [], _('stop managing a revision'))],
2260 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2262 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2261 'qfold':
2263 'qfold':
2262 (fold,
2264 (fold,
2263 [('e', 'edit', None, _('edit patch header')),
2265 [('e', 'edit', None, _('edit patch header')),
2264 ('k', 'keep', None, _('keep folded patch files')),
2266 ('k', 'keep', None, _('keep folded patch files')),
2265 ] + commands.commitopts,
2267 ] + commands.commitopts,
2266 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2268 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2267 'qgoto':
2269 'qgoto':
2268 (goto,
2270 (goto,
2269 [('f', 'force', None, _('overwrite any local changes'))],
2271 [('f', 'force', None, _('overwrite any local changes'))],
2270 _('hg qgoto [OPTION]... PATCH')),
2272 _('hg qgoto [OPTION]... PATCH')),
2271 'qguard':
2273 'qguard':
2272 (guard,
2274 (guard,
2273 [('l', 'list', None, _('list all patches and guards')),
2275 [('l', 'list', None, _('list all patches and guards')),
2274 ('n', 'none', None, _('drop all guards'))],
2276 ('n', 'none', None, _('drop all guards'))],
2275 _('hg qguard [-l] [-n] [PATCH] [+GUARD]... [-GUARD]...')),
2277 _('hg qguard [-l] [-n] [PATCH] [+GUARD]... [-GUARD]...')),
2276 'qheader': (header, [], _('hg qheader [PATCH]')),
2278 'qheader': (header, [], _('hg qheader [PATCH]')),
2277 "^qimport":
2279 "^qimport":
2278 (qimport,
2280 (qimport,
2279 [('e', 'existing', None, 'import file in patch dir'),
2281 [('e', 'existing', None, 'import file in patch dir'),
2280 ('n', 'name', '', 'patch file name'),
2282 ('n', 'name', '', 'patch file name'),
2281 ('f', 'force', None, 'overwrite existing files'),
2283 ('f', 'force', None, 'overwrite existing files'),
2282 ('r', 'rev', [], 'place existing revisions under mq control'),
2284 ('r', 'rev', [], 'place existing revisions under mq control'),
2283 ('g', 'git', None, _('use git extended diff format'))],
2285 ('g', 'git', None, _('use git extended diff format'))],
2284 _('hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...')),
2286 _('hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...')),
2285 "^qinit":
2287 "^qinit":
2286 (init,
2288 (init,
2287 [('c', 'create-repo', None, 'create queue repository')],
2289 [('c', 'create-repo', None, 'create queue repository')],
2288 _('hg qinit [-c]')),
2290 _('hg qinit [-c]')),
2289 "qnew":
2291 "qnew":
2290 (new,
2292 (new,
2291 [('e', 'edit', None, _('edit commit message')),
2293 [('e', 'edit', None, _('edit commit message')),
2292 ('f', 'force', None, _('import uncommitted changes into patch')),
2294 ('f', 'force', None, _('import uncommitted changes into patch')),
2293 ('g', 'git', None, _('use git extended diff format')),
2295 ('g', 'git', None, _('use git extended diff format')),
2294 ] + commands.walkopts + commands.commitopts + headeropts,
2296 ] + commands.walkopts + commands.commitopts + headeropts,
2295 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2297 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2296 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2298 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2297 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2299 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2298 "^qpop":
2300 "^qpop":
2299 (pop,
2301 (pop,
2300 [('a', 'all', None, _('pop all patches')),
2302 [('a', 'all', None, _('pop all patches')),
2301 ('n', 'name', '', _('queue name to pop')),
2303 ('n', 'name', '', _('queue name to pop')),
2302 ('f', 'force', None, _('forget any local changes'))],
2304 ('f', 'force', None, _('forget any local changes'))],
2303 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2305 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2304 "^qpush":
2306 "^qpush":
2305 (push,
2307 (push,
2306 [('f', 'force', None, _('apply if the patch has rejects')),
2308 [('f', 'force', None, _('apply if the patch has rejects')),
2307 ('l', 'list', None, _('list patch name in commit text')),
2309 ('l', 'list', None, _('list patch name in commit text')),
2308 ('a', 'all', None, _('apply all patches')),
2310 ('a', 'all', None, _('apply all patches')),
2309 ('m', 'merge', None, _('merge from another queue')),
2311 ('m', 'merge', None, _('merge from another queue')),
2310 ('n', 'name', '', _('merge queue name'))],
2312 ('n', 'name', '', _('merge queue name'))],
2311 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2313 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2312 "^qrefresh":
2314 "^qrefresh":
2313 (refresh,
2315 (refresh,
2314 [('e', 'edit', None, _('edit commit message')),
2316 [('e', 'edit', None, _('edit commit message')),
2315 ('g', 'git', None, _('use git extended diff format')),
2317 ('g', 'git', None, _('use git extended diff format')),
2316 ('s', 'short', None, _('refresh only files already in the patch')),
2318 ('s', 'short', None, _('refresh only files already in the patch')),
2317 ] + commands.walkopts + commands.commitopts + headeropts,
2319 ] + commands.walkopts + commands.commitopts + headeropts,
2318 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2320 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2319 'qrename|qmv':
2321 'qrename|qmv':
2320 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2322 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2321 "qrestore":
2323 "qrestore":
2322 (restore,
2324 (restore,
2323 [('d', 'delete', None, _('delete save entry')),
2325 [('d', 'delete', None, _('delete save entry')),
2324 ('u', 'update', None, _('update queue working dir'))],
2326 ('u', 'update', None, _('update queue working dir'))],
2325 _('hg qrestore [-d] [-u] REV')),
2327 _('hg qrestore [-d] [-u] REV')),
2326 "qsave":
2328 "qsave":
2327 (save,
2329 (save,
2328 [('c', 'copy', None, _('copy patch directory')),
2330 [('c', 'copy', None, _('copy patch directory')),
2329 ('n', 'name', '', _('copy directory name')),
2331 ('n', 'name', '', _('copy directory name')),
2330 ('e', 'empty', None, _('clear queue status file')),
2332 ('e', 'empty', None, _('clear queue status file')),
2331 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2333 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2332 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2334 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2333 "qselect":
2335 "qselect":
2334 (select,
2336 (select,
2335 [('n', 'none', None, _('disable all guards')),
2337 [('n', 'none', None, _('disable all guards')),
2336 ('s', 'series', None, _('list all guards in series file')),
2338 ('s', 'series', None, _('list all guards in series file')),
2337 ('', 'pop', None, _('pop to before first guarded applied patch')),
2339 ('', 'pop', None, _('pop to before first guarded applied patch')),
2338 ('', 'reapply', None, _('pop, then reapply patches'))],
2340 ('', 'reapply', None, _('pop, then reapply patches'))],
2339 _('hg qselect [OPTION]... [GUARD]...')),
2341 _('hg qselect [OPTION]... [GUARD]...')),
2340 "qseries":
2342 "qseries":
2341 (series,
2343 (series,
2342 [('m', 'missing', None, _('print patches not in series')),
2344 [('m', 'missing', None, _('print patches not in series')),
2343 ] + seriesopts,
2345 ] + seriesopts,
2344 _('hg qseries [-ms]')),
2346 _('hg qseries [-ms]')),
2345 "^strip":
2347 "^strip":
2346 (strip,
2348 (strip,
2347 [('b', 'backup', None, _('bundle unrelated changesets')),
2349 [('b', 'backup', None, _('bundle unrelated changesets')),
2348 ('n', 'nobackup', None, _('no backups'))],
2350 ('n', 'nobackup', None, _('no backups'))],
2349 _('hg strip [-f] [-b] [-n] REV')),
2351 _('hg strip [-f] [-b] [-n] REV')),
2350 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2352 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2351 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2353 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2352 }
2354 }
@@ -1,3179 +1,3180 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from repo import RepoError
9 from i18n import _
10 from i18n import _
10 import os, re, sys, urllib
11 import os, re, sys, urllib
11 import hg, util, revlog, bundlerepo, extensions
12 import hg, util, revlog, bundlerepo, extensions
12 import difflib, patch, time, help, mdiff, tempfile
13 import difflib, patch, time, help, mdiff, tempfile
13 import version, socket
14 import version, socket
14 import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
15 import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
15
16
16 # Commands start here, listed alphabetically
17 # Commands start here, listed alphabetically
17
18
18 def add(ui, repo, *pats, **opts):
19 def add(ui, repo, *pats, **opts):
19 """add the specified files on the next commit
20 """add the specified files on the next commit
20
21
21 Schedule files to be version controlled and added to the repository.
22 Schedule files to be version controlled and added to the repository.
22
23
23 The files will be added to the repository at the next commit. To
24 The files will be added to the repository at the next commit. To
24 undo an add before that, see hg revert.
25 undo an add before that, see hg revert.
25
26
26 If no names are given, add all files in the repository.
27 If no names are given, add all files in the repository.
27 """
28 """
28
29
29 rejected = None
30 rejected = None
30 exacts = {}
31 exacts = {}
31 names = []
32 names = []
32 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
33 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
33 badmatch=util.always):
34 badmatch=util.always):
34 if exact:
35 if exact:
35 if ui.verbose:
36 if ui.verbose:
36 ui.status(_('adding %s\n') % rel)
37 ui.status(_('adding %s\n') % rel)
37 names.append(abs)
38 names.append(abs)
38 exacts[abs] = 1
39 exacts[abs] = 1
39 elif abs not in repo.dirstate:
40 elif abs not in repo.dirstate:
40 ui.status(_('adding %s\n') % rel)
41 ui.status(_('adding %s\n') % rel)
41 names.append(abs)
42 names.append(abs)
42 if not opts.get('dry_run'):
43 if not opts.get('dry_run'):
43 rejected = repo.add(names)
44 rejected = repo.add(names)
44 rejected = [p for p in rejected if p in exacts]
45 rejected = [p for p in rejected if p in exacts]
45 return rejected and 1 or 0
46 return rejected and 1 or 0
46
47
47 def addremove(ui, repo, *pats, **opts):
48 def addremove(ui, repo, *pats, **opts):
48 """add all new files, delete all missing files
49 """add all new files, delete all missing files
49
50
50 Add all new files and remove all missing files from the repository.
51 Add all new files and remove all missing files from the repository.
51
52
52 New files are ignored if they match any of the patterns in .hgignore. As
53 New files are ignored if they match any of the patterns in .hgignore. As
53 with add, these changes take effect at the next commit.
54 with add, these changes take effect at the next commit.
54
55
55 Use the -s option to detect renamed files. With a parameter > 0,
56 Use the -s option to detect renamed files. With a parameter > 0,
56 this compares every removed file with every added file and records
57 this compares every removed file with every added file and records
57 those similar enough as renames. This option takes a percentage
58 those similar enough as renames. This option takes a percentage
58 between 0 (disabled) and 100 (files must be identical) as its
59 between 0 (disabled) and 100 (files must be identical) as its
59 parameter. Detecting renamed files this way can be expensive.
60 parameter. Detecting renamed files this way can be expensive.
60 """
61 """
61 try:
62 try:
62 sim = float(opts.get('similarity') or 0)
63 sim = float(opts.get('similarity') or 0)
63 except ValueError:
64 except ValueError:
64 raise util.Abort(_('similarity must be a number'))
65 raise util.Abort(_('similarity must be a number'))
65 if sim < 0 or sim > 100:
66 if sim < 0 or sim > 100:
66 raise util.Abort(_('similarity must be between 0 and 100'))
67 raise util.Abort(_('similarity must be between 0 and 100'))
67 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
68 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
68
69
69 def annotate(ui, repo, *pats, **opts):
70 def annotate(ui, repo, *pats, **opts):
70 """show changeset information per file line
71 """show changeset information per file line
71
72
72 List changes in files, showing the revision id responsible for each line
73 List changes in files, showing the revision id responsible for each line
73
74
74 This command is useful to discover who did a change or when a change took
75 This command is useful to discover who did a change or when a change took
75 place.
76 place.
76
77
77 Without the -a option, annotate will avoid processing files it
78 Without the -a option, annotate will avoid processing files it
78 detects as binary. With -a, annotate will generate an annotation
79 detects as binary. With -a, annotate will generate an annotation
79 anyway, probably with undesirable results.
80 anyway, probably with undesirable results.
80 """
81 """
81 datefunc = ui.quiet and util.shortdate or util.datestr
82 datefunc = ui.quiet and util.shortdate or util.datestr
82 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
83 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
83
84
84 if not pats:
85 if not pats:
85 raise util.Abort(_('at least one file name or pattern required'))
86 raise util.Abort(_('at least one file name or pattern required'))
86
87
87 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
88 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
88 ('number', lambda x: str(x[0].rev())),
89 ('number', lambda x: str(x[0].rev())),
89 ('changeset', lambda x: short(x[0].node())),
90 ('changeset', lambda x: short(x[0].node())),
90 ('date', getdate),
91 ('date', getdate),
91 ('follow', lambda x: x[0].path()),
92 ('follow', lambda x: x[0].path()),
92 ]
93 ]
93
94
94 if (not opts['user'] and not opts['changeset'] and not opts['date']
95 if (not opts['user'] and not opts['changeset'] and not opts['date']
95 and not opts['follow']):
96 and not opts['follow']):
96 opts['number'] = 1
97 opts['number'] = 1
97
98
98 linenumber = opts.get('line_number') is not None
99 linenumber = opts.get('line_number') is not None
99 if (linenumber and (not opts['changeset']) and (not opts['number'])):
100 if (linenumber and (not opts['changeset']) and (not opts['number'])):
100 raise util.Abort(_('at least one of -n/-c is required for -l'))
101 raise util.Abort(_('at least one of -n/-c is required for -l'))
101
102
102 funcmap = [func for op, func in opmap if opts.get(op)]
103 funcmap = [func for op, func in opmap if opts.get(op)]
103 if linenumber:
104 if linenumber:
104 lastfunc = funcmap[-1]
105 lastfunc = funcmap[-1]
105 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
106 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
106
107
107 ctx = repo.changectx(opts['rev'])
108 ctx = repo.changectx(opts['rev'])
108
109
109 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
110 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
110 node=ctx.node()):
111 node=ctx.node()):
111 fctx = ctx.filectx(abs)
112 fctx = ctx.filectx(abs)
112 if not opts['text'] and util.binary(fctx.data()):
113 if not opts['text'] and util.binary(fctx.data()):
113 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
114 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
114 continue
115 continue
115
116
116 lines = fctx.annotate(follow=opts.get('follow'),
117 lines = fctx.annotate(follow=opts.get('follow'),
117 linenumber=linenumber)
118 linenumber=linenumber)
118 pieces = []
119 pieces = []
119
120
120 for f in funcmap:
121 for f in funcmap:
121 l = [f(n) for n, dummy in lines]
122 l = [f(n) for n, dummy in lines]
122 if l:
123 if l:
123 m = max(map(len, l))
124 m = max(map(len, l))
124 pieces.append(["%*s" % (m, x) for x in l])
125 pieces.append(["%*s" % (m, x) for x in l])
125
126
126 if pieces:
127 if pieces:
127 for p, l in zip(zip(*pieces), lines):
128 for p, l in zip(zip(*pieces), lines):
128 ui.write("%s: %s" % (" ".join(p), l[1]))
129 ui.write("%s: %s" % (" ".join(p), l[1]))
129
130
130 def archive(ui, repo, dest, **opts):
131 def archive(ui, repo, dest, **opts):
131 '''create unversioned archive of a repository revision
132 '''create unversioned archive of a repository revision
132
133
133 By default, the revision used is the parent of the working
134 By default, the revision used is the parent of the working
134 directory; use "-r" to specify a different revision.
135 directory; use "-r" to specify a different revision.
135
136
136 To specify the type of archive to create, use "-t". Valid
137 To specify the type of archive to create, use "-t". Valid
137 types are:
138 types are:
138
139
139 "files" (default): a directory full of files
140 "files" (default): a directory full of files
140 "tar": tar archive, uncompressed
141 "tar": tar archive, uncompressed
141 "tbz2": tar archive, compressed using bzip2
142 "tbz2": tar archive, compressed using bzip2
142 "tgz": tar archive, compressed using gzip
143 "tgz": tar archive, compressed using gzip
143 "uzip": zip archive, uncompressed
144 "uzip": zip archive, uncompressed
144 "zip": zip archive, compressed using deflate
145 "zip": zip archive, compressed using deflate
145
146
146 The exact name of the destination archive or directory is given
147 The exact name of the destination archive or directory is given
147 using a format string; see "hg help export" for details.
148 using a format string; see "hg help export" for details.
148
149
149 Each member added to an archive file has a directory prefix
150 Each member added to an archive file has a directory prefix
150 prepended. Use "-p" to specify a format string for the prefix.
151 prepended. Use "-p" to specify a format string for the prefix.
151 The default is the basename of the archive, with suffixes removed.
152 The default is the basename of the archive, with suffixes removed.
152 '''
153 '''
153
154
154 ctx = repo.changectx(opts['rev'])
155 ctx = repo.changectx(opts['rev'])
155 if not ctx:
156 if not ctx:
156 raise util.Abort(_('repository has no revisions'))
157 raise util.Abort(_('repository has no revisions'))
157 node = ctx.node()
158 node = ctx.node()
158 dest = cmdutil.make_filename(repo, dest, node)
159 dest = cmdutil.make_filename(repo, dest, node)
159 if os.path.realpath(dest) == repo.root:
160 if os.path.realpath(dest) == repo.root:
160 raise util.Abort(_('repository root cannot be destination'))
161 raise util.Abort(_('repository root cannot be destination'))
161 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
162 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
162 kind = opts.get('type') or 'files'
163 kind = opts.get('type') or 'files'
163 prefix = opts['prefix']
164 prefix = opts['prefix']
164 if dest == '-':
165 if dest == '-':
165 if kind == 'files':
166 if kind == 'files':
166 raise util.Abort(_('cannot archive plain files to stdout'))
167 raise util.Abort(_('cannot archive plain files to stdout'))
167 dest = sys.stdout
168 dest = sys.stdout
168 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
169 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
169 prefix = cmdutil.make_filename(repo, prefix, node)
170 prefix = cmdutil.make_filename(repo, prefix, node)
170 archival.archive(repo, dest, node, kind, not opts['no_decode'],
171 archival.archive(repo, dest, node, kind, not opts['no_decode'],
171 matchfn, prefix)
172 matchfn, prefix)
172
173
173 def backout(ui, repo, node=None, rev=None, **opts):
174 def backout(ui, repo, node=None, rev=None, **opts):
174 '''reverse effect of earlier changeset
175 '''reverse effect of earlier changeset
175
176
176 Commit the backed out changes as a new changeset. The new
177 Commit the backed out changes as a new changeset. The new
177 changeset is a child of the backed out changeset.
178 changeset is a child of the backed out changeset.
178
179
179 If you back out a changeset other than the tip, a new head is
180 If you back out a changeset other than the tip, a new head is
180 created. This head will be the new tip and you should merge this
181 created. This head will be the new tip and you should merge this
181 backout changeset with another head (current one by default).
182 backout changeset with another head (current one by default).
182
183
183 The --merge option remembers the parent of the working directory
184 The --merge option remembers the parent of the working directory
184 before starting the backout, then merges the new head with that
185 before starting the backout, then merges the new head with that
185 changeset afterwards. This saves you from doing the merge by
186 changeset afterwards. This saves you from doing the merge by
186 hand. The result of this merge is not committed, as for a normal
187 hand. The result of this merge is not committed, as for a normal
187 merge.
188 merge.
188
189
189 See 'hg help dates' for a list of formats valid for -d/--date.
190 See 'hg help dates' for a list of formats valid for -d/--date.
190 '''
191 '''
191 if rev and node:
192 if rev and node:
192 raise util.Abort(_("please specify just one revision"))
193 raise util.Abort(_("please specify just one revision"))
193
194
194 if not rev:
195 if not rev:
195 rev = node
196 rev = node
196
197
197 if not rev:
198 if not rev:
198 raise util.Abort(_("please specify a revision to backout"))
199 raise util.Abort(_("please specify a revision to backout"))
199
200
200 date = opts.get('date')
201 date = opts.get('date')
201 if date:
202 if date:
202 opts['date'] = util.parsedate(date)
203 opts['date'] = util.parsedate(date)
203
204
204 cmdutil.bail_if_changed(repo)
205 cmdutil.bail_if_changed(repo)
205 node = repo.lookup(rev)
206 node = repo.lookup(rev)
206
207
207 op1, op2 = repo.dirstate.parents()
208 op1, op2 = repo.dirstate.parents()
208 a = repo.changelog.ancestor(op1, node)
209 a = repo.changelog.ancestor(op1, node)
209 if a != node:
210 if a != node:
210 raise util.Abort(_('cannot back out change on a different branch'))
211 raise util.Abort(_('cannot back out change on a different branch'))
211
212
212 p1, p2 = repo.changelog.parents(node)
213 p1, p2 = repo.changelog.parents(node)
213 if p1 == nullid:
214 if p1 == nullid:
214 raise util.Abort(_('cannot back out a change with no parents'))
215 raise util.Abort(_('cannot back out a change with no parents'))
215 if p2 != nullid:
216 if p2 != nullid:
216 if not opts['parent']:
217 if not opts['parent']:
217 raise util.Abort(_('cannot back out a merge changeset without '
218 raise util.Abort(_('cannot back out a merge changeset without '
218 '--parent'))
219 '--parent'))
219 p = repo.lookup(opts['parent'])
220 p = repo.lookup(opts['parent'])
220 if p not in (p1, p2):
221 if p not in (p1, p2):
221 raise util.Abort(_('%s is not a parent of %s') %
222 raise util.Abort(_('%s is not a parent of %s') %
222 (short(p), short(node)))
223 (short(p), short(node)))
223 parent = p
224 parent = p
224 else:
225 else:
225 if opts['parent']:
226 if opts['parent']:
226 raise util.Abort(_('cannot use --parent on non-merge changeset'))
227 raise util.Abort(_('cannot use --parent on non-merge changeset'))
227 parent = p1
228 parent = p1
228
229
229 hg.clean(repo, node, show_stats=False)
230 hg.clean(repo, node, show_stats=False)
230 revert_opts = opts.copy()
231 revert_opts = opts.copy()
231 revert_opts['date'] = None
232 revert_opts['date'] = None
232 revert_opts['all'] = True
233 revert_opts['all'] = True
233 revert_opts['rev'] = hex(parent)
234 revert_opts['rev'] = hex(parent)
234 revert_opts['no_backup'] = None
235 revert_opts['no_backup'] = None
235 revert(ui, repo, **revert_opts)
236 revert(ui, repo, **revert_opts)
236 commit_opts = opts.copy()
237 commit_opts = opts.copy()
237 commit_opts['addremove'] = False
238 commit_opts['addremove'] = False
238 if not commit_opts['message'] and not commit_opts['logfile']:
239 if not commit_opts['message'] and not commit_opts['logfile']:
239 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
240 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
240 commit_opts['force_editor'] = True
241 commit_opts['force_editor'] = True
241 commit(ui, repo, **commit_opts)
242 commit(ui, repo, **commit_opts)
242 def nice(node):
243 def nice(node):
243 return '%d:%s' % (repo.changelog.rev(node), short(node))
244 return '%d:%s' % (repo.changelog.rev(node), short(node))
244 ui.status(_('changeset %s backs out changeset %s\n') %
245 ui.status(_('changeset %s backs out changeset %s\n') %
245 (nice(repo.changelog.tip()), nice(node)))
246 (nice(repo.changelog.tip()), nice(node)))
246 if op1 != node:
247 if op1 != node:
247 hg.clean(repo, op1, show_stats=False)
248 hg.clean(repo, op1, show_stats=False)
248 if opts['merge']:
249 if opts['merge']:
249 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
250 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
250 hg.merge(repo, hex(repo.changelog.tip()))
251 hg.merge(repo, hex(repo.changelog.tip()))
251 else:
252 else:
252 ui.status(_('the backout changeset is a new head - '
253 ui.status(_('the backout changeset is a new head - '
253 'do not forget to merge\n'))
254 'do not forget to merge\n'))
254 ui.status(_('(use "backout --merge" '
255 ui.status(_('(use "backout --merge" '
255 'if you want to auto-merge)\n'))
256 'if you want to auto-merge)\n'))
256
257
257 def bisect(ui, repo, rev=None, extra=None,
258 def bisect(ui, repo, rev=None, extra=None,
258 reset=None, good=None, bad=None, skip=None, noupdate=None):
259 reset=None, good=None, bad=None, skip=None, noupdate=None):
259 """subdivision search of changesets
260 """subdivision search of changesets
260
261
261 This command helps to find changesets which introduce problems.
262 This command helps to find changesets which introduce problems.
262 To use, mark the earliest changeset you know exhibits the problem
263 To use, mark the earliest changeset you know exhibits the problem
263 as bad, then mark the latest changeset which is free from the
264 as bad, then mark the latest changeset which is free from the
264 problem as good. Bisect will update your working directory to a
265 problem as good. Bisect will update your working directory to a
265 revision for testing. Once you have performed tests, mark the
266 revision for testing. Once you have performed tests, mark the
266 working directory as bad or good and bisect will either update to
267 working directory as bad or good and bisect will either update to
267 another candidate changeset or announce that it has found the bad
268 another candidate changeset or announce that it has found the bad
268 revision.
269 revision.
269 """
270 """
270 # backward compatibility
271 # backward compatibility
271 if rev in "good bad reset init".split():
272 if rev in "good bad reset init".split():
272 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
273 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
273 cmd, rev, extra = rev, extra, None
274 cmd, rev, extra = rev, extra, None
274 if cmd == "good":
275 if cmd == "good":
275 good = True
276 good = True
276 elif cmd == "bad":
277 elif cmd == "bad":
277 bad = True
278 bad = True
278 else:
279 else:
279 reset = True
280 reset = True
280 elif extra or good + bad + skip + reset > 1:
281 elif extra or good + bad + skip + reset > 1:
281 raise util.Abort("Incompatible arguments")
282 raise util.Abort("Incompatible arguments")
282
283
283 if reset:
284 if reset:
284 p = repo.join("bisect.state")
285 p = repo.join("bisect.state")
285 if os.path.exists(p):
286 if os.path.exists(p):
286 os.unlink(p)
287 os.unlink(p)
287 return
288 return
288
289
289 # load state
290 # load state
290 state = {'good': [], 'bad': [], 'skip': []}
291 state = {'good': [], 'bad': [], 'skip': []}
291 if os.path.exists(repo.join("bisect.state")):
292 if os.path.exists(repo.join("bisect.state")):
292 for l in repo.opener("bisect.state"):
293 for l in repo.opener("bisect.state"):
293 kind, node = l[:-1].split()
294 kind, node = l[:-1].split()
294 node = repo.lookup(node)
295 node = repo.lookup(node)
295 if kind not in state:
296 if kind not in state:
296 raise util.Abort(_("unknown bisect kind %s") % kind)
297 raise util.Abort(_("unknown bisect kind %s") % kind)
297 state[kind].append(node)
298 state[kind].append(node)
298
299
299 # update state
300 # update state
300 node = repo.lookup(rev or '.')
301 node = repo.lookup(rev or '.')
301 if good:
302 if good:
302 state['good'].append(node)
303 state['good'].append(node)
303 elif bad:
304 elif bad:
304 state['bad'].append(node)
305 state['bad'].append(node)
305 elif skip:
306 elif skip:
306 state['skip'].append(node)
307 state['skip'].append(node)
307
308
308 # save state
309 # save state
309 f = repo.opener("bisect.state", "w", atomictemp=True)
310 f = repo.opener("bisect.state", "w", atomictemp=True)
310 wlock = repo.wlock()
311 wlock = repo.wlock()
311 try:
312 try:
312 for kind in state:
313 for kind in state:
313 for node in state[kind]:
314 for node in state[kind]:
314 f.write("%s %s\n" % (kind, hg.hex(node)))
315 f.write("%s %s\n" % (kind, hex(node)))
315 f.rename()
316 f.rename()
316 finally:
317 finally:
317 del wlock
318 del wlock
318
319
319 if not state['good'] or not state['bad']:
320 if not state['good'] or not state['bad']:
320 return
321 return
321
322
322 # actually bisect
323 # actually bisect
323 node, changesets, good = hbisect.bisect(repo.changelog, state)
324 node, changesets, good = hbisect.bisect(repo.changelog, state)
324 if changesets == 0:
325 if changesets == 0:
325 ui.write(_("The first %s revision is:\n") % (good and "good" or "bad"))
326 ui.write(_("The first %s revision is:\n") % (good and "good" or "bad"))
326 displayer = cmdutil.show_changeset(ui, repo, {})
327 displayer = cmdutil.show_changeset(ui, repo, {})
327 displayer.show(changenode=node)
328 displayer.show(changenode=node)
328 elif node is not None:
329 elif node is not None:
329 # compute the approximate number of remaining tests
330 # compute the approximate number of remaining tests
330 tests, size = 0, 2
331 tests, size = 0, 2
331 while size <= changesets:
332 while size <= changesets:
332 tests, size = tests + 1, size * 2
333 tests, size = tests + 1, size * 2
333 rev = repo.changelog.rev(node)
334 rev = repo.changelog.rev(node)
334 ui.write(_("Testing changeset %s:%s "
335 ui.write(_("Testing changeset %s:%s "
335 "(%s changesets remaining, ~%s tests)\n")
336 "(%s changesets remaining, ~%s tests)\n")
336 % (rev, hg.short(node), changesets, tests))
337 % (rev, short(node), changesets, tests))
337 if not noupdate:
338 if not noupdate:
338 cmdutil.bail_if_changed(repo)
339 cmdutil.bail_if_changed(repo)
339 return hg.clean(repo, node)
340 return hg.clean(repo, node)
340
341
341 def branch(ui, repo, label=None, **opts):
342 def branch(ui, repo, label=None, **opts):
342 """set or show the current branch name
343 """set or show the current branch name
343
344
344 With no argument, show the current branch name. With one argument,
345 With no argument, show the current branch name. With one argument,
345 set the working directory branch name (the branch does not exist in
346 set the working directory branch name (the branch does not exist in
346 the repository until the next commit).
347 the repository until the next commit).
347
348
348 Unless --force is specified, branch will not let you set a
349 Unless --force is specified, branch will not let you set a
349 branch name that shadows an existing branch.
350 branch name that shadows an existing branch.
350
351
351 Use the command 'hg update' to switch to an existing branch.
352 Use the command 'hg update' to switch to an existing branch.
352 """
353 """
353
354
354 if label:
355 if label:
355 if not opts.get('force') and label in repo.branchtags():
356 if not opts.get('force') and label in repo.branchtags():
356 if label not in [p.branch() for p in repo.workingctx().parents()]:
357 if label not in [p.branch() for p in repo.workingctx().parents()]:
357 raise util.Abort(_('a branch of the same name already exists'
358 raise util.Abort(_('a branch of the same name already exists'
358 ' (use --force to override)'))
359 ' (use --force to override)'))
359 repo.dirstate.setbranch(util.fromlocal(label))
360 repo.dirstate.setbranch(util.fromlocal(label))
360 ui.status(_('marked working directory as branch %s\n') % label)
361 ui.status(_('marked working directory as branch %s\n') % label)
361 else:
362 else:
362 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
363 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
363
364
364 def branches(ui, repo, active=False):
365 def branches(ui, repo, active=False):
365 """list repository named branches
366 """list repository named branches
366
367
367 List the repository's named branches, indicating which ones are
368 List the repository's named branches, indicating which ones are
368 inactive. If active is specified, only show active branches.
369 inactive. If active is specified, only show active branches.
369
370
370 A branch is considered active if it contains unmerged heads.
371 A branch is considered active if it contains unmerged heads.
371
372
372 Use the command 'hg update' to switch to an existing branch.
373 Use the command 'hg update' to switch to an existing branch.
373 """
374 """
374 b = repo.branchtags()
375 b = repo.branchtags()
375 heads = dict.fromkeys(repo.heads(), 1)
376 heads = dict.fromkeys(repo.heads(), 1)
376 l = [((n in heads), repo.changelog.rev(n), n, t) for t, n in b.items()]
377 l = [((n in heads), repo.changelog.rev(n), n, t) for t, n in b.items()]
377 l.sort()
378 l.sort()
378 l.reverse()
379 l.reverse()
379 for ishead, r, n, t in l:
380 for ishead, r, n, t in l:
380 if active and not ishead:
381 if active and not ishead:
381 # If we're only displaying active branches, abort the loop on
382 # If we're only displaying active branches, abort the loop on
382 # encountering the first inactive head
383 # encountering the first inactive head
383 break
384 break
384 else:
385 else:
385 hexfunc = ui.debugflag and hex or short
386 hexfunc = ui.debugflag and hex or short
386 if ui.quiet:
387 if ui.quiet:
387 ui.write("%s\n" % t)
388 ui.write("%s\n" % t)
388 else:
389 else:
389 spaces = " " * (30 - util.locallen(t))
390 spaces = " " * (30 - util.locallen(t))
390 # The code only gets here if inactive branches are being
391 # The code only gets here if inactive branches are being
391 # displayed or the branch is active.
392 # displayed or the branch is active.
392 isinactive = ((not ishead) and " (inactive)") or ''
393 isinactive = ((not ishead) and " (inactive)") or ''
393 ui.write("%s%s %s:%s%s\n" % (t, spaces, r, hexfunc(n), isinactive))
394 ui.write("%s%s %s:%s%s\n" % (t, spaces, r, hexfunc(n), isinactive))
394
395
395 def bundle(ui, repo, fname, dest=None, **opts):
396 def bundle(ui, repo, fname, dest=None, **opts):
396 """create a changegroup file
397 """create a changegroup file
397
398
398 Generate a compressed changegroup file collecting changesets not
399 Generate a compressed changegroup file collecting changesets not
399 found in the other repository.
400 found in the other repository.
400
401
401 If no destination repository is specified the destination is
402 If no destination repository is specified the destination is
402 assumed to have all the nodes specified by one or more --base
403 assumed to have all the nodes specified by one or more --base
403 parameters. To create a bundle containing all changesets, use
404 parameters. To create a bundle containing all changesets, use
404 --all (or --base null).
405 --all (or --base null).
405
406
406 The bundle file can then be transferred using conventional means and
407 The bundle file can then be transferred using conventional means and
407 applied to another repository with the unbundle or pull command.
408 applied to another repository with the unbundle or pull command.
408 This is useful when direct push and pull are not available or when
409 This is useful when direct push and pull are not available or when
409 exporting an entire repository is undesirable.
410 exporting an entire repository is undesirable.
410
411
411 Applying bundles preserves all changeset contents including
412 Applying bundles preserves all changeset contents including
412 permissions, copy/rename information, and revision history.
413 permissions, copy/rename information, and revision history.
413 """
414 """
414 revs = opts.get('rev') or None
415 revs = opts.get('rev') or None
415 if revs:
416 if revs:
416 revs = [repo.lookup(rev) for rev in revs]
417 revs = [repo.lookup(rev) for rev in revs]
417 if opts.get('all'):
418 if opts.get('all'):
418 base = ['null']
419 base = ['null']
419 else:
420 else:
420 base = opts.get('base')
421 base = opts.get('base')
421 if base:
422 if base:
422 if dest:
423 if dest:
423 raise util.Abort(_("--base is incompatible with specifiying "
424 raise util.Abort(_("--base is incompatible with specifiying "
424 "a destination"))
425 "a destination"))
425 base = [repo.lookup(rev) for rev in base]
426 base = [repo.lookup(rev) for rev in base]
426 # create the right base
427 # create the right base
427 # XXX: nodesbetween / changegroup* should be "fixed" instead
428 # XXX: nodesbetween / changegroup* should be "fixed" instead
428 o = []
429 o = []
429 has = {nullid: None}
430 has = {nullid: None}
430 for n in base:
431 for n in base:
431 has.update(repo.changelog.reachable(n))
432 has.update(repo.changelog.reachable(n))
432 if revs:
433 if revs:
433 visit = list(revs)
434 visit = list(revs)
434 else:
435 else:
435 visit = repo.changelog.heads()
436 visit = repo.changelog.heads()
436 seen = {}
437 seen = {}
437 while visit:
438 while visit:
438 n = visit.pop(0)
439 n = visit.pop(0)
439 parents = [p for p in repo.changelog.parents(n) if p not in has]
440 parents = [p for p in repo.changelog.parents(n) if p not in has]
440 if len(parents) == 0:
441 if len(parents) == 0:
441 o.insert(0, n)
442 o.insert(0, n)
442 else:
443 else:
443 for p in parents:
444 for p in parents:
444 if p not in seen:
445 if p not in seen:
445 seen[p] = 1
446 seen[p] = 1
446 visit.append(p)
447 visit.append(p)
447 else:
448 else:
448 cmdutil.setremoteconfig(ui, opts)
449 cmdutil.setremoteconfig(ui, opts)
449 dest, revs, checkout = hg.parseurl(
450 dest, revs, checkout = hg.parseurl(
450 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
451 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
451 other = hg.repository(ui, dest)
452 other = hg.repository(ui, dest)
452 o = repo.findoutgoing(other, force=opts['force'])
453 o = repo.findoutgoing(other, force=opts['force'])
453
454
454 if revs:
455 if revs:
455 cg = repo.changegroupsubset(o, revs, 'bundle')
456 cg = repo.changegroupsubset(o, revs, 'bundle')
456 else:
457 else:
457 cg = repo.changegroup(o, 'bundle')
458 cg = repo.changegroup(o, 'bundle')
458 changegroup.writebundle(cg, fname, "HG10BZ")
459 changegroup.writebundle(cg, fname, "HG10BZ")
459
460
460 def cat(ui, repo, file1, *pats, **opts):
461 def cat(ui, repo, file1, *pats, **opts):
461 """output the current or given revision of files
462 """output the current or given revision of files
462
463
463 Print the specified files as they were at the given revision.
464 Print the specified files as they were at the given revision.
464 If no revision is given, the parent of the working directory is used,
465 If no revision is given, the parent of the working directory is used,
465 or tip if no revision is checked out.
466 or tip if no revision is checked out.
466
467
467 Output may be to a file, in which case the name of the file is
468 Output may be to a file, in which case the name of the file is
468 given using a format string. The formatting rules are the same as
469 given using a format string. The formatting rules are the same as
469 for the export command, with the following additions:
470 for the export command, with the following additions:
470
471
471 %s basename of file being printed
472 %s basename of file being printed
472 %d dirname of file being printed, or '.' if in repo root
473 %d dirname of file being printed, or '.' if in repo root
473 %p root-relative path name of file being printed
474 %p root-relative path name of file being printed
474 """
475 """
475 ctx = repo.changectx(opts['rev'])
476 ctx = repo.changectx(opts['rev'])
476 err = 1
477 err = 1
477 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
478 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
478 ctx.node()):
479 ctx.node()):
479 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
480 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
480 data = ctx.filectx(abs).data()
481 data = ctx.filectx(abs).data()
481 if opts.get('decode'):
482 if opts.get('decode'):
482 data = repo.wwritedata(abs, data)
483 data = repo.wwritedata(abs, data)
483 fp.write(data)
484 fp.write(data)
484 err = 0
485 err = 0
485 return err
486 return err
486
487
487 def clone(ui, source, dest=None, **opts):
488 def clone(ui, source, dest=None, **opts):
488 """make a copy of an existing repository
489 """make a copy of an existing repository
489
490
490 Create a copy of an existing repository in a new directory.
491 Create a copy of an existing repository in a new directory.
491
492
492 If no destination directory name is specified, it defaults to the
493 If no destination directory name is specified, it defaults to the
493 basename of the source.
494 basename of the source.
494
495
495 The location of the source is added to the new repository's
496 The location of the source is added to the new repository's
496 .hg/hgrc file, as the default to be used for future pulls.
497 .hg/hgrc file, as the default to be used for future pulls.
497
498
498 For efficiency, hardlinks are used for cloning whenever the source
499 For efficiency, hardlinks are used for cloning whenever the source
499 and destination are on the same filesystem (note this applies only
500 and destination are on the same filesystem (note this applies only
500 to the repository data, not to the checked out files). Some
501 to the repository data, not to the checked out files). Some
501 filesystems, such as AFS, implement hardlinking incorrectly, but
502 filesystems, such as AFS, implement hardlinking incorrectly, but
502 do not report errors. In these cases, use the --pull option to
503 do not report errors. In these cases, use the --pull option to
503 avoid hardlinking.
504 avoid hardlinking.
504
505
505 You can safely clone repositories and checked out files using full
506 You can safely clone repositories and checked out files using full
506 hardlinks with
507 hardlinks with
507
508
508 $ cp -al REPO REPOCLONE
509 $ cp -al REPO REPOCLONE
509
510
510 which is the fastest way to clone. However, the operation is not
511 which is the fastest way to clone. However, the operation is not
511 atomic (making sure REPO is not modified during the operation is
512 atomic (making sure REPO is not modified during the operation is
512 up to you) and you have to make sure your editor breaks hardlinks
513 up to you) and you have to make sure your editor breaks hardlinks
513 (Emacs and most Linux Kernel tools do so).
514 (Emacs and most Linux Kernel tools do so).
514
515
515 If you use the -r option to clone up to a specific revision, no
516 If you use the -r option to clone up to a specific revision, no
516 subsequent revisions will be present in the cloned repository.
517 subsequent revisions will be present in the cloned repository.
517 This option implies --pull, even on local repositories.
518 This option implies --pull, even on local repositories.
518
519
519 See pull for valid source format details.
520 See pull for valid source format details.
520
521
521 It is possible to specify an ssh:// URL as the destination, but no
522 It is possible to specify an ssh:// URL as the destination, but no
522 .hg/hgrc and working directory will be created on the remote side.
523 .hg/hgrc and working directory will be created on the remote side.
523 Look at the help text for the pull command for important details
524 Look at the help text for the pull command for important details
524 about ssh:// URLs.
525 about ssh:// URLs.
525 """
526 """
526 cmdutil.setremoteconfig(ui, opts)
527 cmdutil.setremoteconfig(ui, opts)
527 hg.clone(ui, source, dest,
528 hg.clone(ui, source, dest,
528 pull=opts['pull'],
529 pull=opts['pull'],
529 stream=opts['uncompressed'],
530 stream=opts['uncompressed'],
530 rev=opts['rev'],
531 rev=opts['rev'],
531 update=not opts['noupdate'])
532 update=not opts['noupdate'])
532
533
533 def commit(ui, repo, *pats, **opts):
534 def commit(ui, repo, *pats, **opts):
534 """commit the specified files or all outstanding changes
535 """commit the specified files or all outstanding changes
535
536
536 Commit changes to the given files into the repository.
537 Commit changes to the given files into the repository.
537
538
538 If a list of files is omitted, all changes reported by "hg status"
539 If a list of files is omitted, all changes reported by "hg status"
539 will be committed.
540 will be committed.
540
541
541 If no commit message is specified, the configured editor is started to
542 If no commit message is specified, the configured editor is started to
542 enter a message.
543 enter a message.
543
544
544 See 'hg help dates' for a list of formats valid for -d/--date.
545 See 'hg help dates' for a list of formats valid for -d/--date.
545 """
546 """
546 def commitfunc(ui, repo, files, message, match, opts):
547 def commitfunc(ui, repo, files, message, match, opts):
547 return repo.commit(files, message, opts['user'], opts['date'], match,
548 return repo.commit(files, message, opts['user'], opts['date'], match,
548 force_editor=opts.get('force_editor'))
549 force_editor=opts.get('force_editor'))
549 cmdutil.commit(ui, repo, commitfunc, pats, opts)
550 cmdutil.commit(ui, repo, commitfunc, pats, opts)
550
551
551 def copy(ui, repo, *pats, **opts):
552 def copy(ui, repo, *pats, **opts):
552 """mark files as copied for the next commit
553 """mark files as copied for the next commit
553
554
554 Mark dest as having copies of source files. If dest is a
555 Mark dest as having copies of source files. If dest is a
555 directory, copies are put in that directory. If dest is a file,
556 directory, copies are put in that directory. If dest is a file,
556 there can only be one source.
557 there can only be one source.
557
558
558 By default, this command copies the contents of files as they
559 By default, this command copies the contents of files as they
559 stand in the working directory. If invoked with --after, the
560 stand in the working directory. If invoked with --after, the
560 operation is recorded, but no copying is performed.
561 operation is recorded, but no copying is performed.
561
562
562 This command takes effect in the next commit. To undo a copy
563 This command takes effect in the next commit. To undo a copy
563 before that, see hg revert.
564 before that, see hg revert.
564 """
565 """
565 wlock = repo.wlock(False)
566 wlock = repo.wlock(False)
566 try:
567 try:
567 return cmdutil.copy(ui, repo, pats, opts)
568 return cmdutil.copy(ui, repo, pats, opts)
568 finally:
569 finally:
569 del wlock
570 del wlock
570
571
571 def debugancestor(ui, repo, *args):
572 def debugancestor(ui, repo, *args):
572 """find the ancestor revision of two revisions in a given index"""
573 """find the ancestor revision of two revisions in a given index"""
573 if len(args) == 3:
574 if len(args) == 3:
574 index, rev1, rev2 = args
575 index, rev1, rev2 = args
575 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
576 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
576 elif len(args) == 2:
577 elif len(args) == 2:
577 if not repo:
578 if not repo:
578 raise util.Abort(_("There is no Mercurial repository here "
579 raise util.Abort(_("There is no Mercurial repository here "
579 "(.hg not found)"))
580 "(.hg not found)"))
580 rev1, rev2 = args
581 rev1, rev2 = args
581 r = repo.changelog
582 r = repo.changelog
582 else:
583 else:
583 raise util.Abort(_('either two or three arguments required'))
584 raise util.Abort(_('either two or three arguments required'))
584 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
585 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
585 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
586 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
586
587
587 def debugcomplete(ui, cmd='', **opts):
588 def debugcomplete(ui, cmd='', **opts):
588 """returns the completion list associated with the given command"""
589 """returns the completion list associated with the given command"""
589
590
590 if opts['options']:
591 if opts['options']:
591 options = []
592 options = []
592 otables = [globalopts]
593 otables = [globalopts]
593 if cmd:
594 if cmd:
594 aliases, entry = cmdutil.findcmd(ui, cmd, table)
595 aliases, entry = cmdutil.findcmd(ui, cmd, table)
595 otables.append(entry[1])
596 otables.append(entry[1])
596 for t in otables:
597 for t in otables:
597 for o in t:
598 for o in t:
598 if o[0]:
599 if o[0]:
599 options.append('-%s' % o[0])
600 options.append('-%s' % o[0])
600 options.append('--%s' % o[1])
601 options.append('--%s' % o[1])
601 ui.write("%s\n" % "\n".join(options))
602 ui.write("%s\n" % "\n".join(options))
602 return
603 return
603
604
604 clist = cmdutil.findpossible(ui, cmd, table).keys()
605 clist = cmdutil.findpossible(ui, cmd, table).keys()
605 clist.sort()
606 clist.sort()
606 ui.write("%s\n" % "\n".join(clist))
607 ui.write("%s\n" % "\n".join(clist))
607
608
608 def debugfsinfo(ui, path = "."):
609 def debugfsinfo(ui, path = "."):
609 file('.debugfsinfo', 'w').write('')
610 file('.debugfsinfo', 'w').write('')
610 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
611 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
611 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
612 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
612 ui.write('case-sensitive: %s\n' % (util.checkfolding('.debugfsinfo')
613 ui.write('case-sensitive: %s\n' % (util.checkfolding('.debugfsinfo')
613 and 'yes' or 'no'))
614 and 'yes' or 'no'))
614 os.unlink('.debugfsinfo')
615 os.unlink('.debugfsinfo')
615
616
616 def debugrebuildstate(ui, repo, rev=""):
617 def debugrebuildstate(ui, repo, rev=""):
617 """rebuild the dirstate as it would look like for the given revision"""
618 """rebuild the dirstate as it would look like for the given revision"""
618 if rev == "":
619 if rev == "":
619 rev = repo.changelog.tip()
620 rev = repo.changelog.tip()
620 ctx = repo.changectx(rev)
621 ctx = repo.changectx(rev)
621 files = ctx.manifest()
622 files = ctx.manifest()
622 wlock = repo.wlock()
623 wlock = repo.wlock()
623 try:
624 try:
624 repo.dirstate.rebuild(rev, files)
625 repo.dirstate.rebuild(rev, files)
625 finally:
626 finally:
626 del wlock
627 del wlock
627
628
628 def debugcheckstate(ui, repo):
629 def debugcheckstate(ui, repo):
629 """validate the correctness of the current dirstate"""
630 """validate the correctness of the current dirstate"""
630 parent1, parent2 = repo.dirstate.parents()
631 parent1, parent2 = repo.dirstate.parents()
631 m1 = repo.changectx(parent1).manifest()
632 m1 = repo.changectx(parent1).manifest()
632 m2 = repo.changectx(parent2).manifest()
633 m2 = repo.changectx(parent2).manifest()
633 errors = 0
634 errors = 0
634 for f in repo.dirstate:
635 for f in repo.dirstate:
635 state = repo.dirstate[f]
636 state = repo.dirstate[f]
636 if state in "nr" and f not in m1:
637 if state in "nr" and f not in m1:
637 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
638 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
638 errors += 1
639 errors += 1
639 if state in "a" and f in m1:
640 if state in "a" and f in m1:
640 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
641 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
641 errors += 1
642 errors += 1
642 if state in "m" and f not in m1 and f not in m2:
643 if state in "m" and f not in m1 and f not in m2:
643 ui.warn(_("%s in state %s, but not in either manifest\n") %
644 ui.warn(_("%s in state %s, but not in either manifest\n") %
644 (f, state))
645 (f, state))
645 errors += 1
646 errors += 1
646 for f in m1:
647 for f in m1:
647 state = repo.dirstate[f]
648 state = repo.dirstate[f]
648 if state not in "nrm":
649 if state not in "nrm":
649 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
650 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
650 errors += 1
651 errors += 1
651 if errors:
652 if errors:
652 error = _(".hg/dirstate inconsistent with current parent's manifest")
653 error = _(".hg/dirstate inconsistent with current parent's manifest")
653 raise util.Abort(error)
654 raise util.Abort(error)
654
655
655 def showconfig(ui, repo, *values, **opts):
656 def showconfig(ui, repo, *values, **opts):
656 """show combined config settings from all hgrc files
657 """show combined config settings from all hgrc files
657
658
658 With no args, print names and values of all config items.
659 With no args, print names and values of all config items.
659
660
660 With one arg of the form section.name, print just the value of
661 With one arg of the form section.name, print just the value of
661 that config item.
662 that config item.
662
663
663 With multiple args, print names and values of all config items
664 With multiple args, print names and values of all config items
664 with matching section names."""
665 with matching section names."""
665
666
666 untrusted = bool(opts.get('untrusted'))
667 untrusted = bool(opts.get('untrusted'))
667 if values:
668 if values:
668 if len([v for v in values if '.' in v]) > 1:
669 if len([v for v in values if '.' in v]) > 1:
669 raise util.Abort(_('only one config item permitted'))
670 raise util.Abort(_('only one config item permitted'))
670 for section, name, value in ui.walkconfig(untrusted=untrusted):
671 for section, name, value in ui.walkconfig(untrusted=untrusted):
671 sectname = section + '.' + name
672 sectname = section + '.' + name
672 if values:
673 if values:
673 for v in values:
674 for v in values:
674 if v == section:
675 if v == section:
675 ui.write('%s=%s\n' % (sectname, value))
676 ui.write('%s=%s\n' % (sectname, value))
676 elif v == sectname:
677 elif v == sectname:
677 ui.write(value, '\n')
678 ui.write(value, '\n')
678 else:
679 else:
679 ui.write('%s=%s\n' % (sectname, value))
680 ui.write('%s=%s\n' % (sectname, value))
680
681
681 def debugsetparents(ui, repo, rev1, rev2=None):
682 def debugsetparents(ui, repo, rev1, rev2=None):
682 """manually set the parents of the current working directory
683 """manually set the parents of the current working directory
683
684
684 This is useful for writing repository conversion tools, but should
685 This is useful for writing repository conversion tools, but should
685 be used with care.
686 be used with care.
686 """
687 """
687
688
688 if not rev2:
689 if not rev2:
689 rev2 = hex(nullid)
690 rev2 = hex(nullid)
690
691
691 wlock = repo.wlock()
692 wlock = repo.wlock()
692 try:
693 try:
693 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
694 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
694 finally:
695 finally:
695 del wlock
696 del wlock
696
697
697 def debugstate(ui, repo):
698 def debugstate(ui, repo):
698 """show the contents of the current dirstate"""
699 """show the contents of the current dirstate"""
699 k = repo.dirstate._map.items()
700 k = repo.dirstate._map.items()
700 k.sort()
701 k.sort()
701 for file_, ent in k:
702 for file_, ent in k:
702 if ent[3] == -1:
703 if ent[3] == -1:
703 # Pad or slice to locale representation
704 # Pad or slice to locale representation
704 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(0)))
705 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(0)))
705 timestr = 'unset'
706 timestr = 'unset'
706 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
707 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
707 else:
708 else:
708 timestr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ent[3]))
709 timestr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ent[3]))
709 if ent[1] & 020000:
710 if ent[1] & 020000:
710 mode = 'lnk'
711 mode = 'lnk'
711 else:
712 else:
712 mode = '%3o' % (ent[1] & 0777)
713 mode = '%3o' % (ent[1] & 0777)
713 ui.write("%c %s %10d %s %s\n" % (ent[0], mode, ent[2], timestr, file_))
714 ui.write("%c %s %10d %s %s\n" % (ent[0], mode, ent[2], timestr, file_))
714 for f in repo.dirstate.copies():
715 for f in repo.dirstate.copies():
715 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
716 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
716
717
717 def debugdata(ui, file_, rev):
718 def debugdata(ui, file_, rev):
718 """dump the contents of a data file revision"""
719 """dump the contents of a data file revision"""
719 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
720 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
720 try:
721 try:
721 ui.write(r.revision(r.lookup(rev)))
722 ui.write(r.revision(r.lookup(rev)))
722 except KeyError:
723 except KeyError:
723 raise util.Abort(_('invalid revision identifier %s') % rev)
724 raise util.Abort(_('invalid revision identifier %s') % rev)
724
725
725 def debugdate(ui, date, range=None, **opts):
726 def debugdate(ui, date, range=None, **opts):
726 """parse and display a date"""
727 """parse and display a date"""
727 if opts["extended"]:
728 if opts["extended"]:
728 d = util.parsedate(date, util.extendeddateformats)
729 d = util.parsedate(date, util.extendeddateformats)
729 else:
730 else:
730 d = util.parsedate(date)
731 d = util.parsedate(date)
731 ui.write("internal: %s %s\n" % d)
732 ui.write("internal: %s %s\n" % d)
732 ui.write("standard: %s\n" % util.datestr(d))
733 ui.write("standard: %s\n" % util.datestr(d))
733 if range:
734 if range:
734 m = util.matchdate(range)
735 m = util.matchdate(range)
735 ui.write("match: %s\n" % m(d[0]))
736 ui.write("match: %s\n" % m(d[0]))
736
737
737 def debugindex(ui, file_):
738 def debugindex(ui, file_):
738 """dump the contents of an index file"""
739 """dump the contents of an index file"""
739 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
740 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
740 ui.write(" rev offset length base linkrev" +
741 ui.write(" rev offset length base linkrev" +
741 " nodeid p1 p2\n")
742 " nodeid p1 p2\n")
742 for i in xrange(r.count()):
743 for i in xrange(r.count()):
743 node = r.node(i)
744 node = r.node(i)
744 try:
745 try:
745 pp = r.parents(node)
746 pp = r.parents(node)
746 except:
747 except:
747 pp = [nullid, nullid]
748 pp = [nullid, nullid]
748 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
749 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
749 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
750 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
750 short(node), short(pp[0]), short(pp[1])))
751 short(node), short(pp[0]), short(pp[1])))
751
752
752 def debugindexdot(ui, file_):
753 def debugindexdot(ui, file_):
753 """dump an index DAG as a .dot file"""
754 """dump an index DAG as a .dot file"""
754 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
755 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
755 ui.write("digraph G {\n")
756 ui.write("digraph G {\n")
756 for i in xrange(r.count()):
757 for i in xrange(r.count()):
757 node = r.node(i)
758 node = r.node(i)
758 pp = r.parents(node)
759 pp = r.parents(node)
759 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
760 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
760 if pp[1] != nullid:
761 if pp[1] != nullid:
761 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
762 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
762 ui.write("}\n")
763 ui.write("}\n")
763
764
764 def debuginstall(ui):
765 def debuginstall(ui):
765 '''test Mercurial installation'''
766 '''test Mercurial installation'''
766
767
767 def writetemp(contents):
768 def writetemp(contents):
768 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
769 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
769 f = os.fdopen(fd, "wb")
770 f = os.fdopen(fd, "wb")
770 f.write(contents)
771 f.write(contents)
771 f.close()
772 f.close()
772 return name
773 return name
773
774
774 problems = 0
775 problems = 0
775
776
776 # encoding
777 # encoding
777 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
778 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
778 try:
779 try:
779 util.fromlocal("test")
780 util.fromlocal("test")
780 except util.Abort, inst:
781 except util.Abort, inst:
781 ui.write(" %s\n" % inst)
782 ui.write(" %s\n" % inst)
782 ui.write(_(" (check that your locale is properly set)\n"))
783 ui.write(_(" (check that your locale is properly set)\n"))
783 problems += 1
784 problems += 1
784
785
785 # compiled modules
786 # compiled modules
786 ui.status(_("Checking extensions...\n"))
787 ui.status(_("Checking extensions...\n"))
787 try:
788 try:
788 import bdiff, mpatch, base85
789 import bdiff, mpatch, base85
789 except Exception, inst:
790 except Exception, inst:
790 ui.write(" %s\n" % inst)
791 ui.write(" %s\n" % inst)
791 ui.write(_(" One or more extensions could not be found"))
792 ui.write(_(" One or more extensions could not be found"))
792 ui.write(_(" (check that you compiled the extensions)\n"))
793 ui.write(_(" (check that you compiled the extensions)\n"))
793 problems += 1
794 problems += 1
794
795
795 # templates
796 # templates
796 ui.status(_("Checking templates...\n"))
797 ui.status(_("Checking templates...\n"))
797 try:
798 try:
798 import templater
799 import templater
799 t = templater.templater(templater.templatepath("map-cmdline.default"))
800 t = templater.templater(templater.templatepath("map-cmdline.default"))
800 except Exception, inst:
801 except Exception, inst:
801 ui.write(" %s\n" % inst)
802 ui.write(" %s\n" % inst)
802 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
803 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
803 problems += 1
804 problems += 1
804
805
805 # patch
806 # patch
806 ui.status(_("Checking patch...\n"))
807 ui.status(_("Checking patch...\n"))
807 patchproblems = 0
808 patchproblems = 0
808 a = "1\n2\n3\n4\n"
809 a = "1\n2\n3\n4\n"
809 b = "1\n2\n3\ninsert\n4\n"
810 b = "1\n2\n3\ninsert\n4\n"
810 fa = writetemp(a)
811 fa = writetemp(a)
811 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
812 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
812 os.path.basename(fa))
813 os.path.basename(fa))
813 fd = writetemp(d)
814 fd = writetemp(d)
814
815
815 files = {}
816 files = {}
816 try:
817 try:
817 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
818 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
818 except util.Abort, e:
819 except util.Abort, e:
819 ui.write(_(" patch call failed:\n"))
820 ui.write(_(" patch call failed:\n"))
820 ui.write(" " + str(e) + "\n")
821 ui.write(" " + str(e) + "\n")
821 patchproblems += 1
822 patchproblems += 1
822 else:
823 else:
823 if list(files) != [os.path.basename(fa)]:
824 if list(files) != [os.path.basename(fa)]:
824 ui.write(_(" unexpected patch output!\n"))
825 ui.write(_(" unexpected patch output!\n"))
825 patchproblems += 1
826 patchproblems += 1
826 a = file(fa).read()
827 a = file(fa).read()
827 if a != b:
828 if a != b:
828 ui.write(_(" patch test failed!\n"))
829 ui.write(_(" patch test failed!\n"))
829 patchproblems += 1
830 patchproblems += 1
830
831
831 if patchproblems:
832 if patchproblems:
832 if ui.config('ui', 'patch'):
833 if ui.config('ui', 'patch'):
833 ui.write(_(" (Current patch tool may be incompatible with patch,"
834 ui.write(_(" (Current patch tool may be incompatible with patch,"
834 " or misconfigured. Please check your .hgrc file)\n"))
835 " or misconfigured. Please check your .hgrc file)\n"))
835 else:
836 else:
836 ui.write(_(" Internal patcher failure, please report this error"
837 ui.write(_(" Internal patcher failure, please report this error"
837 " to http://www.selenic.com/mercurial/bts\n"))
838 " to http://www.selenic.com/mercurial/bts\n"))
838 problems += patchproblems
839 problems += patchproblems
839
840
840 os.unlink(fa)
841 os.unlink(fa)
841 os.unlink(fd)
842 os.unlink(fd)
842
843
843 # editor
844 # editor
844 ui.status(_("Checking commit editor...\n"))
845 ui.status(_("Checking commit editor...\n"))
845 editor = ui.geteditor()
846 editor = ui.geteditor()
846 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
847 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
847 if not cmdpath:
848 if not cmdpath:
848 if editor == 'vi':
849 if editor == 'vi':
849 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
850 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
850 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
851 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
851 else:
852 else:
852 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
853 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
853 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
854 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
854 problems += 1
855 problems += 1
855
856
856 # check username
857 # check username
857 ui.status(_("Checking username...\n"))
858 ui.status(_("Checking username...\n"))
858 user = os.environ.get("HGUSER")
859 user = os.environ.get("HGUSER")
859 if user is None:
860 if user is None:
860 user = ui.config("ui", "username")
861 user = ui.config("ui", "username")
861 if user is None:
862 if user is None:
862 user = os.environ.get("EMAIL")
863 user = os.environ.get("EMAIL")
863 if not user:
864 if not user:
864 ui.warn(" ")
865 ui.warn(" ")
865 ui.username()
866 ui.username()
866 ui.write(_(" (specify a username in your .hgrc file)\n"))
867 ui.write(_(" (specify a username in your .hgrc file)\n"))
867
868
868 if not problems:
869 if not problems:
869 ui.status(_("No problems detected\n"))
870 ui.status(_("No problems detected\n"))
870 else:
871 else:
871 ui.write(_("%s problems detected,"
872 ui.write(_("%s problems detected,"
872 " please check your install!\n") % problems)
873 " please check your install!\n") % problems)
873
874
874 return problems
875 return problems
875
876
876 def debugrename(ui, repo, file1, *pats, **opts):
877 def debugrename(ui, repo, file1, *pats, **opts):
877 """dump rename information"""
878 """dump rename information"""
878
879
879 ctx = repo.changectx(opts.get('rev', 'tip'))
880 ctx = repo.changectx(opts.get('rev', 'tip'))
880 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
881 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
881 ctx.node()):
882 ctx.node()):
882 fctx = ctx.filectx(abs)
883 fctx = ctx.filectx(abs)
883 m = fctx.filelog().renamed(fctx.filenode())
884 m = fctx.filelog().renamed(fctx.filenode())
884 if m:
885 if m:
885 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
886 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
886 else:
887 else:
887 ui.write(_("%s not renamed\n") % rel)
888 ui.write(_("%s not renamed\n") % rel)
888
889
889 def debugwalk(ui, repo, *pats, **opts):
890 def debugwalk(ui, repo, *pats, **opts):
890 """show how files match on given patterns"""
891 """show how files match on given patterns"""
891 items = list(cmdutil.walk(repo, pats, opts))
892 items = list(cmdutil.walk(repo, pats, opts))
892 if not items:
893 if not items:
893 return
894 return
894 fmt = '%%s %%-%ds %%-%ds %%s' % (
895 fmt = '%%s %%-%ds %%-%ds %%s' % (
895 max([len(abs) for (src, abs, rel, exact) in items]),
896 max([len(abs) for (src, abs, rel, exact) in items]),
896 max([len(rel) for (src, abs, rel, exact) in items]))
897 max([len(rel) for (src, abs, rel, exact) in items]))
897 for src, abs, rel, exact in items:
898 for src, abs, rel, exact in items:
898 line = fmt % (src, abs, rel, exact and 'exact' or '')
899 line = fmt % (src, abs, rel, exact and 'exact' or '')
899 ui.write("%s\n" % line.rstrip())
900 ui.write("%s\n" % line.rstrip())
900
901
901 def diff(ui, repo, *pats, **opts):
902 def diff(ui, repo, *pats, **opts):
902 """diff repository (or selected files)
903 """diff repository (or selected files)
903
904
904 Show differences between revisions for the specified files.
905 Show differences between revisions for the specified files.
905
906
906 Differences between files are shown using the unified diff format.
907 Differences between files are shown using the unified diff format.
907
908
908 NOTE: diff may generate unexpected results for merges, as it will
909 NOTE: diff may generate unexpected results for merges, as it will
909 default to comparing against the working directory's first parent
910 default to comparing against the working directory's first parent
910 changeset if no revisions are specified.
911 changeset if no revisions are specified.
911
912
912 When two revision arguments are given, then changes are shown
913 When two revision arguments are given, then changes are shown
913 between those revisions. If only one revision is specified then
914 between those revisions. If only one revision is specified then
914 that revision is compared to the working directory, and, when no
915 that revision is compared to the working directory, and, when no
915 revisions are specified, the working directory files are compared
916 revisions are specified, the working directory files are compared
916 to its parent.
917 to its parent.
917
918
918 Without the -a option, diff will avoid generating diffs of files
919 Without the -a option, diff will avoid generating diffs of files
919 it detects as binary. With -a, diff will generate a diff anyway,
920 it detects as binary. With -a, diff will generate a diff anyway,
920 probably with undesirable results.
921 probably with undesirable results.
921 """
922 """
922 node1, node2 = cmdutil.revpair(repo, opts['rev'])
923 node1, node2 = cmdutil.revpair(repo, opts['rev'])
923
924
924 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
925 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
925
926
926 patch.diff(repo, node1, node2, fns, match=matchfn,
927 patch.diff(repo, node1, node2, fns, match=matchfn,
927 opts=patch.diffopts(ui, opts))
928 opts=patch.diffopts(ui, opts))
928
929
929 def export(ui, repo, *changesets, **opts):
930 def export(ui, repo, *changesets, **opts):
930 """dump the header and diffs for one or more changesets
931 """dump the header and diffs for one or more changesets
931
932
932 Print the changeset header and diffs for one or more revisions.
933 Print the changeset header and diffs for one or more revisions.
933
934
934 The information shown in the changeset header is: author,
935 The information shown in the changeset header is: author,
935 changeset hash, parent(s) and commit comment.
936 changeset hash, parent(s) and commit comment.
936
937
937 NOTE: export may generate unexpected diff output for merge changesets,
938 NOTE: export may generate unexpected diff output for merge changesets,
938 as it will compare the merge changeset against its first parent only.
939 as it will compare the merge changeset against its first parent only.
939
940
940 Output may be to a file, in which case the name of the file is
941 Output may be to a file, in which case the name of the file is
941 given using a format string. The formatting rules are as follows:
942 given using a format string. The formatting rules are as follows:
942
943
943 %% literal "%" character
944 %% literal "%" character
944 %H changeset hash (40 bytes of hexadecimal)
945 %H changeset hash (40 bytes of hexadecimal)
945 %N number of patches being generated
946 %N number of patches being generated
946 %R changeset revision number
947 %R changeset revision number
947 %b basename of the exporting repository
948 %b basename of the exporting repository
948 %h short-form changeset hash (12 bytes of hexadecimal)
949 %h short-form changeset hash (12 bytes of hexadecimal)
949 %n zero-padded sequence number, starting at 1
950 %n zero-padded sequence number, starting at 1
950 %r zero-padded changeset revision number
951 %r zero-padded changeset revision number
951
952
952 Without the -a option, export will avoid generating diffs of files
953 Without the -a option, export will avoid generating diffs of files
953 it detects as binary. With -a, export will generate a diff anyway,
954 it detects as binary. With -a, export will generate a diff anyway,
954 probably with undesirable results.
955 probably with undesirable results.
955
956
956 With the --switch-parent option, the diff will be against the second
957 With the --switch-parent option, the diff will be against the second
957 parent. It can be useful to review a merge.
958 parent. It can be useful to review a merge.
958 """
959 """
959 if not changesets:
960 if not changesets:
960 raise util.Abort(_("export requires at least one changeset"))
961 raise util.Abort(_("export requires at least one changeset"))
961 revs = cmdutil.revrange(repo, changesets)
962 revs = cmdutil.revrange(repo, changesets)
962 if len(revs) > 1:
963 if len(revs) > 1:
963 ui.note(_('exporting patches:\n'))
964 ui.note(_('exporting patches:\n'))
964 else:
965 else:
965 ui.note(_('exporting patch:\n'))
966 ui.note(_('exporting patch:\n'))
966 patch.export(repo, revs, template=opts['output'],
967 patch.export(repo, revs, template=opts['output'],
967 switch_parent=opts['switch_parent'],
968 switch_parent=opts['switch_parent'],
968 opts=patch.diffopts(ui, opts))
969 opts=patch.diffopts(ui, opts))
969
970
970 def grep(ui, repo, pattern, *pats, **opts):
971 def grep(ui, repo, pattern, *pats, **opts):
971 """search for a pattern in specified files and revisions
972 """search for a pattern in specified files and revisions
972
973
973 Search revisions of files for a regular expression.
974 Search revisions of files for a regular expression.
974
975
975 This command behaves differently than Unix grep. It only accepts
976 This command behaves differently than Unix grep. It only accepts
976 Python/Perl regexps. It searches repository history, not the
977 Python/Perl regexps. It searches repository history, not the
977 working directory. It always prints the revision number in which
978 working directory. It always prints the revision number in which
978 a match appears.
979 a match appears.
979
980
980 By default, grep only prints output for the first revision of a
981 By default, grep only prints output for the first revision of a
981 file in which it finds a match. To get it to print every revision
982 file in which it finds a match. To get it to print every revision
982 that contains a change in match status ("-" for a match that
983 that contains a change in match status ("-" for a match that
983 becomes a non-match, or "+" for a non-match that becomes a match),
984 becomes a non-match, or "+" for a non-match that becomes a match),
984 use the --all flag.
985 use the --all flag.
985 """
986 """
986 reflags = 0
987 reflags = 0
987 if opts['ignore_case']:
988 if opts['ignore_case']:
988 reflags |= re.I
989 reflags |= re.I
989 try:
990 try:
990 regexp = re.compile(pattern, reflags)
991 regexp = re.compile(pattern, reflags)
991 except Exception, inst:
992 except Exception, inst:
992 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
993 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
993 return None
994 return None
994 sep, eol = ':', '\n'
995 sep, eol = ':', '\n'
995 if opts['print0']:
996 if opts['print0']:
996 sep = eol = '\0'
997 sep = eol = '\0'
997
998
998 fcache = {}
999 fcache = {}
999 def getfile(fn):
1000 def getfile(fn):
1000 if fn not in fcache:
1001 if fn not in fcache:
1001 fcache[fn] = repo.file(fn)
1002 fcache[fn] = repo.file(fn)
1002 return fcache[fn]
1003 return fcache[fn]
1003
1004
1004 def matchlines(body):
1005 def matchlines(body):
1005 begin = 0
1006 begin = 0
1006 linenum = 0
1007 linenum = 0
1007 while True:
1008 while True:
1008 match = regexp.search(body, begin)
1009 match = regexp.search(body, begin)
1009 if not match:
1010 if not match:
1010 break
1011 break
1011 mstart, mend = match.span()
1012 mstart, mend = match.span()
1012 linenum += body.count('\n', begin, mstart) + 1
1013 linenum += body.count('\n', begin, mstart) + 1
1013 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1014 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1014 lend = body.find('\n', mend)
1015 lend = body.find('\n', mend)
1015 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1016 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1016 begin = lend + 1
1017 begin = lend + 1
1017
1018
1018 class linestate(object):
1019 class linestate(object):
1019 def __init__(self, line, linenum, colstart, colend):
1020 def __init__(self, line, linenum, colstart, colend):
1020 self.line = line
1021 self.line = line
1021 self.linenum = linenum
1022 self.linenum = linenum
1022 self.colstart = colstart
1023 self.colstart = colstart
1023 self.colend = colend
1024 self.colend = colend
1024
1025
1025 def __eq__(self, other):
1026 def __eq__(self, other):
1026 return self.line == other.line
1027 return self.line == other.line
1027
1028
1028 matches = {}
1029 matches = {}
1029 copies = {}
1030 copies = {}
1030 def grepbody(fn, rev, body):
1031 def grepbody(fn, rev, body):
1031 matches[rev].setdefault(fn, [])
1032 matches[rev].setdefault(fn, [])
1032 m = matches[rev][fn]
1033 m = matches[rev][fn]
1033 for lnum, cstart, cend, line in matchlines(body):
1034 for lnum, cstart, cend, line in matchlines(body):
1034 s = linestate(line, lnum, cstart, cend)
1035 s = linestate(line, lnum, cstart, cend)
1035 m.append(s)
1036 m.append(s)
1036
1037
1037 def difflinestates(a, b):
1038 def difflinestates(a, b):
1038 sm = difflib.SequenceMatcher(None, a, b)
1039 sm = difflib.SequenceMatcher(None, a, b)
1039 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1040 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1040 if tag == 'insert':
1041 if tag == 'insert':
1041 for i in xrange(blo, bhi):
1042 for i in xrange(blo, bhi):
1042 yield ('+', b[i])
1043 yield ('+', b[i])
1043 elif tag == 'delete':
1044 elif tag == 'delete':
1044 for i in xrange(alo, ahi):
1045 for i in xrange(alo, ahi):
1045 yield ('-', a[i])
1046 yield ('-', a[i])
1046 elif tag == 'replace':
1047 elif tag == 'replace':
1047 for i in xrange(alo, ahi):
1048 for i in xrange(alo, ahi):
1048 yield ('-', a[i])
1049 yield ('-', a[i])
1049 for i in xrange(blo, bhi):
1050 for i in xrange(blo, bhi):
1050 yield ('+', b[i])
1051 yield ('+', b[i])
1051
1052
1052 prev = {}
1053 prev = {}
1053 def display(fn, rev, states, prevstates):
1054 def display(fn, rev, states, prevstates):
1054 datefunc = ui.quiet and util.shortdate or util.datestr
1055 datefunc = ui.quiet and util.shortdate or util.datestr
1055 found = False
1056 found = False
1056 filerevmatches = {}
1057 filerevmatches = {}
1057 r = prev.get(fn, -1)
1058 r = prev.get(fn, -1)
1058 if opts['all']:
1059 if opts['all']:
1059 iter = difflinestates(states, prevstates)
1060 iter = difflinestates(states, prevstates)
1060 else:
1061 else:
1061 iter = [('', l) for l in prevstates]
1062 iter = [('', l) for l in prevstates]
1062 for change, l in iter:
1063 for change, l in iter:
1063 cols = [fn, str(r)]
1064 cols = [fn, str(r)]
1064 if opts['line_number']:
1065 if opts['line_number']:
1065 cols.append(str(l.linenum))
1066 cols.append(str(l.linenum))
1066 if opts['all']:
1067 if opts['all']:
1067 cols.append(change)
1068 cols.append(change)
1068 if opts['user']:
1069 if opts['user']:
1069 cols.append(ui.shortuser(get(r)[1]))
1070 cols.append(ui.shortuser(get(r)[1]))
1070 if opts.get('date'):
1071 if opts.get('date'):
1071 cols.append(datefunc(get(r)[2]))
1072 cols.append(datefunc(get(r)[2]))
1072 if opts['files_with_matches']:
1073 if opts['files_with_matches']:
1073 c = (fn, r)
1074 c = (fn, r)
1074 if c in filerevmatches:
1075 if c in filerevmatches:
1075 continue
1076 continue
1076 filerevmatches[c] = 1
1077 filerevmatches[c] = 1
1077 else:
1078 else:
1078 cols.append(l.line)
1079 cols.append(l.line)
1079 ui.write(sep.join(cols), eol)
1080 ui.write(sep.join(cols), eol)
1080 found = True
1081 found = True
1081 return found
1082 return found
1082
1083
1083 fstate = {}
1084 fstate = {}
1084 skip = {}
1085 skip = {}
1085 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1086 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1086 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1087 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1087 found = False
1088 found = False
1088 follow = opts.get('follow')
1089 follow = opts.get('follow')
1089 for st, rev, fns in changeiter:
1090 for st, rev, fns in changeiter:
1090 if st == 'window':
1091 if st == 'window':
1091 matches.clear()
1092 matches.clear()
1092 elif st == 'add':
1093 elif st == 'add':
1093 ctx = repo.changectx(rev)
1094 ctx = repo.changectx(rev)
1094 matches[rev] = {}
1095 matches[rev] = {}
1095 for fn in fns:
1096 for fn in fns:
1096 if fn in skip:
1097 if fn in skip:
1097 continue
1098 continue
1098 try:
1099 try:
1099 grepbody(fn, rev, getfile(fn).read(ctx.filenode(fn)))
1100 grepbody(fn, rev, getfile(fn).read(ctx.filenode(fn)))
1100 fstate.setdefault(fn, [])
1101 fstate.setdefault(fn, [])
1101 if follow:
1102 if follow:
1102 copied = getfile(fn).renamed(ctx.filenode(fn))
1103 copied = getfile(fn).renamed(ctx.filenode(fn))
1103 if copied:
1104 if copied:
1104 copies.setdefault(rev, {})[fn] = copied[0]
1105 copies.setdefault(rev, {})[fn] = copied[0]
1105 except revlog.LookupError:
1106 except revlog.LookupError:
1106 pass
1107 pass
1107 elif st == 'iter':
1108 elif st == 'iter':
1108 states = matches[rev].items()
1109 states = matches[rev].items()
1109 states.sort()
1110 states.sort()
1110 for fn, m in states:
1111 for fn, m in states:
1111 copy = copies.get(rev, {}).get(fn)
1112 copy = copies.get(rev, {}).get(fn)
1112 if fn in skip:
1113 if fn in skip:
1113 if copy:
1114 if copy:
1114 skip[copy] = True
1115 skip[copy] = True
1115 continue
1116 continue
1116 if fn in prev or fstate[fn]:
1117 if fn in prev or fstate[fn]:
1117 r = display(fn, rev, m, fstate[fn])
1118 r = display(fn, rev, m, fstate[fn])
1118 found = found or r
1119 found = found or r
1119 if r and not opts['all']:
1120 if r and not opts['all']:
1120 skip[fn] = True
1121 skip[fn] = True
1121 if copy:
1122 if copy:
1122 skip[copy] = True
1123 skip[copy] = True
1123 fstate[fn] = m
1124 fstate[fn] = m
1124 if copy:
1125 if copy:
1125 fstate[copy] = m
1126 fstate[copy] = m
1126 prev[fn] = rev
1127 prev[fn] = rev
1127
1128
1128 fstate = fstate.items()
1129 fstate = fstate.items()
1129 fstate.sort()
1130 fstate.sort()
1130 for fn, state in fstate:
1131 for fn, state in fstate:
1131 if fn in skip:
1132 if fn in skip:
1132 continue
1133 continue
1133 if fn not in copies.get(prev[fn], {}):
1134 if fn not in copies.get(prev[fn], {}):
1134 found = display(fn, rev, {}, state) or found
1135 found = display(fn, rev, {}, state) or found
1135 return (not found and 1) or 0
1136 return (not found and 1) or 0
1136
1137
1137 def heads(ui, repo, *branchrevs, **opts):
1138 def heads(ui, repo, *branchrevs, **opts):
1138 """show current repository heads or show branch heads
1139 """show current repository heads or show branch heads
1139
1140
1140 With no arguments, show all repository head changesets.
1141 With no arguments, show all repository head changesets.
1141
1142
1142 If branch or revisions names are given this will show the heads of
1143 If branch or revisions names are given this will show the heads of
1143 the specified branches or the branches those revisions are tagged
1144 the specified branches or the branches those revisions are tagged
1144 with.
1145 with.
1145
1146
1146 Repository "heads" are changesets that don't have child
1147 Repository "heads" are changesets that don't have child
1147 changesets. They are where development generally takes place and
1148 changesets. They are where development generally takes place and
1148 are the usual targets for update and merge operations.
1149 are the usual targets for update and merge operations.
1149
1150
1150 Branch heads are changesets that have a given branch tag, but have
1151 Branch heads are changesets that have a given branch tag, but have
1151 no child changesets with that tag. They are usually where
1152 no child changesets with that tag. They are usually where
1152 development on the given branch takes place.
1153 development on the given branch takes place.
1153 """
1154 """
1154 if opts['rev']:
1155 if opts['rev']:
1155 start = repo.lookup(opts['rev'])
1156 start = repo.lookup(opts['rev'])
1156 else:
1157 else:
1157 start = None
1158 start = None
1158 if not branchrevs:
1159 if not branchrevs:
1159 # Assume we're looking repo-wide heads if no revs were specified.
1160 # Assume we're looking repo-wide heads if no revs were specified.
1160 heads = repo.heads(start)
1161 heads = repo.heads(start)
1161 else:
1162 else:
1162 heads = []
1163 heads = []
1163 visitedset = util.set()
1164 visitedset = util.set()
1164 for branchrev in branchrevs:
1165 for branchrev in branchrevs:
1165 branch = repo.changectx(branchrev).branch()
1166 branch = repo.changectx(branchrev).branch()
1166 if branch in visitedset:
1167 if branch in visitedset:
1167 continue
1168 continue
1168 visitedset.add(branch)
1169 visitedset.add(branch)
1169 bheads = repo.branchheads(branch, start)
1170 bheads = repo.branchheads(branch, start)
1170 if not bheads:
1171 if not bheads:
1171 if branch != branchrev:
1172 if branch != branchrev:
1172 ui.warn(_("no changes on branch %s containing %s are "
1173 ui.warn(_("no changes on branch %s containing %s are "
1173 "reachable from %s\n")
1174 "reachable from %s\n")
1174 % (branch, branchrev, opts['rev']))
1175 % (branch, branchrev, opts['rev']))
1175 else:
1176 else:
1176 ui.warn(_("no changes on branch %s are reachable from %s\n")
1177 ui.warn(_("no changes on branch %s are reachable from %s\n")
1177 % (branch, opts['rev']))
1178 % (branch, opts['rev']))
1178 heads.extend(bheads)
1179 heads.extend(bheads)
1179 if not heads:
1180 if not heads:
1180 return 1
1181 return 1
1181 displayer = cmdutil.show_changeset(ui, repo, opts)
1182 displayer = cmdutil.show_changeset(ui, repo, opts)
1182 for n in heads:
1183 for n in heads:
1183 displayer.show(changenode=n)
1184 displayer.show(changenode=n)
1184
1185
1185 def help_(ui, name=None, with_version=False):
1186 def help_(ui, name=None, with_version=False):
1186 """show help for a command, extension, or list of commands
1187 """show help for a command, extension, or list of commands
1187
1188
1188 With no arguments, print a list of commands and short help.
1189 With no arguments, print a list of commands and short help.
1189
1190
1190 Given a command name, print help for that command.
1191 Given a command name, print help for that command.
1191
1192
1192 Given an extension name, print help for that extension, and the
1193 Given an extension name, print help for that extension, and the
1193 commands it provides."""
1194 commands it provides."""
1194 option_lists = []
1195 option_lists = []
1195
1196
1196 def addglobalopts(aliases):
1197 def addglobalopts(aliases):
1197 if ui.verbose:
1198 if ui.verbose:
1198 option_lists.append((_("global options:"), globalopts))
1199 option_lists.append((_("global options:"), globalopts))
1199 if name == 'shortlist':
1200 if name == 'shortlist':
1200 option_lists.append((_('use "hg help" for the full list '
1201 option_lists.append((_('use "hg help" for the full list '
1201 'of commands'), ()))
1202 'of commands'), ()))
1202 else:
1203 else:
1203 if name == 'shortlist':
1204 if name == 'shortlist':
1204 msg = _('use "hg help" for the full list of commands '
1205 msg = _('use "hg help" for the full list of commands '
1205 'or "hg -v" for details')
1206 'or "hg -v" for details')
1206 elif aliases:
1207 elif aliases:
1207 msg = _('use "hg -v help%s" to show aliases and '
1208 msg = _('use "hg -v help%s" to show aliases and '
1208 'global options') % (name and " " + name or "")
1209 'global options') % (name and " " + name or "")
1209 else:
1210 else:
1210 msg = _('use "hg -v help %s" to show global options') % name
1211 msg = _('use "hg -v help %s" to show global options') % name
1211 option_lists.append((msg, ()))
1212 option_lists.append((msg, ()))
1212
1213
1213 def helpcmd(name):
1214 def helpcmd(name):
1214 if with_version:
1215 if with_version:
1215 version_(ui)
1216 version_(ui)
1216 ui.write('\n')
1217 ui.write('\n')
1217 aliases, i = cmdutil.findcmd(ui, name, table)
1218 aliases, i = cmdutil.findcmd(ui, name, table)
1218 # synopsis
1219 # synopsis
1219 ui.write("%s\n" % i[2])
1220 ui.write("%s\n" % i[2])
1220
1221
1221 # aliases
1222 # aliases
1222 if not ui.quiet and len(aliases) > 1:
1223 if not ui.quiet and len(aliases) > 1:
1223 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1224 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1224
1225
1225 # description
1226 # description
1226 doc = i[0].__doc__
1227 doc = i[0].__doc__
1227 if not doc:
1228 if not doc:
1228 doc = _("(No help text available)")
1229 doc = _("(No help text available)")
1229 if ui.quiet:
1230 if ui.quiet:
1230 doc = doc.splitlines(0)[0]
1231 doc = doc.splitlines(0)[0]
1231 ui.write("\n%s\n" % doc.rstrip())
1232 ui.write("\n%s\n" % doc.rstrip())
1232
1233
1233 if not ui.quiet:
1234 if not ui.quiet:
1234 # options
1235 # options
1235 if i[1]:
1236 if i[1]:
1236 option_lists.append((_("options:\n"), i[1]))
1237 option_lists.append((_("options:\n"), i[1]))
1237
1238
1238 addglobalopts(False)
1239 addglobalopts(False)
1239
1240
1240 def helplist(header, select=None):
1241 def helplist(header, select=None):
1241 h = {}
1242 h = {}
1242 cmds = {}
1243 cmds = {}
1243 for c, e in table.items():
1244 for c, e in table.items():
1244 f = c.split("|", 1)[0]
1245 f = c.split("|", 1)[0]
1245 if select and not select(f):
1246 if select and not select(f):
1246 continue
1247 continue
1247 if name == "shortlist" and not f.startswith("^"):
1248 if name == "shortlist" and not f.startswith("^"):
1248 continue
1249 continue
1249 f = f.lstrip("^")
1250 f = f.lstrip("^")
1250 if not ui.debugflag and f.startswith("debug"):
1251 if not ui.debugflag and f.startswith("debug"):
1251 continue
1252 continue
1252 doc = e[0].__doc__
1253 doc = e[0].__doc__
1253 if not doc:
1254 if not doc:
1254 doc = _("(No help text available)")
1255 doc = _("(No help text available)")
1255 h[f] = doc.splitlines(0)[0].rstrip()
1256 h[f] = doc.splitlines(0)[0].rstrip()
1256 cmds[f] = c.lstrip("^")
1257 cmds[f] = c.lstrip("^")
1257
1258
1258 if not h:
1259 if not h:
1259 ui.status(_('no commands defined\n'))
1260 ui.status(_('no commands defined\n'))
1260 return
1261 return
1261
1262
1262 ui.status(header)
1263 ui.status(header)
1263 fns = h.keys()
1264 fns = h.keys()
1264 fns.sort()
1265 fns.sort()
1265 m = max(map(len, fns))
1266 m = max(map(len, fns))
1266 for f in fns:
1267 for f in fns:
1267 if ui.verbose:
1268 if ui.verbose:
1268 commands = cmds[f].replace("|",", ")
1269 commands = cmds[f].replace("|",", ")
1269 ui.write(" %s:\n %s\n"%(commands, h[f]))
1270 ui.write(" %s:\n %s\n"%(commands, h[f]))
1270 else:
1271 else:
1271 ui.write(' %-*s %s\n' % (m, f, h[f]))
1272 ui.write(' %-*s %s\n' % (m, f, h[f]))
1272
1273
1273 if not ui.quiet:
1274 if not ui.quiet:
1274 addglobalopts(True)
1275 addglobalopts(True)
1275
1276
1276 def helptopic(name):
1277 def helptopic(name):
1277 v = None
1278 v = None
1278 for i in help.helptable:
1279 for i in help.helptable:
1279 l = i.split('|')
1280 l = i.split('|')
1280 if name in l:
1281 if name in l:
1281 v = i
1282 v = i
1282 header = l[-1]
1283 header = l[-1]
1283 if not v:
1284 if not v:
1284 raise cmdutil.UnknownCommand(name)
1285 raise cmdutil.UnknownCommand(name)
1285
1286
1286 # description
1287 # description
1287 doc = help.helptable[v]
1288 doc = help.helptable[v]
1288 if not doc:
1289 if not doc:
1289 doc = _("(No help text available)")
1290 doc = _("(No help text available)")
1290 if callable(doc):
1291 if callable(doc):
1291 doc = doc()
1292 doc = doc()
1292
1293
1293 ui.write("%s\n" % header)
1294 ui.write("%s\n" % header)
1294 ui.write("%s\n" % doc.rstrip())
1295 ui.write("%s\n" % doc.rstrip())
1295
1296
1296 def helpext(name):
1297 def helpext(name):
1297 try:
1298 try:
1298 mod = extensions.find(name)
1299 mod = extensions.find(name)
1299 except KeyError:
1300 except KeyError:
1300 raise cmdutil.UnknownCommand(name)
1301 raise cmdutil.UnknownCommand(name)
1301
1302
1302 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1303 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1303 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1304 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1304 for d in doc[1:]:
1305 for d in doc[1:]:
1305 ui.write(d, '\n')
1306 ui.write(d, '\n')
1306
1307
1307 ui.status('\n')
1308 ui.status('\n')
1308
1309
1309 try:
1310 try:
1310 ct = mod.cmdtable
1311 ct = mod.cmdtable
1311 except AttributeError:
1312 except AttributeError:
1312 ct = {}
1313 ct = {}
1313
1314
1314 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1315 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1315 helplist(_('list of commands:\n\n'), modcmds.has_key)
1316 helplist(_('list of commands:\n\n'), modcmds.has_key)
1316
1317
1317 if name and name != 'shortlist':
1318 if name and name != 'shortlist':
1318 i = None
1319 i = None
1319 for f in (helpcmd, helptopic, helpext):
1320 for f in (helpcmd, helptopic, helpext):
1320 try:
1321 try:
1321 f(name)
1322 f(name)
1322 i = None
1323 i = None
1323 break
1324 break
1324 except cmdutil.UnknownCommand, inst:
1325 except cmdutil.UnknownCommand, inst:
1325 i = inst
1326 i = inst
1326 if i:
1327 if i:
1327 raise i
1328 raise i
1328
1329
1329 else:
1330 else:
1330 # program name
1331 # program name
1331 if ui.verbose or with_version:
1332 if ui.verbose or with_version:
1332 version_(ui)
1333 version_(ui)
1333 else:
1334 else:
1334 ui.status(_("Mercurial Distributed SCM\n"))
1335 ui.status(_("Mercurial Distributed SCM\n"))
1335 ui.status('\n')
1336 ui.status('\n')
1336
1337
1337 # list of commands
1338 # list of commands
1338 if name == "shortlist":
1339 if name == "shortlist":
1339 header = _('basic commands:\n\n')
1340 header = _('basic commands:\n\n')
1340 else:
1341 else:
1341 header = _('list of commands:\n\n')
1342 header = _('list of commands:\n\n')
1342
1343
1343 helplist(header)
1344 helplist(header)
1344
1345
1345 # list all option lists
1346 # list all option lists
1346 opt_output = []
1347 opt_output = []
1347 for title, options in option_lists:
1348 for title, options in option_lists:
1348 opt_output.append(("\n%s" % title, None))
1349 opt_output.append(("\n%s" % title, None))
1349 for shortopt, longopt, default, desc in options:
1350 for shortopt, longopt, default, desc in options:
1350 if "DEPRECATED" in desc and not ui.verbose: continue
1351 if "DEPRECATED" in desc and not ui.verbose: continue
1351 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1352 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1352 longopt and " --%s" % longopt),
1353 longopt and " --%s" % longopt),
1353 "%s%s" % (desc,
1354 "%s%s" % (desc,
1354 default
1355 default
1355 and _(" (default: %s)") % default
1356 and _(" (default: %s)") % default
1356 or "")))
1357 or "")))
1357
1358
1358 if opt_output:
1359 if opt_output:
1359 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1360 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1360 for first, second in opt_output:
1361 for first, second in opt_output:
1361 if second:
1362 if second:
1362 ui.write(" %-*s %s\n" % (opts_len, first, second))
1363 ui.write(" %-*s %s\n" % (opts_len, first, second))
1363 else:
1364 else:
1364 ui.write("%s\n" % first)
1365 ui.write("%s\n" % first)
1365
1366
1366 def identify(ui, repo, source=None,
1367 def identify(ui, repo, source=None,
1367 rev=None, num=None, id=None, branch=None, tags=None):
1368 rev=None, num=None, id=None, branch=None, tags=None):
1368 """identify the working copy or specified revision
1369 """identify the working copy or specified revision
1369
1370
1370 With no revision, print a summary of the current state of the repo.
1371 With no revision, print a summary of the current state of the repo.
1371
1372
1372 With a path, do a lookup in another repository.
1373 With a path, do a lookup in another repository.
1373
1374
1374 This summary identifies the repository state using one or two parent
1375 This summary identifies the repository state using one or two parent
1375 hash identifiers, followed by a "+" if there are uncommitted changes
1376 hash identifiers, followed by a "+" if there are uncommitted changes
1376 in the working directory, a list of tags for this revision and a branch
1377 in the working directory, a list of tags for this revision and a branch
1377 name for non-default branches.
1378 name for non-default branches.
1378 """
1379 """
1379
1380
1380 if not repo and not source:
1381 if not repo and not source:
1381 raise util.Abort(_("There is no Mercurial repository here "
1382 raise util.Abort(_("There is no Mercurial repository here "
1382 "(.hg not found)"))
1383 "(.hg not found)"))
1383
1384
1384 hexfunc = ui.debugflag and hex or short
1385 hexfunc = ui.debugflag and hex or short
1385 default = not (num or id or branch or tags)
1386 default = not (num or id or branch or tags)
1386 output = []
1387 output = []
1387
1388
1388 if source:
1389 if source:
1389 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1390 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1390 srepo = hg.repository(ui, source)
1391 srepo = hg.repository(ui, source)
1391 if not rev and revs:
1392 if not rev and revs:
1392 rev = revs[0]
1393 rev = revs[0]
1393 if not rev:
1394 if not rev:
1394 rev = "tip"
1395 rev = "tip"
1395 if num or branch or tags:
1396 if num or branch or tags:
1396 raise util.Abort(
1397 raise util.Abort(
1397 "can't query remote revision number, branch, or tags")
1398 "can't query remote revision number, branch, or tags")
1398 output = [hexfunc(srepo.lookup(rev))]
1399 output = [hexfunc(srepo.lookup(rev))]
1399 elif not rev:
1400 elif not rev:
1400 ctx = repo.workingctx()
1401 ctx = repo.workingctx()
1401 parents = ctx.parents()
1402 parents = ctx.parents()
1402 changed = False
1403 changed = False
1403 if default or id or num:
1404 if default or id or num:
1404 changed = ctx.files() + ctx.deleted()
1405 changed = ctx.files() + ctx.deleted()
1405 if default or id:
1406 if default or id:
1406 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1407 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1407 (changed) and "+" or "")]
1408 (changed) and "+" or "")]
1408 if num:
1409 if num:
1409 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1410 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1410 (changed) and "+" or ""))
1411 (changed) and "+" or ""))
1411 else:
1412 else:
1412 ctx = repo.changectx(rev)
1413 ctx = repo.changectx(rev)
1413 if default or id:
1414 if default or id:
1414 output = [hexfunc(ctx.node())]
1415 output = [hexfunc(ctx.node())]
1415 if num:
1416 if num:
1416 output.append(str(ctx.rev()))
1417 output.append(str(ctx.rev()))
1417
1418
1418 if not source and default and not ui.quiet:
1419 if not source and default and not ui.quiet:
1419 b = util.tolocal(ctx.branch())
1420 b = util.tolocal(ctx.branch())
1420 if b != 'default':
1421 if b != 'default':
1421 output.append("(%s)" % b)
1422 output.append("(%s)" % b)
1422
1423
1423 # multiple tags for a single parent separated by '/'
1424 # multiple tags for a single parent separated by '/'
1424 t = "/".join(ctx.tags())
1425 t = "/".join(ctx.tags())
1425 if t:
1426 if t:
1426 output.append(t)
1427 output.append(t)
1427
1428
1428 if branch:
1429 if branch:
1429 output.append(util.tolocal(ctx.branch()))
1430 output.append(util.tolocal(ctx.branch()))
1430
1431
1431 if tags:
1432 if tags:
1432 output.extend(ctx.tags())
1433 output.extend(ctx.tags())
1433
1434
1434 ui.write("%s\n" % ' '.join(output))
1435 ui.write("%s\n" % ' '.join(output))
1435
1436
1436 def import_(ui, repo, patch1, *patches, **opts):
1437 def import_(ui, repo, patch1, *patches, **opts):
1437 """import an ordered set of patches
1438 """import an ordered set of patches
1438
1439
1439 Import a list of patches and commit them individually.
1440 Import a list of patches and commit them individually.
1440
1441
1441 If there are outstanding changes in the working directory, import
1442 If there are outstanding changes in the working directory, import
1442 will abort unless given the -f flag.
1443 will abort unless given the -f flag.
1443
1444
1444 You can import a patch straight from a mail message. Even patches
1445 You can import a patch straight from a mail message. Even patches
1445 as attachments work (body part must be type text/plain or
1446 as attachments work (body part must be type text/plain or
1446 text/x-patch to be used). From and Subject headers of email
1447 text/x-patch to be used). From and Subject headers of email
1447 message are used as default committer and commit message. All
1448 message are used as default committer and commit message. All
1448 text/plain body parts before first diff are added to commit
1449 text/plain body parts before first diff are added to commit
1449 message.
1450 message.
1450
1451
1451 If the imported patch was generated by hg export, user and description
1452 If the imported patch was generated by hg export, user and description
1452 from patch override values from message headers and body. Values
1453 from patch override values from message headers and body. Values
1453 given on command line with -m and -u override these.
1454 given on command line with -m and -u override these.
1454
1455
1455 If --exact is specified, import will set the working directory
1456 If --exact is specified, import will set the working directory
1456 to the parent of each patch before applying it, and will abort
1457 to the parent of each patch before applying it, and will abort
1457 if the resulting changeset has a different ID than the one
1458 if the resulting changeset has a different ID than the one
1458 recorded in the patch. This may happen due to character set
1459 recorded in the patch. This may happen due to character set
1459 problems or other deficiencies in the text patch format.
1460 problems or other deficiencies in the text patch format.
1460
1461
1461 To read a patch from standard input, use patch name "-".
1462 To read a patch from standard input, use patch name "-".
1462 See 'hg help dates' for a list of formats valid for -d/--date.
1463 See 'hg help dates' for a list of formats valid for -d/--date.
1463 """
1464 """
1464 patches = (patch1,) + patches
1465 patches = (patch1,) + patches
1465
1466
1466 date = opts.get('date')
1467 date = opts.get('date')
1467 if date:
1468 if date:
1468 opts['date'] = util.parsedate(date)
1469 opts['date'] = util.parsedate(date)
1469
1470
1470 if opts.get('exact') or not opts['force']:
1471 if opts.get('exact') or not opts['force']:
1471 cmdutil.bail_if_changed(repo)
1472 cmdutil.bail_if_changed(repo)
1472
1473
1473 d = opts["base"]
1474 d = opts["base"]
1474 strip = opts["strip"]
1475 strip = opts["strip"]
1475 wlock = lock = None
1476 wlock = lock = None
1476 try:
1477 try:
1477 wlock = repo.wlock()
1478 wlock = repo.wlock()
1478 lock = repo.lock()
1479 lock = repo.lock()
1479 for p in patches:
1480 for p in patches:
1480 pf = os.path.join(d, p)
1481 pf = os.path.join(d, p)
1481
1482
1482 if pf == '-':
1483 if pf == '-':
1483 ui.status(_("applying patch from stdin\n"))
1484 ui.status(_("applying patch from stdin\n"))
1484 data = patch.extract(ui, sys.stdin)
1485 data = patch.extract(ui, sys.stdin)
1485 else:
1486 else:
1486 ui.status(_("applying %s\n") % p)
1487 ui.status(_("applying %s\n") % p)
1487 if os.path.exists(pf):
1488 if os.path.exists(pf):
1488 data = patch.extract(ui, file(pf, 'rb'))
1489 data = patch.extract(ui, file(pf, 'rb'))
1489 else:
1490 else:
1490 data = patch.extract(ui, urllib.urlopen(pf))
1491 data = patch.extract(ui, urllib.urlopen(pf))
1491 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1492 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1492
1493
1493 if tmpname is None:
1494 if tmpname is None:
1494 raise util.Abort(_('no diffs found'))
1495 raise util.Abort(_('no diffs found'))
1495
1496
1496 try:
1497 try:
1497 cmdline_message = cmdutil.logmessage(opts)
1498 cmdline_message = cmdutil.logmessage(opts)
1498 if cmdline_message:
1499 if cmdline_message:
1499 # pickup the cmdline msg
1500 # pickup the cmdline msg
1500 message = cmdline_message
1501 message = cmdline_message
1501 elif message:
1502 elif message:
1502 # pickup the patch msg
1503 # pickup the patch msg
1503 message = message.strip()
1504 message = message.strip()
1504 else:
1505 else:
1505 # launch the editor
1506 # launch the editor
1506 message = None
1507 message = None
1507 ui.debug(_('message:\n%s\n') % message)
1508 ui.debug(_('message:\n%s\n') % message)
1508
1509
1509 wp = repo.workingctx().parents()
1510 wp = repo.workingctx().parents()
1510 if opts.get('exact'):
1511 if opts.get('exact'):
1511 if not nodeid or not p1:
1512 if not nodeid or not p1:
1512 raise util.Abort(_('not a mercurial patch'))
1513 raise util.Abort(_('not a mercurial patch'))
1513 p1 = repo.lookup(p1)
1514 p1 = repo.lookup(p1)
1514 p2 = repo.lookup(p2 or hex(nullid))
1515 p2 = repo.lookup(p2 or hex(nullid))
1515
1516
1516 if p1 != wp[0].node():
1517 if p1 != wp[0].node():
1517 hg.clean(repo, p1)
1518 hg.clean(repo, p1)
1518 repo.dirstate.setparents(p1, p2)
1519 repo.dirstate.setparents(p1, p2)
1519 elif p2:
1520 elif p2:
1520 try:
1521 try:
1521 p1 = repo.lookup(p1)
1522 p1 = repo.lookup(p1)
1522 p2 = repo.lookup(p2)
1523 p2 = repo.lookup(p2)
1523 if p1 == wp[0].node():
1524 if p1 == wp[0].node():
1524 repo.dirstate.setparents(p1, p2)
1525 repo.dirstate.setparents(p1, p2)
1525 except hg.RepoError:
1526 except RepoError:
1526 pass
1527 pass
1527 if opts.get('exact') or opts.get('import_branch'):
1528 if opts.get('exact') or opts.get('import_branch'):
1528 repo.dirstate.setbranch(branch or 'default')
1529 repo.dirstate.setbranch(branch or 'default')
1529
1530
1530 files = {}
1531 files = {}
1531 try:
1532 try:
1532 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1533 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1533 files=files)
1534 files=files)
1534 finally:
1535 finally:
1535 files = patch.updatedir(ui, repo, files)
1536 files = patch.updatedir(ui, repo, files)
1536 if not opts.get('no_commit'):
1537 if not opts.get('no_commit'):
1537 n = repo.commit(files, message, opts.get('user') or user,
1538 n = repo.commit(files, message, opts.get('user') or user,
1538 opts.get('date') or date)
1539 opts.get('date') or date)
1539 if opts.get('exact'):
1540 if opts.get('exact'):
1540 if hex(n) != nodeid:
1541 if hex(n) != nodeid:
1541 repo.rollback()
1542 repo.rollback()
1542 raise util.Abort(_('patch is damaged'
1543 raise util.Abort(_('patch is damaged'
1543 ' or loses information'))
1544 ' or loses information'))
1544 # Force a dirstate write so that the next transaction
1545 # Force a dirstate write so that the next transaction
1545 # backups an up-do-date file.
1546 # backups an up-do-date file.
1546 repo.dirstate.write()
1547 repo.dirstate.write()
1547 finally:
1548 finally:
1548 os.unlink(tmpname)
1549 os.unlink(tmpname)
1549 finally:
1550 finally:
1550 del lock, wlock
1551 del lock, wlock
1551
1552
1552 def incoming(ui, repo, source="default", **opts):
1553 def incoming(ui, repo, source="default", **opts):
1553 """show new changesets found in source
1554 """show new changesets found in source
1554
1555
1555 Show new changesets found in the specified path/URL or the default
1556 Show new changesets found in the specified path/URL or the default
1556 pull location. These are the changesets that would be pulled if a pull
1557 pull location. These are the changesets that would be pulled if a pull
1557 was requested.
1558 was requested.
1558
1559
1559 For remote repository, using --bundle avoids downloading the changesets
1560 For remote repository, using --bundle avoids downloading the changesets
1560 twice if the incoming is followed by a pull.
1561 twice if the incoming is followed by a pull.
1561
1562
1562 See pull for valid source format details.
1563 See pull for valid source format details.
1563 """
1564 """
1564 limit = cmdutil.loglimit(opts)
1565 limit = cmdutil.loglimit(opts)
1565 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1566 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1566 cmdutil.setremoteconfig(ui, opts)
1567 cmdutil.setremoteconfig(ui, opts)
1567
1568
1568 other = hg.repository(ui, source)
1569 other = hg.repository(ui, source)
1569 ui.status(_('comparing with %s\n') % util.hidepassword(source))
1570 ui.status(_('comparing with %s\n') % util.hidepassword(source))
1570 if revs:
1571 if revs:
1571 revs = [other.lookup(rev) for rev in revs]
1572 revs = [other.lookup(rev) for rev in revs]
1572 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1573 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1573 if not incoming:
1574 if not incoming:
1574 try:
1575 try:
1575 os.unlink(opts["bundle"])
1576 os.unlink(opts["bundle"])
1576 except:
1577 except:
1577 pass
1578 pass
1578 ui.status(_("no changes found\n"))
1579 ui.status(_("no changes found\n"))
1579 return 1
1580 return 1
1580
1581
1581 cleanup = None
1582 cleanup = None
1582 try:
1583 try:
1583 fname = opts["bundle"]
1584 fname = opts["bundle"]
1584 if fname or not other.local():
1585 if fname or not other.local():
1585 # create a bundle (uncompressed if other repo is not local)
1586 # create a bundle (uncompressed if other repo is not local)
1586 if revs is None:
1587 if revs is None:
1587 cg = other.changegroup(incoming, "incoming")
1588 cg = other.changegroup(incoming, "incoming")
1588 else:
1589 else:
1589 cg = other.changegroupsubset(incoming, revs, 'incoming')
1590 cg = other.changegroupsubset(incoming, revs, 'incoming')
1590 bundletype = other.local() and "HG10BZ" or "HG10UN"
1591 bundletype = other.local() and "HG10BZ" or "HG10UN"
1591 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1592 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1592 # keep written bundle?
1593 # keep written bundle?
1593 if opts["bundle"]:
1594 if opts["bundle"]:
1594 cleanup = None
1595 cleanup = None
1595 if not other.local():
1596 if not other.local():
1596 # use the created uncompressed bundlerepo
1597 # use the created uncompressed bundlerepo
1597 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1598 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1598
1599
1599 o = other.changelog.nodesbetween(incoming, revs)[0]
1600 o = other.changelog.nodesbetween(incoming, revs)[0]
1600 if opts['newest_first']:
1601 if opts['newest_first']:
1601 o.reverse()
1602 o.reverse()
1602 displayer = cmdutil.show_changeset(ui, other, opts)
1603 displayer = cmdutil.show_changeset(ui, other, opts)
1603 count = 0
1604 count = 0
1604 for n in o:
1605 for n in o:
1605 if count >= limit:
1606 if count >= limit:
1606 break
1607 break
1607 parents = [p for p in other.changelog.parents(n) if p != nullid]
1608 parents = [p for p in other.changelog.parents(n) if p != nullid]
1608 if opts['no_merges'] and len(parents) == 2:
1609 if opts['no_merges'] and len(parents) == 2:
1609 continue
1610 continue
1610 count += 1
1611 count += 1
1611 displayer.show(changenode=n)
1612 displayer.show(changenode=n)
1612 finally:
1613 finally:
1613 if hasattr(other, 'close'):
1614 if hasattr(other, 'close'):
1614 other.close()
1615 other.close()
1615 if cleanup:
1616 if cleanup:
1616 os.unlink(cleanup)
1617 os.unlink(cleanup)
1617
1618
1618 def init(ui, dest=".", **opts):
1619 def init(ui, dest=".", **opts):
1619 """create a new repository in the given directory
1620 """create a new repository in the given directory
1620
1621
1621 Initialize a new repository in the given directory. If the given
1622 Initialize a new repository in the given directory. If the given
1622 directory does not exist, it is created.
1623 directory does not exist, it is created.
1623
1624
1624 If no directory is given, the current directory is used.
1625 If no directory is given, the current directory is used.
1625
1626
1626 It is possible to specify an ssh:// URL as the destination.
1627 It is possible to specify an ssh:// URL as the destination.
1627 Look at the help text for the pull command for important details
1628 Look at the help text for the pull command for important details
1628 about ssh:// URLs.
1629 about ssh:// URLs.
1629 """
1630 """
1630 cmdutil.setremoteconfig(ui, opts)
1631 cmdutil.setremoteconfig(ui, opts)
1631 hg.repository(ui, dest, create=1)
1632 hg.repository(ui, dest, create=1)
1632
1633
1633 def locate(ui, repo, *pats, **opts):
1634 def locate(ui, repo, *pats, **opts):
1634 """locate files matching specific patterns
1635 """locate files matching specific patterns
1635
1636
1636 Print all files under Mercurial control whose names match the
1637 Print all files under Mercurial control whose names match the
1637 given patterns.
1638 given patterns.
1638
1639
1639 This command searches the entire repository by default. To search
1640 This command searches the entire repository by default. To search
1640 just the current directory and its subdirectories, use
1641 just the current directory and its subdirectories, use
1641 "--include .".
1642 "--include .".
1642
1643
1643 If no patterns are given to match, this command prints all file
1644 If no patterns are given to match, this command prints all file
1644 names.
1645 names.
1645
1646
1646 If you want to feed the output of this command into the "xargs"
1647 If you want to feed the output of this command into the "xargs"
1647 command, use the "-0" option to both this command and "xargs".
1648 command, use the "-0" option to both this command and "xargs".
1648 This will avoid the problem of "xargs" treating single filenames
1649 This will avoid the problem of "xargs" treating single filenames
1649 that contain white space as multiple filenames.
1650 that contain white space as multiple filenames.
1650 """
1651 """
1651 end = opts['print0'] and '\0' or '\n'
1652 end = opts['print0'] and '\0' or '\n'
1652 rev = opts['rev']
1653 rev = opts['rev']
1653 if rev:
1654 if rev:
1654 node = repo.lookup(rev)
1655 node = repo.lookup(rev)
1655 else:
1656 else:
1656 node = None
1657 node = None
1657
1658
1658 ret = 1
1659 ret = 1
1659 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1660 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1660 badmatch=util.always,
1661 badmatch=util.always,
1661 default='relglob'):
1662 default='relglob'):
1662 if src == 'b':
1663 if src == 'b':
1663 continue
1664 continue
1664 if not node and abs not in repo.dirstate:
1665 if not node and abs not in repo.dirstate:
1665 continue
1666 continue
1666 if opts['fullpath']:
1667 if opts['fullpath']:
1667 ui.write(os.path.join(repo.root, abs), end)
1668 ui.write(os.path.join(repo.root, abs), end)
1668 else:
1669 else:
1669 ui.write(((pats and rel) or abs), end)
1670 ui.write(((pats and rel) or abs), end)
1670 ret = 0
1671 ret = 0
1671
1672
1672 return ret
1673 return ret
1673
1674
1674 def log(ui, repo, *pats, **opts):
1675 def log(ui, repo, *pats, **opts):
1675 """show revision history of entire repository or files
1676 """show revision history of entire repository or files
1676
1677
1677 Print the revision history of the specified files or the entire
1678 Print the revision history of the specified files or the entire
1678 project.
1679 project.
1679
1680
1680 File history is shown without following rename or copy history of
1681 File history is shown without following rename or copy history of
1681 files. Use -f/--follow with a file name to follow history across
1682 files. Use -f/--follow with a file name to follow history across
1682 renames and copies. --follow without a file name will only show
1683 renames and copies. --follow without a file name will only show
1683 ancestors or descendants of the starting revision. --follow-first
1684 ancestors or descendants of the starting revision. --follow-first
1684 only follows the first parent of merge revisions.
1685 only follows the first parent of merge revisions.
1685
1686
1686 If no revision range is specified, the default is tip:0 unless
1687 If no revision range is specified, the default is tip:0 unless
1687 --follow is set, in which case the working directory parent is
1688 --follow is set, in which case the working directory parent is
1688 used as the starting revision.
1689 used as the starting revision.
1689
1690
1690 See 'hg help dates' for a list of formats valid for -d/--date.
1691 See 'hg help dates' for a list of formats valid for -d/--date.
1691
1692
1692 By default this command outputs: changeset id and hash, tags,
1693 By default this command outputs: changeset id and hash, tags,
1693 non-trivial parents, user, date and time, and a summary for each
1694 non-trivial parents, user, date and time, and a summary for each
1694 commit. When the -v/--verbose switch is used, the list of changed
1695 commit. When the -v/--verbose switch is used, the list of changed
1695 files and full commit message is shown.
1696 files and full commit message is shown.
1696
1697
1697 NOTE: log -p may generate unexpected diff output for merge
1698 NOTE: log -p may generate unexpected diff output for merge
1698 changesets, as it will compare the merge changeset against its
1699 changesets, as it will compare the merge changeset against its
1699 first parent only. Also, the files: list will only reflect files
1700 first parent only. Also, the files: list will only reflect files
1700 that are different from BOTH parents.
1701 that are different from BOTH parents.
1701
1702
1702 """
1703 """
1703
1704
1704 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1705 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1705 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1706 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1706
1707
1707 limit = cmdutil.loglimit(opts)
1708 limit = cmdutil.loglimit(opts)
1708 count = 0
1709 count = 0
1709
1710
1710 if opts['copies'] and opts['rev']:
1711 if opts['copies'] and opts['rev']:
1711 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1712 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1712 else:
1713 else:
1713 endrev = repo.changelog.count()
1714 endrev = repo.changelog.count()
1714 rcache = {}
1715 rcache = {}
1715 ncache = {}
1716 ncache = {}
1716 def getrenamed(fn, rev):
1717 def getrenamed(fn, rev):
1717 '''looks up all renames for a file (up to endrev) the first
1718 '''looks up all renames for a file (up to endrev) the first
1718 time the file is given. It indexes on the changerev and only
1719 time the file is given. It indexes on the changerev and only
1719 parses the manifest if linkrev != changerev.
1720 parses the manifest if linkrev != changerev.
1720 Returns rename info for fn at changerev rev.'''
1721 Returns rename info for fn at changerev rev.'''
1721 if fn not in rcache:
1722 if fn not in rcache:
1722 rcache[fn] = {}
1723 rcache[fn] = {}
1723 ncache[fn] = {}
1724 ncache[fn] = {}
1724 fl = repo.file(fn)
1725 fl = repo.file(fn)
1725 for i in xrange(fl.count()):
1726 for i in xrange(fl.count()):
1726 node = fl.node(i)
1727 node = fl.node(i)
1727 lr = fl.linkrev(node)
1728 lr = fl.linkrev(node)
1728 renamed = fl.renamed(node)
1729 renamed = fl.renamed(node)
1729 rcache[fn][lr] = renamed
1730 rcache[fn][lr] = renamed
1730 if renamed:
1731 if renamed:
1731 ncache[fn][node] = renamed
1732 ncache[fn][node] = renamed
1732 if lr >= endrev:
1733 if lr >= endrev:
1733 break
1734 break
1734 if rev in rcache[fn]:
1735 if rev in rcache[fn]:
1735 return rcache[fn][rev]
1736 return rcache[fn][rev]
1736
1737
1737 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1738 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1738 # filectx logic.
1739 # filectx logic.
1739
1740
1740 try:
1741 try:
1741 return repo.changectx(rev).filectx(fn).renamed()
1742 return repo.changectx(rev).filectx(fn).renamed()
1742 except revlog.LookupError:
1743 except revlog.LookupError:
1743 pass
1744 pass
1744 return None
1745 return None
1745
1746
1746 df = False
1747 df = False
1747 if opts["date"]:
1748 if opts["date"]:
1748 df = util.matchdate(opts["date"])
1749 df = util.matchdate(opts["date"])
1749
1750
1750 only_branches = opts['only_branch']
1751 only_branches = opts['only_branch']
1751
1752
1752 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1753 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1753 for st, rev, fns in changeiter:
1754 for st, rev, fns in changeiter:
1754 if st == 'add':
1755 if st == 'add':
1755 changenode = repo.changelog.node(rev)
1756 changenode = repo.changelog.node(rev)
1756 parents = [p for p in repo.changelog.parentrevs(rev)
1757 parents = [p for p in repo.changelog.parentrevs(rev)
1757 if p != nullrev]
1758 if p != nullrev]
1758 if opts['no_merges'] and len(parents) == 2:
1759 if opts['no_merges'] and len(parents) == 2:
1759 continue
1760 continue
1760 if opts['only_merges'] and len(parents) != 2:
1761 if opts['only_merges'] and len(parents) != 2:
1761 continue
1762 continue
1762
1763
1763 if only_branches:
1764 if only_branches:
1764 revbranch = get(rev)[5]['branch']
1765 revbranch = get(rev)[5]['branch']
1765 if revbranch not in only_branches:
1766 if revbranch not in only_branches:
1766 continue
1767 continue
1767
1768
1768 if df:
1769 if df:
1769 changes = get(rev)
1770 changes = get(rev)
1770 if not df(changes[2][0]):
1771 if not df(changes[2][0]):
1771 continue
1772 continue
1772
1773
1773 if opts['keyword']:
1774 if opts['keyword']:
1774 changes = get(rev)
1775 changes = get(rev)
1775 miss = 0
1776 miss = 0
1776 for k in [kw.lower() for kw in opts['keyword']]:
1777 for k in [kw.lower() for kw in opts['keyword']]:
1777 if not (k in changes[1].lower() or
1778 if not (k in changes[1].lower() or
1778 k in changes[4].lower() or
1779 k in changes[4].lower() or
1779 k in " ".join(changes[3]).lower()):
1780 k in " ".join(changes[3]).lower()):
1780 miss = 1
1781 miss = 1
1781 break
1782 break
1782 if miss:
1783 if miss:
1783 continue
1784 continue
1784
1785
1785 copies = []
1786 copies = []
1786 if opts.get('copies') and rev:
1787 if opts.get('copies') and rev:
1787 for fn in get(rev)[3]:
1788 for fn in get(rev)[3]:
1788 rename = getrenamed(fn, rev)
1789 rename = getrenamed(fn, rev)
1789 if rename:
1790 if rename:
1790 copies.append((fn, rename[0]))
1791 copies.append((fn, rename[0]))
1791 displayer.show(rev, changenode, copies=copies)
1792 displayer.show(rev, changenode, copies=copies)
1792 elif st == 'iter':
1793 elif st == 'iter':
1793 if count == limit: break
1794 if count == limit: break
1794 if displayer.flush(rev):
1795 if displayer.flush(rev):
1795 count += 1
1796 count += 1
1796
1797
1797 def manifest(ui, repo, node=None, rev=None):
1798 def manifest(ui, repo, node=None, rev=None):
1798 """output the current or given revision of the project manifest
1799 """output the current or given revision of the project manifest
1799
1800
1800 Print a list of version controlled files for the given revision.
1801 Print a list of version controlled files for the given revision.
1801 If no revision is given, the parent of the working directory is used,
1802 If no revision is given, the parent of the working directory is used,
1802 or tip if no revision is checked out.
1803 or tip if no revision is checked out.
1803
1804
1804 The manifest is the list of files being version controlled. If no revision
1805 The manifest is the list of files being version controlled. If no revision
1805 is given then the first parent of the working directory is used.
1806 is given then the first parent of the working directory is used.
1806
1807
1807 With -v flag, print file permissions, symlink and executable bits. With
1808 With -v flag, print file permissions, symlink and executable bits. With
1808 --debug flag, print file revision hashes.
1809 --debug flag, print file revision hashes.
1809 """
1810 """
1810
1811
1811 if rev and node:
1812 if rev and node:
1812 raise util.Abort(_("please specify just one revision"))
1813 raise util.Abort(_("please specify just one revision"))
1813
1814
1814 if not node:
1815 if not node:
1815 node = rev
1816 node = rev
1816
1817
1817 m = repo.changectx(node).manifest()
1818 m = repo.changectx(node).manifest()
1818 files = m.keys()
1819 files = m.keys()
1819 files.sort()
1820 files.sort()
1820
1821
1821 for f in files:
1822 for f in files:
1822 if ui.debugflag:
1823 if ui.debugflag:
1823 ui.write("%40s " % hex(m[f]))
1824 ui.write("%40s " % hex(m[f]))
1824 if ui.verbose:
1825 if ui.verbose:
1825 type = m.execf(f) and "*" or m.linkf(f) and "@" or " "
1826 type = m.execf(f) and "*" or m.linkf(f) and "@" or " "
1826 perm = m.execf(f) and "755" or "644"
1827 perm = m.execf(f) and "755" or "644"
1827 ui.write("%3s %1s " % (perm, type))
1828 ui.write("%3s %1s " % (perm, type))
1828 ui.write("%s\n" % f)
1829 ui.write("%s\n" % f)
1829
1830
1830 def merge(ui, repo, node=None, force=None, rev=None):
1831 def merge(ui, repo, node=None, force=None, rev=None):
1831 """merge working directory with another revision
1832 """merge working directory with another revision
1832
1833
1833 Merge the contents of the current working directory and the
1834 Merge the contents of the current working directory and the
1834 requested revision. Files that changed between either parent are
1835 requested revision. Files that changed between either parent are
1835 marked as changed for the next commit and a commit must be
1836 marked as changed for the next commit and a commit must be
1836 performed before any further updates are allowed.
1837 performed before any further updates are allowed.
1837
1838
1838 If no revision is specified, the working directory's parent is a
1839 If no revision is specified, the working directory's parent is a
1839 head revision, and the repository contains exactly one other head,
1840 head revision, and the repository contains exactly one other head,
1840 the other head is merged with by default. Otherwise, an explicit
1841 the other head is merged with by default. Otherwise, an explicit
1841 revision to merge with must be provided.
1842 revision to merge with must be provided.
1842 """
1843 """
1843
1844
1844 if rev and node:
1845 if rev and node:
1845 raise util.Abort(_("please specify just one revision"))
1846 raise util.Abort(_("please specify just one revision"))
1846 if not node:
1847 if not node:
1847 node = rev
1848 node = rev
1848
1849
1849 if not node:
1850 if not node:
1850 heads = repo.heads()
1851 heads = repo.heads()
1851 if len(heads) > 2:
1852 if len(heads) > 2:
1852 raise util.Abort(_('repo has %d heads - '
1853 raise util.Abort(_('repo has %d heads - '
1853 'please merge with an explicit rev') %
1854 'please merge with an explicit rev') %
1854 len(heads))
1855 len(heads))
1855 parent = repo.dirstate.parents()[0]
1856 parent = repo.dirstate.parents()[0]
1856 if len(heads) == 1:
1857 if len(heads) == 1:
1857 msg = _('there is nothing to merge')
1858 msg = _('there is nothing to merge')
1858 if parent != repo.lookup(repo.workingctx().branch()):
1859 if parent != repo.lookup(repo.workingctx().branch()):
1859 msg = _('%s - use "hg update" instead') % msg
1860 msg = _('%s - use "hg update" instead') % msg
1860 raise util.Abort(msg)
1861 raise util.Abort(msg)
1861
1862
1862 if parent not in heads:
1863 if parent not in heads:
1863 raise util.Abort(_('working dir not at a head rev - '
1864 raise util.Abort(_('working dir not at a head rev - '
1864 'use "hg update" or merge with an explicit rev'))
1865 'use "hg update" or merge with an explicit rev'))
1865 node = parent == heads[0] and heads[-1] or heads[0]
1866 node = parent == heads[0] and heads[-1] or heads[0]
1866 return hg.merge(repo, node, force=force)
1867 return hg.merge(repo, node, force=force)
1867
1868
1868 def outgoing(ui, repo, dest=None, **opts):
1869 def outgoing(ui, repo, dest=None, **opts):
1869 """show changesets not found in destination
1870 """show changesets not found in destination
1870
1871
1871 Show changesets not found in the specified destination repository or
1872 Show changesets not found in the specified destination repository or
1872 the default push location. These are the changesets that would be pushed
1873 the default push location. These are the changesets that would be pushed
1873 if a push was requested.
1874 if a push was requested.
1874
1875
1875 See pull for valid destination format details.
1876 See pull for valid destination format details.
1876 """
1877 """
1877 limit = cmdutil.loglimit(opts)
1878 limit = cmdutil.loglimit(opts)
1878 dest, revs, checkout = hg.parseurl(
1879 dest, revs, checkout = hg.parseurl(
1879 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1880 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1880 cmdutil.setremoteconfig(ui, opts)
1881 cmdutil.setremoteconfig(ui, opts)
1881 if revs:
1882 if revs:
1882 revs = [repo.lookup(rev) for rev in revs]
1883 revs = [repo.lookup(rev) for rev in revs]
1883
1884
1884 other = hg.repository(ui, dest)
1885 other = hg.repository(ui, dest)
1885 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
1886 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
1886 o = repo.findoutgoing(other, force=opts['force'])
1887 o = repo.findoutgoing(other, force=opts['force'])
1887 if not o:
1888 if not o:
1888 ui.status(_("no changes found\n"))
1889 ui.status(_("no changes found\n"))
1889 return 1
1890 return 1
1890 o = repo.changelog.nodesbetween(o, revs)[0]
1891 o = repo.changelog.nodesbetween(o, revs)[0]
1891 if opts['newest_first']:
1892 if opts['newest_first']:
1892 o.reverse()
1893 o.reverse()
1893 displayer = cmdutil.show_changeset(ui, repo, opts)
1894 displayer = cmdutil.show_changeset(ui, repo, opts)
1894 count = 0
1895 count = 0
1895 for n in o:
1896 for n in o:
1896 if count >= limit:
1897 if count >= limit:
1897 break
1898 break
1898 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1899 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1899 if opts['no_merges'] and len(parents) == 2:
1900 if opts['no_merges'] and len(parents) == 2:
1900 continue
1901 continue
1901 count += 1
1902 count += 1
1902 displayer.show(changenode=n)
1903 displayer.show(changenode=n)
1903
1904
1904 def parents(ui, repo, file_=None, **opts):
1905 def parents(ui, repo, file_=None, **opts):
1905 """show the parents of the working dir or revision
1906 """show the parents of the working dir or revision
1906
1907
1907 Print the working directory's parent revisions. If a
1908 Print the working directory's parent revisions. If a
1908 revision is given via --rev, the parent of that revision
1909 revision is given via --rev, the parent of that revision
1909 will be printed. If a file argument is given, revision in
1910 will be printed. If a file argument is given, revision in
1910 which the file was last changed (before the working directory
1911 which the file was last changed (before the working directory
1911 revision or the argument to --rev if given) is printed.
1912 revision or the argument to --rev if given) is printed.
1912 """
1913 """
1913 rev = opts.get('rev')
1914 rev = opts.get('rev')
1914 if rev:
1915 if rev:
1915 ctx = repo.changectx(rev)
1916 ctx = repo.changectx(rev)
1916 else:
1917 else:
1917 ctx = repo.workingctx()
1918 ctx = repo.workingctx()
1918
1919
1919 if file_:
1920 if file_:
1920 files, match, anypats = cmdutil.matchpats(repo, (file_,), opts)
1921 files, match, anypats = cmdutil.matchpats(repo, (file_,), opts)
1921 if anypats or len(files) != 1:
1922 if anypats or len(files) != 1:
1922 raise util.Abort(_('can only specify an explicit file name'))
1923 raise util.Abort(_('can only specify an explicit file name'))
1923 file_ = files[0]
1924 file_ = files[0]
1924 filenodes = []
1925 filenodes = []
1925 for cp in ctx.parents():
1926 for cp in ctx.parents():
1926 if not cp:
1927 if not cp:
1927 continue
1928 continue
1928 try:
1929 try:
1929 filenodes.append(cp.filenode(file_))
1930 filenodes.append(cp.filenode(file_))
1930 except revlog.LookupError:
1931 except revlog.LookupError:
1931 pass
1932 pass
1932 if not filenodes:
1933 if not filenodes:
1933 raise util.Abort(_("'%s' not found in manifest!") % file_)
1934 raise util.Abort(_("'%s' not found in manifest!") % file_)
1934 fl = repo.file(file_)
1935 fl = repo.file(file_)
1935 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
1936 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
1936 else:
1937 else:
1937 p = [cp.node() for cp in ctx.parents()]
1938 p = [cp.node() for cp in ctx.parents()]
1938
1939
1939 displayer = cmdutil.show_changeset(ui, repo, opts)
1940 displayer = cmdutil.show_changeset(ui, repo, opts)
1940 for n in p:
1941 for n in p:
1941 if n != nullid:
1942 if n != nullid:
1942 displayer.show(changenode=n)
1943 displayer.show(changenode=n)
1943
1944
1944 def paths(ui, repo, search=None):
1945 def paths(ui, repo, search=None):
1945 """show definition of symbolic path names
1946 """show definition of symbolic path names
1946
1947
1947 Show definition of symbolic path name NAME. If no name is given, show
1948 Show definition of symbolic path name NAME. If no name is given, show
1948 definition of available names.
1949 definition of available names.
1949
1950
1950 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1951 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1951 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1952 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1952 """
1953 """
1953 if search:
1954 if search:
1954 for name, path in ui.configitems("paths"):
1955 for name, path in ui.configitems("paths"):
1955 if name == search:
1956 if name == search:
1956 ui.write("%s\n" % path)
1957 ui.write("%s\n" % path)
1957 return
1958 return
1958 ui.warn(_("not found!\n"))
1959 ui.warn(_("not found!\n"))
1959 return 1
1960 return 1
1960 else:
1961 else:
1961 for name, path in ui.configitems("paths"):
1962 for name, path in ui.configitems("paths"):
1962 ui.write("%s = %s\n" % (name, path))
1963 ui.write("%s = %s\n" % (name, path))
1963
1964
1964 def postincoming(ui, repo, modheads, optupdate, checkout):
1965 def postincoming(ui, repo, modheads, optupdate, checkout):
1965 if modheads == 0:
1966 if modheads == 0:
1966 return
1967 return
1967 if optupdate:
1968 if optupdate:
1968 if modheads <= 1 or checkout:
1969 if modheads <= 1 or checkout:
1969 return hg.update(repo, checkout)
1970 return hg.update(repo, checkout)
1970 else:
1971 else:
1971 ui.status(_("not updating, since new heads added\n"))
1972 ui.status(_("not updating, since new heads added\n"))
1972 if modheads > 1:
1973 if modheads > 1:
1973 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
1974 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
1974 else:
1975 else:
1975 ui.status(_("(run 'hg update' to get a working copy)\n"))
1976 ui.status(_("(run 'hg update' to get a working copy)\n"))
1976
1977
1977 def pull(ui, repo, source="default", **opts):
1978 def pull(ui, repo, source="default", **opts):
1978 """pull changes from the specified source
1979 """pull changes from the specified source
1979
1980
1980 Pull changes from a remote repository to a local one.
1981 Pull changes from a remote repository to a local one.
1981
1982
1982 This finds all changes from the repository at the specified path
1983 This finds all changes from the repository at the specified path
1983 or URL and adds them to the local repository. By default, this
1984 or URL and adds them to the local repository. By default, this
1984 does not update the copy of the project in the working directory.
1985 does not update the copy of the project in the working directory.
1985
1986
1986 Valid URLs are of the form:
1987 Valid URLs are of the form:
1987
1988
1988 local/filesystem/path (or file://local/filesystem/path)
1989 local/filesystem/path (or file://local/filesystem/path)
1989 http://[user@]host[:port]/[path]
1990 http://[user@]host[:port]/[path]
1990 https://[user@]host[:port]/[path]
1991 https://[user@]host[:port]/[path]
1991 ssh://[user@]host[:port]/[path]
1992 ssh://[user@]host[:port]/[path]
1992 static-http://host[:port]/[path]
1993 static-http://host[:port]/[path]
1993
1994
1994 Paths in the local filesystem can either point to Mercurial
1995 Paths in the local filesystem can either point to Mercurial
1995 repositories or to bundle files (as created by 'hg bundle' or
1996 repositories or to bundle files (as created by 'hg bundle' or
1996 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
1997 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
1997 allows access to a Mercurial repository where you simply use a web
1998 allows access to a Mercurial repository where you simply use a web
1998 server to publish the .hg directory as static content.
1999 server to publish the .hg directory as static content.
1999
2000
2000 An optional identifier after # indicates a particular branch, tag,
2001 An optional identifier after # indicates a particular branch, tag,
2001 or changeset to pull.
2002 or changeset to pull.
2002
2003
2003 Some notes about using SSH with Mercurial:
2004 Some notes about using SSH with Mercurial:
2004 - SSH requires an accessible shell account on the destination machine
2005 - SSH requires an accessible shell account on the destination machine
2005 and a copy of hg in the remote path or specified with as remotecmd.
2006 and a copy of hg in the remote path or specified with as remotecmd.
2006 - path is relative to the remote user's home directory by default.
2007 - path is relative to the remote user's home directory by default.
2007 Use an extra slash at the start of a path to specify an absolute path:
2008 Use an extra slash at the start of a path to specify an absolute path:
2008 ssh://example.com//tmp/repository
2009 ssh://example.com//tmp/repository
2009 - Mercurial doesn't use its own compression via SSH; the right thing
2010 - Mercurial doesn't use its own compression via SSH; the right thing
2010 to do is to configure it in your ~/.ssh/config, e.g.:
2011 to do is to configure it in your ~/.ssh/config, e.g.:
2011 Host *.mylocalnetwork.example.com
2012 Host *.mylocalnetwork.example.com
2012 Compression no
2013 Compression no
2013 Host *
2014 Host *
2014 Compression yes
2015 Compression yes
2015 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2016 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2016 with the --ssh command line option.
2017 with the --ssh command line option.
2017 """
2018 """
2018 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2019 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2019 cmdutil.setremoteconfig(ui, opts)
2020 cmdutil.setremoteconfig(ui, opts)
2020
2021
2021 other = hg.repository(ui, source)
2022 other = hg.repository(ui, source)
2022 ui.status(_('pulling from %s\n') % util.hidepassword(source))
2023 ui.status(_('pulling from %s\n') % util.hidepassword(source))
2023 if revs:
2024 if revs:
2024 try:
2025 try:
2025 revs = [other.lookup(rev) for rev in revs]
2026 revs = [other.lookup(rev) for rev in revs]
2026 except repo.NoCapability:
2027 except repo.NoCapability:
2027 error = _("Other repository doesn't support revision lookup, "
2028 error = _("Other repository doesn't support revision lookup, "
2028 "so a rev cannot be specified.")
2029 "so a rev cannot be specified.")
2029 raise util.Abort(error)
2030 raise util.Abort(error)
2030
2031
2031 modheads = repo.pull(other, heads=revs, force=opts['force'])
2032 modheads = repo.pull(other, heads=revs, force=opts['force'])
2032 return postincoming(ui, repo, modheads, opts['update'], checkout)
2033 return postincoming(ui, repo, modheads, opts['update'], checkout)
2033
2034
2034 def push(ui, repo, dest=None, **opts):
2035 def push(ui, repo, dest=None, **opts):
2035 """push changes to the specified destination
2036 """push changes to the specified destination
2036
2037
2037 Push changes from the local repository to the given destination.
2038 Push changes from the local repository to the given destination.
2038
2039
2039 This is the symmetrical operation for pull. It helps to move
2040 This is the symmetrical operation for pull. It helps to move
2040 changes from the current repository to a different one. If the
2041 changes from the current repository to a different one. If the
2041 destination is local this is identical to a pull in that directory
2042 destination is local this is identical to a pull in that directory
2042 from the current one.
2043 from the current one.
2043
2044
2044 By default, push will refuse to run if it detects the result would
2045 By default, push will refuse to run if it detects the result would
2045 increase the number of remote heads. This generally indicates the
2046 increase the number of remote heads. This generally indicates the
2046 the client has forgotten to sync and merge before pushing.
2047 the client has forgotten to sync and merge before pushing.
2047
2048
2048 Valid URLs are of the form:
2049 Valid URLs are of the form:
2049
2050
2050 local/filesystem/path (or file://local/filesystem/path)
2051 local/filesystem/path (or file://local/filesystem/path)
2051 ssh://[user@]host[:port]/[path]
2052 ssh://[user@]host[:port]/[path]
2052 http://[user@]host[:port]/[path]
2053 http://[user@]host[:port]/[path]
2053 https://[user@]host[:port]/[path]
2054 https://[user@]host[:port]/[path]
2054
2055
2055 An optional identifier after # indicates a particular branch, tag,
2056 An optional identifier after # indicates a particular branch, tag,
2056 or changeset to push.
2057 or changeset to push.
2057
2058
2058 Look at the help text for the pull command for important details
2059 Look at the help text for the pull command for important details
2059 about ssh:// URLs.
2060 about ssh:// URLs.
2060
2061
2061 Pushing to http:// and https:// URLs is only possible, if this
2062 Pushing to http:// and https:// URLs is only possible, if this
2062 feature is explicitly enabled on the remote Mercurial server.
2063 feature is explicitly enabled on the remote Mercurial server.
2063 """
2064 """
2064 dest, revs, checkout = hg.parseurl(
2065 dest, revs, checkout = hg.parseurl(
2065 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2066 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2066 cmdutil.setremoteconfig(ui, opts)
2067 cmdutil.setremoteconfig(ui, opts)
2067
2068
2068 other = hg.repository(ui, dest)
2069 other = hg.repository(ui, dest)
2069 ui.status('pushing to %s\n' % util.hidepassword(dest))
2070 ui.status('pushing to %s\n' % util.hidepassword(dest))
2070 if revs:
2071 if revs:
2071 revs = [repo.lookup(rev) for rev in revs]
2072 revs = [repo.lookup(rev) for rev in revs]
2072 r = repo.push(other, opts['force'], revs=revs)
2073 r = repo.push(other, opts['force'], revs=revs)
2073 return r == 0
2074 return r == 0
2074
2075
2075 def rawcommit(ui, repo, *pats, **opts):
2076 def rawcommit(ui, repo, *pats, **opts):
2076 """raw commit interface (DEPRECATED)
2077 """raw commit interface (DEPRECATED)
2077
2078
2078 (DEPRECATED)
2079 (DEPRECATED)
2079 Lowlevel commit, for use in helper scripts.
2080 Lowlevel commit, for use in helper scripts.
2080
2081
2081 This command is not intended to be used by normal users, as it is
2082 This command is not intended to be used by normal users, as it is
2082 primarily useful for importing from other SCMs.
2083 primarily useful for importing from other SCMs.
2083
2084
2084 This command is now deprecated and will be removed in a future
2085 This command is now deprecated and will be removed in a future
2085 release, please use debugsetparents and commit instead.
2086 release, please use debugsetparents and commit instead.
2086 """
2087 """
2087
2088
2088 ui.warn(_("(the rawcommit command is deprecated)\n"))
2089 ui.warn(_("(the rawcommit command is deprecated)\n"))
2089
2090
2090 message = cmdutil.logmessage(opts)
2091 message = cmdutil.logmessage(opts)
2091
2092
2092 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2093 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2093 if opts['files']:
2094 if opts['files']:
2094 files += open(opts['files']).read().splitlines()
2095 files += open(opts['files']).read().splitlines()
2095
2096
2096 parents = [repo.lookup(p) for p in opts['parent']]
2097 parents = [repo.lookup(p) for p in opts['parent']]
2097
2098
2098 try:
2099 try:
2099 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2100 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2100 except ValueError, inst:
2101 except ValueError, inst:
2101 raise util.Abort(str(inst))
2102 raise util.Abort(str(inst))
2102
2103
2103 def recover(ui, repo):
2104 def recover(ui, repo):
2104 """roll back an interrupted transaction
2105 """roll back an interrupted transaction
2105
2106
2106 Recover from an interrupted commit or pull.
2107 Recover from an interrupted commit or pull.
2107
2108
2108 This command tries to fix the repository status after an interrupted
2109 This command tries to fix the repository status after an interrupted
2109 operation. It should only be necessary when Mercurial suggests it.
2110 operation. It should only be necessary when Mercurial suggests it.
2110 """
2111 """
2111 if repo.recover():
2112 if repo.recover():
2112 return hg.verify(repo)
2113 return hg.verify(repo)
2113 return 1
2114 return 1
2114
2115
2115 def remove(ui, repo, *pats, **opts):
2116 def remove(ui, repo, *pats, **opts):
2116 """remove the specified files on the next commit
2117 """remove the specified files on the next commit
2117
2118
2118 Schedule the indicated files for removal from the repository.
2119 Schedule the indicated files for removal from the repository.
2119
2120
2120 This only removes files from the current branch, not from the
2121 This only removes files from the current branch, not from the
2121 entire project history. If the files still exist in the working
2122 entire project history. If the files still exist in the working
2122 directory, they will be deleted from it. If invoked with --after,
2123 directory, they will be deleted from it. If invoked with --after,
2123 files are marked as removed, but not actually unlinked unless --force
2124 files are marked as removed, but not actually unlinked unless --force
2124 is also given. Without exact file names, --after will only mark
2125 is also given. Without exact file names, --after will only mark
2125 files as removed if they are no longer in the working directory.
2126 files as removed if they are no longer in the working directory.
2126
2127
2127 This command schedules the files to be removed at the next commit.
2128 This command schedules the files to be removed at the next commit.
2128 To undo a remove before that, see hg revert.
2129 To undo a remove before that, see hg revert.
2129
2130
2130 Modified files and added files are not removed by default. To
2131 Modified files and added files are not removed by default. To
2131 remove them, use the -f/--force option.
2132 remove them, use the -f/--force option.
2132 """
2133 """
2133 if not opts['after'] and not pats:
2134 if not opts['after'] and not pats:
2134 raise util.Abort(_('no files specified'))
2135 raise util.Abort(_('no files specified'))
2135 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2136 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2136 exact = dict.fromkeys(files)
2137 exact = dict.fromkeys(files)
2137 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2138 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2138 modified, added, removed, deleted, unknown = mardu
2139 modified, added, removed, deleted, unknown = mardu
2139 remove, forget = [], []
2140 remove, forget = [], []
2140 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2141 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2141 reason = None
2142 reason = None
2142 if abs in modified and not opts['force']:
2143 if abs in modified and not opts['force']:
2143 reason = _('is modified (use -f to force removal)')
2144 reason = _('is modified (use -f to force removal)')
2144 elif abs in added:
2145 elif abs in added:
2145 if opts['force']:
2146 if opts['force']:
2146 forget.append(abs)
2147 forget.append(abs)
2147 continue
2148 continue
2148 reason = _('has been marked for add (use -f to force removal)')
2149 reason = _('has been marked for add (use -f to force removal)')
2149 exact = 1 # force the message
2150 exact = 1 # force the message
2150 elif abs not in repo.dirstate:
2151 elif abs not in repo.dirstate:
2151 reason = _('is not managed')
2152 reason = _('is not managed')
2152 elif opts['after'] and not exact and abs not in deleted:
2153 elif opts['after'] and not exact and abs not in deleted:
2153 continue
2154 continue
2154 elif abs in removed:
2155 elif abs in removed:
2155 continue
2156 continue
2156 if reason:
2157 if reason:
2157 if exact:
2158 if exact:
2158 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2159 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2159 else:
2160 else:
2160 if ui.verbose or not exact:
2161 if ui.verbose or not exact:
2161 ui.status(_('removing %s\n') % rel)
2162 ui.status(_('removing %s\n') % rel)
2162 remove.append(abs)
2163 remove.append(abs)
2163 repo.forget(forget)
2164 repo.forget(forget)
2164 repo.remove(remove, unlink=opts['force'] or not opts['after'])
2165 repo.remove(remove, unlink=opts['force'] or not opts['after'])
2165
2166
2166 def rename(ui, repo, *pats, **opts):
2167 def rename(ui, repo, *pats, **opts):
2167 """rename files; equivalent of copy + remove
2168 """rename files; equivalent of copy + remove
2168
2169
2169 Mark dest as copies of sources; mark sources for deletion. If
2170 Mark dest as copies of sources; mark sources for deletion. If
2170 dest is a directory, copies are put in that directory. If dest is
2171 dest is a directory, copies are put in that directory. If dest is
2171 a file, there can only be one source.
2172 a file, there can only be one source.
2172
2173
2173 By default, this command copies the contents of files as they
2174 By default, this command copies the contents of files as they
2174 stand in the working directory. If invoked with --after, the
2175 stand in the working directory. If invoked with --after, the
2175 operation is recorded, but no copying is performed.
2176 operation is recorded, but no copying is performed.
2176
2177
2177 This command takes effect in the next commit. To undo a rename
2178 This command takes effect in the next commit. To undo a rename
2178 before that, see hg revert.
2179 before that, see hg revert.
2179 """
2180 """
2180 wlock = repo.wlock(False)
2181 wlock = repo.wlock(False)
2181 try:
2182 try:
2182 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2183 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2183 finally:
2184 finally:
2184 del wlock
2185 del wlock
2185
2186
2186 def revert(ui, repo, *pats, **opts):
2187 def revert(ui, repo, *pats, **opts):
2187 """restore individual files or dirs to an earlier state
2188 """restore individual files or dirs to an earlier state
2188
2189
2189 (use update -r to check out earlier revisions, revert does not
2190 (use update -r to check out earlier revisions, revert does not
2190 change the working dir parents)
2191 change the working dir parents)
2191
2192
2192 With no revision specified, revert the named files or directories
2193 With no revision specified, revert the named files or directories
2193 to the contents they had in the parent of the working directory.
2194 to the contents they had in the parent of the working directory.
2194 This restores the contents of the affected files to an unmodified
2195 This restores the contents of the affected files to an unmodified
2195 state and unschedules adds, removes, copies, and renames. If the
2196 state and unschedules adds, removes, copies, and renames. If the
2196 working directory has two parents, you must explicitly specify the
2197 working directory has two parents, you must explicitly specify the
2197 revision to revert to.
2198 revision to revert to.
2198
2199
2199 Using the -r option, revert the given files or directories to their
2200 Using the -r option, revert the given files or directories to their
2200 contents as of a specific revision. This can be helpful to "roll
2201 contents as of a specific revision. This can be helpful to "roll
2201 back" some or all of an earlier change.
2202 back" some or all of an earlier change.
2202 See 'hg help dates' for a list of formats valid for -d/--date.
2203 See 'hg help dates' for a list of formats valid for -d/--date.
2203
2204
2204 Revert modifies the working directory. It does not commit any
2205 Revert modifies the working directory. It does not commit any
2205 changes, or change the parent of the working directory. If you
2206 changes, or change the parent of the working directory. If you
2206 revert to a revision other than the parent of the working
2207 revert to a revision other than the parent of the working
2207 directory, the reverted files will thus appear modified
2208 directory, the reverted files will thus appear modified
2208 afterwards.
2209 afterwards.
2209
2210
2210 If a file has been deleted, it is restored. If the executable
2211 If a file has been deleted, it is restored. If the executable
2211 mode of a file was changed, it is reset.
2212 mode of a file was changed, it is reset.
2212
2213
2213 If names are given, all files matching the names are reverted.
2214 If names are given, all files matching the names are reverted.
2214 If no arguments are given, no files are reverted.
2215 If no arguments are given, no files are reverted.
2215
2216
2216 Modified files are saved with a .orig suffix before reverting.
2217 Modified files are saved with a .orig suffix before reverting.
2217 To disable these backups, use --no-backup.
2218 To disable these backups, use --no-backup.
2218 """
2219 """
2219
2220
2220 if opts["date"]:
2221 if opts["date"]:
2221 if opts["rev"]:
2222 if opts["rev"]:
2222 raise util.Abort(_("you can't specify a revision and a date"))
2223 raise util.Abort(_("you can't specify a revision and a date"))
2223 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2224 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2224
2225
2225 if not pats and not opts['all']:
2226 if not pats and not opts['all']:
2226 raise util.Abort(_('no files or directories specified; '
2227 raise util.Abort(_('no files or directories specified; '
2227 'use --all to revert the whole repo'))
2228 'use --all to revert the whole repo'))
2228
2229
2229 parent, p2 = repo.dirstate.parents()
2230 parent, p2 = repo.dirstate.parents()
2230 if not opts['rev'] and p2 != nullid:
2231 if not opts['rev'] and p2 != nullid:
2231 raise util.Abort(_('uncommitted merge - please provide a '
2232 raise util.Abort(_('uncommitted merge - please provide a '
2232 'specific revision'))
2233 'specific revision'))
2233 ctx = repo.changectx(opts['rev'])
2234 ctx = repo.changectx(opts['rev'])
2234 node = ctx.node()
2235 node = ctx.node()
2235 mf = ctx.manifest()
2236 mf = ctx.manifest()
2236 if node == parent:
2237 if node == parent:
2237 pmf = mf
2238 pmf = mf
2238 else:
2239 else:
2239 pmf = None
2240 pmf = None
2240
2241
2241 # need all matching names in dirstate and manifest of target rev,
2242 # need all matching names in dirstate and manifest of target rev,
2242 # so have to walk both. do not print errors if files exist in one
2243 # so have to walk both. do not print errors if files exist in one
2243 # but not other.
2244 # but not other.
2244
2245
2245 names = {}
2246 names = {}
2246
2247
2247 wlock = repo.wlock()
2248 wlock = repo.wlock()
2248 try:
2249 try:
2249 # walk dirstate.
2250 # walk dirstate.
2250 files = []
2251 files = []
2251 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2252 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2252 badmatch=mf.has_key):
2253 badmatch=mf.has_key):
2253 names[abs] = (rel, exact)
2254 names[abs] = (rel, exact)
2254 if src != 'b':
2255 if src != 'b':
2255 files.append(abs)
2256 files.append(abs)
2256
2257
2257 # walk target manifest.
2258 # walk target manifest.
2258
2259
2259 def badmatch(path):
2260 def badmatch(path):
2260 if path in names:
2261 if path in names:
2261 return True
2262 return True
2262 path_ = path + '/'
2263 path_ = path + '/'
2263 for f in names:
2264 for f in names:
2264 if f.startswith(path_):
2265 if f.startswith(path_):
2265 return True
2266 return True
2266 return False
2267 return False
2267
2268
2268 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2269 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2269 badmatch=badmatch):
2270 badmatch=badmatch):
2270 if abs in names or src == 'b':
2271 if abs in names or src == 'b':
2271 continue
2272 continue
2272 names[abs] = (rel, exact)
2273 names[abs] = (rel, exact)
2273
2274
2274 changes = repo.status(files=files, match=names.has_key)[:4]
2275 changes = repo.status(files=files, match=names.has_key)[:4]
2275 modified, added, removed, deleted = map(dict.fromkeys, changes)
2276 modified, added, removed, deleted = map(dict.fromkeys, changes)
2276
2277
2277 # if f is a rename, also revert the source
2278 # if f is a rename, also revert the source
2278 cwd = repo.getcwd()
2279 cwd = repo.getcwd()
2279 for f in added:
2280 for f in added:
2280 src = repo.dirstate.copied(f)
2281 src = repo.dirstate.copied(f)
2281 if src and src not in names and repo.dirstate[src] == 'r':
2282 if src and src not in names and repo.dirstate[src] == 'r':
2282 removed[src] = None
2283 removed[src] = None
2283 names[src] = (repo.pathto(src, cwd), True)
2284 names[src] = (repo.pathto(src, cwd), True)
2284
2285
2285 def removeforget(abs):
2286 def removeforget(abs):
2286 if repo.dirstate[abs] == 'a':
2287 if repo.dirstate[abs] == 'a':
2287 return _('forgetting %s\n')
2288 return _('forgetting %s\n')
2288 return _('removing %s\n')
2289 return _('removing %s\n')
2289
2290
2290 revert = ([], _('reverting %s\n'))
2291 revert = ([], _('reverting %s\n'))
2291 add = ([], _('adding %s\n'))
2292 add = ([], _('adding %s\n'))
2292 remove = ([], removeforget)
2293 remove = ([], removeforget)
2293 undelete = ([], _('undeleting %s\n'))
2294 undelete = ([], _('undeleting %s\n'))
2294
2295
2295 disptable = (
2296 disptable = (
2296 # dispatch table:
2297 # dispatch table:
2297 # file state
2298 # file state
2298 # action if in target manifest
2299 # action if in target manifest
2299 # action if not in target manifest
2300 # action if not in target manifest
2300 # make backup if in target manifest
2301 # make backup if in target manifest
2301 # make backup if not in target manifest
2302 # make backup if not in target manifest
2302 (modified, revert, remove, True, True),
2303 (modified, revert, remove, True, True),
2303 (added, revert, remove, True, False),
2304 (added, revert, remove, True, False),
2304 (removed, undelete, None, False, False),
2305 (removed, undelete, None, False, False),
2305 (deleted, revert, remove, False, False),
2306 (deleted, revert, remove, False, False),
2306 )
2307 )
2307
2308
2308 entries = names.items()
2309 entries = names.items()
2309 entries.sort()
2310 entries.sort()
2310
2311
2311 for abs, (rel, exact) in entries:
2312 for abs, (rel, exact) in entries:
2312 mfentry = mf.get(abs)
2313 mfentry = mf.get(abs)
2313 target = repo.wjoin(abs)
2314 target = repo.wjoin(abs)
2314 def handle(xlist, dobackup):
2315 def handle(xlist, dobackup):
2315 xlist[0].append(abs)
2316 xlist[0].append(abs)
2316 if dobackup and not opts['no_backup'] and util.lexists(target):
2317 if dobackup and not opts['no_backup'] and util.lexists(target):
2317 bakname = "%s.orig" % rel
2318 bakname = "%s.orig" % rel
2318 ui.note(_('saving current version of %s as %s\n') %
2319 ui.note(_('saving current version of %s as %s\n') %
2319 (rel, bakname))
2320 (rel, bakname))
2320 if not opts.get('dry_run'):
2321 if not opts.get('dry_run'):
2321 util.copyfile(target, bakname)
2322 util.copyfile(target, bakname)
2322 if ui.verbose or not exact:
2323 if ui.verbose or not exact:
2323 msg = xlist[1]
2324 msg = xlist[1]
2324 if not isinstance(msg, basestring):
2325 if not isinstance(msg, basestring):
2325 msg = msg(abs)
2326 msg = msg(abs)
2326 ui.status(msg % rel)
2327 ui.status(msg % rel)
2327 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2328 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2328 if abs not in table: continue
2329 if abs not in table: continue
2329 # file has changed in dirstate
2330 # file has changed in dirstate
2330 if mfentry:
2331 if mfentry:
2331 handle(hitlist, backuphit)
2332 handle(hitlist, backuphit)
2332 elif misslist is not None:
2333 elif misslist is not None:
2333 handle(misslist, backupmiss)
2334 handle(misslist, backupmiss)
2334 break
2335 break
2335 else:
2336 else:
2336 if abs not in repo.dirstate:
2337 if abs not in repo.dirstate:
2337 if mfentry:
2338 if mfentry:
2338 handle(add, True)
2339 handle(add, True)
2339 elif exact:
2340 elif exact:
2340 ui.warn(_('file not managed: %s\n') % rel)
2341 ui.warn(_('file not managed: %s\n') % rel)
2341 continue
2342 continue
2342 # file has not changed in dirstate
2343 # file has not changed in dirstate
2343 if node == parent:
2344 if node == parent:
2344 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2345 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2345 continue
2346 continue
2346 if pmf is None:
2347 if pmf is None:
2347 # only need parent manifest in this unlikely case,
2348 # only need parent manifest in this unlikely case,
2348 # so do not read by default
2349 # so do not read by default
2349 pmf = repo.changectx(parent).manifest()
2350 pmf = repo.changectx(parent).manifest()
2350 if abs in pmf:
2351 if abs in pmf:
2351 if mfentry:
2352 if mfentry:
2352 # if version of file is same in parent and target
2353 # if version of file is same in parent and target
2353 # manifests, do nothing
2354 # manifests, do nothing
2354 if (pmf[abs] != mfentry or
2355 if (pmf[abs] != mfentry or
2355 pmf.flags(abs) != mf.flags(abs)):
2356 pmf.flags(abs) != mf.flags(abs)):
2356 handle(revert, False)
2357 handle(revert, False)
2357 else:
2358 else:
2358 handle(remove, False)
2359 handle(remove, False)
2359
2360
2360 if not opts.get('dry_run'):
2361 if not opts.get('dry_run'):
2361 def checkout(f):
2362 def checkout(f):
2362 fc = ctx[f]
2363 fc = ctx[f]
2363 repo.wwrite(f, fc.data(), fc.fileflags())
2364 repo.wwrite(f, fc.data(), fc.fileflags())
2364
2365
2365 audit_path = util.path_auditor(repo.root)
2366 audit_path = util.path_auditor(repo.root)
2366 for f in remove[0]:
2367 for f in remove[0]:
2367 if repo.dirstate[f] == 'a':
2368 if repo.dirstate[f] == 'a':
2368 repo.dirstate.forget(f)
2369 repo.dirstate.forget(f)
2369 continue
2370 continue
2370 audit_path(f)
2371 audit_path(f)
2371 try:
2372 try:
2372 util.unlink(repo.wjoin(f))
2373 util.unlink(repo.wjoin(f))
2373 except OSError:
2374 except OSError:
2374 pass
2375 pass
2375 repo.dirstate.remove(f)
2376 repo.dirstate.remove(f)
2376
2377
2377 for f in revert[0]:
2378 for f in revert[0]:
2378 checkout(f)
2379 checkout(f)
2379
2380
2380 for f in add[0]:
2381 for f in add[0]:
2381 checkout(f)
2382 checkout(f)
2382 repo.dirstate.add(f)
2383 repo.dirstate.add(f)
2383
2384
2384 normal = repo.dirstate.normallookup
2385 normal = repo.dirstate.normallookup
2385 if node == parent and p2 == nullid:
2386 if node == parent and p2 == nullid:
2386 normal = repo.dirstate.normal
2387 normal = repo.dirstate.normal
2387 for f in undelete[0]:
2388 for f in undelete[0]:
2388 checkout(f)
2389 checkout(f)
2389 normal(f)
2390 normal(f)
2390
2391
2391 finally:
2392 finally:
2392 del wlock
2393 del wlock
2393
2394
2394 def rollback(ui, repo):
2395 def rollback(ui, repo):
2395 """roll back the last transaction
2396 """roll back the last transaction
2396
2397
2397 This command should be used with care. There is only one level of
2398 This command should be used with care. There is only one level of
2398 rollback, and there is no way to undo a rollback. It will also
2399 rollback, and there is no way to undo a rollback. It will also
2399 restore the dirstate at the time of the last transaction, losing
2400 restore the dirstate at the time of the last transaction, losing
2400 any dirstate changes since that time.
2401 any dirstate changes since that time.
2401
2402
2402 Transactions are used to encapsulate the effects of all commands
2403 Transactions are used to encapsulate the effects of all commands
2403 that create new changesets or propagate existing changesets into a
2404 that create new changesets or propagate existing changesets into a
2404 repository. For example, the following commands are transactional,
2405 repository. For example, the following commands are transactional,
2405 and their effects can be rolled back:
2406 and their effects can be rolled back:
2406
2407
2407 commit
2408 commit
2408 import
2409 import
2409 pull
2410 pull
2410 push (with this repository as destination)
2411 push (with this repository as destination)
2411 unbundle
2412 unbundle
2412
2413
2413 This command is not intended for use on public repositories. Once
2414 This command is not intended for use on public repositories. Once
2414 changes are visible for pull by other users, rolling a transaction
2415 changes are visible for pull by other users, rolling a transaction
2415 back locally is ineffective (someone else may already have pulled
2416 back locally is ineffective (someone else may already have pulled
2416 the changes). Furthermore, a race is possible with readers of the
2417 the changes). Furthermore, a race is possible with readers of the
2417 repository; for example an in-progress pull from the repository
2418 repository; for example an in-progress pull from the repository
2418 may fail if a rollback is performed.
2419 may fail if a rollback is performed.
2419 """
2420 """
2420 repo.rollback()
2421 repo.rollback()
2421
2422
2422 def root(ui, repo):
2423 def root(ui, repo):
2423 """print the root (top) of the current working dir
2424 """print the root (top) of the current working dir
2424
2425
2425 Print the root directory of the current repository.
2426 Print the root directory of the current repository.
2426 """
2427 """
2427 ui.write(repo.root + "\n")
2428 ui.write(repo.root + "\n")
2428
2429
2429 def serve(ui, repo, **opts):
2430 def serve(ui, repo, **opts):
2430 """export the repository via HTTP
2431 """export the repository via HTTP
2431
2432
2432 Start a local HTTP repository browser and pull server.
2433 Start a local HTTP repository browser and pull server.
2433
2434
2434 By default, the server logs accesses to stdout and errors to
2435 By default, the server logs accesses to stdout and errors to
2435 stderr. Use the "-A" and "-E" options to log to files.
2436 stderr. Use the "-A" and "-E" options to log to files.
2436 """
2437 """
2437
2438
2438 if opts["stdio"]:
2439 if opts["stdio"]:
2439 if repo is None:
2440 if repo is None:
2440 raise hg.RepoError(_("There is no Mercurial repository here"
2441 raise RepoError(_("There is no Mercurial repository here"
2441 " (.hg not found)"))
2442 " (.hg not found)"))
2442 s = sshserver.sshserver(ui, repo)
2443 s = sshserver.sshserver(ui, repo)
2443 s.serve_forever()
2444 s.serve_forever()
2444
2445
2445 parentui = ui.parentui or ui
2446 parentui = ui.parentui or ui
2446 optlist = ("name templates style address port prefix ipv6"
2447 optlist = ("name templates style address port prefix ipv6"
2447 " accesslog errorlog webdir_conf certificate")
2448 " accesslog errorlog webdir_conf certificate")
2448 for o in optlist.split():
2449 for o in optlist.split():
2449 if opts[o]:
2450 if opts[o]:
2450 parentui.setconfig("web", o, str(opts[o]))
2451 parentui.setconfig("web", o, str(opts[o]))
2451 if (repo is not None) and (repo.ui != parentui):
2452 if (repo is not None) and (repo.ui != parentui):
2452 repo.ui.setconfig("web", o, str(opts[o]))
2453 repo.ui.setconfig("web", o, str(opts[o]))
2453
2454
2454 if repo is None and not ui.config("web", "webdir_conf"):
2455 if repo is None and not ui.config("web", "webdir_conf"):
2455 raise hg.RepoError(_("There is no Mercurial repository here"
2456 raise RepoError(_("There is no Mercurial repository here"
2456 " (.hg not found)"))
2457 " (.hg not found)"))
2457
2458
2458 class service:
2459 class service:
2459 def init(self):
2460 def init(self):
2460 util.set_signal_handler()
2461 util.set_signal_handler()
2461 try:
2462 try:
2462 self.httpd = hgweb.server.create_server(parentui, repo)
2463 self.httpd = hgweb.server.create_server(parentui, repo)
2463 except socket.error, inst:
2464 except socket.error, inst:
2464 raise util.Abort(_('cannot start server: ') + inst.args[1])
2465 raise util.Abort(_('cannot start server: ') + inst.args[1])
2465
2466
2466 if not ui.verbose: return
2467 if not ui.verbose: return
2467
2468
2468 if self.httpd.prefix:
2469 if self.httpd.prefix:
2469 prefix = self.httpd.prefix.strip('/') + '/'
2470 prefix = self.httpd.prefix.strip('/') + '/'
2470 else:
2471 else:
2471 prefix = ''
2472 prefix = ''
2472
2473
2473 if self.httpd.port != 80:
2474 if self.httpd.port != 80:
2474 ui.status(_('listening at http://%s:%d/%s\n') %
2475 ui.status(_('listening at http://%s:%d/%s\n') %
2475 (self.httpd.addr, self.httpd.port, prefix))
2476 (self.httpd.addr, self.httpd.port, prefix))
2476 else:
2477 else:
2477 ui.status(_('listening at http://%s/%s\n') %
2478 ui.status(_('listening at http://%s/%s\n') %
2478 (self.httpd.addr, prefix))
2479 (self.httpd.addr, prefix))
2479
2480
2480 def run(self):
2481 def run(self):
2481 self.httpd.serve_forever()
2482 self.httpd.serve_forever()
2482
2483
2483 service = service()
2484 service = service()
2484
2485
2485 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2486 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2486
2487
2487 def status(ui, repo, *pats, **opts):
2488 def status(ui, repo, *pats, **opts):
2488 """show changed files in the working directory
2489 """show changed files in the working directory
2489
2490
2490 Show status of files in the repository. If names are given, only
2491 Show status of files in the repository. If names are given, only
2491 files that match are shown. Files that are clean or ignored or
2492 files that match are shown. Files that are clean or ignored or
2492 source of a copy/move operation, are not listed unless -c (clean),
2493 source of a copy/move operation, are not listed unless -c (clean),
2493 -i (ignored), -C (copies) or -A is given. Unless options described
2494 -i (ignored), -C (copies) or -A is given. Unless options described
2494 with "show only ..." are given, the options -mardu are used.
2495 with "show only ..." are given, the options -mardu are used.
2495
2496
2496 Option -q/--quiet hides untracked (unknown and ignored) files
2497 Option -q/--quiet hides untracked (unknown and ignored) files
2497 unless explicitly requested with -u/--unknown or -i/-ignored.
2498 unless explicitly requested with -u/--unknown or -i/-ignored.
2498
2499
2499 NOTE: status may appear to disagree with diff if permissions have
2500 NOTE: status may appear to disagree with diff if permissions have
2500 changed or a merge has occurred. The standard diff format does not
2501 changed or a merge has occurred. The standard diff format does not
2501 report permission changes and diff only reports changes relative
2502 report permission changes and diff only reports changes relative
2502 to one merge parent.
2503 to one merge parent.
2503
2504
2504 If one revision is given, it is used as the base revision.
2505 If one revision is given, it is used as the base revision.
2505 If two revisions are given, the difference between them is shown.
2506 If two revisions are given, the difference between them is shown.
2506
2507
2507 The codes used to show the status of files are:
2508 The codes used to show the status of files are:
2508 M = modified
2509 M = modified
2509 A = added
2510 A = added
2510 R = removed
2511 R = removed
2511 C = clean
2512 C = clean
2512 ! = deleted, but still tracked
2513 ! = deleted, but still tracked
2513 ? = not tracked
2514 ? = not tracked
2514 I = ignored
2515 I = ignored
2515 = the previous added file was copied from here
2516 = the previous added file was copied from here
2516 """
2517 """
2517
2518
2518 all = opts['all']
2519 all = opts['all']
2519 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2520 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2520
2521
2521 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2522 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2522 cwd = (pats and repo.getcwd()) or ''
2523 cwd = (pats and repo.getcwd()) or ''
2523 modified, added, removed, deleted, unknown, ignored, clean = [
2524 modified, added, removed, deleted, unknown, ignored, clean = [
2524 n for n in repo.status(node1=node1, node2=node2, files=files,
2525 n for n in repo.status(node1=node1, node2=node2, files=files,
2525 match=matchfn,
2526 match=matchfn,
2526 list_ignored=opts['ignored']
2527 list_ignored=opts['ignored']
2527 or all and not ui.quiet,
2528 or all and not ui.quiet,
2528 list_clean=opts['clean'] or all,
2529 list_clean=opts['clean'] or all,
2529 list_unknown=opts['unknown']
2530 list_unknown=opts['unknown']
2530 or not (ui.quiet or
2531 or not (ui.quiet or
2531 opts['modified'] or
2532 opts['modified'] or
2532 opts['added'] or
2533 opts['added'] or
2533 opts['removed'] or
2534 opts['removed'] or
2534 opts['deleted'] or
2535 opts['deleted'] or
2535 opts['ignored']))]
2536 opts['ignored']))]
2536
2537
2537 changetypes = (('modified', 'M', modified),
2538 changetypes = (('modified', 'M', modified),
2538 ('added', 'A', added),
2539 ('added', 'A', added),
2539 ('removed', 'R', removed),
2540 ('removed', 'R', removed),
2540 ('deleted', '!', deleted),
2541 ('deleted', '!', deleted),
2541 ('unknown', '?', unknown),
2542 ('unknown', '?', unknown),
2542 ('ignored', 'I', ignored))
2543 ('ignored', 'I', ignored))
2543
2544
2544 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2545 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2545
2546
2546 end = opts['print0'] and '\0' or '\n'
2547 end = opts['print0'] and '\0' or '\n'
2547
2548
2548 for opt, char, changes in ([ct for ct in explicit_changetypes
2549 for opt, char, changes in ([ct for ct in explicit_changetypes
2549 if all or opts[ct[0]]]
2550 if all or opts[ct[0]]]
2550 or changetypes):
2551 or changetypes):
2551
2552
2552 if opts['no_status']:
2553 if opts['no_status']:
2553 format = "%%s%s" % end
2554 format = "%%s%s" % end
2554 else:
2555 else:
2555 format = "%s %%s%s" % (char, end)
2556 format = "%s %%s%s" % (char, end)
2556
2557
2557 for f in changes:
2558 for f in changes:
2558 ui.write(format % repo.pathto(f, cwd))
2559 ui.write(format % repo.pathto(f, cwd))
2559 if ((all or opts.get('copies')) and not opts.get('no_status')):
2560 if ((all or opts.get('copies')) and not opts.get('no_status')):
2560 copied = repo.dirstate.copied(f)
2561 copied = repo.dirstate.copied(f)
2561 if copied:
2562 if copied:
2562 ui.write(' %s%s' % (repo.pathto(copied, cwd), end))
2563 ui.write(' %s%s' % (repo.pathto(copied, cwd), end))
2563
2564
2564 def tag(ui, repo, name, rev_=None, **opts):
2565 def tag(ui, repo, name, rev_=None, **opts):
2565 """add a tag for the current or given revision
2566 """add a tag for the current or given revision
2566
2567
2567 Name a particular revision using <name>.
2568 Name a particular revision using <name>.
2568
2569
2569 Tags are used to name particular revisions of the repository and are
2570 Tags are used to name particular revisions of the repository and are
2570 very useful to compare different revision, to go back to significant
2571 very useful to compare different revision, to go back to significant
2571 earlier versions or to mark branch points as releases, etc.
2572 earlier versions or to mark branch points as releases, etc.
2572
2573
2573 If no revision is given, the parent of the working directory is used,
2574 If no revision is given, the parent of the working directory is used,
2574 or tip if no revision is checked out.
2575 or tip if no revision is checked out.
2575
2576
2576 To facilitate version control, distribution, and merging of tags,
2577 To facilitate version control, distribution, and merging of tags,
2577 they are stored as a file named ".hgtags" which is managed
2578 they are stored as a file named ".hgtags" which is managed
2578 similarly to other project files and can be hand-edited if
2579 similarly to other project files and can be hand-edited if
2579 necessary. The file '.hg/localtags' is used for local tags (not
2580 necessary. The file '.hg/localtags' is used for local tags (not
2580 shared among repositories).
2581 shared among repositories).
2581
2582
2582 See 'hg help dates' for a list of formats valid for -d/--date.
2583 See 'hg help dates' for a list of formats valid for -d/--date.
2583 """
2584 """
2584 if name in ['tip', '.', 'null']:
2585 if name in ['tip', '.', 'null']:
2585 raise util.Abort(_("the name '%s' is reserved") % name)
2586 raise util.Abort(_("the name '%s' is reserved") % name)
2586 if rev_ is not None:
2587 if rev_ is not None:
2587 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2588 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2588 "please use 'hg tag [-r REV] NAME' instead\n"))
2589 "please use 'hg tag [-r REV] NAME' instead\n"))
2589 if opts['rev']:
2590 if opts['rev']:
2590 raise util.Abort(_("use only one form to specify the revision"))
2591 raise util.Abort(_("use only one form to specify the revision"))
2591 if opts['rev'] and opts['remove']:
2592 if opts['rev'] and opts['remove']:
2592 raise util.Abort(_("--rev and --remove are incompatible"))
2593 raise util.Abort(_("--rev and --remove are incompatible"))
2593 if opts['rev']:
2594 if opts['rev']:
2594 rev_ = opts['rev']
2595 rev_ = opts['rev']
2595 message = opts['message']
2596 message = opts['message']
2596 if opts['remove']:
2597 if opts['remove']:
2597 tagtype = repo.tagtype(name)
2598 tagtype = repo.tagtype(name)
2598
2599
2599 if not tagtype:
2600 if not tagtype:
2600 raise util.Abort(_('tag %s does not exist') % name)
2601 raise util.Abort(_('tag %s does not exist') % name)
2601 if opts['local'] and tagtype == 'global':
2602 if opts['local'] and tagtype == 'global':
2602 raise util.Abort(_('%s tag is global') % name)
2603 raise util.Abort(_('%s tag is global') % name)
2603 if not opts['local'] and tagtype == 'local':
2604 if not opts['local'] and tagtype == 'local':
2604 raise util.Abort(_('%s tag is local') % name)
2605 raise util.Abort(_('%s tag is local') % name)
2605
2606
2606 rev_ = nullid
2607 rev_ = nullid
2607 if not message:
2608 if not message:
2608 message = _('Removed tag %s') % name
2609 message = _('Removed tag %s') % name
2609 elif name in repo.tags() and not opts['force']:
2610 elif name in repo.tags() and not opts['force']:
2610 raise util.Abort(_('a tag named %s already exists (use -f to force)')
2611 raise util.Abort(_('a tag named %s already exists (use -f to force)')
2611 % name)
2612 % name)
2612 if not rev_ and repo.dirstate.parents()[1] != nullid:
2613 if not rev_ and repo.dirstate.parents()[1] != nullid:
2613 raise util.Abort(_('uncommitted merge - please provide a '
2614 raise util.Abort(_('uncommitted merge - please provide a '
2614 'specific revision'))
2615 'specific revision'))
2615 r = repo.changectx(rev_).node()
2616 r = repo.changectx(rev_).node()
2616
2617
2617 if not message:
2618 if not message:
2618 message = _('Added tag %s for changeset %s') % (name, short(r))
2619 message = _('Added tag %s for changeset %s') % (name, short(r))
2619
2620
2620 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2621 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2621
2622
2622 def tags(ui, repo):
2623 def tags(ui, repo):
2623 """list repository tags
2624 """list repository tags
2624
2625
2625 List the repository tags.
2626 List the repository tags.
2626
2627
2627 This lists both regular and local tags. When the -v/--verbose switch
2628 This lists both regular and local tags. When the -v/--verbose switch
2628 is used, a third column "local" is printed for local tags.
2629 is used, a third column "local" is printed for local tags.
2629 """
2630 """
2630
2631
2631 l = repo.tagslist()
2632 l = repo.tagslist()
2632 l.reverse()
2633 l.reverse()
2633 hexfunc = ui.debugflag and hex or short
2634 hexfunc = ui.debugflag and hex or short
2634 tagtype = ""
2635 tagtype = ""
2635
2636
2636 for t, n in l:
2637 for t, n in l:
2637 if ui.quiet:
2638 if ui.quiet:
2638 ui.write("%s\n" % t)
2639 ui.write("%s\n" % t)
2639 continue
2640 continue
2640
2641
2641 try:
2642 try:
2642 hn = hexfunc(n)
2643 hn = hexfunc(n)
2643 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2644 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2644 except revlog.LookupError:
2645 except revlog.LookupError:
2645 r = " ?:%s" % hn
2646 r = " ?:%s" % hn
2646 else:
2647 else:
2647 spaces = " " * (30 - util.locallen(t))
2648 spaces = " " * (30 - util.locallen(t))
2648 if ui.verbose:
2649 if ui.verbose:
2649 if repo.tagtype(t) == 'local':
2650 if repo.tagtype(t) == 'local':
2650 tagtype = " local"
2651 tagtype = " local"
2651 else:
2652 else:
2652 tagtype = ""
2653 tagtype = ""
2653 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2654 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2654
2655
2655 def tip(ui, repo, **opts):
2656 def tip(ui, repo, **opts):
2656 """show the tip revision
2657 """show the tip revision
2657
2658
2658 Show the tip revision.
2659 Show the tip revision.
2659 """
2660 """
2660 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2661 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2661
2662
2662 def unbundle(ui, repo, fname1, *fnames, **opts):
2663 def unbundle(ui, repo, fname1, *fnames, **opts):
2663 """apply one or more changegroup files
2664 """apply one or more changegroup files
2664
2665
2665 Apply one or more compressed changegroup files generated by the
2666 Apply one or more compressed changegroup files generated by the
2666 bundle command.
2667 bundle command.
2667 """
2668 """
2668 fnames = (fname1,) + fnames
2669 fnames = (fname1,) + fnames
2669
2670
2670 lock = None
2671 lock = None
2671 try:
2672 try:
2672 lock = repo.lock()
2673 lock = repo.lock()
2673 for fname in fnames:
2674 for fname in fnames:
2674 if os.path.exists(fname):
2675 if os.path.exists(fname):
2675 f = open(fname, "rb")
2676 f = open(fname, "rb")
2676 else:
2677 else:
2677 f = urllib.urlopen(fname)
2678 f = urllib.urlopen(fname)
2678 gen = changegroup.readbundle(f, fname)
2679 gen = changegroup.readbundle(f, fname)
2679 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2680 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2680 finally:
2681 finally:
2681 del lock
2682 del lock
2682
2683
2683 return postincoming(ui, repo, modheads, opts['update'], None)
2684 return postincoming(ui, repo, modheads, opts['update'], None)
2684
2685
2685 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2686 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2686 """update working directory
2687 """update working directory
2687
2688
2688 Update the working directory to the specified revision, or the
2689 Update the working directory to the specified revision, or the
2689 tip of the current branch if none is specified.
2690 tip of the current branch if none is specified.
2690 See 'hg help dates' for a list of formats valid for -d/--date.
2691 See 'hg help dates' for a list of formats valid for -d/--date.
2691
2692
2692 If there are no outstanding changes in the working directory and
2693 If there are no outstanding changes in the working directory and
2693 there is a linear relationship between the current version and the
2694 there is a linear relationship between the current version and the
2694 requested version, the result is the requested version.
2695 requested version, the result is the requested version.
2695
2696
2696 To merge the working directory with another revision, use the
2697 To merge the working directory with another revision, use the
2697 merge command.
2698 merge command.
2698
2699
2699 By default, update will refuse to run if doing so would require
2700 By default, update will refuse to run if doing so would require
2700 discarding local changes.
2701 discarding local changes.
2701 """
2702 """
2702 if rev and node:
2703 if rev and node:
2703 raise util.Abort(_("please specify just one revision"))
2704 raise util.Abort(_("please specify just one revision"))
2704
2705
2705 if not rev:
2706 if not rev:
2706 rev = node
2707 rev = node
2707
2708
2708 if date:
2709 if date:
2709 if rev:
2710 if rev:
2710 raise util.Abort(_("you can't specify a revision and a date"))
2711 raise util.Abort(_("you can't specify a revision and a date"))
2711 rev = cmdutil.finddate(ui, repo, date)
2712 rev = cmdutil.finddate(ui, repo, date)
2712
2713
2713 if clean:
2714 if clean:
2714 return hg.clean(repo, rev)
2715 return hg.clean(repo, rev)
2715 else:
2716 else:
2716 return hg.update(repo, rev)
2717 return hg.update(repo, rev)
2717
2718
2718 def verify(ui, repo):
2719 def verify(ui, repo):
2719 """verify the integrity of the repository
2720 """verify the integrity of the repository
2720
2721
2721 Verify the integrity of the current repository.
2722 Verify the integrity of the current repository.
2722
2723
2723 This will perform an extensive check of the repository's
2724 This will perform an extensive check of the repository's
2724 integrity, validating the hashes and checksums of each entry in
2725 integrity, validating the hashes and checksums of each entry in
2725 the changelog, manifest, and tracked files, as well as the
2726 the changelog, manifest, and tracked files, as well as the
2726 integrity of their crosslinks and indices.
2727 integrity of their crosslinks and indices.
2727 """
2728 """
2728 return hg.verify(repo)
2729 return hg.verify(repo)
2729
2730
2730 def version_(ui):
2731 def version_(ui):
2731 """output version and copyright information"""
2732 """output version and copyright information"""
2732 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2733 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2733 % version.get_version())
2734 % version.get_version())
2734 ui.status(_(
2735 ui.status(_(
2735 "\nCopyright (C) 2005-2008 Matt Mackall <mpm@selenic.com> and others\n"
2736 "\nCopyright (C) 2005-2008 Matt Mackall <mpm@selenic.com> and others\n"
2736 "This is free software; see the source for copying conditions. "
2737 "This is free software; see the source for copying conditions. "
2737 "There is NO\nwarranty; "
2738 "There is NO\nwarranty; "
2738 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2739 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2739 ))
2740 ))
2740
2741
2741 # Command options and aliases are listed here, alphabetically
2742 # Command options and aliases are listed here, alphabetically
2742
2743
2743 globalopts = [
2744 globalopts = [
2744 ('R', 'repository', '',
2745 ('R', 'repository', '',
2745 _('repository root directory or symbolic path name')),
2746 _('repository root directory or symbolic path name')),
2746 ('', 'cwd', '', _('change working directory')),
2747 ('', 'cwd', '', _('change working directory')),
2747 ('y', 'noninteractive', None,
2748 ('y', 'noninteractive', None,
2748 _('do not prompt, assume \'yes\' for any required answers')),
2749 _('do not prompt, assume \'yes\' for any required answers')),
2749 ('q', 'quiet', None, _('suppress output')),
2750 ('q', 'quiet', None, _('suppress output')),
2750 ('v', 'verbose', None, _('enable additional output')),
2751 ('v', 'verbose', None, _('enable additional output')),
2751 ('', 'config', [], _('set/override config option')),
2752 ('', 'config', [], _('set/override config option')),
2752 ('', 'debug', None, _('enable debugging output')),
2753 ('', 'debug', None, _('enable debugging output')),
2753 ('', 'debugger', None, _('start debugger')),
2754 ('', 'debugger', None, _('start debugger')),
2754 ('', 'encoding', util._encoding, _('set the charset encoding')),
2755 ('', 'encoding', util._encoding, _('set the charset encoding')),
2755 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2756 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2756 ('', 'lsprof', None, _('print improved command execution profile')),
2757 ('', 'lsprof', None, _('print improved command execution profile')),
2757 ('', 'traceback', None, _('print traceback on exception')),
2758 ('', 'traceback', None, _('print traceback on exception')),
2758 ('', 'time', None, _('time how long the command takes')),
2759 ('', 'time', None, _('time how long the command takes')),
2759 ('', 'profile', None, _('print command execution profile')),
2760 ('', 'profile', None, _('print command execution profile')),
2760 ('', 'version', None, _('output version information and exit')),
2761 ('', 'version', None, _('output version information and exit')),
2761 ('h', 'help', None, _('display help and exit')),
2762 ('h', 'help', None, _('display help and exit')),
2762 ]
2763 ]
2763
2764
2764 dryrunopts = [('n', 'dry-run', None,
2765 dryrunopts = [('n', 'dry-run', None,
2765 _('do not perform actions, just print output'))]
2766 _('do not perform actions, just print output'))]
2766
2767
2767 remoteopts = [
2768 remoteopts = [
2768 ('e', 'ssh', '', _('specify ssh command to use')),
2769 ('e', 'ssh', '', _('specify ssh command to use')),
2769 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2770 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2770 ]
2771 ]
2771
2772
2772 walkopts = [
2773 walkopts = [
2773 ('I', 'include', [], _('include names matching the given patterns')),
2774 ('I', 'include', [], _('include names matching the given patterns')),
2774 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2775 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2775 ]
2776 ]
2776
2777
2777 commitopts = [
2778 commitopts = [
2778 ('m', 'message', '', _('use <text> as commit message')),
2779 ('m', 'message', '', _('use <text> as commit message')),
2779 ('l', 'logfile', '', _('read commit message from <file>')),
2780 ('l', 'logfile', '', _('read commit message from <file>')),
2780 ]
2781 ]
2781
2782
2782 commitopts2 = [
2783 commitopts2 = [
2783 ('d', 'date', '', _('record datecode as commit date')),
2784 ('d', 'date', '', _('record datecode as commit date')),
2784 ('u', 'user', '', _('record user as committer')),
2785 ('u', 'user', '', _('record user as committer')),
2785 ]
2786 ]
2786
2787
2787 templateopts = [
2788 templateopts = [
2788 ('', 'style', '', _('display using template map file')),
2789 ('', 'style', '', _('display using template map file')),
2789 ('', 'template', '', _('display with template')),
2790 ('', 'template', '', _('display with template')),
2790 ]
2791 ]
2791
2792
2792 logopts = [
2793 logopts = [
2793 ('p', 'patch', None, _('show patch')),
2794 ('p', 'patch', None, _('show patch')),
2794 ('l', 'limit', '', _('limit number of changes displayed')),
2795 ('l', 'limit', '', _('limit number of changes displayed')),
2795 ('M', 'no-merges', None, _('do not show merges')),
2796 ('M', 'no-merges', None, _('do not show merges')),
2796 ] + templateopts
2797 ] + templateopts
2797
2798
2798 table = {
2799 table = {
2799 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2800 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2800 "addremove":
2801 "addremove":
2801 (addremove,
2802 (addremove,
2802 [('s', 'similarity', '',
2803 [('s', 'similarity', '',
2803 _('guess renamed files by similarity (0<=s<=100)')),
2804 _('guess renamed files by similarity (0<=s<=100)')),
2804 ] + walkopts + dryrunopts,
2805 ] + walkopts + dryrunopts,
2805 _('hg addremove [OPTION]... [FILE]...')),
2806 _('hg addremove [OPTION]... [FILE]...')),
2806 "^annotate|blame":
2807 "^annotate|blame":
2807 (annotate,
2808 (annotate,
2808 [('r', 'rev', '', _('annotate the specified revision')),
2809 [('r', 'rev', '', _('annotate the specified revision')),
2809 ('f', 'follow', None, _('follow file copies and renames')),
2810 ('f', 'follow', None, _('follow file copies and renames')),
2810 ('a', 'text', None, _('treat all files as text')),
2811 ('a', 'text', None, _('treat all files as text')),
2811 ('u', 'user', None, _('list the author (long with -v)')),
2812 ('u', 'user', None, _('list the author (long with -v)')),
2812 ('d', 'date', None, _('list the date (short with -q)')),
2813 ('d', 'date', None, _('list the date (short with -q)')),
2813 ('n', 'number', None, _('list the revision number (default)')),
2814 ('n', 'number', None, _('list the revision number (default)')),
2814 ('c', 'changeset', None, _('list the changeset')),
2815 ('c', 'changeset', None, _('list the changeset')),
2815 ('l', 'line-number', None,
2816 ('l', 'line-number', None,
2816 _('show line number at the first appearance'))
2817 _('show line number at the first appearance'))
2817 ] + walkopts,
2818 ] + walkopts,
2818 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2819 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2819 "archive":
2820 "archive":
2820 (archive,
2821 (archive,
2821 [('', 'no-decode', None, _('do not pass files through decoders')),
2822 [('', 'no-decode', None, _('do not pass files through decoders')),
2822 ('p', 'prefix', '', _('directory prefix for files in archive')),
2823 ('p', 'prefix', '', _('directory prefix for files in archive')),
2823 ('r', 'rev', '', _('revision to distribute')),
2824 ('r', 'rev', '', _('revision to distribute')),
2824 ('t', 'type', '', _('type of distribution to create')),
2825 ('t', 'type', '', _('type of distribution to create')),
2825 ] + walkopts,
2826 ] + walkopts,
2826 _('hg archive [OPTION]... DEST')),
2827 _('hg archive [OPTION]... DEST')),
2827 "backout":
2828 "backout":
2828 (backout,
2829 (backout,
2829 [('', 'merge', None,
2830 [('', 'merge', None,
2830 _('merge with old dirstate parent after backout')),
2831 _('merge with old dirstate parent after backout')),
2831 ('', 'parent', '', _('parent to choose when backing out merge')),
2832 ('', 'parent', '', _('parent to choose when backing out merge')),
2832 ('r', 'rev', '', _('revision to backout')),
2833 ('r', 'rev', '', _('revision to backout')),
2833 ] + walkopts + commitopts + commitopts2,
2834 ] + walkopts + commitopts + commitopts2,
2834 _('hg backout [OPTION]... [-r] REV')),
2835 _('hg backout [OPTION]... [-r] REV')),
2835 "bisect":
2836 "bisect":
2836 (bisect,
2837 (bisect,
2837 [('r', 'reset', False, _('reset bisect state')),
2838 [('r', 'reset', False, _('reset bisect state')),
2838 ('g', 'good', False, _('mark changeset good')),
2839 ('g', 'good', False, _('mark changeset good')),
2839 ('b', 'bad', False, _('mark changeset bad')),
2840 ('b', 'bad', False, _('mark changeset bad')),
2840 ('s', 'skip', False, _('skip testing changeset')),
2841 ('s', 'skip', False, _('skip testing changeset')),
2841 ('U', 'noupdate', False, _('do not update to target'))],
2842 ('U', 'noupdate', False, _('do not update to target'))],
2842 _("hg bisect [-gbsr] [REV]")),
2843 _("hg bisect [-gbsr] [REV]")),
2843 "branch":
2844 "branch":
2844 (branch,
2845 (branch,
2845 [('f', 'force', None,
2846 [('f', 'force', None,
2846 _('set branch name even if it shadows an existing branch'))],
2847 _('set branch name even if it shadows an existing branch'))],
2847 _('hg branch [-f] [NAME]')),
2848 _('hg branch [-f] [NAME]')),
2848 "branches":
2849 "branches":
2849 (branches,
2850 (branches,
2850 [('a', 'active', False,
2851 [('a', 'active', False,
2851 _('show only branches that have unmerged heads'))],
2852 _('show only branches that have unmerged heads'))],
2852 _('hg branches [-a]')),
2853 _('hg branches [-a]')),
2853 "bundle":
2854 "bundle":
2854 (bundle,
2855 (bundle,
2855 [('f', 'force', None,
2856 [('f', 'force', None,
2856 _('run even when remote repository is unrelated')),
2857 _('run even when remote repository is unrelated')),
2857 ('r', 'rev', [],
2858 ('r', 'rev', [],
2858 _('a changeset you would like to bundle')),
2859 _('a changeset you would like to bundle')),
2859 ('', 'base', [],
2860 ('', 'base', [],
2860 _('a base changeset to specify instead of a destination')),
2861 _('a base changeset to specify instead of a destination')),
2861 ('a', 'all', None,
2862 ('a', 'all', None,
2862 _('bundle all changesets in the repository')),
2863 _('bundle all changesets in the repository')),
2863 ] + remoteopts,
2864 ] + remoteopts,
2864 _('hg bundle [-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
2865 _('hg bundle [-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
2865 "cat":
2866 "cat":
2866 (cat,
2867 (cat,
2867 [('o', 'output', '', _('print output to file with formatted name')),
2868 [('o', 'output', '', _('print output to file with formatted name')),
2868 ('r', 'rev', '', _('print the given revision')),
2869 ('r', 'rev', '', _('print the given revision')),
2869 ('', 'decode', None, _('apply any matching decode filter')),
2870 ('', 'decode', None, _('apply any matching decode filter')),
2870 ] + walkopts,
2871 ] + walkopts,
2871 _('hg cat [OPTION]... FILE...')),
2872 _('hg cat [OPTION]... FILE...')),
2872 "^clone":
2873 "^clone":
2873 (clone,
2874 (clone,
2874 [('U', 'noupdate', None, _('do not update the new working directory')),
2875 [('U', 'noupdate', None, _('do not update the new working directory')),
2875 ('r', 'rev', [],
2876 ('r', 'rev', [],
2876 _('a changeset you would like to have after cloning')),
2877 _('a changeset you would like to have after cloning')),
2877 ('', 'pull', None, _('use pull protocol to copy metadata')),
2878 ('', 'pull', None, _('use pull protocol to copy metadata')),
2878 ('', 'uncompressed', None,
2879 ('', 'uncompressed', None,
2879 _('use uncompressed transfer (fast over LAN)')),
2880 _('use uncompressed transfer (fast over LAN)')),
2880 ] + remoteopts,
2881 ] + remoteopts,
2881 _('hg clone [OPTION]... SOURCE [DEST]')),
2882 _('hg clone [OPTION]... SOURCE [DEST]')),
2882 "^commit|ci":
2883 "^commit|ci":
2883 (commit,
2884 (commit,
2884 [('A', 'addremove', None,
2885 [('A', 'addremove', None,
2885 _('mark new/missing files as added/removed before committing')),
2886 _('mark new/missing files as added/removed before committing')),
2886 ] + walkopts + commitopts + commitopts2,
2887 ] + walkopts + commitopts + commitopts2,
2887 _('hg commit [OPTION]... [FILE]...')),
2888 _('hg commit [OPTION]... [FILE]...')),
2888 "copy|cp":
2889 "copy|cp":
2889 (copy,
2890 (copy,
2890 [('A', 'after', None, _('record a copy that has already occurred')),
2891 [('A', 'after', None, _('record a copy that has already occurred')),
2891 ('f', 'force', None,
2892 ('f', 'force', None,
2892 _('forcibly copy over an existing managed file')),
2893 _('forcibly copy over an existing managed file')),
2893 ] + walkopts + dryrunopts,
2894 ] + walkopts + dryrunopts,
2894 _('hg copy [OPTION]... [SOURCE]... DEST')),
2895 _('hg copy [OPTION]... [SOURCE]... DEST')),
2895 "debugancestor": (debugancestor, [],
2896 "debugancestor": (debugancestor, [],
2896 _('hg debugancestor [INDEX] REV1 REV2')),
2897 _('hg debugancestor [INDEX] REV1 REV2')),
2897 "debugcheckstate": (debugcheckstate, [], _('hg debugcheckstate')),
2898 "debugcheckstate": (debugcheckstate, [], _('hg debugcheckstate')),
2898 "debugcomplete":
2899 "debugcomplete":
2899 (debugcomplete,
2900 (debugcomplete,
2900 [('o', 'options', None, _('show the command options'))],
2901 [('o', 'options', None, _('show the command options'))],
2901 _('hg debugcomplete [-o] CMD')),
2902 _('hg debugcomplete [-o] CMD')),
2902 "debugdate":
2903 "debugdate":
2903 (debugdate,
2904 (debugdate,
2904 [('e', 'extended', None, _('try extended date formats'))],
2905 [('e', 'extended', None, _('try extended date formats'))],
2905 _('hg debugdate [-e] DATE [RANGE]')),
2906 _('hg debugdate [-e] DATE [RANGE]')),
2906 "debugdata": (debugdata, [], _('hg debugdata FILE REV')),
2907 "debugdata": (debugdata, [], _('hg debugdata FILE REV')),
2907 "debugfsinfo": (debugfsinfo, [], _('hg debugfsinfo [PATH]')),
2908 "debugfsinfo": (debugfsinfo, [], _('hg debugfsinfo [PATH]')),
2908 "debugindex": (debugindex, [], _('hg debugindex FILE')),
2909 "debugindex": (debugindex, [], _('hg debugindex FILE')),
2909 "debugindexdot": (debugindexdot, [], _('hg debugindexdot FILE')),
2910 "debugindexdot": (debugindexdot, [], _('hg debugindexdot FILE')),
2910 "debuginstall": (debuginstall, [], _('hg debuginstall')),
2911 "debuginstall": (debuginstall, [], _('hg debuginstall')),
2911 "debugrawcommit|rawcommit":
2912 "debugrawcommit|rawcommit":
2912 (rawcommit,
2913 (rawcommit,
2913 [('p', 'parent', [], _('parent')),
2914 [('p', 'parent', [], _('parent')),
2914 ('F', 'files', '', _('file list'))
2915 ('F', 'files', '', _('file list'))
2915 ] + commitopts + commitopts2,
2916 ] + commitopts + commitopts2,
2916 _('hg debugrawcommit [OPTION]... [FILE]...')),
2917 _('hg debugrawcommit [OPTION]... [FILE]...')),
2917 "debugrebuildstate":
2918 "debugrebuildstate":
2918 (debugrebuildstate,
2919 (debugrebuildstate,
2919 [('r', 'rev', '', _('revision to rebuild to'))],
2920 [('r', 'rev', '', _('revision to rebuild to'))],
2920 _('hg debugrebuildstate [-r REV] [REV]')),
2921 _('hg debugrebuildstate [-r REV] [REV]')),
2921 "debugrename":
2922 "debugrename":
2922 (debugrename,
2923 (debugrename,
2923 [('r', 'rev', '', _('revision to debug'))],
2924 [('r', 'rev', '', _('revision to debug'))],
2924 _('hg debugrename [-r REV] FILE')),
2925 _('hg debugrename [-r REV] FILE')),
2925 "debugsetparents":
2926 "debugsetparents":
2926 (debugsetparents,
2927 (debugsetparents,
2927 [],
2928 [],
2928 _('hg debugsetparents REV1 [REV2]')),
2929 _('hg debugsetparents REV1 [REV2]')),
2929 "debugstate": (debugstate, [], _('hg debugstate')),
2930 "debugstate": (debugstate, [], _('hg debugstate')),
2930 "debugwalk": (debugwalk, walkopts, _('hg debugwalk [OPTION]... [FILE]...')),
2931 "debugwalk": (debugwalk, walkopts, _('hg debugwalk [OPTION]... [FILE]...')),
2931 "^diff":
2932 "^diff":
2932 (diff,
2933 (diff,
2933 [('r', 'rev', [], _('revision')),
2934 [('r', 'rev', [], _('revision')),
2934 ('a', 'text', None, _('treat all files as text')),
2935 ('a', 'text', None, _('treat all files as text')),
2935 ('p', 'show-function', None,
2936 ('p', 'show-function', None,
2936 _('show which function each change is in')),
2937 _('show which function each change is in')),
2937 ('g', 'git', None, _('use git extended diff format')),
2938 ('g', 'git', None, _('use git extended diff format')),
2938 ('', 'nodates', None, _("don't include dates in diff headers")),
2939 ('', 'nodates', None, _("don't include dates in diff headers")),
2939 ('w', 'ignore-all-space', None,
2940 ('w', 'ignore-all-space', None,
2940 _('ignore white space when comparing lines')),
2941 _('ignore white space when comparing lines')),
2941 ('b', 'ignore-space-change', None,
2942 ('b', 'ignore-space-change', None,
2942 _('ignore changes in the amount of white space')),
2943 _('ignore changes in the amount of white space')),
2943 ('B', 'ignore-blank-lines', None,
2944 ('B', 'ignore-blank-lines', None,
2944 _('ignore changes whose lines are all blank')),
2945 _('ignore changes whose lines are all blank')),
2945 ('U', 'unified', 3,
2946 ('U', 'unified', 3,
2946 _('number of lines of context to show'))
2947 _('number of lines of context to show'))
2947 ] + walkopts,
2948 ] + walkopts,
2948 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2949 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2949 "^export":
2950 "^export":
2950 (export,
2951 (export,
2951 [('o', 'output', '', _('print output to file with formatted name')),
2952 [('o', 'output', '', _('print output to file with formatted name')),
2952 ('a', 'text', None, _('treat all files as text')),
2953 ('a', 'text', None, _('treat all files as text')),
2953 ('g', 'git', None, _('use git extended diff format')),
2954 ('g', 'git', None, _('use git extended diff format')),
2954 ('', 'nodates', None, _("don't include dates in diff headers")),
2955 ('', 'nodates', None, _("don't include dates in diff headers")),
2955 ('', 'switch-parent', None, _('diff against the second parent'))],
2956 ('', 'switch-parent', None, _('diff against the second parent'))],
2956 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2957 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2957 "grep":
2958 "grep":
2958 (grep,
2959 (grep,
2959 [('0', 'print0', None, _('end fields with NUL')),
2960 [('0', 'print0', None, _('end fields with NUL')),
2960 ('', 'all', None, _('print all revisions that match')),
2961 ('', 'all', None, _('print all revisions that match')),
2961 ('f', 'follow', None,
2962 ('f', 'follow', None,
2962 _('follow changeset history, or file history across copies and renames')),
2963 _('follow changeset history, or file history across copies and renames')),
2963 ('i', 'ignore-case', None, _('ignore case when matching')),
2964 ('i', 'ignore-case', None, _('ignore case when matching')),
2964 ('l', 'files-with-matches', None,
2965 ('l', 'files-with-matches', None,
2965 _('print only filenames and revs that match')),
2966 _('print only filenames and revs that match')),
2966 ('n', 'line-number', None, _('print matching line numbers')),
2967 ('n', 'line-number', None, _('print matching line numbers')),
2967 ('r', 'rev', [], _('search in given revision range')),
2968 ('r', 'rev', [], _('search in given revision range')),
2968 ('u', 'user', None, _('list the author (long with -v)')),
2969 ('u', 'user', None, _('list the author (long with -v)')),
2969 ('d', 'date', None, _('list the date (short with -q)')),
2970 ('d', 'date', None, _('list the date (short with -q)')),
2970 ] + walkopts,
2971 ] + walkopts,
2971 _('hg grep [OPTION]... PATTERN [FILE]...')),
2972 _('hg grep [OPTION]... PATTERN [FILE]...')),
2972 "heads":
2973 "heads":
2973 (heads,
2974 (heads,
2974 [('r', 'rev', '', _('show only heads which are descendants of rev')),
2975 [('r', 'rev', '', _('show only heads which are descendants of rev')),
2975 ] + templateopts,
2976 ] + templateopts,
2976 _('hg heads [-r REV] [REV]...')),
2977 _('hg heads [-r REV] [REV]...')),
2977 "help": (help_, [], _('hg help [COMMAND]')),
2978 "help": (help_, [], _('hg help [COMMAND]')),
2978 "identify|id":
2979 "identify|id":
2979 (identify,
2980 (identify,
2980 [('r', 'rev', '', _('identify the specified rev')),
2981 [('r', 'rev', '', _('identify the specified rev')),
2981 ('n', 'num', None, _('show local revision number')),
2982 ('n', 'num', None, _('show local revision number')),
2982 ('i', 'id', None, _('show global revision id')),
2983 ('i', 'id', None, _('show global revision id')),
2983 ('b', 'branch', None, _('show branch')),
2984 ('b', 'branch', None, _('show branch')),
2984 ('t', 'tags', None, _('show tags'))],
2985 ('t', 'tags', None, _('show tags'))],
2985 _('hg identify [-nibt] [-r REV] [SOURCE]')),
2986 _('hg identify [-nibt] [-r REV] [SOURCE]')),
2986 "import|patch":
2987 "import|patch":
2987 (import_,
2988 (import_,
2988 [('p', 'strip', 1,
2989 [('p', 'strip', 1,
2989 _('directory strip option for patch. This has the same\n'
2990 _('directory strip option for patch. This has the same\n'
2990 'meaning as the corresponding patch option')),
2991 'meaning as the corresponding patch option')),
2991 ('b', 'base', '', _('base path')),
2992 ('b', 'base', '', _('base path')),
2992 ('f', 'force', None,
2993 ('f', 'force', None,
2993 _('skip check for outstanding uncommitted changes')),
2994 _('skip check for outstanding uncommitted changes')),
2994 ('', 'no-commit', None, _("don't commit, just update the working directory")),
2995 ('', 'no-commit', None, _("don't commit, just update the working directory")),
2995 ('', 'exact', None,
2996 ('', 'exact', None,
2996 _('apply patch to the nodes from which it was generated')),
2997 _('apply patch to the nodes from which it was generated')),
2997 ('', 'import-branch', None,
2998 ('', 'import-branch', None,
2998 _('Use any branch information in patch (implied by --exact)'))] +
2999 _('Use any branch information in patch (implied by --exact)'))] +
2999 commitopts + commitopts2,
3000 commitopts + commitopts2,
3000 _('hg import [OPTION]... PATCH...')),
3001 _('hg import [OPTION]... PATCH...')),
3001 "incoming|in":
3002 "incoming|in":
3002 (incoming,
3003 (incoming,
3003 [('f', 'force', None,
3004 [('f', 'force', None,
3004 _('run even when remote repository is unrelated')),
3005 _('run even when remote repository is unrelated')),
3005 ('n', 'newest-first', None, _('show newest record first')),
3006 ('n', 'newest-first', None, _('show newest record first')),
3006 ('', 'bundle', '', _('file to store the bundles into')),
3007 ('', 'bundle', '', _('file to store the bundles into')),
3007 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
3008 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
3008 ] + logopts + remoteopts,
3009 ] + logopts + remoteopts,
3009 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
3010 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
3010 ' [--bundle FILENAME] [SOURCE]')),
3011 ' [--bundle FILENAME] [SOURCE]')),
3011 "^init":
3012 "^init":
3012 (init,
3013 (init,
3013 remoteopts,
3014 remoteopts,
3014 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
3015 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
3015 "locate":
3016 "locate":
3016 (locate,
3017 (locate,
3017 [('r', 'rev', '', _('search the repository as it stood at rev')),
3018 [('r', 'rev', '', _('search the repository as it stood at rev')),
3018 ('0', 'print0', None,
3019 ('0', 'print0', None,
3019 _('end filenames with NUL, for use with xargs')),
3020 _('end filenames with NUL, for use with xargs')),
3020 ('f', 'fullpath', None,
3021 ('f', 'fullpath', None,
3021 _('print complete paths from the filesystem root')),
3022 _('print complete paths from the filesystem root')),
3022 ] + walkopts,
3023 ] + walkopts,
3023 _('hg locate [OPTION]... [PATTERN]...')),
3024 _('hg locate [OPTION]... [PATTERN]...')),
3024 "^log|history":
3025 "^log|history":
3025 (log,
3026 (log,
3026 [('f', 'follow', None,
3027 [('f', 'follow', None,
3027 _('follow changeset history, or file history across copies and renames')),
3028 _('follow changeset history, or file history across copies and renames')),
3028 ('', 'follow-first', None,
3029 ('', 'follow-first', None,
3029 _('only follow the first parent of merge changesets')),
3030 _('only follow the first parent of merge changesets')),
3030 ('d', 'date', '', _('show revs matching date spec')),
3031 ('d', 'date', '', _('show revs matching date spec')),
3031 ('C', 'copies', None, _('show copied files')),
3032 ('C', 'copies', None, _('show copied files')),
3032 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3033 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3033 ('r', 'rev', [], _('show the specified revision or range')),
3034 ('r', 'rev', [], _('show the specified revision or range')),
3034 ('', 'removed', None, _('include revs where files were removed')),
3035 ('', 'removed', None, _('include revs where files were removed')),
3035 ('m', 'only-merges', None, _('show only merges')),
3036 ('m', 'only-merges', None, _('show only merges')),
3036 ('b', 'only-branch', [],
3037 ('b', 'only-branch', [],
3037 _('show only changesets within the given named branch')),
3038 _('show only changesets within the given named branch')),
3038 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3039 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3039 ] + logopts + walkopts,
3040 ] + logopts + walkopts,
3040 _('hg log [OPTION]... [FILE]')),
3041 _('hg log [OPTION]... [FILE]')),
3041 "manifest":
3042 "manifest":
3042 (manifest,
3043 (manifest,
3043 [('r', 'rev', '', _('revision to display'))],
3044 [('r', 'rev', '', _('revision to display'))],
3044 _('hg manifest [-r REV]')),
3045 _('hg manifest [-r REV]')),
3045 "^merge":
3046 "^merge":
3046 (merge,
3047 (merge,
3047 [('f', 'force', None, _('force a merge with outstanding changes')),
3048 [('f', 'force', None, _('force a merge with outstanding changes')),
3048 ('r', 'rev', '', _('revision to merge')),
3049 ('r', 'rev', '', _('revision to merge')),
3049 ],
3050 ],
3050 _('hg merge [-f] [[-r] REV]')),
3051 _('hg merge [-f] [[-r] REV]')),
3051 "outgoing|out":
3052 "outgoing|out":
3052 (outgoing,
3053 (outgoing,
3053 [('f', 'force', None,
3054 [('f', 'force', None,
3054 _('run even when remote repository is unrelated')),
3055 _('run even when remote repository is unrelated')),
3055 ('r', 'rev', [], _('a specific revision you would like to push')),
3056 ('r', 'rev', [], _('a specific revision you would like to push')),
3056 ('n', 'newest-first', None, _('show newest record first')),
3057 ('n', 'newest-first', None, _('show newest record first')),
3057 ] + logopts + remoteopts,
3058 ] + logopts + remoteopts,
3058 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3059 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3059 "^parents":
3060 "^parents":
3060 (parents,
3061 (parents,
3061 [('r', 'rev', '', _('show parents from the specified rev')),
3062 [('r', 'rev', '', _('show parents from the specified rev')),
3062 ] + templateopts,
3063 ] + templateopts,
3063 _('hg parents [-r REV] [FILE]')),
3064 _('hg parents [-r REV] [FILE]')),
3064 "paths": (paths, [], _('hg paths [NAME]')),
3065 "paths": (paths, [], _('hg paths [NAME]')),
3065 "^pull":
3066 "^pull":
3066 (pull,
3067 (pull,
3067 [('u', 'update', None,
3068 [('u', 'update', None,
3068 _('update to new tip if changesets were pulled')),
3069 _('update to new tip if changesets were pulled')),
3069 ('f', 'force', None,
3070 ('f', 'force', None,
3070 _('run even when remote repository is unrelated')),
3071 _('run even when remote repository is unrelated')),
3071 ('r', 'rev', [],
3072 ('r', 'rev', [],
3072 _('a specific revision up to which you would like to pull')),
3073 _('a specific revision up to which you would like to pull')),
3073 ] + remoteopts,
3074 ] + remoteopts,
3074 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3075 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3075 "^push":
3076 "^push":
3076 (push,
3077 (push,
3077 [('f', 'force', None, _('force push')),
3078 [('f', 'force', None, _('force push')),
3078 ('r', 'rev', [], _('a specific revision you would like to push')),
3079 ('r', 'rev', [], _('a specific revision you would like to push')),
3079 ] + remoteopts,
3080 ] + remoteopts,
3080 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3081 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3081 "recover": (recover, [], _('hg recover')),
3082 "recover": (recover, [], _('hg recover')),
3082 "^remove|rm":
3083 "^remove|rm":
3083 (remove,
3084 (remove,
3084 [('A', 'after', None, _('record remove without deleting')),
3085 [('A', 'after', None, _('record remove without deleting')),
3085 ('f', 'force', None, _('remove file even if modified')),
3086 ('f', 'force', None, _('remove file even if modified')),
3086 ] + walkopts,
3087 ] + walkopts,
3087 _('hg remove [OPTION]... FILE...')),
3088 _('hg remove [OPTION]... FILE...')),
3088 "rename|mv":
3089 "rename|mv":
3089 (rename,
3090 (rename,
3090 [('A', 'after', None, _('record a rename that has already occurred')),
3091 [('A', 'after', None, _('record a rename that has already occurred')),
3091 ('f', 'force', None,
3092 ('f', 'force', None,
3092 _('forcibly copy over an existing managed file')),
3093 _('forcibly copy over an existing managed file')),
3093 ] + walkopts + dryrunopts,
3094 ] + walkopts + dryrunopts,
3094 _('hg rename [OPTION]... SOURCE... DEST')),
3095 _('hg rename [OPTION]... SOURCE... DEST')),
3095 "revert":
3096 "revert":
3096 (revert,
3097 (revert,
3097 [('a', 'all', None, _('revert all changes when no arguments given')),
3098 [('a', 'all', None, _('revert all changes when no arguments given')),
3098 ('d', 'date', '', _('tipmost revision matching date')),
3099 ('d', 'date', '', _('tipmost revision matching date')),
3099 ('r', 'rev', '', _('revision to revert to')),
3100 ('r', 'rev', '', _('revision to revert to')),
3100 ('', 'no-backup', None, _('do not save backup copies of files')),
3101 ('', 'no-backup', None, _('do not save backup copies of files')),
3101 ] + walkopts + dryrunopts,
3102 ] + walkopts + dryrunopts,
3102 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3103 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3103 "rollback": (rollback, [], _('hg rollback')),
3104 "rollback": (rollback, [], _('hg rollback')),
3104 "root": (root, [], _('hg root')),
3105 "root": (root, [], _('hg root')),
3105 "^serve":
3106 "^serve":
3106 (serve,
3107 (serve,
3107 [('A', 'accesslog', '', _('name of access log file to write to')),
3108 [('A', 'accesslog', '', _('name of access log file to write to')),
3108 ('d', 'daemon', None, _('run server in background')),
3109 ('d', 'daemon', None, _('run server in background')),
3109 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3110 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3110 ('E', 'errorlog', '', _('name of error log file to write to')),
3111 ('E', 'errorlog', '', _('name of error log file to write to')),
3111 ('p', 'port', 0, _('port to use (default: 8000)')),
3112 ('p', 'port', 0, _('port to use (default: 8000)')),
3112 ('a', 'address', '', _('address to use')),
3113 ('a', 'address', '', _('address to use')),
3113 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3114 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3114 ('n', 'name', '',
3115 ('n', 'name', '',
3115 _('name to show in web pages (default: working dir)')),
3116 _('name to show in web pages (default: working dir)')),
3116 ('', 'webdir-conf', '', _('name of the webdir config file'
3117 ('', 'webdir-conf', '', _('name of the webdir config file'
3117 ' (serve more than one repo)')),
3118 ' (serve more than one repo)')),
3118 ('', 'pid-file', '', _('name of file to write process ID to')),
3119 ('', 'pid-file', '', _('name of file to write process ID to')),
3119 ('', 'stdio', None, _('for remote clients')),
3120 ('', 'stdio', None, _('for remote clients')),
3120 ('t', 'templates', '', _('web templates to use')),
3121 ('t', 'templates', '', _('web templates to use')),
3121 ('', 'style', '', _('template style to use')),
3122 ('', 'style', '', _('template style to use')),
3122 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3123 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3123 ('', 'certificate', '', _('SSL certificate file'))],
3124 ('', 'certificate', '', _('SSL certificate file'))],
3124 _('hg serve [OPTION]...')),
3125 _('hg serve [OPTION]...')),
3125 "showconfig|debugconfig":
3126 "showconfig|debugconfig":
3126 (showconfig,
3127 (showconfig,
3127 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3128 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3128 _('hg showconfig [-u] [NAME]...')),
3129 _('hg showconfig [-u] [NAME]...')),
3129 "^status|st":
3130 "^status|st":
3130 (status,
3131 (status,
3131 [('A', 'all', None, _('show status of all files')),
3132 [('A', 'all', None, _('show status of all files')),
3132 ('m', 'modified', None, _('show only modified files')),
3133 ('m', 'modified', None, _('show only modified files')),
3133 ('a', 'added', None, _('show only added files')),
3134 ('a', 'added', None, _('show only added files')),
3134 ('r', 'removed', None, _('show only removed files')),
3135 ('r', 'removed', None, _('show only removed files')),
3135 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3136 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3136 ('c', 'clean', None, _('show only files without changes')),
3137 ('c', 'clean', None, _('show only files without changes')),
3137 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3138 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3138 ('i', 'ignored', None, _('show only ignored files')),
3139 ('i', 'ignored', None, _('show only ignored files')),
3139 ('n', 'no-status', None, _('hide status prefix')),
3140 ('n', 'no-status', None, _('hide status prefix')),
3140 ('C', 'copies', None, _('show source of copied files')),
3141 ('C', 'copies', None, _('show source of copied files')),
3141 ('0', 'print0', None,
3142 ('0', 'print0', None,
3142 _('end filenames with NUL, for use with xargs')),
3143 _('end filenames with NUL, for use with xargs')),
3143 ('', 'rev', [], _('show difference from revision')),
3144 ('', 'rev', [], _('show difference from revision')),
3144 ] + walkopts,
3145 ] + walkopts,
3145 _('hg status [OPTION]... [FILE]...')),
3146 _('hg status [OPTION]... [FILE]...')),
3146 "tag":
3147 "tag":
3147 (tag,
3148 (tag,
3148 [('f', 'force', None, _('replace existing tag')),
3149 [('f', 'force', None, _('replace existing tag')),
3149 ('l', 'local', None, _('make the tag local')),
3150 ('l', 'local', None, _('make the tag local')),
3150 ('r', 'rev', '', _('revision to tag')),
3151 ('r', 'rev', '', _('revision to tag')),
3151 ('', 'remove', None, _('remove a tag')),
3152 ('', 'remove', None, _('remove a tag')),
3152 # -l/--local is already there, commitopts cannot be used
3153 # -l/--local is already there, commitopts cannot be used
3153 ('m', 'message', '', _('use <text> as commit message')),
3154 ('m', 'message', '', _('use <text> as commit message')),
3154 ] + commitopts2,
3155 ] + commitopts2,
3155 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3156 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3156 "tags": (tags, [], _('hg tags')),
3157 "tags": (tags, [], _('hg tags')),
3157 "tip":
3158 "tip":
3158 (tip,
3159 (tip,
3159 [('p', 'patch', None, _('show patch')),
3160 [('p', 'patch', None, _('show patch')),
3160 ] + templateopts,
3161 ] + templateopts,
3161 _('hg tip [-p]')),
3162 _('hg tip [-p]')),
3162 "unbundle":
3163 "unbundle":
3163 (unbundle,
3164 (unbundle,
3164 [('u', 'update', None,
3165 [('u', 'update', None,
3165 _('update to new tip if changesets were unbundled'))],
3166 _('update to new tip if changesets were unbundled'))],
3166 _('hg unbundle [-u] FILE...')),
3167 _('hg unbundle [-u] FILE...')),
3167 "^update|up|checkout|co":
3168 "^update|up|checkout|co":
3168 (update,
3169 (update,
3169 [('C', 'clean', None, _('overwrite locally modified files')),
3170 [('C', 'clean', None, _('overwrite locally modified files')),
3170 ('d', 'date', '', _('tipmost revision matching date')),
3171 ('d', 'date', '', _('tipmost revision matching date')),
3171 ('r', 'rev', '', _('revision'))],
3172 ('r', 'rev', '', _('revision'))],
3172 _('hg update [-C] [-d DATE] [[-r] REV]')),
3173 _('hg update [-C] [-d DATE] [[-r] REV]')),
3173 "verify": (verify, [], _('hg verify')),
3174 "verify": (verify, [], _('hg verify')),
3174 "version": (version_, [], _('hg version')),
3175 "version": (version_, [], _('hg version')),
3175 }
3176 }
3176
3177
3177 norepo = ("clone init version help debugcomplete debugdata"
3178 norepo = ("clone init version help debugcomplete debugdata"
3178 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3179 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3179 optionalrepo = ("identify paths serve showconfig debugancestor")
3180 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,416 +1,417 b''
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 from repo import RepoError
9 import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex, time
10 import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex, time
10 import util, commands, hg, lock, fancyopts, revlog, version, extensions, hook
11 import util, commands, hg, lock, fancyopts, revlog, version, extensions, hook
11 import cmdutil
12 import cmdutil
12 import ui as _ui
13 import ui as _ui
13
14
14 class ParseError(Exception):
15 class ParseError(Exception):
15 """Exception raised on errors in parsing the command line."""
16 """Exception raised on errors in parsing the command line."""
16
17
17 def run():
18 def run():
18 "run the command in sys.argv"
19 "run the command in sys.argv"
19 sys.exit(dispatch(sys.argv[1:]))
20 sys.exit(dispatch(sys.argv[1:]))
20
21
21 def dispatch(args):
22 def dispatch(args):
22 "run the command specified in args"
23 "run the command specified in args"
23 try:
24 try:
24 u = _ui.ui(traceback='--traceback' in args)
25 u = _ui.ui(traceback='--traceback' in args)
25 except util.Abort, inst:
26 except util.Abort, inst:
26 sys.stderr.write(_("abort: %s\n") % inst)
27 sys.stderr.write(_("abort: %s\n") % inst)
27 return -1
28 return -1
28 return _runcatch(u, args)
29 return _runcatch(u, args)
29
30
30 def _runcatch(ui, args):
31 def _runcatch(ui, args):
31 def catchterm(*args):
32 def catchterm(*args):
32 raise util.SignalInterrupt
33 raise util.SignalInterrupt
33
34
34 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
35 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
35 num = getattr(signal, name, None)
36 num = getattr(signal, name, None)
36 if num: signal.signal(num, catchterm)
37 if num: signal.signal(num, catchterm)
37
38
38 try:
39 try:
39 try:
40 try:
40 # enter the debugger before command execution
41 # enter the debugger before command execution
41 if '--debugger' in args:
42 if '--debugger' in args:
42 pdb.set_trace()
43 pdb.set_trace()
43 try:
44 try:
44 return _dispatch(ui, args)
45 return _dispatch(ui, args)
45 finally:
46 finally:
46 ui.flush()
47 ui.flush()
47 except:
48 except:
48 # enter the debugger when we hit an exception
49 # enter the debugger when we hit an exception
49 if '--debugger' in args:
50 if '--debugger' in args:
50 pdb.post_mortem(sys.exc_info()[2])
51 pdb.post_mortem(sys.exc_info()[2])
51 ui.print_exc()
52 ui.print_exc()
52 raise
53 raise
53
54
54 except ParseError, inst:
55 except ParseError, inst:
55 if inst.args[0]:
56 if inst.args[0]:
56 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
57 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
57 commands.help_(ui, inst.args[0])
58 commands.help_(ui, inst.args[0])
58 else:
59 else:
59 ui.warn(_("hg: %s\n") % inst.args[1])
60 ui.warn(_("hg: %s\n") % inst.args[1])
60 commands.help_(ui, 'shortlist')
61 commands.help_(ui, 'shortlist')
61 except cmdutil.AmbiguousCommand, inst:
62 except cmdutil.AmbiguousCommand, inst:
62 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
63 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
63 (inst.args[0], " ".join(inst.args[1])))
64 (inst.args[0], " ".join(inst.args[1])))
64 except cmdutil.UnknownCommand, inst:
65 except cmdutil.UnknownCommand, inst:
65 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
66 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
66 commands.help_(ui, 'shortlist')
67 commands.help_(ui, 'shortlist')
67 except hg.RepoError, inst:
68 except RepoError, inst:
68 ui.warn(_("abort: %s!\n") % inst)
69 ui.warn(_("abort: %s!\n") % inst)
69 except lock.LockHeld, inst:
70 except lock.LockHeld, inst:
70 if inst.errno == errno.ETIMEDOUT:
71 if inst.errno == errno.ETIMEDOUT:
71 reason = _('timed out waiting for lock held by %s') % inst.locker
72 reason = _('timed out waiting for lock held by %s') % inst.locker
72 else:
73 else:
73 reason = _('lock held by %s') % inst.locker
74 reason = _('lock held by %s') % inst.locker
74 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
75 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
75 except lock.LockUnavailable, inst:
76 except lock.LockUnavailable, inst:
76 ui.warn(_("abort: could not lock %s: %s\n") %
77 ui.warn(_("abort: could not lock %s: %s\n") %
77 (inst.desc or inst.filename, inst.strerror))
78 (inst.desc or inst.filename, inst.strerror))
78 except revlog.RevlogError, inst:
79 except revlog.RevlogError, inst:
79 ui.warn(_("abort: %s!\n") % inst)
80 ui.warn(_("abort: %s!\n") % inst)
80 except util.SignalInterrupt:
81 except util.SignalInterrupt:
81 ui.warn(_("killed!\n"))
82 ui.warn(_("killed!\n"))
82 except KeyboardInterrupt:
83 except KeyboardInterrupt:
83 try:
84 try:
84 ui.warn(_("interrupted!\n"))
85 ui.warn(_("interrupted!\n"))
85 except IOError, inst:
86 except IOError, inst:
86 if inst.errno == errno.EPIPE:
87 if inst.errno == errno.EPIPE:
87 if ui.debugflag:
88 if ui.debugflag:
88 ui.warn(_("\nbroken pipe\n"))
89 ui.warn(_("\nbroken pipe\n"))
89 else:
90 else:
90 raise
91 raise
91 except socket.error, inst:
92 except socket.error, inst:
92 ui.warn(_("abort: %s\n") % inst[1])
93 ui.warn(_("abort: %s\n") % inst[1])
93 except IOError, inst:
94 except IOError, inst:
94 if hasattr(inst, "code"):
95 if hasattr(inst, "code"):
95 ui.warn(_("abort: %s\n") % inst)
96 ui.warn(_("abort: %s\n") % inst)
96 elif hasattr(inst, "reason"):
97 elif hasattr(inst, "reason"):
97 try: # usually it is in the form (errno, strerror)
98 try: # usually it is in the form (errno, strerror)
98 reason = inst.reason.args[1]
99 reason = inst.reason.args[1]
99 except: # it might be anything, for example a string
100 except: # it might be anything, for example a string
100 reason = inst.reason
101 reason = inst.reason
101 ui.warn(_("abort: error: %s\n") % reason)
102 ui.warn(_("abort: error: %s\n") % reason)
102 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
103 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
103 if ui.debugflag:
104 if ui.debugflag:
104 ui.warn(_("broken pipe\n"))
105 ui.warn(_("broken pipe\n"))
105 elif getattr(inst, "strerror", None):
106 elif getattr(inst, "strerror", None):
106 if getattr(inst, "filename", None):
107 if getattr(inst, "filename", None):
107 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
108 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
108 else:
109 else:
109 ui.warn(_("abort: %s\n") % inst.strerror)
110 ui.warn(_("abort: %s\n") % inst.strerror)
110 else:
111 else:
111 raise
112 raise
112 except OSError, inst:
113 except OSError, inst:
113 if getattr(inst, "filename", None):
114 if getattr(inst, "filename", None):
114 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
115 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
115 else:
116 else:
116 ui.warn(_("abort: %s\n") % inst.strerror)
117 ui.warn(_("abort: %s\n") % inst.strerror)
117 except util.UnexpectedOutput, inst:
118 except util.UnexpectedOutput, inst:
118 ui.warn(_("abort: %s") % inst[0])
119 ui.warn(_("abort: %s") % inst[0])
119 if not isinstance(inst[1], basestring):
120 if not isinstance(inst[1], basestring):
120 ui.warn(" %r\n" % (inst[1],))
121 ui.warn(" %r\n" % (inst[1],))
121 elif not inst[1]:
122 elif not inst[1]:
122 ui.warn(_(" empty string\n"))
123 ui.warn(_(" empty string\n"))
123 else:
124 else:
124 ui.warn("\n%r\n" % util.ellipsis(inst[1]))
125 ui.warn("\n%r\n" % util.ellipsis(inst[1]))
125 except ImportError, inst:
126 except ImportError, inst:
126 m = str(inst).split()[-1]
127 m = str(inst).split()[-1]
127 ui.warn(_("abort: could not import module %s!\n") % m)
128 ui.warn(_("abort: could not import module %s!\n") % m)
128 if m in "mpatch bdiff".split():
129 if m in "mpatch bdiff".split():
129 ui.warn(_("(did you forget to compile extensions?)\n"))
130 ui.warn(_("(did you forget to compile extensions?)\n"))
130 elif m in "zlib".split():
131 elif m in "zlib".split():
131 ui.warn(_("(is your Python install correct?)\n"))
132 ui.warn(_("(is your Python install correct?)\n"))
132
133
133 except util.Abort, inst:
134 except util.Abort, inst:
134 ui.warn(_("abort: %s\n") % inst)
135 ui.warn(_("abort: %s\n") % inst)
135 except MemoryError:
136 except MemoryError:
136 ui.warn(_("abort: out of memory\n"))
137 ui.warn(_("abort: out of memory\n"))
137 except SystemExit, inst:
138 except SystemExit, inst:
138 # Commands shouldn't sys.exit directly, but give a return code.
139 # Commands shouldn't sys.exit directly, but give a return code.
139 # Just in case catch this and and pass exit code to caller.
140 # Just in case catch this and and pass exit code to caller.
140 return inst.code
141 return inst.code
141 except:
142 except:
142 ui.warn(_("** unknown exception encountered, details follow\n"))
143 ui.warn(_("** unknown exception encountered, details follow\n"))
143 ui.warn(_("** report bug details to "
144 ui.warn(_("** report bug details to "
144 "http://www.selenic.com/mercurial/bts\n"))
145 "http://www.selenic.com/mercurial/bts\n"))
145 ui.warn(_("** or mercurial@selenic.com\n"))
146 ui.warn(_("** or mercurial@selenic.com\n"))
146 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
147 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
147 % version.get_version())
148 % version.get_version())
148 raise
149 raise
149
150
150 return -1
151 return -1
151
152
152 def _findrepo(p):
153 def _findrepo(p):
153 while not os.path.isdir(os.path.join(p, ".hg")):
154 while not os.path.isdir(os.path.join(p, ".hg")):
154 oldp, p = p, os.path.dirname(p)
155 oldp, p = p, os.path.dirname(p)
155 if p == oldp:
156 if p == oldp:
156 return None
157 return None
157
158
158 return p
159 return p
159
160
160 def _parse(ui, args):
161 def _parse(ui, args):
161 options = {}
162 options = {}
162 cmdoptions = {}
163 cmdoptions = {}
163
164
164 try:
165 try:
165 args = fancyopts.fancyopts(args, commands.globalopts, options)
166 args = fancyopts.fancyopts(args, commands.globalopts, options)
166 except fancyopts.getopt.GetoptError, inst:
167 except fancyopts.getopt.GetoptError, inst:
167 raise ParseError(None, inst)
168 raise ParseError(None, inst)
168
169
169 if args:
170 if args:
170 cmd, args = args[0], args[1:]
171 cmd, args = args[0], args[1:]
171 aliases, i = cmdutil.findcmd(ui, cmd, commands.table)
172 aliases, i = cmdutil.findcmd(ui, cmd, commands.table)
172 cmd = aliases[0]
173 cmd = aliases[0]
173 defaults = ui.config("defaults", cmd)
174 defaults = ui.config("defaults", cmd)
174 if defaults:
175 if defaults:
175 args = shlex.split(defaults) + args
176 args = shlex.split(defaults) + args
176 c = list(i[1])
177 c = list(i[1])
177 else:
178 else:
178 cmd = None
179 cmd = None
179 c = []
180 c = []
180
181
181 # combine global options into local
182 # combine global options into local
182 for o in commands.globalopts:
183 for o in commands.globalopts:
183 c.append((o[0], o[1], options[o[1]], o[3]))
184 c.append((o[0], o[1], options[o[1]], o[3]))
184
185
185 try:
186 try:
186 args = fancyopts.fancyopts(args, c, cmdoptions)
187 args = fancyopts.fancyopts(args, c, cmdoptions)
187 except fancyopts.getopt.GetoptError, inst:
188 except fancyopts.getopt.GetoptError, inst:
188 raise ParseError(cmd, inst)
189 raise ParseError(cmd, inst)
189
190
190 # separate global options back out
191 # separate global options back out
191 for o in commands.globalopts:
192 for o in commands.globalopts:
192 n = o[1]
193 n = o[1]
193 options[n] = cmdoptions[n]
194 options[n] = cmdoptions[n]
194 del cmdoptions[n]
195 del cmdoptions[n]
195
196
196 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
197 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
197
198
198 def _parseconfig(config):
199 def _parseconfig(config):
199 """parse the --config options from the command line"""
200 """parse the --config options from the command line"""
200 parsed = []
201 parsed = []
201 for cfg in config:
202 for cfg in config:
202 try:
203 try:
203 name, value = cfg.split('=', 1)
204 name, value = cfg.split('=', 1)
204 section, name = name.split('.', 1)
205 section, name = name.split('.', 1)
205 if not section or not name:
206 if not section or not name:
206 raise IndexError
207 raise IndexError
207 parsed.append((section, name, value))
208 parsed.append((section, name, value))
208 except (IndexError, ValueError):
209 except (IndexError, ValueError):
209 raise util.Abort(_('malformed --config option: %s') % cfg)
210 raise util.Abort(_('malformed --config option: %s') % cfg)
210 return parsed
211 return parsed
211
212
212 def _earlygetopt(aliases, args):
213 def _earlygetopt(aliases, args):
213 """Return list of values for an option (or aliases).
214 """Return list of values for an option (or aliases).
214
215
215 The values are listed in the order they appear in args.
216 The values are listed in the order they appear in args.
216 The options and values are removed from args.
217 The options and values are removed from args.
217 """
218 """
218 try:
219 try:
219 argcount = args.index("--")
220 argcount = args.index("--")
220 except ValueError:
221 except ValueError:
221 argcount = len(args)
222 argcount = len(args)
222 shortopts = [opt for opt in aliases if len(opt) == 2]
223 shortopts = [opt for opt in aliases if len(opt) == 2]
223 values = []
224 values = []
224 pos = 0
225 pos = 0
225 while pos < argcount:
226 while pos < argcount:
226 if args[pos] in aliases:
227 if args[pos] in aliases:
227 if pos + 1 >= argcount:
228 if pos + 1 >= argcount:
228 # ignore and let getopt report an error if there is no value
229 # ignore and let getopt report an error if there is no value
229 break
230 break
230 del args[pos]
231 del args[pos]
231 values.append(args.pop(pos))
232 values.append(args.pop(pos))
232 argcount -= 2
233 argcount -= 2
233 elif args[pos][:2] in shortopts:
234 elif args[pos][:2] in shortopts:
234 # short option can have no following space, e.g. hg log -Rfoo
235 # short option can have no following space, e.g. hg log -Rfoo
235 values.append(args.pop(pos)[2:])
236 values.append(args.pop(pos)[2:])
236 argcount -= 1
237 argcount -= 1
237 else:
238 else:
238 pos += 1
239 pos += 1
239 return values
240 return values
240
241
241 _loaded = {}
242 _loaded = {}
242 def _dispatch(ui, args):
243 def _dispatch(ui, args):
243 # read --config before doing anything else
244 # read --config before doing anything else
244 # (e.g. to change trust settings for reading .hg/hgrc)
245 # (e.g. to change trust settings for reading .hg/hgrc)
245 config = _earlygetopt(['--config'], args)
246 config = _earlygetopt(['--config'], args)
246 if config:
247 if config:
247 ui.updateopts(config=_parseconfig(config))
248 ui.updateopts(config=_parseconfig(config))
248
249
249 # check for cwd
250 # check for cwd
250 cwd = _earlygetopt(['--cwd'], args)
251 cwd = _earlygetopt(['--cwd'], args)
251 if cwd:
252 if cwd:
252 os.chdir(cwd[-1])
253 os.chdir(cwd[-1])
253
254
254 # read the local repository .hgrc into a local ui object
255 # read the local repository .hgrc into a local ui object
255 path = _findrepo(os.getcwd()) or ""
256 path = _findrepo(os.getcwd()) or ""
256 if not path:
257 if not path:
257 lui = ui
258 lui = ui
258 if path:
259 if path:
259 try:
260 try:
260 lui = _ui.ui(parentui=ui)
261 lui = _ui.ui(parentui=ui)
261 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
262 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
262 except IOError:
263 except IOError:
263 pass
264 pass
264
265
265 # now we can expand paths, even ones in .hg/hgrc
266 # now we can expand paths, even ones in .hg/hgrc
266 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
267 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
267 if rpath:
268 if rpath:
268 path = lui.expandpath(rpath[-1])
269 path = lui.expandpath(rpath[-1])
269 lui = _ui.ui(parentui=ui)
270 lui = _ui.ui(parentui=ui)
270 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
271 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
271
272
272 extensions.loadall(lui)
273 extensions.loadall(lui)
273 for name, module in extensions.extensions():
274 for name, module in extensions.extensions():
274 if name in _loaded:
275 if name in _loaded:
275 continue
276 continue
276
277
277 # setup extensions
278 # setup extensions
278 # TODO this should be generalized to scheme, where extensions can
279 # TODO this should be generalized to scheme, where extensions can
279 # redepend on other extensions. then we should toposort them, and
280 # redepend on other extensions. then we should toposort them, and
280 # do initialization in correct order
281 # do initialization in correct order
281 extsetup = getattr(module, 'extsetup', None)
282 extsetup = getattr(module, 'extsetup', None)
282 if extsetup:
283 if extsetup:
283 extsetup()
284 extsetup()
284
285
285 cmdtable = getattr(module, 'cmdtable', {})
286 cmdtable = getattr(module, 'cmdtable', {})
286 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
287 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
287 if overrides:
288 if overrides:
288 ui.warn(_("extension '%s' overrides commands: %s\n")
289 ui.warn(_("extension '%s' overrides commands: %s\n")
289 % (name, " ".join(overrides)))
290 % (name, " ".join(overrides)))
290 commands.table.update(cmdtable)
291 commands.table.update(cmdtable)
291 _loaded[name] = 1
292 _loaded[name] = 1
292 # check for fallback encoding
293 # check for fallback encoding
293 fallback = lui.config('ui', 'fallbackencoding')
294 fallback = lui.config('ui', 'fallbackencoding')
294 if fallback:
295 if fallback:
295 util._fallbackencoding = fallback
296 util._fallbackencoding = fallback
296
297
297 fullargs = args
298 fullargs = args
298 cmd, func, args, options, cmdoptions = _parse(lui, args)
299 cmd, func, args, options, cmdoptions = _parse(lui, args)
299
300
300 if options["config"]:
301 if options["config"]:
301 raise util.Abort(_("Option --config may not be abbreviated!"))
302 raise util.Abort(_("Option --config may not be abbreviated!"))
302 if options["cwd"]:
303 if options["cwd"]:
303 raise util.Abort(_("Option --cwd may not be abbreviated!"))
304 raise util.Abort(_("Option --cwd may not be abbreviated!"))
304 if options["repository"]:
305 if options["repository"]:
305 raise util.Abort(_(
306 raise util.Abort(_(
306 "Option -R has to be separated from other options (i.e. not -qR) "
307 "Option -R has to be separated from other options (i.e. not -qR) "
307 "and --repository may only be abbreviated as --repo!"))
308 "and --repository may only be abbreviated as --repo!"))
308
309
309 if options["encoding"]:
310 if options["encoding"]:
310 util._encoding = options["encoding"]
311 util._encoding = options["encoding"]
311 if options["encodingmode"]:
312 if options["encodingmode"]:
312 util._encodingmode = options["encodingmode"]
313 util._encodingmode = options["encodingmode"]
313 if options["time"]:
314 if options["time"]:
314 def get_times():
315 def get_times():
315 t = os.times()
316 t = os.times()
316 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
317 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
317 t = (t[0], t[1], t[2], t[3], time.clock())
318 t = (t[0], t[1], t[2], t[3], time.clock())
318 return t
319 return t
319 s = get_times()
320 s = get_times()
320 def print_time():
321 def print_time():
321 t = get_times()
322 t = get_times()
322 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
323 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
323 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
324 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
324 atexit.register(print_time)
325 atexit.register(print_time)
325
326
326 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
327 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
327 not options["noninteractive"], options["traceback"])
328 not options["noninteractive"], options["traceback"])
328
329
329 if options['help']:
330 if options['help']:
330 return commands.help_(ui, cmd, options['version'])
331 return commands.help_(ui, cmd, options['version'])
331 elif options['version']:
332 elif options['version']:
332 return commands.version_(ui)
333 return commands.version_(ui)
333 elif not cmd:
334 elif not cmd:
334 return commands.help_(ui, 'shortlist')
335 return commands.help_(ui, 'shortlist')
335
336
336 repo = None
337 repo = None
337 if cmd not in commands.norepo.split():
338 if cmd not in commands.norepo.split():
338 try:
339 try:
339 repo = hg.repository(ui, path=path)
340 repo = hg.repository(ui, path=path)
340 ui = repo.ui
341 ui = repo.ui
341 if not repo.local():
342 if not repo.local():
342 raise util.Abort(_("repository '%s' is not local") % path)
343 raise util.Abort(_("repository '%s' is not local") % path)
343 ui.setconfig("bundle", "mainreporoot", repo.root)
344 ui.setconfig("bundle", "mainreporoot", repo.root)
344 except hg.RepoError:
345 except RepoError:
345 if cmd not in commands.optionalrepo.split():
346 if cmd not in commands.optionalrepo.split():
346 if args and not path: # try to infer -R from command args
347 if args and not path: # try to infer -R from command args
347 repos = map(_findrepo, args)
348 repos = map(_findrepo, args)
348 guess = repos[0]
349 guess = repos[0]
349 if guess and repos.count(guess) == len(repos):
350 if guess and repos.count(guess) == len(repos):
350 return _dispatch(ui, ['--repository', guess] + fullargs)
351 return _dispatch(ui, ['--repository', guess] + fullargs)
351 if not path:
352 if not path:
352 raise hg.RepoError(_("There is no Mercurial repository here"
353 raise RepoError(_("There is no Mercurial repository here"
353 " (.hg not found)"))
354 " (.hg not found)"))
354 raise
355 raise
355 d = lambda: func(ui, repo, *args, **cmdoptions)
356 d = lambda: func(ui, repo, *args, **cmdoptions)
356 else:
357 else:
357 d = lambda: func(ui, *args, **cmdoptions)
358 d = lambda: func(ui, *args, **cmdoptions)
358
359
359 # run pre-hook, and abort if it fails
360 # run pre-hook, and abort if it fails
360 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
361 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
361 if ret:
362 if ret:
362 return ret
363 return ret
363 ret = _runcommand(ui, options, cmd, d)
364 ret = _runcommand(ui, options, cmd, d)
364 # run post-hook, passing command result
365 # run post-hook, passing command result
365 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
366 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
366 result = ret)
367 result = ret)
367 return ret
368 return ret
368
369
369 def _runcommand(ui, options, cmd, cmdfunc):
370 def _runcommand(ui, options, cmd, cmdfunc):
370 def checkargs():
371 def checkargs():
371 try:
372 try:
372 return cmdfunc()
373 return cmdfunc()
373 except TypeError, inst:
374 except TypeError, inst:
374 # was this an argument error?
375 # was this an argument error?
375 tb = traceback.extract_tb(sys.exc_info()[2])
376 tb = traceback.extract_tb(sys.exc_info()[2])
376 if len(tb) != 2: # no
377 if len(tb) != 2: # no
377 raise
378 raise
378 raise ParseError(cmd, _("invalid arguments"))
379 raise ParseError(cmd, _("invalid arguments"))
379
380
380 if options['profile']:
381 if options['profile']:
381 import hotshot, hotshot.stats
382 import hotshot, hotshot.stats
382 prof = hotshot.Profile("hg.prof")
383 prof = hotshot.Profile("hg.prof")
383 try:
384 try:
384 try:
385 try:
385 return prof.runcall(checkargs)
386 return prof.runcall(checkargs)
386 except:
387 except:
387 try:
388 try:
388 ui.warn(_('exception raised - generating '
389 ui.warn(_('exception raised - generating '
389 'profile anyway\n'))
390 'profile anyway\n'))
390 except:
391 except:
391 pass
392 pass
392 raise
393 raise
393 finally:
394 finally:
394 prof.close()
395 prof.close()
395 stats = hotshot.stats.load("hg.prof")
396 stats = hotshot.stats.load("hg.prof")
396 stats.strip_dirs()
397 stats.strip_dirs()
397 stats.sort_stats('time', 'calls')
398 stats.sort_stats('time', 'calls')
398 stats.print_stats(40)
399 stats.print_stats(40)
399 elif options['lsprof']:
400 elif options['lsprof']:
400 try:
401 try:
401 from mercurial import lsprof
402 from mercurial import lsprof
402 except ImportError:
403 except ImportError:
403 raise util.Abort(_(
404 raise util.Abort(_(
404 'lsprof not available - install from '
405 'lsprof not available - install from '
405 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
406 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
406 p = lsprof.Profiler()
407 p = lsprof.Profiler()
407 p.enable(subcalls=True)
408 p.enable(subcalls=True)
408 try:
409 try:
409 return checkargs()
410 return checkargs()
410 finally:
411 finally:
411 p.disable()
412 p.disable()
412 stats = lsprof.Stats(p.getstats())
413 stats = lsprof.Stats(p.getstats())
413 stats.sort()
414 stats.sort()
414 stats.pprint(top=10, file=sys.stderr, climit=5)
415 stats.pprint(top=10, file=sys.stderr, climit=5)
415 else:
416 else:
416 return checkargs()
417 return checkargs()
@@ -1,105 +1,106 b''
1 # changelog bisection for mercurial
1 # changelog bisection for mercurial
2 #
2 #
3 # Copyright 2007 Matt Mackall
3 # Copyright 2007 Matt Mackall
4 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
4 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
5 # Inspired by git bisect, extension skeleton taken from mq.py.
5 # Inspired by git bisect, extension skeleton taken from mq.py.
6 #
6 #
7 # This software may be used and distributed according to the terms
7 # This software may be used and distributed according to the terms
8 # of the GNU General Public License, incorporated herein by reference.
8 # of the GNU General Public License, incorporated herein by reference.
9
9
10 from i18n import _
10 from i18n import _
11 import hg, util
11 from node import short
12 import util
12
13
13 def bisect(changelog, state):
14 def bisect(changelog, state):
14 clparents = changelog.parentrevs
15 clparents = changelog.parentrevs
15 skip = dict.fromkeys([changelog.rev(n) for n in state['skip']])
16 skip = dict.fromkeys([changelog.rev(n) for n in state['skip']])
16
17
17 def buildancestors(bad, good):
18 def buildancestors(bad, good):
18 # only the earliest bad revision matters
19 # only the earliest bad revision matters
19 badrev = min([changelog.rev(n) for n in bad])
20 badrev = min([changelog.rev(n) for n in bad])
20 goodrevs = [changelog.rev(n) for n in good]
21 goodrevs = [changelog.rev(n) for n in good]
21 # build ancestors array
22 # build ancestors array
22 ancestors = [[]] * (changelog.count() + 1) # an extra for [-1]
23 ancestors = [[]] * (changelog.count() + 1) # an extra for [-1]
23
24
24 # clear good revs from array
25 # clear good revs from array
25 for node in goodrevs:
26 for node in goodrevs:
26 ancestors[node] = None
27 ancestors[node] = None
27 for rev in xrange(changelog.count(), -1, -1):
28 for rev in xrange(changelog.count(), -1, -1):
28 if ancestors[rev] is None:
29 if ancestors[rev] is None:
29 for prev in clparents(rev):
30 for prev in clparents(rev):
30 ancestors[prev] = None
31 ancestors[prev] = None
31
32
32 if ancestors[badrev] is None:
33 if ancestors[badrev] is None:
33 return badrev, None
34 return badrev, None
34 return badrev, ancestors
35 return badrev, ancestors
35
36
36 good = 0
37 good = 0
37 badrev, ancestors = buildancestors(state['bad'], state['good'])
38 badrev, ancestors = buildancestors(state['bad'], state['good'])
38 if not ancestors: # looking for bad to good transition?
39 if not ancestors: # looking for bad to good transition?
39 good = 1
40 good = 1
40 badrev, ancestors = buildancestors(state['good'], state['bad'])
41 badrev, ancestors = buildancestors(state['good'], state['bad'])
41 bad = changelog.node(badrev)
42 bad = changelog.node(badrev)
42 if not ancestors: # now we're confused
43 if not ancestors: # now we're confused
43 raise util.Abort(_("Inconsistent state, %s:%s is good and bad")
44 raise util.Abort(_("Inconsistent state, %s:%s is good and bad")
44 % (badrev, hg.short(bad)))
45 % (badrev, short(bad)))
45
46
46 # build children dict
47 # build children dict
47 children = {}
48 children = {}
48 visit = [badrev]
49 visit = [badrev]
49 candidates = []
50 candidates = []
50 while visit:
51 while visit:
51 rev = visit.pop(0)
52 rev = visit.pop(0)
52 if ancestors[rev] == []:
53 if ancestors[rev] == []:
53 candidates.append(rev)
54 candidates.append(rev)
54 for prev in clparents(rev):
55 for prev in clparents(rev):
55 if prev != -1:
56 if prev != -1:
56 if prev in children:
57 if prev in children:
57 children[prev].append(rev)
58 children[prev].append(rev)
58 else:
59 else:
59 children[prev] = [rev]
60 children[prev] = [rev]
60 visit.append(prev)
61 visit.append(prev)
61
62
62 candidates.sort()
63 candidates.sort()
63 # have we narrowed it down to one entry?
64 # have we narrowed it down to one entry?
64 tot = len(candidates)
65 tot = len(candidates)
65 if tot == 1:
66 if tot == 1:
66 return (bad, 0, good)
67 return (bad, 0, good)
67 perfect = tot / 2
68 perfect = tot / 2
68
69
69 # find the best node to test
70 # find the best node to test
70 best_rev = None
71 best_rev = None
71 best_len = -1
72 best_len = -1
72 poison = {}
73 poison = {}
73 for rev in candidates:
74 for rev in candidates:
74 if rev in poison:
75 if rev in poison:
75 for c in children.get(rev, []):
76 for c in children.get(rev, []):
76 poison[c] = True # poison children
77 poison[c] = True # poison children
77 continue
78 continue
78
79
79 a = ancestors[rev] or [rev]
80 a = ancestors[rev] or [rev]
80 ancestors[rev] = None
81 ancestors[rev] = None
81
82
82 x = len(a) # number of ancestors
83 x = len(a) # number of ancestors
83 y = tot - x # number of non-ancestors
84 y = tot - x # number of non-ancestors
84 value = min(x, y) # how good is this test?
85 value = min(x, y) # how good is this test?
85 if value > best_len and rev not in skip:
86 if value > best_len and rev not in skip:
86 best_len = value
87 best_len = value
87 best_rev = rev
88 best_rev = rev
88 if value == perfect: # found a perfect candidate? quit early
89 if value == perfect: # found a perfect candidate? quit early
89 break
90 break
90
91
91 if y < perfect: # all downhill from here?
92 if y < perfect: # all downhill from here?
92 for c in children.get(rev, []):
93 for c in children.get(rev, []):
93 poison[c] = True # poison children
94 poison[c] = True # poison children
94 continue
95 continue
95
96
96 for c in children.get(rev, []):
97 for c in children.get(rev, []):
97 if ancestors[c]:
98 if ancestors[c]:
98 ancestors[c] = dict.fromkeys(ancestors[c] + a).keys()
99 ancestors[c] = dict.fromkeys(ancestors[c] + a).keys()
99 else:
100 else:
100 ancestors[c] = a + [c]
101 ancestors[c] = a + [c]
101
102
102 assert best_rev is not None
103 assert best_rev is not None
103 best_node = changelog.node(best_rev)
104 best_node = changelog.node(best_rev)
104
105
105 return (best_node, tot, good)
106 return (best_node, tot, good)
@@ -1,313 +1,311 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from node import bin, hex, nullid, nullrev, short
10 from repo import NoCapability, RepoError
11 from i18n import _
9 from i18n import _
12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
10 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
13 import errno, lock, os, shutil, util, extensions
11 import errno, lock, os, shutil, util, extensions
14 import merge as _merge
12 import merge as _merge
15 import verify as _verify
13 import verify as _verify
16
14
17 def _local(path):
15 def _local(path):
18 return (os.path.isfile(util.drop_scheme('file', path)) and
16 return (os.path.isfile(util.drop_scheme('file', path)) and
19 bundlerepo or localrepo)
17 bundlerepo or localrepo)
20
18
21 def parseurl(url, revs):
19 def parseurl(url, revs):
22 '''parse url#branch, returning url, branch + revs'''
20 '''parse url#branch, returning url, branch + revs'''
23
21
24 if '#' not in url:
22 if '#' not in url:
25 return url, (revs or None), None
23 return url, (revs or None), None
26
24
27 url, rev = url.split('#', 1)
25 url, rev = url.split('#', 1)
28 return url, revs + [rev], rev
26 return url, revs + [rev], rev
29
27
30 schemes = {
28 schemes = {
31 'bundle': bundlerepo,
29 'bundle': bundlerepo,
32 'file': _local,
30 'file': _local,
33 'http': httprepo,
31 'http': httprepo,
34 'https': httprepo,
32 'https': httprepo,
35 'ssh': sshrepo,
33 'ssh': sshrepo,
36 'static-http': statichttprepo,
34 'static-http': statichttprepo,
37 }
35 }
38
36
39 def _lookup(path):
37 def _lookup(path):
40 scheme = 'file'
38 scheme = 'file'
41 if path:
39 if path:
42 c = path.find(':')
40 c = path.find(':')
43 if c > 0:
41 if c > 0:
44 scheme = path[:c]
42 scheme = path[:c]
45 thing = schemes.get(scheme) or schemes['file']
43 thing = schemes.get(scheme) or schemes['file']
46 try:
44 try:
47 return thing(path)
45 return thing(path)
48 except TypeError:
46 except TypeError:
49 return thing
47 return thing
50
48
51 def islocal(repo):
49 def islocal(repo):
52 '''return true if repo or path is local'''
50 '''return true if repo or path is local'''
53 if isinstance(repo, str):
51 if isinstance(repo, str):
54 try:
52 try:
55 return _lookup(repo).islocal(repo)
53 return _lookup(repo).islocal(repo)
56 except AttributeError:
54 except AttributeError:
57 return False
55 return False
58 return repo.local()
56 return repo.local()
59
57
60 def repository(ui, path='', create=False):
58 def repository(ui, path='', create=False):
61 """return a repository object for the specified path"""
59 """return a repository object for the specified path"""
62 repo = _lookup(path).instance(ui, path, create)
60 repo = _lookup(path).instance(ui, path, create)
63 ui = getattr(repo, "ui", ui)
61 ui = getattr(repo, "ui", ui)
64 for name, module in extensions.extensions():
62 for name, module in extensions.extensions():
65 hook = getattr(module, 'reposetup', None)
63 hook = getattr(module, 'reposetup', None)
66 if hook:
64 if hook:
67 hook(ui, repo)
65 hook(ui, repo)
68 return repo
66 return repo
69
67
70 def defaultdest(source):
68 def defaultdest(source):
71 '''return default destination of clone if none is given'''
69 '''return default destination of clone if none is given'''
72 return os.path.basename(os.path.normpath(source))
70 return os.path.basename(os.path.normpath(source))
73
71
74 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
72 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
75 stream=False):
73 stream=False):
76 """Make a copy of an existing repository.
74 """Make a copy of an existing repository.
77
75
78 Create a copy of an existing repository in a new directory. The
76 Create a copy of an existing repository in a new directory. The
79 source and destination are URLs, as passed to the repository
77 source and destination are URLs, as passed to the repository
80 function. Returns a pair of repository objects, the source and
78 function. Returns a pair of repository objects, the source and
81 newly created destination.
79 newly created destination.
82
80
83 The location of the source is added to the new repository's
81 The location of the source is added to the new repository's
84 .hg/hgrc file, as the default to be used for future pulls and
82 .hg/hgrc file, as the default to be used for future pulls and
85 pushes.
83 pushes.
86
84
87 If an exception is raised, the partly cloned/updated destination
85 If an exception is raised, the partly cloned/updated destination
88 repository will be deleted.
86 repository will be deleted.
89
87
90 Arguments:
88 Arguments:
91
89
92 source: repository object or URL
90 source: repository object or URL
93
91
94 dest: URL of destination repository to create (defaults to base
92 dest: URL of destination repository to create (defaults to base
95 name of source repository)
93 name of source repository)
96
94
97 pull: always pull from source repository, even in local case
95 pull: always pull from source repository, even in local case
98
96
99 stream: stream raw data uncompressed from repository (fast over
97 stream: stream raw data uncompressed from repository (fast over
100 LAN, slow over WAN)
98 LAN, slow over WAN)
101
99
102 rev: revision to clone up to (implies pull=True)
100 rev: revision to clone up to (implies pull=True)
103
101
104 update: update working directory after clone completes, if
102 update: update working directory after clone completes, if
105 destination is local repository
103 destination is local repository
106 """
104 """
107
105
108 if isinstance(source, str):
106 if isinstance(source, str):
109 origsource = ui.expandpath(source)
107 origsource = ui.expandpath(source)
110 source, rev, checkout = parseurl(origsource, rev)
108 source, rev, checkout = parseurl(origsource, rev)
111 src_repo = repository(ui, source)
109 src_repo = repository(ui, source)
112 else:
110 else:
113 src_repo = source
111 src_repo = source
114 origsource = source = src_repo.url()
112 origsource = source = src_repo.url()
115 checkout = None
113 checkout = None
116
114
117 if dest is None:
115 if dest is None:
118 dest = defaultdest(source)
116 dest = defaultdest(source)
119 ui.status(_("destination directory: %s\n") % dest)
117 ui.status(_("destination directory: %s\n") % dest)
120
118
121 def localpath(path):
119 def localpath(path):
122 if path.startswith('file://localhost/'):
120 if path.startswith('file://localhost/'):
123 return path[16:]
121 return path[16:]
124 if path.startswith('file://'):
122 if path.startswith('file://'):
125 return path[7:]
123 return path[7:]
126 if path.startswith('file:'):
124 if path.startswith('file:'):
127 return path[5:]
125 return path[5:]
128 return path
126 return path
129
127
130 dest = localpath(dest)
128 dest = localpath(dest)
131 source = localpath(source)
129 source = localpath(source)
132
130
133 if os.path.exists(dest):
131 if os.path.exists(dest):
134 raise util.Abort(_("destination '%s' already exists") % dest)
132 raise util.Abort(_("destination '%s' already exists") % dest)
135
133
136 class DirCleanup(object):
134 class DirCleanup(object):
137 def __init__(self, dir_):
135 def __init__(self, dir_):
138 self.rmtree = shutil.rmtree
136 self.rmtree = shutil.rmtree
139 self.dir_ = dir_
137 self.dir_ = dir_
140 def close(self):
138 def close(self):
141 self.dir_ = None
139 self.dir_ = None
142 def __del__(self):
140 def __del__(self):
143 if self.dir_:
141 if self.dir_:
144 self.rmtree(self.dir_, True)
142 self.rmtree(self.dir_, True)
145
143
146 src_lock = dest_lock = dir_cleanup = None
144 src_lock = dest_lock = dir_cleanup = None
147 try:
145 try:
148 if islocal(dest):
146 if islocal(dest):
149 dir_cleanup = DirCleanup(dest)
147 dir_cleanup = DirCleanup(dest)
150
148
151 abspath = origsource
149 abspath = origsource
152 copy = False
150 copy = False
153 if src_repo.local() and islocal(dest):
151 if src_repo.local() and islocal(dest):
154 abspath = os.path.abspath(util.drop_scheme('file', origsource))
152 abspath = os.path.abspath(util.drop_scheme('file', origsource))
155 copy = not pull and not rev
153 copy = not pull and not rev
156
154
157 if copy:
155 if copy:
158 try:
156 try:
159 # we use a lock here because if we race with commit, we
157 # we use a lock here because if we race with commit, we
160 # can end up with extra data in the cloned revlogs that's
158 # can end up with extra data in the cloned revlogs that's
161 # not pointed to by changesets, thus causing verify to
159 # not pointed to by changesets, thus causing verify to
162 # fail
160 # fail
163 src_lock = src_repo.lock()
161 src_lock = src_repo.lock()
164 except lock.LockException:
162 except lock.LockException:
165 copy = False
163 copy = False
166
164
167 if copy:
165 if copy:
168 def force_copy(src, dst):
166 def force_copy(src, dst):
169 if not os.path.exists(src):
167 if not os.path.exists(src):
170 # Tolerate empty source repository and optional files
168 # Tolerate empty source repository and optional files
171 return
169 return
172 util.copyfiles(src, dst)
170 util.copyfiles(src, dst)
173
171
174 src_store = os.path.realpath(src_repo.spath)
172 src_store = os.path.realpath(src_repo.spath)
175 if not os.path.exists(dest):
173 if not os.path.exists(dest):
176 os.mkdir(dest)
174 os.mkdir(dest)
177 try:
175 try:
178 dest_path = os.path.realpath(os.path.join(dest, ".hg"))
176 dest_path = os.path.realpath(os.path.join(dest, ".hg"))
179 os.mkdir(dest_path)
177 os.mkdir(dest_path)
180 except OSError, inst:
178 except OSError, inst:
181 if inst.errno == errno.EEXIST:
179 if inst.errno == errno.EEXIST:
182 dir_cleanup.close()
180 dir_cleanup.close()
183 raise util.Abort(_("destination '%s' already exists")
181 raise util.Abort(_("destination '%s' already exists")
184 % dest)
182 % dest)
185 raise
183 raise
186 if src_repo.spath != src_repo.path:
184 if src_repo.spath != src_repo.path:
187 # XXX racy
185 # XXX racy
188 dummy_changelog = os.path.join(dest_path, "00changelog.i")
186 dummy_changelog = os.path.join(dest_path, "00changelog.i")
189 # copy the dummy changelog
187 # copy the dummy changelog
190 force_copy(src_repo.join("00changelog.i"), dummy_changelog)
188 force_copy(src_repo.join("00changelog.i"), dummy_changelog)
191 dest_store = os.path.join(dest_path, "store")
189 dest_store = os.path.join(dest_path, "store")
192 os.mkdir(dest_store)
190 os.mkdir(dest_store)
193 else:
191 else:
194 dest_store = dest_path
192 dest_store = dest_path
195 # copy the requires file
193 # copy the requires file
196 force_copy(src_repo.join("requires"),
194 force_copy(src_repo.join("requires"),
197 os.path.join(dest_path, "requires"))
195 os.path.join(dest_path, "requires"))
198 # we lock here to avoid premature writing to the target
196 # we lock here to avoid premature writing to the target
199 dest_lock = lock.lock(os.path.join(dest_store, "lock"))
197 dest_lock = lock.lock(os.path.join(dest_store, "lock"))
200
198
201 files = ("data",
199 files = ("data",
202 "00manifest.d", "00manifest.i",
200 "00manifest.d", "00manifest.i",
203 "00changelog.d", "00changelog.i")
201 "00changelog.d", "00changelog.i")
204 for f in files:
202 for f in files:
205 src = os.path.join(src_store, f)
203 src = os.path.join(src_store, f)
206 dst = os.path.join(dest_store, f)
204 dst = os.path.join(dest_store, f)
207 force_copy(src, dst)
205 force_copy(src, dst)
208
206
209 # we need to re-init the repo after manually copying the data
207 # we need to re-init the repo after manually copying the data
210 # into it
208 # into it
211 dest_repo = repository(ui, dest)
209 dest_repo = repository(ui, dest)
212
210
213 else:
211 else:
214 try:
212 try:
215 dest_repo = repository(ui, dest, create=True)
213 dest_repo = repository(ui, dest, create=True)
216 except OSError, inst:
214 except OSError, inst:
217 if inst.errno == errno.EEXIST:
215 if inst.errno == errno.EEXIST:
218 dir_cleanup.close()
216 dir_cleanup.close()
219 raise util.Abort(_("destination '%s' already exists")
217 raise util.Abort(_("destination '%s' already exists")
220 % dest)
218 % dest)
221 raise
219 raise
222
220
223 revs = None
221 revs = None
224 if rev:
222 if rev:
225 if 'lookup' not in src_repo.capabilities:
223 if 'lookup' not in src_repo.capabilities:
226 raise util.Abort(_("src repository does not support revision "
224 raise util.Abort(_("src repository does not support revision "
227 "lookup and so doesn't support clone by "
225 "lookup and so doesn't support clone by "
228 "revision"))
226 "revision"))
229 revs = [src_repo.lookup(r) for r in rev]
227 revs = [src_repo.lookup(r) for r in rev]
230
228
231 if dest_repo.local():
229 if dest_repo.local():
232 dest_repo.clone(src_repo, heads=revs, stream=stream)
230 dest_repo.clone(src_repo, heads=revs, stream=stream)
233 elif src_repo.local():
231 elif src_repo.local():
234 src_repo.push(dest_repo, revs=revs)
232 src_repo.push(dest_repo, revs=revs)
235 else:
233 else:
236 raise util.Abort(_("clone from remote to remote not supported"))
234 raise util.Abort(_("clone from remote to remote not supported"))
237
235
238 if dir_cleanup:
236 if dir_cleanup:
239 dir_cleanup.close()
237 dir_cleanup.close()
240
238
241 if dest_repo.local():
239 if dest_repo.local():
242 fp = dest_repo.opener("hgrc", "w", text=True)
240 fp = dest_repo.opener("hgrc", "w", text=True)
243 fp.write("[paths]\n")
241 fp.write("[paths]\n")
244 fp.write("default = %s\n" % abspath)
242 fp.write("default = %s\n" % abspath)
245 fp.close()
243 fp.close()
246
244
247 if update:
245 if update:
248 if not checkout:
246 if not checkout:
249 try:
247 try:
250 checkout = dest_repo.lookup("default")
248 checkout = dest_repo.lookup("default")
251 except:
249 except:
252 checkout = dest_repo.changelog.tip()
250 checkout = dest_repo.changelog.tip()
253 _update(dest_repo, checkout)
251 _update(dest_repo, checkout)
254
252
255 return src_repo, dest_repo
253 return src_repo, dest_repo
256 finally:
254 finally:
257 del src_lock, dest_lock, dir_cleanup
255 del src_lock, dest_lock, dir_cleanup
258
256
259 def _showstats(repo, stats):
257 def _showstats(repo, stats):
260 stats = ((stats[0], _("updated")),
258 stats = ((stats[0], _("updated")),
261 (stats[1], _("merged")),
259 (stats[1], _("merged")),
262 (stats[2], _("removed")),
260 (stats[2], _("removed")),
263 (stats[3], _("unresolved")))
261 (stats[3], _("unresolved")))
264 note = ", ".join([_("%d files %s") % s for s in stats])
262 note = ", ".join([_("%d files %s") % s for s in stats])
265 repo.ui.status("%s\n" % note)
263 repo.ui.status("%s\n" % note)
266
264
267 def _update(repo, node): return update(repo, node)
265 def _update(repo, node): return update(repo, node)
268
266
269 def update(repo, node):
267 def update(repo, node):
270 """update the working directory to node, merging linear changes"""
268 """update the working directory to node, merging linear changes"""
271 pl = repo.parents()
269 pl = repo.parents()
272 stats = _merge.update(repo, node, False, False, None)
270 stats = _merge.update(repo, node, False, False, None)
273 _showstats(repo, stats)
271 _showstats(repo, stats)
274 if stats[3]:
272 if stats[3]:
275 repo.ui.status(_("There are unresolved merges with"
273 repo.ui.status(_("There are unresolved merges with"
276 " locally modified files.\n"))
274 " locally modified files.\n"))
277 if stats[1]:
275 if stats[1]:
278 repo.ui.status(_("You can finish the partial merge using:\n"))
276 repo.ui.status(_("You can finish the partial merge using:\n"))
279 else:
277 else:
280 repo.ui.status(_("You can redo the full merge using:\n"))
278 repo.ui.status(_("You can redo the full merge using:\n"))
281 # len(pl)==1, otherwise _merge.update() would have raised util.Abort:
279 # len(pl)==1, otherwise _merge.update() would have raised util.Abort:
282 repo.ui.status(_(" hg update %s\n hg update %s\n")
280 repo.ui.status(_(" hg update %s\n hg update %s\n")
283 % (pl[0].rev(), repo.changectx(node).rev()))
281 % (pl[0].rev(), repo.changectx(node).rev()))
284 return stats[3] > 0
282 return stats[3] > 0
285
283
286 def clean(repo, node, show_stats=True):
284 def clean(repo, node, show_stats=True):
287 """forcibly switch the working directory to node, clobbering changes"""
285 """forcibly switch the working directory to node, clobbering changes"""
288 stats = _merge.update(repo, node, False, True, None)
286 stats = _merge.update(repo, node, False, True, None)
289 if show_stats: _showstats(repo, stats)
287 if show_stats: _showstats(repo, stats)
290 return stats[3] > 0
288 return stats[3] > 0
291
289
292 def merge(repo, node, force=None, remind=True):
290 def merge(repo, node, force=None, remind=True):
293 """branch merge with node, resolving changes"""
291 """branch merge with node, resolving changes"""
294 stats = _merge.update(repo, node, True, force, False)
292 stats = _merge.update(repo, node, True, force, False)
295 _showstats(repo, stats)
293 _showstats(repo, stats)
296 if stats[3]:
294 if stats[3]:
297 pl = repo.parents()
295 pl = repo.parents()
298 repo.ui.status(_("There are unresolved merges,"
296 repo.ui.status(_("There are unresolved merges,"
299 " you can redo the full merge using:\n"
297 " you can redo the full merge using:\n"
300 " hg update -C %s\n"
298 " hg update -C %s\n"
301 " hg merge %s\n")
299 " hg merge %s\n")
302 % (pl[0].rev(), pl[1].rev()))
300 % (pl[0].rev(), pl[1].rev()))
303 elif remind:
301 elif remind:
304 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
302 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
305 return stats[3] > 0
303 return stats[3] > 0
306
304
307 def revert(repo, node, choose):
305 def revert(repo, node, choose):
308 """revert changes to revision in node without updating dirstate"""
306 """revert changes to revision in node without updating dirstate"""
309 return _merge.update(repo, node, False, True, choose)[3] > 0
307 return _merge.update(repo, node, False, True, choose)[3] > 0
310
308
311 def verify(repo):
309 def verify(repo):
312 """verify the consistency of a repository"""
310 """verify the consistency of a repository"""
313 return _verify.verify(repo)
311 return _verify.verify(repo)
@@ -1,951 +1,952 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, mimetypes, re
9 import os, mimetypes, re
10 from mercurial.node import hex, nullid, short
10 from mercurial.node import hex, nullid, short
11 from mercurial.repo import RepoError
11 from mercurial import mdiff, ui, hg, util, archival, patch, hook
12 from mercurial import mdiff, ui, hg, util, archival, patch, hook
12 from mercurial import revlog, templater, templatefilters, changegroup
13 from mercurial import revlog, templater, templatefilters, changegroup
13 from common import get_mtime, style_map, paritygen, countgen, get_contact
14 from common import get_mtime, style_map, paritygen, countgen, get_contact
14 from common import ErrorResponse
15 from common import ErrorResponse
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from request import wsgirequest
17 from request import wsgirequest
17 import webcommands, protocol
18 import webcommands, protocol
18
19
19 shortcuts = {
20 shortcuts = {
20 'cl': [('cmd', ['changelog']), ('rev', None)],
21 'cl': [('cmd', ['changelog']), ('rev', None)],
21 'sl': [('cmd', ['shortlog']), ('rev', None)],
22 'sl': [('cmd', ['shortlog']), ('rev', None)],
22 'cs': [('cmd', ['changeset']), ('node', None)],
23 'cs': [('cmd', ['changeset']), ('node', None)],
23 'f': [('cmd', ['file']), ('filenode', None)],
24 'f': [('cmd', ['file']), ('filenode', None)],
24 'fl': [('cmd', ['filelog']), ('filenode', None)],
25 'fl': [('cmd', ['filelog']), ('filenode', None)],
25 'fd': [('cmd', ['filediff']), ('node', None)],
26 'fd': [('cmd', ['filediff']), ('node', None)],
26 'fa': [('cmd', ['annotate']), ('filenode', None)],
27 'fa': [('cmd', ['annotate']), ('filenode', None)],
27 'mf': [('cmd', ['manifest']), ('manifest', None)],
28 'mf': [('cmd', ['manifest']), ('manifest', None)],
28 'ca': [('cmd', ['archive']), ('node', None)],
29 'ca': [('cmd', ['archive']), ('node', None)],
29 'tags': [('cmd', ['tags'])],
30 'tags': [('cmd', ['tags'])],
30 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
31 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
31 'static': [('cmd', ['static']), ('file', None)]
32 'static': [('cmd', ['static']), ('file', None)]
32 }
33 }
33
34
34 def _up(p):
35 def _up(p):
35 if p[0] != "/":
36 if p[0] != "/":
36 p = "/" + p
37 p = "/" + p
37 if p[-1] == "/":
38 if p[-1] == "/":
38 p = p[:-1]
39 p = p[:-1]
39 up = os.path.dirname(p)
40 up = os.path.dirname(p)
40 if up == "/":
41 if up == "/":
41 return "/"
42 return "/"
42 return up + "/"
43 return up + "/"
43
44
44 def revnavgen(pos, pagelen, limit, nodefunc):
45 def revnavgen(pos, pagelen, limit, nodefunc):
45 def seq(factor, limit=None):
46 def seq(factor, limit=None):
46 if limit:
47 if limit:
47 yield limit
48 yield limit
48 if limit >= 20 and limit <= 40:
49 if limit >= 20 and limit <= 40:
49 yield 50
50 yield 50
50 else:
51 else:
51 yield 1 * factor
52 yield 1 * factor
52 yield 3 * factor
53 yield 3 * factor
53 for f in seq(factor * 10):
54 for f in seq(factor * 10):
54 yield f
55 yield f
55
56
56 def nav(**map):
57 def nav(**map):
57 l = []
58 l = []
58 last = 0
59 last = 0
59 for f in seq(1, pagelen):
60 for f in seq(1, pagelen):
60 if f < pagelen or f <= last:
61 if f < pagelen or f <= last:
61 continue
62 continue
62 if f > limit:
63 if f > limit:
63 break
64 break
64 last = f
65 last = f
65 if pos + f < limit:
66 if pos + f < limit:
66 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
67 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
67 if pos - f >= 0:
68 if pos - f >= 0:
68 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
69 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
69
70
70 try:
71 try:
71 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
72 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
72
73
73 for label, node in l:
74 for label, node in l:
74 yield {"label": label, "node": node}
75 yield {"label": label, "node": node}
75
76
76 yield {"label": "tip", "node": "tip"}
77 yield {"label": "tip", "node": "tip"}
77 except hg.RepoError:
78 except RepoError:
78 pass
79 pass
79
80
80 return nav
81 return nav
81
82
82 class hgweb(object):
83 class hgweb(object):
83 def __init__(self, repo, name=None):
84 def __init__(self, repo, name=None):
84 if isinstance(repo, str):
85 if isinstance(repo, str):
85 parentui = ui.ui(report_untrusted=False, interactive=False)
86 parentui = ui.ui(report_untrusted=False, interactive=False)
86 self.repo = hg.repository(parentui, repo)
87 self.repo = hg.repository(parentui, repo)
87 else:
88 else:
88 self.repo = repo
89 self.repo = repo
89
90
90 hook.redirect(True)
91 hook.redirect(True)
91 self.mtime = -1
92 self.mtime = -1
92 self.reponame = name
93 self.reponame = name
93 self.archives = 'zip', 'gz', 'bz2'
94 self.archives = 'zip', 'gz', 'bz2'
94 self.stripecount = 1
95 self.stripecount = 1
95 self._capabilities = None
96 self._capabilities = None
96 # a repo owner may set web.templates in .hg/hgrc to get any file
97 # a repo owner may set web.templates in .hg/hgrc to get any file
97 # readable by the user running the CGI script
98 # readable by the user running the CGI script
98 self.templatepath = self.config("web", "templates",
99 self.templatepath = self.config("web", "templates",
99 templater.templatepath(),
100 templater.templatepath(),
100 untrusted=False)
101 untrusted=False)
101
102
102 # The CGI scripts are often run by a user different from the repo owner.
103 # The CGI scripts are often run by a user different from the repo owner.
103 # Trust the settings from the .hg/hgrc files by default.
104 # Trust the settings from the .hg/hgrc files by default.
104 def config(self, section, name, default=None, untrusted=True):
105 def config(self, section, name, default=None, untrusted=True):
105 return self.repo.ui.config(section, name, default,
106 return self.repo.ui.config(section, name, default,
106 untrusted=untrusted)
107 untrusted=untrusted)
107
108
108 def configbool(self, section, name, default=False, untrusted=True):
109 def configbool(self, section, name, default=False, untrusted=True):
109 return self.repo.ui.configbool(section, name, default,
110 return self.repo.ui.configbool(section, name, default,
110 untrusted=untrusted)
111 untrusted=untrusted)
111
112
112 def configlist(self, section, name, default=None, untrusted=True):
113 def configlist(self, section, name, default=None, untrusted=True):
113 return self.repo.ui.configlist(section, name, default,
114 return self.repo.ui.configlist(section, name, default,
114 untrusted=untrusted)
115 untrusted=untrusted)
115
116
116 def refresh(self):
117 def refresh(self):
117 mtime = get_mtime(self.repo.root)
118 mtime = get_mtime(self.repo.root)
118 if mtime != self.mtime:
119 if mtime != self.mtime:
119 self.mtime = mtime
120 self.mtime = mtime
120 self.repo = hg.repository(self.repo.ui, self.repo.root)
121 self.repo = hg.repository(self.repo.ui, self.repo.root)
121 self.maxchanges = int(self.config("web", "maxchanges", 10))
122 self.maxchanges = int(self.config("web", "maxchanges", 10))
122 self.stripecount = int(self.config("web", "stripes", 1))
123 self.stripecount = int(self.config("web", "stripes", 1))
123 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
124 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
124 self.maxfiles = int(self.config("web", "maxfiles", 10))
125 self.maxfiles = int(self.config("web", "maxfiles", 10))
125 self.allowpull = self.configbool("web", "allowpull", True)
126 self.allowpull = self.configbool("web", "allowpull", True)
126 self.encoding = self.config("web", "encoding", util._encoding)
127 self.encoding = self.config("web", "encoding", util._encoding)
127 self._capabilities = None
128 self._capabilities = None
128
129
129 def capabilities(self):
130 def capabilities(self):
130 if self._capabilities is not None:
131 if self._capabilities is not None:
131 return self._capabilities
132 return self._capabilities
132 caps = ['lookup', 'changegroupsubset']
133 caps = ['lookup', 'changegroupsubset']
133 if self.configbool('server', 'uncompressed'):
134 if self.configbool('server', 'uncompressed'):
134 caps.append('stream=%d' % self.repo.changelog.version)
135 caps.append('stream=%d' % self.repo.changelog.version)
135 if changegroup.bundlepriority:
136 if changegroup.bundlepriority:
136 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
137 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
137 self._capabilities = caps
138 self._capabilities = caps
138 return caps
139 return caps
139
140
140 def run(self):
141 def run(self):
141 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
142 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
142 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
143 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
143 import mercurial.hgweb.wsgicgi as wsgicgi
144 import mercurial.hgweb.wsgicgi as wsgicgi
144 wsgicgi.launch(self)
145 wsgicgi.launch(self)
145
146
146 def __call__(self, env, respond):
147 def __call__(self, env, respond):
147 req = wsgirequest(env, respond)
148 req = wsgirequest(env, respond)
148 self.run_wsgi(req)
149 self.run_wsgi(req)
149 return req
150 return req
150
151
151 def run_wsgi(self, req):
152 def run_wsgi(self, req):
152
153
153 self.refresh()
154 self.refresh()
154
155
155 # expand form shortcuts
156 # expand form shortcuts
156
157
157 for k in shortcuts.iterkeys():
158 for k in shortcuts.iterkeys():
158 if k in req.form:
159 if k in req.form:
159 for name, value in shortcuts[k]:
160 for name, value in shortcuts[k]:
160 if value is None:
161 if value is None:
161 value = req.form[k]
162 value = req.form[k]
162 req.form[name] = value
163 req.form[name] = value
163 del req.form[k]
164 del req.form[k]
164
165
165 # work with CGI variables to create coherent structure
166 # work with CGI variables to create coherent structure
166 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
167 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
167
168
168 req.url = req.env['SCRIPT_NAME']
169 req.url = req.env['SCRIPT_NAME']
169 if not req.url.endswith('/'):
170 if not req.url.endswith('/'):
170 req.url += '/'
171 req.url += '/'
171 if 'REPO_NAME' in req.env:
172 if 'REPO_NAME' in req.env:
172 req.url += req.env['REPO_NAME'] + '/'
173 req.url += req.env['REPO_NAME'] + '/'
173
174
174 if req.env.get('PATH_INFO'):
175 if req.env.get('PATH_INFO'):
175 parts = req.env.get('PATH_INFO').strip('/').split('/')
176 parts = req.env.get('PATH_INFO').strip('/').split('/')
176 repo_parts = req.env.get('REPO_NAME', '').split('/')
177 repo_parts = req.env.get('REPO_NAME', '').split('/')
177 if parts[:len(repo_parts)] == repo_parts:
178 if parts[:len(repo_parts)] == repo_parts:
178 parts = parts[len(repo_parts):]
179 parts = parts[len(repo_parts):]
179 query = '/'.join(parts)
180 query = '/'.join(parts)
180 else:
181 else:
181 query = req.env['QUERY_STRING'].split('&', 1)[0]
182 query = req.env['QUERY_STRING'].split('&', 1)[0]
182 query = query.split(';', 1)[0]
183 query = query.split(';', 1)[0]
183
184
184 # translate user-visible url structure to internal structure
185 # translate user-visible url structure to internal structure
185
186
186 args = query.split('/', 2)
187 args = query.split('/', 2)
187 if 'cmd' not in req.form and args and args[0]:
188 if 'cmd' not in req.form and args and args[0]:
188
189
189 cmd = args.pop(0)
190 cmd = args.pop(0)
190 style = cmd.rfind('-')
191 style = cmd.rfind('-')
191 if style != -1:
192 if style != -1:
192 req.form['style'] = [cmd[:style]]
193 req.form['style'] = [cmd[:style]]
193 cmd = cmd[style+1:]
194 cmd = cmd[style+1:]
194
195
195 # avoid accepting e.g. style parameter as command
196 # avoid accepting e.g. style parameter as command
196 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
197 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
197 req.form['cmd'] = [cmd]
198 req.form['cmd'] = [cmd]
198
199
199 if args and args[0]:
200 if args and args[0]:
200 node = args.pop(0)
201 node = args.pop(0)
201 req.form['node'] = [node]
202 req.form['node'] = [node]
202 if args:
203 if args:
203 req.form['file'] = args
204 req.form['file'] = args
204
205
205 if cmd == 'static':
206 if cmd == 'static':
206 req.form['file'] = req.form['node']
207 req.form['file'] = req.form['node']
207 elif cmd == 'archive':
208 elif cmd == 'archive':
208 fn = req.form['node'][0]
209 fn = req.form['node'][0]
209 for type_, spec in self.archive_specs.iteritems():
210 for type_, spec in self.archive_specs.iteritems():
210 ext = spec[2]
211 ext = spec[2]
211 if fn.endswith(ext):
212 if fn.endswith(ext):
212 req.form['node'] = [fn[:-len(ext)]]
213 req.form['node'] = [fn[:-len(ext)]]
213 req.form['type'] = [type_]
214 req.form['type'] = [type_]
214
215
215 # process this if it's a protocol request
216 # process this if it's a protocol request
216
217
217 cmd = req.form.get('cmd', [''])[0]
218 cmd = req.form.get('cmd', [''])[0]
218 if cmd in protocol.__all__:
219 if cmd in protocol.__all__:
219 method = getattr(protocol, cmd)
220 method = getattr(protocol, cmd)
220 method(self, req)
221 method(self, req)
221 return
222 return
222
223
223 # process the web interface request
224 # process the web interface request
224
225
225 try:
226 try:
226
227
227 tmpl = self.templater(req)
228 tmpl = self.templater(req)
228 ctype = tmpl('mimetype', encoding=self.encoding)
229 ctype = tmpl('mimetype', encoding=self.encoding)
229 ctype = templater.stringify(ctype)
230 ctype = templater.stringify(ctype)
230
231
231 if cmd == '':
232 if cmd == '':
232 req.form['cmd'] = [tmpl.cache['default']]
233 req.form['cmd'] = [tmpl.cache['default']]
233 cmd = req.form['cmd'][0]
234 cmd = req.form['cmd'][0]
234
235
235 if cmd not in webcommands.__all__:
236 if cmd not in webcommands.__all__:
236 msg = 'No such method: %s' % cmd
237 msg = 'No such method: %s' % cmd
237 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
238 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
238 elif cmd == 'file' and 'raw' in req.form.get('style', []):
239 elif cmd == 'file' and 'raw' in req.form.get('style', []):
239 self.ctype = ctype
240 self.ctype = ctype
240 content = webcommands.rawfile(self, req, tmpl)
241 content = webcommands.rawfile(self, req, tmpl)
241 else:
242 else:
242 content = getattr(webcommands, cmd)(self, req, tmpl)
243 content = getattr(webcommands, cmd)(self, req, tmpl)
243 req.respond(HTTP_OK, ctype)
244 req.respond(HTTP_OK, ctype)
244
245
245 req.write(content)
246 req.write(content)
246 del tmpl
247 del tmpl
247
248
248 except revlog.LookupError, err:
249 except revlog.LookupError, err:
249 req.respond(HTTP_NOT_FOUND, ctype)
250 req.respond(HTTP_NOT_FOUND, ctype)
250 req.write(tmpl('error', error='revision not found: %s' % err.name))
251 req.write(tmpl('error', error='revision not found: %s' % err.name))
251 except (hg.RepoError, revlog.RevlogError), inst:
252 except (RepoError, revlog.RevlogError), inst:
252 req.respond(HTTP_SERVER_ERROR, ctype)
253 req.respond(HTTP_SERVER_ERROR, ctype)
253 req.write(tmpl('error', error=str(inst)))
254 req.write(tmpl('error', error=str(inst)))
254 except ErrorResponse, inst:
255 except ErrorResponse, inst:
255 req.respond(inst.code, ctype)
256 req.respond(inst.code, ctype)
256 req.write(tmpl('error', error=inst.message))
257 req.write(tmpl('error', error=inst.message))
257
258
258 def templater(self, req):
259 def templater(self, req):
259
260
260 # determine scheme, port and server name
261 # determine scheme, port and server name
261 # this is needed to create absolute urls
262 # this is needed to create absolute urls
262
263
263 proto = req.env.get('wsgi.url_scheme')
264 proto = req.env.get('wsgi.url_scheme')
264 if proto == 'https':
265 if proto == 'https':
265 proto = 'https'
266 proto = 'https'
266 default_port = "443"
267 default_port = "443"
267 else:
268 else:
268 proto = 'http'
269 proto = 'http'
269 default_port = "80"
270 default_port = "80"
270
271
271 port = req.env["SERVER_PORT"]
272 port = req.env["SERVER_PORT"]
272 port = port != default_port and (":" + port) or ""
273 port = port != default_port and (":" + port) or ""
273 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
274 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
274 staticurl = self.config("web", "staticurl") or req.url + 'static/'
275 staticurl = self.config("web", "staticurl") or req.url + 'static/'
275 if not staticurl.endswith('/'):
276 if not staticurl.endswith('/'):
276 staticurl += '/'
277 staticurl += '/'
277
278
278 # some functions for the templater
279 # some functions for the templater
279
280
280 def header(**map):
281 def header(**map):
281 yield tmpl('header', encoding=self.encoding, **map)
282 yield tmpl('header', encoding=self.encoding, **map)
282
283
283 def footer(**map):
284 def footer(**map):
284 yield tmpl("footer", **map)
285 yield tmpl("footer", **map)
285
286
286 def motd(**map):
287 def motd(**map):
287 yield self.config("web", "motd", "")
288 yield self.config("web", "motd", "")
288
289
289 def sessionvars(**map):
290 def sessionvars(**map):
290 fields = []
291 fields = []
291 if 'style' in req.form:
292 if 'style' in req.form:
292 style = req.form['style'][0]
293 style = req.form['style'][0]
293 if style != self.config('web', 'style', ''):
294 if style != self.config('web', 'style', ''):
294 fields.append(('style', style))
295 fields.append(('style', style))
295
296
296 separator = req.url[-1] == '?' and ';' or '?'
297 separator = req.url[-1] == '?' and ';' or '?'
297 for name, value in fields:
298 for name, value in fields:
298 yield dict(name=name, value=value, separator=separator)
299 yield dict(name=name, value=value, separator=separator)
299 separator = ';'
300 separator = ';'
300
301
301 # figure out which style to use
302 # figure out which style to use
302
303
303 style = self.config("web", "style", "")
304 style = self.config("web", "style", "")
304 if 'style' in req.form:
305 if 'style' in req.form:
305 style = req.form['style'][0]
306 style = req.form['style'][0]
306 mapfile = style_map(self.templatepath, style)
307 mapfile = style_map(self.templatepath, style)
307
308
308 if not self.reponame:
309 if not self.reponame:
309 self.reponame = (self.config("web", "name")
310 self.reponame = (self.config("web", "name")
310 or req.env.get('REPO_NAME')
311 or req.env.get('REPO_NAME')
311 or req.url.strip('/') or self.repo.root)
312 or req.url.strip('/') or self.repo.root)
312
313
313 # create the templater
314 # create the templater
314
315
315 tmpl = templater.templater(mapfile, templatefilters.filters,
316 tmpl = templater.templater(mapfile, templatefilters.filters,
316 defaults={"url": req.url,
317 defaults={"url": req.url,
317 "staticurl": staticurl,
318 "staticurl": staticurl,
318 "urlbase": urlbase,
319 "urlbase": urlbase,
319 "repo": self.reponame,
320 "repo": self.reponame,
320 "header": header,
321 "header": header,
321 "footer": footer,
322 "footer": footer,
322 "motd": motd,
323 "motd": motd,
323 "sessionvars": sessionvars
324 "sessionvars": sessionvars
324 })
325 })
325 return tmpl
326 return tmpl
326
327
327 def archivelist(self, nodeid):
328 def archivelist(self, nodeid):
328 allowed = self.configlist("web", "allow_archive")
329 allowed = self.configlist("web", "allow_archive")
329 for i, spec in self.archive_specs.iteritems():
330 for i, spec in self.archive_specs.iteritems():
330 if i in allowed or self.configbool("web", "allow" + i):
331 if i in allowed or self.configbool("web", "allow" + i):
331 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
332 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
332
333
333 def listfilediffs(self, tmpl, files, changeset):
334 def listfilediffs(self, tmpl, files, changeset):
334 for f in files[:self.maxfiles]:
335 for f in files[:self.maxfiles]:
335 yield tmpl("filedifflink", node=hex(changeset), file=f)
336 yield tmpl("filedifflink", node=hex(changeset), file=f)
336 if len(files) > self.maxfiles:
337 if len(files) > self.maxfiles:
337 yield tmpl("fileellipses")
338 yield tmpl("fileellipses")
338
339
339 def siblings(self, siblings=[], hiderev=None, **args):
340 def siblings(self, siblings=[], hiderev=None, **args):
340 siblings = [s for s in siblings if s.node() != nullid]
341 siblings = [s for s in siblings if s.node() != nullid]
341 if len(siblings) == 1 and siblings[0].rev() == hiderev:
342 if len(siblings) == 1 and siblings[0].rev() == hiderev:
342 return
343 return
343 for s in siblings:
344 for s in siblings:
344 d = {'node': hex(s.node()), 'rev': s.rev()}
345 d = {'node': hex(s.node()), 'rev': s.rev()}
345 if hasattr(s, 'path'):
346 if hasattr(s, 'path'):
346 d['file'] = s.path()
347 d['file'] = s.path()
347 d.update(args)
348 d.update(args)
348 yield d
349 yield d
349
350
350 def renamelink(self, fl, node):
351 def renamelink(self, fl, node):
351 r = fl.renamed(node)
352 r = fl.renamed(node)
352 if r:
353 if r:
353 return [dict(file=r[0], node=hex(r[1]))]
354 return [dict(file=r[0], node=hex(r[1]))]
354 return []
355 return []
355
356
356 def nodetagsdict(self, node):
357 def nodetagsdict(self, node):
357 return [{"name": i} for i in self.repo.nodetags(node)]
358 return [{"name": i} for i in self.repo.nodetags(node)]
358
359
359 def nodebranchdict(self, ctx):
360 def nodebranchdict(self, ctx):
360 branches = []
361 branches = []
361 branch = ctx.branch()
362 branch = ctx.branch()
362 # If this is an empty repo, ctx.node() == nullid,
363 # If this is an empty repo, ctx.node() == nullid,
363 # ctx.branch() == 'default', but branchtags() is
364 # ctx.branch() == 'default', but branchtags() is
364 # an empty dict. Using dict.get avoids a traceback.
365 # an empty dict. Using dict.get avoids a traceback.
365 if self.repo.branchtags().get(branch) == ctx.node():
366 if self.repo.branchtags().get(branch) == ctx.node():
366 branches.append({"name": branch})
367 branches.append({"name": branch})
367 return branches
368 return branches
368
369
369 def showtag(self, tmpl, t1, node=nullid, **args):
370 def showtag(self, tmpl, t1, node=nullid, **args):
370 for t in self.repo.nodetags(node):
371 for t in self.repo.nodetags(node):
371 yield tmpl(t1, tag=t, **args)
372 yield tmpl(t1, tag=t, **args)
372
373
373 def diff(self, tmpl, node1, node2, files):
374 def diff(self, tmpl, node1, node2, files):
374 def filterfiles(filters, files):
375 def filterfiles(filters, files):
375 l = [x for x in files if x in filters]
376 l = [x for x in files if x in filters]
376
377
377 for t in filters:
378 for t in filters:
378 if t and t[-1] != os.sep:
379 if t and t[-1] != os.sep:
379 t += os.sep
380 t += os.sep
380 l += [x for x in files if x.startswith(t)]
381 l += [x for x in files if x.startswith(t)]
381 return l
382 return l
382
383
383 parity = paritygen(self.stripecount)
384 parity = paritygen(self.stripecount)
384 def diffblock(diff, f, fn):
385 def diffblock(diff, f, fn):
385 yield tmpl("diffblock",
386 yield tmpl("diffblock",
386 lines=prettyprintlines(diff),
387 lines=prettyprintlines(diff),
387 parity=parity.next(),
388 parity=parity.next(),
388 file=f,
389 file=f,
389 filenode=hex(fn or nullid))
390 filenode=hex(fn or nullid))
390
391
391 blockcount = countgen()
392 blockcount = countgen()
392 def prettyprintlines(diff):
393 def prettyprintlines(diff):
393 blockno = blockcount.next()
394 blockno = blockcount.next()
394 for lineno, l in enumerate(diff.splitlines(1)):
395 for lineno, l in enumerate(diff.splitlines(1)):
395 if blockno == 0:
396 if blockno == 0:
396 lineno = lineno + 1
397 lineno = lineno + 1
397 else:
398 else:
398 lineno = "%d.%d" % (blockno, lineno + 1)
399 lineno = "%d.%d" % (blockno, lineno + 1)
399 if l.startswith('+'):
400 if l.startswith('+'):
400 ltype = "difflineplus"
401 ltype = "difflineplus"
401 elif l.startswith('-'):
402 elif l.startswith('-'):
402 ltype = "difflineminus"
403 ltype = "difflineminus"
403 elif l.startswith('@'):
404 elif l.startswith('@'):
404 ltype = "difflineat"
405 ltype = "difflineat"
405 else:
406 else:
406 ltype = "diffline"
407 ltype = "diffline"
407 yield tmpl(ltype,
408 yield tmpl(ltype,
408 line=l,
409 line=l,
409 lineid="l%s" % lineno,
410 lineid="l%s" % lineno,
410 linenumber="% 8s" % lineno)
411 linenumber="% 8s" % lineno)
411
412
412 r = self.repo
413 r = self.repo
413 c1 = r.changectx(node1)
414 c1 = r.changectx(node1)
414 c2 = r.changectx(node2)
415 c2 = r.changectx(node2)
415 date1 = util.datestr(c1.date())
416 date1 = util.datestr(c1.date())
416 date2 = util.datestr(c2.date())
417 date2 = util.datestr(c2.date())
417
418
418 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
419 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
419 if files:
420 if files:
420 modified, added, removed = map(lambda x: filterfiles(files, x),
421 modified, added, removed = map(lambda x: filterfiles(files, x),
421 (modified, added, removed))
422 (modified, added, removed))
422
423
423 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
424 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
424 for f in modified:
425 for f in modified:
425 to = c1.filectx(f).data()
426 to = c1.filectx(f).data()
426 tn = c2.filectx(f).data()
427 tn = c2.filectx(f).data()
427 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
428 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
428 opts=diffopts), f, tn)
429 opts=diffopts), f, tn)
429 for f in added:
430 for f in added:
430 to = None
431 to = None
431 tn = c2.filectx(f).data()
432 tn = c2.filectx(f).data()
432 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
433 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
433 opts=diffopts), f, tn)
434 opts=diffopts), f, tn)
434 for f in removed:
435 for f in removed:
435 to = c1.filectx(f).data()
436 to = c1.filectx(f).data()
436 tn = None
437 tn = None
437 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
438 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
438 opts=diffopts), f, tn)
439 opts=diffopts), f, tn)
439
440
440 def changelog(self, tmpl, ctx, shortlog=False):
441 def changelog(self, tmpl, ctx, shortlog=False):
441 def changelist(limit=0,**map):
442 def changelist(limit=0,**map):
442 cl = self.repo.changelog
443 cl = self.repo.changelog
443 l = [] # build a list in forward order for efficiency
444 l = [] # build a list in forward order for efficiency
444 for i in xrange(start, end):
445 for i in xrange(start, end):
445 ctx = self.repo.changectx(i)
446 ctx = self.repo.changectx(i)
446 n = ctx.node()
447 n = ctx.node()
447 showtags = self.showtag(tmpl, 'changelogtag', n)
448 showtags = self.showtag(tmpl, 'changelogtag', n)
448
449
449 l.insert(0, {"parity": parity.next(),
450 l.insert(0, {"parity": parity.next(),
450 "author": ctx.user(),
451 "author": ctx.user(),
451 "parent": self.siblings(ctx.parents(), i - 1),
452 "parent": self.siblings(ctx.parents(), i - 1),
452 "child": self.siblings(ctx.children(), i + 1),
453 "child": self.siblings(ctx.children(), i + 1),
453 "changelogtag": showtags,
454 "changelogtag": showtags,
454 "desc": ctx.description(),
455 "desc": ctx.description(),
455 "date": ctx.date(),
456 "date": ctx.date(),
456 "files": self.listfilediffs(tmpl, ctx.files(), n),
457 "files": self.listfilediffs(tmpl, ctx.files(), n),
457 "rev": i,
458 "rev": i,
458 "node": hex(n),
459 "node": hex(n),
459 "tags": self.nodetagsdict(n),
460 "tags": self.nodetagsdict(n),
460 "branches": self.nodebranchdict(ctx)})
461 "branches": self.nodebranchdict(ctx)})
461
462
462 if limit > 0:
463 if limit > 0:
463 l = l[:limit]
464 l = l[:limit]
464
465
465 for e in l:
466 for e in l:
466 yield e
467 yield e
467
468
468 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
469 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
469 cl = self.repo.changelog
470 cl = self.repo.changelog
470 count = cl.count()
471 count = cl.count()
471 pos = ctx.rev()
472 pos = ctx.rev()
472 start = max(0, pos - maxchanges + 1)
473 start = max(0, pos - maxchanges + 1)
473 end = min(count, start + maxchanges)
474 end = min(count, start + maxchanges)
474 pos = end - 1
475 pos = end - 1
475 parity = paritygen(self.stripecount, offset=start-end)
476 parity = paritygen(self.stripecount, offset=start-end)
476
477
477 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
478 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
478
479
479 return tmpl(shortlog and 'shortlog' or 'changelog',
480 return tmpl(shortlog and 'shortlog' or 'changelog',
480 changenav=changenav,
481 changenav=changenav,
481 node=hex(cl.tip()),
482 node=hex(cl.tip()),
482 rev=pos, changesets=count,
483 rev=pos, changesets=count,
483 entries=lambda **x: changelist(limit=0,**x),
484 entries=lambda **x: changelist(limit=0,**x),
484 latestentry=lambda **x: changelist(limit=1,**x),
485 latestentry=lambda **x: changelist(limit=1,**x),
485 archives=self.archivelist("tip"))
486 archives=self.archivelist("tip"))
486
487
487 def search(self, tmpl, query):
488 def search(self, tmpl, query):
488
489
489 def changelist(**map):
490 def changelist(**map):
490 cl = self.repo.changelog
491 cl = self.repo.changelog
491 count = 0
492 count = 0
492 qw = query.lower().split()
493 qw = query.lower().split()
493
494
494 def revgen():
495 def revgen():
495 for i in xrange(cl.count() - 1, 0, -100):
496 for i in xrange(cl.count() - 1, 0, -100):
496 l = []
497 l = []
497 for j in xrange(max(0, i - 100), i + 1):
498 for j in xrange(max(0, i - 100), i + 1):
498 ctx = self.repo.changectx(j)
499 ctx = self.repo.changectx(j)
499 l.append(ctx)
500 l.append(ctx)
500 l.reverse()
501 l.reverse()
501 for e in l:
502 for e in l:
502 yield e
503 yield e
503
504
504 for ctx in revgen():
505 for ctx in revgen():
505 miss = 0
506 miss = 0
506 for q in qw:
507 for q in qw:
507 if not (q in ctx.user().lower() or
508 if not (q in ctx.user().lower() or
508 q in ctx.description().lower() or
509 q in ctx.description().lower() or
509 q in " ".join(ctx.files()).lower()):
510 q in " ".join(ctx.files()).lower()):
510 miss = 1
511 miss = 1
511 break
512 break
512 if miss:
513 if miss:
513 continue
514 continue
514
515
515 count += 1
516 count += 1
516 n = ctx.node()
517 n = ctx.node()
517 showtags = self.showtag(tmpl, 'changelogtag', n)
518 showtags = self.showtag(tmpl, 'changelogtag', n)
518
519
519 yield tmpl('searchentry',
520 yield tmpl('searchentry',
520 parity=parity.next(),
521 parity=parity.next(),
521 author=ctx.user(),
522 author=ctx.user(),
522 parent=self.siblings(ctx.parents()),
523 parent=self.siblings(ctx.parents()),
523 child=self.siblings(ctx.children()),
524 child=self.siblings(ctx.children()),
524 changelogtag=showtags,
525 changelogtag=showtags,
525 desc=ctx.description(),
526 desc=ctx.description(),
526 date=ctx.date(),
527 date=ctx.date(),
527 files=self.listfilediffs(tmpl, ctx.files(), n),
528 files=self.listfilediffs(tmpl, ctx.files(), n),
528 rev=ctx.rev(),
529 rev=ctx.rev(),
529 node=hex(n),
530 node=hex(n),
530 tags=self.nodetagsdict(n),
531 tags=self.nodetagsdict(n),
531 branches=self.nodebranchdict(ctx))
532 branches=self.nodebranchdict(ctx))
532
533
533 if count >= self.maxchanges:
534 if count >= self.maxchanges:
534 break
535 break
535
536
536 cl = self.repo.changelog
537 cl = self.repo.changelog
537 parity = paritygen(self.stripecount)
538 parity = paritygen(self.stripecount)
538
539
539 return tmpl('search',
540 return tmpl('search',
540 query=query,
541 query=query,
541 node=hex(cl.tip()),
542 node=hex(cl.tip()),
542 entries=changelist,
543 entries=changelist,
543 archives=self.archivelist("tip"))
544 archives=self.archivelist("tip"))
544
545
545 def changeset(self, tmpl, ctx):
546 def changeset(self, tmpl, ctx):
546 n = ctx.node()
547 n = ctx.node()
547 showtags = self.showtag(tmpl, 'changesettag', n)
548 showtags = self.showtag(tmpl, 'changesettag', n)
548 parents = ctx.parents()
549 parents = ctx.parents()
549 p1 = parents[0].node()
550 p1 = parents[0].node()
550
551
551 files = []
552 files = []
552 parity = paritygen(self.stripecount)
553 parity = paritygen(self.stripecount)
553 for f in ctx.files():
554 for f in ctx.files():
554 files.append(tmpl("filenodelink",
555 files.append(tmpl("filenodelink",
555 node=hex(n), file=f,
556 node=hex(n), file=f,
556 parity=parity.next()))
557 parity=parity.next()))
557
558
558 def diff(**map):
559 def diff(**map):
559 yield self.diff(tmpl, p1, n, None)
560 yield self.diff(tmpl, p1, n, None)
560
561
561 return tmpl('changeset',
562 return tmpl('changeset',
562 diff=diff,
563 diff=diff,
563 rev=ctx.rev(),
564 rev=ctx.rev(),
564 node=hex(n),
565 node=hex(n),
565 parent=self.siblings(parents),
566 parent=self.siblings(parents),
566 child=self.siblings(ctx.children()),
567 child=self.siblings(ctx.children()),
567 changesettag=showtags,
568 changesettag=showtags,
568 author=ctx.user(),
569 author=ctx.user(),
569 desc=ctx.description(),
570 desc=ctx.description(),
570 date=ctx.date(),
571 date=ctx.date(),
571 files=files,
572 files=files,
572 archives=self.archivelist(hex(n)),
573 archives=self.archivelist(hex(n)),
573 tags=self.nodetagsdict(n),
574 tags=self.nodetagsdict(n),
574 branches=self.nodebranchdict(ctx))
575 branches=self.nodebranchdict(ctx))
575
576
576 def filelog(self, tmpl, fctx):
577 def filelog(self, tmpl, fctx):
577 f = fctx.path()
578 f = fctx.path()
578 fl = fctx.filelog()
579 fl = fctx.filelog()
579 count = fl.count()
580 count = fl.count()
580 pagelen = self.maxshortchanges
581 pagelen = self.maxshortchanges
581 pos = fctx.filerev()
582 pos = fctx.filerev()
582 start = max(0, pos - pagelen + 1)
583 start = max(0, pos - pagelen + 1)
583 end = min(count, start + pagelen)
584 end = min(count, start + pagelen)
584 pos = end - 1
585 pos = end - 1
585 parity = paritygen(self.stripecount, offset=start-end)
586 parity = paritygen(self.stripecount, offset=start-end)
586
587
587 def entries(limit=0, **map):
588 def entries(limit=0, **map):
588 l = []
589 l = []
589
590
590 for i in xrange(start, end):
591 for i in xrange(start, end):
591 ctx = fctx.filectx(i)
592 ctx = fctx.filectx(i)
592 n = fl.node(i)
593 n = fl.node(i)
593
594
594 l.insert(0, {"parity": parity.next(),
595 l.insert(0, {"parity": parity.next(),
595 "filerev": i,
596 "filerev": i,
596 "file": f,
597 "file": f,
597 "node": hex(ctx.node()),
598 "node": hex(ctx.node()),
598 "author": ctx.user(),
599 "author": ctx.user(),
599 "date": ctx.date(),
600 "date": ctx.date(),
600 "rename": self.renamelink(fl, n),
601 "rename": self.renamelink(fl, n),
601 "parent": self.siblings(fctx.parents()),
602 "parent": self.siblings(fctx.parents()),
602 "child": self.siblings(fctx.children()),
603 "child": self.siblings(fctx.children()),
603 "desc": ctx.description()})
604 "desc": ctx.description()})
604
605
605 if limit > 0:
606 if limit > 0:
606 l = l[:limit]
607 l = l[:limit]
607
608
608 for e in l:
609 for e in l:
609 yield e
610 yield e
610
611
611 nodefunc = lambda x: fctx.filectx(fileid=x)
612 nodefunc = lambda x: fctx.filectx(fileid=x)
612 nav = revnavgen(pos, pagelen, count, nodefunc)
613 nav = revnavgen(pos, pagelen, count, nodefunc)
613 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
614 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
614 entries=lambda **x: entries(limit=0, **x),
615 entries=lambda **x: entries(limit=0, **x),
615 latestentry=lambda **x: entries(limit=1, **x))
616 latestentry=lambda **x: entries(limit=1, **x))
616
617
617 def filerevision(self, tmpl, fctx):
618 def filerevision(self, tmpl, fctx):
618 f = fctx.path()
619 f = fctx.path()
619 text = fctx.data()
620 text = fctx.data()
620 fl = fctx.filelog()
621 fl = fctx.filelog()
621 n = fctx.filenode()
622 n = fctx.filenode()
622 parity = paritygen(self.stripecount)
623 parity = paritygen(self.stripecount)
623
624
624 if util.binary(text):
625 if util.binary(text):
625 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
626 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
626 text = '(binary:%s)' % mt
627 text = '(binary:%s)' % mt
627
628
628 def lines():
629 def lines():
629 for lineno, t in enumerate(text.splitlines(1)):
630 for lineno, t in enumerate(text.splitlines(1)):
630 yield {"line": t,
631 yield {"line": t,
631 "lineid": "l%d" % (lineno + 1),
632 "lineid": "l%d" % (lineno + 1),
632 "linenumber": "% 6d" % (lineno + 1),
633 "linenumber": "% 6d" % (lineno + 1),
633 "parity": parity.next()}
634 "parity": parity.next()}
634
635
635 return tmpl("filerevision",
636 return tmpl("filerevision",
636 file=f,
637 file=f,
637 path=_up(f),
638 path=_up(f),
638 text=lines(),
639 text=lines(),
639 rev=fctx.rev(),
640 rev=fctx.rev(),
640 node=hex(fctx.node()),
641 node=hex(fctx.node()),
641 author=fctx.user(),
642 author=fctx.user(),
642 date=fctx.date(),
643 date=fctx.date(),
643 desc=fctx.description(),
644 desc=fctx.description(),
644 parent=self.siblings(fctx.parents()),
645 parent=self.siblings(fctx.parents()),
645 child=self.siblings(fctx.children()),
646 child=self.siblings(fctx.children()),
646 rename=self.renamelink(fl, n),
647 rename=self.renamelink(fl, n),
647 permissions=fctx.manifest().flags(f))
648 permissions=fctx.manifest().flags(f))
648
649
649 def fileannotate(self, tmpl, fctx):
650 def fileannotate(self, tmpl, fctx):
650 f = fctx.path()
651 f = fctx.path()
651 n = fctx.filenode()
652 n = fctx.filenode()
652 fl = fctx.filelog()
653 fl = fctx.filelog()
653 parity = paritygen(self.stripecount)
654 parity = paritygen(self.stripecount)
654
655
655 def annotate(**map):
656 def annotate(**map):
656 last = None
657 last = None
657 if util.binary(fctx.data()):
658 if util.binary(fctx.data()):
658 mt = (mimetypes.guess_type(fctx.path())[0]
659 mt = (mimetypes.guess_type(fctx.path())[0]
659 or 'application/octet-stream')
660 or 'application/octet-stream')
660 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
661 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
661 '(binary:%s)' % mt)])
662 '(binary:%s)' % mt)])
662 else:
663 else:
663 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
664 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
664 for lineno, ((f, targetline), l) in lines:
665 for lineno, ((f, targetline), l) in lines:
665 fnode = f.filenode()
666 fnode = f.filenode()
666 name = self.repo.ui.shortuser(f.user())
667 name = self.repo.ui.shortuser(f.user())
667
668
668 if last != fnode:
669 if last != fnode:
669 last = fnode
670 last = fnode
670
671
671 yield {"parity": parity.next(),
672 yield {"parity": parity.next(),
672 "node": hex(f.node()),
673 "node": hex(f.node()),
673 "rev": f.rev(),
674 "rev": f.rev(),
674 "author": name,
675 "author": name,
675 "file": f.path(),
676 "file": f.path(),
676 "targetline": targetline,
677 "targetline": targetline,
677 "line": l,
678 "line": l,
678 "lineid": "l%d" % (lineno + 1),
679 "lineid": "l%d" % (lineno + 1),
679 "linenumber": "% 6d" % (lineno + 1)}
680 "linenumber": "% 6d" % (lineno + 1)}
680
681
681 return tmpl("fileannotate",
682 return tmpl("fileannotate",
682 file=f,
683 file=f,
683 annotate=annotate,
684 annotate=annotate,
684 path=_up(f),
685 path=_up(f),
685 rev=fctx.rev(),
686 rev=fctx.rev(),
686 node=hex(fctx.node()),
687 node=hex(fctx.node()),
687 author=fctx.user(),
688 author=fctx.user(),
688 date=fctx.date(),
689 date=fctx.date(),
689 desc=fctx.description(),
690 desc=fctx.description(),
690 rename=self.renamelink(fl, n),
691 rename=self.renamelink(fl, n),
691 parent=self.siblings(fctx.parents()),
692 parent=self.siblings(fctx.parents()),
692 child=self.siblings(fctx.children()),
693 child=self.siblings(fctx.children()),
693 permissions=fctx.manifest().flags(f))
694 permissions=fctx.manifest().flags(f))
694
695
695 def manifest(self, tmpl, ctx, path):
696 def manifest(self, tmpl, ctx, path):
696 mf = ctx.manifest()
697 mf = ctx.manifest()
697 node = ctx.node()
698 node = ctx.node()
698
699
699 files = {}
700 files = {}
700 parity = paritygen(self.stripecount)
701 parity = paritygen(self.stripecount)
701
702
702 if path and path[-1] != "/":
703 if path and path[-1] != "/":
703 path += "/"
704 path += "/"
704 l = len(path)
705 l = len(path)
705 abspath = "/" + path
706 abspath = "/" + path
706
707
707 for f, n in mf.items():
708 for f, n in mf.items():
708 if f[:l] != path:
709 if f[:l] != path:
709 continue
710 continue
710 remain = f[l:]
711 remain = f[l:]
711 if "/" in remain:
712 if "/" in remain:
712 short = remain[:remain.index("/") + 1] # bleah
713 short = remain[:remain.index("/") + 1] # bleah
713 files[short] = (f, None)
714 files[short] = (f, None)
714 else:
715 else:
715 short = os.path.basename(remain)
716 short = os.path.basename(remain)
716 files[short] = (f, n)
717 files[short] = (f, n)
717
718
718 if not files:
719 if not files:
719 raise ErrorResponse(HTTP_NOT_FOUND, 'Path not found: ' + path)
720 raise ErrorResponse(HTTP_NOT_FOUND, 'Path not found: ' + path)
720
721
721 def filelist(**map):
722 def filelist(**map):
722 fl = files.keys()
723 fl = files.keys()
723 fl.sort()
724 fl.sort()
724 for f in fl:
725 for f in fl:
725 full, fnode = files[f]
726 full, fnode = files[f]
726 if not fnode:
727 if not fnode:
727 continue
728 continue
728
729
729 fctx = ctx.filectx(full)
730 fctx = ctx.filectx(full)
730 yield {"file": full,
731 yield {"file": full,
731 "parity": parity.next(),
732 "parity": parity.next(),
732 "basename": f,
733 "basename": f,
733 "date": fctx.changectx().date(),
734 "date": fctx.changectx().date(),
734 "size": fctx.size(),
735 "size": fctx.size(),
735 "permissions": mf.flags(full)}
736 "permissions": mf.flags(full)}
736
737
737 def dirlist(**map):
738 def dirlist(**map):
738 fl = files.keys()
739 fl = files.keys()
739 fl.sort()
740 fl.sort()
740 for f in fl:
741 for f in fl:
741 full, fnode = files[f]
742 full, fnode = files[f]
742 if fnode:
743 if fnode:
743 continue
744 continue
744
745
745 yield {"parity": parity.next(),
746 yield {"parity": parity.next(),
746 "path": "%s%s" % (abspath, f),
747 "path": "%s%s" % (abspath, f),
747 "basename": f[:-1]}
748 "basename": f[:-1]}
748
749
749 return tmpl("manifest",
750 return tmpl("manifest",
750 rev=ctx.rev(),
751 rev=ctx.rev(),
751 node=hex(node),
752 node=hex(node),
752 path=abspath,
753 path=abspath,
753 up=_up(abspath),
754 up=_up(abspath),
754 upparity=parity.next(),
755 upparity=parity.next(),
755 fentries=filelist,
756 fentries=filelist,
756 dentries=dirlist,
757 dentries=dirlist,
757 archives=self.archivelist(hex(node)),
758 archives=self.archivelist(hex(node)),
758 tags=self.nodetagsdict(node),
759 tags=self.nodetagsdict(node),
759 branches=self.nodebranchdict(ctx))
760 branches=self.nodebranchdict(ctx))
760
761
761 def tags(self, tmpl):
762 def tags(self, tmpl):
762 i = self.repo.tagslist()
763 i = self.repo.tagslist()
763 i.reverse()
764 i.reverse()
764 parity = paritygen(self.stripecount)
765 parity = paritygen(self.stripecount)
765
766
766 def entries(notip=False,limit=0, **map):
767 def entries(notip=False,limit=0, **map):
767 count = 0
768 count = 0
768 for k, n in i:
769 for k, n in i:
769 if notip and k == "tip":
770 if notip and k == "tip":
770 continue
771 continue
771 if limit > 0 and count >= limit:
772 if limit > 0 and count >= limit:
772 continue
773 continue
773 count = count + 1
774 count = count + 1
774 yield {"parity": parity.next(),
775 yield {"parity": parity.next(),
775 "tag": k,
776 "tag": k,
776 "date": self.repo.changectx(n).date(),
777 "date": self.repo.changectx(n).date(),
777 "node": hex(n)}
778 "node": hex(n)}
778
779
779 return tmpl("tags",
780 return tmpl("tags",
780 node=hex(self.repo.changelog.tip()),
781 node=hex(self.repo.changelog.tip()),
781 entries=lambda **x: entries(False,0, **x),
782 entries=lambda **x: entries(False,0, **x),
782 entriesnotip=lambda **x: entries(True,0, **x),
783 entriesnotip=lambda **x: entries(True,0, **x),
783 latestentry=lambda **x: entries(True,1, **x))
784 latestentry=lambda **x: entries(True,1, **x))
784
785
785 def summary(self, tmpl):
786 def summary(self, tmpl):
786 i = self.repo.tagslist()
787 i = self.repo.tagslist()
787 i.reverse()
788 i.reverse()
788
789
789 def tagentries(**map):
790 def tagentries(**map):
790 parity = paritygen(self.stripecount)
791 parity = paritygen(self.stripecount)
791 count = 0
792 count = 0
792 for k, n in i:
793 for k, n in i:
793 if k == "tip": # skip tip
794 if k == "tip": # skip tip
794 continue;
795 continue;
795
796
796 count += 1
797 count += 1
797 if count > 10: # limit to 10 tags
798 if count > 10: # limit to 10 tags
798 break;
799 break;
799
800
800 yield tmpl("tagentry",
801 yield tmpl("tagentry",
801 parity=parity.next(),
802 parity=parity.next(),
802 tag=k,
803 tag=k,
803 node=hex(n),
804 node=hex(n),
804 date=self.repo.changectx(n).date())
805 date=self.repo.changectx(n).date())
805
806
806
807
807 def branches(**map):
808 def branches(**map):
808 parity = paritygen(self.stripecount)
809 parity = paritygen(self.stripecount)
809
810
810 b = self.repo.branchtags()
811 b = self.repo.branchtags()
811 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
812 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
812 l.sort()
813 l.sort()
813
814
814 for r,n,t in l:
815 for r,n,t in l:
815 ctx = self.repo.changectx(n)
816 ctx = self.repo.changectx(n)
816
817
817 yield {'parity': parity.next(),
818 yield {'parity': parity.next(),
818 'branch': t,
819 'branch': t,
819 'node': hex(n),
820 'node': hex(n),
820 'date': ctx.date()}
821 'date': ctx.date()}
821
822
822 def changelist(**map):
823 def changelist(**map):
823 parity = paritygen(self.stripecount, offset=start-end)
824 parity = paritygen(self.stripecount, offset=start-end)
824 l = [] # build a list in forward order for efficiency
825 l = [] # build a list in forward order for efficiency
825 for i in xrange(start, end):
826 for i in xrange(start, end):
826 ctx = self.repo.changectx(i)
827 ctx = self.repo.changectx(i)
827 n = ctx.node()
828 n = ctx.node()
828 hn = hex(n)
829 hn = hex(n)
829
830
830 l.insert(0, tmpl(
831 l.insert(0, tmpl(
831 'shortlogentry',
832 'shortlogentry',
832 parity=parity.next(),
833 parity=parity.next(),
833 author=ctx.user(),
834 author=ctx.user(),
834 desc=ctx.description(),
835 desc=ctx.description(),
835 date=ctx.date(),
836 date=ctx.date(),
836 rev=i,
837 rev=i,
837 node=hn,
838 node=hn,
838 tags=self.nodetagsdict(n),
839 tags=self.nodetagsdict(n),
839 branches=self.nodebranchdict(ctx)))
840 branches=self.nodebranchdict(ctx)))
840
841
841 yield l
842 yield l
842
843
843 cl = self.repo.changelog
844 cl = self.repo.changelog
844 count = cl.count()
845 count = cl.count()
845 start = max(0, count - self.maxchanges)
846 start = max(0, count - self.maxchanges)
846 end = min(count, start + self.maxchanges)
847 end = min(count, start + self.maxchanges)
847
848
848 return tmpl("summary",
849 return tmpl("summary",
849 desc=self.config("web", "description", "unknown"),
850 desc=self.config("web", "description", "unknown"),
850 owner=get_contact(self.config) or "unknown",
851 owner=get_contact(self.config) or "unknown",
851 lastchange=cl.read(cl.tip())[2],
852 lastchange=cl.read(cl.tip())[2],
852 tags=tagentries,
853 tags=tagentries,
853 branches=branches,
854 branches=branches,
854 shortlog=changelist,
855 shortlog=changelist,
855 node=hex(cl.tip()),
856 node=hex(cl.tip()),
856 archives=self.archivelist("tip"))
857 archives=self.archivelist("tip"))
857
858
858 def filediff(self, tmpl, fctx):
859 def filediff(self, tmpl, fctx):
859 n = fctx.node()
860 n = fctx.node()
860 path = fctx.path()
861 path = fctx.path()
861 parents = fctx.parents()
862 parents = fctx.parents()
862 p1 = parents and parents[0].node() or nullid
863 p1 = parents and parents[0].node() or nullid
863
864
864 def diff(**map):
865 def diff(**map):
865 yield self.diff(tmpl, p1, n, [path])
866 yield self.diff(tmpl, p1, n, [path])
866
867
867 return tmpl("filediff",
868 return tmpl("filediff",
868 file=path,
869 file=path,
869 node=hex(n),
870 node=hex(n),
870 rev=fctx.rev(),
871 rev=fctx.rev(),
871 parent=self.siblings(parents),
872 parent=self.siblings(parents),
872 child=self.siblings(fctx.children()),
873 child=self.siblings(fctx.children()),
873 diff=diff)
874 diff=diff)
874
875
875 archive_specs = {
876 archive_specs = {
876 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
877 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
877 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
878 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
878 'zip': ('application/zip', 'zip', '.zip', None),
879 'zip': ('application/zip', 'zip', '.zip', None),
879 }
880 }
880
881
881 def archive(self, tmpl, req, key, type_):
882 def archive(self, tmpl, req, key, type_):
882 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
883 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
883 cnode = self.repo.lookup(key)
884 cnode = self.repo.lookup(key)
884 arch_version = key
885 arch_version = key
885 if cnode == key or key == 'tip':
886 if cnode == key or key == 'tip':
886 arch_version = short(cnode)
887 arch_version = short(cnode)
887 name = "%s-%s" % (reponame, arch_version)
888 name = "%s-%s" % (reponame, arch_version)
888 mimetype, artype, extension, encoding = self.archive_specs[type_]
889 mimetype, artype, extension, encoding = self.archive_specs[type_]
889 headers = [
890 headers = [
890 ('Content-Type', mimetype),
891 ('Content-Type', mimetype),
891 ('Content-Disposition', 'attachment; filename=%s%s' %
892 ('Content-Disposition', 'attachment; filename=%s%s' %
892 (name, extension))
893 (name, extension))
893 ]
894 ]
894 if encoding:
895 if encoding:
895 headers.append(('Content-Encoding', encoding))
896 headers.append(('Content-Encoding', encoding))
896 req.header(headers)
897 req.header(headers)
897 req.respond(HTTP_OK)
898 req.respond(HTTP_OK)
898 archival.archive(self.repo, req, cnode, artype, prefix=name)
899 archival.archive(self.repo, req, cnode, artype, prefix=name)
899
900
900 # add tags to things
901 # add tags to things
901 # tags -> list of changesets corresponding to tags
902 # tags -> list of changesets corresponding to tags
902 # find tag, changeset, file
903 # find tag, changeset, file
903
904
904 def cleanpath(self, path):
905 def cleanpath(self, path):
905 path = path.lstrip('/')
906 path = path.lstrip('/')
906 return util.canonpath(self.repo.root, '', path)
907 return util.canonpath(self.repo.root, '', path)
907
908
908 def changectx(self, req):
909 def changectx(self, req):
909 if 'node' in req.form:
910 if 'node' in req.form:
910 changeid = req.form['node'][0]
911 changeid = req.form['node'][0]
911 elif 'manifest' in req.form:
912 elif 'manifest' in req.form:
912 changeid = req.form['manifest'][0]
913 changeid = req.form['manifest'][0]
913 else:
914 else:
914 changeid = self.repo.changelog.count() - 1
915 changeid = self.repo.changelog.count() - 1
915
916
916 try:
917 try:
917 ctx = self.repo.changectx(changeid)
918 ctx = self.repo.changectx(changeid)
918 except hg.RepoError:
919 except RepoError:
919 man = self.repo.manifest
920 man = self.repo.manifest
920 mn = man.lookup(changeid)
921 mn = man.lookup(changeid)
921 ctx = self.repo.changectx(man.linkrev(mn))
922 ctx = self.repo.changectx(man.linkrev(mn))
922
923
923 return ctx
924 return ctx
924
925
925 def filectx(self, req):
926 def filectx(self, req):
926 path = self.cleanpath(req.form['file'][0])
927 path = self.cleanpath(req.form['file'][0])
927 if 'node' in req.form:
928 if 'node' in req.form:
928 changeid = req.form['node'][0]
929 changeid = req.form['node'][0]
929 else:
930 else:
930 changeid = req.form['filenode'][0]
931 changeid = req.form['filenode'][0]
931 try:
932 try:
932 ctx = self.repo.changectx(changeid)
933 ctx = self.repo.changectx(changeid)
933 fctx = ctx.filectx(path)
934 fctx = ctx.filectx(path)
934 except hg.RepoError:
935 except RepoError:
935 fctx = self.repo.filectx(path, fileid=changeid)
936 fctx = self.repo.filectx(path, fileid=changeid)
936
937
937 return fctx
938 return fctx
938
939
939 def check_perm(self, req, op, default):
940 def check_perm(self, req, op, default):
940 '''check permission for operation based on user auth.
941 '''check permission for operation based on user auth.
941 return true if op allowed, else false.
942 return true if op allowed, else false.
942 default is policy to use if no config given.'''
943 default is policy to use if no config given.'''
943
944
944 user = req.env.get('REMOTE_USER')
945 user = req.env.get('REMOTE_USER')
945
946
946 deny = self.configlist('web', 'deny_' + op)
947 deny = self.configlist('web', 'deny_' + op)
947 if deny and (not user or deny == ['*'] or user in deny):
948 if deny and (not user or deny == ['*'] or user in deny):
948 return False
949 return False
949
950
950 allow = self.configlist('web', 'allow_' + op)
951 allow = self.configlist('web', 'allow_' + op)
951 return (allow and (allow == ['*'] or user in allow)) or default
952 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,278 +1,279 b''
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os
9 import os
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from mercurial.repo import RepoError
11 from mercurial import ui, hg, util, templater, templatefilters
12 from mercurial import ui, hg, util, templater, templatefilters
12 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen,\
13 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen,\
13 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 from hgweb_mod import hgweb
15 from hgweb_mod import hgweb
15 from request import wsgirequest
16 from request import wsgirequest
16
17
17 # This is a stopgap
18 # This is a stopgap
18 class hgwebdir(object):
19 class hgwebdir(object):
19 def __init__(self, config, parentui=None):
20 def __init__(self, config, parentui=None):
20 def cleannames(items):
21 def cleannames(items):
21 return [(util.pconvert(name).strip('/'), path)
22 return [(util.pconvert(name).strip('/'), path)
22 for name, path in items]
23 for name, path in items]
23
24
24 self.parentui = parentui or ui.ui(report_untrusted=False,
25 self.parentui = parentui or ui.ui(report_untrusted=False,
25 interactive = False)
26 interactive = False)
26 self.motd = None
27 self.motd = None
27 self.style = None
28 self.style = None
28 self.stripecount = None
29 self.stripecount = None
29 self.repos_sorted = ('name', False)
30 self.repos_sorted = ('name', False)
30 if isinstance(config, (list, tuple)):
31 if isinstance(config, (list, tuple)):
31 self.repos = cleannames(config)
32 self.repos = cleannames(config)
32 self.repos_sorted = ('', False)
33 self.repos_sorted = ('', False)
33 elif isinstance(config, dict):
34 elif isinstance(config, dict):
34 self.repos = cleannames(config.items())
35 self.repos = cleannames(config.items())
35 self.repos.sort()
36 self.repos.sort()
36 else:
37 else:
37 if isinstance(config, util.configparser):
38 if isinstance(config, util.configparser):
38 cp = config
39 cp = config
39 else:
40 else:
40 cp = util.configparser()
41 cp = util.configparser()
41 cp.read(config)
42 cp.read(config)
42 self.repos = []
43 self.repos = []
43 if cp.has_section('web'):
44 if cp.has_section('web'):
44 if cp.has_option('web', 'motd'):
45 if cp.has_option('web', 'motd'):
45 self.motd = cp.get('web', 'motd')
46 self.motd = cp.get('web', 'motd')
46 if cp.has_option('web', 'style'):
47 if cp.has_option('web', 'style'):
47 self.style = cp.get('web', 'style')
48 self.style = cp.get('web', 'style')
48 if cp.has_option('web', 'stripes'):
49 if cp.has_option('web', 'stripes'):
49 self.stripecount = int(cp.get('web', 'stripes'))
50 self.stripecount = int(cp.get('web', 'stripes'))
50 if cp.has_section('paths'):
51 if cp.has_section('paths'):
51 self.repos.extend(cleannames(cp.items('paths')))
52 self.repos.extend(cleannames(cp.items('paths')))
52 if cp.has_section('collections'):
53 if cp.has_section('collections'):
53 for prefix, root in cp.items('collections'):
54 for prefix, root in cp.items('collections'):
54 for path in util.walkrepos(root):
55 for path in util.walkrepos(root):
55 repo = os.path.normpath(path)
56 repo = os.path.normpath(path)
56 name = repo
57 name = repo
57 if name.startswith(prefix):
58 if name.startswith(prefix):
58 name = name[len(prefix):]
59 name = name[len(prefix):]
59 self.repos.append((name.lstrip(os.sep), repo))
60 self.repos.append((name.lstrip(os.sep), repo))
60 self.repos.sort()
61 self.repos.sort()
61
62
62 def run(self):
63 def run(self):
63 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
64 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
64 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
65 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
65 import mercurial.hgweb.wsgicgi as wsgicgi
66 import mercurial.hgweb.wsgicgi as wsgicgi
66 wsgicgi.launch(self)
67 wsgicgi.launch(self)
67
68
68 def __call__(self, env, respond):
69 def __call__(self, env, respond):
69 req = wsgirequest(env, respond)
70 req = wsgirequest(env, respond)
70 self.run_wsgi(req)
71 self.run_wsgi(req)
71 return req
72 return req
72
73
73 def run_wsgi(self, req):
74 def run_wsgi(self, req):
74
75
75 try:
76 try:
76 try:
77 try:
77
78
78 virtual = req.env.get("PATH_INFO", "").strip('/')
79 virtual = req.env.get("PATH_INFO", "").strip('/')
79 tmpl = self.templater(req)
80 tmpl = self.templater(req)
80 ctype = tmpl('mimetype', encoding=util._encoding)
81 ctype = tmpl('mimetype', encoding=util._encoding)
81 ctype = templater.stringify(ctype)
82 ctype = templater.stringify(ctype)
82
83
83 # a static file
84 # a static file
84 if virtual.startswith('static/') or 'static' in req.form:
85 if virtual.startswith('static/') or 'static' in req.form:
85 static = os.path.join(templater.templatepath(), 'static')
86 static = os.path.join(templater.templatepath(), 'static')
86 if virtual.startswith('static/'):
87 if virtual.startswith('static/'):
87 fname = virtual[7:]
88 fname = virtual[7:]
88 else:
89 else:
89 fname = req.form['static'][0]
90 fname = req.form['static'][0]
90 req.write(staticfile(static, fname, req))
91 req.write(staticfile(static, fname, req))
91 return
92 return
92
93
93 # top-level index
94 # top-level index
94 elif not virtual:
95 elif not virtual:
95 req.respond(HTTP_OK, ctype)
96 req.respond(HTTP_OK, ctype)
96 req.write(self.makeindex(req, tmpl))
97 req.write(self.makeindex(req, tmpl))
97 return
98 return
98
99
99 # nested indexes and hgwebs
100 # nested indexes and hgwebs
100
101
101 repos = dict(self.repos)
102 repos = dict(self.repos)
102 while virtual:
103 while virtual:
103 real = repos.get(virtual)
104 real = repos.get(virtual)
104 if real:
105 if real:
105 req.env['REPO_NAME'] = virtual
106 req.env['REPO_NAME'] = virtual
106 try:
107 try:
107 repo = hg.repository(self.parentui, real)
108 repo = hg.repository(self.parentui, real)
108 hgweb(repo).run_wsgi(req)
109 hgweb(repo).run_wsgi(req)
109 return
110 return
110 except IOError, inst:
111 except IOError, inst:
111 msg = inst.strerror
112 msg = inst.strerror
112 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
113 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
113 except hg.RepoError, inst:
114 except RepoError, inst:
114 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
115 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
115
116
116 # browse subdirectories
117 # browse subdirectories
117 subdir = virtual + '/'
118 subdir = virtual + '/'
118 if [r for r in repos if r.startswith(subdir)]:
119 if [r for r in repos if r.startswith(subdir)]:
119 req.respond(HTTP_OK, ctype)
120 req.respond(HTTP_OK, ctype)
120 req.write(self.makeindex(req, tmpl, subdir))
121 req.write(self.makeindex(req, tmpl, subdir))
121 return
122 return
122
123
123 up = virtual.rfind('/')
124 up = virtual.rfind('/')
124 if up < 0:
125 if up < 0:
125 break
126 break
126 virtual = virtual[:up]
127 virtual = virtual[:up]
127
128
128 # prefixes not found
129 # prefixes not found
129 req.respond(HTTP_NOT_FOUND, ctype)
130 req.respond(HTTP_NOT_FOUND, ctype)
130 req.write(tmpl("notfound", repo=virtual))
131 req.write(tmpl("notfound", repo=virtual))
131
132
132 except ErrorResponse, err:
133 except ErrorResponse, err:
133 req.respond(err.code, ctype)
134 req.respond(err.code, ctype)
134 req.write(tmpl('error', error=err.message or ''))
135 req.write(tmpl('error', error=err.message or ''))
135 finally:
136 finally:
136 tmpl = None
137 tmpl = None
137
138
138 def makeindex(self, req, tmpl, subdir=""):
139 def makeindex(self, req, tmpl, subdir=""):
139
140
140 def archivelist(ui, nodeid, url):
141 def archivelist(ui, nodeid, url):
141 allowed = ui.configlist("web", "allow_archive", untrusted=True)
142 allowed = ui.configlist("web", "allow_archive", untrusted=True)
142 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
143 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
143 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
144 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
144 untrusted=True):
145 untrusted=True):
145 yield {"type" : i[0], "extension": i[1],
146 yield {"type" : i[0], "extension": i[1],
146 "node": nodeid, "url": url}
147 "node": nodeid, "url": url}
147
148
148 def entries(sortcolumn="", descending=False, subdir="", **map):
149 def entries(sortcolumn="", descending=False, subdir="", **map):
149 def sessionvars(**map):
150 def sessionvars(**map):
150 fields = []
151 fields = []
151 if 'style' in req.form:
152 if 'style' in req.form:
152 style = req.form['style'][0]
153 style = req.form['style'][0]
153 if style != get('web', 'style', ''):
154 if style != get('web', 'style', ''):
154 fields.append(('style', style))
155 fields.append(('style', style))
155
156
156 separator = url[-1] == '?' and ';' or '?'
157 separator = url[-1] == '?' and ';' or '?'
157 for name, value in fields:
158 for name, value in fields:
158 yield dict(name=name, value=value, separator=separator)
159 yield dict(name=name, value=value, separator=separator)
159 separator = ';'
160 separator = ';'
160
161
161 rows = []
162 rows = []
162 parity = paritygen(self.stripecount)
163 parity = paritygen(self.stripecount)
163 for name, path in self.repos:
164 for name, path in self.repos:
164 if not name.startswith(subdir):
165 if not name.startswith(subdir):
165 continue
166 continue
166 name = name[len(subdir):]
167 name = name[len(subdir):]
167
168
168 u = ui.ui(parentui=self.parentui)
169 u = ui.ui(parentui=self.parentui)
169 try:
170 try:
170 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
171 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
171 except Exception, e:
172 except Exception, e:
172 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
173 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
173 continue
174 continue
174 def get(section, name, default=None):
175 def get(section, name, default=None):
175 return u.config(section, name, default, untrusted=True)
176 return u.config(section, name, default, untrusted=True)
176
177
177 if u.configbool("web", "hidden", untrusted=True):
178 if u.configbool("web", "hidden", untrusted=True):
178 continue
179 continue
179
180
180 parts = [req.env['PATH_INFO'].rstrip('/'), name]
181 parts = [req.env['PATH_INFO'].rstrip('/'), name]
181 if req.env['SCRIPT_NAME']:
182 if req.env['SCRIPT_NAME']:
182 parts.insert(0, req.env['SCRIPT_NAME'])
183 parts.insert(0, req.env['SCRIPT_NAME'])
183 url = ('/'.join(parts).replace("//", "/")) + '/'
184 url = ('/'.join(parts).replace("//", "/")) + '/'
184
185
185 # update time with local timezone
186 # update time with local timezone
186 try:
187 try:
187 d = (get_mtime(path), util.makedate()[1])
188 d = (get_mtime(path), util.makedate()[1])
188 except OSError:
189 except OSError:
189 continue
190 continue
190
191
191 contact = get_contact(get)
192 contact = get_contact(get)
192 description = get("web", "description", "")
193 description = get("web", "description", "")
193 name = get("web", "name", name)
194 name = get("web", "name", name)
194 row = dict(contact=contact or "unknown",
195 row = dict(contact=contact or "unknown",
195 contact_sort=contact.upper() or "unknown",
196 contact_sort=contact.upper() or "unknown",
196 name=name,
197 name=name,
197 name_sort=name,
198 name_sort=name,
198 url=url,
199 url=url,
199 description=description or "unknown",
200 description=description or "unknown",
200 description_sort=description.upper() or "unknown",
201 description_sort=description.upper() or "unknown",
201 lastchange=d,
202 lastchange=d,
202 lastchange_sort=d[1]-d[0],
203 lastchange_sort=d[1]-d[0],
203 sessionvars=sessionvars,
204 sessionvars=sessionvars,
204 archives=archivelist(u, "tip", url))
205 archives=archivelist(u, "tip", url))
205 if (not sortcolumn
206 if (not sortcolumn
206 or (sortcolumn, descending) == self.repos_sorted):
207 or (sortcolumn, descending) == self.repos_sorted):
207 # fast path for unsorted output
208 # fast path for unsorted output
208 row['parity'] = parity.next()
209 row['parity'] = parity.next()
209 yield row
210 yield row
210 else:
211 else:
211 rows.append((row["%s_sort" % sortcolumn], row))
212 rows.append((row["%s_sort" % sortcolumn], row))
212 if rows:
213 if rows:
213 rows.sort()
214 rows.sort()
214 if descending:
215 if descending:
215 rows.reverse()
216 rows.reverse()
216 for key, row in rows:
217 for key, row in rows:
217 row['parity'] = parity.next()
218 row['parity'] = parity.next()
218 yield row
219 yield row
219
220
220 sortable = ["name", "description", "contact", "lastchange"]
221 sortable = ["name", "description", "contact", "lastchange"]
221 sortcolumn, descending = self.repos_sorted
222 sortcolumn, descending = self.repos_sorted
222 if 'sort' in req.form:
223 if 'sort' in req.form:
223 sortcolumn = req.form['sort'][0]
224 sortcolumn = req.form['sort'][0]
224 descending = sortcolumn.startswith('-')
225 descending = sortcolumn.startswith('-')
225 if descending:
226 if descending:
226 sortcolumn = sortcolumn[1:]
227 sortcolumn = sortcolumn[1:]
227 if sortcolumn not in sortable:
228 if sortcolumn not in sortable:
228 sortcolumn = ""
229 sortcolumn = ""
229
230
230 sort = [("sort_%s" % column,
231 sort = [("sort_%s" % column,
231 "%s%s" % ((not descending and column == sortcolumn)
232 "%s%s" % ((not descending and column == sortcolumn)
232 and "-" or "", column))
233 and "-" or "", column))
233 for column in sortable]
234 for column in sortable]
234
235
235 return tmpl("index", entries=entries, subdir=subdir,
236 return tmpl("index", entries=entries, subdir=subdir,
236 sortcolumn=sortcolumn, descending=descending,
237 sortcolumn=sortcolumn, descending=descending,
237 **dict(sort))
238 **dict(sort))
238
239
239 def templater(self, req):
240 def templater(self, req):
240
241
241 def header(**map):
242 def header(**map):
242 yield tmpl('header', encoding=util._encoding, **map)
243 yield tmpl('header', encoding=util._encoding, **map)
243
244
244 def footer(**map):
245 def footer(**map):
245 yield tmpl("footer", **map)
246 yield tmpl("footer", **map)
246
247
247 def motd(**map):
248 def motd(**map):
248 if self.motd is not None:
249 if self.motd is not None:
249 yield self.motd
250 yield self.motd
250 else:
251 else:
251 yield config('web', 'motd', '')
252 yield config('web', 'motd', '')
252
253
253 def config(section, name, default=None, untrusted=True):
254 def config(section, name, default=None, untrusted=True):
254 return self.parentui.config(section, name, default, untrusted)
255 return self.parentui.config(section, name, default, untrusted)
255
256
256 url = req.env.get('SCRIPT_NAME', '')
257 url = req.env.get('SCRIPT_NAME', '')
257 if not url.endswith('/'):
258 if not url.endswith('/'):
258 url += '/'
259 url += '/'
259
260
260 staticurl = config('web', 'staticurl') or url + 'static/'
261 staticurl = config('web', 'staticurl') or url + 'static/'
261 if not staticurl.endswith('/'):
262 if not staticurl.endswith('/'):
262 staticurl += '/'
263 staticurl += '/'
263
264
264 style = self.style
265 style = self.style
265 if style is None:
266 if style is None:
266 style = config('web', 'style', '')
267 style = config('web', 'style', '')
267 if 'style' in req.form:
268 if 'style' in req.form:
268 style = req.form['style'][0]
269 style = req.form['style'][0]
269 if self.stripecount is None:
270 if self.stripecount is None:
270 self.stripecount = int(config('web', 'stripes', 1))
271 self.stripecount = int(config('web', 'stripes', 1))
271 mapfile = style_map(templater.templatepath(), style)
272 mapfile = style_map(templater.templatepath(), style)
272 tmpl = templater.templater(mapfile, templatefilters.filters,
273 tmpl = templater.templater(mapfile, templatefilters.filters,
273 defaults={"header": header,
274 defaults={"header": header,
274 "footer": footer,
275 "footer": footer,
275 "motd": motd,
276 "motd": motd,
276 "url": url,
277 "url": url,
277 "staticurl": staticurl})
278 "staticurl": staticurl})
278 return tmpl
279 return tmpl
@@ -1,294 +1,295 b''
1 # hgweb/server.py - The standalone hg web server.
1 # hgweb/server.py - The standalone hg web server.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
9 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
10 from mercurial import hg, util
10 from mercurial import hg, util
11 from mercurial.repo import RepoError
11 from hgweb_mod import hgweb
12 from hgweb_mod import hgweb
12 from hgwebdir_mod import hgwebdir
13 from hgwebdir_mod import hgwebdir
13 from mercurial.i18n import gettext as _
14 from mercurial.i18n import gettext as _
14
15
15 def _splitURI(uri):
16 def _splitURI(uri):
16 """ Return path and query splited from uri
17 """ Return path and query splited from uri
17
18
18 Just like CGI environment, the path is unquoted, the query is
19 Just like CGI environment, the path is unquoted, the query is
19 not.
20 not.
20 """
21 """
21 if '?' in uri:
22 if '?' in uri:
22 path, query = uri.split('?', 1)
23 path, query = uri.split('?', 1)
23 else:
24 else:
24 path, query = uri, ''
25 path, query = uri, ''
25 return urllib.unquote(path), query
26 return urllib.unquote(path), query
26
27
27 class _error_logger(object):
28 class _error_logger(object):
28 def __init__(self, handler):
29 def __init__(self, handler):
29 self.handler = handler
30 self.handler = handler
30 def flush(self):
31 def flush(self):
31 pass
32 pass
32 def write(self, str):
33 def write(self, str):
33 self.writelines(str.split('\n'))
34 self.writelines(str.split('\n'))
34 def writelines(self, seq):
35 def writelines(self, seq):
35 for msg in seq:
36 for msg in seq:
36 self.handler.log_error("HG error: %s", msg)
37 self.handler.log_error("HG error: %s", msg)
37
38
38 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
39 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
39
40
40 url_scheme = 'http'
41 url_scheme = 'http'
41
42
42 def __init__(self, *args, **kargs):
43 def __init__(self, *args, **kargs):
43 self.protocol_version = 'HTTP/1.1'
44 self.protocol_version = 'HTTP/1.1'
44 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
45 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
45
46
46 def _log_any(self, fp, format, *args):
47 def _log_any(self, fp, format, *args):
47 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
48 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
48 self.log_date_time_string(),
49 self.log_date_time_string(),
49 format % args))
50 format % args))
50 fp.flush()
51 fp.flush()
51
52
52 def log_error(self, format, *args):
53 def log_error(self, format, *args):
53 self._log_any(self.server.errorlog, format, *args)
54 self._log_any(self.server.errorlog, format, *args)
54
55
55 def log_message(self, format, *args):
56 def log_message(self, format, *args):
56 self._log_any(self.server.accesslog, format, *args)
57 self._log_any(self.server.accesslog, format, *args)
57
58
58 def do_write(self):
59 def do_write(self):
59 try:
60 try:
60 self.do_hgweb()
61 self.do_hgweb()
61 except socket.error, inst:
62 except socket.error, inst:
62 if inst[0] != errno.EPIPE:
63 if inst[0] != errno.EPIPE:
63 raise
64 raise
64
65
65 def do_POST(self):
66 def do_POST(self):
66 try:
67 try:
67 self.do_write()
68 self.do_write()
68 except StandardError, inst:
69 except StandardError, inst:
69 self._start_response("500 Internal Server Error", [])
70 self._start_response("500 Internal Server Error", [])
70 self._write("Internal Server Error")
71 self._write("Internal Server Error")
71 tb = "".join(traceback.format_exception(*sys.exc_info()))
72 tb = "".join(traceback.format_exception(*sys.exc_info()))
72 self.log_error("Exception happened during processing request '%s':\n%s",
73 self.log_error("Exception happened during processing request '%s':\n%s",
73 self.path, tb)
74 self.path, tb)
74
75
75 def do_GET(self):
76 def do_GET(self):
76 self.do_POST()
77 self.do_POST()
77
78
78 def do_hgweb(self):
79 def do_hgweb(self):
79 path, query = _splitURI(self.path)
80 path, query = _splitURI(self.path)
80
81
81 env = {}
82 env = {}
82 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
83 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
83 env['REQUEST_METHOD'] = self.command
84 env['REQUEST_METHOD'] = self.command
84 env['SERVER_NAME'] = self.server.server_name
85 env['SERVER_NAME'] = self.server.server_name
85 env['SERVER_PORT'] = str(self.server.server_port)
86 env['SERVER_PORT'] = str(self.server.server_port)
86 env['REQUEST_URI'] = self.path
87 env['REQUEST_URI'] = self.path
87 env['SCRIPT_NAME'] = self.server.prefix
88 env['SCRIPT_NAME'] = self.server.prefix
88 env['PATH_INFO'] = path[len(self.server.prefix):]
89 env['PATH_INFO'] = path[len(self.server.prefix):]
89 env['REMOTE_HOST'] = self.client_address[0]
90 env['REMOTE_HOST'] = self.client_address[0]
90 env['REMOTE_ADDR'] = self.client_address[0]
91 env['REMOTE_ADDR'] = self.client_address[0]
91 if query:
92 if query:
92 env['QUERY_STRING'] = query
93 env['QUERY_STRING'] = query
93
94
94 if self.headers.typeheader is None:
95 if self.headers.typeheader is None:
95 env['CONTENT_TYPE'] = self.headers.type
96 env['CONTENT_TYPE'] = self.headers.type
96 else:
97 else:
97 env['CONTENT_TYPE'] = self.headers.typeheader
98 env['CONTENT_TYPE'] = self.headers.typeheader
98 length = self.headers.getheader('content-length')
99 length = self.headers.getheader('content-length')
99 if length:
100 if length:
100 env['CONTENT_LENGTH'] = length
101 env['CONTENT_LENGTH'] = length
101 for header in [h for h in self.headers.keys()
102 for header in [h for h in self.headers.keys()
102 if h not in ('content-type', 'content-length')]:
103 if h not in ('content-type', 'content-length')]:
103 hkey = 'HTTP_' + header.replace('-', '_').upper()
104 hkey = 'HTTP_' + header.replace('-', '_').upper()
104 hval = self.headers.getheader(header)
105 hval = self.headers.getheader(header)
105 hval = hval.replace('\n', '').strip()
106 hval = hval.replace('\n', '').strip()
106 if hval:
107 if hval:
107 env[hkey] = hval
108 env[hkey] = hval
108 env['SERVER_PROTOCOL'] = self.request_version
109 env['SERVER_PROTOCOL'] = self.request_version
109 env['wsgi.version'] = (1, 0)
110 env['wsgi.version'] = (1, 0)
110 env['wsgi.url_scheme'] = self.url_scheme
111 env['wsgi.url_scheme'] = self.url_scheme
111 env['wsgi.input'] = self.rfile
112 env['wsgi.input'] = self.rfile
112 env['wsgi.errors'] = _error_logger(self)
113 env['wsgi.errors'] = _error_logger(self)
113 env['wsgi.multithread'] = isinstance(self.server,
114 env['wsgi.multithread'] = isinstance(self.server,
114 SocketServer.ThreadingMixIn)
115 SocketServer.ThreadingMixIn)
115 env['wsgi.multiprocess'] = isinstance(self.server,
116 env['wsgi.multiprocess'] = isinstance(self.server,
116 SocketServer.ForkingMixIn)
117 SocketServer.ForkingMixIn)
117 env['wsgi.run_once'] = 0
118 env['wsgi.run_once'] = 0
118
119
119 self.close_connection = True
120 self.close_connection = True
120 self.saved_status = None
121 self.saved_status = None
121 self.saved_headers = []
122 self.saved_headers = []
122 self.sent_headers = False
123 self.sent_headers = False
123 self.length = None
124 self.length = None
124 self.server.application(env, self._start_response)
125 self.server.application(env, self._start_response)
125
126
126 def send_headers(self):
127 def send_headers(self):
127 if not self.saved_status:
128 if not self.saved_status:
128 raise AssertionError("Sending headers before start_response() called")
129 raise AssertionError("Sending headers before start_response() called")
129 saved_status = self.saved_status.split(None, 1)
130 saved_status = self.saved_status.split(None, 1)
130 saved_status[0] = int(saved_status[0])
131 saved_status[0] = int(saved_status[0])
131 self.send_response(*saved_status)
132 self.send_response(*saved_status)
132 should_close = True
133 should_close = True
133 for h in self.saved_headers:
134 for h in self.saved_headers:
134 self.send_header(*h)
135 self.send_header(*h)
135 if h[0].lower() == 'content-length':
136 if h[0].lower() == 'content-length':
136 should_close = False
137 should_close = False
137 self.length = int(h[1])
138 self.length = int(h[1])
138 # The value of the Connection header is a list of case-insensitive
139 # The value of the Connection header is a list of case-insensitive
139 # tokens separated by commas and optional whitespace.
140 # tokens separated by commas and optional whitespace.
140 if 'close' in [token.strip().lower() for token in
141 if 'close' in [token.strip().lower() for token in
141 self.headers.get('connection', '').split(',')]:
142 self.headers.get('connection', '').split(',')]:
142 should_close = True
143 should_close = True
143 if should_close:
144 if should_close:
144 self.send_header('Connection', 'close')
145 self.send_header('Connection', 'close')
145 self.close_connection = should_close
146 self.close_connection = should_close
146 self.end_headers()
147 self.end_headers()
147 self.sent_headers = True
148 self.sent_headers = True
148
149
149 def _start_response(self, http_status, headers, exc_info=None):
150 def _start_response(self, http_status, headers, exc_info=None):
150 code, msg = http_status.split(None, 1)
151 code, msg = http_status.split(None, 1)
151 code = int(code)
152 code = int(code)
152 self.saved_status = http_status
153 self.saved_status = http_status
153 bad_headers = ('connection', 'transfer-encoding')
154 bad_headers = ('connection', 'transfer-encoding')
154 self.saved_headers = [h for h in headers
155 self.saved_headers = [h for h in headers
155 if h[0].lower() not in bad_headers]
156 if h[0].lower() not in bad_headers]
156 return self._write
157 return self._write
157
158
158 def _write(self, data):
159 def _write(self, data):
159 if not self.saved_status:
160 if not self.saved_status:
160 raise AssertionError("data written before start_response() called")
161 raise AssertionError("data written before start_response() called")
161 elif not self.sent_headers:
162 elif not self.sent_headers:
162 self.send_headers()
163 self.send_headers()
163 if self.length is not None:
164 if self.length is not None:
164 if len(data) > self.length:
165 if len(data) > self.length:
165 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
166 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
166 self.length = self.length - len(data)
167 self.length = self.length - len(data)
167 self.wfile.write(data)
168 self.wfile.write(data)
168 self.wfile.flush()
169 self.wfile.flush()
169
170
170 class _shgwebhandler(_hgwebhandler):
171 class _shgwebhandler(_hgwebhandler):
171
172
172 url_scheme = 'https'
173 url_scheme = 'https'
173
174
174 def setup(self):
175 def setup(self):
175 self.connection = self.request
176 self.connection = self.request
176 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
177 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
177 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
178 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
178
179
179 def do_write(self):
180 def do_write(self):
180 from OpenSSL.SSL import SysCallError
181 from OpenSSL.SSL import SysCallError
181 try:
182 try:
182 super(_shgwebhandler, self).do_write()
183 super(_shgwebhandler, self).do_write()
183 except SysCallError, inst:
184 except SysCallError, inst:
184 if inst.args[0] != errno.EPIPE:
185 if inst.args[0] != errno.EPIPE:
185 raise
186 raise
186
187
187 def handle_one_request(self):
188 def handle_one_request(self):
188 from OpenSSL.SSL import SysCallError, ZeroReturnError
189 from OpenSSL.SSL import SysCallError, ZeroReturnError
189 try:
190 try:
190 super(_shgwebhandler, self).handle_one_request()
191 super(_shgwebhandler, self).handle_one_request()
191 except (SysCallError, ZeroReturnError):
192 except (SysCallError, ZeroReturnError):
192 self.close_connection = True
193 self.close_connection = True
193 pass
194 pass
194
195
195 def create_server(ui, repo):
196 def create_server(ui, repo):
196 use_threads = True
197 use_threads = True
197
198
198 def openlog(opt, default):
199 def openlog(opt, default):
199 if opt and opt != '-':
200 if opt and opt != '-':
200 return open(opt, 'a')
201 return open(opt, 'a')
201 return default
202 return default
202
203
203 if repo is None:
204 if repo is None:
204 myui = ui
205 myui = ui
205 else:
206 else:
206 myui = repo.ui
207 myui = repo.ui
207 address = myui.config("web", "address", "")
208 address = myui.config("web", "address", "")
208 port = int(myui.config("web", "port", 8000))
209 port = int(myui.config("web", "port", 8000))
209 prefix = myui.config("web", "prefix", "")
210 prefix = myui.config("web", "prefix", "")
210 if prefix:
211 if prefix:
211 prefix = "/" + prefix.strip("/")
212 prefix = "/" + prefix.strip("/")
212 use_ipv6 = myui.configbool("web", "ipv6")
213 use_ipv6 = myui.configbool("web", "ipv6")
213 webdir_conf = myui.config("web", "webdir_conf")
214 webdir_conf = myui.config("web", "webdir_conf")
214 ssl_cert = myui.config("web", "certificate")
215 ssl_cert = myui.config("web", "certificate")
215 accesslog = openlog(myui.config("web", "accesslog", "-"), sys.stdout)
216 accesslog = openlog(myui.config("web", "accesslog", "-"), sys.stdout)
216 errorlog = openlog(myui.config("web", "errorlog", "-"), sys.stderr)
217 errorlog = openlog(myui.config("web", "errorlog", "-"), sys.stderr)
217
218
218 if use_threads:
219 if use_threads:
219 try:
220 try:
220 from threading import activeCount
221 from threading import activeCount
221 except ImportError:
222 except ImportError:
222 use_threads = False
223 use_threads = False
223
224
224 if use_threads:
225 if use_threads:
225 _mixin = SocketServer.ThreadingMixIn
226 _mixin = SocketServer.ThreadingMixIn
226 else:
227 else:
227 if hasattr(os, "fork"):
228 if hasattr(os, "fork"):
228 _mixin = SocketServer.ForkingMixIn
229 _mixin = SocketServer.ForkingMixIn
229 else:
230 else:
230 class _mixin:
231 class _mixin:
231 pass
232 pass
232
233
233 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
234 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
234
235
235 # SO_REUSEADDR has broken semantics on windows
236 # SO_REUSEADDR has broken semantics on windows
236 if os.name == 'nt':
237 if os.name == 'nt':
237 allow_reuse_address = 0
238 allow_reuse_address = 0
238
239
239 def __init__(self, *args, **kargs):
240 def __init__(self, *args, **kargs):
240 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
241 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
241 self.accesslog = accesslog
242 self.accesslog = accesslog
242 self.errorlog = errorlog
243 self.errorlog = errorlog
243 self.daemon_threads = True
244 self.daemon_threads = True
244 def make_handler():
245 def make_handler():
245 if webdir_conf:
246 if webdir_conf:
246 hgwebobj = hgwebdir(webdir_conf, ui)
247 hgwebobj = hgwebdir(webdir_conf, ui)
247 elif repo is not None:
248 elif repo is not None:
248 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
249 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
249 else:
250 else:
250 raise hg.RepoError(_("There is no Mercurial repository here"
251 raise RepoError(_("There is no Mercurial repository here"
251 " (.hg not found)"))
252 " (.hg not found)"))
252 return hgwebobj
253 return hgwebobj
253 self.application = make_handler()
254 self.application = make_handler()
254
255
255 addr = address
256 addr = address
256 if addr in ('', '::'):
257 if addr in ('', '::'):
257 addr = socket.gethostname()
258 addr = socket.gethostname()
258
259
259 self.addr, self.port = addr, port
260 self.addr, self.port = addr, port
260 self.prefix = prefix
261 self.prefix = prefix
261
262
262 if ssl_cert:
263 if ssl_cert:
263 try:
264 try:
264 from OpenSSL import SSL
265 from OpenSSL import SSL
265 ctx = SSL.Context(SSL.SSLv23_METHOD)
266 ctx = SSL.Context(SSL.SSLv23_METHOD)
266 except ImportError:
267 except ImportError:
267 raise util.Abort("SSL support is unavailable")
268 raise util.Abort("SSL support is unavailable")
268 ctx.use_privatekey_file(ssl_cert)
269 ctx.use_privatekey_file(ssl_cert)
269 ctx.use_certificate_file(ssl_cert)
270 ctx.use_certificate_file(ssl_cert)
270 sock = socket.socket(self.address_family, self.socket_type)
271 sock = socket.socket(self.address_family, self.socket_type)
271 self.socket = SSL.Connection(ctx, sock)
272 self.socket = SSL.Connection(ctx, sock)
272 self.server_bind()
273 self.server_bind()
273 self.server_activate()
274 self.server_activate()
274
275
275 class IPv6HTTPServer(MercurialHTTPServer):
276 class IPv6HTTPServer(MercurialHTTPServer):
276 address_family = getattr(socket, 'AF_INET6', None)
277 address_family = getattr(socket, 'AF_INET6', None)
277
278
278 def __init__(self, *args, **kwargs):
279 def __init__(self, *args, **kwargs):
279 if self.address_family is None:
280 if self.address_family is None:
280 raise hg.RepoError(_('IPv6 not available on this system'))
281 raise RepoError(_('IPv6 not available on this system'))
281 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
282 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
282
283
283 if ssl_cert:
284 if ssl_cert:
284 handler = _shgwebhandler
285 handler = _shgwebhandler
285 else:
286 else:
286 handler = _hgwebhandler
287 handler = _hgwebhandler
287
288
288 try:
289 try:
289 if use_ipv6:
290 if use_ipv6:
290 return IPv6HTTPServer((address, port), handler)
291 return IPv6HTTPServer((address, port), handler)
291 else:
292 else:
292 return MercurialHTTPServer((address, port), handler)
293 return MercurialHTTPServer((address, port), handler)
293 except socket.error, inst:
294 except socket.error, inst:
294 raise util.Abort(_('cannot start server: %s') % inst.args[1])
295 raise util.Abort(_('cannot start server: %s') % inst.args[1])
@@ -1,120 +1,121 b''
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import os, mimetypes
8 import os, mimetypes
9 from mercurial import revlog, util, hg
9 from mercurial import revlog, util
10 from mercurial.repo import RepoError
10 from common import staticfile, ErrorResponse, HTTP_OK, HTTP_NOT_FOUND
11 from common import staticfile, ErrorResponse, HTTP_OK, HTTP_NOT_FOUND
11
12
12 # __all__ is populated with the allowed commands. Be sure to add to it if
13 # __all__ is populated with the allowed commands. Be sure to add to it if
13 # you're adding a new command, or the new command won't work.
14 # you're adding a new command, or the new command won't work.
14
15
15 __all__ = [
16 __all__ = [
16 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
17 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
17 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
18 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
18 'archive', 'static',
19 'archive', 'static',
19 ]
20 ]
20
21
21 def log(web, req, tmpl):
22 def log(web, req, tmpl):
22 if 'file' in req.form and req.form['file'][0]:
23 if 'file' in req.form and req.form['file'][0]:
23 return filelog(web, req, tmpl)
24 return filelog(web, req, tmpl)
24 else:
25 else:
25 return changelog(web, req, tmpl)
26 return changelog(web, req, tmpl)
26
27
27 def rawfile(web, req, tmpl):
28 def rawfile(web, req, tmpl):
28 path = web.cleanpath(req.form.get('file', [''])[0])
29 path = web.cleanpath(req.form.get('file', [''])[0])
29 if not path:
30 if not path:
30 content = web.manifest(tmpl, web.changectx(req), path)
31 content = web.manifest(tmpl, web.changectx(req), path)
31 req.respond(HTTP_OK, web.ctype)
32 req.respond(HTTP_OK, web.ctype)
32 return content
33 return content
33
34
34 try:
35 try:
35 fctx = web.filectx(req)
36 fctx = web.filectx(req)
36 except revlog.LookupError:
37 except revlog.LookupError:
37 content = web.manifest(tmpl, web.changectx(req), path)
38 content = web.manifest(tmpl, web.changectx(req), path)
38 req.respond(HTTP_OK, web.ctype)
39 req.respond(HTTP_OK, web.ctype)
39 return content
40 return content
40
41
41 path = fctx.path()
42 path = fctx.path()
42 text = fctx.data()
43 text = fctx.data()
43 mt = mimetypes.guess_type(path)[0]
44 mt = mimetypes.guess_type(path)[0]
44 if mt is None or util.binary(text):
45 if mt is None or util.binary(text):
45 mt = mt or 'application/octet-stream'
46 mt = mt or 'application/octet-stream'
46
47
47 req.respond(HTTP_OK, mt, path, len(text))
48 req.respond(HTTP_OK, mt, path, len(text))
48 return [text]
49 return [text]
49
50
50 def file(web, req, tmpl):
51 def file(web, req, tmpl):
51 path = web.cleanpath(req.form.get('file', [''])[0])
52 path = web.cleanpath(req.form.get('file', [''])[0])
52 if path:
53 if path:
53 try:
54 try:
54 return web.filerevision(tmpl, web.filectx(req))
55 return web.filerevision(tmpl, web.filectx(req))
55 except revlog.LookupError:
56 except revlog.LookupError:
56 pass
57 pass
57
58
58 return web.manifest(tmpl, web.changectx(req), path)
59 return web.manifest(tmpl, web.changectx(req), path)
59
60
60 def changelog(web, req, tmpl, shortlog = False):
61 def changelog(web, req, tmpl, shortlog = False):
61 if 'node' in req.form:
62 if 'node' in req.form:
62 ctx = web.changectx(req)
63 ctx = web.changectx(req)
63 else:
64 else:
64 if 'rev' in req.form:
65 if 'rev' in req.form:
65 hi = req.form['rev'][0]
66 hi = req.form['rev'][0]
66 else:
67 else:
67 hi = web.repo.changelog.count() - 1
68 hi = web.repo.changelog.count() - 1
68 try:
69 try:
69 ctx = web.repo.changectx(hi)
70 ctx = web.repo.changectx(hi)
70 except hg.RepoError:
71 except RepoError:
71 return web.search(tmpl, hi) # XXX redirect to 404 page?
72 return web.search(tmpl, hi) # XXX redirect to 404 page?
72
73
73 return web.changelog(tmpl, ctx, shortlog = shortlog)
74 return web.changelog(tmpl, ctx, shortlog = shortlog)
74
75
75 def shortlog(web, req, tmpl):
76 def shortlog(web, req, tmpl):
76 return changelog(web, req, tmpl, shortlog = True)
77 return changelog(web, req, tmpl, shortlog = True)
77
78
78 def changeset(web, req, tmpl):
79 def changeset(web, req, tmpl):
79 return web.changeset(tmpl, web.changectx(req))
80 return web.changeset(tmpl, web.changectx(req))
80
81
81 rev = changeset
82 rev = changeset
82
83
83 def manifest(web, req, tmpl):
84 def manifest(web, req, tmpl):
84 return web.manifest(tmpl, web.changectx(req),
85 return web.manifest(tmpl, web.changectx(req),
85 web.cleanpath(req.form['path'][0]))
86 web.cleanpath(req.form['path'][0]))
86
87
87 def tags(web, req, tmpl):
88 def tags(web, req, tmpl):
88 return web.tags(tmpl)
89 return web.tags(tmpl)
89
90
90 def summary(web, req, tmpl):
91 def summary(web, req, tmpl):
91 return web.summary(tmpl)
92 return web.summary(tmpl)
92
93
93 def filediff(web, req, tmpl):
94 def filediff(web, req, tmpl):
94 return web.filediff(tmpl, web.filectx(req))
95 return web.filediff(tmpl, web.filectx(req))
95
96
96 diff = filediff
97 diff = filediff
97
98
98 def annotate(web, req, tmpl):
99 def annotate(web, req, tmpl):
99 return web.fileannotate(tmpl, web.filectx(req))
100 return web.fileannotate(tmpl, web.filectx(req))
100
101
101 def filelog(web, req, tmpl):
102 def filelog(web, req, tmpl):
102 return web.filelog(tmpl, web.filectx(req))
103 return web.filelog(tmpl, web.filectx(req))
103
104
104 def archive(web, req, tmpl):
105 def archive(web, req, tmpl):
105 type_ = req.form['type'][0]
106 type_ = req.form['type'][0]
106 allowed = web.configlist("web", "allow_archive")
107 allowed = web.configlist("web", "allow_archive")
107 if (type_ in web.archives and (type_ in allowed or
108 if (type_ in web.archives and (type_ in allowed or
108 web.configbool("web", "allow" + type_, False))):
109 web.configbool("web", "allow" + type_, False))):
109 web.archive(tmpl, req, req.form['node'][0], type_)
110 web.archive(tmpl, req, req.form['node'][0], type_)
110 return []
111 return []
111 raise ErrorResponse(HTTP_NOT_FOUND, 'Unsupported archive type: %s' % type_)
112 raise ErrorResponse(HTTP_NOT_FOUND, 'Unsupported archive type: %s' % type_)
112
113
113 def static(web, req, tmpl):
114 def static(web, req, tmpl):
114 fname = req.form['file'][0]
115 fname = req.form['file'][0]
115 # a repo owner may set web.static in .hg/hgrc to get any file
116 # a repo owner may set web.static in .hg/hgrc to get any file
116 # readable by the user running the CGI script
117 # readable by the user running the CGI script
117 static = web.config("web", "static",
118 static = web.config("web", "static",
118 os.path.join(web.templatepath, "static"),
119 os.path.join(web.templatepath, "static"),
119 untrusted=False)
120 untrusted=False)
120 return [staticfile(static, fname, req)]
121 return [staticfile(static, fname, req)]
General Comments 0
You need to be logged in to leave comments. Login now