##// END OF EJS Templates
manifest: remove execf/linkf methods
Matt Mackall -
r6749:51b0e799 default
parent child Browse files
Show More
@@ -1,290 +1,289 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.repo import RepoError
19 from mercurial.node import bin, hex, nullid
19 from mercurial.node import bin, hex, nullid
20 from mercurial import hg, revlog, util, context
20 from mercurial import hg, revlog, util, context
21
21
22 from common import NoRepo, commit, converter_source, converter_sink
22 from common import NoRepo, commit, converter_source, converter_sink
23
23
24 class mercurial_sink(converter_sink):
24 class mercurial_sink(converter_sink):
25 def __init__(self, ui, path):
25 def __init__(self, ui, path):
26 converter_sink.__init__(self, ui, path)
26 converter_sink.__init__(self, ui, path)
27 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
27 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
28 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
28 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
29 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
29 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
30 self.lastbranch = None
30 self.lastbranch = None
31 if os.path.isdir(path) and len(os.listdir(path)) > 0:
31 if os.path.isdir(path) and len(os.listdir(path)) > 0:
32 try:
32 try:
33 self.repo = hg.repository(self.ui, path)
33 self.repo = hg.repository(self.ui, path)
34 if not self.repo.local():
34 if not self.repo.local():
35 raise NoRepo(_('%s is not a local Mercurial repo') % path)
35 raise NoRepo(_('%s is not a local Mercurial repo') % path)
36 except RepoError, err:
36 except RepoError, err:
37 ui.print_exc()
37 ui.print_exc()
38 raise NoRepo(err.args[0])
38 raise NoRepo(err.args[0])
39 else:
39 else:
40 try:
40 try:
41 ui.status(_('initializing destination %s repository\n') % path)
41 ui.status(_('initializing destination %s repository\n') % path)
42 self.repo = hg.repository(self.ui, path, create=True)
42 self.repo = hg.repository(self.ui, path, create=True)
43 if not self.repo.local():
43 if not self.repo.local():
44 raise NoRepo(_('%s is not a local Mercurial repo') % path)
44 raise NoRepo(_('%s is not a local Mercurial repo') % path)
45 self.created.append(path)
45 self.created.append(path)
46 except RepoError, err:
46 except RepoError, err:
47 ui.print_exc()
47 ui.print_exc()
48 raise NoRepo("could not create hg repo %s as sink" % path)
48 raise NoRepo("could not create hg repo %s as sink" % path)
49 self.lock = None
49 self.lock = None
50 self.wlock = None
50 self.wlock = None
51 self.filemapmode = False
51 self.filemapmode = False
52
52
53 def before(self):
53 def before(self):
54 self.ui.debug(_('run hg sink pre-conversion action\n'))
54 self.ui.debug(_('run hg sink pre-conversion action\n'))
55 self.wlock = self.repo.wlock()
55 self.wlock = self.repo.wlock()
56 self.lock = self.repo.lock()
56 self.lock = self.repo.lock()
57
57
58 def after(self):
58 def after(self):
59 self.ui.debug(_('run hg sink post-conversion action\n'))
59 self.ui.debug(_('run hg sink post-conversion action\n'))
60 self.lock = None
60 self.lock = None
61 self.wlock = None
61 self.wlock = None
62
62
63 def revmapfile(self):
63 def revmapfile(self):
64 return os.path.join(self.path, ".hg", "shamap")
64 return os.path.join(self.path, ".hg", "shamap")
65
65
66 def authorfile(self):
66 def authorfile(self):
67 return os.path.join(self.path, ".hg", "authormap")
67 return os.path.join(self.path, ".hg", "authormap")
68
68
69 def getheads(self):
69 def getheads(self):
70 h = self.repo.changelog.heads()
70 h = self.repo.changelog.heads()
71 return [ hex(x) for x in h ]
71 return [ hex(x) for x in h ]
72
72
73 def setbranch(self, branch, pbranches):
73 def setbranch(self, branch, pbranches):
74 if not self.clonebranches:
74 if not self.clonebranches:
75 return
75 return
76
76
77 setbranch = (branch != self.lastbranch)
77 setbranch = (branch != self.lastbranch)
78 self.lastbranch = branch
78 self.lastbranch = branch
79 if not branch:
79 if not branch:
80 branch = 'default'
80 branch = 'default'
81 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
81 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
82 pbranch = pbranches and pbranches[0][1] or 'default'
82 pbranch = pbranches and pbranches[0][1] or 'default'
83
83
84 branchpath = os.path.join(self.path, branch)
84 branchpath = os.path.join(self.path, branch)
85 if setbranch:
85 if setbranch:
86 self.after()
86 self.after()
87 try:
87 try:
88 self.repo = hg.repository(self.ui, branchpath)
88 self.repo = hg.repository(self.ui, branchpath)
89 except:
89 except:
90 self.repo = hg.repository(self.ui, branchpath, create=True)
90 self.repo = hg.repository(self.ui, branchpath, create=True)
91 self.before()
91 self.before()
92
92
93 # pbranches may bring revisions from other branches (merge parents)
93 # pbranches may bring revisions from other branches (merge parents)
94 # Make sure we have them, or pull them.
94 # Make sure we have them, or pull them.
95 missings = {}
95 missings = {}
96 for b in pbranches:
96 for b in pbranches:
97 try:
97 try:
98 self.repo.lookup(b[0])
98 self.repo.lookup(b[0])
99 except:
99 except:
100 missings.setdefault(b[1], []).append(b[0])
100 missings.setdefault(b[1], []).append(b[0])
101
101
102 if missings:
102 if missings:
103 self.after()
103 self.after()
104 for pbranch, heads in missings.iteritems():
104 for pbranch, heads in missings.iteritems():
105 pbranchpath = os.path.join(self.path, pbranch)
105 pbranchpath = os.path.join(self.path, pbranch)
106 prepo = hg.repository(self.ui, pbranchpath)
106 prepo = hg.repository(self.ui, pbranchpath)
107 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
107 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
108 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
108 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
109 self.before()
109 self.before()
110
110
111 def putcommit(self, files, copies, parents, commit, source):
111 def putcommit(self, files, copies, parents, commit, source):
112
112
113 files = dict(files)
113 files = dict(files)
114 def getfilectx(repo, memctx, f):
114 def getfilectx(repo, memctx, f):
115 v = files[f]
115 v = files[f]
116 data = source.getfile(f, v)
116 data = source.getfile(f, v)
117 e = source.getmode(f, v)
117 e = source.getmode(f, v)
118 return context.memfilectx(f, data, 'l' in e, 'x' in e, copies.get(f))
118 return context.memfilectx(f, data, 'l' in e, 'x' in e, copies.get(f))
119
119
120 pl = []
120 pl = []
121 for p in parents:
121 for p in parents:
122 if p not in pl:
122 if p not in pl:
123 pl.append(p)
123 pl.append(p)
124 parents = pl
124 parents = pl
125 nparents = len(parents)
125 nparents = len(parents)
126 if self.filemapmode and nparents == 1:
126 if self.filemapmode and nparents == 1:
127 m1node = self.repo.changelog.read(bin(parents[0]))[0]
127 m1node = self.repo.changelog.read(bin(parents[0]))[0]
128 parent = parents[0]
128 parent = parents[0]
129
129
130 if len(parents) < 2: parents.append("0" * 40)
130 if len(parents) < 2: parents.append("0" * 40)
131 if len(parents) < 2: parents.append("0" * 40)
131 if len(parents) < 2: parents.append("0" * 40)
132 p2 = parents.pop(0)
132 p2 = parents.pop(0)
133
133
134 text = commit.desc
134 text = commit.desc
135 extra = commit.extra.copy()
135 extra = commit.extra.copy()
136 if self.branchnames and commit.branch:
136 if self.branchnames and commit.branch:
137 extra['branch'] = commit.branch
137 extra['branch'] = commit.branch
138 if commit.rev:
138 if commit.rev:
139 extra['convert_revision'] = commit.rev
139 extra['convert_revision'] = commit.rev
140
140
141 while parents:
141 while parents:
142 p1 = p2
142 p1 = p2
143 p2 = parents.pop(0)
143 p2 = parents.pop(0)
144 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(), getfilectx,
144 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(), getfilectx,
145 commit.author, commit.date, extra)
145 commit.author, commit.date, extra)
146 a = self.repo.commitctx(ctx)
146 a = self.repo.commitctx(ctx)
147 text = "(octopus merge fixup)\n"
147 text = "(octopus merge fixup)\n"
148 p2 = hex(self.repo.changelog.tip())
148 p2 = hex(self.repo.changelog.tip())
149
149
150 if self.filemapmode and nparents == 1:
150 if self.filemapmode and nparents == 1:
151 man = self.repo.manifest
151 man = self.repo.manifest
152 mnode = self.repo.changelog.read(bin(p2))[0]
152 mnode = self.repo.changelog.read(bin(p2))[0]
153 if not man.cmp(m1node, man.revision(mnode)):
153 if not man.cmp(m1node, man.revision(mnode)):
154 self.repo.rollback()
154 self.repo.rollback()
155 return parent
155 return parent
156 return p2
156 return p2
157
157
158 def puttags(self, tags):
158 def puttags(self, tags):
159 try:
159 try:
160 parentctx = self.repo[self.tagsbranch]
160 parentctx = self.repo[self.tagsbranch]
161 tagparent = parentctx.node()
161 tagparent = parentctx.node()
162 except RepoError, inst:
162 except RepoError, inst:
163 parentctx = None
163 parentctx = None
164 tagparent = nullid
164 tagparent = nullid
165
165
166 try:
166 try:
167 old = parentctx.filectx(".hgtags").data()
167 old = parentctx.filectx(".hgtags").data()
168 oldlines = old.splitlines(1)
168 oldlines = old.splitlines(1)
169 oldlines.sort()
169 oldlines.sort()
170 except:
170 except:
171 oldlines = []
171 oldlines = []
172
172
173 newlines = [("%s %s\n" % (tags[tag], tag)) for tag in tags.keys()]
173 newlines = [("%s %s\n" % (tags[tag], tag)) for tag in tags.keys()]
174 newlines.sort()
174 newlines.sort()
175
175
176 if newlines == oldlines:
176 if newlines == oldlines:
177 return None
177 return None
178 data = "".join(newlines)
178 data = "".join(newlines)
179
179
180 def getfilectx(repo, memctx, f):
180 def getfilectx(repo, memctx, f):
181 return context.memfilectx(f, data, False, False, None)
181 return context.memfilectx(f, data, False, False, None)
182
182
183 self.ui.status("updating tags\n")
183 self.ui.status("updating tags\n")
184 date = "%s 0" % int(time.mktime(time.gmtime()))
184 date = "%s 0" % int(time.mktime(time.gmtime()))
185 extra = {'branch': self.tagsbranch}
185 extra = {'branch': self.tagsbranch}
186 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
186 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
187 [".hgtags"], getfilectx, "convert-repo", date,
187 [".hgtags"], getfilectx, "convert-repo", date,
188 extra)
188 extra)
189 self.repo.commitctx(ctx)
189 self.repo.commitctx(ctx)
190 return hex(self.repo.changelog.tip())
190 return hex(self.repo.changelog.tip())
191
191
192 def setfilemapmode(self, active):
192 def setfilemapmode(self, active):
193 self.filemapmode = active
193 self.filemapmode = active
194
194
195 class mercurial_source(converter_source):
195 class mercurial_source(converter_source):
196 def __init__(self, ui, path, rev=None):
196 def __init__(self, ui, path, rev=None):
197 converter_source.__init__(self, ui, path, rev)
197 converter_source.__init__(self, ui, path, rev)
198 self.saverev = ui.configbool('convert', 'hg.saverev', True)
198 self.saverev = ui.configbool('convert', 'hg.saverev', True)
199 try:
199 try:
200 self.repo = hg.repository(self.ui, path)
200 self.repo = hg.repository(self.ui, path)
201 # try to provoke an exception if this isn't really a hg
201 # try to provoke an exception if this isn't really a hg
202 # repo, but some other bogus compatible-looking url
202 # repo, but some other bogus compatible-looking url
203 if not self.repo.local():
203 if not self.repo.local():
204 raise RepoError()
204 raise RepoError()
205 except RepoError:
205 except RepoError:
206 ui.print_exc()
206 ui.print_exc()
207 raise NoRepo("%s is not a local Mercurial repo" % path)
207 raise NoRepo("%s is not a local Mercurial repo" % path)
208 self.lastrev = None
208 self.lastrev = None
209 self.lastctx = None
209 self.lastctx = None
210 self._changescache = None
210 self._changescache = None
211 self.convertfp = None
211 self.convertfp = None
212
212
213 def changectx(self, rev):
213 def changectx(self, rev):
214 if self.lastrev != rev:
214 if self.lastrev != rev:
215 self.lastctx = self.repo[rev]
215 self.lastctx = self.repo[rev]
216 self.lastrev = rev
216 self.lastrev = rev
217 return self.lastctx
217 return self.lastctx
218
218
219 def getheads(self):
219 def getheads(self):
220 if self.rev:
220 if self.rev:
221 return [hex(self.repo[self.rev].node())]
221 return [hex(self.repo[self.rev].node())]
222 else:
222 else:
223 return [hex(node) for node in self.repo.heads()]
223 return [hex(node) for node in self.repo.heads()]
224
224
225 def getfile(self, name, rev):
225 def getfile(self, name, rev):
226 try:
226 try:
227 return self.changectx(rev)[name].data()
227 return self.changectx(rev)[name].data()
228 except revlog.LookupError, err:
228 except revlog.LookupError, err:
229 raise IOError(err)
229 raise IOError(err)
230
230
231 def getmode(self, name, rev):
231 def getmode(self, name, rev):
232 m = self.changectx(rev).manifest()
232 return self.changectx(rev).manifest().flags(name)
233 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
234
233
235 def getchanges(self, rev):
234 def getchanges(self, rev):
236 ctx = self.changectx(rev)
235 ctx = self.changectx(rev)
237 if self._changescache and self._changescache[0] == rev:
236 if self._changescache and self._changescache[0] == rev:
238 m, a, r = self._changescache[1]
237 m, a, r = self._changescache[1]
239 else:
238 else:
240 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
239 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
241 changes = [(name, rev) for name in m + a + r]
240 changes = [(name, rev) for name in m + a + r]
242 changes.sort()
241 changes.sort()
243 return (changes, self.getcopies(ctx, m + a))
242 return (changes, self.getcopies(ctx, m + a))
244
243
245 def getcopies(self, ctx, files):
244 def getcopies(self, ctx, files):
246 copies = {}
245 copies = {}
247 for name in files:
246 for name in files:
248 try:
247 try:
249 copies[name] = ctx.filectx(name).renamed()[0]
248 copies[name] = ctx.filectx(name).renamed()[0]
250 except TypeError:
249 except TypeError:
251 pass
250 pass
252 return copies
251 return copies
253
252
254 def getcommit(self, rev):
253 def getcommit(self, rev):
255 ctx = self.changectx(rev)
254 ctx = self.changectx(rev)
256 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
255 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
257 if self.saverev:
256 if self.saverev:
258 crev = rev
257 crev = rev
259 else:
258 else:
260 crev = None
259 crev = None
261 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
260 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
262 desc=ctx.description(), rev=crev, parents=parents,
261 desc=ctx.description(), rev=crev, parents=parents,
263 branch=ctx.branch(), extra=ctx.extra())
262 branch=ctx.branch(), extra=ctx.extra())
264
263
265 def gettags(self):
264 def gettags(self):
266 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
265 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
267 return dict([(name, hex(node)) for name, node in tags])
266 return dict([(name, hex(node)) for name, node in tags])
268
267
269 def getchangedfiles(self, rev, i):
268 def getchangedfiles(self, rev, i):
270 ctx = self.changectx(rev)
269 ctx = self.changectx(rev)
271 i = i or 0
270 i = i or 0
272 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
271 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
273
272
274 if i == 0:
273 if i == 0:
275 self._changescache = (rev, changes)
274 self._changescache = (rev, changes)
276
275
277 return changes[0] + changes[1] + changes[2]
276 return changes[0] + changes[1] + changes[2]
278
277
279 def converted(self, rev, destrev):
278 def converted(self, rev, destrev):
280 if self.convertfp is None:
279 if self.convertfp is None:
281 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
280 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
282 'a')
281 'a')
283 self.convertfp.write('%s %s\n' % (destrev, rev))
282 self.convertfp.write('%s %s\n' % (destrev, rev))
284 self.convertfp.flush()
283 self.convertfp.flush()
285
284
286 def before(self):
285 def before(self):
287 self.ui.debug(_('run hg source pre-conversion action\n'))
286 self.ui.debug(_('run hg source pre-conversion action\n'))
288
287
289 def after(self):
288 def after(self):
290 self.ui.debug(_('run hg source post-conversion action\n'))
289 self.ui.debug(_('run hg source post-conversion action\n'))
@@ -1,567 +1,565 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
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 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a DSCM
10 # Keyword expansion hack against the grain of a DSCM
11 #
11 #
12 # There are many good reasons why this is not needed in a distributed
12 # There are many good reasons why this is not needed in a distributed
13 # SCM, still it may be useful in very small projects based on single
13 # SCM, still it may be useful in very small projects based on single
14 # files (like LaTeX packages), that are mostly addressed to an audience
14 # files (like LaTeX packages), that are mostly addressed to an audience
15 # not running a version control system.
15 # not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
19 #
19 #
20 # Keyword expansion is based on Mercurial's changeset template mappings.
20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 #
21 #
22 # Binary files are not touched.
22 # Binary files are not touched.
23 #
23 #
24 # Setup in hgrc:
24 # Setup in hgrc:
25 #
25 #
26 # [extensions]
26 # [extensions]
27 # # enable extension
27 # # enable extension
28 # hgext.keyword =
28 # hgext.keyword =
29 #
29 #
30 # Files to act upon/ignore are specified in the [keyword] section.
30 # Files to act upon/ignore are specified in the [keyword] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
32 #
32 #
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
34
34
35 '''keyword expansion in local repositories
35 '''keyword expansion in local repositories
36
36
37 This extension expands RCS/CVS-like or self-customized $Keywords$
37 This extension expands RCS/CVS-like or self-customized $Keywords$
38 in tracked text files selected by your configuration.
38 in tracked text files selected by your configuration.
39
39
40 Keywords are only expanded in local repositories and not stored in
40 Keywords are only expanded in local repositories and not stored in
41 the change history. The mechanism can be regarded as a convenience
41 the change history. The mechanism can be regarded as a convenience
42 for the current user or for archive distribution.
42 for the current user or for archive distribution.
43
43
44 Configuration is done in the [keyword] and [keywordmaps] sections
44 Configuration is done in the [keyword] and [keywordmaps] sections
45 of hgrc files.
45 of hgrc files.
46
46
47 Example:
47 Example:
48
48
49 [keyword]
49 [keyword]
50 # expand keywords in every python file except those matching "x*"
50 # expand keywords in every python file except those matching "x*"
51 **.py =
51 **.py =
52 x* = ignore
52 x* = ignore
53
53
54 Note: the more specific you are in your filename patterns
54 Note: the more specific you are in your filename patterns
55 the less you lose speed in huge repos.
55 the less you lose speed in huge repos.
56
56
57 For [keywordmaps] template mapping and expansion demonstration and
57 For [keywordmaps] template mapping and expansion demonstration and
58 control run "hg kwdemo".
58 control run "hg kwdemo".
59
59
60 An additional date template filter {date|utcdate} is provided.
60 An additional date template filter {date|utcdate} is provided.
61
61
62 The default template mappings (view with "hg kwdemo -d") can be replaced
62 The default template mappings (view with "hg kwdemo -d") can be replaced
63 with customized keywords and templates.
63 with customized keywords and templates.
64 Again, run "hg kwdemo" to control the results of your config changes.
64 Again, run "hg kwdemo" to control the results of your config changes.
65
65
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
67 the risk of inadvertedly storing expanded keywords in the change history.
67 the risk of inadvertedly storing expanded keywords in the change history.
68
68
69 To force expansion after enabling it, or a configuration change, run
69 To force expansion after enabling it, or a configuration change, run
70 "hg kwexpand".
70 "hg kwexpand".
71
71
72 Also, when committing with the record extension or using mq's qrecord, be aware
72 Also, when committing with the record extension or using mq's qrecord, be aware
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
74 question to update keyword expansions after all changes have been checked in.
74 question to update keyword expansions after all changes have been checked in.
75
75
76 Expansions spanning more than one line and incremental expansions,
76 Expansions spanning more than one line and incremental expansions,
77 like CVS' $Log$, are not supported. A keyword template map
77 like CVS' $Log$, are not supported. A keyword template map
78 "Log = {desc}" expands to the first line of the changeset description.
78 "Log = {desc}" expands to the first line of the changeset description.
79 '''
79 '''
80
80
81 from mercurial import commands, cmdutil, dispatch, filelog, revlog
81 from mercurial import commands, cmdutil, dispatch, filelog, revlog
82 from mercurial import patch, localrepo, templater, templatefilters, util
82 from mercurial import patch, localrepo, templater, templatefilters, util
83 from mercurial.hgweb import webcommands
83 from mercurial.hgweb import webcommands
84 from mercurial.node import nullid, hex
84 from mercurial.node import nullid, hex
85 from mercurial.i18n import _
85 from mercurial.i18n import _
86 import re, shutil, tempfile, time
86 import re, shutil, tempfile, time
87
87
88 commands.optionalrepo += ' kwdemo'
88 commands.optionalrepo += ' kwdemo'
89
89
90 # hg commands that do not act on keywords
90 # hg commands that do not act on keywords
91 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
91 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
92 ' log outgoing push rename rollback tip'
92 ' log outgoing push rename rollback tip'
93 ' convert email glog')
93 ' convert email glog')
94
94
95 # hg commands that trigger expansion only when writing to working dir,
95 # hg commands that trigger expansion only when writing to working dir,
96 # not when reading filelog, and unexpand when reading from working dir
96 # not when reading filelog, and unexpand when reading from working dir
97 restricted = 'record qfold qimport qnew qpush qrefresh qrecord'
97 restricted = 'record qfold qimport qnew qpush qrefresh qrecord'
98
98
99 def utcdate(date):
99 def utcdate(date):
100 '''Returns hgdate in cvs-like UTC format.'''
100 '''Returns hgdate in cvs-like UTC format.'''
101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
102
102
103 # make keyword tools accessible
103 # make keyword tools accessible
104 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
104 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
105
105
106
106
107 class kwtemplater(object):
107 class kwtemplater(object):
108 '''
108 '''
109 Sets up keyword templates, corresponding keyword regex, and
109 Sets up keyword templates, corresponding keyword regex, and
110 provides keyword substitution functions.
110 provides keyword substitution functions.
111 '''
111 '''
112 templates = {
112 templates = {
113 'Revision': '{node|short}',
113 'Revision': '{node|short}',
114 'Author': '{author|user}',
114 'Author': '{author|user}',
115 'Date': '{date|utcdate}',
115 'Date': '{date|utcdate}',
116 'RCSFile': '{file|basename},v',
116 'RCSFile': '{file|basename},v',
117 'Source': '{root}/{file},v',
117 'Source': '{root}/{file},v',
118 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
118 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
119 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
119 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
120 }
120 }
121
121
122 def __init__(self, ui, repo):
122 def __init__(self, ui, repo):
123 self.ui = ui
123 self.ui = ui
124 self.repo = repo
124 self.repo = repo
125 self.matcher = util.matcher(repo.root,
125 self.matcher = util.matcher(repo.root,
126 inc=kwtools['inc'], exc=kwtools['exc'])[1]
126 inc=kwtools['inc'], exc=kwtools['exc'])[1]
127 self.restrict = kwtools['hgcmd'] in restricted.split()
127 self.restrict = kwtools['hgcmd'] in restricted.split()
128
128
129 kwmaps = self.ui.configitems('keywordmaps')
129 kwmaps = self.ui.configitems('keywordmaps')
130 if kwmaps: # override default templates
130 if kwmaps: # override default templates
131 kwmaps = [(k, templater.parsestring(v, False))
131 kwmaps = [(k, templater.parsestring(v, False))
132 for (k, v) in kwmaps]
132 for (k, v) in kwmaps]
133 self.templates = dict(kwmaps)
133 self.templates = dict(kwmaps)
134 escaped = map(re.escape, self.templates.keys())
134 escaped = map(re.escape, self.templates.keys())
135 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
135 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
136 self.re_kw = re.compile(kwpat)
136 self.re_kw = re.compile(kwpat)
137
137
138 templatefilters.filters['utcdate'] = utcdate
138 templatefilters.filters['utcdate'] = utcdate
139 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
139 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
140 False, '', False)
140 False, '', False)
141
141
142 def getnode(self, path, fnode):
142 def getnode(self, path, fnode):
143 '''Derives changenode from file path and filenode.'''
143 '''Derives changenode from file path and filenode.'''
144 # used by kwfilelog.read and kwexpand
144 # used by kwfilelog.read and kwexpand
145 c = self.repo.filectx(path, fileid=fnode)
145 c = self.repo.filectx(path, fileid=fnode)
146 return c.node()
146 return c.node()
147
147
148 def substitute(self, data, path, node, subfunc):
148 def substitute(self, data, path, node, subfunc):
149 '''Replaces keywords in data with expanded template.'''
149 '''Replaces keywords in data with expanded template.'''
150 def kwsub(mobj):
150 def kwsub(mobj):
151 kw = mobj.group(1)
151 kw = mobj.group(1)
152 self.ct.use_template(self.templates[kw])
152 self.ct.use_template(self.templates[kw])
153 self.ui.pushbuffer()
153 self.ui.pushbuffer()
154 self.ct.show(changenode=node, root=self.repo.root, file=path)
154 self.ct.show(changenode=node, root=self.repo.root, file=path)
155 ekw = templatefilters.firstline(self.ui.popbuffer())
155 ekw = templatefilters.firstline(self.ui.popbuffer())
156 return '$%s: %s $' % (kw, ekw)
156 return '$%s: %s $' % (kw, ekw)
157 return subfunc(kwsub, data)
157 return subfunc(kwsub, data)
158
158
159 def expand(self, path, node, data):
159 def expand(self, path, node, data):
160 '''Returns data with keywords expanded.'''
160 '''Returns data with keywords expanded.'''
161 if not self.restrict and self.matcher(path) and not util.binary(data):
161 if not self.restrict and self.matcher(path) and not util.binary(data):
162 changenode = self.getnode(path, node)
162 changenode = self.getnode(path, node)
163 return self.substitute(data, path, changenode, self.re_kw.sub)
163 return self.substitute(data, path, changenode, self.re_kw.sub)
164 return data
164 return data
165
165
166 def iskwfile(self, path, islink):
166 def iskwfile(self, path, flagfunc):
167 '''Returns true if path matches [keyword] pattern
167 '''Returns true if path matches [keyword] pattern
168 and is not a symbolic link.
168 and is not a symbolic link.
169 Caveat: localrepository._link fails on Windows.'''
169 Caveat: localrepository._link fails on Windows.'''
170 return self.matcher(path) and not islink(path)
170 return self.matcher(path) and not 'l' in flagfunc(path)
171
171
172 def overwrite(self, node, expand, files):
172 def overwrite(self, node, expand, files):
173 '''Overwrites selected files expanding/shrinking keywords.'''
173 '''Overwrites selected files expanding/shrinking keywords.'''
174 if node is not None: # commit
174 if node is not None: # commit
175 ctx = self.repo[node]
175 ctx = self.repo[node]
176 mf = ctx.manifest()
176 mf = ctx.manifest()
177 files = [f for f in ctx.files() if f in mf]
177 files = [f for f in ctx.files() if f in mf]
178 notify = self.ui.debug
178 notify = self.ui.debug
179 else: # kwexpand/kwshrink
179 else: # kwexpand/kwshrink
180 ctx = self.repo['.']
180 ctx = self.repo['.']
181 mf = ctx.manifest()
182 notify = self.ui.note
181 notify = self.ui.note
183 candidates = [f for f in files if self.iskwfile(f, mf.linkf)]
182 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
184 if candidates:
183 if candidates:
185 self.restrict = True # do not expand when reading
184 self.restrict = True # do not expand when reading
186 candidates.sort()
185 candidates.sort()
187 action = expand and 'expanding' or 'shrinking'
186 action = expand and 'expanding' or 'shrinking'
188 for f in candidates:
187 for f in candidates:
189 fp = self.repo.file(f)
188 fp = self.repo.file(f)
190 data = fp.read(mf[f])
189 data = fp.read(mf[f])
191 if util.binary(data):
190 if util.binary(data):
192 continue
191 continue
193 if expand:
192 if expand:
194 changenode = node or self.getnode(f, mf[f])
193 changenode = node or self.getnode(f, mf[f])
195 data, found = self.substitute(data, f, changenode,
194 data, found = self.substitute(data, f, changenode,
196 self.re_kw.subn)
195 self.re_kw.subn)
197 else:
196 else:
198 found = self.re_kw.search(data)
197 found = self.re_kw.search(data)
199 if found:
198 if found:
200 notify(_('overwriting %s %s keywords\n') % (f, action))
199 notify(_('overwriting %s %s keywords\n') % (f, action))
201 self.repo.wwrite(f, data, mf.flags(f))
200 self.repo.wwrite(f, data, mf.flags(f))
202 self.repo.dirstate.normal(f)
201 self.repo.dirstate.normal(f)
203 self.restrict = False
202 self.restrict = False
204
203
205 def shrinktext(self, text):
204 def shrinktext(self, text):
206 '''Unconditionally removes all keyword substitutions from text.'''
205 '''Unconditionally removes all keyword substitutions from text.'''
207 return self.re_kw.sub(r'$\1$', text)
206 return self.re_kw.sub(r'$\1$', text)
208
207
209 def shrink(self, fname, text):
208 def shrink(self, fname, text):
210 '''Returns text with all keyword substitutions removed.'''
209 '''Returns text with all keyword substitutions removed.'''
211 if self.matcher(fname) and not util.binary(text):
210 if self.matcher(fname) and not util.binary(text):
212 return self.shrinktext(text)
211 return self.shrinktext(text)
213 return text
212 return text
214
213
215 def shrinklines(self, fname, lines):
214 def shrinklines(self, fname, lines):
216 '''Returns lines with keyword substitutions removed.'''
215 '''Returns lines with keyword substitutions removed.'''
217 if self.matcher(fname):
216 if self.matcher(fname):
218 text = ''.join(lines)
217 text = ''.join(lines)
219 if not util.binary(text):
218 if not util.binary(text):
220 return self.shrinktext(text).splitlines(True)
219 return self.shrinktext(text).splitlines(True)
221 return lines
220 return lines
222
221
223 def wread(self, fname, data):
222 def wread(self, fname, data):
224 '''If in restricted mode returns data read from wdir with
223 '''If in restricted mode returns data read from wdir with
225 keyword substitutions removed.'''
224 keyword substitutions removed.'''
226 return self.restrict and self.shrink(fname, data) or data
225 return self.restrict and self.shrink(fname, data) or data
227
226
228 class kwfilelog(filelog.filelog):
227 class kwfilelog(filelog.filelog):
229 '''
228 '''
230 Subclass of filelog to hook into its read, add, cmp methods.
229 Subclass of filelog to hook into its read, add, cmp methods.
231 Keywords are "stored" unexpanded, and processed on reading.
230 Keywords are "stored" unexpanded, and processed on reading.
232 '''
231 '''
233 def __init__(self, opener, kwt, path):
232 def __init__(self, opener, kwt, path):
234 super(kwfilelog, self).__init__(opener, path)
233 super(kwfilelog, self).__init__(opener, path)
235 self.kwt = kwt
234 self.kwt = kwt
236 self.path = path
235 self.path = path
237
236
238 def read(self, node):
237 def read(self, node):
239 '''Expands keywords when reading filelog.'''
238 '''Expands keywords when reading filelog.'''
240 data = super(kwfilelog, self).read(node)
239 data = super(kwfilelog, self).read(node)
241 return self.kwt.expand(self.path, node, data)
240 return self.kwt.expand(self.path, node, data)
242
241
243 def add(self, text, meta, tr, link, p1=None, p2=None):
242 def add(self, text, meta, tr, link, p1=None, p2=None):
244 '''Removes keyword substitutions when adding to filelog.'''
243 '''Removes keyword substitutions when adding to filelog.'''
245 text = self.kwt.shrink(self.path, text)
244 text = self.kwt.shrink(self.path, text)
246 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
245 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
247
246
248 def cmp(self, node, text):
247 def cmp(self, node, text):
249 '''Removes keyword substitutions for comparison.'''
248 '''Removes keyword substitutions for comparison.'''
250 text = self.kwt.shrink(self.path, text)
249 text = self.kwt.shrink(self.path, text)
251 if self.renamed(node):
250 if self.renamed(node):
252 t2 = super(kwfilelog, self).read(node)
251 t2 = super(kwfilelog, self).read(node)
253 return t2 != text
252 return t2 != text
254 return revlog.revlog.cmp(self, node, text)
253 return revlog.revlog.cmp(self, node, text)
255
254
256 def _status(ui, repo, kwt, *pats, **opts):
255 def _status(ui, repo, kwt, *pats, **opts):
257 '''Bails out if [keyword] configuration is not active.
256 '''Bails out if [keyword] configuration is not active.
258 Returns status of working directory.'''
257 Returns status of working directory.'''
259 if kwt:
258 if kwt:
260 matcher = cmdutil.match(repo, pats, opts)
259 matcher = cmdutil.match(repo, pats, opts)
261 return repo.status(match=matcher, list_clean=True)
260 return repo.status(match=matcher, list_clean=True)
262 if ui.configitems('keyword'):
261 if ui.configitems('keyword'):
263 raise util.Abort(_('[keyword] patterns cannot match'))
262 raise util.Abort(_('[keyword] patterns cannot match'))
264 raise util.Abort(_('no [keyword] patterns configured'))
263 raise util.Abort(_('no [keyword] patterns configured'))
265
264
266 def _kwfwrite(ui, repo, expand, *pats, **opts):
265 def _kwfwrite(ui, repo, expand, *pats, **opts):
267 '''Selects files and passes them to kwtemplater.overwrite.'''
266 '''Selects files and passes them to kwtemplater.overwrite.'''
268 if repo.dirstate.parents()[1] != nullid:
267 if repo.dirstate.parents()[1] != nullid:
269 raise util.Abort(_('outstanding uncommitted merge'))
268 raise util.Abort(_('outstanding uncommitted merge'))
270 kwt = kwtools['templater']
269 kwt = kwtools['templater']
271 status = _status(ui, repo, kwt, *pats, **opts)
270 status = _status(ui, repo, kwt, *pats, **opts)
272 modified, added, removed, deleted, unknown, ignored, clean = status
271 modified, added, removed, deleted, unknown, ignored, clean = status
273 if modified or added or removed or deleted:
272 if modified or added or removed or deleted:
274 raise util.Abort(_('outstanding uncommitted changes'))
273 raise util.Abort(_('outstanding uncommitted changes'))
275 wlock = lock = None
274 wlock = lock = None
276 try:
275 try:
277 wlock = repo.wlock()
276 wlock = repo.wlock()
278 lock = repo.lock()
277 lock = repo.lock()
279 kwt.overwrite(None, expand, clean)
278 kwt.overwrite(None, expand, clean)
280 finally:
279 finally:
281 del wlock, lock
280 del wlock, lock
282
281
283
282
284 def demo(ui, repo, *args, **opts):
283 def demo(ui, repo, *args, **opts):
285 '''print [keywordmaps] configuration and an expansion example
284 '''print [keywordmaps] configuration and an expansion example
286
285
287 Show current, custom, or default keyword template maps
286 Show current, custom, or default keyword template maps
288 and their expansion.
287 and their expansion.
289
288
290 Extend current configuration by specifying maps as arguments
289 Extend current configuration by specifying maps as arguments
291 and optionally by reading from an additional hgrc file.
290 and optionally by reading from an additional hgrc file.
292
291
293 Override current keyword template maps with "default" option.
292 Override current keyword template maps with "default" option.
294 '''
293 '''
295 def demostatus(stat):
294 def demostatus(stat):
296 ui.status(_('\n\t%s\n') % stat)
295 ui.status(_('\n\t%s\n') % stat)
297
296
298 def demoitems(section, items):
297 def demoitems(section, items):
299 ui.write('[%s]\n' % section)
298 ui.write('[%s]\n' % section)
300 for k, v in items:
299 for k, v in items:
301 ui.write('%s = %s\n' % (k, v))
300 ui.write('%s = %s\n' % (k, v))
302
301
303 msg = 'hg keyword config and expansion example'
302 msg = 'hg keyword config and expansion example'
304 kwstatus = 'current'
303 kwstatus = 'current'
305 fn = 'demo.txt'
304 fn = 'demo.txt'
306 branchname = 'demobranch'
305 branchname = 'demobranch'
307 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
306 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
308 ui.note(_('creating temporary repo at %s\n') % tmpdir)
307 ui.note(_('creating temporary repo at %s\n') % tmpdir)
309 repo = localrepo.localrepository(ui, tmpdir, True)
308 repo = localrepo.localrepository(ui, tmpdir, True)
310 ui.setconfig('keyword', fn, '')
309 ui.setconfig('keyword', fn, '')
311 if args or opts.get('rcfile'):
310 if args or opts.get('rcfile'):
312 kwstatus = 'custom'
311 kwstatus = 'custom'
313 if opts.get('rcfile'):
312 if opts.get('rcfile'):
314 ui.readconfig(opts.get('rcfile'))
313 ui.readconfig(opts.get('rcfile'))
315 if opts.get('default'):
314 if opts.get('default'):
316 kwstatus = 'default'
315 kwstatus = 'default'
317 kwmaps = kwtemplater.templates
316 kwmaps = kwtemplater.templates
318 if ui.configitems('keywordmaps'):
317 if ui.configitems('keywordmaps'):
319 # override maps from optional rcfile
318 # override maps from optional rcfile
320 for k, v in kwmaps.iteritems():
319 for k, v in kwmaps.iteritems():
321 ui.setconfig('keywordmaps', k, v)
320 ui.setconfig('keywordmaps', k, v)
322 elif args:
321 elif args:
323 # simulate hgrc parsing
322 # simulate hgrc parsing
324 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
323 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
325 fp = repo.opener('hgrc', 'w')
324 fp = repo.opener('hgrc', 'w')
326 fp.writelines(rcmaps)
325 fp.writelines(rcmaps)
327 fp.close()
326 fp.close()
328 ui.readconfig(repo.join('hgrc'))
327 ui.readconfig(repo.join('hgrc'))
329 if not opts.get('default'):
328 if not opts.get('default'):
330 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
329 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
331 uisetup(ui)
330 uisetup(ui)
332 reposetup(ui, repo)
331 reposetup(ui, repo)
333 for k, v in ui.configitems('extensions'):
332 for k, v in ui.configitems('extensions'):
334 if k.endswith('keyword'):
333 if k.endswith('keyword'):
335 extension = '%s = %s' % (k, v)
334 extension = '%s = %s' % (k, v)
336 break
335 break
337 demostatus('config using %s keyword template maps' % kwstatus)
336 demostatus('config using %s keyword template maps' % kwstatus)
338 ui.write('[extensions]\n%s\n' % extension)
337 ui.write('[extensions]\n%s\n' % extension)
339 demoitems('keyword', ui.configitems('keyword'))
338 demoitems('keyword', ui.configitems('keyword'))
340 demoitems('keywordmaps', kwmaps.iteritems())
339 demoitems('keywordmaps', kwmaps.iteritems())
341 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
340 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
342 repo.wopener(fn, 'w').write(keywords)
341 repo.wopener(fn, 'w').write(keywords)
343 repo.add([fn])
342 repo.add([fn])
344 path = repo.wjoin(fn)
343 path = repo.wjoin(fn)
345 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
344 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
346 ui.note(keywords)
345 ui.note(keywords)
347 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
346 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
348 # silence branch command if not verbose
347 # silence branch command if not verbose
349 quiet = ui.quiet
348 quiet = ui.quiet
350 ui.quiet = not ui.verbose
349 ui.quiet = not ui.verbose
351 commands.branch(ui, repo, branchname)
350 commands.branch(ui, repo, branchname)
352 ui.quiet = quiet
351 ui.quiet = quiet
353 for name, cmd in ui.configitems('hooks'):
352 for name, cmd in ui.configitems('hooks'):
354 if name.split('.', 1)[0].find('commit') > -1:
353 if name.split('.', 1)[0].find('commit') > -1:
355 repo.ui.setconfig('hooks', name, '')
354 repo.ui.setconfig('hooks', name, '')
356 ui.note(_('unhooked all commit hooks\n'))
355 ui.note(_('unhooked all commit hooks\n'))
357 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
356 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
358 repo.commit(text=msg)
357 repo.commit(text=msg)
359 format = ui.verbose and ' in %s' % path or ''
358 format = ui.verbose and ' in %s' % path or ''
360 demostatus('%s keywords expanded%s' % (kwstatus, format))
359 demostatus('%s keywords expanded%s' % (kwstatus, format))
361 ui.write(repo.wread(fn))
360 ui.write(repo.wread(fn))
362 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
361 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
363 shutil.rmtree(tmpdir, ignore_errors=True)
362 shutil.rmtree(tmpdir, ignore_errors=True)
364
363
365 def expand(ui, repo, *pats, **opts):
364 def expand(ui, repo, *pats, **opts):
366 '''expand keywords in working directory
365 '''expand keywords in working directory
367
366
368 Run after (re)enabling keyword expansion.
367 Run after (re)enabling keyword expansion.
369
368
370 kwexpand refuses to run if given files contain local changes.
369 kwexpand refuses to run if given files contain local changes.
371 '''
370 '''
372 # 3rd argument sets expansion to True
371 # 3rd argument sets expansion to True
373 _kwfwrite(ui, repo, True, *pats, **opts)
372 _kwfwrite(ui, repo, True, *pats, **opts)
374
373
375 def files(ui, repo, *pats, **opts):
374 def files(ui, repo, *pats, **opts):
376 '''print files currently configured for keyword expansion
375 '''print files currently configured for keyword expansion
377
376
378 Crosscheck which files in working directory are potential targets for
377 Crosscheck which files in working directory are potential targets for
379 keyword expansion.
378 keyword expansion.
380 That is, files matched by [keyword] config patterns but not symlinks.
379 That is, files matched by [keyword] config patterns but not symlinks.
381 '''
380 '''
382 kwt = kwtools['templater']
381 kwt = kwtools['templater']
383 status = _status(ui, repo, kwt, *pats, **opts)
382 status = _status(ui, repo, kwt, *pats, **opts)
384 modified, added, removed, deleted, unknown, ignored, clean = status
383 modified, added, removed, deleted, unknown, ignored, clean = status
385 files = modified + added + clean
384 files = modified + added + clean
386 if opts.get('untracked'):
385 if opts.get('untracked'):
387 files += unknown
386 files += unknown
388 files.sort()
387 files.sort()
389 wctx = repo[None]
388 wctx = repo[None]
390 islink = lambda p: 'l' in wctx.flags(p)
389 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
391 kwfiles = [f for f in files if kwt.iskwfile(f, islink)]
392 cwd = pats and repo.getcwd() or ''
390 cwd = pats and repo.getcwd() or ''
393 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
391 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
394 if opts.get('all') or opts.get('ignore'):
392 if opts.get('all') or opts.get('ignore'):
395 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
393 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
396 for char, filenames in kwfstats:
394 for char, filenames in kwfstats:
397 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
395 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
398 for f in filenames:
396 for f in filenames:
399 ui.write(format % repo.pathto(f, cwd))
397 ui.write(format % repo.pathto(f, cwd))
400
398
401 def shrink(ui, repo, *pats, **opts):
399 def shrink(ui, repo, *pats, **opts):
402 '''revert expanded keywords in working directory
400 '''revert expanded keywords in working directory
403
401
404 Run before changing/disabling active keywords
402 Run before changing/disabling active keywords
405 or if you experience problems with "hg import" or "hg merge".
403 or if you experience problems with "hg import" or "hg merge".
406
404
407 kwshrink refuses to run if given files contain local changes.
405 kwshrink refuses to run if given files contain local changes.
408 '''
406 '''
409 # 3rd argument sets expansion to False
407 # 3rd argument sets expansion to False
410 _kwfwrite(ui, repo, False, *pats, **opts)
408 _kwfwrite(ui, repo, False, *pats, **opts)
411
409
412
410
413 def uisetup(ui):
411 def uisetup(ui):
414 '''Collects [keyword] config in kwtools.
412 '''Collects [keyword] config in kwtools.
415 Monkeypatches dispatch._parse if needed.'''
413 Monkeypatches dispatch._parse if needed.'''
416
414
417 for pat, opt in ui.configitems('keyword'):
415 for pat, opt in ui.configitems('keyword'):
418 if opt != 'ignore':
416 if opt != 'ignore':
419 kwtools['inc'].append(pat)
417 kwtools['inc'].append(pat)
420 else:
418 else:
421 kwtools['exc'].append(pat)
419 kwtools['exc'].append(pat)
422
420
423 if kwtools['inc']:
421 if kwtools['inc']:
424 def kwdispatch_parse(ui, args):
422 def kwdispatch_parse(ui, args):
425 '''Monkeypatch dispatch._parse to obtain running hg command.'''
423 '''Monkeypatch dispatch._parse to obtain running hg command.'''
426 cmd, func, args, options, cmdoptions = dispatch_parse(ui, args)
424 cmd, func, args, options, cmdoptions = dispatch_parse(ui, args)
427 kwtools['hgcmd'] = cmd
425 kwtools['hgcmd'] = cmd
428 return cmd, func, args, options, cmdoptions
426 return cmd, func, args, options, cmdoptions
429
427
430 dispatch_parse = dispatch._parse
428 dispatch_parse = dispatch._parse
431 dispatch._parse = kwdispatch_parse
429 dispatch._parse = kwdispatch_parse
432
430
433 def reposetup(ui, repo):
431 def reposetup(ui, repo):
434 '''Sets up repo as kwrepo for keyword substitution.
432 '''Sets up repo as kwrepo for keyword substitution.
435 Overrides file method to return kwfilelog instead of filelog
433 Overrides file method to return kwfilelog instead of filelog
436 if file matches user configuration.
434 if file matches user configuration.
437 Wraps commit to overwrite configured files with updated
435 Wraps commit to overwrite configured files with updated
438 keyword substitutions.
436 keyword substitutions.
439 Monkeypatches patch and webcommands.'''
437 Monkeypatches patch and webcommands.'''
440
438
441 try:
439 try:
442 if (not repo.local() or not kwtools['inc']
440 if (not repo.local() or not kwtools['inc']
443 or kwtools['hgcmd'] in nokwcommands.split()
441 or kwtools['hgcmd'] in nokwcommands.split()
444 or '.hg' in util.splitpath(repo.root)
442 or '.hg' in util.splitpath(repo.root)
445 or repo._url.startswith('bundle:')):
443 or repo._url.startswith('bundle:')):
446 return
444 return
447 except AttributeError:
445 except AttributeError:
448 pass
446 pass
449
447
450 kwtools['templater'] = kwt = kwtemplater(ui, repo)
448 kwtools['templater'] = kwt = kwtemplater(ui, repo)
451
449
452 class kwrepo(repo.__class__):
450 class kwrepo(repo.__class__):
453 def file(self, f):
451 def file(self, f):
454 if f[0] == '/':
452 if f[0] == '/':
455 f = f[1:]
453 f = f[1:]
456 return kwfilelog(self.sopener, kwt, f)
454 return kwfilelog(self.sopener, kwt, f)
457
455
458 def wread(self, filename):
456 def wread(self, filename):
459 data = super(kwrepo, self).wread(filename)
457 data = super(kwrepo, self).wread(filename)
460 return kwt.wread(filename, data)
458 return kwt.wread(filename, data)
461
459
462 def commit(self, files=None, text='', user=None, date=None,
460 def commit(self, files=None, text='', user=None, date=None,
463 match=None, force=False, force_editor=False,
461 match=None, force=False, force_editor=False,
464 p1=None, p2=None, extra={}, empty_ok=False):
462 p1=None, p2=None, extra={}, empty_ok=False):
465 wlock = lock = None
463 wlock = lock = None
466 _p1 = _p2 = None
464 _p1 = _p2 = None
467 try:
465 try:
468 wlock = self.wlock()
466 wlock = self.wlock()
469 lock = self.lock()
467 lock = self.lock()
470 # store and postpone commit hooks
468 # store and postpone commit hooks
471 commithooks = {}
469 commithooks = {}
472 for name, cmd in ui.configitems('hooks'):
470 for name, cmd in ui.configitems('hooks'):
473 if name.split('.', 1)[0] == 'commit':
471 if name.split('.', 1)[0] == 'commit':
474 commithooks[name] = cmd
472 commithooks[name] = cmd
475 ui.setconfig('hooks', name, None)
473 ui.setconfig('hooks', name, None)
476 if commithooks:
474 if commithooks:
477 # store parents for commit hook environment
475 # store parents for commit hook environment
478 if p1 is None:
476 if p1 is None:
479 _p1, _p2 = repo.dirstate.parents()
477 _p1, _p2 = repo.dirstate.parents()
480 else:
478 else:
481 _p1, _p2 = p1, p2 or nullid
479 _p1, _p2 = p1, p2 or nullid
482 _p1 = hex(_p1)
480 _p1 = hex(_p1)
483 if _p2 == nullid:
481 if _p2 == nullid:
484 _p2 = ''
482 _p2 = ''
485 else:
483 else:
486 _p2 = hex(_p2)
484 _p2 = hex(_p2)
487
485
488 n = super(kwrepo, self).commit(files, text, user, date, match,
486 n = super(kwrepo, self).commit(files, text, user, date, match,
489 force, force_editor, p1, p2,
487 force, force_editor, p1, p2,
490 extra, empty_ok)
488 extra, empty_ok)
491
489
492 # restore commit hooks
490 # restore commit hooks
493 for name, cmd in commithooks.iteritems():
491 for name, cmd in commithooks.iteritems():
494 ui.setconfig('hooks', name, cmd)
492 ui.setconfig('hooks', name, cmd)
495 if n is not None:
493 if n is not None:
496 kwt.overwrite(n, True, None)
494 kwt.overwrite(n, True, None)
497 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
495 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
498 return n
496 return n
499 finally:
497 finally:
500 del wlock, lock
498 del wlock, lock
501
499
502 # monkeypatches
500 # monkeypatches
503 def kwpatchfile_init(self, ui, fname, missing=False):
501 def kwpatchfile_init(self, ui, fname, missing=False):
504 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
502 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
505 rejects or conflicts due to expanded keywords in working dir.'''
503 rejects or conflicts due to expanded keywords in working dir.'''
506 patchfile_init(self, ui, fname, missing)
504 patchfile_init(self, ui, fname, missing)
507 # shrink keywords read from working dir
505 # shrink keywords read from working dir
508 self.lines = kwt.shrinklines(self.fname, self.lines)
506 self.lines = kwt.shrinklines(self.fname, self.lines)
509
507
510 def kw_diff(repo, node1=None, node2=None, match=None,
508 def kw_diff(repo, node1=None, node2=None, match=None,
511 fp=None, changes=None, opts=None):
509 fp=None, changes=None, opts=None):
512 '''Monkeypatch patch.diff to avoid expansion except when
510 '''Monkeypatch patch.diff to avoid expansion except when
513 comparing against working dir.'''
511 comparing against working dir.'''
514 if node2 is not None:
512 if node2 is not None:
515 kwt.matcher = util.never
513 kwt.matcher = util.never
516 elif node1 is not None and node1 != repo['.'].node():
514 elif node1 is not None and node1 != repo['.'].node():
517 kwt.restrict = True
515 kwt.restrict = True
518 patch_diff(repo, node1, node2, match, fp, changes, opts)
516 patch_diff(repo, node1, node2, match, fp, changes, opts)
519
517
520 def kwweb_annotate(web, req, tmpl):
518 def kwweb_annotate(web, req, tmpl):
521 '''Wraps webcommands.annotate turning off keyword expansion.'''
519 '''Wraps webcommands.annotate turning off keyword expansion.'''
522 kwt.matcher = util.never
520 kwt.matcher = util.never
523 return webcommands_annotate(web, req, tmpl)
521 return webcommands_annotate(web, req, tmpl)
524
522
525 def kwweb_changeset(web, req, tmpl):
523 def kwweb_changeset(web, req, tmpl):
526 '''Wraps webcommands.changeset turning off keyword expansion.'''
524 '''Wraps webcommands.changeset turning off keyword expansion.'''
527 kwt.matcher = util.never
525 kwt.matcher = util.never
528 return webcommands_changeset(web, req, tmpl)
526 return webcommands_changeset(web, req, tmpl)
529
527
530 def kwweb_filediff(web, req, tmpl):
528 def kwweb_filediff(web, req, tmpl):
531 '''Wraps webcommands.filediff turning off keyword expansion.'''
529 '''Wraps webcommands.filediff turning off keyword expansion.'''
532 kwt.matcher = util.never
530 kwt.matcher = util.never
533 return webcommands_filediff(web, req, tmpl)
531 return webcommands_filediff(web, req, tmpl)
534
532
535 repo.__class__ = kwrepo
533 repo.__class__ = kwrepo
536
534
537 patchfile_init = patch.patchfile.__init__
535 patchfile_init = patch.patchfile.__init__
538 patch_diff = patch.diff
536 patch_diff = patch.diff
539 webcommands_annotate = webcommands.annotate
537 webcommands_annotate = webcommands.annotate
540 webcommands_changeset = webcommands.changeset
538 webcommands_changeset = webcommands.changeset
541 webcommands_filediff = webcommands.filediff
539 webcommands_filediff = webcommands.filediff
542
540
543 patch.patchfile.__init__ = kwpatchfile_init
541 patch.patchfile.__init__ = kwpatchfile_init
544 patch.diff = kw_diff
542 patch.diff = kw_diff
545 webcommands.annotate = kwweb_annotate
543 webcommands.annotate = kwweb_annotate
546 webcommands.changeset = webcommands.rev = kwweb_changeset
544 webcommands.changeset = webcommands.rev = kwweb_changeset
547 webcommands.filediff = webcommands.diff = kwweb_filediff
545 webcommands.filediff = webcommands.diff = kwweb_filediff
548
546
549
547
550 cmdtable = {
548 cmdtable = {
551 'kwdemo':
549 'kwdemo':
552 (demo,
550 (demo,
553 [('d', 'default', None, _('show default keyword template maps')),
551 [('d', 'default', None, _('show default keyword template maps')),
554 ('f', 'rcfile', [], _('read maps from rcfile'))],
552 ('f', 'rcfile', [], _('read maps from rcfile'))],
555 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
553 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
556 'kwexpand': (expand, commands.walkopts,
554 'kwexpand': (expand, commands.walkopts,
557 _('hg kwexpand [OPTION]... [FILE]...')),
555 _('hg kwexpand [OPTION]... [FILE]...')),
558 'kwfiles':
556 'kwfiles':
559 (files,
557 (files,
560 [('a', 'all', None, _('show keyword status flags of all files')),
558 [('a', 'all', None, _('show keyword status flags of all files')),
561 ('i', 'ignore', None, _('show files excluded from expansion')),
559 ('i', 'ignore', None, _('show files excluded from expansion')),
562 ('u', 'untracked', None, _('additionally show untracked files')),
560 ('u', 'untracked', None, _('additionally show untracked files')),
563 ] + commands.walkopts,
561 ] + commands.walkopts,
564 _('hg kwfiles [OPTION]... [FILE]...')),
562 _('hg kwfiles [OPTION]... [FILE]...')),
565 'kwshrink': (shrink, commands.walkopts,
563 'kwshrink': (shrink, commands.walkopts,
566 _('hg kwshrink [OPTION]... [FILE]...')),
564 _('hg kwshrink [OPTION]... [FILE]...')),
567 }
565 }
@@ -1,225 +1,224 b''
1 # archival.py - revision archival for mercurial
1 # archival.py - revision archival for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of
5 # This software may be used and distributed according to the terms of
6 # the GNU General Public License, incorporated herein by reference.
6 # the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 from node import hex
9 from node import hex
10 import cStringIO, os, stat, tarfile, time, util, zipfile
10 import cStringIO, os, stat, tarfile, time, util, zipfile
11 import zlib, gzip
11 import zlib, gzip
12
12
13 def tidyprefix(dest, prefix, suffixes):
13 def tidyprefix(dest, prefix, suffixes):
14 '''choose prefix to use for names in archive. make sure prefix is
14 '''choose prefix to use for names in archive. make sure prefix is
15 safe for consumers.'''
15 safe for consumers.'''
16
16
17 if prefix:
17 if prefix:
18 prefix = util.normpath(prefix)
18 prefix = util.normpath(prefix)
19 else:
19 else:
20 if not isinstance(dest, str):
20 if not isinstance(dest, str):
21 raise ValueError('dest must be string if no prefix')
21 raise ValueError('dest must be string if no prefix')
22 prefix = os.path.basename(dest)
22 prefix = os.path.basename(dest)
23 lower = prefix.lower()
23 lower = prefix.lower()
24 for sfx in suffixes:
24 for sfx in suffixes:
25 if lower.endswith(sfx):
25 if lower.endswith(sfx):
26 prefix = prefix[:-len(sfx)]
26 prefix = prefix[:-len(sfx)]
27 break
27 break
28 lpfx = os.path.normpath(util.localpath(prefix))
28 lpfx = os.path.normpath(util.localpath(prefix))
29 prefix = util.pconvert(lpfx)
29 prefix = util.pconvert(lpfx)
30 if not prefix.endswith('/'):
30 if not prefix.endswith('/'):
31 prefix += '/'
31 prefix += '/'
32 if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix:
32 if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix:
33 raise util.Abort(_('archive prefix contains illegal components'))
33 raise util.Abort(_('archive prefix contains illegal components'))
34 return prefix
34 return prefix
35
35
36 class tarit:
36 class tarit:
37 '''write archive to tar file or stream. can write uncompressed,
37 '''write archive to tar file or stream. can write uncompressed,
38 or compress with gzip or bzip2.'''
38 or compress with gzip or bzip2.'''
39
39
40 class GzipFileWithTime(gzip.GzipFile):
40 class GzipFileWithTime(gzip.GzipFile):
41
41
42 def __init__(self, *args, **kw):
42 def __init__(self, *args, **kw):
43 timestamp = None
43 timestamp = None
44 if 'timestamp' in kw:
44 if 'timestamp' in kw:
45 timestamp = kw.pop('timestamp')
45 timestamp = kw.pop('timestamp')
46 if timestamp == None:
46 if timestamp == None:
47 self.timestamp = time.time()
47 self.timestamp = time.time()
48 else:
48 else:
49 self.timestamp = timestamp
49 self.timestamp = timestamp
50 gzip.GzipFile.__init__(self, *args, **kw)
50 gzip.GzipFile.__init__(self, *args, **kw)
51
51
52 def _write_gzip_header(self):
52 def _write_gzip_header(self):
53 self.fileobj.write('\037\213') # magic header
53 self.fileobj.write('\037\213') # magic header
54 self.fileobj.write('\010') # compression method
54 self.fileobj.write('\010') # compression method
55 # Python 2.6 deprecates self.filename
55 # Python 2.6 deprecates self.filename
56 fname = getattr(self, 'name', None) or self.filename
56 fname = getattr(self, 'name', None) or self.filename
57 flags = 0
57 flags = 0
58 if fname:
58 if fname:
59 flags = gzip.FNAME
59 flags = gzip.FNAME
60 self.fileobj.write(chr(flags))
60 self.fileobj.write(chr(flags))
61 gzip.write32u(self.fileobj, long(self.timestamp))
61 gzip.write32u(self.fileobj, long(self.timestamp))
62 self.fileobj.write('\002')
62 self.fileobj.write('\002')
63 self.fileobj.write('\377')
63 self.fileobj.write('\377')
64 if fname:
64 if fname:
65 self.fileobj.write(fname + '\000')
65 self.fileobj.write(fname + '\000')
66
66
67 def __init__(self, dest, prefix, mtime, kind=''):
67 def __init__(self, dest, prefix, mtime, kind=''):
68 self.prefix = tidyprefix(dest, prefix, ['.tar', '.tar.bz2', '.tar.gz',
68 self.prefix = tidyprefix(dest, prefix, ['.tar', '.tar.bz2', '.tar.gz',
69 '.tgz', '.tbz2'])
69 '.tgz', '.tbz2'])
70 self.mtime = mtime
70 self.mtime = mtime
71
71
72 def taropen(name, mode, fileobj=None):
72 def taropen(name, mode, fileobj=None):
73 if kind == 'gz':
73 if kind == 'gz':
74 mode = mode[0]
74 mode = mode[0]
75 if not fileobj:
75 if not fileobj:
76 fileobj = open(name, mode + 'b')
76 fileobj = open(name, mode + 'b')
77 gzfileobj = self.GzipFileWithTime(name, mode + 'b',
77 gzfileobj = self.GzipFileWithTime(name, mode + 'b',
78 zlib.Z_BEST_COMPRESSION,
78 zlib.Z_BEST_COMPRESSION,
79 fileobj, timestamp=mtime)
79 fileobj, timestamp=mtime)
80 return tarfile.TarFile.taropen(name, mode, gzfileobj)
80 return tarfile.TarFile.taropen(name, mode, gzfileobj)
81 else:
81 else:
82 return tarfile.open(name, mode + kind, fileobj)
82 return tarfile.open(name, mode + kind, fileobj)
83
83
84 if isinstance(dest, str):
84 if isinstance(dest, str):
85 self.z = taropen(dest, mode='w:')
85 self.z = taropen(dest, mode='w:')
86 else:
86 else:
87 # Python 2.5-2.5.1 have a regression that requires a name arg
87 # Python 2.5-2.5.1 have a regression that requires a name arg
88 self.z = taropen(name='', mode='w|', fileobj=dest)
88 self.z = taropen(name='', mode='w|', fileobj=dest)
89
89
90 def addfile(self, name, mode, islink, data):
90 def addfile(self, name, mode, islink, data):
91 i = tarfile.TarInfo(self.prefix + name)
91 i = tarfile.TarInfo(self.prefix + name)
92 i.mtime = self.mtime
92 i.mtime = self.mtime
93 i.size = len(data)
93 i.size = len(data)
94 if islink:
94 if islink:
95 i.type = tarfile.SYMTYPE
95 i.type = tarfile.SYMTYPE
96 i.mode = 0777
96 i.mode = 0777
97 i.linkname = data
97 i.linkname = data
98 data = None
98 data = None
99 else:
99 else:
100 i.mode = mode
100 i.mode = mode
101 data = cStringIO.StringIO(data)
101 data = cStringIO.StringIO(data)
102 self.z.addfile(i, data)
102 self.z.addfile(i, data)
103
103
104 def done(self):
104 def done(self):
105 self.z.close()
105 self.z.close()
106
106
107 class tellable:
107 class tellable:
108 '''provide tell method for zipfile.ZipFile when writing to http
108 '''provide tell method for zipfile.ZipFile when writing to http
109 response file object.'''
109 response file object.'''
110
110
111 def __init__(self, fp):
111 def __init__(self, fp):
112 self.fp = fp
112 self.fp = fp
113 self.offset = 0
113 self.offset = 0
114
114
115 def __getattr__(self, key):
115 def __getattr__(self, key):
116 return getattr(self.fp, key)
116 return getattr(self.fp, key)
117
117
118 def write(self, s):
118 def write(self, s):
119 self.fp.write(s)
119 self.fp.write(s)
120 self.offset += len(s)
120 self.offset += len(s)
121
121
122 def tell(self):
122 def tell(self):
123 return self.offset
123 return self.offset
124
124
125 class zipit:
125 class zipit:
126 '''write archive to zip file or stream. can write uncompressed,
126 '''write archive to zip file or stream. can write uncompressed,
127 or compressed with deflate.'''
127 or compressed with deflate.'''
128
128
129 def __init__(self, dest, prefix, mtime, compress=True):
129 def __init__(self, dest, prefix, mtime, compress=True):
130 self.prefix = tidyprefix(dest, prefix, ('.zip',))
130 self.prefix = tidyprefix(dest, prefix, ('.zip',))
131 if not isinstance(dest, str):
131 if not isinstance(dest, str):
132 try:
132 try:
133 dest.tell()
133 dest.tell()
134 except (AttributeError, IOError):
134 except (AttributeError, IOError):
135 dest = tellable(dest)
135 dest = tellable(dest)
136 self.z = zipfile.ZipFile(dest, 'w',
136 self.z = zipfile.ZipFile(dest, 'w',
137 compress and zipfile.ZIP_DEFLATED or
137 compress and zipfile.ZIP_DEFLATED or
138 zipfile.ZIP_STORED)
138 zipfile.ZIP_STORED)
139 self.date_time = time.gmtime(mtime)[:6]
139 self.date_time = time.gmtime(mtime)[:6]
140
140
141 def addfile(self, name, mode, islink, data):
141 def addfile(self, name, mode, islink, data):
142 i = zipfile.ZipInfo(self.prefix + name, self.date_time)
142 i = zipfile.ZipInfo(self.prefix + name, self.date_time)
143 i.compress_type = self.z.compression
143 i.compress_type = self.z.compression
144 # unzip will not honor unix file modes unless file creator is
144 # unzip will not honor unix file modes unless file creator is
145 # set to unix (id 3).
145 # set to unix (id 3).
146 i.create_system = 3
146 i.create_system = 3
147 ftype = stat.S_IFREG
147 ftype = stat.S_IFREG
148 if islink:
148 if islink:
149 mode = 0777
149 mode = 0777
150 ftype = stat.S_IFLNK
150 ftype = stat.S_IFLNK
151 i.external_attr = (mode | ftype) << 16L
151 i.external_attr = (mode | ftype) << 16L
152 self.z.writestr(i, data)
152 self.z.writestr(i, data)
153
153
154 def done(self):
154 def done(self):
155 self.z.close()
155 self.z.close()
156
156
157 class fileit:
157 class fileit:
158 '''write archive as files in directory.'''
158 '''write archive as files in directory.'''
159
159
160 def __init__(self, name, prefix, mtime):
160 def __init__(self, name, prefix, mtime):
161 if prefix:
161 if prefix:
162 raise util.Abort(_('cannot give prefix when archiving to files'))
162 raise util.Abort(_('cannot give prefix when archiving to files'))
163 self.basedir = name
163 self.basedir = name
164 self.opener = util.opener(self.basedir)
164 self.opener = util.opener(self.basedir)
165
165
166 def addfile(self, name, mode, islink, data):
166 def addfile(self, name, mode, islink, data):
167 if islink:
167 if islink:
168 self.opener.symlink(data, name)
168 self.opener.symlink(data, name)
169 return
169 return
170 f = self.opener(name, "w", atomictemp=True)
170 f = self.opener(name, "w", atomictemp=True)
171 f.write(data)
171 f.write(data)
172 f.rename()
172 f.rename()
173 destfile = os.path.join(self.basedir, name)
173 destfile = os.path.join(self.basedir, name)
174 os.chmod(destfile, mode)
174 os.chmod(destfile, mode)
175
175
176 def done(self):
176 def done(self):
177 pass
177 pass
178
178
179 archivers = {
179 archivers = {
180 'files': fileit,
180 'files': fileit,
181 'tar': tarit,
181 'tar': tarit,
182 'tbz2': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'bz2'),
182 'tbz2': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'bz2'),
183 'tgz': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'gz'),
183 'tgz': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'gz'),
184 'uzip': lambda name, prefix, mtime: zipit(name, prefix, mtime, False),
184 'uzip': lambda name, prefix, mtime: zipit(name, prefix, mtime, False),
185 'zip': zipit,
185 'zip': zipit,
186 }
186 }
187
187
188 def archive(repo, dest, node, kind, decode=True, matchfn=None,
188 def archive(repo, dest, node, kind, decode=True, matchfn=None,
189 prefix=None, mtime=None):
189 prefix=None, mtime=None):
190 '''create archive of repo as it was at node.
190 '''create archive of repo as it was at node.
191
191
192 dest can be name of directory, name of archive file, or file
192 dest can be name of directory, name of archive file, or file
193 object to write archive to.
193 object to write archive to.
194
194
195 kind is type of archive to create.
195 kind is type of archive to create.
196
196
197 decode tells whether to put files through decode filters from
197 decode tells whether to put files through decode filters from
198 hgrc.
198 hgrc.
199
199
200 matchfn is function to filter names of files to write to archive.
200 matchfn is function to filter names of files to write to archive.
201
201
202 prefix is name of path to put before every archive member.'''
202 prefix is name of path to put before every archive member.'''
203
203
204 def write(name, mode, islink, getdata):
204 def write(name, mode, islink, getdata):
205 if matchfn and not matchfn(name): return
205 if matchfn and not matchfn(name): return
206 data = getdata()
206 data = getdata()
207 if decode:
207 if decode:
208 data = repo.wwritedata(name, data)
208 data = repo.wwritedata(name, data)
209 archiver.addfile(name, mode, islink, data)
209 archiver.addfile(name, mode, islink, data)
210
210
211 ctx = repo[node]
212 if kind not in archivers:
211 if kind not in archivers:
213 raise util.Abort(_("unknown archive type '%s'" % kind))
212 raise util.Abort(_("unknown archive type '%s'" % kind))
213
214 ctx = repo[node]
214 archiver = archivers[kind](dest, prefix, mtime or ctx.date()[0])
215 archiver = archivers[kind](dest, prefix, mtime or ctx.date()[0])
215 m = ctx.manifest()
216
216 items = m.items()
217 items.sort()
218 if repo.ui.configbool("ui", "archivemeta", True):
217 if repo.ui.configbool("ui", "archivemeta", True):
219 write('.hg_archival.txt', 0644, False,
218 write('.hg_archival.txt', 0644, False,
220 lambda: 'repo: %s\nnode: %s\n' % (
219 lambda: 'repo: %s\nnode: %s\n' % (
221 hex(repo.changelog.node(0)), hex(node)))
220 hex(repo.changelog.node(0)), hex(node)))
222 for filename, filenode in items:
221 for f in ctx:
223 write(filename, m.execf(filename) and 0755 or 0644, m.linkf(filename),
222 ff = ctx.flags(f)
224 lambda: repo.file(filename).read(filenode))
223 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, ctx[f].data)
225 archiver.done()
224 archiver.done()
@@ -1,3336 +1,3332 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, NoCapability
9 from repo import RepoError, NoCapability
10 from i18n import _
10 from i18n import _
11 import os, re, sys, urllib
11 import os, re, sys, urllib
12 import hg, util, revlog, bundlerepo, extensions, copies
12 import hg, util, revlog, bundlerepo, extensions, copies
13 import difflib, patch, time, help, mdiff, tempfile
13 import difflib, patch, time, help, mdiff, tempfile
14 import version, socket
14 import version, socket
15 import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
15 import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
16 import merge as merge_
16 import merge as merge_
17
17
18 # Commands start here, listed alphabetically
18 # Commands start here, listed alphabetically
19
19
20 def add(ui, repo, *pats, **opts):
20 def add(ui, repo, *pats, **opts):
21 """add the specified files on the next commit
21 """add the specified files on the next commit
22
22
23 Schedule files to be version controlled and added to the repository.
23 Schedule files to be version controlled and added to the repository.
24
24
25 The files will be added to the repository at the next commit. To
25 The files will be added to the repository at the next commit. To
26 undo an add before that, see hg revert.
26 undo an add before that, see hg revert.
27
27
28 If no names are given, add all files in the repository.
28 If no names are given, add all files in the repository.
29 """
29 """
30
30
31 rejected = None
31 rejected = None
32 exacts = {}
32 exacts = {}
33 names = []
33 names = []
34 m = cmdutil.match(repo, pats, opts)
34 m = cmdutil.match(repo, pats, opts)
35 m.bad = lambda x,y: True
35 m.bad = lambda x,y: True
36 for abs in repo.walk(m):
36 for abs in repo.walk(m):
37 if m.exact(abs):
37 if m.exact(abs):
38 if ui.verbose:
38 if ui.verbose:
39 ui.status(_('adding %s\n') % m.rel(abs))
39 ui.status(_('adding %s\n') % m.rel(abs))
40 names.append(abs)
40 names.append(abs)
41 exacts[abs] = 1
41 exacts[abs] = 1
42 elif abs not in repo.dirstate:
42 elif abs not in repo.dirstate:
43 ui.status(_('adding %s\n') % m.rel(abs))
43 ui.status(_('adding %s\n') % m.rel(abs))
44 names.append(abs)
44 names.append(abs)
45 if not opts.get('dry_run'):
45 if not opts.get('dry_run'):
46 rejected = repo.add(names)
46 rejected = repo.add(names)
47 rejected = [p for p in rejected if p in exacts]
47 rejected = [p for p in rejected if p in exacts]
48 return rejected and 1 or 0
48 return rejected and 1 or 0
49
49
50 def addremove(ui, repo, *pats, **opts):
50 def addremove(ui, repo, *pats, **opts):
51 """add all new files, delete all missing files
51 """add all new files, delete all missing files
52
52
53 Add all new files and remove all missing files from the repository.
53 Add all new files and remove all missing files from the repository.
54
54
55 New files are ignored if they match any of the patterns in .hgignore. As
55 New files are ignored if they match any of the patterns in .hgignore. As
56 with add, these changes take effect at the next commit.
56 with add, these changes take effect at the next commit.
57
57
58 Use the -s option to detect renamed files. With a parameter > 0,
58 Use the -s option to detect renamed files. With a parameter > 0,
59 this compares every removed file with every added file and records
59 this compares every removed file with every added file and records
60 those similar enough as renames. This option takes a percentage
60 those similar enough as renames. This option takes a percentage
61 between 0 (disabled) and 100 (files must be identical) as its
61 between 0 (disabled) and 100 (files must be identical) as its
62 parameter. Detecting renamed files this way can be expensive.
62 parameter. Detecting renamed files this way can be expensive.
63 """
63 """
64 try:
64 try:
65 sim = float(opts.get('similarity') or 0)
65 sim = float(opts.get('similarity') or 0)
66 except ValueError:
66 except ValueError:
67 raise util.Abort(_('similarity must be a number'))
67 raise util.Abort(_('similarity must be a number'))
68 if sim < 0 or sim > 100:
68 if sim < 0 or sim > 100:
69 raise util.Abort(_('similarity must be between 0 and 100'))
69 raise util.Abort(_('similarity must be between 0 and 100'))
70 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
70 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
71
71
72 def annotate(ui, repo, *pats, **opts):
72 def annotate(ui, repo, *pats, **opts):
73 """show changeset information per file line
73 """show changeset information per file line
74
74
75 List changes in files, showing the revision id responsible for each line
75 List changes in files, showing the revision id responsible for each line
76
76
77 This command is useful to discover who did a change or when a change took
77 This command is useful to discover who did a change or when a change took
78 place.
78 place.
79
79
80 Without the -a option, annotate will avoid processing files it
80 Without the -a option, annotate will avoid processing files it
81 detects as binary. With -a, annotate will generate an annotation
81 detects as binary. With -a, annotate will generate an annotation
82 anyway, probably with undesirable results.
82 anyway, probably with undesirable results.
83 """
83 """
84 datefunc = ui.quiet and util.shortdate or util.datestr
84 datefunc = ui.quiet and util.shortdate or util.datestr
85 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
85 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
86
86
87 if not pats:
87 if not pats:
88 raise util.Abort(_('at least one file name or pattern required'))
88 raise util.Abort(_('at least one file name or pattern required'))
89
89
90 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
90 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
91 ('number', lambda x: str(x[0].rev())),
91 ('number', lambda x: str(x[0].rev())),
92 ('changeset', lambda x: short(x[0].node())),
92 ('changeset', lambda x: short(x[0].node())),
93 ('date', getdate),
93 ('date', getdate),
94 ('follow', lambda x: x[0].path()),
94 ('follow', lambda x: x[0].path()),
95 ]
95 ]
96
96
97 if (not opts['user'] and not opts['changeset'] and not opts['date']
97 if (not opts['user'] and not opts['changeset'] and not opts['date']
98 and not opts['follow']):
98 and not opts['follow']):
99 opts['number'] = 1
99 opts['number'] = 1
100
100
101 linenumber = opts.get('line_number') is not None
101 linenumber = opts.get('line_number') is not None
102 if (linenumber and (not opts['changeset']) and (not opts['number'])):
102 if (linenumber and (not opts['changeset']) and (not opts['number'])):
103 raise util.Abort(_('at least one of -n/-c is required for -l'))
103 raise util.Abort(_('at least one of -n/-c is required for -l'))
104
104
105 funcmap = [func for op, func in opmap if opts.get(op)]
105 funcmap = [func for op, func in opmap if opts.get(op)]
106 if linenumber:
106 if linenumber:
107 lastfunc = funcmap[-1]
107 lastfunc = funcmap[-1]
108 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
108 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
109
109
110 ctx = repo[opts['rev']]
110 ctx = repo[opts['rev']]
111
111
112 m = cmdutil.match(repo, pats, opts)
112 m = cmdutil.match(repo, pats, opts)
113 for abs in repo.walk(m, ctx.node()):
113 for abs in repo.walk(m, ctx.node()):
114 fctx = ctx.filectx(abs)
114 fctx = ctx.filectx(abs)
115 if not opts['text'] and util.binary(fctx.data()):
115 if not opts['text'] and util.binary(fctx.data()):
116 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
116 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
117 continue
117 continue
118
118
119 lines = fctx.annotate(follow=opts.get('follow'),
119 lines = fctx.annotate(follow=opts.get('follow'),
120 linenumber=linenumber)
120 linenumber=linenumber)
121 pieces = []
121 pieces = []
122
122
123 for f in funcmap:
123 for f in funcmap:
124 l = [f(n) for n, dummy in lines]
124 l = [f(n) for n, dummy in lines]
125 if l:
125 if l:
126 m = max(map(len, l))
126 m = max(map(len, l))
127 pieces.append(["%*s" % (m, x) for x in l])
127 pieces.append(["%*s" % (m, x) for x in l])
128
128
129 if pieces:
129 if pieces:
130 for p, l in zip(zip(*pieces), lines):
130 for p, l in zip(zip(*pieces), lines):
131 ui.write("%s: %s" % (" ".join(p), l[1]))
131 ui.write("%s: %s" % (" ".join(p), l[1]))
132
132
133 def archive(ui, repo, dest, **opts):
133 def archive(ui, repo, dest, **opts):
134 '''create unversioned archive of a repository revision
134 '''create unversioned archive of a repository revision
135
135
136 By default, the revision used is the parent of the working
136 By default, the revision used is the parent of the working
137 directory; use "-r" to specify a different revision.
137 directory; use "-r" to specify a different revision.
138
138
139 To specify the type of archive to create, use "-t". Valid
139 To specify the type of archive to create, use "-t". Valid
140 types are:
140 types are:
141
141
142 "files" (default): a directory full of files
142 "files" (default): a directory full of files
143 "tar": tar archive, uncompressed
143 "tar": tar archive, uncompressed
144 "tbz2": tar archive, compressed using bzip2
144 "tbz2": tar archive, compressed using bzip2
145 "tgz": tar archive, compressed using gzip
145 "tgz": tar archive, compressed using gzip
146 "uzip": zip archive, uncompressed
146 "uzip": zip archive, uncompressed
147 "zip": zip archive, compressed using deflate
147 "zip": zip archive, compressed using deflate
148
148
149 The exact name of the destination archive or directory is given
149 The exact name of the destination archive or directory is given
150 using a format string; see "hg help export" for details.
150 using a format string; see "hg help export" for details.
151
151
152 Each member added to an archive file has a directory prefix
152 Each member added to an archive file has a directory prefix
153 prepended. Use "-p" to specify a format string for the prefix.
153 prepended. Use "-p" to specify a format string for the prefix.
154 The default is the basename of the archive, with suffixes removed.
154 The default is the basename of the archive, with suffixes removed.
155 '''
155 '''
156
156
157 ctx = repo[opts['rev']]
157 ctx = repo[opts['rev']]
158 if not ctx:
158 if not ctx:
159 raise util.Abort(_('repository has no revisions'))
159 raise util.Abort(_('repository has no revisions'))
160 node = ctx.node()
160 node = ctx.node()
161 dest = cmdutil.make_filename(repo, dest, node)
161 dest = cmdutil.make_filename(repo, dest, node)
162 if os.path.realpath(dest) == repo.root:
162 if os.path.realpath(dest) == repo.root:
163 raise util.Abort(_('repository root cannot be destination'))
163 raise util.Abort(_('repository root cannot be destination'))
164 matchfn = cmdutil.match(repo, [], opts)
164 matchfn = cmdutil.match(repo, [], opts)
165 kind = opts.get('type') or 'files'
165 kind = opts.get('type') or 'files'
166 prefix = opts['prefix']
166 prefix = opts['prefix']
167 if dest == '-':
167 if dest == '-':
168 if kind == 'files':
168 if kind == 'files':
169 raise util.Abort(_('cannot archive plain files to stdout'))
169 raise util.Abort(_('cannot archive plain files to stdout'))
170 dest = sys.stdout
170 dest = sys.stdout
171 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
171 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
172 prefix = cmdutil.make_filename(repo, prefix, node)
172 prefix = cmdutil.make_filename(repo, prefix, node)
173 archival.archive(repo, dest, node, kind, not opts['no_decode'],
173 archival.archive(repo, dest, node, kind, not opts['no_decode'],
174 matchfn, prefix)
174 matchfn, prefix)
175
175
176 def backout(ui, repo, node=None, rev=None, **opts):
176 def backout(ui, repo, node=None, rev=None, **opts):
177 '''reverse effect of earlier changeset
177 '''reverse effect of earlier changeset
178
178
179 Commit the backed out changes as a new changeset. The new
179 Commit the backed out changes as a new changeset. The new
180 changeset is a child of the backed out changeset.
180 changeset is a child of the backed out changeset.
181
181
182 If you back out a changeset other than the tip, a new head is
182 If you back out a changeset other than the tip, a new head is
183 created. This head will be the new tip and you should merge this
183 created. This head will be the new tip and you should merge this
184 backout changeset with another head (current one by default).
184 backout changeset with another head (current one by default).
185
185
186 The --merge option remembers the parent of the working directory
186 The --merge option remembers the parent of the working directory
187 before starting the backout, then merges the new head with that
187 before starting the backout, then merges the new head with that
188 changeset afterwards. This saves you from doing the merge by
188 changeset afterwards. This saves you from doing the merge by
189 hand. The result of this merge is not committed, as for a normal
189 hand. The result of this merge is not committed, as for a normal
190 merge.
190 merge.
191
191
192 See \'hg help dates\' for a list of formats valid for -d/--date.
192 See \'hg help dates\' for a list of formats valid for -d/--date.
193 '''
193 '''
194 if rev and node:
194 if rev and node:
195 raise util.Abort(_("please specify just one revision"))
195 raise util.Abort(_("please specify just one revision"))
196
196
197 if not rev:
197 if not rev:
198 rev = node
198 rev = node
199
199
200 if not rev:
200 if not rev:
201 raise util.Abort(_("please specify a revision to backout"))
201 raise util.Abort(_("please specify a revision to backout"))
202
202
203 date = opts.get('date')
203 date = opts.get('date')
204 if date:
204 if date:
205 opts['date'] = util.parsedate(date)
205 opts['date'] = util.parsedate(date)
206
206
207 cmdutil.bail_if_changed(repo)
207 cmdutil.bail_if_changed(repo)
208 node = repo.lookup(rev)
208 node = repo.lookup(rev)
209
209
210 op1, op2 = repo.dirstate.parents()
210 op1, op2 = repo.dirstate.parents()
211 a = repo.changelog.ancestor(op1, node)
211 a = repo.changelog.ancestor(op1, node)
212 if a != node:
212 if a != node:
213 raise util.Abort(_('cannot back out change on a different branch'))
213 raise util.Abort(_('cannot back out change on a different branch'))
214
214
215 p1, p2 = repo.changelog.parents(node)
215 p1, p2 = repo.changelog.parents(node)
216 if p1 == nullid:
216 if p1 == nullid:
217 raise util.Abort(_('cannot back out a change with no parents'))
217 raise util.Abort(_('cannot back out a change with no parents'))
218 if p2 != nullid:
218 if p2 != nullid:
219 if not opts['parent']:
219 if not opts['parent']:
220 raise util.Abort(_('cannot back out a merge changeset without '
220 raise util.Abort(_('cannot back out a merge changeset without '
221 '--parent'))
221 '--parent'))
222 p = repo.lookup(opts['parent'])
222 p = repo.lookup(opts['parent'])
223 if p not in (p1, p2):
223 if p not in (p1, p2):
224 raise util.Abort(_('%s is not a parent of %s') %
224 raise util.Abort(_('%s is not a parent of %s') %
225 (short(p), short(node)))
225 (short(p), short(node)))
226 parent = p
226 parent = p
227 else:
227 else:
228 if opts['parent']:
228 if opts['parent']:
229 raise util.Abort(_('cannot use --parent on non-merge changeset'))
229 raise util.Abort(_('cannot use --parent on non-merge changeset'))
230 parent = p1
230 parent = p1
231
231
232 # the backout should appear on the same branch
232 # the backout should appear on the same branch
233 branch = repo.dirstate.branch()
233 branch = repo.dirstate.branch()
234 hg.clean(repo, node, show_stats=False)
234 hg.clean(repo, node, show_stats=False)
235 repo.dirstate.setbranch(branch)
235 repo.dirstate.setbranch(branch)
236 revert_opts = opts.copy()
236 revert_opts = opts.copy()
237 revert_opts['date'] = None
237 revert_opts['date'] = None
238 revert_opts['all'] = True
238 revert_opts['all'] = True
239 revert_opts['rev'] = hex(parent)
239 revert_opts['rev'] = hex(parent)
240 revert_opts['no_backup'] = None
240 revert_opts['no_backup'] = None
241 revert(ui, repo, **revert_opts)
241 revert(ui, repo, **revert_opts)
242 commit_opts = opts.copy()
242 commit_opts = opts.copy()
243 commit_opts['addremove'] = False
243 commit_opts['addremove'] = False
244 if not commit_opts['message'] and not commit_opts['logfile']:
244 if not commit_opts['message'] and not commit_opts['logfile']:
245 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
245 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
246 commit_opts['force_editor'] = True
246 commit_opts['force_editor'] = True
247 commit(ui, repo, **commit_opts)
247 commit(ui, repo, **commit_opts)
248 def nice(node):
248 def nice(node):
249 return '%d:%s' % (repo.changelog.rev(node), short(node))
249 return '%d:%s' % (repo.changelog.rev(node), short(node))
250 ui.status(_('changeset %s backs out changeset %s\n') %
250 ui.status(_('changeset %s backs out changeset %s\n') %
251 (nice(repo.changelog.tip()), nice(node)))
251 (nice(repo.changelog.tip()), nice(node)))
252 if op1 != node:
252 if op1 != node:
253 hg.clean(repo, op1, show_stats=False)
253 hg.clean(repo, op1, show_stats=False)
254 if opts['merge']:
254 if opts['merge']:
255 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
255 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
256 hg.merge(repo, hex(repo.changelog.tip()))
256 hg.merge(repo, hex(repo.changelog.tip()))
257 else:
257 else:
258 ui.status(_('the backout changeset is a new head - '
258 ui.status(_('the backout changeset is a new head - '
259 'do not forget to merge\n'))
259 'do not forget to merge\n'))
260 ui.status(_('(use "backout --merge" '
260 ui.status(_('(use "backout --merge" '
261 'if you want to auto-merge)\n'))
261 'if you want to auto-merge)\n'))
262
262
263 def bisect(ui, repo, rev=None, extra=None,
263 def bisect(ui, repo, rev=None, extra=None,
264 reset=None, good=None, bad=None, skip=None, noupdate=None):
264 reset=None, good=None, bad=None, skip=None, noupdate=None):
265 """subdivision search of changesets
265 """subdivision search of changesets
266
266
267 This command helps to find changesets which introduce problems.
267 This command helps to find changesets which introduce problems.
268 To use, mark the earliest changeset you know exhibits the problem
268 To use, mark the earliest changeset you know exhibits the problem
269 as bad, then mark the latest changeset which is free from the
269 as bad, then mark the latest changeset which is free from the
270 problem as good. Bisect will update your working directory to a
270 problem as good. Bisect will update your working directory to a
271 revision for testing. Once you have performed tests, mark the
271 revision for testing. Once you have performed tests, mark the
272 working directory as bad or good and bisect will either update to
272 working directory as bad or good and bisect will either update to
273 another candidate changeset or announce that it has found the bad
273 another candidate changeset or announce that it has found the bad
274 revision.
274 revision.
275 """
275 """
276 # backward compatibility
276 # backward compatibility
277 if rev in "good bad reset init".split():
277 if rev in "good bad reset init".split():
278 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
278 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
279 cmd, rev, extra = rev, extra, None
279 cmd, rev, extra = rev, extra, None
280 if cmd == "good":
280 if cmd == "good":
281 good = True
281 good = True
282 elif cmd == "bad":
282 elif cmd == "bad":
283 bad = True
283 bad = True
284 else:
284 else:
285 reset = True
285 reset = True
286 elif extra or good + bad + skip + reset > 1:
286 elif extra or good + bad + skip + reset > 1:
287 raise util.Abort("Incompatible arguments")
287 raise util.Abort("Incompatible arguments")
288
288
289 if reset:
289 if reset:
290 p = repo.join("bisect.state")
290 p = repo.join("bisect.state")
291 if os.path.exists(p):
291 if os.path.exists(p):
292 os.unlink(p)
292 os.unlink(p)
293 return
293 return
294
294
295 # load state
295 # load state
296 state = {'good': [], 'bad': [], 'skip': []}
296 state = {'good': [], 'bad': [], 'skip': []}
297 if os.path.exists(repo.join("bisect.state")):
297 if os.path.exists(repo.join("bisect.state")):
298 for l in repo.opener("bisect.state"):
298 for l in repo.opener("bisect.state"):
299 kind, node = l[:-1].split()
299 kind, node = l[:-1].split()
300 node = repo.lookup(node)
300 node = repo.lookup(node)
301 if kind not in state:
301 if kind not in state:
302 raise util.Abort(_("unknown bisect kind %s") % kind)
302 raise util.Abort(_("unknown bisect kind %s") % kind)
303 state[kind].append(node)
303 state[kind].append(node)
304
304
305 # update state
305 # update state
306 node = repo.lookup(rev or '.')
306 node = repo.lookup(rev or '.')
307 if good:
307 if good:
308 state['good'].append(node)
308 state['good'].append(node)
309 elif bad:
309 elif bad:
310 state['bad'].append(node)
310 state['bad'].append(node)
311 elif skip:
311 elif skip:
312 state['skip'].append(node)
312 state['skip'].append(node)
313
313
314 # save state
314 # save state
315 f = repo.opener("bisect.state", "w", atomictemp=True)
315 f = repo.opener("bisect.state", "w", atomictemp=True)
316 wlock = repo.wlock()
316 wlock = repo.wlock()
317 try:
317 try:
318 for kind in state:
318 for kind in state:
319 for node in state[kind]:
319 for node in state[kind]:
320 f.write("%s %s\n" % (kind, hex(node)))
320 f.write("%s %s\n" % (kind, hex(node)))
321 f.rename()
321 f.rename()
322 finally:
322 finally:
323 del wlock
323 del wlock
324
324
325 if not state['good'] or not state['bad']:
325 if not state['good'] or not state['bad']:
326 return
326 return
327
327
328 # actually bisect
328 # actually bisect
329 node, changesets, good = hbisect.bisect(repo.changelog, state)
329 node, changesets, good = hbisect.bisect(repo.changelog, state)
330 if changesets == 0:
330 if changesets == 0:
331 ui.write(_("The first %s revision is:\n") % (good and "good" or "bad"))
331 ui.write(_("The first %s revision is:\n") % (good and "good" or "bad"))
332 displayer = cmdutil.show_changeset(ui, repo, {})
332 displayer = cmdutil.show_changeset(ui, repo, {})
333 displayer.show(changenode=node)
333 displayer.show(changenode=node)
334 elif node is not None:
334 elif node is not None:
335 # compute the approximate number of remaining tests
335 # compute the approximate number of remaining tests
336 tests, size = 0, 2
336 tests, size = 0, 2
337 while size <= changesets:
337 while size <= changesets:
338 tests, size = tests + 1, size * 2
338 tests, size = tests + 1, size * 2
339 rev = repo.changelog.rev(node)
339 rev = repo.changelog.rev(node)
340 ui.write(_("Testing changeset %s:%s "
340 ui.write(_("Testing changeset %s:%s "
341 "(%s changesets remaining, ~%s tests)\n")
341 "(%s changesets remaining, ~%s tests)\n")
342 % (rev, short(node), changesets, tests))
342 % (rev, short(node), changesets, tests))
343 if not noupdate:
343 if not noupdate:
344 cmdutil.bail_if_changed(repo)
344 cmdutil.bail_if_changed(repo)
345 return hg.clean(repo, node)
345 return hg.clean(repo, node)
346
346
347 def branch(ui, repo, label=None, **opts):
347 def branch(ui, repo, label=None, **opts):
348 """set or show the current branch name
348 """set or show the current branch name
349
349
350 With no argument, show the current branch name. With one argument,
350 With no argument, show the current branch name. With one argument,
351 set the working directory branch name (the branch does not exist in
351 set the working directory branch name (the branch does not exist in
352 the repository until the next commit).
352 the repository until the next commit).
353
353
354 Unless --force is specified, branch will not let you set a
354 Unless --force is specified, branch will not let you set a
355 branch name that shadows an existing branch.
355 branch name that shadows an existing branch.
356
356
357 Use the command 'hg update' to switch to an existing branch.
357 Use the command 'hg update' to switch to an existing branch.
358 """
358 """
359
359
360 if label:
360 if label:
361 if not opts.get('force') and label in repo.branchtags():
361 if not opts.get('force') and label in repo.branchtags():
362 if label not in [p.branch() for p in repo.parents()]:
362 if label not in [p.branch() for p in repo.parents()]:
363 raise util.Abort(_('a branch of the same name already exists'
363 raise util.Abort(_('a branch of the same name already exists'
364 ' (use --force to override)'))
364 ' (use --force to override)'))
365 repo.dirstate.setbranch(util.fromlocal(label))
365 repo.dirstate.setbranch(util.fromlocal(label))
366 ui.status(_('marked working directory as branch %s\n') % label)
366 ui.status(_('marked working directory as branch %s\n') % label)
367 else:
367 else:
368 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
368 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
369
369
370 def branches(ui, repo, active=False):
370 def branches(ui, repo, active=False):
371 """list repository named branches
371 """list repository named branches
372
372
373 List the repository's named branches, indicating which ones are
373 List the repository's named branches, indicating which ones are
374 inactive. If active is specified, only show active branches.
374 inactive. If active is specified, only show active branches.
375
375
376 A branch is considered active if it contains repository heads.
376 A branch is considered active if it contains repository heads.
377
377
378 Use the command 'hg update' to switch to an existing branch.
378 Use the command 'hg update' to switch to an existing branch.
379 """
379 """
380 hexfunc = ui.debugflag and hex or short
380 hexfunc = ui.debugflag and hex or short
381 activebranches = [util.tolocal(repo[n].branch())
381 activebranches = [util.tolocal(repo[n].branch())
382 for n in repo.heads()]
382 for n in repo.heads()]
383 branches = [(tag in activebranches, repo.changelog.rev(node), tag)
383 branches = [(tag in activebranches, repo.changelog.rev(node), tag)
384 for tag, node in repo.branchtags().items()]
384 for tag, node in repo.branchtags().items()]
385 branches.sort()
385 branches.sort()
386 branches.reverse()
386 branches.reverse()
387
387
388 for isactive, node, tag in branches:
388 for isactive, node, tag in branches:
389 if (not active) or isactive:
389 if (not active) or isactive:
390 if ui.quiet:
390 if ui.quiet:
391 ui.write("%s\n" % tag)
391 ui.write("%s\n" % tag)
392 else:
392 else:
393 rev = str(node).rjust(32 - util.locallen(tag))
393 rev = str(node).rjust(32 - util.locallen(tag))
394 isinactive = ((not isactive) and " (inactive)") or ''
394 isinactive = ((not isactive) and " (inactive)") or ''
395 data = tag, rev, hexfunc(repo.lookup(node)), isinactive
395 data = tag, rev, hexfunc(repo.lookup(node)), isinactive
396 ui.write("%s%s:%s%s\n" % data)
396 ui.write("%s%s:%s%s\n" % data)
397
397
398 def bundle(ui, repo, fname, dest=None, **opts):
398 def bundle(ui, repo, fname, dest=None, **opts):
399 """create a changegroup file
399 """create a changegroup file
400
400
401 Generate a compressed changegroup file collecting changesets not
401 Generate a compressed changegroup file collecting changesets not
402 found in the other repository.
402 found in the other repository.
403
403
404 If no destination repository is specified the destination is
404 If no destination repository is specified the destination is
405 assumed to have all the nodes specified by one or more --base
405 assumed to have all the nodes specified by one or more --base
406 parameters. To create a bundle containing all changesets, use
406 parameters. To create a bundle containing all changesets, use
407 --all (or --base null). To change the compression method applied,
407 --all (or --base null). To change the compression method applied,
408 use the -t option (by default, bundles are compressed using bz2).
408 use the -t option (by default, bundles are compressed using bz2).
409
409
410 The bundle file can then be transferred using conventional means and
410 The bundle file can then be transferred using conventional means and
411 applied to another repository with the unbundle or pull command.
411 applied to another repository with the unbundle or pull command.
412 This is useful when direct push and pull are not available or when
412 This is useful when direct push and pull are not available or when
413 exporting an entire repository is undesirable.
413 exporting an entire repository is undesirable.
414
414
415 Applying bundles preserves all changeset contents including
415 Applying bundles preserves all changeset contents including
416 permissions, copy/rename information, and revision history.
416 permissions, copy/rename information, and revision history.
417 """
417 """
418 revs = opts.get('rev') or None
418 revs = opts.get('rev') or None
419 if revs:
419 if revs:
420 revs = [repo.lookup(rev) for rev in revs]
420 revs = [repo.lookup(rev) for rev in revs]
421 if opts.get('all'):
421 if opts.get('all'):
422 base = ['null']
422 base = ['null']
423 else:
423 else:
424 base = opts.get('base')
424 base = opts.get('base')
425 if base:
425 if base:
426 if dest:
426 if dest:
427 raise util.Abort(_("--base is incompatible with specifiying "
427 raise util.Abort(_("--base is incompatible with specifiying "
428 "a destination"))
428 "a destination"))
429 base = [repo.lookup(rev) for rev in base]
429 base = [repo.lookup(rev) for rev in base]
430 # create the right base
430 # create the right base
431 # XXX: nodesbetween / changegroup* should be "fixed" instead
431 # XXX: nodesbetween / changegroup* should be "fixed" instead
432 o = []
432 o = []
433 has = {nullid: None}
433 has = {nullid: None}
434 for n in base:
434 for n in base:
435 has.update(repo.changelog.reachable(n))
435 has.update(repo.changelog.reachable(n))
436 if revs:
436 if revs:
437 visit = list(revs)
437 visit = list(revs)
438 else:
438 else:
439 visit = repo.changelog.heads()
439 visit = repo.changelog.heads()
440 seen = {}
440 seen = {}
441 while visit:
441 while visit:
442 n = visit.pop(0)
442 n = visit.pop(0)
443 parents = [p for p in repo.changelog.parents(n) if p not in has]
443 parents = [p for p in repo.changelog.parents(n) if p not in has]
444 if len(parents) == 0:
444 if len(parents) == 0:
445 o.insert(0, n)
445 o.insert(0, n)
446 else:
446 else:
447 for p in parents:
447 for p in parents:
448 if p not in seen:
448 if p not in seen:
449 seen[p] = 1
449 seen[p] = 1
450 visit.append(p)
450 visit.append(p)
451 else:
451 else:
452 cmdutil.setremoteconfig(ui, opts)
452 cmdutil.setremoteconfig(ui, opts)
453 dest, revs, checkout = hg.parseurl(
453 dest, revs, checkout = hg.parseurl(
454 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
454 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
455 other = hg.repository(ui, dest)
455 other = hg.repository(ui, dest)
456 o = repo.findoutgoing(other, force=opts['force'])
456 o = repo.findoutgoing(other, force=opts['force'])
457
457
458 if revs:
458 if revs:
459 cg = repo.changegroupsubset(o, revs, 'bundle')
459 cg = repo.changegroupsubset(o, revs, 'bundle')
460 else:
460 else:
461 cg = repo.changegroup(o, 'bundle')
461 cg = repo.changegroup(o, 'bundle')
462
462
463 bundletype = opts.get('type', 'bzip2').lower()
463 bundletype = opts.get('type', 'bzip2').lower()
464 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
464 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
465 bundletype = btypes.get(bundletype)
465 bundletype = btypes.get(bundletype)
466 if bundletype not in changegroup.bundletypes:
466 if bundletype not in changegroup.bundletypes:
467 raise util.Abort(_('unknown bundle type specified with --type'))
467 raise util.Abort(_('unknown bundle type specified with --type'))
468
468
469 changegroup.writebundle(cg, fname, bundletype)
469 changegroup.writebundle(cg, fname, bundletype)
470
470
471 def cat(ui, repo, file1, *pats, **opts):
471 def cat(ui, repo, file1, *pats, **opts):
472 """output the current or given revision of files
472 """output the current or given revision of files
473
473
474 Print the specified files as they were at the given revision.
474 Print the specified files as they were at the given revision.
475 If no revision is given, the parent of the working directory is used,
475 If no revision is given, the parent of the working directory is used,
476 or tip if no revision is checked out.
476 or tip if no revision is checked out.
477
477
478 Output may be to a file, in which case the name of the file is
478 Output may be to a file, in which case the name of the file is
479 given using a format string. The formatting rules are the same as
479 given using a format string. The formatting rules are the same as
480 for the export command, with the following additions:
480 for the export command, with the following additions:
481
481
482 %s basename of file being printed
482 %s basename of file being printed
483 %d dirname of file being printed, or '.' if in repo root
483 %d dirname of file being printed, or '.' if in repo root
484 %p root-relative path name of file being printed
484 %p root-relative path name of file being printed
485 """
485 """
486 ctx = repo[opts['rev']]
486 ctx = repo[opts['rev']]
487 err = 1
487 err = 1
488 m = cmdutil.match(repo, (file1,) + pats, opts)
488 m = cmdutil.match(repo, (file1,) + pats, opts)
489 for abs in repo.walk(m, ctx.node()):
489 for abs in repo.walk(m, ctx.node()):
490 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
490 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
491 data = ctx.filectx(abs).data()
491 data = ctx.filectx(abs).data()
492 if opts.get('decode'):
492 if opts.get('decode'):
493 data = repo.wwritedata(abs, data)
493 data = repo.wwritedata(abs, data)
494 fp.write(data)
494 fp.write(data)
495 err = 0
495 err = 0
496 return err
496 return err
497
497
498 def clone(ui, source, dest=None, **opts):
498 def clone(ui, source, dest=None, **opts):
499 """make a copy of an existing repository
499 """make a copy of an existing repository
500
500
501 Create a copy of an existing repository in a new directory.
501 Create a copy of an existing repository in a new directory.
502
502
503 If no destination directory name is specified, it defaults to the
503 If no destination directory name is specified, it defaults to the
504 basename of the source.
504 basename of the source.
505
505
506 The location of the source is added to the new repository's
506 The location of the source is added to the new repository's
507 .hg/hgrc file, as the default to be used for future pulls.
507 .hg/hgrc file, as the default to be used for future pulls.
508
508
509 For efficiency, hardlinks are used for cloning whenever the source
509 For efficiency, hardlinks are used for cloning whenever the source
510 and destination are on the same filesystem (note this applies only
510 and destination are on the same filesystem (note this applies only
511 to the repository data, not to the checked out files). Some
511 to the repository data, not to the checked out files). Some
512 filesystems, such as AFS, implement hardlinking incorrectly, but
512 filesystems, such as AFS, implement hardlinking incorrectly, but
513 do not report errors. In these cases, use the --pull option to
513 do not report errors. In these cases, use the --pull option to
514 avoid hardlinking.
514 avoid hardlinking.
515
515
516 In some cases, you can clone repositories and checked out files
516 In some cases, you can clone repositories and checked out files
517 using full hardlinks with
517 using full hardlinks with
518
518
519 $ cp -al REPO REPOCLONE
519 $ cp -al REPO REPOCLONE
520
520
521 This is the fastest way to clone, but it is not always safe. The
521 This is the fastest way to clone, but it is not always safe. The
522 operation is not atomic (making sure REPO is not modified during
522 operation is not atomic (making sure REPO is not modified during
523 the operation is up to you) and you have to make sure your editor
523 the operation is up to you) and you have to make sure your editor
524 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
524 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
525 this is not compatible with certain extensions that place their
525 this is not compatible with certain extensions that place their
526 metadata under the .hg directory, such as mq.
526 metadata under the .hg directory, such as mq.
527
527
528 If you use the -r option to clone up to a specific revision, no
528 If you use the -r option to clone up to a specific revision, no
529 subsequent revisions will be present in the cloned repository.
529 subsequent revisions will be present in the cloned repository.
530 This option implies --pull, even on local repositories.
530 This option implies --pull, even on local repositories.
531
531
532 If the -U option is used, the new clone will contain only a repository
532 If the -U option is used, the new clone will contain only a repository
533 (.hg) and no working copy (the working copy parent is the null revision).
533 (.hg) and no working copy (the working copy parent is the null revision).
534
534
535 See pull for valid source format details.
535 See pull for valid source format details.
536
536
537 It is possible to specify an ssh:// URL as the destination, but no
537 It is possible to specify an ssh:// URL as the destination, but no
538 .hg/hgrc and working directory will be created on the remote side.
538 .hg/hgrc and working directory will be created on the remote side.
539 Look at the help text for the pull command for important details
539 Look at the help text for the pull command for important details
540 about ssh:// URLs.
540 about ssh:// URLs.
541 """
541 """
542 cmdutil.setremoteconfig(ui, opts)
542 cmdutil.setremoteconfig(ui, opts)
543 hg.clone(ui, source, dest,
543 hg.clone(ui, source, dest,
544 pull=opts['pull'],
544 pull=opts['pull'],
545 stream=opts['uncompressed'],
545 stream=opts['uncompressed'],
546 rev=opts['rev'],
546 rev=opts['rev'],
547 update=not opts['noupdate'])
547 update=not opts['noupdate'])
548
548
549 def commit(ui, repo, *pats, **opts):
549 def commit(ui, repo, *pats, **opts):
550 """commit the specified files or all outstanding changes
550 """commit the specified files or all outstanding changes
551
551
552 Commit changes to the given files into the repository.
552 Commit changes to the given files into the repository.
553
553
554 If a list of files is omitted, all changes reported by "hg status"
554 If a list of files is omitted, all changes reported by "hg status"
555 will be committed.
555 will be committed.
556
556
557 If you are committing the result of a merge, do not provide any
557 If you are committing the result of a merge, do not provide any
558 file names or -I/-X filters.
558 file names or -I/-X filters.
559
559
560 If no commit message is specified, the configured editor is started to
560 If no commit message is specified, the configured editor is started to
561 enter a message.
561 enter a message.
562
562
563 See 'hg help dates' for a list of formats valid for -d/--date.
563 See 'hg help dates' for a list of formats valid for -d/--date.
564 """
564 """
565 def commitfunc(ui, repo, message, match, opts):
565 def commitfunc(ui, repo, message, match, opts):
566 return repo.commit(match.files(), message, opts['user'], opts['date'],
566 return repo.commit(match.files(), message, opts['user'], opts['date'],
567 match, force_editor=opts.get('force_editor'))
567 match, force_editor=opts.get('force_editor'))
568
568
569 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
569 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
570 if not node:
570 if not node:
571 return
571 return
572 cl = repo.changelog
572 cl = repo.changelog
573 rev = cl.rev(node)
573 rev = cl.rev(node)
574 parents = cl.parentrevs(rev)
574 parents = cl.parentrevs(rev)
575 if rev - 1 in parents:
575 if rev - 1 in parents:
576 # one of the parents was the old tip
576 # one of the parents was the old tip
577 return
577 return
578 if (parents == (nullrev, nullrev) or
578 if (parents == (nullrev, nullrev) or
579 len(cl.heads(cl.node(parents[0]))) > 1 and
579 len(cl.heads(cl.node(parents[0]))) > 1 and
580 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
580 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
581 ui.status(_('created new head\n'))
581 ui.status(_('created new head\n'))
582
582
583 def copy(ui, repo, *pats, **opts):
583 def copy(ui, repo, *pats, **opts):
584 """mark files as copied for the next commit
584 """mark files as copied for the next commit
585
585
586 Mark dest as having copies of source files. If dest is a
586 Mark dest as having copies of source files. If dest is a
587 directory, copies are put in that directory. If dest is a file,
587 directory, copies are put in that directory. If dest is a file,
588 there can only be one source.
588 there can only be one source.
589
589
590 By default, this command copies the contents of files as they
590 By default, this command copies the contents of files as they
591 stand in the working directory. If invoked with --after, the
591 stand in the working directory. If invoked with --after, the
592 operation is recorded, but no copying is performed.
592 operation is recorded, but no copying is performed.
593
593
594 This command takes effect in the next commit. To undo a copy
594 This command takes effect in the next commit. To undo a copy
595 before that, see hg revert.
595 before that, see hg revert.
596 """
596 """
597 wlock = repo.wlock(False)
597 wlock = repo.wlock(False)
598 try:
598 try:
599 return cmdutil.copy(ui, repo, pats, opts)
599 return cmdutil.copy(ui, repo, pats, opts)
600 finally:
600 finally:
601 del wlock
601 del wlock
602
602
603 def debugancestor(ui, repo, *args):
603 def debugancestor(ui, repo, *args):
604 """find the ancestor revision of two revisions in a given index"""
604 """find the ancestor revision of two revisions in a given index"""
605 if len(args) == 3:
605 if len(args) == 3:
606 index, rev1, rev2 = args
606 index, rev1, rev2 = args
607 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
607 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
608 lookup = r.lookup
608 lookup = r.lookup
609 elif len(args) == 2:
609 elif len(args) == 2:
610 if not repo:
610 if not repo:
611 raise util.Abort(_("There is no Mercurial repository here "
611 raise util.Abort(_("There is no Mercurial repository here "
612 "(.hg not found)"))
612 "(.hg not found)"))
613 rev1, rev2 = args
613 rev1, rev2 = args
614 r = repo.changelog
614 r = repo.changelog
615 lookup = repo.lookup
615 lookup = repo.lookup
616 else:
616 else:
617 raise util.Abort(_('either two or three arguments required'))
617 raise util.Abort(_('either two or three arguments required'))
618 a = r.ancestor(lookup(rev1), lookup(rev2))
618 a = r.ancestor(lookup(rev1), lookup(rev2))
619 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
619 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
620
620
621 def debugcomplete(ui, cmd='', **opts):
621 def debugcomplete(ui, cmd='', **opts):
622 """returns the completion list associated with the given command"""
622 """returns the completion list associated with the given command"""
623
623
624 if opts['options']:
624 if opts['options']:
625 options = []
625 options = []
626 otables = [globalopts]
626 otables = [globalopts]
627 if cmd:
627 if cmd:
628 aliases, entry = cmdutil.findcmd(ui, cmd, table)
628 aliases, entry = cmdutil.findcmd(ui, cmd, table)
629 otables.append(entry[1])
629 otables.append(entry[1])
630 for t in otables:
630 for t in otables:
631 for o in t:
631 for o in t:
632 if o[0]:
632 if o[0]:
633 options.append('-%s' % o[0])
633 options.append('-%s' % o[0])
634 options.append('--%s' % o[1])
634 options.append('--%s' % o[1])
635 ui.write("%s\n" % "\n".join(options))
635 ui.write("%s\n" % "\n".join(options))
636 return
636 return
637
637
638 clist = cmdutil.findpossible(ui, cmd, table).keys()
638 clist = cmdutil.findpossible(ui, cmd, table).keys()
639 clist.sort()
639 clist.sort()
640 ui.write("%s\n" % "\n".join(clist))
640 ui.write("%s\n" % "\n".join(clist))
641
641
642 def debugfsinfo(ui, path = "."):
642 def debugfsinfo(ui, path = "."):
643 file('.debugfsinfo', 'w').write('')
643 file('.debugfsinfo', 'w').write('')
644 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
644 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
645 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
645 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
646 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
646 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
647 and 'yes' or 'no'))
647 and 'yes' or 'no'))
648 os.unlink('.debugfsinfo')
648 os.unlink('.debugfsinfo')
649
649
650 def debugrebuildstate(ui, repo, rev="tip"):
650 def debugrebuildstate(ui, repo, rev="tip"):
651 """rebuild the dirstate as it would look like for the given revision"""
651 """rebuild the dirstate as it would look like for the given revision"""
652 ctx = repo[rev]
652 ctx = repo[rev]
653 wlock = repo.wlock()
653 wlock = repo.wlock()
654 try:
654 try:
655 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
655 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
656 finally:
656 finally:
657 del wlock
657 del wlock
658
658
659 def debugcheckstate(ui, repo):
659 def debugcheckstate(ui, repo):
660 """validate the correctness of the current dirstate"""
660 """validate the correctness of the current dirstate"""
661 parent1, parent2 = repo.dirstate.parents()
661 parent1, parent2 = repo.dirstate.parents()
662 m1 = repo[parent1].manifest()
662 m1 = repo[parent1].manifest()
663 m2 = repo[parent2].manifest()
663 m2 = repo[parent2].manifest()
664 errors = 0
664 errors = 0
665 for f in repo.dirstate:
665 for f in repo.dirstate:
666 state = repo.dirstate[f]
666 state = repo.dirstate[f]
667 if state in "nr" and f not in m1:
667 if state in "nr" and f not in m1:
668 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
668 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
669 errors += 1
669 errors += 1
670 if state in "a" and f in m1:
670 if state in "a" and f in m1:
671 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
671 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
672 errors += 1
672 errors += 1
673 if state in "m" and f not in m1 and f not in m2:
673 if state in "m" and f not in m1 and f not in m2:
674 ui.warn(_("%s in state %s, but not in either manifest\n") %
674 ui.warn(_("%s in state %s, but not in either manifest\n") %
675 (f, state))
675 (f, state))
676 errors += 1
676 errors += 1
677 for f in m1:
677 for f in m1:
678 state = repo.dirstate[f]
678 state = repo.dirstate[f]
679 if state not in "nrm":
679 if state not in "nrm":
680 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
680 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
681 errors += 1
681 errors += 1
682 if errors:
682 if errors:
683 error = _(".hg/dirstate inconsistent with current parent's manifest")
683 error = _(".hg/dirstate inconsistent with current parent's manifest")
684 raise util.Abort(error)
684 raise util.Abort(error)
685
685
686 def showconfig(ui, repo, *values, **opts):
686 def showconfig(ui, repo, *values, **opts):
687 """show combined config settings from all hgrc files
687 """show combined config settings from all hgrc files
688
688
689 With no args, print names and values of all config items.
689 With no args, print names and values of all config items.
690
690
691 With one arg of the form section.name, print just the value of
691 With one arg of the form section.name, print just the value of
692 that config item.
692 that config item.
693
693
694 With multiple args, print names and values of all config items
694 With multiple args, print names and values of all config items
695 with matching section names."""
695 with matching section names."""
696
696
697 untrusted = bool(opts.get('untrusted'))
697 untrusted = bool(opts.get('untrusted'))
698 if values:
698 if values:
699 if len([v for v in values if '.' in v]) > 1:
699 if len([v for v in values if '.' in v]) > 1:
700 raise util.Abort(_('only one config item permitted'))
700 raise util.Abort(_('only one config item permitted'))
701 for section, name, value in ui.walkconfig(untrusted=untrusted):
701 for section, name, value in ui.walkconfig(untrusted=untrusted):
702 sectname = section + '.' + name
702 sectname = section + '.' + name
703 if values:
703 if values:
704 for v in values:
704 for v in values:
705 if v == section:
705 if v == section:
706 ui.write('%s=%s\n' % (sectname, value))
706 ui.write('%s=%s\n' % (sectname, value))
707 elif v == sectname:
707 elif v == sectname:
708 ui.write(value, '\n')
708 ui.write(value, '\n')
709 else:
709 else:
710 ui.write('%s=%s\n' % (sectname, value))
710 ui.write('%s=%s\n' % (sectname, value))
711
711
712 def debugsetparents(ui, repo, rev1, rev2=None):
712 def debugsetparents(ui, repo, rev1, rev2=None):
713 """manually set the parents of the current working directory
713 """manually set the parents of the current working directory
714
714
715 This is useful for writing repository conversion tools, but should
715 This is useful for writing repository conversion tools, but should
716 be used with care.
716 be used with care.
717 """
717 """
718
718
719 if not rev2:
719 if not rev2:
720 rev2 = hex(nullid)
720 rev2 = hex(nullid)
721
721
722 wlock = repo.wlock()
722 wlock = repo.wlock()
723 try:
723 try:
724 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
724 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
725 finally:
725 finally:
726 del wlock
726 del wlock
727
727
728 def debugstate(ui, repo, nodates=None):
728 def debugstate(ui, repo, nodates=None):
729 """show the contents of the current dirstate"""
729 """show the contents of the current dirstate"""
730 k = repo.dirstate._map.items()
730 k = repo.dirstate._map.items()
731 k.sort()
731 k.sort()
732 timestr = ""
732 timestr = ""
733 showdate = not nodates
733 showdate = not nodates
734 for file_, ent in k:
734 for file_, ent in k:
735 if showdate:
735 if showdate:
736 if ent[3] == -1:
736 if ent[3] == -1:
737 # Pad or slice to locale representation
737 # Pad or slice to locale representation
738 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
738 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
739 timestr = 'unset'
739 timestr = 'unset'
740 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
740 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
741 else:
741 else:
742 timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
742 timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
743 if ent[1] & 020000:
743 if ent[1] & 020000:
744 mode = 'lnk'
744 mode = 'lnk'
745 else:
745 else:
746 mode = '%3o' % (ent[1] & 0777)
746 mode = '%3o' % (ent[1] & 0777)
747 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
747 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
748 for f in repo.dirstate.copies():
748 for f in repo.dirstate.copies():
749 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
749 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
750
750
751 def debugdata(ui, file_, rev):
751 def debugdata(ui, file_, rev):
752 """dump the contents of a data file revision"""
752 """dump the contents of a data file revision"""
753 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
753 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
754 try:
754 try:
755 ui.write(r.revision(r.lookup(rev)))
755 ui.write(r.revision(r.lookup(rev)))
756 except KeyError:
756 except KeyError:
757 raise util.Abort(_('invalid revision identifier %s') % rev)
757 raise util.Abort(_('invalid revision identifier %s') % rev)
758
758
759 def debugdate(ui, date, range=None, **opts):
759 def debugdate(ui, date, range=None, **opts):
760 """parse and display a date"""
760 """parse and display a date"""
761 if opts["extended"]:
761 if opts["extended"]:
762 d = util.parsedate(date, util.extendeddateformats)
762 d = util.parsedate(date, util.extendeddateformats)
763 else:
763 else:
764 d = util.parsedate(date)
764 d = util.parsedate(date)
765 ui.write("internal: %s %s\n" % d)
765 ui.write("internal: %s %s\n" % d)
766 ui.write("standard: %s\n" % util.datestr(d))
766 ui.write("standard: %s\n" % util.datestr(d))
767 if range:
767 if range:
768 m = util.matchdate(range)
768 m = util.matchdate(range)
769 ui.write("match: %s\n" % m(d[0]))
769 ui.write("match: %s\n" % m(d[0]))
770
770
771 def debugindex(ui, file_):
771 def debugindex(ui, file_):
772 """dump the contents of an index file"""
772 """dump the contents of an index file"""
773 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
773 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
774 ui.write(" rev offset length base linkrev" +
774 ui.write(" rev offset length base linkrev" +
775 " nodeid p1 p2\n")
775 " nodeid p1 p2\n")
776 for i in xrange(r.count()):
776 for i in xrange(r.count()):
777 node = r.node(i)
777 node = r.node(i)
778 try:
778 try:
779 pp = r.parents(node)
779 pp = r.parents(node)
780 except:
780 except:
781 pp = [nullid, nullid]
781 pp = [nullid, nullid]
782 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
782 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
783 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
783 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
784 short(node), short(pp[0]), short(pp[1])))
784 short(node), short(pp[0]), short(pp[1])))
785
785
786 def debugindexdot(ui, file_):
786 def debugindexdot(ui, file_):
787 """dump an index DAG as a .dot file"""
787 """dump an index DAG as a .dot file"""
788 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
788 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
789 ui.write("digraph G {\n")
789 ui.write("digraph G {\n")
790 for i in xrange(r.count()):
790 for i in xrange(r.count()):
791 node = r.node(i)
791 node = r.node(i)
792 pp = r.parents(node)
792 pp = r.parents(node)
793 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
793 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
794 if pp[1] != nullid:
794 if pp[1] != nullid:
795 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
795 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
796 ui.write("}\n")
796 ui.write("}\n")
797
797
798 def debuginstall(ui):
798 def debuginstall(ui):
799 '''test Mercurial installation'''
799 '''test Mercurial installation'''
800
800
801 def writetemp(contents):
801 def writetemp(contents):
802 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
802 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
803 f = os.fdopen(fd, "wb")
803 f = os.fdopen(fd, "wb")
804 f.write(contents)
804 f.write(contents)
805 f.close()
805 f.close()
806 return name
806 return name
807
807
808 problems = 0
808 problems = 0
809
809
810 # encoding
810 # encoding
811 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
811 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
812 try:
812 try:
813 util.fromlocal("test")
813 util.fromlocal("test")
814 except util.Abort, inst:
814 except util.Abort, inst:
815 ui.write(" %s\n" % inst)
815 ui.write(" %s\n" % inst)
816 ui.write(_(" (check that your locale is properly set)\n"))
816 ui.write(_(" (check that your locale is properly set)\n"))
817 problems += 1
817 problems += 1
818
818
819 # compiled modules
819 # compiled modules
820 ui.status(_("Checking extensions...\n"))
820 ui.status(_("Checking extensions...\n"))
821 try:
821 try:
822 import bdiff, mpatch, base85
822 import bdiff, mpatch, base85
823 except Exception, inst:
823 except Exception, inst:
824 ui.write(" %s\n" % inst)
824 ui.write(" %s\n" % inst)
825 ui.write(_(" One or more extensions could not be found"))
825 ui.write(_(" One or more extensions could not be found"))
826 ui.write(_(" (check that you compiled the extensions)\n"))
826 ui.write(_(" (check that you compiled the extensions)\n"))
827 problems += 1
827 problems += 1
828
828
829 # templates
829 # templates
830 ui.status(_("Checking templates...\n"))
830 ui.status(_("Checking templates...\n"))
831 try:
831 try:
832 import templater
832 import templater
833 t = templater.templater(templater.templatepath("map-cmdline.default"))
833 t = templater.templater(templater.templatepath("map-cmdline.default"))
834 except Exception, inst:
834 except Exception, inst:
835 ui.write(" %s\n" % inst)
835 ui.write(" %s\n" % inst)
836 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
836 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
837 problems += 1
837 problems += 1
838
838
839 # patch
839 # patch
840 ui.status(_("Checking patch...\n"))
840 ui.status(_("Checking patch...\n"))
841 patchproblems = 0
841 patchproblems = 0
842 a = "1\n2\n3\n4\n"
842 a = "1\n2\n3\n4\n"
843 b = "1\n2\n3\ninsert\n4\n"
843 b = "1\n2\n3\ninsert\n4\n"
844 fa = writetemp(a)
844 fa = writetemp(a)
845 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
845 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
846 os.path.basename(fa))
846 os.path.basename(fa))
847 fd = writetemp(d)
847 fd = writetemp(d)
848
848
849 files = {}
849 files = {}
850 try:
850 try:
851 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
851 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
852 except util.Abort, e:
852 except util.Abort, e:
853 ui.write(_(" patch call failed:\n"))
853 ui.write(_(" patch call failed:\n"))
854 ui.write(" " + str(e) + "\n")
854 ui.write(" " + str(e) + "\n")
855 patchproblems += 1
855 patchproblems += 1
856 else:
856 else:
857 if list(files) != [os.path.basename(fa)]:
857 if list(files) != [os.path.basename(fa)]:
858 ui.write(_(" unexpected patch output!\n"))
858 ui.write(_(" unexpected patch output!\n"))
859 patchproblems += 1
859 patchproblems += 1
860 a = file(fa).read()
860 a = file(fa).read()
861 if a != b:
861 if a != b:
862 ui.write(_(" patch test failed!\n"))
862 ui.write(_(" patch test failed!\n"))
863 patchproblems += 1
863 patchproblems += 1
864
864
865 if patchproblems:
865 if patchproblems:
866 if ui.config('ui', 'patch'):
866 if ui.config('ui', 'patch'):
867 ui.write(_(" (Current patch tool may be incompatible with patch,"
867 ui.write(_(" (Current patch tool may be incompatible with patch,"
868 " or misconfigured. Please check your .hgrc file)\n"))
868 " or misconfigured. Please check your .hgrc file)\n"))
869 else:
869 else:
870 ui.write(_(" Internal patcher failure, please report this error"
870 ui.write(_(" Internal patcher failure, please report this error"
871 " to http://www.selenic.com/mercurial/bts\n"))
871 " to http://www.selenic.com/mercurial/bts\n"))
872 problems += patchproblems
872 problems += patchproblems
873
873
874 os.unlink(fa)
874 os.unlink(fa)
875 os.unlink(fd)
875 os.unlink(fd)
876
876
877 # editor
877 # editor
878 ui.status(_("Checking commit editor...\n"))
878 ui.status(_("Checking commit editor...\n"))
879 editor = ui.geteditor()
879 editor = ui.geteditor()
880 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
880 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
881 if not cmdpath:
881 if not cmdpath:
882 if editor == 'vi':
882 if editor == 'vi':
883 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
883 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
884 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
884 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
885 else:
885 else:
886 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
886 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
887 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
887 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
888 problems += 1
888 problems += 1
889
889
890 # check username
890 # check username
891 ui.status(_("Checking username...\n"))
891 ui.status(_("Checking username...\n"))
892 user = os.environ.get("HGUSER")
892 user = os.environ.get("HGUSER")
893 if user is None:
893 if user is None:
894 user = ui.config("ui", "username")
894 user = ui.config("ui", "username")
895 if user is None:
895 if user is None:
896 user = os.environ.get("EMAIL")
896 user = os.environ.get("EMAIL")
897 if not user:
897 if not user:
898 ui.warn(" ")
898 ui.warn(" ")
899 ui.username()
899 ui.username()
900 ui.write(_(" (specify a username in your .hgrc file)\n"))
900 ui.write(_(" (specify a username in your .hgrc file)\n"))
901
901
902 if not problems:
902 if not problems:
903 ui.status(_("No problems detected\n"))
903 ui.status(_("No problems detected\n"))
904 else:
904 else:
905 ui.write(_("%s problems detected,"
905 ui.write(_("%s problems detected,"
906 " please check your install!\n") % problems)
906 " please check your install!\n") % problems)
907
907
908 return problems
908 return problems
909
909
910 def debugrename(ui, repo, file1, *pats, **opts):
910 def debugrename(ui, repo, file1, *pats, **opts):
911 """dump rename information"""
911 """dump rename information"""
912
912
913 ctx = repo[opts.get('rev')]
913 ctx = repo[opts.get('rev')]
914 m = cmdutil.match(repo, (file1,) + pats, opts)
914 m = cmdutil.match(repo, (file1,) + pats, opts)
915 for abs in repo.walk(m, ctx.node()):
915 for abs in repo.walk(m, ctx.node()):
916 fctx = ctx.filectx(abs)
916 fctx = ctx.filectx(abs)
917 o = fctx.filelog().renamed(fctx.filenode())
917 o = fctx.filelog().renamed(fctx.filenode())
918 rel = m.rel(abs)
918 rel = m.rel(abs)
919 if o:
919 if o:
920 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
920 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
921 else:
921 else:
922 ui.write(_("%s not renamed\n") % rel)
922 ui.write(_("%s not renamed\n") % rel)
923
923
924 def debugwalk(ui, repo, *pats, **opts):
924 def debugwalk(ui, repo, *pats, **opts):
925 """show how files match on given patterns"""
925 """show how files match on given patterns"""
926 m = cmdutil.match(repo, pats, opts)
926 m = cmdutil.match(repo, pats, opts)
927 items = list(repo.walk(m))
927 items = list(repo.walk(m))
928 if not items:
928 if not items:
929 return
929 return
930 fmt = 'f %%-%ds %%-%ds %%s' % (
930 fmt = 'f %%-%ds %%-%ds %%s' % (
931 max([len(abs) for abs in items]),
931 max([len(abs) for abs in items]),
932 max([len(m.rel(abs)) for abs in items]))
932 max([len(m.rel(abs)) for abs in items]))
933 for abs in items:
933 for abs in items:
934 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
934 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
935 ui.write("%s\n" % line.rstrip())
935 ui.write("%s\n" % line.rstrip())
936
936
937 def diff(ui, repo, *pats, **opts):
937 def diff(ui, repo, *pats, **opts):
938 """diff repository (or selected files)
938 """diff repository (or selected files)
939
939
940 Show differences between revisions for the specified files.
940 Show differences between revisions for the specified files.
941
941
942 Differences between files are shown using the unified diff format.
942 Differences between files are shown using the unified diff format.
943
943
944 NOTE: diff may generate unexpected results for merges, as it will
944 NOTE: diff may generate unexpected results for merges, as it will
945 default to comparing against the working directory's first parent
945 default to comparing against the working directory's first parent
946 changeset if no revisions are specified.
946 changeset if no revisions are specified.
947
947
948 When two revision arguments are given, then changes are shown
948 When two revision arguments are given, then changes are shown
949 between those revisions. If only one revision is specified then
949 between those revisions. If only one revision is specified then
950 that revision is compared to the working directory, and, when no
950 that revision is compared to the working directory, and, when no
951 revisions are specified, the working directory files are compared
951 revisions are specified, the working directory files are compared
952 to its parent.
952 to its parent.
953
953
954 Without the -a option, diff will avoid generating diffs of files
954 Without the -a option, diff will avoid generating diffs of files
955 it detects as binary. With -a, diff will generate a diff anyway,
955 it detects as binary. With -a, diff will generate a diff anyway,
956 probably with undesirable results.
956 probably with undesirable results.
957 """
957 """
958 node1, node2 = cmdutil.revpair(repo, opts['rev'])
958 node1, node2 = cmdutil.revpair(repo, opts['rev'])
959
959
960 m = cmdutil.match(repo, pats, opts)
960 m = cmdutil.match(repo, pats, opts)
961 patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts))
961 patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts))
962
962
963 def export(ui, repo, *changesets, **opts):
963 def export(ui, repo, *changesets, **opts):
964 """dump the header and diffs for one or more changesets
964 """dump the header and diffs for one or more changesets
965
965
966 Print the changeset header and diffs for one or more revisions.
966 Print the changeset header and diffs for one or more revisions.
967
967
968 The information shown in the changeset header is: author,
968 The information shown in the changeset header is: author,
969 changeset hash, parent(s) and commit comment.
969 changeset hash, parent(s) and commit comment.
970
970
971 NOTE: export may generate unexpected diff output for merge changesets,
971 NOTE: export may generate unexpected diff output for merge changesets,
972 as it will compare the merge changeset against its first parent only.
972 as it will compare the merge changeset against its first parent only.
973
973
974 Output may be to a file, in which case the name of the file is
974 Output may be to a file, in which case the name of the file is
975 given using a format string. The formatting rules are as follows:
975 given using a format string. The formatting rules are as follows:
976
976
977 %% literal "%" character
977 %% literal "%" character
978 %H changeset hash (40 bytes of hexadecimal)
978 %H changeset hash (40 bytes of hexadecimal)
979 %N number of patches being generated
979 %N number of patches being generated
980 %R changeset revision number
980 %R changeset revision number
981 %b basename of the exporting repository
981 %b basename of the exporting repository
982 %h short-form changeset hash (12 bytes of hexadecimal)
982 %h short-form changeset hash (12 bytes of hexadecimal)
983 %n zero-padded sequence number, starting at 1
983 %n zero-padded sequence number, starting at 1
984 %r zero-padded changeset revision number
984 %r zero-padded changeset revision number
985
985
986 Without the -a option, export will avoid generating diffs of files
986 Without the -a option, export will avoid generating diffs of files
987 it detects as binary. With -a, export will generate a diff anyway,
987 it detects as binary. With -a, export will generate a diff anyway,
988 probably with undesirable results.
988 probably with undesirable results.
989
989
990 With the --switch-parent option, the diff will be against the second
990 With the --switch-parent option, the diff will be against the second
991 parent. It can be useful to review a merge.
991 parent. It can be useful to review a merge.
992 """
992 """
993 if not changesets:
993 if not changesets:
994 raise util.Abort(_("export requires at least one changeset"))
994 raise util.Abort(_("export requires at least one changeset"))
995 revs = cmdutil.revrange(repo, changesets)
995 revs = cmdutil.revrange(repo, changesets)
996 if len(revs) > 1:
996 if len(revs) > 1:
997 ui.note(_('exporting patches:\n'))
997 ui.note(_('exporting patches:\n'))
998 else:
998 else:
999 ui.note(_('exporting patch:\n'))
999 ui.note(_('exporting patch:\n'))
1000 patch.export(repo, revs, template=opts['output'],
1000 patch.export(repo, revs, template=opts['output'],
1001 switch_parent=opts['switch_parent'],
1001 switch_parent=opts['switch_parent'],
1002 opts=patch.diffopts(ui, opts))
1002 opts=patch.diffopts(ui, opts))
1003
1003
1004 def grep(ui, repo, pattern, *pats, **opts):
1004 def grep(ui, repo, pattern, *pats, **opts):
1005 """search for a pattern in specified files and revisions
1005 """search for a pattern in specified files and revisions
1006
1006
1007 Search revisions of files for a regular expression.
1007 Search revisions of files for a regular expression.
1008
1008
1009 This command behaves differently than Unix grep. It only accepts
1009 This command behaves differently than Unix grep. It only accepts
1010 Python/Perl regexps. It searches repository history, not the
1010 Python/Perl regexps. It searches repository history, not the
1011 working directory. It always prints the revision number in which
1011 working directory. It always prints the revision number in which
1012 a match appears.
1012 a match appears.
1013
1013
1014 By default, grep only prints output for the first revision of a
1014 By default, grep only prints output for the first revision of a
1015 file in which it finds a match. To get it to print every revision
1015 file in which it finds a match. To get it to print every revision
1016 that contains a change in match status ("-" for a match that
1016 that contains a change in match status ("-" for a match that
1017 becomes a non-match, or "+" for a non-match that becomes a match),
1017 becomes a non-match, or "+" for a non-match that becomes a match),
1018 use the --all flag.
1018 use the --all flag.
1019 """
1019 """
1020 reflags = 0
1020 reflags = 0
1021 if opts['ignore_case']:
1021 if opts['ignore_case']:
1022 reflags |= re.I
1022 reflags |= re.I
1023 try:
1023 try:
1024 regexp = re.compile(pattern, reflags)
1024 regexp = re.compile(pattern, reflags)
1025 except Exception, inst:
1025 except Exception, inst:
1026 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1026 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1027 return None
1027 return None
1028 sep, eol = ':', '\n'
1028 sep, eol = ':', '\n'
1029 if opts['print0']:
1029 if opts['print0']:
1030 sep = eol = '\0'
1030 sep = eol = '\0'
1031
1031
1032 fcache = {}
1032 fcache = {}
1033 def getfile(fn):
1033 def getfile(fn):
1034 if fn not in fcache:
1034 if fn not in fcache:
1035 fcache[fn] = repo.file(fn)
1035 fcache[fn] = repo.file(fn)
1036 return fcache[fn]
1036 return fcache[fn]
1037
1037
1038 def matchlines(body):
1038 def matchlines(body):
1039 begin = 0
1039 begin = 0
1040 linenum = 0
1040 linenum = 0
1041 while True:
1041 while True:
1042 match = regexp.search(body, begin)
1042 match = regexp.search(body, begin)
1043 if not match:
1043 if not match:
1044 break
1044 break
1045 mstart, mend = match.span()
1045 mstart, mend = match.span()
1046 linenum += body.count('\n', begin, mstart) + 1
1046 linenum += body.count('\n', begin, mstart) + 1
1047 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1047 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1048 lend = body.find('\n', mend)
1048 lend = body.find('\n', mend)
1049 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1049 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1050 begin = lend + 1
1050 begin = lend + 1
1051
1051
1052 class linestate(object):
1052 class linestate(object):
1053 def __init__(self, line, linenum, colstart, colend):
1053 def __init__(self, line, linenum, colstart, colend):
1054 self.line = line
1054 self.line = line
1055 self.linenum = linenum
1055 self.linenum = linenum
1056 self.colstart = colstart
1056 self.colstart = colstart
1057 self.colend = colend
1057 self.colend = colend
1058
1058
1059 def __hash__(self):
1059 def __hash__(self):
1060 return hash((self.linenum, self.line))
1060 return hash((self.linenum, self.line))
1061
1061
1062 def __eq__(self, other):
1062 def __eq__(self, other):
1063 return self.line == other.line
1063 return self.line == other.line
1064
1064
1065 matches = {}
1065 matches = {}
1066 copies = {}
1066 copies = {}
1067 def grepbody(fn, rev, body):
1067 def grepbody(fn, rev, body):
1068 matches[rev].setdefault(fn, [])
1068 matches[rev].setdefault(fn, [])
1069 m = matches[rev][fn]
1069 m = matches[rev][fn]
1070 for lnum, cstart, cend, line in matchlines(body):
1070 for lnum, cstart, cend, line in matchlines(body):
1071 s = linestate(line, lnum, cstart, cend)
1071 s = linestate(line, lnum, cstart, cend)
1072 m.append(s)
1072 m.append(s)
1073
1073
1074 def difflinestates(a, b):
1074 def difflinestates(a, b):
1075 sm = difflib.SequenceMatcher(None, a, b)
1075 sm = difflib.SequenceMatcher(None, a, b)
1076 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1076 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1077 if tag == 'insert':
1077 if tag == 'insert':
1078 for i in xrange(blo, bhi):
1078 for i in xrange(blo, bhi):
1079 yield ('+', b[i])
1079 yield ('+', b[i])
1080 elif tag == 'delete':
1080 elif tag == 'delete':
1081 for i in xrange(alo, ahi):
1081 for i in xrange(alo, ahi):
1082 yield ('-', a[i])
1082 yield ('-', a[i])
1083 elif tag == 'replace':
1083 elif tag == 'replace':
1084 for i in xrange(alo, ahi):
1084 for i in xrange(alo, ahi):
1085 yield ('-', a[i])
1085 yield ('-', a[i])
1086 for i in xrange(blo, bhi):
1086 for i in xrange(blo, bhi):
1087 yield ('+', b[i])
1087 yield ('+', b[i])
1088
1088
1089 prev = {}
1089 prev = {}
1090 def display(fn, rev, states, prevstates):
1090 def display(fn, rev, states, prevstates):
1091 datefunc = ui.quiet and util.shortdate or util.datestr
1091 datefunc = ui.quiet and util.shortdate or util.datestr
1092 found = False
1092 found = False
1093 filerevmatches = {}
1093 filerevmatches = {}
1094 r = prev.get(fn, -1)
1094 r = prev.get(fn, -1)
1095 if opts['all']:
1095 if opts['all']:
1096 iter = difflinestates(states, prevstates)
1096 iter = difflinestates(states, prevstates)
1097 else:
1097 else:
1098 iter = [('', l) for l in prevstates]
1098 iter = [('', l) for l in prevstates]
1099 for change, l in iter:
1099 for change, l in iter:
1100 cols = [fn, str(r)]
1100 cols = [fn, str(r)]
1101 if opts['line_number']:
1101 if opts['line_number']:
1102 cols.append(str(l.linenum))
1102 cols.append(str(l.linenum))
1103 if opts['all']:
1103 if opts['all']:
1104 cols.append(change)
1104 cols.append(change)
1105 if opts['user']:
1105 if opts['user']:
1106 cols.append(ui.shortuser(get(r)[1]))
1106 cols.append(ui.shortuser(get(r)[1]))
1107 if opts.get('date'):
1107 if opts.get('date'):
1108 cols.append(datefunc(get(r)[2]))
1108 cols.append(datefunc(get(r)[2]))
1109 if opts['files_with_matches']:
1109 if opts['files_with_matches']:
1110 c = (fn, r)
1110 c = (fn, r)
1111 if c in filerevmatches:
1111 if c in filerevmatches:
1112 continue
1112 continue
1113 filerevmatches[c] = 1
1113 filerevmatches[c] = 1
1114 else:
1114 else:
1115 cols.append(l.line)
1115 cols.append(l.line)
1116 ui.write(sep.join(cols), eol)
1116 ui.write(sep.join(cols), eol)
1117 found = True
1117 found = True
1118 return found
1118 return found
1119
1119
1120 fstate = {}
1120 fstate = {}
1121 skip = {}
1121 skip = {}
1122 get = util.cachefunc(lambda r: repo[r].changeset())
1122 get = util.cachefunc(lambda r: repo[r].changeset())
1123 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1123 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1124 found = False
1124 found = False
1125 follow = opts.get('follow')
1125 follow = opts.get('follow')
1126 for st, rev, fns in changeiter:
1126 for st, rev, fns in changeiter:
1127 if st == 'window':
1127 if st == 'window':
1128 matches.clear()
1128 matches.clear()
1129 elif st == 'add':
1129 elif st == 'add':
1130 ctx = repo[rev]
1130 ctx = repo[rev]
1131 matches[rev] = {}
1131 matches[rev] = {}
1132 for fn in fns:
1132 for fn in fns:
1133 if fn in skip:
1133 if fn in skip:
1134 continue
1134 continue
1135 try:
1135 try:
1136 grepbody(fn, rev, getfile(fn).read(ctx.filenode(fn)))
1136 grepbody(fn, rev, getfile(fn).read(ctx.filenode(fn)))
1137 fstate.setdefault(fn, [])
1137 fstate.setdefault(fn, [])
1138 if follow:
1138 if follow:
1139 copied = getfile(fn).renamed(ctx.filenode(fn))
1139 copied = getfile(fn).renamed(ctx.filenode(fn))
1140 if copied:
1140 if copied:
1141 copies.setdefault(rev, {})[fn] = copied[0]
1141 copies.setdefault(rev, {})[fn] = copied[0]
1142 except revlog.LookupError:
1142 except revlog.LookupError:
1143 pass
1143 pass
1144 elif st == 'iter':
1144 elif st == 'iter':
1145 states = matches[rev].items()
1145 states = matches[rev].items()
1146 states.sort()
1146 states.sort()
1147 for fn, m in states:
1147 for fn, m in states:
1148 copy = copies.get(rev, {}).get(fn)
1148 copy = copies.get(rev, {}).get(fn)
1149 if fn in skip:
1149 if fn in skip:
1150 if copy:
1150 if copy:
1151 skip[copy] = True
1151 skip[copy] = True
1152 continue
1152 continue
1153 if fn in prev or fstate[fn]:
1153 if fn in prev or fstate[fn]:
1154 r = display(fn, rev, m, fstate[fn])
1154 r = display(fn, rev, m, fstate[fn])
1155 found = found or r
1155 found = found or r
1156 if r and not opts['all']:
1156 if r and not opts['all']:
1157 skip[fn] = True
1157 skip[fn] = True
1158 if copy:
1158 if copy:
1159 skip[copy] = True
1159 skip[copy] = True
1160 fstate[fn] = m
1160 fstate[fn] = m
1161 if copy:
1161 if copy:
1162 fstate[copy] = m
1162 fstate[copy] = m
1163 prev[fn] = rev
1163 prev[fn] = rev
1164
1164
1165 fstate = fstate.items()
1165 fstate = fstate.items()
1166 fstate.sort()
1166 fstate.sort()
1167 for fn, state in fstate:
1167 for fn, state in fstate:
1168 if fn in skip:
1168 if fn in skip:
1169 continue
1169 continue
1170 if fn not in copies.get(prev[fn], {}):
1170 if fn not in copies.get(prev[fn], {}):
1171 found = display(fn, rev, {}, state) or found
1171 found = display(fn, rev, {}, state) or found
1172 return (not found and 1) or 0
1172 return (not found and 1) or 0
1173
1173
1174 def heads(ui, repo, *branchrevs, **opts):
1174 def heads(ui, repo, *branchrevs, **opts):
1175 """show current repository heads or show branch heads
1175 """show current repository heads or show branch heads
1176
1176
1177 With no arguments, show all repository head changesets.
1177 With no arguments, show all repository head changesets.
1178
1178
1179 If branch or revisions names are given this will show the heads of
1179 If branch or revisions names are given this will show the heads of
1180 the specified branches or the branches those revisions are tagged
1180 the specified branches or the branches those revisions are tagged
1181 with.
1181 with.
1182
1182
1183 Repository "heads" are changesets that don't have child
1183 Repository "heads" are changesets that don't have child
1184 changesets. They are where development generally takes place and
1184 changesets. They are where development generally takes place and
1185 are the usual targets for update and merge operations.
1185 are the usual targets for update and merge operations.
1186
1186
1187 Branch heads are changesets that have a given branch tag, but have
1187 Branch heads are changesets that have a given branch tag, but have
1188 no child changesets with that tag. They are usually where
1188 no child changesets with that tag. They are usually where
1189 development on the given branch takes place.
1189 development on the given branch takes place.
1190 """
1190 """
1191 if opts['rev']:
1191 if opts['rev']:
1192 start = repo.lookup(opts['rev'])
1192 start = repo.lookup(opts['rev'])
1193 else:
1193 else:
1194 start = None
1194 start = None
1195 if not branchrevs:
1195 if not branchrevs:
1196 # Assume we're looking repo-wide heads if no revs were specified.
1196 # Assume we're looking repo-wide heads if no revs were specified.
1197 heads = repo.heads(start)
1197 heads = repo.heads(start)
1198 else:
1198 else:
1199 heads = []
1199 heads = []
1200 visitedset = util.set()
1200 visitedset = util.set()
1201 for branchrev in branchrevs:
1201 for branchrev in branchrevs:
1202 branch = repo[branchrev].branch()
1202 branch = repo[branchrev].branch()
1203 if branch in visitedset:
1203 if branch in visitedset:
1204 continue
1204 continue
1205 visitedset.add(branch)
1205 visitedset.add(branch)
1206 bheads = repo.branchheads(branch, start)
1206 bheads = repo.branchheads(branch, start)
1207 if not bheads:
1207 if not bheads:
1208 if branch != branchrev:
1208 if branch != branchrev:
1209 ui.warn(_("no changes on branch %s containing %s are "
1209 ui.warn(_("no changes on branch %s containing %s are "
1210 "reachable from %s\n")
1210 "reachable from %s\n")
1211 % (branch, branchrev, opts['rev']))
1211 % (branch, branchrev, opts['rev']))
1212 else:
1212 else:
1213 ui.warn(_("no changes on branch %s are reachable from %s\n")
1213 ui.warn(_("no changes on branch %s are reachable from %s\n")
1214 % (branch, opts['rev']))
1214 % (branch, opts['rev']))
1215 heads.extend(bheads)
1215 heads.extend(bheads)
1216 if not heads:
1216 if not heads:
1217 return 1
1217 return 1
1218 displayer = cmdutil.show_changeset(ui, repo, opts)
1218 displayer = cmdutil.show_changeset(ui, repo, opts)
1219 for n in heads:
1219 for n in heads:
1220 displayer.show(changenode=n)
1220 displayer.show(changenode=n)
1221
1221
1222 def help_(ui, name=None, with_version=False):
1222 def help_(ui, name=None, with_version=False):
1223 """show help for a command, extension, or list of commands
1223 """show help for a command, extension, or list of commands
1224
1224
1225 With no arguments, print a list of commands and short help.
1225 With no arguments, print a list of commands and short help.
1226
1226
1227 Given a command name, print help for that command.
1227 Given a command name, print help for that command.
1228
1228
1229 Given an extension name, print help for that extension, and the
1229 Given an extension name, print help for that extension, and the
1230 commands it provides."""
1230 commands it provides."""
1231 option_lists = []
1231 option_lists = []
1232
1232
1233 def addglobalopts(aliases):
1233 def addglobalopts(aliases):
1234 if ui.verbose:
1234 if ui.verbose:
1235 option_lists.append((_("global options:"), globalopts))
1235 option_lists.append((_("global options:"), globalopts))
1236 if name == 'shortlist':
1236 if name == 'shortlist':
1237 option_lists.append((_('use "hg help" for the full list '
1237 option_lists.append((_('use "hg help" for the full list '
1238 'of commands'), ()))
1238 'of commands'), ()))
1239 else:
1239 else:
1240 if name == 'shortlist':
1240 if name == 'shortlist':
1241 msg = _('use "hg help" for the full list of commands '
1241 msg = _('use "hg help" for the full list of commands '
1242 'or "hg -v" for details')
1242 'or "hg -v" for details')
1243 elif aliases:
1243 elif aliases:
1244 msg = _('use "hg -v help%s" to show aliases and '
1244 msg = _('use "hg -v help%s" to show aliases and '
1245 'global options') % (name and " " + name or "")
1245 'global options') % (name and " " + name or "")
1246 else:
1246 else:
1247 msg = _('use "hg -v help %s" to show global options') % name
1247 msg = _('use "hg -v help %s" to show global options') % name
1248 option_lists.append((msg, ()))
1248 option_lists.append((msg, ()))
1249
1249
1250 def helpcmd(name):
1250 def helpcmd(name):
1251 if with_version:
1251 if with_version:
1252 version_(ui)
1252 version_(ui)
1253 ui.write('\n')
1253 ui.write('\n')
1254
1254
1255 try:
1255 try:
1256 aliases, i = cmdutil.findcmd(ui, name, table)
1256 aliases, i = cmdutil.findcmd(ui, name, table)
1257 except cmdutil.AmbiguousCommand, inst:
1257 except cmdutil.AmbiguousCommand, inst:
1258 select = lambda c: c.lstrip('^').startswith(inst.args[0])
1258 select = lambda c: c.lstrip('^').startswith(inst.args[0])
1259 helplist(_('list of commands:\n\n'), select)
1259 helplist(_('list of commands:\n\n'), select)
1260 return
1260 return
1261
1261
1262 # synopsis
1262 # synopsis
1263 ui.write("%s\n" % i[2])
1263 ui.write("%s\n" % i[2])
1264
1264
1265 # aliases
1265 # aliases
1266 if not ui.quiet and len(aliases) > 1:
1266 if not ui.quiet and len(aliases) > 1:
1267 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1267 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1268
1268
1269 # description
1269 # description
1270 doc = i[0].__doc__
1270 doc = i[0].__doc__
1271 if not doc:
1271 if not doc:
1272 doc = _("(No help text available)")
1272 doc = _("(No help text available)")
1273 if ui.quiet:
1273 if ui.quiet:
1274 doc = doc.splitlines(0)[0]
1274 doc = doc.splitlines(0)[0]
1275 ui.write("\n%s\n" % doc.rstrip())
1275 ui.write("\n%s\n" % doc.rstrip())
1276
1276
1277 if not ui.quiet:
1277 if not ui.quiet:
1278 # options
1278 # options
1279 if i[1]:
1279 if i[1]:
1280 option_lists.append((_("options:\n"), i[1]))
1280 option_lists.append((_("options:\n"), i[1]))
1281
1281
1282 addglobalopts(False)
1282 addglobalopts(False)
1283
1283
1284 def helplist(header, select=None):
1284 def helplist(header, select=None):
1285 h = {}
1285 h = {}
1286 cmds = {}
1286 cmds = {}
1287 for c, e in table.items():
1287 for c, e in table.items():
1288 f = c.split("|", 1)[0]
1288 f = c.split("|", 1)[0]
1289 if select and not select(f):
1289 if select and not select(f):
1290 continue
1290 continue
1291 if name == "shortlist" and not f.startswith("^"):
1291 if name == "shortlist" and not f.startswith("^"):
1292 continue
1292 continue
1293 f = f.lstrip("^")
1293 f = f.lstrip("^")
1294 if not ui.debugflag and f.startswith("debug"):
1294 if not ui.debugflag and f.startswith("debug"):
1295 continue
1295 continue
1296 doc = e[0].__doc__
1296 doc = e[0].__doc__
1297 if not doc:
1297 if not doc:
1298 doc = _("(No help text available)")
1298 doc = _("(No help text available)")
1299 h[f] = doc.splitlines(0)[0].rstrip()
1299 h[f] = doc.splitlines(0)[0].rstrip()
1300 cmds[f] = c.lstrip("^")
1300 cmds[f] = c.lstrip("^")
1301
1301
1302 if not h:
1302 if not h:
1303 ui.status(_('no commands defined\n'))
1303 ui.status(_('no commands defined\n'))
1304 return
1304 return
1305
1305
1306 ui.status(header)
1306 ui.status(header)
1307 fns = h.keys()
1307 fns = h.keys()
1308 fns.sort()
1308 fns.sort()
1309 m = max(map(len, fns))
1309 m = max(map(len, fns))
1310 for f in fns:
1310 for f in fns:
1311 if ui.verbose:
1311 if ui.verbose:
1312 commands = cmds[f].replace("|",", ")
1312 commands = cmds[f].replace("|",", ")
1313 ui.write(" %s:\n %s\n"%(commands, h[f]))
1313 ui.write(" %s:\n %s\n"%(commands, h[f]))
1314 else:
1314 else:
1315 ui.write(' %-*s %s\n' % (m, f, h[f]))
1315 ui.write(' %-*s %s\n' % (m, f, h[f]))
1316
1316
1317 if not ui.quiet:
1317 if not ui.quiet:
1318 addglobalopts(True)
1318 addglobalopts(True)
1319
1319
1320 def helptopic(name):
1320 def helptopic(name):
1321 v = None
1321 v = None
1322 for i, d in help.helptable:
1322 for i, d in help.helptable:
1323 l = i.split('|')
1323 l = i.split('|')
1324 if name in l:
1324 if name in l:
1325 v = i
1325 v = i
1326 header = l[-1]
1326 header = l[-1]
1327 doc = d
1327 doc = d
1328 if not v:
1328 if not v:
1329 raise cmdutil.UnknownCommand(name)
1329 raise cmdutil.UnknownCommand(name)
1330
1330
1331 # description
1331 # description
1332 if not doc:
1332 if not doc:
1333 doc = _("(No help text available)")
1333 doc = _("(No help text available)")
1334 if callable(doc):
1334 if callable(doc):
1335 doc = doc()
1335 doc = doc()
1336
1336
1337 ui.write("%s\n" % header)
1337 ui.write("%s\n" % header)
1338 ui.write("%s\n" % doc.rstrip())
1338 ui.write("%s\n" % doc.rstrip())
1339
1339
1340 def helpext(name):
1340 def helpext(name):
1341 try:
1341 try:
1342 mod = extensions.find(name)
1342 mod = extensions.find(name)
1343 except KeyError:
1343 except KeyError:
1344 raise cmdutil.UnknownCommand(name)
1344 raise cmdutil.UnknownCommand(name)
1345
1345
1346 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1346 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1347 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1347 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1348 for d in doc[1:]:
1348 for d in doc[1:]:
1349 ui.write(d, '\n')
1349 ui.write(d, '\n')
1350
1350
1351 ui.status('\n')
1351 ui.status('\n')
1352
1352
1353 try:
1353 try:
1354 ct = mod.cmdtable
1354 ct = mod.cmdtable
1355 except AttributeError:
1355 except AttributeError:
1356 ct = {}
1356 ct = {}
1357
1357
1358 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1358 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1359 helplist(_('list of commands:\n\n'), modcmds.has_key)
1359 helplist(_('list of commands:\n\n'), modcmds.has_key)
1360
1360
1361 if name and name != 'shortlist':
1361 if name and name != 'shortlist':
1362 i = None
1362 i = None
1363 for f in (helpcmd, helptopic, helpext):
1363 for f in (helpcmd, helptopic, helpext):
1364 try:
1364 try:
1365 f(name)
1365 f(name)
1366 i = None
1366 i = None
1367 break
1367 break
1368 except cmdutil.UnknownCommand, inst:
1368 except cmdutil.UnknownCommand, inst:
1369 i = inst
1369 i = inst
1370 if i:
1370 if i:
1371 raise i
1371 raise i
1372
1372
1373 else:
1373 else:
1374 # program name
1374 # program name
1375 if ui.verbose or with_version:
1375 if ui.verbose or with_version:
1376 version_(ui)
1376 version_(ui)
1377 else:
1377 else:
1378 ui.status(_("Mercurial Distributed SCM\n"))
1378 ui.status(_("Mercurial Distributed SCM\n"))
1379 ui.status('\n')
1379 ui.status('\n')
1380
1380
1381 # list of commands
1381 # list of commands
1382 if name == "shortlist":
1382 if name == "shortlist":
1383 header = _('basic commands:\n\n')
1383 header = _('basic commands:\n\n')
1384 else:
1384 else:
1385 header = _('list of commands:\n\n')
1385 header = _('list of commands:\n\n')
1386
1386
1387 helplist(header)
1387 helplist(header)
1388
1388
1389 # list all option lists
1389 # list all option lists
1390 opt_output = []
1390 opt_output = []
1391 for title, options in option_lists:
1391 for title, options in option_lists:
1392 opt_output.append(("\n%s" % title, None))
1392 opt_output.append(("\n%s" % title, None))
1393 for shortopt, longopt, default, desc in options:
1393 for shortopt, longopt, default, desc in options:
1394 if "DEPRECATED" in desc and not ui.verbose: continue
1394 if "DEPRECATED" in desc and not ui.verbose: continue
1395 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1395 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1396 longopt and " --%s" % longopt),
1396 longopt and " --%s" % longopt),
1397 "%s%s" % (desc,
1397 "%s%s" % (desc,
1398 default
1398 default
1399 and _(" (default: %s)") % default
1399 and _(" (default: %s)") % default
1400 or "")))
1400 or "")))
1401
1401
1402 if ui.verbose:
1402 if ui.verbose:
1403 ui.write(_("\nspecial help topics:\n"))
1403 ui.write(_("\nspecial help topics:\n"))
1404 topics = []
1404 topics = []
1405 for i, d in help.helptable:
1405 for i, d in help.helptable:
1406 l = i.split('|')
1406 l = i.split('|')
1407 topics.append((", ".join(l[:-1]), l[-1]))
1407 topics.append((", ".join(l[:-1]), l[-1]))
1408 topics_len = max([len(s[0]) for s in topics])
1408 topics_len = max([len(s[0]) for s in topics])
1409 for t, desc in topics:
1409 for t, desc in topics:
1410 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1410 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1411
1411
1412 if opt_output:
1412 if opt_output:
1413 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1413 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1414 for first, second in opt_output:
1414 for first, second in opt_output:
1415 if second:
1415 if second:
1416 ui.write(" %-*s %s\n" % (opts_len, first, second))
1416 ui.write(" %-*s %s\n" % (opts_len, first, second))
1417 else:
1417 else:
1418 ui.write("%s\n" % first)
1418 ui.write("%s\n" % first)
1419
1419
1420 def identify(ui, repo, source=None,
1420 def identify(ui, repo, source=None,
1421 rev=None, num=None, id=None, branch=None, tags=None):
1421 rev=None, num=None, id=None, branch=None, tags=None):
1422 """identify the working copy or specified revision
1422 """identify the working copy or specified revision
1423
1423
1424 With no revision, print a summary of the current state of the repo.
1424 With no revision, print a summary of the current state of the repo.
1425
1425
1426 With a path, do a lookup in another repository.
1426 With a path, do a lookup in another repository.
1427
1427
1428 This summary identifies the repository state using one or two parent
1428 This summary identifies the repository state using one or two parent
1429 hash identifiers, followed by a "+" if there are uncommitted changes
1429 hash identifiers, followed by a "+" if there are uncommitted changes
1430 in the working directory, a list of tags for this revision and a branch
1430 in the working directory, a list of tags for this revision and a branch
1431 name for non-default branches.
1431 name for non-default branches.
1432 """
1432 """
1433
1433
1434 if not repo and not source:
1434 if not repo and not source:
1435 raise util.Abort(_("There is no Mercurial repository here "
1435 raise util.Abort(_("There is no Mercurial repository here "
1436 "(.hg not found)"))
1436 "(.hg not found)"))
1437
1437
1438 hexfunc = ui.debugflag and hex or short
1438 hexfunc = ui.debugflag and hex or short
1439 default = not (num or id or branch or tags)
1439 default = not (num or id or branch or tags)
1440 output = []
1440 output = []
1441
1441
1442 if source:
1442 if source:
1443 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1443 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1444 srepo = hg.repository(ui, source)
1444 srepo = hg.repository(ui, source)
1445 if not rev and revs:
1445 if not rev and revs:
1446 rev = revs[0]
1446 rev = revs[0]
1447 if not rev:
1447 if not rev:
1448 rev = "tip"
1448 rev = "tip"
1449 if num or branch or tags:
1449 if num or branch or tags:
1450 raise util.Abort(
1450 raise util.Abort(
1451 "can't query remote revision number, branch, or tags")
1451 "can't query remote revision number, branch, or tags")
1452 output = [hexfunc(srepo.lookup(rev))]
1452 output = [hexfunc(srepo.lookup(rev))]
1453 elif not rev:
1453 elif not rev:
1454 ctx = repo[None]
1454 ctx = repo[None]
1455 parents = ctx.parents()
1455 parents = ctx.parents()
1456 changed = False
1456 changed = False
1457 if default or id or num:
1457 if default or id or num:
1458 changed = ctx.files() + ctx.deleted()
1458 changed = ctx.files() + ctx.deleted()
1459 if default or id:
1459 if default or id:
1460 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1460 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1461 (changed) and "+" or "")]
1461 (changed) and "+" or "")]
1462 if num:
1462 if num:
1463 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1463 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1464 (changed) and "+" or ""))
1464 (changed) and "+" or ""))
1465 else:
1465 else:
1466 ctx = repo[rev]
1466 ctx = repo[rev]
1467 if default or id:
1467 if default or id:
1468 output = [hexfunc(ctx.node())]
1468 output = [hexfunc(ctx.node())]
1469 if num:
1469 if num:
1470 output.append(str(ctx.rev()))
1470 output.append(str(ctx.rev()))
1471
1471
1472 if not source and default and not ui.quiet:
1472 if not source and default and not ui.quiet:
1473 b = util.tolocal(ctx.branch())
1473 b = util.tolocal(ctx.branch())
1474 if b != 'default':
1474 if b != 'default':
1475 output.append("(%s)" % b)
1475 output.append("(%s)" % b)
1476
1476
1477 # multiple tags for a single parent separated by '/'
1477 # multiple tags for a single parent separated by '/'
1478 t = "/".join(ctx.tags())
1478 t = "/".join(ctx.tags())
1479 if t:
1479 if t:
1480 output.append(t)
1480 output.append(t)
1481
1481
1482 if branch:
1482 if branch:
1483 output.append(util.tolocal(ctx.branch()))
1483 output.append(util.tolocal(ctx.branch()))
1484
1484
1485 if tags:
1485 if tags:
1486 output.extend(ctx.tags())
1486 output.extend(ctx.tags())
1487
1487
1488 ui.write("%s\n" % ' '.join(output))
1488 ui.write("%s\n" % ' '.join(output))
1489
1489
1490 def import_(ui, repo, patch1, *patches, **opts):
1490 def import_(ui, repo, patch1, *patches, **opts):
1491 """import an ordered set of patches
1491 """import an ordered set of patches
1492
1492
1493 Import a list of patches and commit them individually.
1493 Import a list of patches and commit them individually.
1494
1494
1495 If there are outstanding changes in the working directory, import
1495 If there are outstanding changes in the working directory, import
1496 will abort unless given the -f flag.
1496 will abort unless given the -f flag.
1497
1497
1498 You can import a patch straight from a mail message. Even patches
1498 You can import a patch straight from a mail message. Even patches
1499 as attachments work (body part must be type text/plain or
1499 as attachments work (body part must be type text/plain or
1500 text/x-patch to be used). From and Subject headers of email
1500 text/x-patch to be used). From and Subject headers of email
1501 message are used as default committer and commit message. All
1501 message are used as default committer and commit message. All
1502 text/plain body parts before first diff are added to commit
1502 text/plain body parts before first diff are added to commit
1503 message.
1503 message.
1504
1504
1505 If the imported patch was generated by hg export, user and description
1505 If the imported patch was generated by hg export, user and description
1506 from patch override values from message headers and body. Values
1506 from patch override values from message headers and body. Values
1507 given on command line with -m and -u override these.
1507 given on command line with -m and -u override these.
1508
1508
1509 If --exact is specified, import will set the working directory
1509 If --exact is specified, import will set the working directory
1510 to the parent of each patch before applying it, and will abort
1510 to the parent of each patch before applying it, and will abort
1511 if the resulting changeset has a different ID than the one
1511 if the resulting changeset has a different ID than the one
1512 recorded in the patch. This may happen due to character set
1512 recorded in the patch. This may happen due to character set
1513 problems or other deficiencies in the text patch format.
1513 problems or other deficiencies in the text patch format.
1514
1514
1515 To read a patch from standard input, use patch name "-".
1515 To read a patch from standard input, use patch name "-".
1516 See 'hg help dates' for a list of formats valid for -d/--date.
1516 See 'hg help dates' for a list of formats valid for -d/--date.
1517 """
1517 """
1518 patches = (patch1,) + patches
1518 patches = (patch1,) + patches
1519
1519
1520 date = opts.get('date')
1520 date = opts.get('date')
1521 if date:
1521 if date:
1522 opts['date'] = util.parsedate(date)
1522 opts['date'] = util.parsedate(date)
1523
1523
1524 if opts.get('exact') or not opts['force']:
1524 if opts.get('exact') or not opts['force']:
1525 cmdutil.bail_if_changed(repo)
1525 cmdutil.bail_if_changed(repo)
1526
1526
1527 d = opts["base"]
1527 d = opts["base"]
1528 strip = opts["strip"]
1528 strip = opts["strip"]
1529 wlock = lock = None
1529 wlock = lock = None
1530 try:
1530 try:
1531 wlock = repo.wlock()
1531 wlock = repo.wlock()
1532 lock = repo.lock()
1532 lock = repo.lock()
1533 for p in patches:
1533 for p in patches:
1534 pf = os.path.join(d, p)
1534 pf = os.path.join(d, p)
1535
1535
1536 if pf == '-':
1536 if pf == '-':
1537 ui.status(_("applying patch from stdin\n"))
1537 ui.status(_("applying patch from stdin\n"))
1538 data = patch.extract(ui, sys.stdin)
1538 data = patch.extract(ui, sys.stdin)
1539 else:
1539 else:
1540 ui.status(_("applying %s\n") % p)
1540 ui.status(_("applying %s\n") % p)
1541 if os.path.exists(pf):
1541 if os.path.exists(pf):
1542 data = patch.extract(ui, file(pf, 'rb'))
1542 data = patch.extract(ui, file(pf, 'rb'))
1543 else:
1543 else:
1544 data = patch.extract(ui, urllib.urlopen(pf))
1544 data = patch.extract(ui, urllib.urlopen(pf))
1545 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1545 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1546
1546
1547 if tmpname is None:
1547 if tmpname is None:
1548 raise util.Abort(_('no diffs found'))
1548 raise util.Abort(_('no diffs found'))
1549
1549
1550 try:
1550 try:
1551 cmdline_message = cmdutil.logmessage(opts)
1551 cmdline_message = cmdutil.logmessage(opts)
1552 if cmdline_message:
1552 if cmdline_message:
1553 # pickup the cmdline msg
1553 # pickup the cmdline msg
1554 message = cmdline_message
1554 message = cmdline_message
1555 elif message:
1555 elif message:
1556 # pickup the patch msg
1556 # pickup the patch msg
1557 message = message.strip()
1557 message = message.strip()
1558 else:
1558 else:
1559 # launch the editor
1559 # launch the editor
1560 message = None
1560 message = None
1561 ui.debug(_('message:\n%s\n') % message)
1561 ui.debug(_('message:\n%s\n') % message)
1562
1562
1563 wp = repo.parents()
1563 wp = repo.parents()
1564 if opts.get('exact'):
1564 if opts.get('exact'):
1565 if not nodeid or not p1:
1565 if not nodeid or not p1:
1566 raise util.Abort(_('not a mercurial patch'))
1566 raise util.Abort(_('not a mercurial patch'))
1567 p1 = repo.lookup(p1)
1567 p1 = repo.lookup(p1)
1568 p2 = repo.lookup(p2 or hex(nullid))
1568 p2 = repo.lookup(p2 or hex(nullid))
1569
1569
1570 if p1 != wp[0].node():
1570 if p1 != wp[0].node():
1571 hg.clean(repo, p1)
1571 hg.clean(repo, p1)
1572 repo.dirstate.setparents(p1, p2)
1572 repo.dirstate.setparents(p1, p2)
1573 elif p2:
1573 elif p2:
1574 try:
1574 try:
1575 p1 = repo.lookup(p1)
1575 p1 = repo.lookup(p1)
1576 p2 = repo.lookup(p2)
1576 p2 = repo.lookup(p2)
1577 if p1 == wp[0].node():
1577 if p1 == wp[0].node():
1578 repo.dirstate.setparents(p1, p2)
1578 repo.dirstate.setparents(p1, p2)
1579 except RepoError:
1579 except RepoError:
1580 pass
1580 pass
1581 if opts.get('exact') or opts.get('import_branch'):
1581 if opts.get('exact') or opts.get('import_branch'):
1582 repo.dirstate.setbranch(branch or 'default')
1582 repo.dirstate.setbranch(branch or 'default')
1583
1583
1584 files = {}
1584 files = {}
1585 try:
1585 try:
1586 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1586 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1587 files=files)
1587 files=files)
1588 finally:
1588 finally:
1589 files = patch.updatedir(ui, repo, files)
1589 files = patch.updatedir(ui, repo, files)
1590 if not opts.get('no_commit'):
1590 if not opts.get('no_commit'):
1591 n = repo.commit(files, message, opts.get('user') or user,
1591 n = repo.commit(files, message, opts.get('user') or user,
1592 opts.get('date') or date)
1592 opts.get('date') or date)
1593 if opts.get('exact'):
1593 if opts.get('exact'):
1594 if hex(n) != nodeid:
1594 if hex(n) != nodeid:
1595 repo.rollback()
1595 repo.rollback()
1596 raise util.Abort(_('patch is damaged'
1596 raise util.Abort(_('patch is damaged'
1597 ' or loses information'))
1597 ' or loses information'))
1598 # Force a dirstate write so that the next transaction
1598 # Force a dirstate write so that the next transaction
1599 # backups an up-do-date file.
1599 # backups an up-do-date file.
1600 repo.dirstate.write()
1600 repo.dirstate.write()
1601 finally:
1601 finally:
1602 os.unlink(tmpname)
1602 os.unlink(tmpname)
1603 finally:
1603 finally:
1604 del lock, wlock
1604 del lock, wlock
1605
1605
1606 def incoming(ui, repo, source="default", **opts):
1606 def incoming(ui, repo, source="default", **opts):
1607 """show new changesets found in source
1607 """show new changesets found in source
1608
1608
1609 Show new changesets found in the specified path/URL or the default
1609 Show new changesets found in the specified path/URL or the default
1610 pull location. These are the changesets that would be pulled if a pull
1610 pull location. These are the changesets that would be pulled if a pull
1611 was requested.
1611 was requested.
1612
1612
1613 For remote repository, using --bundle avoids downloading the changesets
1613 For remote repository, using --bundle avoids downloading the changesets
1614 twice if the incoming is followed by a pull.
1614 twice if the incoming is followed by a pull.
1615
1615
1616 See pull for valid source format details.
1616 See pull for valid source format details.
1617 """
1617 """
1618 limit = cmdutil.loglimit(opts)
1618 limit = cmdutil.loglimit(opts)
1619 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1619 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1620 cmdutil.setremoteconfig(ui, opts)
1620 cmdutil.setremoteconfig(ui, opts)
1621
1621
1622 other = hg.repository(ui, source)
1622 other = hg.repository(ui, source)
1623 ui.status(_('comparing with %s\n') % util.hidepassword(source))
1623 ui.status(_('comparing with %s\n') % util.hidepassword(source))
1624 if revs:
1624 if revs:
1625 revs = [other.lookup(rev) for rev in revs]
1625 revs = [other.lookup(rev) for rev in revs]
1626 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1626 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1627 if not incoming:
1627 if not incoming:
1628 try:
1628 try:
1629 os.unlink(opts["bundle"])
1629 os.unlink(opts["bundle"])
1630 except:
1630 except:
1631 pass
1631 pass
1632 ui.status(_("no changes found\n"))
1632 ui.status(_("no changes found\n"))
1633 return 1
1633 return 1
1634
1634
1635 cleanup = None
1635 cleanup = None
1636 try:
1636 try:
1637 fname = opts["bundle"]
1637 fname = opts["bundle"]
1638 if fname or not other.local():
1638 if fname or not other.local():
1639 # create a bundle (uncompressed if other repo is not local)
1639 # create a bundle (uncompressed if other repo is not local)
1640 if revs is None:
1640 if revs is None:
1641 cg = other.changegroup(incoming, "incoming")
1641 cg = other.changegroup(incoming, "incoming")
1642 else:
1642 else:
1643 cg = other.changegroupsubset(incoming, revs, 'incoming')
1643 cg = other.changegroupsubset(incoming, revs, 'incoming')
1644 bundletype = other.local() and "HG10BZ" or "HG10UN"
1644 bundletype = other.local() and "HG10BZ" or "HG10UN"
1645 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1645 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1646 # keep written bundle?
1646 # keep written bundle?
1647 if opts["bundle"]:
1647 if opts["bundle"]:
1648 cleanup = None
1648 cleanup = None
1649 if not other.local():
1649 if not other.local():
1650 # use the created uncompressed bundlerepo
1650 # use the created uncompressed bundlerepo
1651 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1651 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1652
1652
1653 o = other.changelog.nodesbetween(incoming, revs)[0]
1653 o = other.changelog.nodesbetween(incoming, revs)[0]
1654 if opts['newest_first']:
1654 if opts['newest_first']:
1655 o.reverse()
1655 o.reverse()
1656 displayer = cmdutil.show_changeset(ui, other, opts)
1656 displayer = cmdutil.show_changeset(ui, other, opts)
1657 count = 0
1657 count = 0
1658 for n in o:
1658 for n in o:
1659 if count >= limit:
1659 if count >= limit:
1660 break
1660 break
1661 parents = [p for p in other.changelog.parents(n) if p != nullid]
1661 parents = [p for p in other.changelog.parents(n) if p != nullid]
1662 if opts['no_merges'] and len(parents) == 2:
1662 if opts['no_merges'] and len(parents) == 2:
1663 continue
1663 continue
1664 count += 1
1664 count += 1
1665 displayer.show(changenode=n)
1665 displayer.show(changenode=n)
1666 finally:
1666 finally:
1667 if hasattr(other, 'close'):
1667 if hasattr(other, 'close'):
1668 other.close()
1668 other.close()
1669 if cleanup:
1669 if cleanup:
1670 os.unlink(cleanup)
1670 os.unlink(cleanup)
1671
1671
1672 def init(ui, dest=".", **opts):
1672 def init(ui, dest=".", **opts):
1673 """create a new repository in the given directory
1673 """create a new repository in the given directory
1674
1674
1675 Initialize a new repository in the given directory. If the given
1675 Initialize a new repository in the given directory. If the given
1676 directory does not exist, it is created.
1676 directory does not exist, it is created.
1677
1677
1678 If no directory is given, the current directory is used.
1678 If no directory is given, the current directory is used.
1679
1679
1680 It is possible to specify an ssh:// URL as the destination.
1680 It is possible to specify an ssh:// URL as the destination.
1681 Look at the help text for the pull command for important details
1681 Look at the help text for the pull command for important details
1682 about ssh:// URLs.
1682 about ssh:// URLs.
1683 """
1683 """
1684 cmdutil.setremoteconfig(ui, opts)
1684 cmdutil.setremoteconfig(ui, opts)
1685 hg.repository(ui, dest, create=1)
1685 hg.repository(ui, dest, create=1)
1686
1686
1687 def locate(ui, repo, *pats, **opts):
1687 def locate(ui, repo, *pats, **opts):
1688 """locate files matching specific patterns
1688 """locate files matching specific patterns
1689
1689
1690 Print all files under Mercurial control whose names match the
1690 Print all files under Mercurial control whose names match the
1691 given patterns.
1691 given patterns.
1692
1692
1693 This command searches the entire repository by default. To search
1693 This command searches the entire repository by default. To search
1694 just the current directory and its subdirectories, use
1694 just the current directory and its subdirectories, use
1695 "--include .".
1695 "--include .".
1696
1696
1697 If no patterns are given to match, this command prints all file
1697 If no patterns are given to match, this command prints all file
1698 names.
1698 names.
1699
1699
1700 If you want to feed the output of this command into the "xargs"
1700 If you want to feed the output of this command into the "xargs"
1701 command, use the "-0" option to both this command and "xargs".
1701 command, use the "-0" option to both this command and "xargs".
1702 This will avoid the problem of "xargs" treating single filenames
1702 This will avoid the problem of "xargs" treating single filenames
1703 that contain white space as multiple filenames.
1703 that contain white space as multiple filenames.
1704 """
1704 """
1705 end = opts['print0'] and '\0' or '\n'
1705 end = opts['print0'] and '\0' or '\n'
1706 rev = opts['rev']
1706 rev = opts['rev']
1707 if rev:
1707 if rev:
1708 node = repo.lookup(rev)
1708 node = repo.lookup(rev)
1709 else:
1709 else:
1710 node = None
1710 node = None
1711
1711
1712 ret = 1
1712 ret = 1
1713 m = cmdutil.match(repo, pats, opts, default='relglob')
1713 m = cmdutil.match(repo, pats, opts, default='relglob')
1714 m.bad = lambda x,y: False
1714 m.bad = lambda x,y: False
1715 for abs in repo.walk(m, node):
1715 for abs in repo.walk(m, node):
1716 if not node and abs not in repo.dirstate:
1716 if not node and abs not in repo.dirstate:
1717 continue
1717 continue
1718 if opts['fullpath']:
1718 if opts['fullpath']:
1719 ui.write(os.path.join(repo.root, abs), end)
1719 ui.write(os.path.join(repo.root, abs), end)
1720 else:
1720 else:
1721 ui.write(((pats and m.rel(abs)) or abs), end)
1721 ui.write(((pats and m.rel(abs)) or abs), end)
1722 ret = 0
1722 ret = 0
1723
1723
1724 return ret
1724 return ret
1725
1725
1726 def log(ui, repo, *pats, **opts):
1726 def log(ui, repo, *pats, **opts):
1727 """show revision history of entire repository or files
1727 """show revision history of entire repository or files
1728
1728
1729 Print the revision history of the specified files or the entire
1729 Print the revision history of the specified files or the entire
1730 project.
1730 project.
1731
1731
1732 File history is shown without following rename or copy history of
1732 File history is shown without following rename or copy history of
1733 files. Use -f/--follow with a file name to follow history across
1733 files. Use -f/--follow with a file name to follow history across
1734 renames and copies. --follow without a file name will only show
1734 renames and copies. --follow without a file name will only show
1735 ancestors or descendants of the starting revision. --follow-first
1735 ancestors or descendants of the starting revision. --follow-first
1736 only follows the first parent of merge revisions.
1736 only follows the first parent of merge revisions.
1737
1737
1738 If no revision range is specified, the default is tip:0 unless
1738 If no revision range is specified, the default is tip:0 unless
1739 --follow is set, in which case the working directory parent is
1739 --follow is set, in which case the working directory parent is
1740 used as the starting revision.
1740 used as the starting revision.
1741
1741
1742 See 'hg help dates' for a list of formats valid for -d/--date.
1742 See 'hg help dates' for a list of formats valid for -d/--date.
1743
1743
1744 By default this command outputs: changeset id and hash, tags,
1744 By default this command outputs: changeset id and hash, tags,
1745 non-trivial parents, user, date and time, and a summary for each
1745 non-trivial parents, user, date and time, and a summary for each
1746 commit. When the -v/--verbose switch is used, the list of changed
1746 commit. When the -v/--verbose switch is used, the list of changed
1747 files and full commit message is shown.
1747 files and full commit message is shown.
1748
1748
1749 NOTE: log -p may generate unexpected diff output for merge
1749 NOTE: log -p may generate unexpected diff output for merge
1750 changesets, as it will compare the merge changeset against its
1750 changesets, as it will compare the merge changeset against its
1751 first parent only. Also, the files: list will only reflect files
1751 first parent only. Also, the files: list will only reflect files
1752 that are different from BOTH parents.
1752 that are different from BOTH parents.
1753
1753
1754 """
1754 """
1755
1755
1756 get = util.cachefunc(lambda r: repo[r].changeset())
1756 get = util.cachefunc(lambda r: repo[r].changeset())
1757 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1757 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1758
1758
1759 limit = cmdutil.loglimit(opts)
1759 limit = cmdutil.loglimit(opts)
1760 count = 0
1760 count = 0
1761
1761
1762 if opts['copies'] and opts['rev']:
1762 if opts['copies'] and opts['rev']:
1763 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1763 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1764 else:
1764 else:
1765 endrev = repo.changelog.count()
1765 endrev = repo.changelog.count()
1766 rcache = {}
1766 rcache = {}
1767 ncache = {}
1767 ncache = {}
1768 def getrenamed(fn, rev):
1768 def getrenamed(fn, rev):
1769 '''looks up all renames for a file (up to endrev) the first
1769 '''looks up all renames for a file (up to endrev) the first
1770 time the file is given. It indexes on the changerev and only
1770 time the file is given. It indexes on the changerev and only
1771 parses the manifest if linkrev != changerev.
1771 parses the manifest if linkrev != changerev.
1772 Returns rename info for fn at changerev rev.'''
1772 Returns rename info for fn at changerev rev.'''
1773 if fn not in rcache:
1773 if fn not in rcache:
1774 rcache[fn] = {}
1774 rcache[fn] = {}
1775 ncache[fn] = {}
1775 ncache[fn] = {}
1776 fl = repo.file(fn)
1776 fl = repo.file(fn)
1777 for i in xrange(fl.count()):
1777 for i in xrange(fl.count()):
1778 node = fl.node(i)
1778 node = fl.node(i)
1779 lr = fl.linkrev(node)
1779 lr = fl.linkrev(node)
1780 renamed = fl.renamed(node)
1780 renamed = fl.renamed(node)
1781 rcache[fn][lr] = renamed
1781 rcache[fn][lr] = renamed
1782 if renamed:
1782 if renamed:
1783 ncache[fn][node] = renamed
1783 ncache[fn][node] = renamed
1784 if lr >= endrev:
1784 if lr >= endrev:
1785 break
1785 break
1786 if rev in rcache[fn]:
1786 if rev in rcache[fn]:
1787 return rcache[fn][rev]
1787 return rcache[fn][rev]
1788
1788
1789 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1789 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1790 # filectx logic.
1790 # filectx logic.
1791
1791
1792 try:
1792 try:
1793 return repo[rev][fn].renamed()
1793 return repo[rev][fn].renamed()
1794 except revlog.LookupError:
1794 except revlog.LookupError:
1795 pass
1795 pass
1796 return None
1796 return None
1797
1797
1798 df = False
1798 df = False
1799 if opts["date"]:
1799 if opts["date"]:
1800 df = util.matchdate(opts["date"])
1800 df = util.matchdate(opts["date"])
1801
1801
1802 only_branches = opts['only_branch']
1802 only_branches = opts['only_branch']
1803
1803
1804 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1804 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1805 for st, rev, fns in changeiter:
1805 for st, rev, fns in changeiter:
1806 if st == 'add':
1806 if st == 'add':
1807 changenode = repo.changelog.node(rev)
1807 changenode = repo.changelog.node(rev)
1808 parents = [p for p in repo.changelog.parentrevs(rev)
1808 parents = [p for p in repo.changelog.parentrevs(rev)
1809 if p != nullrev]
1809 if p != nullrev]
1810 if opts['no_merges'] and len(parents) == 2:
1810 if opts['no_merges'] and len(parents) == 2:
1811 continue
1811 continue
1812 if opts['only_merges'] and len(parents) != 2:
1812 if opts['only_merges'] and len(parents) != 2:
1813 continue
1813 continue
1814
1814
1815 if only_branches:
1815 if only_branches:
1816 revbranch = get(rev)[5]['branch']
1816 revbranch = get(rev)[5]['branch']
1817 if revbranch not in only_branches:
1817 if revbranch not in only_branches:
1818 continue
1818 continue
1819
1819
1820 if df:
1820 if df:
1821 changes = get(rev)
1821 changes = get(rev)
1822 if not df(changes[2][0]):
1822 if not df(changes[2][0]):
1823 continue
1823 continue
1824
1824
1825 if opts['keyword']:
1825 if opts['keyword']:
1826 changes = get(rev)
1826 changes = get(rev)
1827 miss = 0
1827 miss = 0
1828 for k in [kw.lower() for kw in opts['keyword']]:
1828 for k in [kw.lower() for kw in opts['keyword']]:
1829 if not (k in changes[1].lower() or
1829 if not (k in changes[1].lower() or
1830 k in changes[4].lower() or
1830 k in changes[4].lower() or
1831 k in " ".join(changes[3]).lower()):
1831 k in " ".join(changes[3]).lower()):
1832 miss = 1
1832 miss = 1
1833 break
1833 break
1834 if miss:
1834 if miss:
1835 continue
1835 continue
1836
1836
1837 copies = []
1837 copies = []
1838 if opts.get('copies') and rev:
1838 if opts.get('copies') and rev:
1839 for fn in get(rev)[3]:
1839 for fn in get(rev)[3]:
1840 rename = getrenamed(fn, rev)
1840 rename = getrenamed(fn, rev)
1841 if rename:
1841 if rename:
1842 copies.append((fn, rename[0]))
1842 copies.append((fn, rename[0]))
1843 displayer.show(rev, changenode, copies=copies)
1843 displayer.show(rev, changenode, copies=copies)
1844 elif st == 'iter':
1844 elif st == 'iter':
1845 if count == limit: break
1845 if count == limit: break
1846 if displayer.flush(rev):
1846 if displayer.flush(rev):
1847 count += 1
1847 count += 1
1848
1848
1849 def manifest(ui, repo, node=None, rev=None):
1849 def manifest(ui, repo, node=None, rev=None):
1850 """output the current or given revision of the project manifest
1850 """output the current or given revision of the project manifest
1851
1851
1852 Print a list of version controlled files for the given revision.
1852 Print a list of version controlled files for the given revision.
1853 If no revision is given, the parent of the working directory is used,
1853 If no revision is given, the parent of the working directory is used,
1854 or tip if no revision is checked out.
1854 or tip if no revision is checked out.
1855
1855
1856 The manifest is the list of files being version controlled. If no revision
1856 The manifest is the list of files being version controlled. If no revision
1857 is given then the first parent of the working directory is used.
1857 is given then the first parent of the working directory is used.
1858
1858
1859 With -v flag, print file permissions, symlink and executable bits. With
1859 With -v flag, print file permissions, symlink and executable bits. With
1860 --debug flag, print file revision hashes.
1860 --debug flag, print file revision hashes.
1861 """
1861 """
1862
1862
1863 if rev and node:
1863 if rev and node:
1864 raise util.Abort(_("please specify just one revision"))
1864 raise util.Abort(_("please specify just one revision"))
1865
1865
1866 if not node:
1866 if not node:
1867 node = rev
1867 node = rev
1868
1868
1869 m = repo[node].manifest()
1869 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
1870 files = m.keys()
1870 ctx = repo[node]
1871 files.sort()
1871 for f in ctx:
1872
1873 for f in files:
1874 if ui.debugflag:
1872 if ui.debugflag:
1875 ui.write("%40s " % hex(m[f]))
1873 ui.write("%40s " % hex(ctx.manifest()[f]))
1876 if ui.verbose:
1874 if ui.verbose:
1877 type = m.execf(f) and "*" or m.linkf(f) and "@" or " "
1875 ui.write(decor[ctx.flags(f)])
1878 perm = m.execf(f) and "755" or "644"
1879 ui.write("%3s %1s " % (perm, type))
1880 ui.write("%s\n" % f)
1876 ui.write("%s\n" % f)
1881
1877
1882 def merge(ui, repo, node=None, force=None, rev=None):
1878 def merge(ui, repo, node=None, force=None, rev=None):
1883 """merge working directory with another revision
1879 """merge working directory with another revision
1884
1880
1885 Merge the contents of the current working directory and the
1881 Merge the contents of the current working directory and the
1886 requested revision. Files that changed between either parent are
1882 requested revision. Files that changed between either parent are
1887 marked as changed for the next commit and a commit must be
1883 marked as changed for the next commit and a commit must be
1888 performed before any further updates are allowed.
1884 performed before any further updates are allowed.
1889
1885
1890 If no revision is specified, the working directory's parent is a
1886 If no revision is specified, the working directory's parent is a
1891 head revision, and the current branch contains exactly one other head,
1887 head revision, and the current branch contains exactly one other head,
1892 the other head is merged with by default. Otherwise, an explicit
1888 the other head is merged with by default. Otherwise, an explicit
1893 revision to merge with must be provided.
1889 revision to merge with must be provided.
1894 """
1890 """
1895
1891
1896 if rev and node:
1892 if rev and node:
1897 raise util.Abort(_("please specify just one revision"))
1893 raise util.Abort(_("please specify just one revision"))
1898 if not node:
1894 if not node:
1899 node = rev
1895 node = rev
1900
1896
1901 if not node:
1897 if not node:
1902 branch = repo.changectx(None).branch()
1898 branch = repo.changectx(None).branch()
1903 bheads = repo.branchheads()
1899 bheads = repo.branchheads()
1904 if len(bheads) > 2:
1900 if len(bheads) > 2:
1905 raise util.Abort(_("branch '%s' has %d heads - "
1901 raise util.Abort(_("branch '%s' has %d heads - "
1906 "please merge with an explicit rev") %
1902 "please merge with an explicit rev") %
1907 (branch, len(bheads)))
1903 (branch, len(bheads)))
1908
1904
1909 parent = repo.dirstate.parents()[0]
1905 parent = repo.dirstate.parents()[0]
1910 if len(bheads) == 1:
1906 if len(bheads) == 1:
1911 if len(repo.heads()) > 1:
1907 if len(repo.heads()) > 1:
1912 raise util.Abort(_("branch '%s' has one head - "
1908 raise util.Abort(_("branch '%s' has one head - "
1913 "please merge with an explicit rev") %
1909 "please merge with an explicit rev") %
1914 branch)
1910 branch)
1915 msg = _('there is nothing to merge')
1911 msg = _('there is nothing to merge')
1916 if parent != repo.lookup(repo[None].branch()):
1912 if parent != repo.lookup(repo[None].branch()):
1917 msg = _('%s - use "hg update" instead') % msg
1913 msg = _('%s - use "hg update" instead') % msg
1918 raise util.Abort(msg)
1914 raise util.Abort(msg)
1919
1915
1920 if parent not in bheads:
1916 if parent not in bheads:
1921 raise util.Abort(_('working dir not at a head rev - '
1917 raise util.Abort(_('working dir not at a head rev - '
1922 'use "hg update" or merge with an explicit rev'))
1918 'use "hg update" or merge with an explicit rev'))
1923 node = parent == bheads[0] and bheads[-1] or bheads[0]
1919 node = parent == bheads[0] and bheads[-1] or bheads[0]
1924 return hg.merge(repo, node, force=force)
1920 return hg.merge(repo, node, force=force)
1925
1921
1926 def outgoing(ui, repo, dest=None, **opts):
1922 def outgoing(ui, repo, dest=None, **opts):
1927 """show changesets not found in destination
1923 """show changesets not found in destination
1928
1924
1929 Show changesets not found in the specified destination repository or
1925 Show changesets not found in the specified destination repository or
1930 the default push location. These are the changesets that would be pushed
1926 the default push location. These are the changesets that would be pushed
1931 if a push was requested.
1927 if a push was requested.
1932
1928
1933 See pull for valid destination format details.
1929 See pull for valid destination format details.
1934 """
1930 """
1935 limit = cmdutil.loglimit(opts)
1931 limit = cmdutil.loglimit(opts)
1936 dest, revs, checkout = hg.parseurl(
1932 dest, revs, checkout = hg.parseurl(
1937 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1933 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1938 cmdutil.setremoteconfig(ui, opts)
1934 cmdutil.setremoteconfig(ui, opts)
1939 if revs:
1935 if revs:
1940 revs = [repo.lookup(rev) for rev in revs]
1936 revs = [repo.lookup(rev) for rev in revs]
1941
1937
1942 other = hg.repository(ui, dest)
1938 other = hg.repository(ui, dest)
1943 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
1939 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
1944 o = repo.findoutgoing(other, force=opts['force'])
1940 o = repo.findoutgoing(other, force=opts['force'])
1945 if not o:
1941 if not o:
1946 ui.status(_("no changes found\n"))
1942 ui.status(_("no changes found\n"))
1947 return 1
1943 return 1
1948 o = repo.changelog.nodesbetween(o, revs)[0]
1944 o = repo.changelog.nodesbetween(o, revs)[0]
1949 if opts['newest_first']:
1945 if opts['newest_first']:
1950 o.reverse()
1946 o.reverse()
1951 displayer = cmdutil.show_changeset(ui, repo, opts)
1947 displayer = cmdutil.show_changeset(ui, repo, opts)
1952 count = 0
1948 count = 0
1953 for n in o:
1949 for n in o:
1954 if count >= limit:
1950 if count >= limit:
1955 break
1951 break
1956 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1952 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1957 if opts['no_merges'] and len(parents) == 2:
1953 if opts['no_merges'] and len(parents) == 2:
1958 continue
1954 continue
1959 count += 1
1955 count += 1
1960 displayer.show(changenode=n)
1956 displayer.show(changenode=n)
1961
1957
1962 def parents(ui, repo, file_=None, **opts):
1958 def parents(ui, repo, file_=None, **opts):
1963 """show the parents of the working dir or revision
1959 """show the parents of the working dir or revision
1964
1960
1965 Print the working directory's parent revisions. If a
1961 Print the working directory's parent revisions. If a
1966 revision is given via --rev, the parent of that revision
1962 revision is given via --rev, the parent of that revision
1967 will be printed. If a file argument is given, revision in
1963 will be printed. If a file argument is given, revision in
1968 which the file was last changed (before the working directory
1964 which the file was last changed (before the working directory
1969 revision or the argument to --rev if given) is printed.
1965 revision or the argument to --rev if given) is printed.
1970 """
1966 """
1971 rev = opts.get('rev')
1967 rev = opts.get('rev')
1972 if rev:
1968 if rev:
1973 ctx = repo[rev]
1969 ctx = repo[rev]
1974 else:
1970 else:
1975 ctx = repo[None]
1971 ctx = repo[None]
1976
1972
1977 if file_:
1973 if file_:
1978 m = cmdutil.match(repo, (file_,), opts)
1974 m = cmdutil.match(repo, (file_,), opts)
1979 if m.anypats() or len(m.files()) != 1:
1975 if m.anypats() or len(m.files()) != 1:
1980 raise util.Abort(_('can only specify an explicit file name'))
1976 raise util.Abort(_('can only specify an explicit file name'))
1981 file_ = m.files()[0]
1977 file_ = m.files()[0]
1982 filenodes = []
1978 filenodes = []
1983 for cp in ctx.parents():
1979 for cp in ctx.parents():
1984 if not cp:
1980 if not cp:
1985 continue
1981 continue
1986 try:
1982 try:
1987 filenodes.append(cp.filenode(file_))
1983 filenodes.append(cp.filenode(file_))
1988 except revlog.LookupError:
1984 except revlog.LookupError:
1989 pass
1985 pass
1990 if not filenodes:
1986 if not filenodes:
1991 raise util.Abort(_("'%s' not found in manifest!") % file_)
1987 raise util.Abort(_("'%s' not found in manifest!") % file_)
1992 fl = repo.file(file_)
1988 fl = repo.file(file_)
1993 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
1989 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
1994 else:
1990 else:
1995 p = [cp.node() for cp in ctx.parents()]
1991 p = [cp.node() for cp in ctx.parents()]
1996
1992
1997 displayer = cmdutil.show_changeset(ui, repo, opts)
1993 displayer = cmdutil.show_changeset(ui, repo, opts)
1998 for n in p:
1994 for n in p:
1999 if n != nullid:
1995 if n != nullid:
2000 displayer.show(changenode=n)
1996 displayer.show(changenode=n)
2001
1997
2002 def paths(ui, repo, search=None):
1998 def paths(ui, repo, search=None):
2003 """show definition of symbolic path names
1999 """show definition of symbolic path names
2004
2000
2005 Show definition of symbolic path name NAME. If no name is given, show
2001 Show definition of symbolic path name NAME. If no name is given, show
2006 definition of available names.
2002 definition of available names.
2007
2003
2008 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2004 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2009 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2005 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2010 """
2006 """
2011 if search:
2007 if search:
2012 for name, path in ui.configitems("paths"):
2008 for name, path in ui.configitems("paths"):
2013 if name == search:
2009 if name == search:
2014 ui.write("%s\n" % util.hidepassword(path))
2010 ui.write("%s\n" % util.hidepassword(path))
2015 return
2011 return
2016 ui.warn(_("not found!\n"))
2012 ui.warn(_("not found!\n"))
2017 return 1
2013 return 1
2018 else:
2014 else:
2019 for name, path in ui.configitems("paths"):
2015 for name, path in ui.configitems("paths"):
2020 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
2016 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
2021
2017
2022 def postincoming(ui, repo, modheads, optupdate, checkout):
2018 def postincoming(ui, repo, modheads, optupdate, checkout):
2023 if modheads == 0:
2019 if modheads == 0:
2024 return
2020 return
2025 if optupdate:
2021 if optupdate:
2026 if modheads <= 1 or checkout:
2022 if modheads <= 1 or checkout:
2027 return hg.update(repo, checkout)
2023 return hg.update(repo, checkout)
2028 else:
2024 else:
2029 ui.status(_("not updating, since new heads added\n"))
2025 ui.status(_("not updating, since new heads added\n"))
2030 if modheads > 1:
2026 if modheads > 1:
2031 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2027 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2032 else:
2028 else:
2033 ui.status(_("(run 'hg update' to get a working copy)\n"))
2029 ui.status(_("(run 'hg update' to get a working copy)\n"))
2034
2030
2035 def pull(ui, repo, source="default", **opts):
2031 def pull(ui, repo, source="default", **opts):
2036 """pull changes from the specified source
2032 """pull changes from the specified source
2037
2033
2038 Pull changes from a remote repository to a local one.
2034 Pull changes from a remote repository to a local one.
2039
2035
2040 This finds all changes from the repository at the specified path
2036 This finds all changes from the repository at the specified path
2041 or URL and adds them to the local repository. By default, this
2037 or URL and adds them to the local repository. By default, this
2042 does not update the copy of the project in the working directory.
2038 does not update the copy of the project in the working directory.
2043
2039
2044 Valid URLs are of the form:
2040 Valid URLs are of the form:
2045
2041
2046 local/filesystem/path (or file://local/filesystem/path)
2042 local/filesystem/path (or file://local/filesystem/path)
2047 http://[user@]host[:port]/[path]
2043 http://[user@]host[:port]/[path]
2048 https://[user@]host[:port]/[path]
2044 https://[user@]host[:port]/[path]
2049 ssh://[user@]host[:port]/[path]
2045 ssh://[user@]host[:port]/[path]
2050 static-http://host[:port]/[path]
2046 static-http://host[:port]/[path]
2051
2047
2052 Paths in the local filesystem can either point to Mercurial
2048 Paths in the local filesystem can either point to Mercurial
2053 repositories or to bundle files (as created by 'hg bundle' or
2049 repositories or to bundle files (as created by 'hg bundle' or
2054 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2050 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2055 allows access to a Mercurial repository where you simply use a web
2051 allows access to a Mercurial repository where you simply use a web
2056 server to publish the .hg directory as static content.
2052 server to publish the .hg directory as static content.
2057
2053
2058 An optional identifier after # indicates a particular branch, tag,
2054 An optional identifier after # indicates a particular branch, tag,
2059 or changeset to pull.
2055 or changeset to pull.
2060
2056
2061 Some notes about using SSH with Mercurial:
2057 Some notes about using SSH with Mercurial:
2062 - SSH requires an accessible shell account on the destination machine
2058 - SSH requires an accessible shell account on the destination machine
2063 and a copy of hg in the remote path or specified with as remotecmd.
2059 and a copy of hg in the remote path or specified with as remotecmd.
2064 - path is relative to the remote user's home directory by default.
2060 - path is relative to the remote user's home directory by default.
2065 Use an extra slash at the start of a path to specify an absolute path:
2061 Use an extra slash at the start of a path to specify an absolute path:
2066 ssh://example.com//tmp/repository
2062 ssh://example.com//tmp/repository
2067 - Mercurial doesn't use its own compression via SSH; the right thing
2063 - Mercurial doesn't use its own compression via SSH; the right thing
2068 to do is to configure it in your ~/.ssh/config, e.g.:
2064 to do is to configure it in your ~/.ssh/config, e.g.:
2069 Host *.mylocalnetwork.example.com
2065 Host *.mylocalnetwork.example.com
2070 Compression no
2066 Compression no
2071 Host *
2067 Host *
2072 Compression yes
2068 Compression yes
2073 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2069 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2074 with the --ssh command line option.
2070 with the --ssh command line option.
2075 """
2071 """
2076 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2072 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2077 cmdutil.setremoteconfig(ui, opts)
2073 cmdutil.setremoteconfig(ui, opts)
2078
2074
2079 other = hg.repository(ui, source)
2075 other = hg.repository(ui, source)
2080 ui.status(_('pulling from %s\n') % util.hidepassword(source))
2076 ui.status(_('pulling from %s\n') % util.hidepassword(source))
2081 if revs:
2077 if revs:
2082 try:
2078 try:
2083 revs = [other.lookup(rev) for rev in revs]
2079 revs = [other.lookup(rev) for rev in revs]
2084 except NoCapability:
2080 except NoCapability:
2085 error = _("Other repository doesn't support revision lookup, "
2081 error = _("Other repository doesn't support revision lookup, "
2086 "so a rev cannot be specified.")
2082 "so a rev cannot be specified.")
2087 raise util.Abort(error)
2083 raise util.Abort(error)
2088
2084
2089 modheads = repo.pull(other, heads=revs, force=opts['force'])
2085 modheads = repo.pull(other, heads=revs, force=opts['force'])
2090 return postincoming(ui, repo, modheads, opts['update'], checkout)
2086 return postincoming(ui, repo, modheads, opts['update'], checkout)
2091
2087
2092 def push(ui, repo, dest=None, **opts):
2088 def push(ui, repo, dest=None, **opts):
2093 """push changes to the specified destination
2089 """push changes to the specified destination
2094
2090
2095 Push changes from the local repository to the given destination.
2091 Push changes from the local repository to the given destination.
2096
2092
2097 This is the symmetrical operation for pull. It helps to move
2093 This is the symmetrical operation for pull. It helps to move
2098 changes from the current repository to a different one. If the
2094 changes from the current repository to a different one. If the
2099 destination is local this is identical to a pull in that directory
2095 destination is local this is identical to a pull in that directory
2100 from the current one.
2096 from the current one.
2101
2097
2102 By default, push will refuse to run if it detects the result would
2098 By default, push will refuse to run if it detects the result would
2103 increase the number of remote heads. This generally indicates the
2099 increase the number of remote heads. This generally indicates the
2104 the client has forgotten to pull and merge before pushing.
2100 the client has forgotten to pull and merge before pushing.
2105
2101
2106 Valid URLs are of the form:
2102 Valid URLs are of the form:
2107
2103
2108 local/filesystem/path (or file://local/filesystem/path)
2104 local/filesystem/path (or file://local/filesystem/path)
2109 ssh://[user@]host[:port]/[path]
2105 ssh://[user@]host[:port]/[path]
2110 http://[user@]host[:port]/[path]
2106 http://[user@]host[:port]/[path]
2111 https://[user@]host[:port]/[path]
2107 https://[user@]host[:port]/[path]
2112
2108
2113 An optional identifier after # indicates a particular branch, tag,
2109 An optional identifier after # indicates a particular branch, tag,
2114 or changeset to push. If -r is used, the named changeset and all its
2110 or changeset to push. If -r is used, the named changeset and all its
2115 ancestors will be pushed to the remote repository.
2111 ancestors will be pushed to the remote repository.
2116
2112
2117 Look at the help text for the pull command for important details
2113 Look at the help text for the pull command for important details
2118 about ssh:// URLs.
2114 about ssh:// URLs.
2119
2115
2120 Pushing to http:// and https:// URLs is only possible, if this
2116 Pushing to http:// and https:// URLs is only possible, if this
2121 feature is explicitly enabled on the remote Mercurial server.
2117 feature is explicitly enabled on the remote Mercurial server.
2122 """
2118 """
2123 dest, revs, checkout = hg.parseurl(
2119 dest, revs, checkout = hg.parseurl(
2124 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2120 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2125 cmdutil.setremoteconfig(ui, opts)
2121 cmdutil.setremoteconfig(ui, opts)
2126
2122
2127 other = hg.repository(ui, dest)
2123 other = hg.repository(ui, dest)
2128 ui.status('pushing to %s\n' % util.hidepassword(dest))
2124 ui.status('pushing to %s\n' % util.hidepassword(dest))
2129 if revs:
2125 if revs:
2130 revs = [repo.lookup(rev) for rev in revs]
2126 revs = [repo.lookup(rev) for rev in revs]
2131 r = repo.push(other, opts['force'], revs=revs)
2127 r = repo.push(other, opts['force'], revs=revs)
2132 return r == 0
2128 return r == 0
2133
2129
2134 def rawcommit(ui, repo, *pats, **opts):
2130 def rawcommit(ui, repo, *pats, **opts):
2135 """raw commit interface (DEPRECATED)
2131 """raw commit interface (DEPRECATED)
2136
2132
2137 (DEPRECATED)
2133 (DEPRECATED)
2138 Lowlevel commit, for use in helper scripts.
2134 Lowlevel commit, for use in helper scripts.
2139
2135
2140 This command is not intended to be used by normal users, as it is
2136 This command is not intended to be used by normal users, as it is
2141 primarily useful for importing from other SCMs.
2137 primarily useful for importing from other SCMs.
2142
2138
2143 This command is now deprecated and will be removed in a future
2139 This command is now deprecated and will be removed in a future
2144 release, please use debugsetparents and commit instead.
2140 release, please use debugsetparents and commit instead.
2145 """
2141 """
2146
2142
2147 ui.warn(_("(the rawcommit command is deprecated)\n"))
2143 ui.warn(_("(the rawcommit command is deprecated)\n"))
2148
2144
2149 message = cmdutil.logmessage(opts)
2145 message = cmdutil.logmessage(opts)
2150
2146
2151 files = cmdutil.match(repo, pats, opts).files()
2147 files = cmdutil.match(repo, pats, opts).files()
2152 if opts['files']:
2148 if opts['files']:
2153 files += open(opts['files']).read().splitlines()
2149 files += open(opts['files']).read().splitlines()
2154
2150
2155 parents = [repo.lookup(p) for p in opts['parent']]
2151 parents = [repo.lookup(p) for p in opts['parent']]
2156
2152
2157 try:
2153 try:
2158 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2154 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2159 except ValueError, inst:
2155 except ValueError, inst:
2160 raise util.Abort(str(inst))
2156 raise util.Abort(str(inst))
2161
2157
2162 def recover(ui, repo):
2158 def recover(ui, repo):
2163 """roll back an interrupted transaction
2159 """roll back an interrupted transaction
2164
2160
2165 Recover from an interrupted commit or pull.
2161 Recover from an interrupted commit or pull.
2166
2162
2167 This command tries to fix the repository status after an interrupted
2163 This command tries to fix the repository status after an interrupted
2168 operation. It should only be necessary when Mercurial suggests it.
2164 operation. It should only be necessary when Mercurial suggests it.
2169 """
2165 """
2170 if repo.recover():
2166 if repo.recover():
2171 return hg.verify(repo)
2167 return hg.verify(repo)
2172 return 1
2168 return 1
2173
2169
2174 def remove(ui, repo, *pats, **opts):
2170 def remove(ui, repo, *pats, **opts):
2175 """remove the specified files on the next commit
2171 """remove the specified files on the next commit
2176
2172
2177 Schedule the indicated files for removal from the repository.
2173 Schedule the indicated files for removal from the repository.
2178
2174
2179 This only removes files from the current branch, not from the entire
2175 This only removes files from the current branch, not from the entire
2180 project history. -A can be used to remove only files that have already
2176 project history. -A can be used to remove only files that have already
2181 been deleted, -f can be used to force deletion, and -Af can be used
2177 been deleted, -f can be used to force deletion, and -Af can be used
2182 to remove files from the next revision without deleting them.
2178 to remove files from the next revision without deleting them.
2183
2179
2184 The following table details the behavior of remove for different file
2180 The following table details the behavior of remove for different file
2185 states (columns) and option combinations (rows). The file states are
2181 states (columns) and option combinations (rows). The file states are
2186 Added, Clean, Modified and Missing (as reported by hg status). The
2182 Added, Clean, Modified and Missing (as reported by hg status). The
2187 actions are Warn, Remove (from branch) and Delete (from disk).
2183 actions are Warn, Remove (from branch) and Delete (from disk).
2188
2184
2189 A C M !
2185 A C M !
2190 none W RD W R
2186 none W RD W R
2191 -f R RD RD R
2187 -f R RD RD R
2192 -A W W W R
2188 -A W W W R
2193 -Af R R R R
2189 -Af R R R R
2194
2190
2195 This command schedules the files to be removed at the next commit.
2191 This command schedules the files to be removed at the next commit.
2196 To undo a remove before that, see hg revert.
2192 To undo a remove before that, see hg revert.
2197 """
2193 """
2198
2194
2199 after, force = opts.get('after'), opts.get('force')
2195 after, force = opts.get('after'), opts.get('force')
2200 if not pats and not after:
2196 if not pats and not after:
2201 raise util.Abort(_('no files specified'))
2197 raise util.Abort(_('no files specified'))
2202
2198
2203 m = cmdutil.match(repo, pats, opts)
2199 m = cmdutil.match(repo, pats, opts)
2204 mardu = map(dict.fromkeys, repo.status(match=m))[:5]
2200 mardu = map(dict.fromkeys, repo.status(match=m))[:5]
2205 modified, added, removed, deleted, unknown = mardu
2201 modified, added, removed, deleted, unknown = mardu
2206
2202
2207 remove, forget = [], []
2203 remove, forget = [], []
2208 for abs in repo.walk(m):
2204 for abs in repo.walk(m):
2209
2205
2210 reason = None
2206 reason = None
2211 if abs in removed or abs in unknown:
2207 if abs in removed or abs in unknown:
2212 continue
2208 continue
2213
2209
2214 # last column
2210 # last column
2215 elif abs in deleted:
2211 elif abs in deleted:
2216 remove.append(abs)
2212 remove.append(abs)
2217
2213
2218 # rest of the third row
2214 # rest of the third row
2219 elif after and not force:
2215 elif after and not force:
2220 reason = _('still exists (use -f to force removal)')
2216 reason = _('still exists (use -f to force removal)')
2221
2217
2222 # rest of the first column
2218 # rest of the first column
2223 elif abs in added:
2219 elif abs in added:
2224 if not force:
2220 if not force:
2225 reason = _('has been marked for add (use -f to force removal)')
2221 reason = _('has been marked for add (use -f to force removal)')
2226 else:
2222 else:
2227 forget.append(abs)
2223 forget.append(abs)
2228
2224
2229 # rest of the third column
2225 # rest of the third column
2230 elif abs in modified:
2226 elif abs in modified:
2231 if not force:
2227 if not force:
2232 reason = _('is modified (use -f to force removal)')
2228 reason = _('is modified (use -f to force removal)')
2233 else:
2229 else:
2234 remove.append(abs)
2230 remove.append(abs)
2235
2231
2236 # rest of the second column
2232 # rest of the second column
2237 elif not reason:
2233 elif not reason:
2238 remove.append(abs)
2234 remove.append(abs)
2239
2235
2240 if reason:
2236 if reason:
2241 ui.warn(_('not removing %s: file %s\n') % (m.rel(abs), reason))
2237 ui.warn(_('not removing %s: file %s\n') % (m.rel(abs), reason))
2242 elif ui.verbose or not m.exact(abs):
2238 elif ui.verbose or not m.exact(abs):
2243 ui.status(_('removing %s\n') % m.rel(abs))
2239 ui.status(_('removing %s\n') % m.rel(abs))
2244
2240
2245 repo.forget(forget)
2241 repo.forget(forget)
2246 repo.remove(remove, unlink=not after)
2242 repo.remove(remove, unlink=not after)
2247
2243
2248 def rename(ui, repo, *pats, **opts):
2244 def rename(ui, repo, *pats, **opts):
2249 """rename files; equivalent of copy + remove
2245 """rename files; equivalent of copy + remove
2250
2246
2251 Mark dest as copies of sources; mark sources for deletion. If
2247 Mark dest as copies of sources; mark sources for deletion. If
2252 dest is a directory, copies are put in that directory. If dest is
2248 dest is a directory, copies are put in that directory. If dest is
2253 a file, there can only be one source.
2249 a file, there can only be one source.
2254
2250
2255 By default, this command copies the contents of files as they
2251 By default, this command copies the contents of files as they
2256 stand in the working directory. If invoked with --after, the
2252 stand in the working directory. If invoked with --after, the
2257 operation is recorded, but no copying is performed.
2253 operation is recorded, but no copying is performed.
2258
2254
2259 This command takes effect in the next commit. To undo a rename
2255 This command takes effect in the next commit. To undo a rename
2260 before that, see hg revert.
2256 before that, see hg revert.
2261 """
2257 """
2262 wlock = repo.wlock(False)
2258 wlock = repo.wlock(False)
2263 try:
2259 try:
2264 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2260 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2265 finally:
2261 finally:
2266 del wlock
2262 del wlock
2267
2263
2268 def resolve(ui, repo, *pats, **opts):
2264 def resolve(ui, repo, *pats, **opts):
2269 """resolve file merges from a branch merge or update
2265 """resolve file merges from a branch merge or update
2270
2266
2271 This command will attempt to resolve unresolved merges from the
2267 This command will attempt to resolve unresolved merges from the
2272 last update or merge command. This will use the local file
2268 last update or merge command. This will use the local file
2273 revision preserved at the last update or merge to cleanly retry
2269 revision preserved at the last update or merge to cleanly retry
2274 the file merge attempt. With no file or options specified, this
2270 the file merge attempt. With no file or options specified, this
2275 command will attempt to resolve all unresolved files.
2271 command will attempt to resolve all unresolved files.
2276
2272
2277 The codes used to show the status of files are:
2273 The codes used to show the status of files are:
2278 U = unresolved
2274 U = unresolved
2279 R = resolved
2275 R = resolved
2280 """
2276 """
2281
2277
2282 if len([x for x in opts if opts[x]]) > 1:
2278 if len([x for x in opts if opts[x]]) > 1:
2283 raise util.Abort(_("too many options specified"))
2279 raise util.Abort(_("too many options specified"))
2284
2280
2285 ms = merge_.mergestate(repo)
2281 ms = merge_.mergestate(repo)
2286 m = cmdutil.match(repo, pats, opts)
2282 m = cmdutil.match(repo, pats, opts)
2287
2283
2288 for f in ms:
2284 for f in ms:
2289 if m(f):
2285 if m(f):
2290 if opts.get("list"):
2286 if opts.get("list"):
2291 ui.write("%s %s\n" % (ms[f].upper(), f))
2287 ui.write("%s %s\n" % (ms[f].upper(), f))
2292 elif opts.get("mark"):
2288 elif opts.get("mark"):
2293 ms.mark(f, "r")
2289 ms.mark(f, "r")
2294 elif opts.get("unmark"):
2290 elif opts.get("unmark"):
2295 ms.mark(f, "u")
2291 ms.mark(f, "u")
2296 else:
2292 else:
2297 wctx = repo[None]
2293 wctx = repo[None]
2298 mctx = wctx.parents()[-1]
2294 mctx = wctx.parents()[-1]
2299 ms.resolve(f, wctx, mctx)
2295 ms.resolve(f, wctx, mctx)
2300
2296
2301 def revert(ui, repo, *pats, **opts):
2297 def revert(ui, repo, *pats, **opts):
2302 """restore individual files or dirs to an earlier state
2298 """restore individual files or dirs to an earlier state
2303
2299
2304 (use update -r to check out earlier revisions, revert does not
2300 (use update -r to check out earlier revisions, revert does not
2305 change the working dir parents)
2301 change the working dir parents)
2306
2302
2307 With no revision specified, revert the named files or directories
2303 With no revision specified, revert the named files or directories
2308 to the contents they had in the parent of the working directory.
2304 to the contents they had in the parent of the working directory.
2309 This restores the contents of the affected files to an unmodified
2305 This restores the contents of the affected files to an unmodified
2310 state and unschedules adds, removes, copies, and renames. If the
2306 state and unschedules adds, removes, copies, and renames. If the
2311 working directory has two parents, you must explicitly specify the
2307 working directory has two parents, you must explicitly specify the
2312 revision to revert to.
2308 revision to revert to.
2313
2309
2314 Using the -r option, revert the given files or directories to their
2310 Using the -r option, revert the given files or directories to their
2315 contents as of a specific revision. This can be helpful to "roll
2311 contents as of a specific revision. This can be helpful to "roll
2316 back" some or all of an earlier change.
2312 back" some or all of an earlier change.
2317 See 'hg help dates' for a list of formats valid for -d/--date.
2313 See 'hg help dates' for a list of formats valid for -d/--date.
2318
2314
2319 Revert modifies the working directory. It does not commit any
2315 Revert modifies the working directory. It does not commit any
2320 changes, or change the parent of the working directory. If you
2316 changes, or change the parent of the working directory. If you
2321 revert to a revision other than the parent of the working
2317 revert to a revision other than the parent of the working
2322 directory, the reverted files will thus appear modified
2318 directory, the reverted files will thus appear modified
2323 afterwards.
2319 afterwards.
2324
2320
2325 If a file has been deleted, it is restored. If the executable
2321 If a file has been deleted, it is restored. If the executable
2326 mode of a file was changed, it is reset.
2322 mode of a file was changed, it is reset.
2327
2323
2328 If names are given, all files matching the names are reverted.
2324 If names are given, all files matching the names are reverted.
2329 If no arguments are given, no files are reverted.
2325 If no arguments are given, no files are reverted.
2330
2326
2331 Modified files are saved with a .orig suffix before reverting.
2327 Modified files are saved with a .orig suffix before reverting.
2332 To disable these backups, use --no-backup.
2328 To disable these backups, use --no-backup.
2333 """
2329 """
2334
2330
2335 if opts["date"]:
2331 if opts["date"]:
2336 if opts["rev"]:
2332 if opts["rev"]:
2337 raise util.Abort(_("you can't specify a revision and a date"))
2333 raise util.Abort(_("you can't specify a revision and a date"))
2338 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2334 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2339
2335
2340 if not pats and not opts['all']:
2336 if not pats and not opts['all']:
2341 raise util.Abort(_('no files or directories specified; '
2337 raise util.Abort(_('no files or directories specified; '
2342 'use --all to revert the whole repo'))
2338 'use --all to revert the whole repo'))
2343
2339
2344 parent, p2 = repo.dirstate.parents()
2340 parent, p2 = repo.dirstate.parents()
2345 if not opts['rev'] and p2 != nullid:
2341 if not opts['rev'] and p2 != nullid:
2346 raise util.Abort(_('uncommitted merge - please provide a '
2342 raise util.Abort(_('uncommitted merge - please provide a '
2347 'specific revision'))
2343 'specific revision'))
2348 ctx = repo[opts['rev']]
2344 ctx = repo[opts['rev']]
2349 node = ctx.node()
2345 node = ctx.node()
2350 mf = ctx.manifest()
2346 mf = ctx.manifest()
2351 if node == parent:
2347 if node == parent:
2352 pmf = mf
2348 pmf = mf
2353 else:
2349 else:
2354 pmf = None
2350 pmf = None
2355
2351
2356 # need all matching names in dirstate and manifest of target rev,
2352 # need all matching names in dirstate and manifest of target rev,
2357 # so have to walk both. do not print errors if files exist in one
2353 # so have to walk both. do not print errors if files exist in one
2358 # but not other.
2354 # but not other.
2359
2355
2360 names = {}
2356 names = {}
2361
2357
2362 wlock = repo.wlock()
2358 wlock = repo.wlock()
2363 try:
2359 try:
2364 # walk dirstate.
2360 # walk dirstate.
2365 files = []
2361 files = []
2366
2362
2367 m = cmdutil.match(repo, pats, opts)
2363 m = cmdutil.match(repo, pats, opts)
2368 m.bad = lambda x,y: False
2364 m.bad = lambda x,y: False
2369 for abs in repo.walk(m):
2365 for abs in repo.walk(m):
2370 names[abs] = m.rel(abs), m.exact(abs)
2366 names[abs] = m.rel(abs), m.exact(abs)
2371
2367
2372 # walk target manifest.
2368 # walk target manifest.
2373
2369
2374 def badfn(path, msg):
2370 def badfn(path, msg):
2375 if path in names:
2371 if path in names:
2376 return False
2372 return False
2377 path_ = path + '/'
2373 path_ = path + '/'
2378 for f in names:
2374 for f in names:
2379 if f.startswith(path_):
2375 if f.startswith(path_):
2380 return False
2376 return False
2381 repo.ui.warn("%s: %s\n" % (m.rel(path), msg))
2377 repo.ui.warn("%s: %s\n" % (m.rel(path), msg))
2382 return False
2378 return False
2383
2379
2384 m = cmdutil.match(repo, pats, opts)
2380 m = cmdutil.match(repo, pats, opts)
2385 m.bad = badfn
2381 m.bad = badfn
2386 for abs in repo.walk(m, node=node):
2382 for abs in repo.walk(m, node=node):
2387 if abs not in names:
2383 if abs not in names:
2388 names[abs] = m.rel(abs), m.exact(abs)
2384 names[abs] = m.rel(abs), m.exact(abs)
2389
2385
2390 m = cmdutil.matchfiles(repo, names)
2386 m = cmdutil.matchfiles(repo, names)
2391 changes = repo.status(match=m)[:4]
2387 changes = repo.status(match=m)[:4]
2392 modified, added, removed, deleted = map(dict.fromkeys, changes)
2388 modified, added, removed, deleted = map(dict.fromkeys, changes)
2393
2389
2394 # if f is a rename, also revert the source
2390 # if f is a rename, also revert the source
2395 cwd = repo.getcwd()
2391 cwd = repo.getcwd()
2396 for f in added:
2392 for f in added:
2397 src = repo.dirstate.copied(f)
2393 src = repo.dirstate.copied(f)
2398 if src and src not in names and repo.dirstate[src] == 'r':
2394 if src and src not in names and repo.dirstate[src] == 'r':
2399 removed[src] = None
2395 removed[src] = None
2400 names[src] = (repo.pathto(src, cwd), True)
2396 names[src] = (repo.pathto(src, cwd), True)
2401
2397
2402 def removeforget(abs):
2398 def removeforget(abs):
2403 if repo.dirstate[abs] == 'a':
2399 if repo.dirstate[abs] == 'a':
2404 return _('forgetting %s\n')
2400 return _('forgetting %s\n')
2405 return _('removing %s\n')
2401 return _('removing %s\n')
2406
2402
2407 revert = ([], _('reverting %s\n'))
2403 revert = ([], _('reverting %s\n'))
2408 add = ([], _('adding %s\n'))
2404 add = ([], _('adding %s\n'))
2409 remove = ([], removeforget)
2405 remove = ([], removeforget)
2410 undelete = ([], _('undeleting %s\n'))
2406 undelete = ([], _('undeleting %s\n'))
2411
2407
2412 disptable = (
2408 disptable = (
2413 # dispatch table:
2409 # dispatch table:
2414 # file state
2410 # file state
2415 # action if in target manifest
2411 # action if in target manifest
2416 # action if not in target manifest
2412 # action if not in target manifest
2417 # make backup if in target manifest
2413 # make backup if in target manifest
2418 # make backup if not in target manifest
2414 # make backup if not in target manifest
2419 (modified, revert, remove, True, True),
2415 (modified, revert, remove, True, True),
2420 (added, revert, remove, True, False),
2416 (added, revert, remove, True, False),
2421 (removed, undelete, None, False, False),
2417 (removed, undelete, None, False, False),
2422 (deleted, revert, remove, False, False),
2418 (deleted, revert, remove, False, False),
2423 )
2419 )
2424
2420
2425 entries = names.items()
2421 entries = names.items()
2426 entries.sort()
2422 entries.sort()
2427
2423
2428 for abs, (rel, exact) in entries:
2424 for abs, (rel, exact) in entries:
2429 mfentry = mf.get(abs)
2425 mfentry = mf.get(abs)
2430 target = repo.wjoin(abs)
2426 target = repo.wjoin(abs)
2431 def handle(xlist, dobackup):
2427 def handle(xlist, dobackup):
2432 xlist[0].append(abs)
2428 xlist[0].append(abs)
2433 if dobackup and not opts['no_backup'] and util.lexists(target):
2429 if dobackup and not opts['no_backup'] and util.lexists(target):
2434 bakname = "%s.orig" % rel
2430 bakname = "%s.orig" % rel
2435 ui.note(_('saving current version of %s as %s\n') %
2431 ui.note(_('saving current version of %s as %s\n') %
2436 (rel, bakname))
2432 (rel, bakname))
2437 if not opts.get('dry_run'):
2433 if not opts.get('dry_run'):
2438 util.copyfile(target, bakname)
2434 util.copyfile(target, bakname)
2439 if ui.verbose or not exact:
2435 if ui.verbose or not exact:
2440 msg = xlist[1]
2436 msg = xlist[1]
2441 if not isinstance(msg, basestring):
2437 if not isinstance(msg, basestring):
2442 msg = msg(abs)
2438 msg = msg(abs)
2443 ui.status(msg % rel)
2439 ui.status(msg % rel)
2444 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2440 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2445 if abs not in table: continue
2441 if abs not in table: continue
2446 # file has changed in dirstate
2442 # file has changed in dirstate
2447 if mfentry:
2443 if mfentry:
2448 handle(hitlist, backuphit)
2444 handle(hitlist, backuphit)
2449 elif misslist is not None:
2445 elif misslist is not None:
2450 handle(misslist, backupmiss)
2446 handle(misslist, backupmiss)
2451 break
2447 break
2452 else:
2448 else:
2453 if abs not in repo.dirstate:
2449 if abs not in repo.dirstate:
2454 if mfentry:
2450 if mfentry:
2455 handle(add, True)
2451 handle(add, True)
2456 elif exact:
2452 elif exact:
2457 ui.warn(_('file not managed: %s\n') % rel)
2453 ui.warn(_('file not managed: %s\n') % rel)
2458 continue
2454 continue
2459 # file has not changed in dirstate
2455 # file has not changed in dirstate
2460 if node == parent:
2456 if node == parent:
2461 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2457 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2462 continue
2458 continue
2463 if pmf is None:
2459 if pmf is None:
2464 # only need parent manifest in this unlikely case,
2460 # only need parent manifest in this unlikely case,
2465 # so do not read by default
2461 # so do not read by default
2466 pmf = repo[parent].manifest()
2462 pmf = repo[parent].manifest()
2467 if abs in pmf:
2463 if abs in pmf:
2468 if mfentry:
2464 if mfentry:
2469 # if version of file is same in parent and target
2465 # if version of file is same in parent and target
2470 # manifests, do nothing
2466 # manifests, do nothing
2471 if (pmf[abs] != mfentry or
2467 if (pmf[abs] != mfentry or
2472 pmf.flags(abs) != mf.flags(abs)):
2468 pmf.flags(abs) != mf.flags(abs)):
2473 handle(revert, False)
2469 handle(revert, False)
2474 else:
2470 else:
2475 handle(remove, False)
2471 handle(remove, False)
2476
2472
2477 if not opts.get('dry_run'):
2473 if not opts.get('dry_run'):
2478 def checkout(f):
2474 def checkout(f):
2479 fc = ctx[f]
2475 fc = ctx[f]
2480 repo.wwrite(f, fc.data(), fc.flags())
2476 repo.wwrite(f, fc.data(), fc.flags())
2481
2477
2482 audit_path = util.path_auditor(repo.root)
2478 audit_path = util.path_auditor(repo.root)
2483 for f in remove[0]:
2479 for f in remove[0]:
2484 if repo.dirstate[f] == 'a':
2480 if repo.dirstate[f] == 'a':
2485 repo.dirstate.forget(f)
2481 repo.dirstate.forget(f)
2486 continue
2482 continue
2487 audit_path(f)
2483 audit_path(f)
2488 try:
2484 try:
2489 util.unlink(repo.wjoin(f))
2485 util.unlink(repo.wjoin(f))
2490 except OSError:
2486 except OSError:
2491 pass
2487 pass
2492 repo.dirstate.remove(f)
2488 repo.dirstate.remove(f)
2493
2489
2494 normal = None
2490 normal = None
2495 if node == parent:
2491 if node == parent:
2496 # We're reverting to our parent. If possible, we'd like status
2492 # We're reverting to our parent. If possible, we'd like status
2497 # to report the file as clean. We have to use normallookup for
2493 # to report the file as clean. We have to use normallookup for
2498 # merges to avoid losing information about merged/dirty files.
2494 # merges to avoid losing information about merged/dirty files.
2499 if p2 != nullid:
2495 if p2 != nullid:
2500 normal = repo.dirstate.normallookup
2496 normal = repo.dirstate.normallookup
2501 else:
2497 else:
2502 normal = repo.dirstate.normal
2498 normal = repo.dirstate.normal
2503 for f in revert[0]:
2499 for f in revert[0]:
2504 checkout(f)
2500 checkout(f)
2505 if normal:
2501 if normal:
2506 normal(f)
2502 normal(f)
2507
2503
2508 for f in add[0]:
2504 for f in add[0]:
2509 checkout(f)
2505 checkout(f)
2510 repo.dirstate.add(f)
2506 repo.dirstate.add(f)
2511
2507
2512 normal = repo.dirstate.normallookup
2508 normal = repo.dirstate.normallookup
2513 if node == parent and p2 == nullid:
2509 if node == parent and p2 == nullid:
2514 normal = repo.dirstate.normal
2510 normal = repo.dirstate.normal
2515 for f in undelete[0]:
2511 for f in undelete[0]:
2516 checkout(f)
2512 checkout(f)
2517 normal(f)
2513 normal(f)
2518
2514
2519 finally:
2515 finally:
2520 del wlock
2516 del wlock
2521
2517
2522 def rollback(ui, repo):
2518 def rollback(ui, repo):
2523 """roll back the last transaction
2519 """roll back the last transaction
2524
2520
2525 This command should be used with care. There is only one level of
2521 This command should be used with care. There is only one level of
2526 rollback, and there is no way to undo a rollback. It will also
2522 rollback, and there is no way to undo a rollback. It will also
2527 restore the dirstate at the time of the last transaction, losing
2523 restore the dirstate at the time of the last transaction, losing
2528 any dirstate changes since that time.
2524 any dirstate changes since that time.
2529
2525
2530 Transactions are used to encapsulate the effects of all commands
2526 Transactions are used to encapsulate the effects of all commands
2531 that create new changesets or propagate existing changesets into a
2527 that create new changesets or propagate existing changesets into a
2532 repository. For example, the following commands are transactional,
2528 repository. For example, the following commands are transactional,
2533 and their effects can be rolled back:
2529 and their effects can be rolled back:
2534
2530
2535 commit
2531 commit
2536 import
2532 import
2537 pull
2533 pull
2538 push (with this repository as destination)
2534 push (with this repository as destination)
2539 unbundle
2535 unbundle
2540
2536
2541 This command is not intended for use on public repositories. Once
2537 This command is not intended for use on public repositories. Once
2542 changes are visible for pull by other users, rolling a transaction
2538 changes are visible for pull by other users, rolling a transaction
2543 back locally is ineffective (someone else may already have pulled
2539 back locally is ineffective (someone else may already have pulled
2544 the changes). Furthermore, a race is possible with readers of the
2540 the changes). Furthermore, a race is possible with readers of the
2545 repository; for example an in-progress pull from the repository
2541 repository; for example an in-progress pull from the repository
2546 may fail if a rollback is performed.
2542 may fail if a rollback is performed.
2547 """
2543 """
2548 repo.rollback()
2544 repo.rollback()
2549
2545
2550 def root(ui, repo):
2546 def root(ui, repo):
2551 """print the root (top) of the current working dir
2547 """print the root (top) of the current working dir
2552
2548
2553 Print the root directory of the current repository.
2549 Print the root directory of the current repository.
2554 """
2550 """
2555 ui.write(repo.root + "\n")
2551 ui.write(repo.root + "\n")
2556
2552
2557 def serve(ui, repo, **opts):
2553 def serve(ui, repo, **opts):
2558 """export the repository via HTTP
2554 """export the repository via HTTP
2559
2555
2560 Start a local HTTP repository browser and pull server.
2556 Start a local HTTP repository browser and pull server.
2561
2557
2562 By default, the server logs accesses to stdout and errors to
2558 By default, the server logs accesses to stdout and errors to
2563 stderr. Use the "-A" and "-E" options to log to files.
2559 stderr. Use the "-A" and "-E" options to log to files.
2564 """
2560 """
2565
2561
2566 if opts["stdio"]:
2562 if opts["stdio"]:
2567 if repo is None:
2563 if repo is None:
2568 raise RepoError(_("There is no Mercurial repository here"
2564 raise RepoError(_("There is no Mercurial repository here"
2569 " (.hg not found)"))
2565 " (.hg not found)"))
2570 s = sshserver.sshserver(ui, repo)
2566 s = sshserver.sshserver(ui, repo)
2571 s.serve_forever()
2567 s.serve_forever()
2572
2568
2573 parentui = ui.parentui or ui
2569 parentui = ui.parentui or ui
2574 optlist = ("name templates style address port prefix ipv6"
2570 optlist = ("name templates style address port prefix ipv6"
2575 " accesslog errorlog webdir_conf certificate")
2571 " accesslog errorlog webdir_conf certificate")
2576 for o in optlist.split():
2572 for o in optlist.split():
2577 if opts[o]:
2573 if opts[o]:
2578 parentui.setconfig("web", o, str(opts[o]))
2574 parentui.setconfig("web", o, str(opts[o]))
2579 if (repo is not None) and (repo.ui != parentui):
2575 if (repo is not None) and (repo.ui != parentui):
2580 repo.ui.setconfig("web", o, str(opts[o]))
2576 repo.ui.setconfig("web", o, str(opts[o]))
2581
2577
2582 if repo is None and not ui.config("web", "webdir_conf"):
2578 if repo is None and not ui.config("web", "webdir_conf"):
2583 raise RepoError(_("There is no Mercurial repository here"
2579 raise RepoError(_("There is no Mercurial repository here"
2584 " (.hg not found)"))
2580 " (.hg not found)"))
2585
2581
2586 class service:
2582 class service:
2587 def init(self):
2583 def init(self):
2588 util.set_signal_handler()
2584 util.set_signal_handler()
2589 self.httpd = hgweb.server.create_server(parentui, repo)
2585 self.httpd = hgweb.server.create_server(parentui, repo)
2590
2586
2591 if not ui.verbose: return
2587 if not ui.verbose: return
2592
2588
2593 if self.httpd.prefix:
2589 if self.httpd.prefix:
2594 prefix = self.httpd.prefix.strip('/') + '/'
2590 prefix = self.httpd.prefix.strip('/') + '/'
2595 else:
2591 else:
2596 prefix = ''
2592 prefix = ''
2597
2593
2598 port = ':%d' % self.httpd.port
2594 port = ':%d' % self.httpd.port
2599 if port == ':80':
2595 if port == ':80':
2600 port = ''
2596 port = ''
2601
2597
2602 bindaddr = self.httpd.addr
2598 bindaddr = self.httpd.addr
2603 if bindaddr == '0.0.0.0':
2599 if bindaddr == '0.0.0.0':
2604 bindaddr = '*'
2600 bindaddr = '*'
2605 elif ':' in bindaddr: # IPv6
2601 elif ':' in bindaddr: # IPv6
2606 bindaddr = '[%s]' % bindaddr
2602 bindaddr = '[%s]' % bindaddr
2607
2603
2608 fqaddr = self.httpd.fqaddr
2604 fqaddr = self.httpd.fqaddr
2609 if ':' in fqaddr:
2605 if ':' in fqaddr:
2610 fqaddr = '[%s]' % fqaddr
2606 fqaddr = '[%s]' % fqaddr
2611 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2607 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2612 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2608 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2613
2609
2614 def run(self):
2610 def run(self):
2615 self.httpd.serve_forever()
2611 self.httpd.serve_forever()
2616
2612
2617 service = service()
2613 service = service()
2618
2614
2619 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2615 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2620
2616
2621 def status(ui, repo, *pats, **opts):
2617 def status(ui, repo, *pats, **opts):
2622 """show changed files in the working directory
2618 """show changed files in the working directory
2623
2619
2624 Show status of files in the repository. If names are given, only
2620 Show status of files in the repository. If names are given, only
2625 files that match are shown. Files that are clean or ignored or
2621 files that match are shown. Files that are clean or ignored or
2626 source of a copy/move operation, are not listed unless -c (clean),
2622 source of a copy/move operation, are not listed unless -c (clean),
2627 -i (ignored), -C (copies) or -A is given. Unless options described
2623 -i (ignored), -C (copies) or -A is given. Unless options described
2628 with "show only ..." are given, the options -mardu are used.
2624 with "show only ..." are given, the options -mardu are used.
2629
2625
2630 Option -q/--quiet hides untracked (unknown and ignored) files
2626 Option -q/--quiet hides untracked (unknown and ignored) files
2631 unless explicitly requested with -u/--unknown or -i/-ignored.
2627 unless explicitly requested with -u/--unknown or -i/-ignored.
2632
2628
2633 NOTE: status may appear to disagree with diff if permissions have
2629 NOTE: status may appear to disagree with diff if permissions have
2634 changed or a merge has occurred. The standard diff format does not
2630 changed or a merge has occurred. The standard diff format does not
2635 report permission changes and diff only reports changes relative
2631 report permission changes and diff only reports changes relative
2636 to one merge parent.
2632 to one merge parent.
2637
2633
2638 If one revision is given, it is used as the base revision.
2634 If one revision is given, it is used as the base revision.
2639 If two revisions are given, the difference between them is shown.
2635 If two revisions are given, the difference between them is shown.
2640
2636
2641 The codes used to show the status of files are:
2637 The codes used to show the status of files are:
2642 M = modified
2638 M = modified
2643 A = added
2639 A = added
2644 R = removed
2640 R = removed
2645 C = clean
2641 C = clean
2646 ! = deleted, but still tracked
2642 ! = deleted, but still tracked
2647 ? = not tracked
2643 ? = not tracked
2648 I = ignored
2644 I = ignored
2649 = the previous added file was copied from here
2645 = the previous added file was copied from here
2650 """
2646 """
2651
2647
2652 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2648 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2653 cwd = (pats and repo.getcwd()) or ''
2649 cwd = (pats and repo.getcwd()) or ''
2654 end = opts['print0'] and '\0' or '\n'
2650 end = opts['print0'] and '\0' or '\n'
2655 copy = {}
2651 copy = {}
2656 states = 'modified added removed deleted unknown ignored clean'.split()
2652 states = 'modified added removed deleted unknown ignored clean'.split()
2657 show = [k for k in states if opts[k]]
2653 show = [k for k in states if opts[k]]
2658 if opts['all']:
2654 if opts['all']:
2659 show += ui.quiet and (states[:4] + ['clean']) or states
2655 show += ui.quiet and (states[:4] + ['clean']) or states
2660 if not show:
2656 if not show:
2661 show = ui.quiet and states[:4] or states[:5]
2657 show = ui.quiet and states[:4] or states[:5]
2662
2658
2663 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2659 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2664 'ignored' in show, 'clean' in show, 'unknown' in show)
2660 'ignored' in show, 'clean' in show, 'unknown' in show)
2665 changestates = zip(states, 'MAR!?IC', stat)
2661 changestates = zip(states, 'MAR!?IC', stat)
2666
2662
2667 if (opts['all'] or opts['copies']) and not opts['no_status']:
2663 if (opts['all'] or opts['copies']) and not opts['no_status']:
2668 ctxn = repo[nullid]
2664 ctxn = repo[nullid]
2669 ctx1 = repo[node1]
2665 ctx1 = repo[node1]
2670 ctx2 = repo[node2]
2666 ctx2 = repo[node2]
2671 added = stat[1]
2667 added = stat[1]
2672 if node2 is None:
2668 if node2 is None:
2673 added = stat[0] + stat[1] # merged?
2669 added = stat[0] + stat[1] # merged?
2674
2670
2675 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].items():
2671 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].items():
2676 if k in added:
2672 if k in added:
2677 copy[k] = v
2673 copy[k] = v
2678 elif v in added:
2674 elif v in added:
2679 copy[v] = k
2675 copy[v] = k
2680
2676
2681 for state, char, files in changestates:
2677 for state, char, files in changestates:
2682 if state in show:
2678 if state in show:
2683 format = "%s %%s%s" % (char, end)
2679 format = "%s %%s%s" % (char, end)
2684 if opts['no_status']:
2680 if opts['no_status']:
2685 format = "%%s%s" % end
2681 format = "%%s%s" % end
2686
2682
2687 for f in files:
2683 for f in files:
2688 ui.write(format % repo.pathto(f, cwd))
2684 ui.write(format % repo.pathto(f, cwd))
2689 if f in copy:
2685 if f in copy:
2690 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2686 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2691
2687
2692 def tag(ui, repo, name1, *names, **opts):
2688 def tag(ui, repo, name1, *names, **opts):
2693 """add one or more tags for the current or given revision
2689 """add one or more tags for the current or given revision
2694
2690
2695 Name a particular revision using <name>.
2691 Name a particular revision using <name>.
2696
2692
2697 Tags are used to name particular revisions of the repository and are
2693 Tags are used to name particular revisions of the repository and are
2698 very useful to compare different revisions, to go back to significant
2694 very useful to compare different revisions, to go back to significant
2699 earlier versions or to mark branch points as releases, etc.
2695 earlier versions or to mark branch points as releases, etc.
2700
2696
2701 If no revision is given, the parent of the working directory is used,
2697 If no revision is given, the parent of the working directory is used,
2702 or tip if no revision is checked out.
2698 or tip if no revision is checked out.
2703
2699
2704 To facilitate version control, distribution, and merging of tags,
2700 To facilitate version control, distribution, and merging of tags,
2705 they are stored as a file named ".hgtags" which is managed
2701 they are stored as a file named ".hgtags" which is managed
2706 similarly to other project files and can be hand-edited if
2702 similarly to other project files and can be hand-edited if
2707 necessary. The file '.hg/localtags' is used for local tags (not
2703 necessary. The file '.hg/localtags' is used for local tags (not
2708 shared among repositories).
2704 shared among repositories).
2709
2705
2710 See 'hg help dates' for a list of formats valid for -d/--date.
2706 See 'hg help dates' for a list of formats valid for -d/--date.
2711 """
2707 """
2712
2708
2713 rev_ = "."
2709 rev_ = "."
2714 names = (name1,) + names
2710 names = (name1,) + names
2715 if len(names) != len(dict.fromkeys(names)):
2711 if len(names) != len(dict.fromkeys(names)):
2716 raise util.Abort(_('tag names must be unique'))
2712 raise util.Abort(_('tag names must be unique'))
2717 for n in names:
2713 for n in names:
2718 if n in ['tip', '.', 'null']:
2714 if n in ['tip', '.', 'null']:
2719 raise util.Abort(_('the name \'%s\' is reserved') % n)
2715 raise util.Abort(_('the name \'%s\' is reserved') % n)
2720 if opts['rev'] and opts['remove']:
2716 if opts['rev'] and opts['remove']:
2721 raise util.Abort(_("--rev and --remove are incompatible"))
2717 raise util.Abort(_("--rev and --remove are incompatible"))
2722 if opts['rev']:
2718 if opts['rev']:
2723 rev_ = opts['rev']
2719 rev_ = opts['rev']
2724 message = opts['message']
2720 message = opts['message']
2725 if opts['remove']:
2721 if opts['remove']:
2726 expectedtype = opts['local'] and 'local' or 'global'
2722 expectedtype = opts['local'] and 'local' or 'global'
2727 for n in names:
2723 for n in names:
2728 if not repo.tagtype(n):
2724 if not repo.tagtype(n):
2729 raise util.Abort(_('tag \'%s\' does not exist') % n)
2725 raise util.Abort(_('tag \'%s\' does not exist') % n)
2730 if repo.tagtype(n) != expectedtype:
2726 if repo.tagtype(n) != expectedtype:
2731 raise util.Abort(_('tag \'%s\' is not a %s tag') %
2727 raise util.Abort(_('tag \'%s\' is not a %s tag') %
2732 (n, expectedtype))
2728 (n, expectedtype))
2733 rev_ = nullid
2729 rev_ = nullid
2734 if not message:
2730 if not message:
2735 message = _('Removed tag %s') % ', '.join(names)
2731 message = _('Removed tag %s') % ', '.join(names)
2736 elif not opts['force']:
2732 elif not opts['force']:
2737 for n in names:
2733 for n in names:
2738 if n in repo.tags():
2734 if n in repo.tags():
2739 raise util.Abort(_('tag \'%s\' already exists '
2735 raise util.Abort(_('tag \'%s\' already exists '
2740 '(use -f to force)') % n)
2736 '(use -f to force)') % n)
2741 if not rev_ and repo.dirstate.parents()[1] != nullid:
2737 if not rev_ and repo.dirstate.parents()[1] != nullid:
2742 raise util.Abort(_('uncommitted merge - please provide a '
2738 raise util.Abort(_('uncommitted merge - please provide a '
2743 'specific revision'))
2739 'specific revision'))
2744 r = repo[rev_].node()
2740 r = repo[rev_].node()
2745
2741
2746 if not message:
2742 if not message:
2747 message = (_('Added tag %s for changeset %s') %
2743 message = (_('Added tag %s for changeset %s') %
2748 (', '.join(names), short(r)))
2744 (', '.join(names), short(r)))
2749
2745
2750 date = opts.get('date')
2746 date = opts.get('date')
2751 if date:
2747 if date:
2752 date = util.parsedate(date)
2748 date = util.parsedate(date)
2753
2749
2754 repo.tag(names, r, message, opts['local'], opts['user'], date)
2750 repo.tag(names, r, message, opts['local'], opts['user'], date)
2755
2751
2756 def tags(ui, repo):
2752 def tags(ui, repo):
2757 """list repository tags
2753 """list repository tags
2758
2754
2759 List the repository tags.
2755 List the repository tags.
2760
2756
2761 This lists both regular and local tags. When the -v/--verbose switch
2757 This lists both regular and local tags. When the -v/--verbose switch
2762 is used, a third column "local" is printed for local tags.
2758 is used, a third column "local" is printed for local tags.
2763 """
2759 """
2764
2760
2765 l = repo.tagslist()
2761 l = repo.tagslist()
2766 l.reverse()
2762 l.reverse()
2767 hexfunc = ui.debugflag and hex or short
2763 hexfunc = ui.debugflag and hex or short
2768 tagtype = ""
2764 tagtype = ""
2769
2765
2770 for t, n in l:
2766 for t, n in l:
2771 if ui.quiet:
2767 if ui.quiet:
2772 ui.write("%s\n" % t)
2768 ui.write("%s\n" % t)
2773 continue
2769 continue
2774
2770
2775 try:
2771 try:
2776 hn = hexfunc(n)
2772 hn = hexfunc(n)
2777 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2773 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2778 except revlog.LookupError:
2774 except revlog.LookupError:
2779 r = " ?:%s" % hn
2775 r = " ?:%s" % hn
2780 else:
2776 else:
2781 spaces = " " * (30 - util.locallen(t))
2777 spaces = " " * (30 - util.locallen(t))
2782 if ui.verbose:
2778 if ui.verbose:
2783 if repo.tagtype(t) == 'local':
2779 if repo.tagtype(t) == 'local':
2784 tagtype = " local"
2780 tagtype = " local"
2785 else:
2781 else:
2786 tagtype = ""
2782 tagtype = ""
2787 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2783 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2788
2784
2789 def tip(ui, repo, **opts):
2785 def tip(ui, repo, **opts):
2790 """show the tip revision
2786 """show the tip revision
2791
2787
2792 The tip revision (usually just called the tip) is the most
2788 The tip revision (usually just called the tip) is the most
2793 recently added changeset in the repository, the most recently
2789 recently added changeset in the repository, the most recently
2794 changed head.
2790 changed head.
2795
2791
2796 If you have just made a commit, that commit will be the tip. If
2792 If you have just made a commit, that commit will be the tip. If
2797 you have just pulled changes from another repository, the tip of
2793 you have just pulled changes from another repository, the tip of
2798 that repository becomes the current tip. The "tip" tag is special
2794 that repository becomes the current tip. The "tip" tag is special
2799 and cannot be renamed or assigned to a different changeset.
2795 and cannot be renamed or assigned to a different changeset.
2800 """
2796 """
2801 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2797 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2802
2798
2803 def unbundle(ui, repo, fname1, *fnames, **opts):
2799 def unbundle(ui, repo, fname1, *fnames, **opts):
2804 """apply one or more changegroup files
2800 """apply one or more changegroup files
2805
2801
2806 Apply one or more compressed changegroup files generated by the
2802 Apply one or more compressed changegroup files generated by the
2807 bundle command.
2803 bundle command.
2808 """
2804 """
2809 fnames = (fname1,) + fnames
2805 fnames = (fname1,) + fnames
2810
2806
2811 lock = None
2807 lock = None
2812 try:
2808 try:
2813 lock = repo.lock()
2809 lock = repo.lock()
2814 for fname in fnames:
2810 for fname in fnames:
2815 if os.path.exists(fname):
2811 if os.path.exists(fname):
2816 f = open(fname, "rb")
2812 f = open(fname, "rb")
2817 else:
2813 else:
2818 f = urllib.urlopen(fname)
2814 f = urllib.urlopen(fname)
2819 gen = changegroup.readbundle(f, fname)
2815 gen = changegroup.readbundle(f, fname)
2820 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2816 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2821 finally:
2817 finally:
2822 del lock
2818 del lock
2823
2819
2824 return postincoming(ui, repo, modheads, opts['update'], None)
2820 return postincoming(ui, repo, modheads, opts['update'], None)
2825
2821
2826 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2822 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2827 """update working directory
2823 """update working directory
2828
2824
2829 Update the repository's working directory to the specified revision,
2825 Update the repository's working directory to the specified revision,
2830 or the tip of the current branch if none is specified.
2826 or the tip of the current branch if none is specified.
2831
2827
2832 If the requested revision is a descendant of the working
2828 If the requested revision is a descendant of the working
2833 directory, any outstanding changes in the working directory will
2829 directory, any outstanding changes in the working directory will
2834 be merged into the result. If it is not directly descended but is
2830 be merged into the result. If it is not directly descended but is
2835 on the same named branch, update aborts with a suggestion to use
2831 on the same named branch, update aborts with a suggestion to use
2836 merge or update -C instead.
2832 merge or update -C instead.
2837
2833
2838 If the requested revision is on a different named branch and the
2834 If the requested revision is on a different named branch and the
2839 working directory is clean, update quietly switches branches.
2835 working directory is clean, update quietly switches branches.
2840
2836
2841 See 'hg help dates' for a list of formats valid for --date.
2837 See 'hg help dates' for a list of formats valid for --date.
2842 """
2838 """
2843 if rev and node:
2839 if rev and node:
2844 raise util.Abort(_("please specify just one revision"))
2840 raise util.Abort(_("please specify just one revision"))
2845
2841
2846 if not rev:
2842 if not rev:
2847 rev = node
2843 rev = node
2848
2844
2849 if date:
2845 if date:
2850 if rev:
2846 if rev:
2851 raise util.Abort(_("you can't specify a revision and a date"))
2847 raise util.Abort(_("you can't specify a revision and a date"))
2852 rev = cmdutil.finddate(ui, repo, date)
2848 rev = cmdutil.finddate(ui, repo, date)
2853
2849
2854 if clean:
2850 if clean:
2855 return hg.clean(repo, rev)
2851 return hg.clean(repo, rev)
2856 else:
2852 else:
2857 return hg.update(repo, rev)
2853 return hg.update(repo, rev)
2858
2854
2859 def verify(ui, repo):
2855 def verify(ui, repo):
2860 """verify the integrity of the repository
2856 """verify the integrity of the repository
2861
2857
2862 Verify the integrity of the current repository.
2858 Verify the integrity of the current repository.
2863
2859
2864 This will perform an extensive check of the repository's
2860 This will perform an extensive check of the repository's
2865 integrity, validating the hashes and checksums of each entry in
2861 integrity, validating the hashes and checksums of each entry in
2866 the changelog, manifest, and tracked files, as well as the
2862 the changelog, manifest, and tracked files, as well as the
2867 integrity of their crosslinks and indices.
2863 integrity of their crosslinks and indices.
2868 """
2864 """
2869 return hg.verify(repo)
2865 return hg.verify(repo)
2870
2866
2871 def version_(ui):
2867 def version_(ui):
2872 """output version and copyright information"""
2868 """output version and copyright information"""
2873 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2869 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2874 % version.get_version())
2870 % version.get_version())
2875 ui.status(_(
2871 ui.status(_(
2876 "\nCopyright (C) 2005-2008 Matt Mackall <mpm@selenic.com> and others\n"
2872 "\nCopyright (C) 2005-2008 Matt Mackall <mpm@selenic.com> and others\n"
2877 "This is free software; see the source for copying conditions. "
2873 "This is free software; see the source for copying conditions. "
2878 "There is NO\nwarranty; "
2874 "There is NO\nwarranty; "
2879 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2875 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2880 ))
2876 ))
2881
2877
2882 # Command options and aliases are listed here, alphabetically
2878 # Command options and aliases are listed here, alphabetically
2883
2879
2884 globalopts = [
2880 globalopts = [
2885 ('R', 'repository', '',
2881 ('R', 'repository', '',
2886 _('repository root directory or symbolic path name')),
2882 _('repository root directory or symbolic path name')),
2887 ('', 'cwd', '', _('change working directory')),
2883 ('', 'cwd', '', _('change working directory')),
2888 ('y', 'noninteractive', None,
2884 ('y', 'noninteractive', None,
2889 _('do not prompt, assume \'yes\' for any required answers')),
2885 _('do not prompt, assume \'yes\' for any required answers')),
2890 ('q', 'quiet', None, _('suppress output')),
2886 ('q', 'quiet', None, _('suppress output')),
2891 ('v', 'verbose', None, _('enable additional output')),
2887 ('v', 'verbose', None, _('enable additional output')),
2892 ('', 'config', [], _('set/override config option')),
2888 ('', 'config', [], _('set/override config option')),
2893 ('', 'debug', None, _('enable debugging output')),
2889 ('', 'debug', None, _('enable debugging output')),
2894 ('', 'debugger', None, _('start debugger')),
2890 ('', 'debugger', None, _('start debugger')),
2895 ('', 'encoding', util._encoding, _('set the charset encoding')),
2891 ('', 'encoding', util._encoding, _('set the charset encoding')),
2896 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2892 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2897 ('', 'lsprof', None, _('print improved command execution profile')),
2893 ('', 'lsprof', None, _('print improved command execution profile')),
2898 ('', 'traceback', None, _('print traceback on exception')),
2894 ('', 'traceback', None, _('print traceback on exception')),
2899 ('', 'time', None, _('time how long the command takes')),
2895 ('', 'time', None, _('time how long the command takes')),
2900 ('', 'profile', None, _('print command execution profile')),
2896 ('', 'profile', None, _('print command execution profile')),
2901 ('', 'version', None, _('output version information and exit')),
2897 ('', 'version', None, _('output version information and exit')),
2902 ('h', 'help', None, _('display help and exit')),
2898 ('h', 'help', None, _('display help and exit')),
2903 ]
2899 ]
2904
2900
2905 dryrunopts = [('n', 'dry-run', None,
2901 dryrunopts = [('n', 'dry-run', None,
2906 _('do not perform actions, just print output'))]
2902 _('do not perform actions, just print output'))]
2907
2903
2908 remoteopts = [
2904 remoteopts = [
2909 ('e', 'ssh', '', _('specify ssh command to use')),
2905 ('e', 'ssh', '', _('specify ssh command to use')),
2910 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2906 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2911 ]
2907 ]
2912
2908
2913 walkopts = [
2909 walkopts = [
2914 ('I', 'include', [], _('include names matching the given patterns')),
2910 ('I', 'include', [], _('include names matching the given patterns')),
2915 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2911 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2916 ]
2912 ]
2917
2913
2918 commitopts = [
2914 commitopts = [
2919 ('m', 'message', '', _('use <text> as commit message')),
2915 ('m', 'message', '', _('use <text> as commit message')),
2920 ('l', 'logfile', '', _('read commit message from <file>')),
2916 ('l', 'logfile', '', _('read commit message from <file>')),
2921 ]
2917 ]
2922
2918
2923 commitopts2 = [
2919 commitopts2 = [
2924 ('d', 'date', '', _('record datecode as commit date')),
2920 ('d', 'date', '', _('record datecode as commit date')),
2925 ('u', 'user', '', _('record user as committer')),
2921 ('u', 'user', '', _('record user as committer')),
2926 ]
2922 ]
2927
2923
2928 templateopts = [
2924 templateopts = [
2929 ('', 'style', '', _('display using template map file')),
2925 ('', 'style', '', _('display using template map file')),
2930 ('', 'template', '', _('display with template')),
2926 ('', 'template', '', _('display with template')),
2931 ]
2927 ]
2932
2928
2933 logopts = [
2929 logopts = [
2934 ('p', 'patch', None, _('show patch')),
2930 ('p', 'patch', None, _('show patch')),
2935 ('l', 'limit', '', _('limit number of changes displayed')),
2931 ('l', 'limit', '', _('limit number of changes displayed')),
2936 ('M', 'no-merges', None, _('do not show merges')),
2932 ('M', 'no-merges', None, _('do not show merges')),
2937 ] + templateopts
2933 ] + templateopts
2938
2934
2939 diffopts = [
2935 diffopts = [
2940 ('a', 'text', None, _('treat all files as text')),
2936 ('a', 'text', None, _('treat all files as text')),
2941 ('g', 'git', None, _('use git extended diff format')),
2937 ('g', 'git', None, _('use git extended diff format')),
2942 ('', 'nodates', None, _("don't include dates in diff headers"))
2938 ('', 'nodates', None, _("don't include dates in diff headers"))
2943 ]
2939 ]
2944
2940
2945 diffopts2 = [
2941 diffopts2 = [
2946 ('p', 'show-function', None, _('show which function each change is in')),
2942 ('p', 'show-function', None, _('show which function each change is in')),
2947 ('w', 'ignore-all-space', None,
2943 ('w', 'ignore-all-space', None,
2948 _('ignore white space when comparing lines')),
2944 _('ignore white space when comparing lines')),
2949 ('b', 'ignore-space-change', None,
2945 ('b', 'ignore-space-change', None,
2950 _('ignore changes in the amount of white space')),
2946 _('ignore changes in the amount of white space')),
2951 ('B', 'ignore-blank-lines', None,
2947 ('B', 'ignore-blank-lines', None,
2952 _('ignore changes whose lines are all blank')),
2948 _('ignore changes whose lines are all blank')),
2953 ('U', 'unified', '', _('number of lines of context to show'))
2949 ('U', 'unified', '', _('number of lines of context to show'))
2954 ]
2950 ]
2955
2951
2956 table = {
2952 table = {
2957 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2953 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2958 "addremove":
2954 "addremove":
2959 (addremove,
2955 (addremove,
2960 [('s', 'similarity', '',
2956 [('s', 'similarity', '',
2961 _('guess renamed files by similarity (0<=s<=100)')),
2957 _('guess renamed files by similarity (0<=s<=100)')),
2962 ] + walkopts + dryrunopts,
2958 ] + walkopts + dryrunopts,
2963 _('hg addremove [OPTION]... [FILE]...')),
2959 _('hg addremove [OPTION]... [FILE]...')),
2964 "^annotate|blame":
2960 "^annotate|blame":
2965 (annotate,
2961 (annotate,
2966 [('r', 'rev', '', _('annotate the specified revision')),
2962 [('r', 'rev', '', _('annotate the specified revision')),
2967 ('f', 'follow', None, _('follow file copies and renames')),
2963 ('f', 'follow', None, _('follow file copies and renames')),
2968 ('a', 'text', None, _('treat all files as text')),
2964 ('a', 'text', None, _('treat all files as text')),
2969 ('u', 'user', None, _('list the author (long with -v)')),
2965 ('u', 'user', None, _('list the author (long with -v)')),
2970 ('d', 'date', None, _('list the date (short with -q)')),
2966 ('d', 'date', None, _('list the date (short with -q)')),
2971 ('n', 'number', None, _('list the revision number (default)')),
2967 ('n', 'number', None, _('list the revision number (default)')),
2972 ('c', 'changeset', None, _('list the changeset')),
2968 ('c', 'changeset', None, _('list the changeset')),
2973 ('l', 'line-number', None,
2969 ('l', 'line-number', None,
2974 _('show line number at the first appearance'))
2970 _('show line number at the first appearance'))
2975 ] + walkopts,
2971 ] + walkopts,
2976 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2972 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2977 "archive":
2973 "archive":
2978 (archive,
2974 (archive,
2979 [('', 'no-decode', None, _('do not pass files through decoders')),
2975 [('', 'no-decode', None, _('do not pass files through decoders')),
2980 ('p', 'prefix', '', _('directory prefix for files in archive')),
2976 ('p', 'prefix', '', _('directory prefix for files in archive')),
2981 ('r', 'rev', '', _('revision to distribute')),
2977 ('r', 'rev', '', _('revision to distribute')),
2982 ('t', 'type', '', _('type of distribution to create')),
2978 ('t', 'type', '', _('type of distribution to create')),
2983 ] + walkopts,
2979 ] + walkopts,
2984 _('hg archive [OPTION]... DEST')),
2980 _('hg archive [OPTION]... DEST')),
2985 "backout":
2981 "backout":
2986 (backout,
2982 (backout,
2987 [('', 'merge', None,
2983 [('', 'merge', None,
2988 _('merge with old dirstate parent after backout')),
2984 _('merge with old dirstate parent after backout')),
2989 ('', 'parent', '', _('parent to choose when backing out merge')),
2985 ('', 'parent', '', _('parent to choose when backing out merge')),
2990 ('r', 'rev', '', _('revision to backout')),
2986 ('r', 'rev', '', _('revision to backout')),
2991 ] + walkopts + commitopts + commitopts2,
2987 ] + walkopts + commitopts + commitopts2,
2992 _('hg backout [OPTION]... [-r] REV')),
2988 _('hg backout [OPTION]... [-r] REV')),
2993 "bisect":
2989 "bisect":
2994 (bisect,
2990 (bisect,
2995 [('r', 'reset', False, _('reset bisect state')),
2991 [('r', 'reset', False, _('reset bisect state')),
2996 ('g', 'good', False, _('mark changeset good')),
2992 ('g', 'good', False, _('mark changeset good')),
2997 ('b', 'bad', False, _('mark changeset bad')),
2993 ('b', 'bad', False, _('mark changeset bad')),
2998 ('s', 'skip', False, _('skip testing changeset')),
2994 ('s', 'skip', False, _('skip testing changeset')),
2999 ('U', 'noupdate', False, _('do not update to target'))],
2995 ('U', 'noupdate', False, _('do not update to target'))],
3000 _("hg bisect [-gbsr] [REV]")),
2996 _("hg bisect [-gbsr] [REV]")),
3001 "branch":
2997 "branch":
3002 (branch,
2998 (branch,
3003 [('f', 'force', None,
2999 [('f', 'force', None,
3004 _('set branch name even if it shadows an existing branch'))],
3000 _('set branch name even if it shadows an existing branch'))],
3005 _('hg branch [-f] [NAME]')),
3001 _('hg branch [-f] [NAME]')),
3006 "branches":
3002 "branches":
3007 (branches,
3003 (branches,
3008 [('a', 'active', False,
3004 [('a', 'active', False,
3009 _('show only branches that have unmerged heads'))],
3005 _('show only branches that have unmerged heads'))],
3010 _('hg branches [-a]')),
3006 _('hg branches [-a]')),
3011 "bundle":
3007 "bundle":
3012 (bundle,
3008 (bundle,
3013 [('f', 'force', None,
3009 [('f', 'force', None,
3014 _('run even when remote repository is unrelated')),
3010 _('run even when remote repository is unrelated')),
3015 ('r', 'rev', [],
3011 ('r', 'rev', [],
3016 _('a changeset up to which you would like to bundle')),
3012 _('a changeset up to which you would like to bundle')),
3017 ('', 'base', [],
3013 ('', 'base', [],
3018 _('a base changeset to specify instead of a destination')),
3014 _('a base changeset to specify instead of a destination')),
3019 ('a', 'all', None, _('bundle all changesets in the repository')),
3015 ('a', 'all', None, _('bundle all changesets in the repository')),
3020 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3016 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3021 ] + remoteopts,
3017 ] + remoteopts,
3022 _('hg bundle [-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3018 _('hg bundle [-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3023 "cat":
3019 "cat":
3024 (cat,
3020 (cat,
3025 [('o', 'output', '', _('print output to file with formatted name')),
3021 [('o', 'output', '', _('print output to file with formatted name')),
3026 ('r', 'rev', '', _('print the given revision')),
3022 ('r', 'rev', '', _('print the given revision')),
3027 ('', 'decode', None, _('apply any matching decode filter')),
3023 ('', 'decode', None, _('apply any matching decode filter')),
3028 ] + walkopts,
3024 ] + walkopts,
3029 _('hg cat [OPTION]... FILE...')),
3025 _('hg cat [OPTION]... FILE...')),
3030 "^clone":
3026 "^clone":
3031 (clone,
3027 (clone,
3032 [('U', 'noupdate', None,
3028 [('U', 'noupdate', None,
3033 _('the clone will only contain a repository (no working copy)')),
3029 _('the clone will only contain a repository (no working copy)')),
3034 ('r', 'rev', [],
3030 ('r', 'rev', [],
3035 _('a changeset you would like to have after cloning')),
3031 _('a changeset you would like to have after cloning')),
3036 ('', 'pull', None, _('use pull protocol to copy metadata')),
3032 ('', 'pull', None, _('use pull protocol to copy metadata')),
3037 ('', 'uncompressed', None,
3033 ('', 'uncompressed', None,
3038 _('use uncompressed transfer (fast over LAN)')),
3034 _('use uncompressed transfer (fast over LAN)')),
3039 ] + remoteopts,
3035 ] + remoteopts,
3040 _('hg clone [OPTION]... SOURCE [DEST]')),
3036 _('hg clone [OPTION]... SOURCE [DEST]')),
3041 "^commit|ci":
3037 "^commit|ci":
3042 (commit,
3038 (commit,
3043 [('A', 'addremove', None,
3039 [('A', 'addremove', None,
3044 _('mark new/missing files as added/removed before committing')),
3040 _('mark new/missing files as added/removed before committing')),
3045 ] + walkopts + commitopts + commitopts2,
3041 ] + walkopts + commitopts + commitopts2,
3046 _('hg commit [OPTION]... [FILE]...')),
3042 _('hg commit [OPTION]... [FILE]...')),
3047 "copy|cp":
3043 "copy|cp":
3048 (copy,
3044 (copy,
3049 [('A', 'after', None, _('record a copy that has already occurred')),
3045 [('A', 'after', None, _('record a copy that has already occurred')),
3050 ('f', 'force', None,
3046 ('f', 'force', None,
3051 _('forcibly copy over an existing managed file')),
3047 _('forcibly copy over an existing managed file')),
3052 ] + walkopts + dryrunopts,
3048 ] + walkopts + dryrunopts,
3053 _('hg copy [OPTION]... [SOURCE]... DEST')),
3049 _('hg copy [OPTION]... [SOURCE]... DEST')),
3054 "debugancestor": (debugancestor, [],
3050 "debugancestor": (debugancestor, [],
3055 _('hg debugancestor [INDEX] REV1 REV2')),
3051 _('hg debugancestor [INDEX] REV1 REV2')),
3056 "debugcheckstate": (debugcheckstate, [], _('hg debugcheckstate')),
3052 "debugcheckstate": (debugcheckstate, [], _('hg debugcheckstate')),
3057 "debugcomplete":
3053 "debugcomplete":
3058 (debugcomplete,
3054 (debugcomplete,
3059 [('o', 'options', None, _('show the command options'))],
3055 [('o', 'options', None, _('show the command options'))],
3060 _('hg debugcomplete [-o] CMD')),
3056 _('hg debugcomplete [-o] CMD')),
3061 "debugdate":
3057 "debugdate":
3062 (debugdate,
3058 (debugdate,
3063 [('e', 'extended', None, _('try extended date formats'))],
3059 [('e', 'extended', None, _('try extended date formats'))],
3064 _('hg debugdate [-e] DATE [RANGE]')),
3060 _('hg debugdate [-e] DATE [RANGE]')),
3065 "debugdata": (debugdata, [], _('hg debugdata FILE REV')),
3061 "debugdata": (debugdata, [], _('hg debugdata FILE REV')),
3066 "debugfsinfo": (debugfsinfo, [], _('hg debugfsinfo [PATH]')),
3062 "debugfsinfo": (debugfsinfo, [], _('hg debugfsinfo [PATH]')),
3067 "debugindex": (debugindex, [], _('hg debugindex FILE')),
3063 "debugindex": (debugindex, [], _('hg debugindex FILE')),
3068 "debugindexdot": (debugindexdot, [], _('hg debugindexdot FILE')),
3064 "debugindexdot": (debugindexdot, [], _('hg debugindexdot FILE')),
3069 "debuginstall": (debuginstall, [], _('hg debuginstall')),
3065 "debuginstall": (debuginstall, [], _('hg debuginstall')),
3070 "debugrawcommit|rawcommit":
3066 "debugrawcommit|rawcommit":
3071 (rawcommit,
3067 (rawcommit,
3072 [('p', 'parent', [], _('parent')),
3068 [('p', 'parent', [], _('parent')),
3073 ('F', 'files', '', _('file list'))
3069 ('F', 'files', '', _('file list'))
3074 ] + commitopts + commitopts2,
3070 ] + commitopts + commitopts2,
3075 _('hg debugrawcommit [OPTION]... [FILE]...')),
3071 _('hg debugrawcommit [OPTION]... [FILE]...')),
3076 "debugrebuildstate":
3072 "debugrebuildstate":
3077 (debugrebuildstate,
3073 (debugrebuildstate,
3078 [('r', 'rev', '', _('revision to rebuild to'))],
3074 [('r', 'rev', '', _('revision to rebuild to'))],
3079 _('hg debugrebuildstate [-r REV] [REV]')),
3075 _('hg debugrebuildstate [-r REV] [REV]')),
3080 "debugrename":
3076 "debugrename":
3081 (debugrename,
3077 (debugrename,
3082 [('r', 'rev', '', _('revision to debug'))],
3078 [('r', 'rev', '', _('revision to debug'))],
3083 _('hg debugrename [-r REV] FILE')),
3079 _('hg debugrename [-r REV] FILE')),
3084 "debugsetparents":
3080 "debugsetparents":
3085 (debugsetparents,
3081 (debugsetparents,
3086 [],
3082 [],
3087 _('hg debugsetparents REV1 [REV2]')),
3083 _('hg debugsetparents REV1 [REV2]')),
3088 "debugstate":
3084 "debugstate":
3089 (debugstate,
3085 (debugstate,
3090 [('', 'nodates', None, _('do not display the saved mtime'))],
3086 [('', 'nodates', None, _('do not display the saved mtime'))],
3091 _('hg debugstate [OPTS]')),
3087 _('hg debugstate [OPTS]')),
3092 "debugwalk": (debugwalk, walkopts, _('hg debugwalk [OPTION]... [FILE]...')),
3088 "debugwalk": (debugwalk, walkopts, _('hg debugwalk [OPTION]... [FILE]...')),
3093 "^diff":
3089 "^diff":
3094 (diff,
3090 (diff,
3095 [('r', 'rev', [], _('revision'))
3091 [('r', 'rev', [], _('revision'))
3096 ] + diffopts + diffopts2 + walkopts,
3092 ] + diffopts + diffopts2 + walkopts,
3097 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3093 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3098 "^export":
3094 "^export":
3099 (export,
3095 (export,
3100 [('o', 'output', '', _('print output to file with formatted name')),
3096 [('o', 'output', '', _('print output to file with formatted name')),
3101 ('', 'switch-parent', None, _('diff against the second parent'))
3097 ('', 'switch-parent', None, _('diff against the second parent'))
3102 ] + diffopts,
3098 ] + diffopts,
3103 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
3099 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
3104 "grep":
3100 "grep":
3105 (grep,
3101 (grep,
3106 [('0', 'print0', None, _('end fields with NUL')),
3102 [('0', 'print0', None, _('end fields with NUL')),
3107 ('', 'all', None, _('print all revisions that match')),
3103 ('', 'all', None, _('print all revisions that match')),
3108 ('f', 'follow', None,
3104 ('f', 'follow', None,
3109 _('follow changeset history, or file history across copies and renames')),
3105 _('follow changeset history, or file history across copies and renames')),
3110 ('i', 'ignore-case', None, _('ignore case when matching')),
3106 ('i', 'ignore-case', None, _('ignore case when matching')),
3111 ('l', 'files-with-matches', None,
3107 ('l', 'files-with-matches', None,
3112 _('print only filenames and revs that match')),
3108 _('print only filenames and revs that match')),
3113 ('n', 'line-number', None, _('print matching line numbers')),
3109 ('n', 'line-number', None, _('print matching line numbers')),
3114 ('r', 'rev', [], _('search in given revision range')),
3110 ('r', 'rev', [], _('search in given revision range')),
3115 ('u', 'user', None, _('list the author (long with -v)')),
3111 ('u', 'user', None, _('list the author (long with -v)')),
3116 ('d', 'date', None, _('list the date (short with -q)')),
3112 ('d', 'date', None, _('list the date (short with -q)')),
3117 ] + walkopts,
3113 ] + walkopts,
3118 _('hg grep [OPTION]... PATTERN [FILE]...')),
3114 _('hg grep [OPTION]... PATTERN [FILE]...')),
3119 "heads":
3115 "heads":
3120 (heads,
3116 (heads,
3121 [('r', 'rev', '', _('show only heads which are descendants of rev')),
3117 [('r', 'rev', '', _('show only heads which are descendants of rev')),
3122 ] + templateopts,
3118 ] + templateopts,
3123 _('hg heads [-r REV] [REV]...')),
3119 _('hg heads [-r REV] [REV]...')),
3124 "help": (help_, [], _('hg help [COMMAND]')),
3120 "help": (help_, [], _('hg help [COMMAND]')),
3125 "identify|id":
3121 "identify|id":
3126 (identify,
3122 (identify,
3127 [('r', 'rev', '', _('identify the specified rev')),
3123 [('r', 'rev', '', _('identify the specified rev')),
3128 ('n', 'num', None, _('show local revision number')),
3124 ('n', 'num', None, _('show local revision number')),
3129 ('i', 'id', None, _('show global revision id')),
3125 ('i', 'id', None, _('show global revision id')),
3130 ('b', 'branch', None, _('show branch')),
3126 ('b', 'branch', None, _('show branch')),
3131 ('t', 'tags', None, _('show tags'))],
3127 ('t', 'tags', None, _('show tags'))],
3132 _('hg identify [-nibt] [-r REV] [SOURCE]')),
3128 _('hg identify [-nibt] [-r REV] [SOURCE]')),
3133 "import|patch":
3129 "import|patch":
3134 (import_,
3130 (import_,
3135 [('p', 'strip', 1,
3131 [('p', 'strip', 1,
3136 _('directory strip option for patch. This has the same\n'
3132 _('directory strip option for patch. This has the same\n'
3137 'meaning as the corresponding patch option')),
3133 'meaning as the corresponding patch option')),
3138 ('b', 'base', '', _('base path')),
3134 ('b', 'base', '', _('base path')),
3139 ('f', 'force', None,
3135 ('f', 'force', None,
3140 _('skip check for outstanding uncommitted changes')),
3136 _('skip check for outstanding uncommitted changes')),
3141 ('', 'no-commit', None, _("don't commit, just update the working directory")),
3137 ('', 'no-commit', None, _("don't commit, just update the working directory")),
3142 ('', 'exact', None,
3138 ('', 'exact', None,
3143 _('apply patch to the nodes from which it was generated')),
3139 _('apply patch to the nodes from which it was generated')),
3144 ('', 'import-branch', None,
3140 ('', 'import-branch', None,
3145 _('Use any branch information in patch (implied by --exact)'))] +
3141 _('Use any branch information in patch (implied by --exact)'))] +
3146 commitopts + commitopts2,
3142 commitopts + commitopts2,
3147 _('hg import [OPTION]... PATCH...')),
3143 _('hg import [OPTION]... PATCH...')),
3148 "incoming|in":
3144 "incoming|in":
3149 (incoming,
3145 (incoming,
3150 [('f', 'force', None,
3146 [('f', 'force', None,
3151 _('run even when remote repository is unrelated')),
3147 _('run even when remote repository is unrelated')),
3152 ('n', 'newest-first', None, _('show newest record first')),
3148 ('n', 'newest-first', None, _('show newest record first')),
3153 ('', 'bundle', '', _('file to store the bundles into')),
3149 ('', 'bundle', '', _('file to store the bundles into')),
3154 ('r', 'rev', [],
3150 ('r', 'rev', [],
3155 _('a specific revision up to which you would like to pull')),
3151 _('a specific revision up to which you would like to pull')),
3156 ] + logopts + remoteopts,
3152 ] + logopts + remoteopts,
3157 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
3153 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
3158 ' [--bundle FILENAME] [SOURCE]')),
3154 ' [--bundle FILENAME] [SOURCE]')),
3159 "^init":
3155 "^init":
3160 (init,
3156 (init,
3161 remoteopts,
3157 remoteopts,
3162 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
3158 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
3163 "locate":
3159 "locate":
3164 (locate,
3160 (locate,
3165 [('r', 'rev', '', _('search the repository as it stood at rev')),
3161 [('r', 'rev', '', _('search the repository as it stood at rev')),
3166 ('0', 'print0', None,
3162 ('0', 'print0', None,
3167 _('end filenames with NUL, for use with xargs')),
3163 _('end filenames with NUL, for use with xargs')),
3168 ('f', 'fullpath', None,
3164 ('f', 'fullpath', None,
3169 _('print complete paths from the filesystem root')),
3165 _('print complete paths from the filesystem root')),
3170 ] + walkopts,
3166 ] + walkopts,
3171 _('hg locate [OPTION]... [PATTERN]...')),
3167 _('hg locate [OPTION]... [PATTERN]...')),
3172 "^log|history":
3168 "^log|history":
3173 (log,
3169 (log,
3174 [('f', 'follow', None,
3170 [('f', 'follow', None,
3175 _('follow changeset history, or file history across copies and renames')),
3171 _('follow changeset history, or file history across copies and renames')),
3176 ('', 'follow-first', None,
3172 ('', 'follow-first', None,
3177 _('only follow the first parent of merge changesets')),
3173 _('only follow the first parent of merge changesets')),
3178 ('d', 'date', '', _('show revs matching date spec')),
3174 ('d', 'date', '', _('show revs matching date spec')),
3179 ('C', 'copies', None, _('show copied files')),
3175 ('C', 'copies', None, _('show copied files')),
3180 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3176 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3181 ('r', 'rev', [], _('show the specified revision or range')),
3177 ('r', 'rev', [], _('show the specified revision or range')),
3182 ('', 'removed', None, _('include revs where files were removed')),
3178 ('', 'removed', None, _('include revs where files were removed')),
3183 ('m', 'only-merges', None, _('show only merges')),
3179 ('m', 'only-merges', None, _('show only merges')),
3184 ('b', 'only-branch', [],
3180 ('b', 'only-branch', [],
3185 _('show only changesets within the given named branch')),
3181 _('show only changesets within the given named branch')),
3186 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3182 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3187 ] + logopts + walkopts,
3183 ] + logopts + walkopts,
3188 _('hg log [OPTION]... [FILE]')),
3184 _('hg log [OPTION]... [FILE]')),
3189 "manifest":
3185 "manifest":
3190 (manifest,
3186 (manifest,
3191 [('r', 'rev', '', _('revision to display'))],
3187 [('r', 'rev', '', _('revision to display'))],
3192 _('hg manifest [-r REV]')),
3188 _('hg manifest [-r REV]')),
3193 "^merge":
3189 "^merge":
3194 (merge,
3190 (merge,
3195 [('f', 'force', None, _('force a merge with outstanding changes')),
3191 [('f', 'force', None, _('force a merge with outstanding changes')),
3196 ('r', 'rev', '', _('revision to merge')),
3192 ('r', 'rev', '', _('revision to merge')),
3197 ],
3193 ],
3198 _('hg merge [-f] [[-r] REV]')),
3194 _('hg merge [-f] [[-r] REV]')),
3199 "outgoing|out":
3195 "outgoing|out":
3200 (outgoing,
3196 (outgoing,
3201 [('f', 'force', None,
3197 [('f', 'force', None,
3202 _('run even when remote repository is unrelated')),
3198 _('run even when remote repository is unrelated')),
3203 ('r', 'rev', [],
3199 ('r', 'rev', [],
3204 _('a specific revision up to which you would like to push')),
3200 _('a specific revision up to which you would like to push')),
3205 ('n', 'newest-first', None, _('show newest record first')),
3201 ('n', 'newest-first', None, _('show newest record first')),
3206 ] + logopts + remoteopts,
3202 ] + logopts + remoteopts,
3207 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3203 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3208 "^parents":
3204 "^parents":
3209 (parents,
3205 (parents,
3210 [('r', 'rev', '', _('show parents from the specified rev')),
3206 [('r', 'rev', '', _('show parents from the specified rev')),
3211 ] + templateopts,
3207 ] + templateopts,
3212 _('hg parents [-r REV] [FILE]')),
3208 _('hg parents [-r REV] [FILE]')),
3213 "paths": (paths, [], _('hg paths [NAME]')),
3209 "paths": (paths, [], _('hg paths [NAME]')),
3214 "^pull":
3210 "^pull":
3215 (pull,
3211 (pull,
3216 [('u', 'update', None,
3212 [('u', 'update', None,
3217 _('update to new tip if changesets were pulled')),
3213 _('update to new tip if changesets were pulled')),
3218 ('f', 'force', None,
3214 ('f', 'force', None,
3219 _('run even when remote repository is unrelated')),
3215 _('run even when remote repository is unrelated')),
3220 ('r', 'rev', [],
3216 ('r', 'rev', [],
3221 _('a specific revision up to which you would like to pull')),
3217 _('a specific revision up to which you would like to pull')),
3222 ] + remoteopts,
3218 ] + remoteopts,
3223 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3219 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3224 "^push":
3220 "^push":
3225 (push,
3221 (push,
3226 [('f', 'force', None, _('force push')),
3222 [('f', 'force', None, _('force push')),
3227 ('r', 'rev', [],
3223 ('r', 'rev', [],
3228 _('a specific revision up to which you would like to push')),
3224 _('a specific revision up to which you would like to push')),
3229 ] + remoteopts,
3225 ] + remoteopts,
3230 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3226 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3231 "recover": (recover, [], _('hg recover')),
3227 "recover": (recover, [], _('hg recover')),
3232 "^remove|rm":
3228 "^remove|rm":
3233 (remove,
3229 (remove,
3234 [('A', 'after', None, _('record delete for missing files')),
3230 [('A', 'after', None, _('record delete for missing files')),
3235 ('f', 'force', None,
3231 ('f', 'force', None,
3236 _('remove (and delete) file even if added or modified')),
3232 _('remove (and delete) file even if added or modified')),
3237 ] + walkopts,
3233 ] + walkopts,
3238 _('hg remove [OPTION]... FILE...')),
3234 _('hg remove [OPTION]... FILE...')),
3239 "rename|mv":
3235 "rename|mv":
3240 (rename,
3236 (rename,
3241 [('A', 'after', None, _('record a rename that has already occurred')),
3237 [('A', 'after', None, _('record a rename that has already occurred')),
3242 ('f', 'force', None,
3238 ('f', 'force', None,
3243 _('forcibly copy over an existing managed file')),
3239 _('forcibly copy over an existing managed file')),
3244 ] + walkopts + dryrunopts,
3240 ] + walkopts + dryrunopts,
3245 _('hg rename [OPTION]... SOURCE... DEST')),
3241 _('hg rename [OPTION]... SOURCE... DEST')),
3246 "resolve":
3242 "resolve":
3247 (resolve,
3243 (resolve,
3248 [('l', 'list', None, _('list state of files needing merge')),
3244 [('l', 'list', None, _('list state of files needing merge')),
3249 ('m', 'mark', None, _('mark files as resolved')),
3245 ('m', 'mark', None, _('mark files as resolved')),
3250 ('u', 'unmark', None, _('unmark files as resolved'))],
3246 ('u', 'unmark', None, _('unmark files as resolved'))],
3251 ('hg resolve [OPTION] [FILES...]')),
3247 ('hg resolve [OPTION] [FILES...]')),
3252 "revert":
3248 "revert":
3253 (revert,
3249 (revert,
3254 [('a', 'all', None, _('revert all changes when no arguments given')),
3250 [('a', 'all', None, _('revert all changes when no arguments given')),
3255 ('d', 'date', '', _('tipmost revision matching date')),
3251 ('d', 'date', '', _('tipmost revision matching date')),
3256 ('r', 'rev', '', _('revision to revert to')),
3252 ('r', 'rev', '', _('revision to revert to')),
3257 ('', 'no-backup', None, _('do not save backup copies of files')),
3253 ('', 'no-backup', None, _('do not save backup copies of files')),
3258 ] + walkopts + dryrunopts,
3254 ] + walkopts + dryrunopts,
3259 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3255 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3260 "rollback": (rollback, [], _('hg rollback')),
3256 "rollback": (rollback, [], _('hg rollback')),
3261 "root": (root, [], _('hg root')),
3257 "root": (root, [], _('hg root')),
3262 "^serve":
3258 "^serve":
3263 (serve,
3259 (serve,
3264 [('A', 'accesslog', '', _('name of access log file to write to')),
3260 [('A', 'accesslog', '', _('name of access log file to write to')),
3265 ('d', 'daemon', None, _('run server in background')),
3261 ('d', 'daemon', None, _('run server in background')),
3266 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3262 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3267 ('E', 'errorlog', '', _('name of error log file to write to')),
3263 ('E', 'errorlog', '', _('name of error log file to write to')),
3268 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3264 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3269 ('a', 'address', '', _('address to listen on (default: all interfaces)')),
3265 ('a', 'address', '', _('address to listen on (default: all interfaces)')),
3270 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3266 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3271 ('n', 'name', '',
3267 ('n', 'name', '',
3272 _('name to show in web pages (default: working dir)')),
3268 _('name to show in web pages (default: working dir)')),
3273 ('', 'webdir-conf', '', _('name of the webdir config file'
3269 ('', 'webdir-conf', '', _('name of the webdir config file'
3274 ' (serve more than one repo)')),
3270 ' (serve more than one repo)')),
3275 ('', 'pid-file', '', _('name of file to write process ID to')),
3271 ('', 'pid-file', '', _('name of file to write process ID to')),
3276 ('', 'stdio', None, _('for remote clients')),
3272 ('', 'stdio', None, _('for remote clients')),
3277 ('t', 'templates', '', _('web templates to use')),
3273 ('t', 'templates', '', _('web templates to use')),
3278 ('', 'style', '', _('template style to use')),
3274 ('', 'style', '', _('template style to use')),
3279 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3275 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3280 ('', 'certificate', '', _('SSL certificate file'))],
3276 ('', 'certificate', '', _('SSL certificate file'))],
3281 _('hg serve [OPTION]...')),
3277 _('hg serve [OPTION]...')),
3282 "showconfig|debugconfig":
3278 "showconfig|debugconfig":
3283 (showconfig,
3279 (showconfig,
3284 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3280 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3285 _('hg showconfig [-u] [NAME]...')),
3281 _('hg showconfig [-u] [NAME]...')),
3286 "^status|st":
3282 "^status|st":
3287 (status,
3283 (status,
3288 [('A', 'all', None, _('show status of all files')),
3284 [('A', 'all', None, _('show status of all files')),
3289 ('m', 'modified', None, _('show only modified files')),
3285 ('m', 'modified', None, _('show only modified files')),
3290 ('a', 'added', None, _('show only added files')),
3286 ('a', 'added', None, _('show only added files')),
3291 ('r', 'removed', None, _('show only removed files')),
3287 ('r', 'removed', None, _('show only removed files')),
3292 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3288 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3293 ('c', 'clean', None, _('show only files without changes')),
3289 ('c', 'clean', None, _('show only files without changes')),
3294 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3290 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3295 ('i', 'ignored', None, _('show only ignored files')),
3291 ('i', 'ignored', None, _('show only ignored files')),
3296 ('n', 'no-status', None, _('hide status prefix')),
3292 ('n', 'no-status', None, _('hide status prefix')),
3297 ('C', 'copies', None, _('show source of copied files')),
3293 ('C', 'copies', None, _('show source of copied files')),
3298 ('0', 'print0', None,
3294 ('0', 'print0', None,
3299 _('end filenames with NUL, for use with xargs')),
3295 _('end filenames with NUL, for use with xargs')),
3300 ('', 'rev', [], _('show difference from revision')),
3296 ('', 'rev', [], _('show difference from revision')),
3301 ] + walkopts,
3297 ] + walkopts,
3302 _('hg status [OPTION]... [FILE]...')),
3298 _('hg status [OPTION]... [FILE]...')),
3303 "tag":
3299 "tag":
3304 (tag,
3300 (tag,
3305 [('f', 'force', None, _('replace existing tag')),
3301 [('f', 'force', None, _('replace existing tag')),
3306 ('l', 'local', None, _('make the tag local')),
3302 ('l', 'local', None, _('make the tag local')),
3307 ('r', 'rev', '', _('revision to tag')),
3303 ('r', 'rev', '', _('revision to tag')),
3308 ('', 'remove', None, _('remove a tag')),
3304 ('', 'remove', None, _('remove a tag')),
3309 # -l/--local is already there, commitopts cannot be used
3305 # -l/--local is already there, commitopts cannot be used
3310 ('m', 'message', '', _('use <text> as commit message')),
3306 ('m', 'message', '', _('use <text> as commit message')),
3311 ] + commitopts2,
3307 ] + commitopts2,
3312 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3308 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3313 "tags": (tags, [], _('hg tags')),
3309 "tags": (tags, [], _('hg tags')),
3314 "tip":
3310 "tip":
3315 (tip,
3311 (tip,
3316 [('p', 'patch', None, _('show patch')),
3312 [('p', 'patch', None, _('show patch')),
3317 ] + templateopts,
3313 ] + templateopts,
3318 _('hg tip [-p]')),
3314 _('hg tip [-p]')),
3319 "unbundle":
3315 "unbundle":
3320 (unbundle,
3316 (unbundle,
3321 [('u', 'update', None,
3317 [('u', 'update', None,
3322 _('update to new tip if changesets were unbundled'))],
3318 _('update to new tip if changesets were unbundled'))],
3323 _('hg unbundle [-u] FILE...')),
3319 _('hg unbundle [-u] FILE...')),
3324 "^update|up|checkout|co":
3320 "^update|up|checkout|co":
3325 (update,
3321 (update,
3326 [('C', 'clean', None, _('overwrite locally modified files (no backup)')),
3322 [('C', 'clean', None, _('overwrite locally modified files (no backup)')),
3327 ('d', 'date', '', _('tipmost revision matching date')),
3323 ('d', 'date', '', _('tipmost revision matching date')),
3328 ('r', 'rev', '', _('revision'))],
3324 ('r', 'rev', '', _('revision'))],
3329 _('hg update [-C] [-d DATE] [[-r] REV]')),
3325 _('hg update [-C] [-d DATE] [[-r] REV]')),
3330 "verify": (verify, [], _('hg verify')),
3326 "verify": (verify, [], _('hg verify')),
3331 "version": (version_, [], _('hg version')),
3327 "version": (version_, [], _('hg version')),
3332 }
3328 }
3333
3329
3334 norepo = ("clone init version help debugcomplete debugdata"
3330 norepo = ("clone init version help debugcomplete debugdata"
3335 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3331 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3336 optionalrepo = ("identify paths serve showconfig debugancestor")
3332 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,698 +1,698 b''
1 """
1 """
2 dirstate.py - working directory tracking for mercurial
2 dirstate.py - working directory tracking for mercurial
3
3
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
9
10 from node import nullid
10 from node import nullid
11 from i18n import _
11 from i18n import _
12 import struct, os, bisect, stat, strutil, util, errno, ignore
12 import struct, os, bisect, stat, strutil, util, errno, ignore
13 import cStringIO, osutil, sys
13 import cStringIO, osutil, sys
14
14
15 _unknown = ('?', 0, 0, 0)
15 _unknown = ('?', 0, 0, 0)
16 _format = ">cllll"
16 _format = ">cllll"
17
17
18 class dirstate(object):
18 class dirstate(object):
19
19
20 def __init__(self, opener, ui, root):
20 def __init__(self, opener, ui, root):
21 self._opener = opener
21 self._opener = opener
22 self._root = root
22 self._root = root
23 self._dirty = False
23 self._dirty = False
24 self._dirtypl = False
24 self._dirtypl = False
25 self._ui = ui
25 self._ui = ui
26
26
27 def __getattr__(self, name):
27 def __getattr__(self, name):
28 if name == '_map':
28 if name == '_map':
29 self._read()
29 self._read()
30 return self._map
30 return self._map
31 elif name == '_copymap':
31 elif name == '_copymap':
32 self._read()
32 self._read()
33 return self._copymap
33 return self._copymap
34 elif name == '_foldmap':
34 elif name == '_foldmap':
35 _foldmap = {}
35 _foldmap = {}
36 for name in self._map:
36 for name in self._map:
37 norm = os.path.normcase(os.path.normpath(name))
37 norm = os.path.normcase(os.path.normpath(name))
38 _foldmap[norm] = name
38 _foldmap[norm] = name
39 self._foldmap = _foldmap
39 self._foldmap = _foldmap
40 return self._foldmap
40 return self._foldmap
41 elif name == '_branch':
41 elif name == '_branch':
42 try:
42 try:
43 self._branch = (self._opener("branch").read().strip()
43 self._branch = (self._opener("branch").read().strip()
44 or "default")
44 or "default")
45 except IOError:
45 except IOError:
46 self._branch = "default"
46 self._branch = "default"
47 return self._branch
47 return self._branch
48 elif name == '_pl':
48 elif name == '_pl':
49 self._pl = [nullid, nullid]
49 self._pl = [nullid, nullid]
50 try:
50 try:
51 st = self._opener("dirstate").read(40)
51 st = self._opener("dirstate").read(40)
52 if len(st) == 40:
52 if len(st) == 40:
53 self._pl = st[:20], st[20:40]
53 self._pl = st[:20], st[20:40]
54 except IOError, err:
54 except IOError, err:
55 if err.errno != errno.ENOENT: raise
55 if err.errno != errno.ENOENT: raise
56 return self._pl
56 return self._pl
57 elif name == '_dirs':
57 elif name == '_dirs':
58 self._dirs = {}
58 self._dirs = {}
59 for f in self._map:
59 for f in self._map:
60 if self[f] != 'r':
60 if self[f] != 'r':
61 self._incpath(f)
61 self._incpath(f)
62 return self._dirs
62 return self._dirs
63 elif name == '_ignore':
63 elif name == '_ignore':
64 files = [self._join('.hgignore')]
64 files = [self._join('.hgignore')]
65 for name, path in self._ui.configitems("ui"):
65 for name, path in self._ui.configitems("ui"):
66 if name == 'ignore' or name.startswith('ignore.'):
66 if name == 'ignore' or name.startswith('ignore.'):
67 files.append(os.path.expanduser(path))
67 files.append(os.path.expanduser(path))
68 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
68 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
69 return self._ignore
69 return self._ignore
70 elif name == '_slash':
70 elif name == '_slash':
71 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
71 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
72 return self._slash
72 return self._slash
73 elif name == '_checklink':
73 elif name == '_checklink':
74 self._checklink = util.checklink(self._root)
74 self._checklink = util.checklink(self._root)
75 return self._checklink
75 return self._checklink
76 elif name == '_checkexec':
76 elif name == '_checkexec':
77 self._checkexec = util.checkexec(self._root)
77 self._checkexec = util.checkexec(self._root)
78 return self._checkexec
78 return self._checkexec
79 elif name == '_checkcase':
79 elif name == '_checkcase':
80 self._checkcase = not util.checkcase(self._join('.hg'))
80 self._checkcase = not util.checkcase(self._join('.hg'))
81 return self._checkcase
81 return self._checkcase
82 elif name == 'normalize':
82 elif name == 'normalize':
83 if self._checkcase:
83 if self._checkcase:
84 self.normalize = self._normalize
84 self.normalize = self._normalize
85 else:
85 else:
86 self.normalize = lambda x: x
86 self.normalize = lambda x: x
87 return self.normalize
87 return self.normalize
88 else:
88 else:
89 raise AttributeError, name
89 raise AttributeError, name
90
90
91 def _join(self, f):
91 def _join(self, f):
92 return os.path.join(self._root, f)
92 return os.path.join(self._root, f)
93
93
94 def flagfunc(self, fallback):
94 def flagfunc(self, fallback):
95 if self._checklink:
95 if self._checklink:
96 if self._checkexec:
96 if self._checkexec:
97 def f(x):
97 def f(x):
98 p = os.path.join(self._root, x)
98 p = os.path.join(self._root, x)
99 if os.path.islink(p):
99 if os.path.islink(p):
100 return 'l'
100 return 'l'
101 if util.is_exec(p):
101 if util.is_exec(p):
102 return 'x'
102 return 'x'
103 return ''
103 return ''
104 return f
104 return f
105 def f(x):
105 def f(x):
106 if os.path.islink(os.path.join(self._root, x)):
106 if os.path.islink(os.path.join(self._root, x)):
107 return 'l'
107 return 'l'
108 if 'x' in fallback(x):
108 if 'x' in fallback(x):
109 return 'x'
109 return 'x'
110 return ''
110 return ''
111 return f
111 return f
112 if self._checkexec:
112 if self._checkexec:
113 def f(x):
113 def f(x):
114 if 'l' in fallback(x):
114 if 'l' in fallback(x):
115 return 'l'
115 return 'l'
116 if util.is_exec(os.path.join(self._root, x)):
116 if util.is_exec(os.path.join(self._root, x)):
117 return 'x'
117 return 'x'
118 return ''
118 return ''
119 return f
119 return f
120 return fallback
120 return fallback
121
121
122 def getcwd(self):
122 def getcwd(self):
123 cwd = os.getcwd()
123 cwd = os.getcwd()
124 if cwd == self._root: return ''
124 if cwd == self._root: return ''
125 # self._root ends with a path separator if self._root is '/' or 'C:\'
125 # self._root ends with a path separator if self._root is '/' or 'C:\'
126 rootsep = self._root
126 rootsep = self._root
127 if not util.endswithsep(rootsep):
127 if not util.endswithsep(rootsep):
128 rootsep += os.sep
128 rootsep += os.sep
129 if cwd.startswith(rootsep):
129 if cwd.startswith(rootsep):
130 return cwd[len(rootsep):]
130 return cwd[len(rootsep):]
131 else:
131 else:
132 # we're outside the repo. return an absolute path.
132 # we're outside the repo. return an absolute path.
133 return cwd
133 return cwd
134
134
135 def pathto(self, f, cwd=None):
135 def pathto(self, f, cwd=None):
136 if cwd is None:
136 if cwd is None:
137 cwd = self.getcwd()
137 cwd = self.getcwd()
138 path = util.pathto(self._root, cwd, f)
138 path = util.pathto(self._root, cwd, f)
139 if self._slash:
139 if self._slash:
140 return util.normpath(path)
140 return util.normpath(path)
141 return path
141 return path
142
142
143 def __getitem__(self, key):
143 def __getitem__(self, key):
144 ''' current states:
144 ''' current states:
145 n normal
145 n normal
146 m needs merging
146 m needs merging
147 r marked for removal
147 r marked for removal
148 a marked for addition
148 a marked for addition
149 ? not tracked'''
149 ? not tracked'''
150 return self._map.get(key, ("?",))[0]
150 return self._map.get(key, ("?",))[0]
151
151
152 def __contains__(self, key):
152 def __contains__(self, key):
153 return key in self._map
153 return key in self._map
154
154
155 def __iter__(self):
155 def __iter__(self):
156 a = self._map.keys()
156 a = self._map.keys()
157 a.sort()
157 a.sort()
158 for x in a:
158 for x in a:
159 yield x
159 yield x
160
160
161 def parents(self):
161 def parents(self):
162 return self._pl
162 return self._pl
163
163
164 def branch(self):
164 def branch(self):
165 return self._branch
165 return self._branch
166
166
167 def setparents(self, p1, p2=nullid):
167 def setparents(self, p1, p2=nullid):
168 self._dirty = self._dirtypl = True
168 self._dirty = self._dirtypl = True
169 self._pl = p1, p2
169 self._pl = p1, p2
170
170
171 def setbranch(self, branch):
171 def setbranch(self, branch):
172 self._branch = branch
172 self._branch = branch
173 self._opener("branch", "w").write(branch + '\n')
173 self._opener("branch", "w").write(branch + '\n')
174
174
175 def _read(self):
175 def _read(self):
176 self._map = {}
176 self._map = {}
177 self._copymap = {}
177 self._copymap = {}
178 if not self._dirtypl:
178 if not self._dirtypl:
179 self._pl = [nullid, nullid]
179 self._pl = [nullid, nullid]
180 try:
180 try:
181 st = self._opener("dirstate").read()
181 st = self._opener("dirstate").read()
182 except IOError, err:
182 except IOError, err:
183 if err.errno != errno.ENOENT: raise
183 if err.errno != errno.ENOENT: raise
184 return
184 return
185 if not st:
185 if not st:
186 return
186 return
187
187
188 if not self._dirtypl:
188 if not self._dirtypl:
189 self._pl = [st[:20], st[20: 40]]
189 self._pl = [st[:20], st[20: 40]]
190
190
191 # deref fields so they will be local in loop
191 # deref fields so they will be local in loop
192 dmap = self._map
192 dmap = self._map
193 copymap = self._copymap
193 copymap = self._copymap
194 unpack = struct.unpack
194 unpack = struct.unpack
195 e_size = struct.calcsize(_format)
195 e_size = struct.calcsize(_format)
196 pos1 = 40
196 pos1 = 40
197 l = len(st)
197 l = len(st)
198
198
199 # the inner loop
199 # the inner loop
200 while pos1 < l:
200 while pos1 < l:
201 pos2 = pos1 + e_size
201 pos2 = pos1 + e_size
202 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
202 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
203 pos1 = pos2 + e[4]
203 pos1 = pos2 + e[4]
204 f = st[pos2:pos1]
204 f = st[pos2:pos1]
205 if '\0' in f:
205 if '\0' in f:
206 f, c = f.split('\0')
206 f, c = f.split('\0')
207 copymap[f] = c
207 copymap[f] = c
208 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
208 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
209
209
210 def invalidate(self):
210 def invalidate(self):
211 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
211 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
212 if a in self.__dict__:
212 if a in self.__dict__:
213 delattr(self, a)
213 delattr(self, a)
214 self._dirty = False
214 self._dirty = False
215
215
216 def copy(self, source, dest):
216 def copy(self, source, dest):
217 if source == dest:
217 if source == dest:
218 return
218 return
219 self._dirty = True
219 self._dirty = True
220 self._copymap[dest] = source
220 self._copymap[dest] = source
221
221
222 def copied(self, file):
222 def copied(self, file):
223 return self._copymap.get(file, None)
223 return self._copymap.get(file, None)
224
224
225 def copies(self):
225 def copies(self):
226 return self._copymap
226 return self._copymap
227
227
228 def _incpath(self, path):
228 def _incpath(self, path):
229 c = path.rfind('/')
229 c = path.rfind('/')
230 if c >= 0:
230 if c >= 0:
231 dirs = self._dirs
231 dirs = self._dirs
232 base = path[:c]
232 base = path[:c]
233 if base not in dirs:
233 if base not in dirs:
234 self._incpath(base)
234 self._incpath(base)
235 dirs[base] = 1
235 dirs[base] = 1
236 else:
236 else:
237 dirs[base] += 1
237 dirs[base] += 1
238
238
239 def _decpath(self, path):
239 def _decpath(self, path):
240 c = path.rfind('/')
240 c = path.rfind('/')
241 if c >= 0:
241 if c >= 0:
242 base = path[:c]
242 base = path[:c]
243 dirs = self._dirs
243 dirs = self._dirs
244 if dirs[base] == 1:
244 if dirs[base] == 1:
245 del dirs[base]
245 del dirs[base]
246 self._decpath(base)
246 self._decpath(base)
247 else:
247 else:
248 dirs[base] -= 1
248 dirs[base] -= 1
249
249
250 def _incpathcheck(self, f):
250 def _incpathcheck(self, f):
251 if '\r' in f or '\n' in f:
251 if '\r' in f or '\n' in f:
252 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
252 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
253 % f)
253 % f)
254 # shadows
254 # shadows
255 if f in self._dirs:
255 if f in self._dirs:
256 raise util.Abort(_('directory %r already in dirstate') % f)
256 raise util.Abort(_('directory %r already in dirstate') % f)
257 for c in strutil.rfindall(f, '/'):
257 for c in strutil.rfindall(f, '/'):
258 d = f[:c]
258 d = f[:c]
259 if d in self._dirs:
259 if d in self._dirs:
260 break
260 break
261 if d in self._map and self[d] != 'r':
261 if d in self._map and self[d] != 'r':
262 raise util.Abort(_('file %r in dirstate clashes with %r') %
262 raise util.Abort(_('file %r in dirstate clashes with %r') %
263 (d, f))
263 (d, f))
264 self._incpath(f)
264 self._incpath(f)
265
265
266 def _changepath(self, f, newstate, relaxed=False):
266 def _changepath(self, f, newstate, relaxed=False):
267 # handle upcoming path changes
267 # handle upcoming path changes
268 oldstate = self[f]
268 oldstate = self[f]
269 if oldstate not in "?r" and newstate in "?r":
269 if oldstate not in "?r" and newstate in "?r":
270 if "_dirs" in self.__dict__:
270 if "_dirs" in self.__dict__:
271 self._decpath(f)
271 self._decpath(f)
272 return
272 return
273 if oldstate in "?r" and newstate not in "?r":
273 if oldstate in "?r" and newstate not in "?r":
274 if relaxed and oldstate == '?':
274 if relaxed and oldstate == '?':
275 # XXX
275 # XXX
276 # in relaxed mode we assume the caller knows
276 # in relaxed mode we assume the caller knows
277 # what it is doing, workaround for updating
277 # what it is doing, workaround for updating
278 # dir-to-file revisions
278 # dir-to-file revisions
279 if "_dirs" in self.__dict__:
279 if "_dirs" in self.__dict__:
280 self._incpath(f)
280 self._incpath(f)
281 return
281 return
282 self._incpathcheck(f)
282 self._incpathcheck(f)
283 return
283 return
284
284
285 def normal(self, f):
285 def normal(self, f):
286 'mark a file normal and clean'
286 'mark a file normal and clean'
287 self._dirty = True
287 self._dirty = True
288 self._changepath(f, 'n', True)
288 self._changepath(f, 'n', True)
289 s = os.lstat(self._join(f))
289 s = os.lstat(self._join(f))
290 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
290 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
291 if f in self._copymap:
291 if f in self._copymap:
292 del self._copymap[f]
292 del self._copymap[f]
293
293
294 def normallookup(self, f):
294 def normallookup(self, f):
295 'mark a file normal, but possibly dirty'
295 'mark a file normal, but possibly dirty'
296 if self._pl[1] != nullid and f in self._map:
296 if self._pl[1] != nullid and f in self._map:
297 # if there is a merge going on and the file was either
297 # if there is a merge going on and the file was either
298 # in state 'm' or dirty before being removed, restore that state.
298 # in state 'm' or dirty before being removed, restore that state.
299 entry = self._map[f]
299 entry = self._map[f]
300 if entry[0] == 'r' and entry[2] in (-1, -2):
300 if entry[0] == 'r' and entry[2] in (-1, -2):
301 source = self._copymap.get(f)
301 source = self._copymap.get(f)
302 if entry[2] == -1:
302 if entry[2] == -1:
303 self.merge(f)
303 self.merge(f)
304 elif entry[2] == -2:
304 elif entry[2] == -2:
305 self.normaldirty(f)
305 self.normaldirty(f)
306 if source:
306 if source:
307 self.copy(source, f)
307 self.copy(source, f)
308 return
308 return
309 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
309 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
310 return
310 return
311 self._dirty = True
311 self._dirty = True
312 self._changepath(f, 'n', True)
312 self._changepath(f, 'n', True)
313 self._map[f] = ('n', 0, -1, -1, 0)
313 self._map[f] = ('n', 0, -1, -1, 0)
314 if f in self._copymap:
314 if f in self._copymap:
315 del self._copymap[f]
315 del self._copymap[f]
316
316
317 def normaldirty(self, f):
317 def normaldirty(self, f):
318 'mark a file normal, but dirty'
318 'mark a file normal, but dirty'
319 self._dirty = True
319 self._dirty = True
320 self._changepath(f, 'n', True)
320 self._changepath(f, 'n', True)
321 self._map[f] = ('n', 0, -2, -1, 0)
321 self._map[f] = ('n', 0, -2, -1, 0)
322 if f in self._copymap:
322 if f in self._copymap:
323 del self._copymap[f]
323 del self._copymap[f]
324
324
325 def add(self, f):
325 def add(self, f):
326 'mark a file added'
326 'mark a file added'
327 self._dirty = True
327 self._dirty = True
328 self._changepath(f, 'a')
328 self._changepath(f, 'a')
329 self._map[f] = ('a', 0, -1, -1, 0)
329 self._map[f] = ('a', 0, -1, -1, 0)
330 if f in self._copymap:
330 if f in self._copymap:
331 del self._copymap[f]
331 del self._copymap[f]
332
332
333 def remove(self, f):
333 def remove(self, f):
334 'mark a file removed'
334 'mark a file removed'
335 self._dirty = True
335 self._dirty = True
336 self._changepath(f, 'r')
336 self._changepath(f, 'r')
337 size = 0
337 size = 0
338 if self._pl[1] != nullid and f in self._map:
338 if self._pl[1] != nullid and f in self._map:
339 entry = self._map[f]
339 entry = self._map[f]
340 if entry[0] == 'm':
340 if entry[0] == 'm':
341 size = -1
341 size = -1
342 elif entry[0] == 'n' and entry[2] == -2:
342 elif entry[0] == 'n' and entry[2] == -2:
343 size = -2
343 size = -2
344 self._map[f] = ('r', 0, size, 0, 0)
344 self._map[f] = ('r', 0, size, 0, 0)
345 if size == 0 and f in self._copymap:
345 if size == 0 and f in self._copymap:
346 del self._copymap[f]
346 del self._copymap[f]
347
347
348 def merge(self, f):
348 def merge(self, f):
349 'mark a file merged'
349 'mark a file merged'
350 self._dirty = True
350 self._dirty = True
351 s = os.lstat(self._join(f))
351 s = os.lstat(self._join(f))
352 self._changepath(f, 'm', True)
352 self._changepath(f, 'm', True)
353 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
353 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
354 if f in self._copymap:
354 if f in self._copymap:
355 del self._copymap[f]
355 del self._copymap[f]
356
356
357 def forget(self, f):
357 def forget(self, f):
358 'forget a file'
358 'forget a file'
359 self._dirty = True
359 self._dirty = True
360 try:
360 try:
361 self._changepath(f, '?')
361 self._changepath(f, '?')
362 del self._map[f]
362 del self._map[f]
363 except KeyError:
363 except KeyError:
364 self._ui.warn(_("not in dirstate: %s\n") % f)
364 self._ui.warn(_("not in dirstate: %s\n") % f)
365
365
366 def _normalize(self, path):
366 def _normalize(self, path):
367 normpath = os.path.normcase(os.path.normpath(path))
367 normpath = os.path.normcase(os.path.normpath(path))
368 if normpath in self._foldmap:
368 if normpath in self._foldmap:
369 return self._foldmap[normpath]
369 return self._foldmap[normpath]
370 elif os.path.exists(path):
370 elif os.path.exists(path):
371 self._foldmap[normpath] = util.fspath(path, self._root)
371 self._foldmap[normpath] = util.fspath(path, self._root)
372 return self._foldmap[normpath]
372 return self._foldmap[normpath]
373 else:
373 else:
374 return path
374 return path
375
375
376 def clear(self):
376 def clear(self):
377 self._map = {}
377 self._map = {}
378 if "_dirs" in self.__dict__:
378 if "_dirs" in self.__dict__:
379 delattr(self, "_dirs");
379 delattr(self, "_dirs");
380 self._copymap = {}
380 self._copymap = {}
381 self._pl = [nullid, nullid]
381 self._pl = [nullid, nullid]
382 self._dirty = True
382 self._dirty = True
383
383
384 def rebuild(self, parent, files):
384 def rebuild(self, parent, files):
385 self.clear()
385 self.clear()
386 for f in files:
386 for f in files:
387 if files.execf(f):
387 if 'x' in files.flag(f):
388 self._map[f] = ('n', 0777, -1, 0, 0)
388 self._map[f] = ('n', 0777, -1, 0, 0)
389 else:
389 else:
390 self._map[f] = ('n', 0666, -1, 0, 0)
390 self._map[f] = ('n', 0666, -1, 0, 0)
391 self._pl = (parent, nullid)
391 self._pl = (parent, nullid)
392 self._dirty = True
392 self._dirty = True
393
393
394 def write(self):
394 def write(self):
395 if not self._dirty:
395 if not self._dirty:
396 return
396 return
397 st = self._opener("dirstate", "w", atomictemp=True)
397 st = self._opener("dirstate", "w", atomictemp=True)
398
398
399 try:
399 try:
400 gran = int(self._ui.config('dirstate', 'granularity', 1))
400 gran = int(self._ui.config('dirstate', 'granularity', 1))
401 except ValueError:
401 except ValueError:
402 gran = 1
402 gran = 1
403 limit = sys.maxint
403 limit = sys.maxint
404 if gran > 0:
404 if gran > 0:
405 limit = util.fstat(st).st_mtime - gran
405 limit = util.fstat(st).st_mtime - gran
406
406
407 cs = cStringIO.StringIO()
407 cs = cStringIO.StringIO()
408 copymap = self._copymap
408 copymap = self._copymap
409 pack = struct.pack
409 pack = struct.pack
410 write = cs.write
410 write = cs.write
411 write("".join(self._pl))
411 write("".join(self._pl))
412 for f, e in self._map.iteritems():
412 for f, e in self._map.iteritems():
413 if f in copymap:
413 if f in copymap:
414 f = "%s\0%s" % (f, copymap[f])
414 f = "%s\0%s" % (f, copymap[f])
415 if e[3] > limit and e[0] == 'n':
415 if e[3] > limit and e[0] == 'n':
416 e = (e[0], 0, -1, -1, 0)
416 e = (e[0], 0, -1, -1, 0)
417 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
417 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
418 write(e)
418 write(e)
419 write(f)
419 write(f)
420 st.write(cs.getvalue())
420 st.write(cs.getvalue())
421 st.rename()
421 st.rename()
422 self._dirty = self._dirtypl = False
422 self._dirty = self._dirtypl = False
423
423
424 def _filter(self, files):
424 def _filter(self, files):
425 ret = {}
425 ret = {}
426 unknown = []
426 unknown = []
427
427
428 for x in files:
428 for x in files:
429 if x == '.':
429 if x == '.':
430 return self._map.copy()
430 return self._map.copy()
431 if x not in self._map:
431 if x not in self._map:
432 unknown.append(x)
432 unknown.append(x)
433 else:
433 else:
434 ret[x] = self._map[x]
434 ret[x] = self._map[x]
435
435
436 if not unknown:
436 if not unknown:
437 return ret
437 return ret
438
438
439 b = self._map.keys()
439 b = self._map.keys()
440 b.sort()
440 b.sort()
441 blen = len(b)
441 blen = len(b)
442
442
443 for x in unknown:
443 for x in unknown:
444 bs = bisect.bisect(b, "%s%s" % (x, '/'))
444 bs = bisect.bisect(b, "%s%s" % (x, '/'))
445 while bs < blen:
445 while bs < blen:
446 s = b[bs]
446 s = b[bs]
447 if len(s) > len(x) and s.startswith(x):
447 if len(s) > len(x) and s.startswith(x):
448 ret[s] = self._map[s]
448 ret[s] = self._map[s]
449 else:
449 else:
450 break
450 break
451 bs += 1
451 bs += 1
452 return ret
452 return ret
453
453
454 def _supported(self, f, mode, verbose=False):
454 def _supported(self, f, mode, verbose=False):
455 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
455 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
456 return True
456 return True
457 if verbose:
457 if verbose:
458 kind = 'unknown'
458 kind = 'unknown'
459 if stat.S_ISCHR(mode): kind = _('character device')
459 if stat.S_ISCHR(mode): kind = _('character device')
460 elif stat.S_ISBLK(mode): kind = _('block device')
460 elif stat.S_ISBLK(mode): kind = _('block device')
461 elif stat.S_ISFIFO(mode): kind = _('fifo')
461 elif stat.S_ISFIFO(mode): kind = _('fifo')
462 elif stat.S_ISSOCK(mode): kind = _('socket')
462 elif stat.S_ISSOCK(mode): kind = _('socket')
463 elif stat.S_ISDIR(mode): kind = _('directory')
463 elif stat.S_ISDIR(mode): kind = _('directory')
464 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
464 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
465 % (self.pathto(f), kind))
465 % (self.pathto(f), kind))
466 return False
466 return False
467
467
468 def _dirignore(self, f):
468 def _dirignore(self, f):
469 if f == '.':
469 if f == '.':
470 return False
470 return False
471 if self._ignore(f):
471 if self._ignore(f):
472 return True
472 return True
473 for c in strutil.findall(f, '/'):
473 for c in strutil.findall(f, '/'):
474 if self._ignore(f[:c]):
474 if self._ignore(f[:c]):
475 return True
475 return True
476 return False
476 return False
477
477
478 def walk(self, match):
478 def walk(self, match):
479 # filter out the src and stat
479 # filter out the src and stat
480 for src, f, st in self.statwalk(match):
480 for src, f, st in self.statwalk(match):
481 yield f
481 yield f
482
482
483 def statwalk(self, match, unknown=True, ignored=False):
483 def statwalk(self, match, unknown=True, ignored=False):
484 '''
484 '''
485 walk recursively through the directory tree, finding all files
485 walk recursively through the directory tree, finding all files
486 matched by the match function
486 matched by the match function
487
487
488 results are yielded in a tuple (src, filename, st), where src
488 results are yielded in a tuple (src, filename, st), where src
489 is one of:
489 is one of:
490 'f' the file was found in the directory tree
490 'f' the file was found in the directory tree
491 'm' the file was only in the dirstate and not in the tree
491 'm' the file was only in the dirstate and not in the tree
492
492
493 and st is the stat result if the file was found in the directory.
493 and st is the stat result if the file was found in the directory.
494 '''
494 '''
495
495
496 def fwarn(f, msg):
496 def fwarn(f, msg):
497 self._ui.warn('%s: %s\n' % (self.pathto(ff), msg))
497 self._ui.warn('%s: %s\n' % (self.pathto(ff), msg))
498 return False
498 return False
499 badfn = fwarn
499 badfn = fwarn
500 if hasattr(match, 'bad'):
500 if hasattr(match, 'bad'):
501 badfn = match.bad
501 badfn = match.bad
502
502
503 # walk all files by default
503 # walk all files by default
504 files = match.files()
504 files = match.files()
505 if not files:
505 if not files:
506 files = ['.']
506 files = ['.']
507 dc = self._map.copy()
507 dc = self._map.copy()
508 else:
508 else:
509 files = util.unique(files)
509 files = util.unique(files)
510 dc = self._filter(files)
510 dc = self._filter(files)
511
511
512 def imatch(file_):
512 def imatch(file_):
513 if file_ not in dc and self._ignore(file_):
513 if file_ not in dc and self._ignore(file_):
514 return False
514 return False
515 return match(file_)
515 return match(file_)
516
516
517 # TODO: don't walk unknown directories if unknown and ignored are False
517 # TODO: don't walk unknown directories if unknown and ignored are False
518 ignore = self._ignore
518 ignore = self._ignore
519 dirignore = self._dirignore
519 dirignore = self._dirignore
520 if ignored:
520 if ignored:
521 imatch = match
521 imatch = match
522 ignore = util.never
522 ignore = util.never
523 dirignore = util.never
523 dirignore = util.never
524
524
525 # self._root may end with a path separator when self._root == '/'
525 # self._root may end with a path separator when self._root == '/'
526 common_prefix_len = len(self._root)
526 common_prefix_len = len(self._root)
527 if not util.endswithsep(self._root):
527 if not util.endswithsep(self._root):
528 common_prefix_len += 1
528 common_prefix_len += 1
529
529
530 normpath = util.normpath
530 normpath = util.normpath
531 listdir = osutil.listdir
531 listdir = osutil.listdir
532 lstat = os.lstat
532 lstat = os.lstat
533 bisect_left = bisect.bisect_left
533 bisect_left = bisect.bisect_left
534 isdir = os.path.isdir
534 isdir = os.path.isdir
535 pconvert = util.pconvert
535 pconvert = util.pconvert
536 join = os.path.join
536 join = os.path.join
537 s_isdir = stat.S_ISDIR
537 s_isdir = stat.S_ISDIR
538 supported = self._supported
538 supported = self._supported
539 _join = self._join
539 _join = self._join
540 known = {'.hg': 1}
540 known = {'.hg': 1}
541
541
542 # recursion free walker, faster than os.walk.
542 # recursion free walker, faster than os.walk.
543 def findfiles(s):
543 def findfiles(s):
544 work = [s]
544 work = [s]
545 wadd = work.append
545 wadd = work.append
546 found = []
546 found = []
547 add = found.append
547 add = found.append
548 if hasattr(match, 'dir'):
548 if hasattr(match, 'dir'):
549 match.dir(normpath(s[common_prefix_len:]))
549 match.dir(normpath(s[common_prefix_len:]))
550 while work:
550 while work:
551 top = work.pop()
551 top = work.pop()
552 entries = listdir(top, stat=True)
552 entries = listdir(top, stat=True)
553 # nd is the top of the repository dir tree
553 # nd is the top of the repository dir tree
554 nd = normpath(top[common_prefix_len:])
554 nd = normpath(top[common_prefix_len:])
555 if nd == '.':
555 if nd == '.':
556 nd = ''
556 nd = ''
557 else:
557 else:
558 # do not recurse into a repo contained in this
558 # do not recurse into a repo contained in this
559 # one. use bisect to find .hg directory so speed
559 # one. use bisect to find .hg directory so speed
560 # is good on big directory.
560 # is good on big directory.
561 names = [e[0] for e in entries]
561 names = [e[0] for e in entries]
562 hg = bisect_left(names, '.hg')
562 hg = bisect_left(names, '.hg')
563 if hg < len(names) and names[hg] == '.hg':
563 if hg < len(names) and names[hg] == '.hg':
564 if isdir(join(top, '.hg')):
564 if isdir(join(top, '.hg')):
565 continue
565 continue
566 for f, kind, st in entries:
566 for f, kind, st in entries:
567 np = pconvert(join(nd, f))
567 np = pconvert(join(nd, f))
568 if np in known:
568 if np in known:
569 continue
569 continue
570 known[np] = 1
570 known[np] = 1
571 p = join(top, f)
571 p = join(top, f)
572 # don't trip over symlinks
572 # don't trip over symlinks
573 if kind == stat.S_IFDIR:
573 if kind == stat.S_IFDIR:
574 if not ignore(np):
574 if not ignore(np):
575 wadd(p)
575 wadd(p)
576 if hasattr(match, 'dir'):
576 if hasattr(match, 'dir'):
577 match.dir(np)
577 match.dir(np)
578 if np in dc and match(np):
578 if np in dc and match(np):
579 add((np, 'm', st))
579 add((np, 'm', st))
580 elif imatch(np):
580 elif imatch(np):
581 if supported(np, st.st_mode):
581 if supported(np, st.st_mode):
582 add((np, 'f', st))
582 add((np, 'f', st))
583 elif np in dc:
583 elif np in dc:
584 add((np, 'm', st))
584 add((np, 'm', st))
585 found.sort()
585 found.sort()
586 return found
586 return found
587
587
588 # step one, find all files that match our criteria
588 # step one, find all files that match our criteria
589 files.sort()
589 files.sort()
590 for ff in files:
590 for ff in files:
591 nf = normpath(ff)
591 nf = normpath(ff)
592 f = _join(ff)
592 f = _join(ff)
593 try:
593 try:
594 st = lstat(f)
594 st = lstat(f)
595 except OSError, inst:
595 except OSError, inst:
596 found = False
596 found = False
597 for fn in dc:
597 for fn in dc:
598 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
598 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
599 found = True
599 found = True
600 break
600 break
601 if not found:
601 if not found:
602 if inst.errno != errno.ENOENT:
602 if inst.errno != errno.ENOENT:
603 fwarn(ff, inst.strerror)
603 fwarn(ff, inst.strerror)
604 elif badfn(ff, inst.strerror) and imatch(nf):
604 elif badfn(ff, inst.strerror) and imatch(nf):
605 yield 'f', ff, None
605 yield 'f', ff, None
606 continue
606 continue
607 if s_isdir(st.st_mode):
607 if s_isdir(st.st_mode):
608 if not dirignore(nf):
608 if not dirignore(nf):
609 for f, src, st in findfiles(f):
609 for f, src, st in findfiles(f):
610 yield src, f, st
610 yield src, f, st
611 else:
611 else:
612 if nf in known:
612 if nf in known:
613 continue
613 continue
614 known[nf] = 1
614 known[nf] = 1
615 if match(nf):
615 if match(nf):
616 if supported(ff, st.st_mode, verbose=True):
616 if supported(ff, st.st_mode, verbose=True):
617 yield 'f', self.normalize(nf), st
617 yield 'f', self.normalize(nf), st
618 elif ff in dc:
618 elif ff in dc:
619 yield 'm', nf, st
619 yield 'm', nf, st
620
620
621 # step two run through anything left in the dc hash and yield
621 # step two run through anything left in the dc hash and yield
622 # if we haven't already seen it
622 # if we haven't already seen it
623 ks = dc.keys()
623 ks = dc.keys()
624 ks.sort()
624 ks.sort()
625 for k in ks:
625 for k in ks:
626 if k in known:
626 if k in known:
627 continue
627 continue
628 known[k] = 1
628 known[k] = 1
629 if imatch(k):
629 if imatch(k):
630 yield 'm', k, None
630 yield 'm', k, None
631
631
632 def status(self, match, list_ignored, list_clean, list_unknown):
632 def status(self, match, list_ignored, list_clean, list_unknown):
633 lookup, modified, added, unknown, ignored = [], [], [], [], []
633 lookup, modified, added, unknown, ignored = [], [], [], [], []
634 removed, deleted, clean = [], [], []
634 removed, deleted, clean = [], [], []
635
635
636 _join = self._join
636 _join = self._join
637 lstat = os.lstat
637 lstat = os.lstat
638 cmap = self._copymap
638 cmap = self._copymap
639 dmap = self._map
639 dmap = self._map
640 ladd = lookup.append
640 ladd = lookup.append
641 madd = modified.append
641 madd = modified.append
642 aadd = added.append
642 aadd = added.append
643 uadd = unknown.append
643 uadd = unknown.append
644 iadd = ignored.append
644 iadd = ignored.append
645 radd = removed.append
645 radd = removed.append
646 dadd = deleted.append
646 dadd = deleted.append
647 cadd = clean.append
647 cadd = clean.append
648
648
649 for src, fn, st in self.statwalk(match, unknown=list_unknown,
649 for src, fn, st in self.statwalk(match, unknown=list_unknown,
650 ignored=list_ignored):
650 ignored=list_ignored):
651 if fn not in dmap:
651 if fn not in dmap:
652 if (list_ignored or match.exact(fn)) and self._dirignore(fn):
652 if (list_ignored or match.exact(fn)) and self._dirignore(fn):
653 if list_ignored:
653 if list_ignored:
654 iadd(fn)
654 iadd(fn)
655 elif list_unknown:
655 elif list_unknown:
656 uadd(fn)
656 uadd(fn)
657 continue
657 continue
658
658
659 state, mode, size, time, foo = dmap[fn]
659 state, mode, size, time, foo = dmap[fn]
660
660
661 if src == 'm':
661 if src == 'm':
662 nonexistent = True
662 nonexistent = True
663 if not st:
663 if not st:
664 try:
664 try:
665 st = lstat(_join(fn))
665 st = lstat(_join(fn))
666 except OSError, inst:
666 except OSError, inst:
667 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
667 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
668 raise
668 raise
669 st = None
669 st = None
670 # We need to re-check that it is a valid file
670 # We need to re-check that it is a valid file
671 if st and self._supported(fn, st.st_mode):
671 if st and self._supported(fn, st.st_mode):
672 nonexistent = False
672 nonexistent = False
673 if nonexistent and state in "nma":
673 if nonexistent and state in "nma":
674 dadd(fn)
674 dadd(fn)
675 continue
675 continue
676 # check the common case first
676 # check the common case first
677 if state == 'n':
677 if state == 'n':
678 if not st:
678 if not st:
679 st = lstat(_join(fn))
679 st = lstat(_join(fn))
680 if (size >= 0 and
680 if (size >= 0 and
681 (size != st.st_size
681 (size != st.st_size
682 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
682 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
683 or size == -2
683 or size == -2
684 or fn in self._copymap):
684 or fn in self._copymap):
685 madd(fn)
685 madd(fn)
686 elif time != int(st.st_mtime):
686 elif time != int(st.st_mtime):
687 ladd(fn)
687 ladd(fn)
688 elif list_clean:
688 elif list_clean:
689 cadd(fn)
689 cadd(fn)
690 elif state == 'm':
690 elif state == 'm':
691 madd(fn)
691 madd(fn)
692 elif state == 'a':
692 elif state == 'a':
693 aadd(fn)
693 aadd(fn)
694 elif state == 'r':
694 elif state == 'r':
695 radd(fn)
695 radd(fn)
696
696
697 return (lookup, modified, added, removed, deleted, unknown, ignored,
697 return (lookup, modified, added, removed, deleted, unknown, ignored,
698 clean)
698 clean)
@@ -1,205 +1,199 b''
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class 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 bin, hex, nullid
8 from node import bin, hex, nullid
9 from revlog import revlog, RevlogError
9 from revlog import revlog, RevlogError
10 from i18n import _
10 from i18n import _
11 import array, struct, mdiff, parsers
11 import array, struct, mdiff, parsers
12
12
13 class manifestdict(dict):
13 class manifestdict(dict):
14 def __init__(self, mapping=None, flags=None):
14 def __init__(self, mapping=None, flags=None):
15 if mapping is None: mapping = {}
15 if mapping is None: mapping = {}
16 if flags is None: flags = {}
16 if flags is None: flags = {}
17 dict.__init__(self, mapping)
17 dict.__init__(self, mapping)
18 self._flags = flags
18 self._flags = flags
19 def flags(self, f):
19 def flags(self, f):
20 return self._flags.get(f, "")
20 return self._flags.get(f, "")
21 def execf(self, f):
22 "test for executable in manifest flags"
23 return "x" in self.flags(f)
24 def linkf(self, f):
25 "test for symlink in manifest flags"
26 return "l" in self.flags(f)
27 def set(self, f, flags):
21 def set(self, f, flags):
28 self._flags[f] = flags
22 self._flags[f] = flags
29 def copy(self):
23 def copy(self):
30 return manifestdict(dict.copy(self), dict.copy(self._flags))
24 return manifestdict(dict.copy(self), dict.copy(self._flags))
31
25
32 class manifest(revlog):
26 class manifest(revlog):
33 def __init__(self, opener):
27 def __init__(self, opener):
34 self.mapcache = None
28 self.mapcache = None
35 self.listcache = None
29 self.listcache = None
36 revlog.__init__(self, opener, "00manifest.i")
30 revlog.__init__(self, opener, "00manifest.i")
37
31
38 def parse(self, lines):
32 def parse(self, lines):
39 mfdict = manifestdict()
33 mfdict = manifestdict()
40 parsers.parse_manifest(mfdict, mfdict._flags, lines)
34 parsers.parse_manifest(mfdict, mfdict._flags, lines)
41 return mfdict
35 return mfdict
42
36
43 def readdelta(self, node):
37 def readdelta(self, node):
44 return self.parse(mdiff.patchtext(self.delta(node)))
38 return self.parse(mdiff.patchtext(self.delta(node)))
45
39
46 def read(self, node):
40 def read(self, node):
47 if node == nullid: return manifestdict() # don't upset local cache
41 if node == nullid: return manifestdict() # don't upset local cache
48 if self.mapcache and self.mapcache[0] == node:
42 if self.mapcache and self.mapcache[0] == node:
49 return self.mapcache[1]
43 return self.mapcache[1]
50 text = self.revision(node)
44 text = self.revision(node)
51 self.listcache = array.array('c', text)
45 self.listcache = array.array('c', text)
52 mapping = self.parse(text)
46 mapping = self.parse(text)
53 self.mapcache = (node, mapping)
47 self.mapcache = (node, mapping)
54 return mapping
48 return mapping
55
49
56 def _search(self, m, s, lo=0, hi=None):
50 def _search(self, m, s, lo=0, hi=None):
57 '''return a tuple (start, end) that says where to find s within m.
51 '''return a tuple (start, end) that says where to find s within m.
58
52
59 If the string is found m[start:end] are the line containing
53 If the string is found m[start:end] are the line containing
60 that string. If start == end the string was not found and
54 that string. If start == end the string was not found and
61 they indicate the proper sorted insertion point. This was
55 they indicate the proper sorted insertion point. This was
62 taken from bisect_left, and modified to find line start/end as
56 taken from bisect_left, and modified to find line start/end as
63 it goes along.
57 it goes along.
64
58
65 m should be a buffer or a string
59 m should be a buffer or a string
66 s is a string'''
60 s is a string'''
67 def advance(i, c):
61 def advance(i, c):
68 while i < lenm and m[i] != c:
62 while i < lenm and m[i] != c:
69 i += 1
63 i += 1
70 return i
64 return i
71 lenm = len(m)
65 lenm = len(m)
72 if not hi:
66 if not hi:
73 hi = lenm
67 hi = lenm
74 while lo < hi:
68 while lo < hi:
75 mid = (lo + hi) // 2
69 mid = (lo + hi) // 2
76 start = mid
70 start = mid
77 while start > 0 and m[start-1] != '\n':
71 while start > 0 and m[start-1] != '\n':
78 start -= 1
72 start -= 1
79 end = advance(start, '\0')
73 end = advance(start, '\0')
80 if m[start:end] < s:
74 if m[start:end] < s:
81 # we know that after the null there are 40 bytes of sha1
75 # we know that after the null there are 40 bytes of sha1
82 # this translates to the bisect lo = mid + 1
76 # this translates to the bisect lo = mid + 1
83 lo = advance(end + 40, '\n') + 1
77 lo = advance(end + 40, '\n') + 1
84 else:
78 else:
85 # this translates to the bisect hi = mid
79 # this translates to the bisect hi = mid
86 hi = start
80 hi = start
87 end = advance(lo, '\0')
81 end = advance(lo, '\0')
88 found = m[lo:end]
82 found = m[lo:end]
89 if cmp(s, found) == 0:
83 if cmp(s, found) == 0:
90 # we know that after the null there are 40 bytes of sha1
84 # we know that after the null there are 40 bytes of sha1
91 end = advance(end + 40, '\n')
85 end = advance(end + 40, '\n')
92 return (lo, end+1)
86 return (lo, end+1)
93 else:
87 else:
94 return (lo, lo)
88 return (lo, lo)
95
89
96 def find(self, node, f):
90 def find(self, node, f):
97 '''look up entry for a single file efficiently.
91 '''look up entry for a single file efficiently.
98 return (node, flags) pair if found, (None, None) if not.'''
92 return (node, flags) pair if found, (None, None) if not.'''
99 if self.mapcache and node == self.mapcache[0]:
93 if self.mapcache and node == self.mapcache[0]:
100 return self.mapcache[1].get(f), self.mapcache[1].flags(f)
94 return self.mapcache[1].get(f), self.mapcache[1].flags(f)
101 text = self.revision(node)
95 text = self.revision(node)
102 start, end = self._search(text, f)
96 start, end = self._search(text, f)
103 if start == end:
97 if start == end:
104 return None, None
98 return None, None
105 l = text[start:end]
99 l = text[start:end]
106 f, n = l.split('\0')
100 f, n = l.split('\0')
107 return bin(n[:40]), n[40:-1]
101 return bin(n[:40]), n[40:-1]
108
102
109 def add(self, map, transaction, link, p1=None, p2=None,
103 def add(self, map, transaction, link, p1=None, p2=None,
110 changed=None):
104 changed=None):
111 # apply the changes collected during the bisect loop to our addlist
105 # apply the changes collected during the bisect loop to our addlist
112 # return a delta suitable for addrevision
106 # return a delta suitable for addrevision
113 def addlistdelta(addlist, x):
107 def addlistdelta(addlist, x):
114 # start from the bottom up
108 # start from the bottom up
115 # so changes to the offsets don't mess things up.
109 # so changes to the offsets don't mess things up.
116 i = len(x)
110 i = len(x)
117 while i > 0:
111 while i > 0:
118 i -= 1
112 i -= 1
119 start = x[i][0]
113 start = x[i][0]
120 end = x[i][1]
114 end = x[i][1]
121 if x[i][2]:
115 if x[i][2]:
122 addlist[start:end] = array.array('c', x[i][2])
116 addlist[start:end] = array.array('c', x[i][2])
123 else:
117 else:
124 del addlist[start:end]
118 del addlist[start:end]
125 return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2]
119 return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2]
126 for d in x ])
120 for d in x ])
127
121
128 def checkforbidden(f):
122 def checkforbidden(f):
129 if '\n' in f or '\r' in f:
123 if '\n' in f or '\r' in f:
130 raise RevlogError(_("'\\n' and '\\r' disallowed in filenames"))
124 raise RevlogError(_("'\\n' and '\\r' disallowed in filenames"))
131
125
132 # if we're using the listcache, make sure it is valid and
126 # if we're using the listcache, make sure it is valid and
133 # parented by the same node we're diffing against
127 # parented by the same node we're diffing against
134 if not (changed and self.listcache and p1 and self.mapcache[0] == p1):
128 if not (changed and self.listcache and p1 and self.mapcache[0] == p1):
135 files = map.keys()
129 files = map.keys()
136 files.sort()
130 files.sort()
137
131
138 for f in files:
132 for f in files:
139 checkforbidden(f)
133 checkforbidden(f)
140
134
141 # if this is changed to support newlines in filenames,
135 # if this is changed to support newlines in filenames,
142 # be sure to check the templates/ dir again (especially *-raw.tmpl)
136 # be sure to check the templates/ dir again (especially *-raw.tmpl)
143 text = ["%s\000%s%s\n" % (f, hex(map[f]), map.flags(f))
137 text = ["%s\000%s%s\n" % (f, hex(map[f]), map.flags(f))
144 for f in files]
138 for f in files]
145 self.listcache = array.array('c', "".join(text))
139 self.listcache = array.array('c', "".join(text))
146 cachedelta = None
140 cachedelta = None
147 else:
141 else:
148 addlist = self.listcache
142 addlist = self.listcache
149
143
150 for f in changed[0]:
144 for f in changed[0]:
151 checkforbidden(f)
145 checkforbidden(f)
152 # combine the changed lists into one list for sorting
146 # combine the changed lists into one list for sorting
153 work = [[x, 0] for x in changed[0]]
147 work = [[x, 0] for x in changed[0]]
154 work[len(work):] = [[x, 1] for x in changed[1]]
148 work[len(work):] = [[x, 1] for x in changed[1]]
155 work.sort()
149 work.sort()
156
150
157 delta = []
151 delta = []
158 dstart = None
152 dstart = None
159 dend = None
153 dend = None
160 dline = [""]
154 dline = [""]
161 start = 0
155 start = 0
162 # zero copy representation of addlist as a buffer
156 # zero copy representation of addlist as a buffer
163 addbuf = buffer(addlist)
157 addbuf = buffer(addlist)
164
158
165 # start with a readonly loop that finds the offset of
159 # start with a readonly loop that finds the offset of
166 # each line and creates the deltas
160 # each line and creates the deltas
167 for w in work:
161 for w in work:
168 f = w[0]
162 f = w[0]
169 # bs will either be the index of the item or the insert point
163 # bs will either be the index of the item or the insert point
170 start, end = self._search(addbuf, f, start)
164 start, end = self._search(addbuf, f, start)
171 if w[1] == 0:
165 if w[1] == 0:
172 l = "%s\000%s%s\n" % (f, hex(map[f]), map.flags(f))
166 l = "%s\000%s%s\n" % (f, hex(map[f]), map.flags(f))
173 else:
167 else:
174 l = ""
168 l = ""
175 if start == end and w[1] == 1:
169 if start == end and w[1] == 1:
176 # item we want to delete was not found, error out
170 # item we want to delete was not found, error out
177 raise AssertionError(
171 raise AssertionError(
178 _("failed to remove %s from manifest") % f)
172 _("failed to remove %s from manifest") % f)
179 if dstart != None and dstart <= start and dend >= start:
173 if dstart != None and dstart <= start and dend >= start:
180 if dend < end:
174 if dend < end:
181 dend = end
175 dend = end
182 if l:
176 if l:
183 dline.append(l)
177 dline.append(l)
184 else:
178 else:
185 if dstart != None:
179 if dstart != None:
186 delta.append([dstart, dend, "".join(dline)])
180 delta.append([dstart, dend, "".join(dline)])
187 dstart = start
181 dstart = start
188 dend = end
182 dend = end
189 dline = [l]
183 dline = [l]
190
184
191 if dstart != None:
185 if dstart != None:
192 delta.append([dstart, dend, "".join(dline)])
186 delta.append([dstart, dend, "".join(dline)])
193 # apply the delta to the addlist, and get a delta for addrevision
187 # apply the delta to the addlist, and get a delta for addrevision
194 cachedelta = addlistdelta(addlist, delta)
188 cachedelta = addlistdelta(addlist, delta)
195
189
196 # the delta is only valid if we've been processing the tip revision
190 # the delta is only valid if we've been processing the tip revision
197 if self.mapcache[0] != self.tip():
191 if self.mapcache[0] != self.tip():
198 cachedelta = None
192 cachedelta = None
199 self.listcache = addlist
193 self.listcache = addlist
200
194
201 n = self.addrevision(buffer(self.listcache), transaction, link,
195 n = self.addrevision(buffer(self.listcache), transaction, link,
202 p1, p2, cachedelta)
196 p1, p2, cachedelta)
203 self.mapcache = (n, map)
197 self.mapcache = (n, map)
204
198
205 return n
199 return n
General Comments 0
You need to be logged in to leave comments. Login now