##// END OF EJS Templates
Avoid importing mercurial.node/mercurial.repo stuff from mercurial.hg
Joel Rosdahl -
r6217:fe8dbbe9 default
parent child Browse files
Show More
@@ -1,301 +1,302 b''
1 1 # hg backend for convert extension
2 2
3 3 # Notes for hg->hg conversion:
4 4 #
5 5 # * Old versions of Mercurial didn't trim the whitespace from the ends
6 6 # of commit messages, but new versions do. Changesets created by
7 7 # those older versions, then converted, may thus have different
8 8 # hashes for changesets that are otherwise identical.
9 9 #
10 10 # * By default, the source revision is stored in the converted
11 11 # revision. This will cause the converted revision to have a
12 12 # different identity than the source. To avoid this, use the
13 13 # following option: "--config convert.hg.saverev=false"
14 14
15 15
16 16 import os, time
17 17 from mercurial.i18n import _
18 from mercurial.repo import RepoError
18 19 from mercurial.node import bin, hex, nullid
19 20 from mercurial import hg, revlog, util
20 21
21 22 from common import NoRepo, commit, converter_source, converter_sink
22 23
23 24 class mercurial_sink(converter_sink):
24 25 def __init__(self, ui, path):
25 26 converter_sink.__init__(self, ui, path)
26 27 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
27 28 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
28 29 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
29 30 self.lastbranch = None
30 31 if os.path.isdir(path) and len(os.listdir(path)) > 0:
31 32 try:
32 33 self.repo = hg.repository(self.ui, path)
33 34 if not self.repo.local():
34 35 raise NoRepo(_('%s is not a local Mercurial repo') % path)
35 except hg.RepoError, err:
36 except RepoError, err:
36 37 ui.print_exc()
37 38 raise NoRepo(err.args[0])
38 39 else:
39 40 try:
40 41 ui.status(_('initializing destination %s repository\n') % path)
41 42 self.repo = hg.repository(self.ui, path, create=True)
42 43 if not self.repo.local():
43 44 raise NoRepo(_('%s is not a local Mercurial repo') % path)
44 45 self.created.append(path)
45 except hg.RepoError, err:
46 except RepoError, err:
46 47 ui.print_exc()
47 48 raise NoRepo("could not create hg repo %s as sink" % path)
48 49 self.lock = None
49 50 self.wlock = None
50 51 self.filemapmode = False
51 52
52 53 def before(self):
53 54 self.ui.debug(_('run hg sink pre-conversion action\n'))
54 55 self.wlock = self.repo.wlock()
55 56 self.lock = self.repo.lock()
56 57 self.repo.dirstate.clear()
57 58
58 59 def after(self):
59 60 self.ui.debug(_('run hg sink post-conversion action\n'))
60 61 self.repo.dirstate.invalidate()
61 62 self.lock = None
62 63 self.wlock = None
63 64
64 65 def revmapfile(self):
65 66 return os.path.join(self.path, ".hg", "shamap")
66 67
67 68 def authorfile(self):
68 69 return os.path.join(self.path, ".hg", "authormap")
69 70
70 71 def getheads(self):
71 72 h = self.repo.changelog.heads()
72 73 return [ hex(x) for x in h ]
73 74
74 75 def putfile(self, f, e, data):
75 76 self.repo.wwrite(f, data, e)
76 77 if f not in self.repo.dirstate:
77 78 self.repo.dirstate.normallookup(f)
78 79
79 80 def copyfile(self, source, dest):
80 81 self.repo.copy(source, dest)
81 82
82 83 def delfile(self, f):
83 84 try:
84 85 util.unlink(self.repo.wjoin(f))
85 86 #self.repo.remove([f])
86 87 except OSError:
87 88 pass
88 89
89 90 def setbranch(self, branch, pbranches):
90 91 if not self.clonebranches:
91 92 return
92 93
93 94 setbranch = (branch != self.lastbranch)
94 95 self.lastbranch = branch
95 96 if not branch:
96 97 branch = 'default'
97 98 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
98 99 pbranch = pbranches and pbranches[0][1] or 'default'
99 100
100 101 branchpath = os.path.join(self.path, branch)
101 102 if setbranch:
102 103 self.after()
103 104 try:
104 105 self.repo = hg.repository(self.ui, branchpath)
105 106 except:
106 107 self.repo = hg.repository(self.ui, branchpath, create=True)
107 108 self.before()
108 109
109 110 # pbranches may bring revisions from other branches (merge parents)
110 111 # Make sure we have them, or pull them.
111 112 missings = {}
112 113 for b in pbranches:
113 114 try:
114 115 self.repo.lookup(b[0])
115 116 except:
116 117 missings.setdefault(b[1], []).append(b[0])
117 118
118 119 if missings:
119 120 self.after()
120 121 for pbranch, heads in missings.iteritems():
121 122 pbranchpath = os.path.join(self.path, pbranch)
122 123 prepo = hg.repository(self.ui, pbranchpath)
123 124 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
124 125 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
125 126 self.before()
126 127
127 128 def putcommit(self, files, parents, commit):
128 129 seen = {}
129 130 pl = []
130 131 for p in parents:
131 132 if p not in seen:
132 133 pl.append(p)
133 134 seen[p] = 1
134 135 parents = pl
135 136 nparents = len(parents)
136 137 if self.filemapmode and nparents == 1:
137 138 m1node = self.repo.changelog.read(bin(parents[0]))[0]
138 139 parent = parents[0]
139 140
140 141 if len(parents) < 2: parents.append("0" * 40)
141 142 if len(parents) < 2: parents.append("0" * 40)
142 143 p2 = parents.pop(0)
143 144
144 145 text = commit.desc
145 146 extra = commit.extra.copy()
146 147 if self.branchnames and commit.branch:
147 148 extra['branch'] = commit.branch
148 149 if commit.rev:
149 150 extra['convert_revision'] = commit.rev
150 151
151 152 while parents:
152 153 p1 = p2
153 154 p2 = parents.pop(0)
154 155 a = self.repo.rawcommit(files, text, commit.author, commit.date,
155 156 bin(p1), bin(p2), extra=extra)
156 157 self.repo.dirstate.clear()
157 158 text = "(octopus merge fixup)\n"
158 p2 = hg.hex(self.repo.changelog.tip())
159 p2 = hex(self.repo.changelog.tip())
159 160
160 161 if self.filemapmode and nparents == 1:
161 162 man = self.repo.manifest
162 163 mnode = self.repo.changelog.read(bin(p2))[0]
163 164 if not man.cmp(m1node, man.revision(mnode)):
164 165 self.repo.rollback()
165 166 self.repo.dirstate.clear()
166 167 return parent
167 168 return p2
168 169
169 170 def puttags(self, tags):
170 171 try:
171 172 old = self.repo.wfile(".hgtags").read()
172 173 oldlines = old.splitlines(1)
173 174 oldlines.sort()
174 175 except:
175 176 oldlines = []
176 177
177 178 k = tags.keys()
178 179 k.sort()
179 180 newlines = []
180 181 for tag in k:
181 182 newlines.append("%s %s\n" % (tags[tag], tag))
182 183
183 184 newlines.sort()
184 185
185 186 if newlines != oldlines:
186 187 self.ui.status("updating tags\n")
187 188 f = self.repo.wfile(".hgtags", "w")
188 189 f.write("".join(newlines))
189 190 f.close()
190 191 if not oldlines: self.repo.add([".hgtags"])
191 192 date = "%s 0" % int(time.mktime(time.gmtime()))
192 193 extra = {}
193 194 if self.tagsbranch != 'default':
194 195 extra['branch'] = self.tagsbranch
195 196 try:
196 197 tagparent = self.repo.changectx(self.tagsbranch).node()
197 except hg.RepoError, inst:
198 except RepoError, inst:
198 199 tagparent = nullid
199 200 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
200 201 date, tagparent, nullid, extra=extra)
201 202 return hex(self.repo.changelog.tip())
202 203
203 204 def setfilemapmode(self, active):
204 205 self.filemapmode = active
205 206
206 207 class mercurial_source(converter_source):
207 208 def __init__(self, ui, path, rev=None):
208 209 converter_source.__init__(self, ui, path, rev)
209 210 self.saverev = ui.configbool('convert', 'hg.saverev', True)
210 211 try:
211 212 self.repo = hg.repository(self.ui, path)
212 213 # try to provoke an exception if this isn't really a hg
213 214 # repo, but some other bogus compatible-looking url
214 215 if not self.repo.local():
215 raise hg.RepoError()
216 except hg.RepoError:
216 raise RepoError()
217 except RepoError:
217 218 ui.print_exc()
218 219 raise NoRepo("%s is not a local Mercurial repo" % path)
219 220 self.lastrev = None
220 221 self.lastctx = None
221 222 self._changescache = None
222 223 self.convertfp = None
223 224
224 225 def changectx(self, rev):
225 226 if self.lastrev != rev:
226 227 self.lastctx = self.repo.changectx(rev)
227 228 self.lastrev = rev
228 229 return self.lastctx
229 230
230 231 def getheads(self):
231 232 if self.rev:
232 233 return [hex(self.repo.changectx(self.rev).node())]
233 234 else:
234 235 return [hex(node) for node in self.repo.heads()]
235 236
236 237 def getfile(self, name, rev):
237 238 try:
238 239 return self.changectx(rev).filectx(name).data()
239 240 except revlog.LookupError, err:
240 241 raise IOError(err)
241 242
242 243 def getmode(self, name, rev):
243 244 m = self.changectx(rev).manifest()
244 245 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
245 246
246 247 def getchanges(self, rev):
247 248 ctx = self.changectx(rev)
248 249 if self._changescache and self._changescache[0] == rev:
249 250 m, a, r = self._changescache[1]
250 251 else:
251 252 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
252 253 changes = [(name, rev) for name in m + a + r]
253 254 changes.sort()
254 255 return (changes, self.getcopies(ctx, m + a))
255 256
256 257 def getcopies(self, ctx, files):
257 258 copies = {}
258 259 for name in files:
259 260 try:
260 261 copies[name] = ctx.filectx(name).renamed()[0]
261 262 except TypeError:
262 263 pass
263 264 return copies
264 265
265 266 def getcommit(self, rev):
266 267 ctx = self.changectx(rev)
267 268 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
268 269 if self.saverev:
269 270 crev = rev
270 271 else:
271 272 crev = None
272 273 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
273 274 desc=ctx.description(), rev=crev, parents=parents,
274 275 branch=ctx.branch(), extra=ctx.extra())
275 276
276 277 def gettags(self):
277 278 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
278 279 return dict([(name, hex(node)) for name, node in tags])
279 280
280 281 def getchangedfiles(self, rev, i):
281 282 ctx = self.changectx(rev)
282 283 i = i or 0
283 284 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
284 285
285 286 if i == 0:
286 287 self._changescache = (rev, changes)
287 288
288 289 return changes[0] + changes[1] + changes[2]
289 290
290 291 def converted(self, rev, destrev):
291 292 if self.convertfp is None:
292 293 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
293 294 'a')
294 295 self.convertfp.write('%s %s\n' % (destrev, rev))
295 296 self.convertfp.flush()
296 297
297 298 def before(self):
298 299 self.ui.debug(_('run hg source pre-conversion action\n'))
299 300
300 301 def after(self):
301 302 self.ui.debug(_('run hg source post-conversion action\n'))
@@ -1,356 +1,357 b''
1 1 # Minimal support for git commands on an hg repository
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7 #
8 8 # The hgk extension allows browsing the history of a repository in a
9 9 # graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is
10 10 # not distributed with Mercurial.)
11 11 #
12 12 # hgk consists of two parts: a Tcl script that does the displaying and
13 13 # querying of information, and an extension to mercurial named hgk.py,
14 14 # which provides hooks for hgk to get information. hgk can be found in
15 15 # the contrib directory, and hgk.py can be found in the hgext
16 16 # directory.
17 17 #
18 18 # To load the hgext.py extension, add it to your .hgrc file (you have
19 19 # to use your global $HOME/.hgrc file, not one in a repository). You
20 20 # can specify an absolute path:
21 21 #
22 22 # [extensions]
23 23 # hgk=/usr/local/lib/hgk.py
24 24 #
25 25 # Mercurial can also scan the default python library path for a file
26 26 # named 'hgk.py' if you set hgk empty:
27 27 #
28 28 # [extensions]
29 29 # hgk=
30 30 #
31 31 # The hg view command will launch the hgk Tcl script. For this command
32 32 # to work, hgk must be in your search path. Alternately, you can
33 33 # specify the path to hgk in your .hgrc file:
34 34 #
35 35 # [hgk]
36 36 # path=/location/of/hgk
37 37 #
38 38 # hgk can make use of the extdiff extension to visualize
39 39 # revisions. Assuming you had already configured extdiff vdiff
40 40 # command, just add:
41 41 #
42 42 # [hgk]
43 43 # vdiff=vdiff
44 44 #
45 45 # Revisions context menu will now display additional entries to fire
46 46 # vdiff on hovered and selected revisions.
47 47
48 48 import os
49 from mercurial import hg, commands, util, patch, revlog
49 from mercurial import commands, util, patch, revlog
50 from mercurial.node import nullid, nullrev, short
50 51
51 52 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
52 53 """diff trees from two commits"""
53 54 def __difftree(repo, node1, node2, files=[]):
54 55 assert node2 is not None
55 56 mmap = repo.changectx(node1).manifest()
56 57 mmap2 = repo.changectx(node2).manifest()
57 58 status = repo.status(node1, node2, files=files)[:5]
58 59 modified, added, removed, deleted, unknown = status
59 60
60 empty = hg.short(hg.nullid)
61 empty = short(nullid)
61 62
62 63 for f in modified:
63 64 # TODO get file permissions
64 65 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
65 (hg.short(mmap[f]), hg.short(mmap2[f]), f, f))
66 (short(mmap[f]), short(mmap2[f]), f, f))
66 67 for f in added:
67 68 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
68 (empty, hg.short(mmap2[f]), f, f))
69 (empty, short(mmap2[f]), f, f))
69 70 for f in removed:
70 71 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
71 (hg.short(mmap[f]), empty, f, f))
72 (short(mmap[f]), empty, f, f))
72 73 ##
73 74
74 75 while True:
75 76 if opts['stdin']:
76 77 try:
77 78 line = raw_input().split(' ')
78 79 node1 = line[0]
79 80 if len(line) > 1:
80 81 node2 = line[1]
81 82 else:
82 83 node2 = None
83 84 except EOFError:
84 85 break
85 86 node1 = repo.lookup(node1)
86 87 if node2:
87 88 node2 = repo.lookup(node2)
88 89 else:
89 90 node2 = node1
90 91 node1 = repo.changelog.parents(node1)[0]
91 92 if opts['patch']:
92 93 if opts['pretty']:
93 94 catcommit(ui, repo, node2, "")
94 95 patch.diff(repo, node1, node2,
95 96 files=files,
96 97 opts=patch.diffopts(ui, {'git': True}))
97 98 else:
98 99 __difftree(repo, node1, node2, files=files)
99 100 if not opts['stdin']:
100 101 break
101 102
102 103 def catcommit(ui, repo, n, prefix, ctx=None):
103 104 nlprefix = '\n' + prefix;
104 105 if ctx is None:
105 106 ctx = repo.changectx(n)
106 107 (p1, p2) = ctx.parents()
107 ui.write("tree %s\n" % hg.short(ctx.changeset()[0])) # use ctx.node() instead ??
108 if p1: ui.write("parent %s\n" % hg.short(p1.node()))
109 if p2: ui.write("parent %s\n" % hg.short(p2.node()))
108 ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
109 if p1: ui.write("parent %s\n" % short(p1.node()))
110 if p2: ui.write("parent %s\n" % short(p2.node()))
110 111 date = ctx.date()
111 112 description = ctx.description().replace("\0", "")
112 113 lines = description.splitlines()
113 114 if lines and lines[-1].startswith('committer:'):
114 115 committer = lines[-1].split(': ')[1].rstrip()
115 116 else:
116 117 committer = ctx.user()
117 118
118 119 ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
119 120 ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
120 121 ui.write("revision %d\n" % ctx.rev())
121 122 ui.write("branch %s\n\n" % ctx.branch())
122 123
123 124 if prefix != "":
124 125 ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
125 126 else:
126 127 ui.write(description + "\n")
127 128 if prefix:
128 129 ui.write('\0')
129 130
130 131 def base(ui, repo, node1, node2):
131 132 """Output common ancestor information"""
132 133 node1 = repo.lookup(node1)
133 134 node2 = repo.lookup(node2)
134 135 n = repo.changelog.ancestor(node1, node2)
135 ui.write(hg.short(n) + "\n")
136 ui.write(short(n) + "\n")
136 137
137 138 def catfile(ui, repo, type=None, r=None, **opts):
138 139 """cat a specific revision"""
139 140 # in stdin mode, every line except the commit is prefixed with two
140 141 # spaces. This way the our caller can find the commit without magic
141 142 # strings
142 143 #
143 144 prefix = ""
144 145 if opts['stdin']:
145 146 try:
146 147 (type, r) = raw_input().split(' ');
147 148 prefix = " "
148 149 except EOFError:
149 150 return
150 151
151 152 else:
152 153 if not type or not r:
153 154 ui.warn("cat-file: type or revision not supplied\n")
154 155 commands.help_(ui, 'cat-file')
155 156
156 157 while r:
157 158 if type != "commit":
158 159 ui.warn("aborting hg cat-file only understands commits\n")
159 160 return 1;
160 161 n = repo.lookup(r)
161 162 catcommit(ui, repo, n, prefix)
162 163 if opts['stdin']:
163 164 try:
164 165 (type, r) = raw_input().split(' ');
165 166 except EOFError:
166 167 break
167 168 else:
168 169 break
169 170
170 171 # git rev-tree is a confusing thing. You can supply a number of
171 172 # commit sha1s on the command line, and it walks the commit history
172 173 # telling you which commits are reachable from the supplied ones via
173 174 # a bitmask based on arg position.
174 175 # you can specify a commit to stop at by starting the sha1 with ^
175 176 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
176 177 def chlogwalk():
177 178 count = repo.changelog.count()
178 179 i = count
179 180 l = [0] * 100
180 181 chunk = 100
181 182 while True:
182 183 if chunk > i:
183 184 chunk = i
184 185 i = 0
185 186 else:
186 187 i -= chunk
187 188
188 189 for x in xrange(0, chunk):
189 190 if i + x >= count:
190 191 l[chunk - x:] = [0] * (chunk - x)
191 192 break
192 193 if full != None:
193 194 l[x] = repo.changectx(i + x)
194 195 l[x].changeset() # force reading
195 196 else:
196 197 l[x] = 1
197 198 for x in xrange(chunk-1, -1, -1):
198 199 if l[x] != 0:
199 200 yield (i + x, full != None and l[x] or None)
200 201 if i == 0:
201 202 break
202 203
203 204 # calculate and return the reachability bitmask for sha
204 205 def is_reachable(ar, reachable, sha):
205 206 if len(ar) == 0:
206 207 return 1
207 208 mask = 0
208 209 for i in xrange(len(ar)):
209 210 if sha in reachable[i]:
210 211 mask |= 1 << i
211 212
212 213 return mask
213 214
214 215 reachable = []
215 216 stop_sha1 = []
216 217 want_sha1 = []
217 218 count = 0
218 219
219 220 # figure out which commits they are asking for and which ones they
220 221 # want us to stop on
221 222 for i in xrange(len(args)):
222 223 if args[i].startswith('^'):
223 224 s = repo.lookup(args[i][1:])
224 225 stop_sha1.append(s)
225 226 want_sha1.append(s)
226 227 elif args[i] != 'HEAD':
227 228 want_sha1.append(repo.lookup(args[i]))
228 229
229 230 # calculate the graph for the supplied commits
230 231 for i in xrange(len(want_sha1)):
231 232 reachable.append({});
232 233 n = want_sha1[i];
233 234 visit = [n];
234 235 reachable[i][n] = 1
235 236 while visit:
236 237 n = visit.pop(0)
237 238 if n in stop_sha1:
238 239 continue
239 240 for p in repo.changelog.parents(n):
240 241 if p not in reachable[i]:
241 242 reachable[i][p] = 1
242 243 visit.append(p)
243 244 if p in stop_sha1:
244 245 continue
245 246
246 247 # walk the repository looking for commits that are in our
247 248 # reachability graph
248 249 for i, ctx in chlogwalk():
249 250 n = repo.changelog.node(i)
250 251 mask = is_reachable(want_sha1, reachable, n)
251 252 if mask:
252 253 parentstr = ""
253 254 if parents:
254 255 pp = repo.changelog.parents(n)
255 if pp[0] != hg.nullid:
256 parentstr += " " + hg.short(pp[0])
257 if pp[1] != hg.nullid:
258 parentstr += " " + hg.short(pp[1])
256 if pp[0] != nullid:
257 parentstr += " " + short(pp[0])
258 if pp[1] != nullid:
259 parentstr += " " + short(pp[1])
259 260 if not full:
260 ui.write("%s%s\n" % (hg.short(n), parentstr))
261 ui.write("%s%s\n" % (short(n), parentstr))
261 262 elif full == "commit":
262 ui.write("%s%s\n" % (hg.short(n), parentstr))
263 ui.write("%s%s\n" % (short(n), parentstr))
263 264 catcommit(ui, repo, n, ' ', ctx)
264 265 else:
265 266 (p1, p2) = repo.changelog.parents(n)
266 (h, h1, h2) = map(hg.short, (n, p1, p2))
267 (h, h1, h2) = map(short, (n, p1, p2))
267 268 (i1, i2) = map(repo.changelog.rev, (p1, p2))
268 269
269 270 date = ctx.date()[0]
270 271 ui.write("%s %s:%s" % (date, h, mask))
271 272 mask = is_reachable(want_sha1, reachable, p1)
272 if i1 != hg.nullrev and mask > 0:
273 if i1 != nullrev and mask > 0:
273 274 ui.write("%s:%s " % (h1, mask)),
274 275 mask = is_reachable(want_sha1, reachable, p2)
275 if i2 != hg.nullrev and mask > 0:
276 if i2 != nullrev and mask > 0:
276 277 ui.write("%s:%s " % (h2, mask))
277 278 ui.write("\n")
278 279 if maxnr and count >= maxnr:
279 280 break
280 281 count += 1
281 282
282 283 def revparse(ui, repo, *revs, **opts):
283 284 """Parse given revisions"""
284 285 def revstr(rev):
285 286 if rev == 'HEAD':
286 287 rev = 'tip'
287 288 return revlog.hex(repo.lookup(rev))
288 289
289 290 for r in revs:
290 291 revrange = r.split(':', 1)
291 292 ui.write('%s\n' % revstr(revrange[0]))
292 293 if len(revrange) == 2:
293 294 ui.write('^%s\n' % revstr(revrange[1]))
294 295
295 296 # git rev-list tries to order things by date, and has the ability to stop
296 297 # at a given commit without walking the whole repo. TODO add the stop
297 298 # parameter
298 299 def revlist(ui, repo, *revs, **opts):
299 300 """print revisions"""
300 301 if opts['header']:
301 302 full = "commit"
302 303 else:
303 304 full = None
304 305 copy = [x for x in revs]
305 306 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
306 307
307 308 def config(ui, repo, **opts):
308 309 """print extension options"""
309 310 def writeopt(name, value):
310 311 ui.write('k=%s\nv=%s\n' % (name, value))
311 312
312 313 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
313 314
314 315
315 316 def view(ui, repo, *etc, **opts):
316 317 "start interactive history viewer"
317 318 os.chdir(repo.root)
318 319 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
319 320 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
320 321 ui.debug("running %s\n" % cmd)
321 322 util.system(cmd)
322 323
323 324 cmdtable = {
324 325 "^view":
325 326 (view,
326 327 [('l', 'limit', '', 'limit number of changes displayed')],
327 328 'hg view [-l LIMIT] [REVRANGE]'),
328 329 "debug-diff-tree":
329 330 (difftree,
330 331 [('p', 'patch', None, 'generate patch'),
331 332 ('r', 'recursive', None, 'recursive'),
332 333 ('P', 'pretty', None, 'pretty'),
333 334 ('s', 'stdin', None, 'stdin'),
334 335 ('C', 'copy', None, 'detect copies'),
335 336 ('S', 'search', "", 'search')],
336 337 'hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...'),
337 338 "debug-cat-file":
338 339 (catfile,
339 340 [('s', 'stdin', None, 'stdin')],
340 341 'hg debug-cat-file [OPTION]... TYPE FILE'),
341 342 "debug-config":
342 343 (config, [], 'hg debug-config'),
343 344 "debug-merge-base":
344 345 (base, [], 'hg debug-merge-base node node'),
345 346 "debug-rev-parse":
346 347 (revparse,
347 348 [('', 'default', '', 'ignored')],
348 349 'hg debug-rev-parse REV'),
349 350 "debug-rev-list":
350 351 (revlist,
351 352 [('H', 'header', None, 'header'),
352 353 ('t', 'topo-order', None, 'topo-order'),
353 354 ('p', 'parents', None, 'parents'),
354 355 ('n', 'max-count', 0, 'max-count')],
355 356 'hg debug-rev-list [options] revs'),
356 357 }
@@ -1,2352 +1,2354 b''
1 1 # mq.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 '''patch management and development
9 9
10 10 This extension lets you work with a stack of patches in a Mercurial
11 11 repository. It manages two stacks of patches - all known patches, and
12 12 applied patches (subset of known patches).
13 13
14 14 Known patches are represented as patch files in the .hg/patches
15 15 directory. Applied patches are both patch files and changesets.
16 16
17 17 Common tasks (use "hg help command" for more details):
18 18
19 19 prepare repository to work with patches qinit
20 20 create new patch qnew
21 21 import existing patch qimport
22 22
23 23 print patch series qseries
24 24 print applied patches qapplied
25 25 print name of top applied patch qtop
26 26
27 27 add known patch to applied stack qpush
28 28 remove patch from applied stack qpop
29 29 refresh contents of top applied patch qrefresh
30 30 '''
31 31
32 32 from mercurial.i18n import _
33 from mercurial.node import bin, hex, short
34 from mercurial.repo import RepoError
33 35 from mercurial import commands, cmdutil, hg, patch, revlog, util
34 36 from mercurial import repair
35 37 import os, sys, re, errno
36 38
37 39 commands.norepo += " qclone"
38 40
39 41 # Patch names looks like unix-file names.
40 42 # They must be joinable with queue directory and result in the patch path.
41 43 normname = util.normpath
42 44
43 45 class statusentry:
44 46 def __init__(self, rev, name=None):
45 47 if not name:
46 48 fields = rev.split(':', 1)
47 49 if len(fields) == 2:
48 50 self.rev, self.name = fields
49 51 else:
50 52 self.rev, self.name = None, None
51 53 else:
52 54 self.rev, self.name = rev, name
53 55
54 56 def __str__(self):
55 57 return self.rev + ':' + self.name
56 58
57 59 class queue:
58 60 def __init__(self, ui, path, patchdir=None):
59 61 self.basepath = path
60 62 self.path = patchdir or os.path.join(path, "patches")
61 63 self.opener = util.opener(self.path)
62 64 self.ui = ui
63 65 self.applied = []
64 66 self.full_series = []
65 67 self.applied_dirty = 0
66 68 self.series_dirty = 0
67 69 self.series_path = "series"
68 70 self.status_path = "status"
69 71 self.guards_path = "guards"
70 72 self.active_guards = None
71 73 self.guards_dirty = False
72 74 self._diffopts = None
73 75
74 76 if os.path.exists(self.join(self.series_path)):
75 77 self.full_series = self.opener(self.series_path).read().splitlines()
76 78 self.parse_series()
77 79
78 80 if os.path.exists(self.join(self.status_path)):
79 81 lines = self.opener(self.status_path).read().splitlines()
80 82 self.applied = [statusentry(l) for l in lines]
81 83
82 84 def diffopts(self):
83 85 if self._diffopts is None:
84 86 self._diffopts = patch.diffopts(self.ui)
85 87 return self._diffopts
86 88
87 89 def join(self, *p):
88 90 return os.path.join(self.path, *p)
89 91
90 92 def find_series(self, patch):
91 93 pre = re.compile("(\s*)([^#]+)")
92 94 index = 0
93 95 for l in self.full_series:
94 96 m = pre.match(l)
95 97 if m:
96 98 s = m.group(2)
97 99 s = s.rstrip()
98 100 if s == patch:
99 101 return index
100 102 index += 1
101 103 return None
102 104
103 105 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
104 106
105 107 def parse_series(self):
106 108 self.series = []
107 109 self.series_guards = []
108 110 for l in self.full_series:
109 111 h = l.find('#')
110 112 if h == -1:
111 113 patch = l
112 114 comment = ''
113 115 elif h == 0:
114 116 continue
115 117 else:
116 118 patch = l[:h]
117 119 comment = l[h:]
118 120 patch = patch.strip()
119 121 if patch:
120 122 if patch in self.series:
121 123 raise util.Abort(_('%s appears more than once in %s') %
122 124 (patch, self.join(self.series_path)))
123 125 self.series.append(patch)
124 126 self.series_guards.append(self.guard_re.findall(comment))
125 127
126 128 def check_guard(self, guard):
127 129 bad_chars = '# \t\r\n\f'
128 130 first = guard[0]
129 131 for c in '-+':
130 132 if first == c:
131 133 return (_('guard %r starts with invalid character: %r') %
132 134 (guard, c))
133 135 for c in bad_chars:
134 136 if c in guard:
135 137 return _('invalid character in guard %r: %r') % (guard, c)
136 138
137 139 def set_active(self, guards):
138 140 for guard in guards:
139 141 bad = self.check_guard(guard)
140 142 if bad:
141 143 raise util.Abort(bad)
142 144 guards = dict.fromkeys(guards).keys()
143 145 guards.sort()
144 146 self.ui.debug('active guards: %s\n' % ' '.join(guards))
145 147 self.active_guards = guards
146 148 self.guards_dirty = True
147 149
148 150 def active(self):
149 151 if self.active_guards is None:
150 152 self.active_guards = []
151 153 try:
152 154 guards = self.opener(self.guards_path).read().split()
153 155 except IOError, err:
154 156 if err.errno != errno.ENOENT: raise
155 157 guards = []
156 158 for i, guard in enumerate(guards):
157 159 bad = self.check_guard(guard)
158 160 if bad:
159 161 self.ui.warn('%s:%d: %s\n' %
160 162 (self.join(self.guards_path), i + 1, bad))
161 163 else:
162 164 self.active_guards.append(guard)
163 165 return self.active_guards
164 166
165 167 def set_guards(self, idx, guards):
166 168 for g in guards:
167 169 if len(g) < 2:
168 170 raise util.Abort(_('guard %r too short') % g)
169 171 if g[0] not in '-+':
170 172 raise util.Abort(_('guard %r starts with invalid char') % g)
171 173 bad = self.check_guard(g[1:])
172 174 if bad:
173 175 raise util.Abort(bad)
174 176 drop = self.guard_re.sub('', self.full_series[idx])
175 177 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
176 178 self.parse_series()
177 179 self.series_dirty = True
178 180
179 181 def pushable(self, idx):
180 182 if isinstance(idx, str):
181 183 idx = self.series.index(idx)
182 184 patchguards = self.series_guards[idx]
183 185 if not patchguards:
184 186 return True, None
185 187 default = False
186 188 guards = self.active()
187 189 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
188 190 if exactneg:
189 191 return False, exactneg[0]
190 192 pos = [g for g in patchguards if g[0] == '+']
191 193 exactpos = [g for g in pos if g[1:] in guards]
192 194 if pos:
193 195 if exactpos:
194 196 return True, exactpos[0]
195 197 return False, pos
196 198 return True, ''
197 199
198 200 def explain_pushable(self, idx, all_patches=False):
199 201 write = all_patches and self.ui.write or self.ui.warn
200 202 if all_patches or self.ui.verbose:
201 203 if isinstance(idx, str):
202 204 idx = self.series.index(idx)
203 205 pushable, why = self.pushable(idx)
204 206 if all_patches and pushable:
205 207 if why is None:
206 208 write(_('allowing %s - no guards in effect\n') %
207 209 self.series[idx])
208 210 else:
209 211 if not why:
210 212 write(_('allowing %s - no matching negative guards\n') %
211 213 self.series[idx])
212 214 else:
213 215 write(_('allowing %s - guarded by %r\n') %
214 216 (self.series[idx], why))
215 217 if not pushable:
216 218 if why:
217 219 write(_('skipping %s - guarded by %r\n') %
218 220 (self.series[idx], why))
219 221 else:
220 222 write(_('skipping %s - no matching guards\n') %
221 223 self.series[idx])
222 224
223 225 def save_dirty(self):
224 226 def write_list(items, path):
225 227 fp = self.opener(path, 'w')
226 228 for i in items:
227 229 fp.write("%s\n" % i)
228 230 fp.close()
229 231 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
230 232 if self.series_dirty: write_list(self.full_series, self.series_path)
231 233 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
232 234
233 235 def readheaders(self, patch):
234 236 def eatdiff(lines):
235 237 while lines:
236 238 l = lines[-1]
237 239 if (l.startswith("diff -") or
238 240 l.startswith("Index:") or
239 241 l.startswith("===========")):
240 242 del lines[-1]
241 243 else:
242 244 break
243 245 def eatempty(lines):
244 246 while lines:
245 247 l = lines[-1]
246 248 if re.match('\s*$', l):
247 249 del lines[-1]
248 250 else:
249 251 break
250 252
251 253 pf = self.join(patch)
252 254 message = []
253 255 comments = []
254 256 user = None
255 257 date = None
256 258 format = None
257 259 subject = None
258 260 diffstart = 0
259 261
260 262 for line in file(pf):
261 263 line = line.rstrip()
262 264 if line.startswith('diff --git'):
263 265 diffstart = 2
264 266 break
265 267 if diffstart:
266 268 if line.startswith('+++ '):
267 269 diffstart = 2
268 270 break
269 271 if line.startswith("--- "):
270 272 diffstart = 1
271 273 continue
272 274 elif format == "hgpatch":
273 275 # parse values when importing the result of an hg export
274 276 if line.startswith("# User "):
275 277 user = line[7:]
276 278 elif line.startswith("# Date "):
277 279 date = line[7:]
278 280 elif not line.startswith("# ") and line:
279 281 message.append(line)
280 282 format = None
281 283 elif line == '# HG changeset patch':
282 284 format = "hgpatch"
283 285 elif (format != "tagdone" and (line.startswith("Subject: ") or
284 286 line.startswith("subject: "))):
285 287 subject = line[9:]
286 288 format = "tag"
287 289 elif (format != "tagdone" and (line.startswith("From: ") or
288 290 line.startswith("from: "))):
289 291 user = line[6:]
290 292 format = "tag"
291 293 elif format == "tag" and line == "":
292 294 # when looking for tags (subject: from: etc) they
293 295 # end once you find a blank line in the source
294 296 format = "tagdone"
295 297 elif message or line:
296 298 message.append(line)
297 299 comments.append(line)
298 300
299 301 eatdiff(message)
300 302 eatdiff(comments)
301 303 eatempty(message)
302 304 eatempty(comments)
303 305
304 306 # make sure message isn't empty
305 307 if format and format.startswith("tag") and subject:
306 308 message.insert(0, "")
307 309 message.insert(0, subject)
308 310 return (message, comments, user, date, diffstart > 1)
309 311
310 312 def removeundo(self, repo):
311 313 undo = repo.sjoin('undo')
312 314 if not os.path.exists(undo):
313 315 return
314 316 try:
315 317 os.unlink(undo)
316 318 except OSError, inst:
317 319 self.ui.warn('error removing undo: %s\n' % str(inst))
318 320
319 321 def printdiff(self, repo, node1, node2=None, files=None,
320 322 fp=None, changes=None, opts={}):
321 323 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
322 324
323 325 patch.diff(repo, node1, node2, fns, match=matchfn,
324 326 fp=fp, changes=changes, opts=self.diffopts())
325 327
326 328 def mergeone(self, repo, mergeq, head, patch, rev):
327 329 # first try just applying the patch
328 330 (err, n) = self.apply(repo, [ patch ], update_status=False,
329 331 strict=True, merge=rev)
330 332
331 333 if err == 0:
332 334 return (err, n)
333 335
334 336 if n is None:
335 337 raise util.Abort(_("apply failed for patch %s") % patch)
336 338
337 339 self.ui.warn("patch didn't work out, merging %s\n" % patch)
338 340
339 341 # apply failed, strip away that rev and merge.
340 342 hg.clean(repo, head)
341 343 self.strip(repo, n, update=False, backup='strip')
342 344
343 345 ctx = repo.changectx(rev)
344 346 ret = hg.merge(repo, rev)
345 347 if ret:
346 348 raise util.Abort(_("update returned %d") % ret)
347 349 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
348 350 if n == None:
349 351 raise util.Abort(_("repo commit failed"))
350 352 try:
351 353 message, comments, user, date, patchfound = mergeq.readheaders(patch)
352 354 except:
353 355 raise util.Abort(_("unable to read %s") % patch)
354 356
355 357 patchf = self.opener(patch, "w")
356 358 if comments:
357 359 comments = "\n".join(comments) + '\n\n'
358 360 patchf.write(comments)
359 361 self.printdiff(repo, head, n, fp=patchf)
360 362 patchf.close()
361 363 self.removeundo(repo)
362 364 return (0, n)
363 365
364 366 def qparents(self, repo, rev=None):
365 367 if rev is None:
366 368 (p1, p2) = repo.dirstate.parents()
367 369 if p2 == revlog.nullid:
368 370 return p1
369 371 if len(self.applied) == 0:
370 372 return None
371 373 return revlog.bin(self.applied[-1].rev)
372 374 pp = repo.changelog.parents(rev)
373 375 if pp[1] != revlog.nullid:
374 376 arevs = [ x.rev for x in self.applied ]
375 377 p0 = revlog.hex(pp[0])
376 378 p1 = revlog.hex(pp[1])
377 379 if p0 in arevs:
378 380 return pp[0]
379 381 if p1 in arevs:
380 382 return pp[1]
381 383 return pp[0]
382 384
383 385 def mergepatch(self, repo, mergeq, series):
384 386 if len(self.applied) == 0:
385 387 # each of the patches merged in will have two parents. This
386 388 # can confuse the qrefresh, qdiff, and strip code because it
387 389 # needs to know which parent is actually in the patch queue.
388 390 # so, we insert a merge marker with only one parent. This way
389 391 # the first patch in the queue is never a merge patch
390 392 #
391 393 pname = ".hg.patches.merge.marker"
392 394 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
393 395 self.removeundo(repo)
394 396 self.applied.append(statusentry(revlog.hex(n), pname))
395 397 self.applied_dirty = 1
396 398
397 399 head = self.qparents(repo)
398 400
399 401 for patch in series:
400 402 patch = mergeq.lookup(patch, strict=True)
401 403 if not patch:
402 404 self.ui.warn("patch %s does not exist\n" % patch)
403 405 return (1, None)
404 406 pushable, reason = self.pushable(patch)
405 407 if not pushable:
406 408 self.explain_pushable(patch, all_patches=True)
407 409 continue
408 410 info = mergeq.isapplied(patch)
409 411 if not info:
410 412 self.ui.warn("patch %s is not applied\n" % patch)
411 413 return (1, None)
412 414 rev = revlog.bin(info[1])
413 415 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
414 416 if head:
415 417 self.applied.append(statusentry(revlog.hex(head), patch))
416 418 self.applied_dirty = 1
417 419 if err:
418 420 return (err, head)
419 421 self.save_dirty()
420 422 return (0, head)
421 423
422 424 def patch(self, repo, patchfile):
423 425 '''Apply patchfile to the working directory.
424 426 patchfile: file name of patch'''
425 427 files = {}
426 428 try:
427 429 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
428 430 files=files)
429 431 except Exception, inst:
430 432 self.ui.note(str(inst) + '\n')
431 433 if not self.ui.verbose:
432 434 self.ui.warn("patch failed, unable to continue (try -v)\n")
433 435 return (False, files, False)
434 436
435 437 return (True, files, fuzz)
436 438
437 439 def apply(self, repo, series, list=False, update_status=True,
438 440 strict=False, patchdir=None, merge=None, all_files={}):
439 441 wlock = lock = tr = None
440 442 try:
441 443 wlock = repo.wlock()
442 444 lock = repo.lock()
443 445 tr = repo.transaction()
444 446 try:
445 447 ret = self._apply(repo, series, list, update_status,
446 448 strict, patchdir, merge, all_files=all_files)
447 449 tr.close()
448 450 self.save_dirty()
449 451 return ret
450 452 except:
451 453 try:
452 454 tr.abort()
453 455 finally:
454 456 repo.invalidate()
455 457 repo.dirstate.invalidate()
456 458 raise
457 459 finally:
458 460 del tr, lock, wlock
459 461 self.removeundo(repo)
460 462
461 463 def _apply(self, repo, series, list=False, update_status=True,
462 464 strict=False, patchdir=None, merge=None, all_files={}):
463 465 # TODO unify with commands.py
464 466 if not patchdir:
465 467 patchdir = self.path
466 468 err = 0
467 469 n = None
468 470 for patchname in series:
469 471 pushable, reason = self.pushable(patchname)
470 472 if not pushable:
471 473 self.explain_pushable(patchname, all_patches=True)
472 474 continue
473 475 self.ui.warn("applying %s\n" % patchname)
474 476 pf = os.path.join(patchdir, patchname)
475 477
476 478 try:
477 479 message, comments, user, date, patchfound = self.readheaders(patchname)
478 480 except:
479 481 self.ui.warn("Unable to read %s\n" % patchname)
480 482 err = 1
481 483 break
482 484
483 485 if not message:
484 486 message = "imported patch %s\n" % patchname
485 487 else:
486 488 if list:
487 489 message.append("\nimported patch %s" % patchname)
488 490 message = '\n'.join(message)
489 491
490 492 (patcherr, files, fuzz) = self.patch(repo, pf)
491 493 all_files.update(files)
492 494 patcherr = not patcherr
493 495
494 496 if merge and files:
495 497 # Mark as removed/merged and update dirstate parent info
496 498 removed = []
497 499 merged = []
498 500 for f in files:
499 501 if os.path.exists(repo.wjoin(f)):
500 502 merged.append(f)
501 503 else:
502 504 removed.append(f)
503 505 for f in removed:
504 506 repo.dirstate.remove(f)
505 507 for f in merged:
506 508 repo.dirstate.merge(f)
507 509 p1, p2 = repo.dirstate.parents()
508 510 repo.dirstate.setparents(p1, merge)
509 511 files = patch.updatedir(self.ui, repo, files)
510 512 n = repo.commit(files, message, user, date, force=1)
511 513
512 514 if n == None:
513 515 raise util.Abort(_("repo commit failed"))
514 516
515 517 if update_status:
516 518 self.applied.append(statusentry(revlog.hex(n), patchname))
517 519
518 520 if patcherr:
519 521 if not patchfound:
520 522 self.ui.warn("patch %s is empty\n" % patchname)
521 523 err = 0
522 524 else:
523 525 self.ui.warn("patch failed, rejects left in working dir\n")
524 526 err = 1
525 527 break
526 528
527 529 if fuzz and strict:
528 530 self.ui.warn("fuzz found when applying patch, stopping\n")
529 531 err = 1
530 532 break
531 533 return (err, n)
532 534
533 535 def delete(self, repo, patches, opts):
534 536 if not patches and not opts.get('rev'):
535 537 raise util.Abort(_('qdelete requires at least one revision or '
536 538 'patch name'))
537 539
538 540 realpatches = []
539 541 for patch in patches:
540 542 patch = self.lookup(patch, strict=True)
541 543 info = self.isapplied(patch)
542 544 if info:
543 545 raise util.Abort(_("cannot delete applied patch %s") % patch)
544 546 if patch not in self.series:
545 547 raise util.Abort(_("patch %s not in series file") % patch)
546 548 realpatches.append(patch)
547 549
548 550 appliedbase = 0
549 551 if opts.get('rev'):
550 552 if not self.applied:
551 553 raise util.Abort(_('no patches applied'))
552 554 revs = cmdutil.revrange(repo, opts['rev'])
553 555 if len(revs) > 1 and revs[0] > revs[1]:
554 556 revs.reverse()
555 557 for rev in revs:
556 558 if appliedbase >= len(self.applied):
557 559 raise util.Abort(_("revision %d is not managed") % rev)
558 560
559 561 base = revlog.bin(self.applied[appliedbase].rev)
560 562 node = repo.changelog.node(rev)
561 563 if node != base:
562 564 raise util.Abort(_("cannot delete revision %d above "
563 565 "applied patches") % rev)
564 566 realpatches.append(self.applied[appliedbase].name)
565 567 appliedbase += 1
566 568
567 569 if not opts.get('keep'):
568 570 r = self.qrepo()
569 571 if r:
570 572 r.remove(realpatches, True)
571 573 else:
572 574 for p in realpatches:
573 575 os.unlink(self.join(p))
574 576
575 577 if appliedbase:
576 578 del self.applied[:appliedbase]
577 579 self.applied_dirty = 1
578 580 indices = [self.find_series(p) for p in realpatches]
579 581 indices.sort()
580 582 for i in indices[-1::-1]:
581 583 del self.full_series[i]
582 584 self.parse_series()
583 585 self.series_dirty = 1
584 586
585 587 def check_toppatch(self, repo):
586 588 if len(self.applied) > 0:
587 589 top = revlog.bin(self.applied[-1].rev)
588 590 pp = repo.dirstate.parents()
589 591 if top not in pp:
590 592 raise util.Abort(_("working directory revision is not qtip"))
591 593 return top
592 594 return None
593 595 def check_localchanges(self, repo, force=False, refresh=True):
594 596 m, a, r, d = repo.status()[:4]
595 597 if m or a or r or d:
596 598 if not force:
597 599 if refresh:
598 600 raise util.Abort(_("local changes found, refresh first"))
599 601 else:
600 602 raise util.Abort(_("local changes found"))
601 603 return m, a, r, d
602 604
603 605 _reserved = ('series', 'status', 'guards')
604 606 def check_reserved_name(self, name):
605 607 if (name in self._reserved or name.startswith('.hg')
606 608 or name.startswith('.mq')):
607 609 raise util.Abort(_('"%s" cannot be used as the name of a patch')
608 610 % name)
609 611
610 612 def new(self, repo, patch, *pats, **opts):
611 613 msg = opts.get('msg')
612 614 force = opts.get('force')
613 615 user = opts.get('user')
614 616 date = opts.get('date')
615 617 if date:
616 618 date = util.parsedate(date)
617 619 self.check_reserved_name(patch)
618 620 if os.path.exists(self.join(patch)):
619 621 raise util.Abort(_('patch "%s" already exists') % patch)
620 622 if opts.get('include') or opts.get('exclude') or pats:
621 623 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
622 624 m, a, r, d = repo.status(files=fns, match=match)[:4]
623 625 else:
624 626 m, a, r, d = self.check_localchanges(repo, force)
625 627 fns, match, anypats = cmdutil.matchpats(repo, m + a + r)
626 628 commitfiles = m + a + r
627 629 self.check_toppatch(repo)
628 630 wlock = repo.wlock()
629 631 try:
630 632 insert = self.full_series_end()
631 633 commitmsg = msg and msg or ("[mq]: %s" % patch)
632 634 n = repo.commit(commitfiles, commitmsg, user, date, match=match, force=True)
633 635 if n == None:
634 636 raise util.Abort(_("repo commit failed"))
635 637 self.full_series[insert:insert] = [patch]
636 638 self.applied.append(statusentry(revlog.hex(n), patch))
637 639 self.parse_series()
638 640 self.series_dirty = 1
639 641 self.applied_dirty = 1
640 642 p = self.opener(patch, "w")
641 643 if date:
642 644 p.write("# HG changeset patch\n")
643 645 if user:
644 646 p.write("# User " + user + "\n")
645 647 p.write("# Date %d %d\n" % date)
646 648 p.write("\n")
647 649 elif user:
648 650 p.write("From: " + user + "\n")
649 651 p.write("\n")
650 652 if msg:
651 653 msg = msg + "\n"
652 654 p.write(msg)
653 655 p.close()
654 656 wlock = None
655 657 r = self.qrepo()
656 658 if r: r.add([patch])
657 659 if commitfiles:
658 660 self.refresh(repo, short=True, git=opts.get('git'))
659 661 self.removeundo(repo)
660 662 finally:
661 663 del wlock
662 664
663 665 def strip(self, repo, rev, update=True, backup="all"):
664 666 wlock = lock = None
665 667 try:
666 668 wlock = repo.wlock()
667 669 lock = repo.lock()
668 670
669 671 if update:
670 672 self.check_localchanges(repo, refresh=False)
671 673 urev = self.qparents(repo, rev)
672 674 hg.clean(repo, urev)
673 675 repo.dirstate.write()
674 676
675 677 self.removeundo(repo)
676 678 repair.strip(self.ui, repo, rev, backup)
677 679 # strip may have unbundled a set of backed up revisions after
678 680 # the actual strip
679 681 self.removeundo(repo)
680 682 finally:
681 683 del lock, wlock
682 684
683 685 def isapplied(self, patch):
684 686 """returns (index, rev, patch)"""
685 687 for i in xrange(len(self.applied)):
686 688 a = self.applied[i]
687 689 if a.name == patch:
688 690 return (i, a.rev, a.name)
689 691 return None
690 692
691 693 # if the exact patch name does not exist, we try a few
692 694 # variations. If strict is passed, we try only #1
693 695 #
694 696 # 1) a number to indicate an offset in the series file
695 697 # 2) a unique substring of the patch name was given
696 698 # 3) patchname[-+]num to indicate an offset in the series file
697 699 def lookup(self, patch, strict=False):
698 700 patch = patch and str(patch)
699 701
700 702 def partial_name(s):
701 703 if s in self.series:
702 704 return s
703 705 matches = [x for x in self.series if s in x]
704 706 if len(matches) > 1:
705 707 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
706 708 for m in matches:
707 709 self.ui.warn(' %s\n' % m)
708 710 return None
709 711 if matches:
710 712 return matches[0]
711 713 if len(self.series) > 0 and len(self.applied) > 0:
712 714 if s == 'qtip':
713 715 return self.series[self.series_end(True)-1]
714 716 if s == 'qbase':
715 717 return self.series[0]
716 718 return None
717 719 if patch == None:
718 720 return None
719 721
720 722 # we don't want to return a partial match until we make
721 723 # sure the file name passed in does not exist (checked below)
722 724 res = partial_name(patch)
723 725 if res and res == patch:
724 726 return res
725 727
726 728 if not os.path.isfile(self.join(patch)):
727 729 try:
728 730 sno = int(patch)
729 731 except(ValueError, OverflowError):
730 732 pass
731 733 else:
732 734 if sno < len(self.series):
733 735 return self.series[sno]
734 736 if not strict:
735 737 # return any partial match made above
736 738 if res:
737 739 return res
738 740 minus = patch.rfind('-')
739 741 if minus >= 0:
740 742 res = partial_name(patch[:minus])
741 743 if res:
742 744 i = self.series.index(res)
743 745 try:
744 746 off = int(patch[minus+1:] or 1)
745 747 except(ValueError, OverflowError):
746 748 pass
747 749 else:
748 750 if i - off >= 0:
749 751 return self.series[i - off]
750 752 plus = patch.rfind('+')
751 753 if plus >= 0:
752 754 res = partial_name(patch[:plus])
753 755 if res:
754 756 i = self.series.index(res)
755 757 try:
756 758 off = int(patch[plus+1:] or 1)
757 759 except(ValueError, OverflowError):
758 760 pass
759 761 else:
760 762 if i + off < len(self.series):
761 763 return self.series[i + off]
762 764 raise util.Abort(_("patch %s not in series") % patch)
763 765
764 766 def push(self, repo, patch=None, force=False, list=False,
765 767 mergeq=None):
766 768 wlock = repo.wlock()
767 769 try:
768 770 patch = self.lookup(patch)
769 771 # Suppose our series file is: A B C and the current 'top'
770 772 # patch is B. qpush C should be performed (moving forward)
771 773 # qpush B is a NOP (no change) qpush A is an error (can't
772 774 # go backwards with qpush)
773 775 if patch:
774 776 info = self.isapplied(patch)
775 777 if info:
776 778 if info[0] < len(self.applied) - 1:
777 779 raise util.Abort(
778 780 _("cannot push to a previous patch: %s") % patch)
779 781 if info[0] < len(self.series) - 1:
780 782 self.ui.warn(
781 783 _('qpush: %s is already at the top\n') % patch)
782 784 else:
783 785 self.ui.warn(_('all patches are currently applied\n'))
784 786 return
785 787
786 788 # Following the above example, starting at 'top' of B:
787 789 # qpush should be performed (pushes C), but a subsequent
788 790 # qpush without an argument is an error (nothing to
789 791 # apply). This allows a loop of "...while hg qpush..." to
790 792 # work as it detects an error when done
791 793 if self.series_end() == len(self.series):
792 794 self.ui.warn(_('patch series already fully applied\n'))
793 795 return 1
794 796 if not force:
795 797 self.check_localchanges(repo)
796 798
797 799 self.applied_dirty = 1;
798 800 start = self.series_end()
799 801 if start > 0:
800 802 self.check_toppatch(repo)
801 803 if not patch:
802 804 patch = self.series[start]
803 805 end = start + 1
804 806 else:
805 807 end = self.series.index(patch, start) + 1
806 808 s = self.series[start:end]
807 809 all_files = {}
808 810 try:
809 811 if mergeq:
810 812 ret = self.mergepatch(repo, mergeq, s)
811 813 else:
812 814 ret = self.apply(repo, s, list, all_files=all_files)
813 815 except:
814 816 self.ui.warn(_('cleaning up working directory...'))
815 817 node = repo.dirstate.parents()[0]
816 818 hg.revert(repo, node, None)
817 819 unknown = repo.status()[4]
818 820 # only remove unknown files that we know we touched or
819 821 # created while patching
820 822 for f in unknown:
821 823 if f in all_files:
822 824 util.unlink(repo.wjoin(f))
823 825 self.ui.warn(_('done\n'))
824 826 raise
825 827 top = self.applied[-1].name
826 828 if ret[0]:
827 829 self.ui.write(
828 830 "Errors during apply, please fix and refresh %s\n" % top)
829 831 else:
830 832 self.ui.write("Now at: %s\n" % top)
831 833 return ret[0]
832 834 finally:
833 835 del wlock
834 836
835 837 def pop(self, repo, patch=None, force=False, update=True, all=False):
836 838 def getfile(f, rev, flags):
837 839 t = repo.file(f).read(rev)
838 840 repo.wwrite(f, t, flags)
839 841
840 842 wlock = repo.wlock()
841 843 try:
842 844 if patch:
843 845 # index, rev, patch
844 846 info = self.isapplied(patch)
845 847 if not info:
846 848 patch = self.lookup(patch)
847 849 info = self.isapplied(patch)
848 850 if not info:
849 851 raise util.Abort(_("patch %s is not applied") % patch)
850 852
851 853 if len(self.applied) == 0:
852 854 # Allow qpop -a to work repeatedly,
853 855 # but not qpop without an argument
854 856 self.ui.warn(_("no patches applied\n"))
855 857 return not all
856 858
857 859 if not update:
858 860 parents = repo.dirstate.parents()
859 861 rr = [ revlog.bin(x.rev) for x in self.applied ]
860 862 for p in parents:
861 863 if p in rr:
862 864 self.ui.warn("qpop: forcing dirstate update\n")
863 865 update = True
864 866
865 867 if not force and update:
866 868 self.check_localchanges(repo)
867 869
868 870 self.applied_dirty = 1;
869 871 end = len(self.applied)
870 872 if not patch:
871 873 if all:
872 874 popi = 0
873 875 else:
874 876 popi = len(self.applied) - 1
875 877 else:
876 878 popi = info[0] + 1
877 879 if popi >= end:
878 880 self.ui.warn("qpop: %s is already at the top\n" % patch)
879 881 return
880 882 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
881 883
882 884 start = info[0]
883 885 rev = revlog.bin(info[1])
884 886
885 887 if update:
886 888 top = self.check_toppatch(repo)
887 889
888 890 if repo.changelog.heads(rev) != [revlog.bin(self.applied[-1].rev)]:
889 891 raise util.Abort("popping would remove a revision not "
890 892 "managed by this patch queue")
891 893
892 894 # we know there are no local changes, so we can make a simplified
893 895 # form of hg.update.
894 896 if update:
895 897 qp = self.qparents(repo, rev)
896 898 changes = repo.changelog.read(qp)
897 899 mmap = repo.manifest.read(changes[0])
898 900 m, a, r, d, u = repo.status(qp, top)[:5]
899 901 if d:
900 902 raise util.Abort("deletions found between repo revs")
901 903 for f in m:
902 904 getfile(f, mmap[f], mmap.flags(f))
903 905 for f in r:
904 906 getfile(f, mmap[f], mmap.flags(f))
905 907 for f in m + r:
906 908 repo.dirstate.normal(f)
907 909 for f in a:
908 910 try:
909 911 os.unlink(repo.wjoin(f))
910 912 except OSError, e:
911 913 if e.errno != errno.ENOENT:
912 914 raise
913 915 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
914 916 except: pass
915 917 repo.dirstate.forget(f)
916 918 repo.dirstate.setparents(qp, revlog.nullid)
917 919 del self.applied[start:end]
918 920 self.strip(repo, rev, update=False, backup='strip')
919 921 if len(self.applied):
920 922 self.ui.write("Now at: %s\n" % self.applied[-1].name)
921 923 else:
922 924 self.ui.write("Patch queue now empty\n")
923 925 finally:
924 926 del wlock
925 927
926 928 def diff(self, repo, pats, opts):
927 929 top = self.check_toppatch(repo)
928 930 if not top:
929 931 self.ui.write("No patches applied\n")
930 932 return
931 933 qp = self.qparents(repo, top)
932 934 if opts.get('git'):
933 935 self.diffopts().git = True
934 936 self.printdiff(repo, qp, files=pats, opts=opts)
935 937
936 938 def refresh(self, repo, pats=None, **opts):
937 939 if len(self.applied) == 0:
938 940 self.ui.write("No patches applied\n")
939 941 return 1
940 942 newdate = opts.get('date')
941 943 if newdate:
942 944 newdate = '%d %d' % util.parsedate(newdate)
943 945 wlock = repo.wlock()
944 946 try:
945 947 self.check_toppatch(repo)
946 948 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
947 949 top = revlog.bin(top)
948 950 if repo.changelog.heads(top) != [top]:
949 951 raise util.Abort("cannot refresh a revision with children")
950 952 cparents = repo.changelog.parents(top)
951 953 patchparent = self.qparents(repo, top)
952 954 message, comments, user, date, patchfound = self.readheaders(patchfn)
953 955
954 956 patchf = self.opener(patchfn, 'r+')
955 957
956 958 # if the patch was a git patch, refresh it as a git patch
957 959 for line in patchf:
958 960 if line.startswith('diff --git'):
959 961 self.diffopts().git = True
960 962 break
961 963
962 964 msg = opts.get('msg', '').rstrip()
963 965 if msg and comments:
964 966 # Remove existing message, keeping the rest of the comments
965 967 # fields.
966 968 # If comments contains 'subject: ', message will prepend
967 969 # the field and a blank line.
968 970 if message:
969 971 subj = 'subject: ' + message[0].lower()
970 972 for i in xrange(len(comments)):
971 973 if subj == comments[i].lower():
972 974 del comments[i]
973 975 message = message[2:]
974 976 break
975 977 ci = 0
976 978 for mi in xrange(len(message)):
977 979 while message[mi] != comments[ci]:
978 980 ci += 1
979 981 del comments[ci]
980 982
981 983 def setheaderfield(comments, prefixes, new):
982 984 # Update all references to a field in the patch header.
983 985 # If none found, add it email style.
984 986 res = False
985 987 for prefix in prefixes:
986 988 for i in xrange(len(comments)):
987 989 if comments[i].startswith(prefix):
988 990 comments[i] = prefix + new
989 991 res = True
990 992 break
991 993 return res
992 994
993 995 newuser = opts.get('user')
994 996 if newuser:
995 997 if not setheaderfield(comments, ['From: ', '# User '], newuser):
996 998 try:
997 999 patchheaderat = comments.index('# HG changeset patch')
998 1000 comments.insert(patchheaderat + 1,'# User ' + newuser)
999 1001 except ValueError:
1000 1002 comments = ['From: ' + newuser, ''] + comments
1001 1003 user = newuser
1002 1004
1003 1005 if newdate:
1004 1006 if setheaderfield(comments, ['# Date '], newdate):
1005 1007 date = newdate
1006 1008
1007 1009 if msg:
1008 1010 comments.append(msg)
1009 1011
1010 1012 patchf.seek(0)
1011 1013 patchf.truncate()
1012 1014
1013 1015 if comments:
1014 1016 comments = "\n".join(comments) + '\n\n'
1015 1017 patchf.write(comments)
1016 1018
1017 1019 if opts.get('git'):
1018 1020 self.diffopts().git = True
1019 1021 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1020 1022 tip = repo.changelog.tip()
1021 1023 if top == tip:
1022 1024 # if the top of our patch queue is also the tip, there is an
1023 1025 # optimization here. We update the dirstate in place and strip
1024 1026 # off the tip commit. Then just commit the current directory
1025 1027 # tree. We can also send repo.commit the list of files
1026 1028 # changed to speed up the diff
1027 1029 #
1028 1030 # in short mode, we only diff the files included in the
1029 1031 # patch already
1030 1032 #
1031 1033 # this should really read:
1032 1034 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
1033 1035 # but we do it backwards to take advantage of manifest/chlog
1034 1036 # caching against the next repo.status call
1035 1037 #
1036 1038 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
1037 1039 changes = repo.changelog.read(tip)
1038 1040 man = repo.manifest.read(changes[0])
1039 1041 aaa = aa[:]
1040 1042 if opts.get('short'):
1041 1043 filelist = mm + aa + dd
1042 1044 match = dict.fromkeys(filelist).__contains__
1043 1045 else:
1044 1046 filelist = None
1045 1047 match = util.always
1046 1048 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
1047 1049
1048 1050 # we might end up with files that were added between
1049 1051 # tip and the dirstate parent, but then changed in the
1050 1052 # local dirstate. in this case, we want them to only
1051 1053 # show up in the added section
1052 1054 for x in m:
1053 1055 if x not in aa:
1054 1056 mm.append(x)
1055 1057 # we might end up with files added by the local dirstate that
1056 1058 # were deleted by the patch. In this case, they should only
1057 1059 # show up in the changed section.
1058 1060 for x in a:
1059 1061 if x in dd:
1060 1062 del dd[dd.index(x)]
1061 1063 mm.append(x)
1062 1064 else:
1063 1065 aa.append(x)
1064 1066 # make sure any files deleted in the local dirstate
1065 1067 # are not in the add or change column of the patch
1066 1068 forget = []
1067 1069 for x in d + r:
1068 1070 if x in aa:
1069 1071 del aa[aa.index(x)]
1070 1072 forget.append(x)
1071 1073 continue
1072 1074 elif x in mm:
1073 1075 del mm[mm.index(x)]
1074 1076 dd.append(x)
1075 1077
1076 1078 m = util.unique(mm)
1077 1079 r = util.unique(dd)
1078 1080 a = util.unique(aa)
1079 1081 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1080 1082 filelist = util.unique(c[0] + c[1] + c[2])
1081 1083 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1082 1084 fp=patchf, changes=c, opts=self.diffopts())
1083 1085 patchf.close()
1084 1086
1085 1087 repo.dirstate.setparents(*cparents)
1086 1088 copies = {}
1087 1089 for dst in a:
1088 1090 src = repo.dirstate.copied(dst)
1089 1091 if src is not None:
1090 1092 copies.setdefault(src, []).append(dst)
1091 1093 repo.dirstate.add(dst)
1092 1094 # remember the copies between patchparent and tip
1093 1095 # this may be slow, so don't do it if we're not tracking copies
1094 1096 if self.diffopts().git:
1095 1097 for dst in aaa:
1096 1098 f = repo.file(dst)
1097 1099 src = f.renamed(man[dst])
1098 1100 if src:
1099 1101 copies[src[0]] = copies.get(dst, [])
1100 1102 if dst in a:
1101 1103 copies[src[0]].append(dst)
1102 1104 # we can't copy a file created by the patch itself
1103 1105 if dst in copies:
1104 1106 del copies[dst]
1105 1107 for src, dsts in copies.iteritems():
1106 1108 for dst in dsts:
1107 1109 repo.dirstate.copy(src, dst)
1108 1110 for f in r:
1109 1111 repo.dirstate.remove(f)
1110 1112 # if the patch excludes a modified file, mark that
1111 1113 # file with mtime=0 so status can see it.
1112 1114 mm = []
1113 1115 for i in xrange(len(m)-1, -1, -1):
1114 1116 if not matchfn(m[i]):
1115 1117 mm.append(m[i])
1116 1118 del m[i]
1117 1119 for f in m:
1118 1120 repo.dirstate.normal(f)
1119 1121 for f in mm:
1120 1122 repo.dirstate.normallookup(f)
1121 1123 for f in forget:
1122 1124 repo.dirstate.forget(f)
1123 1125
1124 1126 if not msg:
1125 1127 if not message:
1126 1128 message = "[mq]: %s\n" % patchfn
1127 1129 else:
1128 1130 message = "\n".join(message)
1129 1131 else:
1130 1132 message = msg
1131 1133
1132 1134 if not user:
1133 1135 user = changes[1]
1134 1136
1135 1137 self.applied.pop()
1136 1138 self.applied_dirty = 1
1137 1139 self.strip(repo, top, update=False,
1138 1140 backup='strip')
1139 1141 n = repo.commit(filelist, message, user, date, match=matchfn,
1140 1142 force=1)
1141 1143 self.applied.append(statusentry(revlog.hex(n), patchfn))
1142 1144 self.removeundo(repo)
1143 1145 else:
1144 1146 self.printdiff(repo, patchparent, fp=patchf)
1145 1147 patchf.close()
1146 1148 added = repo.status()[1]
1147 1149 for a in added:
1148 1150 f = repo.wjoin(a)
1149 1151 try:
1150 1152 os.unlink(f)
1151 1153 except OSError, e:
1152 1154 if e.errno != errno.ENOENT:
1153 1155 raise
1154 1156 try: os.removedirs(os.path.dirname(f))
1155 1157 except: pass
1156 1158 # forget the file copies in the dirstate
1157 1159 # push should readd the files later on
1158 1160 repo.dirstate.forget(a)
1159 1161 self.pop(repo, force=True)
1160 1162 self.push(repo, force=True)
1161 1163 finally:
1162 1164 del wlock
1163 1165
1164 1166 def init(self, repo, create=False):
1165 1167 if not create and os.path.isdir(self.path):
1166 1168 raise util.Abort(_("patch queue directory already exists"))
1167 1169 try:
1168 1170 os.mkdir(self.path)
1169 1171 except OSError, inst:
1170 1172 if inst.errno != errno.EEXIST or not create:
1171 1173 raise
1172 1174 if create:
1173 1175 return self.qrepo(create=True)
1174 1176
1175 1177 def unapplied(self, repo, patch=None):
1176 1178 if patch and patch not in self.series:
1177 1179 raise util.Abort(_("patch %s is not in series file") % patch)
1178 1180 if not patch:
1179 1181 start = self.series_end()
1180 1182 else:
1181 1183 start = self.series.index(patch) + 1
1182 1184 unapplied = []
1183 1185 for i in xrange(start, len(self.series)):
1184 1186 pushable, reason = self.pushable(i)
1185 1187 if pushable:
1186 1188 unapplied.append((i, self.series[i]))
1187 1189 self.explain_pushable(i)
1188 1190 return unapplied
1189 1191
1190 1192 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1191 1193 summary=False):
1192 1194 def displayname(patchname):
1193 1195 if summary:
1194 1196 msg = self.readheaders(patchname)[0]
1195 1197 msg = msg and ': ' + msg[0] or ': '
1196 1198 else:
1197 1199 msg = ''
1198 1200 return '%s%s' % (patchname, msg)
1199 1201
1200 1202 applied = dict.fromkeys([p.name for p in self.applied])
1201 1203 if length is None:
1202 1204 length = len(self.series) - start
1203 1205 if not missing:
1204 1206 for i in xrange(start, start+length):
1205 1207 patch = self.series[i]
1206 1208 if patch in applied:
1207 1209 stat = 'A'
1208 1210 elif self.pushable(i)[0]:
1209 1211 stat = 'U'
1210 1212 else:
1211 1213 stat = 'G'
1212 1214 pfx = ''
1213 1215 if self.ui.verbose:
1214 1216 pfx = '%d %s ' % (i, stat)
1215 1217 elif status and status != stat:
1216 1218 continue
1217 1219 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1218 1220 else:
1219 1221 msng_list = []
1220 1222 for root, dirs, files in os.walk(self.path):
1221 1223 d = root[len(self.path) + 1:]
1222 1224 for f in files:
1223 1225 fl = os.path.join(d, f)
1224 1226 if (fl not in self.series and
1225 1227 fl not in (self.status_path, self.series_path,
1226 1228 self.guards_path)
1227 1229 and not fl.startswith('.')):
1228 1230 msng_list.append(fl)
1229 1231 msng_list.sort()
1230 1232 for x in msng_list:
1231 1233 pfx = self.ui.verbose and ('D ') or ''
1232 1234 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1233 1235
1234 1236 def issaveline(self, l):
1235 1237 if l.name == '.hg.patches.save.line':
1236 1238 return True
1237 1239
1238 1240 def qrepo(self, create=False):
1239 1241 if create or os.path.isdir(self.join(".hg")):
1240 1242 return hg.repository(self.ui, path=self.path, create=create)
1241 1243
1242 1244 def restore(self, repo, rev, delete=None, qupdate=None):
1243 1245 c = repo.changelog.read(rev)
1244 1246 desc = c[4].strip()
1245 1247 lines = desc.splitlines()
1246 1248 i = 0
1247 1249 datastart = None
1248 1250 series = []
1249 1251 applied = []
1250 1252 qpp = None
1251 1253 for i in xrange(0, len(lines)):
1252 1254 if lines[i] == 'Patch Data:':
1253 1255 datastart = i + 1
1254 1256 elif lines[i].startswith('Dirstate:'):
1255 1257 l = lines[i].rstrip()
1256 1258 l = l[10:].split(' ')
1257 qpp = [ hg.bin(x) for x in l ]
1259 qpp = [ bin(x) for x in l ]
1258 1260 elif datastart != None:
1259 1261 l = lines[i].rstrip()
1260 1262 se = statusentry(l)
1261 1263 file_ = se.name
1262 1264 if se.rev:
1263 1265 applied.append(se)
1264 1266 else:
1265 1267 series.append(file_)
1266 1268 if datastart == None:
1267 1269 self.ui.warn("No saved patch data found\n")
1268 1270 return 1
1269 1271 self.ui.warn("restoring status: %s\n" % lines[0])
1270 1272 self.full_series = series
1271 1273 self.applied = applied
1272 1274 self.parse_series()
1273 1275 self.series_dirty = 1
1274 1276 self.applied_dirty = 1
1275 1277 heads = repo.changelog.heads()
1276 1278 if delete:
1277 1279 if rev not in heads:
1278 1280 self.ui.warn("save entry has children, leaving it alone\n")
1279 1281 else:
1280 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1282 self.ui.warn("removing save entry %s\n" % short(rev))
1281 1283 pp = repo.dirstate.parents()
1282 1284 if rev in pp:
1283 1285 update = True
1284 1286 else:
1285 1287 update = False
1286 1288 self.strip(repo, rev, update=update, backup='strip')
1287 1289 if qpp:
1288 1290 self.ui.warn("saved queue repository parents: %s %s\n" %
1289 (hg.short(qpp[0]), hg.short(qpp[1])))
1291 (short(qpp[0]), short(qpp[1])))
1290 1292 if qupdate:
1291 1293 self.ui.status(_("queue directory updating\n"))
1292 1294 r = self.qrepo()
1293 1295 if not r:
1294 1296 self.ui.warn("Unable to load queue repository\n")
1295 1297 return 1
1296 1298 hg.clean(r, qpp[0])
1297 1299
1298 1300 def save(self, repo, msg=None):
1299 1301 if len(self.applied) == 0:
1300 1302 self.ui.warn("save: no patches applied, exiting\n")
1301 1303 return 1
1302 1304 if self.issaveline(self.applied[-1]):
1303 1305 self.ui.warn("status is already saved\n")
1304 1306 return 1
1305 1307
1306 1308 ar = [ ':' + x for x in self.full_series ]
1307 1309 if not msg:
1308 1310 msg = "hg patches saved state"
1309 1311 else:
1310 1312 msg = "hg patches: " + msg.rstrip('\r\n')
1311 1313 r = self.qrepo()
1312 1314 if r:
1313 1315 pp = r.dirstate.parents()
1314 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1316 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1315 1317 msg += "\n\nPatch Data:\n"
1316 1318 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1317 1319 "\n".join(ar) + '\n' or "")
1318 1320 n = repo.commit(None, text, user=None, force=1)
1319 1321 if not n:
1320 1322 self.ui.warn("repo commit failed\n")
1321 1323 return 1
1322 1324 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1323 1325 self.applied_dirty = 1
1324 1326 self.removeundo(repo)
1325 1327
1326 1328 def full_series_end(self):
1327 1329 if len(self.applied) > 0:
1328 1330 p = self.applied[-1].name
1329 1331 end = self.find_series(p)
1330 1332 if end == None:
1331 1333 return len(self.full_series)
1332 1334 return end + 1
1333 1335 return 0
1334 1336
1335 1337 def series_end(self, all_patches=False):
1336 1338 """If all_patches is False, return the index of the next pushable patch
1337 1339 in the series, or the series length. If all_patches is True, return the
1338 1340 index of the first patch past the last applied one.
1339 1341 """
1340 1342 end = 0
1341 1343 def next(start):
1342 1344 if all_patches:
1343 1345 return start
1344 1346 i = start
1345 1347 while i < len(self.series):
1346 1348 p, reason = self.pushable(i)
1347 1349 if p:
1348 1350 break
1349 1351 self.explain_pushable(i)
1350 1352 i += 1
1351 1353 return i
1352 1354 if len(self.applied) > 0:
1353 1355 p = self.applied[-1].name
1354 1356 try:
1355 1357 end = self.series.index(p)
1356 1358 except ValueError:
1357 1359 return 0
1358 1360 return next(end + 1)
1359 1361 return next(end)
1360 1362
1361 1363 def appliedname(self, index):
1362 1364 pname = self.applied[index].name
1363 1365 if not self.ui.verbose:
1364 1366 p = pname
1365 1367 else:
1366 1368 p = str(self.series.index(pname)) + " " + pname
1367 1369 return p
1368 1370
1369 1371 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1370 1372 force=None, git=False):
1371 1373 def checkseries(patchname):
1372 1374 if patchname in self.series:
1373 1375 raise util.Abort(_('patch %s is already in the series file')
1374 1376 % patchname)
1375 1377 def checkfile(patchname):
1376 1378 if not force and os.path.exists(self.join(patchname)):
1377 1379 raise util.Abort(_('patch "%s" already exists')
1378 1380 % patchname)
1379 1381
1380 1382 if rev:
1381 1383 if files:
1382 1384 raise util.Abort(_('option "-r" not valid when importing '
1383 1385 'files'))
1384 1386 rev = cmdutil.revrange(repo, rev)
1385 1387 rev.sort(lambda x, y: cmp(y, x))
1386 1388 if (len(files) > 1 or len(rev) > 1) and patchname:
1387 1389 raise util.Abort(_('option "-n" not valid when importing multiple '
1388 1390 'patches'))
1389 1391 i = 0
1390 1392 added = []
1391 1393 if rev:
1392 1394 # If mq patches are applied, we can only import revisions
1393 1395 # that form a linear path to qbase.
1394 1396 # Otherwise, they should form a linear path to a head.
1395 1397 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1396 1398 if len(heads) > 1:
1397 1399 raise util.Abort(_('revision %d is the root of more than one '
1398 1400 'branch') % rev[-1])
1399 1401 if self.applied:
1400 1402 base = revlog.hex(repo.changelog.node(rev[0]))
1401 1403 if base in [n.rev for n in self.applied]:
1402 1404 raise util.Abort(_('revision %d is already managed')
1403 1405 % rev[0])
1404 1406 if heads != [revlog.bin(self.applied[-1].rev)]:
1405 1407 raise util.Abort(_('revision %d is not the parent of '
1406 1408 'the queue') % rev[0])
1407 1409 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1408 1410 lastparent = repo.changelog.parentrevs(base)[0]
1409 1411 else:
1410 1412 if heads != [repo.changelog.node(rev[0])]:
1411 1413 raise util.Abort(_('revision %d has unmanaged children')
1412 1414 % rev[0])
1413 1415 lastparent = None
1414 1416
1415 1417 if git:
1416 1418 self.diffopts().git = True
1417 1419
1418 1420 for r in rev:
1419 1421 p1, p2 = repo.changelog.parentrevs(r)
1420 1422 n = repo.changelog.node(r)
1421 1423 if p2 != revlog.nullrev:
1422 1424 raise util.Abort(_('cannot import merge revision %d') % r)
1423 1425 if lastparent and lastparent != r:
1424 1426 raise util.Abort(_('revision %d is not the parent of %d')
1425 1427 % (r, lastparent))
1426 1428 lastparent = p1
1427 1429
1428 1430 if not patchname:
1429 1431 patchname = normname('%d.diff' % r)
1430 1432 self.check_reserved_name(patchname)
1431 1433 checkseries(patchname)
1432 1434 checkfile(patchname)
1433 1435 self.full_series.insert(0, patchname)
1434 1436
1435 1437 patchf = self.opener(patchname, "w")
1436 1438 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1437 1439 patchf.close()
1438 1440
1439 1441 se = statusentry(revlog.hex(n), patchname)
1440 1442 self.applied.insert(0, se)
1441 1443
1442 1444 added.append(patchname)
1443 1445 patchname = None
1444 1446 self.parse_series()
1445 1447 self.applied_dirty = 1
1446 1448
1447 1449 for filename in files:
1448 1450 if existing:
1449 1451 if filename == '-':
1450 1452 raise util.Abort(_('-e is incompatible with import from -'))
1451 1453 if not patchname:
1452 1454 patchname = normname(filename)
1453 1455 self.check_reserved_name(patchname)
1454 1456 if not os.path.isfile(self.join(patchname)):
1455 1457 raise util.Abort(_("patch %s does not exist") % patchname)
1456 1458 else:
1457 1459 try:
1458 1460 if filename == '-':
1459 1461 if not patchname:
1460 1462 raise util.Abort(_('need --name to import a patch from -'))
1461 1463 text = sys.stdin.read()
1462 1464 else:
1463 1465 text = file(filename, 'rb').read()
1464 1466 except IOError:
1465 1467 raise util.Abort(_("unable to read %s") % patchname)
1466 1468 if not patchname:
1467 1469 patchname = normname(os.path.basename(filename))
1468 1470 self.check_reserved_name(patchname)
1469 1471 checkfile(patchname)
1470 1472 patchf = self.opener(patchname, "w")
1471 1473 patchf.write(text)
1472 1474 checkseries(patchname)
1473 1475 index = self.full_series_end() + i
1474 1476 self.full_series[index:index] = [patchname]
1475 1477 self.parse_series()
1476 1478 self.ui.warn("adding %s to series file\n" % patchname)
1477 1479 i += 1
1478 1480 added.append(patchname)
1479 1481 patchname = None
1480 1482 self.series_dirty = 1
1481 1483 qrepo = self.qrepo()
1482 1484 if qrepo:
1483 1485 qrepo.add(added)
1484 1486
1485 1487 def delete(ui, repo, *patches, **opts):
1486 1488 """remove patches from queue
1487 1489
1488 1490 The patches must not be applied, unless they are arguments to
1489 1491 the --rev parameter. At least one patch or revision is required.
1490 1492
1491 1493 With --rev, mq will stop managing the named revisions (converting
1492 1494 them to regular mercurial changesets). The patches must be applied
1493 1495 and at the base of the stack. This option is useful when the patches
1494 1496 have been applied upstream.
1495 1497
1496 1498 With --keep, the patch files are preserved in the patch directory."""
1497 1499 q = repo.mq
1498 1500 q.delete(repo, patches, opts)
1499 1501 q.save_dirty()
1500 1502 return 0
1501 1503
1502 1504 def applied(ui, repo, patch=None, **opts):
1503 1505 """print the patches already applied"""
1504 1506 q = repo.mq
1505 1507 if patch:
1506 1508 if patch not in q.series:
1507 1509 raise util.Abort(_("patch %s is not in series file") % patch)
1508 1510 end = q.series.index(patch) + 1
1509 1511 else:
1510 1512 end = q.series_end(True)
1511 1513 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1512 1514
1513 1515 def unapplied(ui, repo, patch=None, **opts):
1514 1516 """print the patches not yet applied"""
1515 1517 q = repo.mq
1516 1518 if patch:
1517 1519 if patch not in q.series:
1518 1520 raise util.Abort(_("patch %s is not in series file") % patch)
1519 1521 start = q.series.index(patch) + 1
1520 1522 else:
1521 1523 start = q.series_end(True)
1522 1524 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1523 1525
1524 1526 def qimport(ui, repo, *filename, **opts):
1525 1527 """import a patch
1526 1528
1527 1529 The patch will have the same name as its source file unless you
1528 1530 give it a new one with --name.
1529 1531
1530 1532 You can register an existing patch inside the patch directory
1531 1533 with the --existing flag.
1532 1534
1533 1535 With --force, an existing patch of the same name will be overwritten.
1534 1536
1535 1537 An existing changeset may be placed under mq control with --rev
1536 1538 (e.g. qimport --rev tip -n patch will place tip under mq control).
1537 1539 With --git, patches imported with --rev will use the git diff
1538 1540 format.
1539 1541 """
1540 1542 q = repo.mq
1541 1543 q.qimport(repo, filename, patchname=opts['name'],
1542 1544 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1543 1545 git=opts['git'])
1544 1546 q.save_dirty()
1545 1547 return 0
1546 1548
1547 1549 def init(ui, repo, **opts):
1548 1550 """init a new queue repository
1549 1551
1550 1552 The queue repository is unversioned by default. If -c is
1551 1553 specified, qinit will create a separate nested repository
1552 1554 for patches (qinit -c may also be run later to convert
1553 1555 an unversioned patch repository into a versioned one).
1554 1556 You can use qcommit to commit changes to this queue repository."""
1555 1557 q = repo.mq
1556 1558 r = q.init(repo, create=opts['create_repo'])
1557 1559 q.save_dirty()
1558 1560 if r:
1559 1561 if not os.path.exists(r.wjoin('.hgignore')):
1560 1562 fp = r.wopener('.hgignore', 'w')
1561 1563 fp.write('^\\.hg\n')
1562 1564 fp.write('^\\.mq\n')
1563 1565 fp.write('syntax: glob\n')
1564 1566 fp.write('status\n')
1565 1567 fp.write('guards\n')
1566 1568 fp.close()
1567 1569 if not os.path.exists(r.wjoin('series')):
1568 1570 r.wopener('series', 'w').close()
1569 1571 r.add(['.hgignore', 'series'])
1570 1572 commands.add(ui, r)
1571 1573 return 0
1572 1574
1573 1575 def clone(ui, source, dest=None, **opts):
1574 1576 '''clone main and patch repository at same time
1575 1577
1576 1578 If source is local, destination will have no patches applied. If
1577 1579 source is remote, this command can not check if patches are
1578 1580 applied in source, so cannot guarantee that patches are not
1579 1581 applied in destination. If you clone remote repository, be sure
1580 1582 before that it has no patches applied.
1581 1583
1582 1584 Source patch repository is looked for in <src>/.hg/patches by
1583 1585 default. Use -p <url> to change.
1584 1586
1585 1587 The patch directory must be a nested mercurial repository, as
1586 1588 would be created by qinit -c.
1587 1589 '''
1588 1590 def patchdir(repo):
1589 1591 url = repo.url()
1590 1592 if url.endswith('/'):
1591 1593 url = url[:-1]
1592 1594 return url + '/.hg/patches'
1593 1595 cmdutil.setremoteconfig(ui, opts)
1594 1596 if dest is None:
1595 1597 dest = hg.defaultdest(source)
1596 1598 sr = hg.repository(ui, ui.expandpath(source))
1597 1599 patchespath = opts['patches'] or patchdir(sr)
1598 1600 try:
1599 1601 pr = hg.repository(ui, patchespath)
1600 except hg.RepoError:
1602 except RepoError:
1601 1603 raise util.Abort(_('versioned patch repository not found'
1602 1604 ' (see qinit -c)'))
1603 1605 qbase, destrev = None, None
1604 1606 if sr.local():
1605 1607 if sr.mq.applied:
1606 1608 qbase = revlog.bin(sr.mq.applied[0].rev)
1607 1609 if not hg.islocal(dest):
1608 1610 heads = dict.fromkeys(sr.heads())
1609 1611 for h in sr.heads(qbase):
1610 1612 del heads[h]
1611 1613 destrev = heads.keys()
1612 1614 destrev.append(sr.changelog.parents(qbase)[0])
1613 1615 elif sr.capable('lookup'):
1614 1616 qbase = sr.lookup('qbase')
1615 1617 ui.note(_('cloning main repo\n'))
1616 1618 sr, dr = hg.clone(ui, sr.url(), dest,
1617 1619 pull=opts['pull'],
1618 1620 rev=destrev,
1619 1621 update=False,
1620 1622 stream=opts['uncompressed'])
1621 1623 ui.note(_('cloning patch repo\n'))
1622 1624 spr, dpr = hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1623 1625 pull=opts['pull'], update=not opts['noupdate'],
1624 1626 stream=opts['uncompressed'])
1625 1627 if dr.local():
1626 1628 if qbase:
1627 1629 ui.note(_('stripping applied patches from destination repo\n'))
1628 1630 dr.mq.strip(dr, qbase, update=False, backup=None)
1629 1631 if not opts['noupdate']:
1630 1632 ui.note(_('updating destination repo\n'))
1631 1633 hg.update(dr, dr.changelog.tip())
1632 1634
1633 1635 def commit(ui, repo, *pats, **opts):
1634 1636 """commit changes in the queue repository"""
1635 1637 q = repo.mq
1636 1638 r = q.qrepo()
1637 1639 if not r: raise util.Abort('no queue repository')
1638 1640 commands.commit(r.ui, r, *pats, **opts)
1639 1641
1640 1642 def series(ui, repo, **opts):
1641 1643 """print the entire series file"""
1642 1644 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1643 1645 return 0
1644 1646
1645 1647 def top(ui, repo, **opts):
1646 1648 """print the name of the current patch"""
1647 1649 q = repo.mq
1648 1650 t = q.applied and q.series_end(True) or 0
1649 1651 if t:
1650 1652 return q.qseries(repo, start=t-1, length=1, status='A',
1651 1653 summary=opts.get('summary'))
1652 1654 else:
1653 1655 ui.write("No patches applied\n")
1654 1656 return 1
1655 1657
1656 1658 def next(ui, repo, **opts):
1657 1659 """print the name of the next patch"""
1658 1660 q = repo.mq
1659 1661 end = q.series_end()
1660 1662 if end == len(q.series):
1661 1663 ui.write("All patches applied\n")
1662 1664 return 1
1663 1665 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1664 1666
1665 1667 def prev(ui, repo, **opts):
1666 1668 """print the name of the previous patch"""
1667 1669 q = repo.mq
1668 1670 l = len(q.applied)
1669 1671 if l == 1:
1670 1672 ui.write("Only one patch applied\n")
1671 1673 return 1
1672 1674 if not l:
1673 1675 ui.write("No patches applied\n")
1674 1676 return 1
1675 1677 return q.qseries(repo, start=l-2, length=1, status='A',
1676 1678 summary=opts.get('summary'))
1677 1679
1678 1680 def setupheaderopts(ui, opts):
1679 1681 def do(opt,val):
1680 1682 if not opts[opt] and opts['current' + opt]:
1681 1683 opts[opt] = val
1682 1684 do('user', ui.username())
1683 1685 do('date', "%d %d" % util.makedate())
1684 1686
1685 1687 def new(ui, repo, patch, *args, **opts):
1686 1688 """create a new patch
1687 1689
1688 1690 qnew creates a new patch on top of the currently-applied patch
1689 1691 (if any). It will refuse to run if there are any outstanding
1690 1692 changes unless -f is specified, in which case the patch will
1691 1693 be initialised with them. You may also use -I, -X, and/or a list of
1692 1694 files after the patch name to add only changes to matching files
1693 1695 to the new patch, leaving the rest as uncommitted modifications.
1694 1696
1695 1697 -e, -m or -l set the patch header as well as the commit message.
1696 1698 If none is specified, the patch header is empty and the
1697 1699 commit message is '[mq]: PATCH'"""
1698 1700 q = repo.mq
1699 1701 message = cmdutil.logmessage(opts)
1700 1702 if opts['edit']:
1701 1703 message = ui.edit(message, ui.username())
1702 1704 opts['msg'] = message
1703 1705 setupheaderopts(ui, opts)
1704 1706 q.new(repo, patch, *args, **opts)
1705 1707 q.save_dirty()
1706 1708 return 0
1707 1709
1708 1710 def refresh(ui, repo, *pats, **opts):
1709 1711 """update the current patch
1710 1712
1711 1713 If any file patterns are provided, the refreshed patch will contain only
1712 1714 the modifications that match those patterns; the remaining modifications
1713 1715 will remain in the working directory.
1714 1716
1715 1717 hg add/remove/copy/rename work as usual, though you might want to use
1716 1718 git-style patches (--git or [diff] git=1) to track copies and renames.
1717 1719 """
1718 1720 q = repo.mq
1719 1721 message = cmdutil.logmessage(opts)
1720 1722 if opts['edit']:
1721 1723 if not q.applied:
1722 1724 ui.write(_("No patches applied\n"))
1723 1725 return 1
1724 1726 if message:
1725 1727 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1726 1728 patch = q.applied[-1].name
1727 1729 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1728 1730 message = ui.edit('\n'.join(message), user or ui.username())
1729 1731 setupheaderopts(ui, opts)
1730 1732 ret = q.refresh(repo, pats, msg=message, **opts)
1731 1733 q.save_dirty()
1732 1734 return ret
1733 1735
1734 1736 def diff(ui, repo, *pats, **opts):
1735 1737 """diff of the current patch"""
1736 1738 repo.mq.diff(repo, pats, opts)
1737 1739 return 0
1738 1740
1739 1741 def fold(ui, repo, *files, **opts):
1740 1742 """fold the named patches into the current patch
1741 1743
1742 1744 Patches must not yet be applied. Each patch will be successively
1743 1745 applied to the current patch in the order given. If all the
1744 1746 patches apply successfully, the current patch will be refreshed
1745 1747 with the new cumulative patch, and the folded patches will
1746 1748 be deleted. With -k/--keep, the folded patch files will not
1747 1749 be removed afterwards.
1748 1750
1749 1751 The header for each folded patch will be concatenated with
1750 1752 the current patch header, separated by a line of '* * *'."""
1751 1753
1752 1754 q = repo.mq
1753 1755
1754 1756 if not files:
1755 1757 raise util.Abort(_('qfold requires at least one patch name'))
1756 1758 if not q.check_toppatch(repo):
1757 1759 raise util.Abort(_('No patches applied'))
1758 1760
1759 1761 message = cmdutil.logmessage(opts)
1760 1762 if opts['edit']:
1761 1763 if message:
1762 1764 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1763 1765
1764 1766 parent = q.lookup('qtip')
1765 1767 patches = []
1766 1768 messages = []
1767 1769 for f in files:
1768 1770 p = q.lookup(f)
1769 1771 if p in patches or p == parent:
1770 1772 ui.warn(_('Skipping already folded patch %s') % p)
1771 1773 if q.isapplied(p):
1772 1774 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1773 1775 patches.append(p)
1774 1776
1775 1777 for p in patches:
1776 1778 if not message:
1777 1779 messages.append(q.readheaders(p)[0])
1778 1780 pf = q.join(p)
1779 1781 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1780 1782 if not patchsuccess:
1781 1783 raise util.Abort(_('Error folding patch %s') % p)
1782 1784 patch.updatedir(ui, repo, files)
1783 1785
1784 1786 if not message:
1785 1787 message, comments, user = q.readheaders(parent)[0:3]
1786 1788 for msg in messages:
1787 1789 message.append('* * *')
1788 1790 message.extend(msg)
1789 1791 message = '\n'.join(message)
1790 1792
1791 1793 if opts['edit']:
1792 1794 message = ui.edit(message, user or ui.username())
1793 1795
1794 1796 q.refresh(repo, msg=message)
1795 1797 q.delete(repo, patches, opts)
1796 1798 q.save_dirty()
1797 1799
1798 1800 def goto(ui, repo, patch, **opts):
1799 1801 '''push or pop patches until named patch is at top of stack'''
1800 1802 q = repo.mq
1801 1803 patch = q.lookup(patch)
1802 1804 if q.isapplied(patch):
1803 1805 ret = q.pop(repo, patch, force=opts['force'])
1804 1806 else:
1805 1807 ret = q.push(repo, patch, force=opts['force'])
1806 1808 q.save_dirty()
1807 1809 return ret
1808 1810
1809 1811 def guard(ui, repo, *args, **opts):
1810 1812 '''set or print guards for a patch
1811 1813
1812 1814 Guards control whether a patch can be pushed. A patch with no
1813 1815 guards is always pushed. A patch with a positive guard ("+foo") is
1814 1816 pushed only if the qselect command has activated it. A patch with
1815 1817 a negative guard ("-foo") is never pushed if the qselect command
1816 1818 has activated it.
1817 1819
1818 1820 With no arguments, print the currently active guards.
1819 1821 With arguments, set guards for the named patch.
1820 1822
1821 1823 To set a negative guard "-foo" on topmost patch ("--" is needed so
1822 1824 hg will not interpret "-foo" as an option):
1823 1825 hg qguard -- -foo
1824 1826
1825 1827 To set guards on another patch:
1826 1828 hg qguard other.patch +2.6.17 -stable
1827 1829 '''
1828 1830 def status(idx):
1829 1831 guards = q.series_guards[idx] or ['unguarded']
1830 1832 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1831 1833 q = repo.mq
1832 1834 patch = None
1833 1835 args = list(args)
1834 1836 if opts['list']:
1835 1837 if args or opts['none']:
1836 1838 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1837 1839 for i in xrange(len(q.series)):
1838 1840 status(i)
1839 1841 return
1840 1842 if not args or args[0][0:1] in '-+':
1841 1843 if not q.applied:
1842 1844 raise util.Abort(_('no patches applied'))
1843 1845 patch = q.applied[-1].name
1844 1846 if patch is None and args[0][0:1] not in '-+':
1845 1847 patch = args.pop(0)
1846 1848 if patch is None:
1847 1849 raise util.Abort(_('no patch to work with'))
1848 1850 if args or opts['none']:
1849 1851 idx = q.find_series(patch)
1850 1852 if idx is None:
1851 1853 raise util.Abort(_('no patch named %s') % patch)
1852 1854 q.set_guards(idx, args)
1853 1855 q.save_dirty()
1854 1856 else:
1855 1857 status(q.series.index(q.lookup(patch)))
1856 1858
1857 1859 def header(ui, repo, patch=None):
1858 1860 """Print the header of the topmost or specified patch"""
1859 1861 q = repo.mq
1860 1862
1861 1863 if patch:
1862 1864 patch = q.lookup(patch)
1863 1865 else:
1864 1866 if not q.applied:
1865 1867 ui.write('No patches applied\n')
1866 1868 return 1
1867 1869 patch = q.lookup('qtip')
1868 1870 message = repo.mq.readheaders(patch)[0]
1869 1871
1870 1872 ui.write('\n'.join(message) + '\n')
1871 1873
1872 1874 def lastsavename(path):
1873 1875 (directory, base) = os.path.split(path)
1874 1876 names = os.listdir(directory)
1875 1877 namere = re.compile("%s.([0-9]+)" % base)
1876 1878 maxindex = None
1877 1879 maxname = None
1878 1880 for f in names:
1879 1881 m = namere.match(f)
1880 1882 if m:
1881 1883 index = int(m.group(1))
1882 1884 if maxindex == None or index > maxindex:
1883 1885 maxindex = index
1884 1886 maxname = f
1885 1887 if maxname:
1886 1888 return (os.path.join(directory, maxname), maxindex)
1887 1889 return (None, None)
1888 1890
1889 1891 def savename(path):
1890 1892 (last, index) = lastsavename(path)
1891 1893 if last is None:
1892 1894 index = 0
1893 1895 newpath = path + ".%d" % (index + 1)
1894 1896 return newpath
1895 1897
1896 1898 def push(ui, repo, patch=None, **opts):
1897 1899 """push the next patch onto the stack"""
1898 1900 q = repo.mq
1899 1901 mergeq = None
1900 1902
1901 1903 if opts['all']:
1902 1904 if not q.series:
1903 1905 ui.warn(_('no patches in series\n'))
1904 1906 return 0
1905 1907 patch = q.series[-1]
1906 1908 if opts['merge']:
1907 1909 if opts['name']:
1908 1910 newpath = opts['name']
1909 1911 else:
1910 1912 newpath, i = lastsavename(q.path)
1911 1913 if not newpath:
1912 1914 ui.warn("no saved queues found, please use -n\n")
1913 1915 return 1
1914 1916 mergeq = queue(ui, repo.join(""), newpath)
1915 1917 ui.warn("merging with queue at: %s\n" % mergeq.path)
1916 1918 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1917 1919 mergeq=mergeq)
1918 1920 return ret
1919 1921
1920 1922 def pop(ui, repo, patch=None, **opts):
1921 1923 """pop the current patch off the stack"""
1922 1924 localupdate = True
1923 1925 if opts['name']:
1924 1926 q = queue(ui, repo.join(""), repo.join(opts['name']))
1925 1927 ui.warn('using patch queue: %s\n' % q.path)
1926 1928 localupdate = False
1927 1929 else:
1928 1930 q = repo.mq
1929 1931 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1930 1932 all=opts['all'])
1931 1933 q.save_dirty()
1932 1934 return ret
1933 1935
1934 1936 def rename(ui, repo, patch, name=None, **opts):
1935 1937 """rename a patch
1936 1938
1937 1939 With one argument, renames the current patch to PATCH1.
1938 1940 With two arguments, renames PATCH1 to PATCH2."""
1939 1941
1940 1942 q = repo.mq
1941 1943
1942 1944 if not name:
1943 1945 name = patch
1944 1946 patch = None
1945 1947
1946 1948 if patch:
1947 1949 patch = q.lookup(patch)
1948 1950 else:
1949 1951 if not q.applied:
1950 1952 ui.write(_('No patches applied\n'))
1951 1953 return
1952 1954 patch = q.lookup('qtip')
1953 1955 absdest = q.join(name)
1954 1956 if os.path.isdir(absdest):
1955 1957 name = normname(os.path.join(name, os.path.basename(patch)))
1956 1958 absdest = q.join(name)
1957 1959 if os.path.exists(absdest):
1958 1960 raise util.Abort(_('%s already exists') % absdest)
1959 1961
1960 1962 if name in q.series:
1961 1963 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1962 1964
1963 1965 if ui.verbose:
1964 1966 ui.write('Renaming %s to %s\n' % (patch, name))
1965 1967 i = q.find_series(patch)
1966 1968 guards = q.guard_re.findall(q.full_series[i])
1967 1969 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1968 1970 q.parse_series()
1969 1971 q.series_dirty = 1
1970 1972
1971 1973 info = q.isapplied(patch)
1972 1974 if info:
1973 1975 q.applied[info[0]] = statusentry(info[1], name)
1974 1976 q.applied_dirty = 1
1975 1977
1976 1978 util.rename(q.join(patch), absdest)
1977 1979 r = q.qrepo()
1978 1980 if r:
1979 1981 wlock = r.wlock()
1980 1982 try:
1981 1983 if r.dirstate[name] == 'r':
1982 1984 r.undelete([name])
1983 1985 r.copy(patch, name)
1984 1986 r.remove([patch], False)
1985 1987 finally:
1986 1988 del wlock
1987 1989
1988 1990 q.save_dirty()
1989 1991
1990 1992 def restore(ui, repo, rev, **opts):
1991 1993 """restore the queue state saved by a rev"""
1992 1994 rev = repo.lookup(rev)
1993 1995 q = repo.mq
1994 1996 q.restore(repo, rev, delete=opts['delete'],
1995 1997 qupdate=opts['update'])
1996 1998 q.save_dirty()
1997 1999 return 0
1998 2000
1999 2001 def save(ui, repo, **opts):
2000 2002 """save current queue state"""
2001 2003 q = repo.mq
2002 2004 message = cmdutil.logmessage(opts)
2003 2005 ret = q.save(repo, msg=message)
2004 2006 if ret:
2005 2007 return ret
2006 2008 q.save_dirty()
2007 2009 if opts['copy']:
2008 2010 path = q.path
2009 2011 if opts['name']:
2010 2012 newpath = os.path.join(q.basepath, opts['name'])
2011 2013 if os.path.exists(newpath):
2012 2014 if not os.path.isdir(newpath):
2013 2015 raise util.Abort(_('destination %s exists and is not '
2014 2016 'a directory') % newpath)
2015 2017 if not opts['force']:
2016 2018 raise util.Abort(_('destination %s exists, '
2017 2019 'use -f to force') % newpath)
2018 2020 else:
2019 2021 newpath = savename(path)
2020 2022 ui.warn("copy %s to %s\n" % (path, newpath))
2021 2023 util.copyfiles(path, newpath)
2022 2024 if opts['empty']:
2023 2025 try:
2024 2026 os.unlink(q.join(q.status_path))
2025 2027 except:
2026 2028 pass
2027 2029 return 0
2028 2030
2029 2031 def strip(ui, repo, rev, **opts):
2030 2032 """strip a revision and all later revs on the same branch"""
2031 2033 rev = repo.lookup(rev)
2032 2034 backup = 'all'
2033 2035 if opts['backup']:
2034 2036 backup = 'strip'
2035 2037 elif opts['nobackup']:
2036 2038 backup = 'none'
2037 2039 update = repo.dirstate.parents()[0] != revlog.nullid
2038 2040 repo.mq.strip(repo, rev, backup=backup, update=update)
2039 2041 return 0
2040 2042
2041 2043 def select(ui, repo, *args, **opts):
2042 2044 '''set or print guarded patches to push
2043 2045
2044 2046 Use the qguard command to set or print guards on patch, then use
2045 2047 qselect to tell mq which guards to use. A patch will be pushed if it
2046 2048 has no guards or any positive guards match the currently selected guard,
2047 2049 but will not be pushed if any negative guards match the current guard.
2048 2050 For example:
2049 2051
2050 2052 qguard foo.patch -stable (negative guard)
2051 2053 qguard bar.patch +stable (positive guard)
2052 2054 qselect stable
2053 2055
2054 2056 This activates the "stable" guard. mq will skip foo.patch (because
2055 2057 it has a negative match) but push bar.patch (because it
2056 2058 has a positive match).
2057 2059
2058 2060 With no arguments, prints the currently active guards.
2059 2061 With one argument, sets the active guard.
2060 2062
2061 2063 Use -n/--none to deactivate guards (no other arguments needed).
2062 2064 When no guards are active, patches with positive guards are skipped
2063 2065 and patches with negative guards are pushed.
2064 2066
2065 2067 qselect can change the guards on applied patches. It does not pop
2066 2068 guarded patches by default. Use --pop to pop back to the last applied
2067 2069 patch that is not guarded. Use --reapply (which implies --pop) to push
2068 2070 back to the current patch afterwards, but skip guarded patches.
2069 2071
2070 2072 Use -s/--series to print a list of all guards in the series file (no
2071 2073 other arguments needed). Use -v for more information.'''
2072 2074
2073 2075 q = repo.mq
2074 2076 guards = q.active()
2075 2077 if args or opts['none']:
2076 2078 old_unapplied = q.unapplied(repo)
2077 2079 old_guarded = [i for i in xrange(len(q.applied)) if
2078 2080 not q.pushable(i)[0]]
2079 2081 q.set_active(args)
2080 2082 q.save_dirty()
2081 2083 if not args:
2082 2084 ui.status(_('guards deactivated\n'))
2083 2085 if not opts['pop'] and not opts['reapply']:
2084 2086 unapplied = q.unapplied(repo)
2085 2087 guarded = [i for i in xrange(len(q.applied))
2086 2088 if not q.pushable(i)[0]]
2087 2089 if len(unapplied) != len(old_unapplied):
2088 2090 ui.status(_('number of unguarded, unapplied patches has '
2089 2091 'changed from %d to %d\n') %
2090 2092 (len(old_unapplied), len(unapplied)))
2091 2093 if len(guarded) != len(old_guarded):
2092 2094 ui.status(_('number of guarded, applied patches has changed '
2093 2095 'from %d to %d\n') %
2094 2096 (len(old_guarded), len(guarded)))
2095 2097 elif opts['series']:
2096 2098 guards = {}
2097 2099 noguards = 0
2098 2100 for gs in q.series_guards:
2099 2101 if not gs:
2100 2102 noguards += 1
2101 2103 for g in gs:
2102 2104 guards.setdefault(g, 0)
2103 2105 guards[g] += 1
2104 2106 if ui.verbose:
2105 2107 guards['NONE'] = noguards
2106 2108 guards = guards.items()
2107 2109 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2108 2110 if guards:
2109 2111 ui.note(_('guards in series file:\n'))
2110 2112 for guard, count in guards:
2111 2113 ui.note('%2d ' % count)
2112 2114 ui.write(guard, '\n')
2113 2115 else:
2114 2116 ui.note(_('no guards in series file\n'))
2115 2117 else:
2116 2118 if guards:
2117 2119 ui.note(_('active guards:\n'))
2118 2120 for g in guards:
2119 2121 ui.write(g, '\n')
2120 2122 else:
2121 2123 ui.write(_('no active guards\n'))
2122 2124 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2123 2125 popped = False
2124 2126 if opts['pop'] or opts['reapply']:
2125 2127 for i in xrange(len(q.applied)):
2126 2128 pushable, reason = q.pushable(i)
2127 2129 if not pushable:
2128 2130 ui.status(_('popping guarded patches\n'))
2129 2131 popped = True
2130 2132 if i == 0:
2131 2133 q.pop(repo, all=True)
2132 2134 else:
2133 2135 q.pop(repo, i-1)
2134 2136 break
2135 2137 if popped:
2136 2138 try:
2137 2139 if reapply:
2138 2140 ui.status(_('reapplying unguarded patches\n'))
2139 2141 q.push(repo, reapply)
2140 2142 finally:
2141 2143 q.save_dirty()
2142 2144
2143 2145 def reposetup(ui, repo):
2144 2146 class mqrepo(repo.__class__):
2145 2147 def abort_if_wdir_patched(self, errmsg, force=False):
2146 2148 if self.mq.applied and not force:
2147 2149 parent = revlog.hex(self.dirstate.parents()[0])
2148 2150 if parent in [s.rev for s in self.mq.applied]:
2149 2151 raise util.Abort(errmsg)
2150 2152
2151 2153 def commit(self, *args, **opts):
2152 2154 if len(args) >= 6:
2153 2155 force = args[5]
2154 2156 else:
2155 2157 force = opts.get('force')
2156 2158 self.abort_if_wdir_patched(
2157 2159 _('cannot commit over an applied mq patch'),
2158 2160 force)
2159 2161
2160 2162 return super(mqrepo, self).commit(*args, **opts)
2161 2163
2162 2164 def push(self, remote, force=False, revs=None):
2163 2165 if self.mq.applied and not force and not revs:
2164 2166 raise util.Abort(_('source has mq patches applied'))
2165 2167 return super(mqrepo, self).push(remote, force, revs)
2166 2168
2167 2169 def tags(self):
2168 2170 if self.tagscache:
2169 2171 return self.tagscache
2170 2172
2171 2173 tagscache = super(mqrepo, self).tags()
2172 2174
2173 2175 q = self.mq
2174 2176 if not q.applied:
2175 2177 return tagscache
2176 2178
2177 2179 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2178 2180
2179 2181 if mqtags[-1][0] not in self.changelog.nodemap:
2180 2182 self.ui.warn('mq status file refers to unknown node %s\n'
2181 2183 % revlog.short(mqtags[-1][0]))
2182 2184 return tagscache
2183 2185
2184 2186 mqtags.append((mqtags[-1][0], 'qtip'))
2185 2187 mqtags.append((mqtags[0][0], 'qbase'))
2186 2188 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2187 2189 for patch in mqtags:
2188 2190 if patch[1] in tagscache:
2189 2191 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2190 2192 else:
2191 2193 tagscache[patch[1]] = patch[0]
2192 2194
2193 2195 return tagscache
2194 2196
2195 2197 def _branchtags(self, partial, lrev):
2196 2198 q = self.mq
2197 2199 if not q.applied:
2198 2200 return super(mqrepo, self)._branchtags(partial, lrev)
2199 2201
2200 2202 cl = self.changelog
2201 2203 qbasenode = revlog.bin(q.applied[0].rev)
2202 2204 if qbasenode not in cl.nodemap:
2203 2205 self.ui.warn('mq status file refers to unknown node %s\n'
2204 2206 % revlog.short(qbasenode))
2205 2207 return super(mqrepo, self)._branchtags(partial, lrev)
2206 2208
2207 2209 qbase = cl.rev(qbasenode)
2208 2210 start = lrev + 1
2209 2211 if start < qbase:
2210 2212 # update the cache (excluding the patches) and save it
2211 2213 self._updatebranchcache(partial, lrev+1, qbase)
2212 2214 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2213 2215 start = qbase
2214 2216 # if start = qbase, the cache is as updated as it should be.
2215 2217 # if start > qbase, the cache includes (part of) the patches.
2216 2218 # we might as well use it, but we won't save it.
2217 2219
2218 2220 # update the cache up to the tip
2219 2221 self._updatebranchcache(partial, start, cl.count())
2220 2222
2221 2223 return partial
2222 2224
2223 2225 if repo.local():
2224 2226 repo.__class__ = mqrepo
2225 2227 repo.mq = queue(ui, repo.join(""))
2226 2228
2227 2229 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2228 2230
2229 2231 headeropts = [
2230 2232 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2231 2233 ('u', 'user', '', _('add "From: <given user>" to patch')),
2232 2234 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2233 2235 ('d', 'date', '', _('add "Date: <given date>" to patch'))]
2234 2236
2235 2237 cmdtable = {
2236 2238 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2237 2239 "qclone":
2238 2240 (clone,
2239 2241 [('', 'pull', None, _('use pull protocol to copy metadata')),
2240 2242 ('U', 'noupdate', None, _('do not update the new working directories')),
2241 2243 ('', 'uncompressed', None,
2242 2244 _('use uncompressed transfer (fast over LAN)')),
2243 2245 ('p', 'patches', '', _('location of source patch repo')),
2244 2246 ] + commands.remoteopts,
2245 2247 _('hg qclone [OPTION]... SOURCE [DEST]')),
2246 2248 "qcommit|qci":
2247 2249 (commit,
2248 2250 commands.table["^commit|ci"][1],
2249 2251 _('hg qcommit [OPTION]... [FILE]...')),
2250 2252 "^qdiff":
2251 2253 (diff,
2252 2254 [('g', 'git', None, _('use git extended diff format')),
2253 2255 ('U', 'unified', 3, _('number of lines of context to show')),
2254 2256 ] + commands.walkopts,
2255 2257 _('hg qdiff [-I] [-X] [-U NUM] [-g] [FILE]...')),
2256 2258 "qdelete|qremove|qrm":
2257 2259 (delete,
2258 2260 [('k', 'keep', None, _('keep patch file')),
2259 2261 ('r', 'rev', [], _('stop managing a revision'))],
2260 2262 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2261 2263 'qfold':
2262 2264 (fold,
2263 2265 [('e', 'edit', None, _('edit patch header')),
2264 2266 ('k', 'keep', None, _('keep folded patch files')),
2265 2267 ] + commands.commitopts,
2266 2268 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2267 2269 'qgoto':
2268 2270 (goto,
2269 2271 [('f', 'force', None, _('overwrite any local changes'))],
2270 2272 _('hg qgoto [OPTION]... PATCH')),
2271 2273 'qguard':
2272 2274 (guard,
2273 2275 [('l', 'list', None, _('list all patches and guards')),
2274 2276 ('n', 'none', None, _('drop all guards'))],
2275 2277 _('hg qguard [-l] [-n] [PATCH] [+GUARD]... [-GUARD]...')),
2276 2278 'qheader': (header, [], _('hg qheader [PATCH]')),
2277 2279 "^qimport":
2278 2280 (qimport,
2279 2281 [('e', 'existing', None, 'import file in patch dir'),
2280 2282 ('n', 'name', '', 'patch file name'),
2281 2283 ('f', 'force', None, 'overwrite existing files'),
2282 2284 ('r', 'rev', [], 'place existing revisions under mq control'),
2283 2285 ('g', 'git', None, _('use git extended diff format'))],
2284 2286 _('hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...')),
2285 2287 "^qinit":
2286 2288 (init,
2287 2289 [('c', 'create-repo', None, 'create queue repository')],
2288 2290 _('hg qinit [-c]')),
2289 2291 "qnew":
2290 2292 (new,
2291 2293 [('e', 'edit', None, _('edit commit message')),
2292 2294 ('f', 'force', None, _('import uncommitted changes into patch')),
2293 2295 ('g', 'git', None, _('use git extended diff format')),
2294 2296 ] + commands.walkopts + commands.commitopts + headeropts,
2295 2297 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2296 2298 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2297 2299 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2298 2300 "^qpop":
2299 2301 (pop,
2300 2302 [('a', 'all', None, _('pop all patches')),
2301 2303 ('n', 'name', '', _('queue name to pop')),
2302 2304 ('f', 'force', None, _('forget any local changes'))],
2303 2305 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2304 2306 "^qpush":
2305 2307 (push,
2306 2308 [('f', 'force', None, _('apply if the patch has rejects')),
2307 2309 ('l', 'list', None, _('list patch name in commit text')),
2308 2310 ('a', 'all', None, _('apply all patches')),
2309 2311 ('m', 'merge', None, _('merge from another queue')),
2310 2312 ('n', 'name', '', _('merge queue name'))],
2311 2313 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2312 2314 "^qrefresh":
2313 2315 (refresh,
2314 2316 [('e', 'edit', None, _('edit commit message')),
2315 2317 ('g', 'git', None, _('use git extended diff format')),
2316 2318 ('s', 'short', None, _('refresh only files already in the patch')),
2317 2319 ] + commands.walkopts + commands.commitopts + headeropts,
2318 2320 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2319 2321 'qrename|qmv':
2320 2322 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2321 2323 "qrestore":
2322 2324 (restore,
2323 2325 [('d', 'delete', None, _('delete save entry')),
2324 2326 ('u', 'update', None, _('update queue working dir'))],
2325 2327 _('hg qrestore [-d] [-u] REV')),
2326 2328 "qsave":
2327 2329 (save,
2328 2330 [('c', 'copy', None, _('copy patch directory')),
2329 2331 ('n', 'name', '', _('copy directory name')),
2330 2332 ('e', 'empty', None, _('clear queue status file')),
2331 2333 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2332 2334 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2333 2335 "qselect":
2334 2336 (select,
2335 2337 [('n', 'none', None, _('disable all guards')),
2336 2338 ('s', 'series', None, _('list all guards in series file')),
2337 2339 ('', 'pop', None, _('pop to before first guarded applied patch')),
2338 2340 ('', 'reapply', None, _('pop, then reapply patches'))],
2339 2341 _('hg qselect [OPTION]... [GUARD]...')),
2340 2342 "qseries":
2341 2343 (series,
2342 2344 [('m', 'missing', None, _('print patches not in series')),
2343 2345 ] + seriesopts,
2344 2346 _('hg qseries [-ms]')),
2345 2347 "^strip":
2346 2348 (strip,
2347 2349 [('b', 'backup', None, _('bundle unrelated changesets')),
2348 2350 ('n', 'nobackup', None, _('no backups'))],
2349 2351 _('hg strip [-f] [-b] [-n] REV')),
2350 2352 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2351 2353 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2352 2354 }
@@ -1,3179 +1,3180 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import hex, nullid, nullrev, short
9 from repo import RepoError
9 10 from i18n import _
10 11 import os, re, sys, urllib
11 12 import hg, util, revlog, bundlerepo, extensions
12 13 import difflib, patch, time, help, mdiff, tempfile
13 14 import version, socket
14 15 import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
15 16
16 17 # Commands start here, listed alphabetically
17 18
18 19 def add(ui, repo, *pats, **opts):
19 20 """add the specified files on the next commit
20 21
21 22 Schedule files to be version controlled and added to the repository.
22 23
23 24 The files will be added to the repository at the next commit. To
24 25 undo an add before that, see hg revert.
25 26
26 27 If no names are given, add all files in the repository.
27 28 """
28 29
29 30 rejected = None
30 31 exacts = {}
31 32 names = []
32 33 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
33 34 badmatch=util.always):
34 35 if exact:
35 36 if ui.verbose:
36 37 ui.status(_('adding %s\n') % rel)
37 38 names.append(abs)
38 39 exacts[abs] = 1
39 40 elif abs not in repo.dirstate:
40 41 ui.status(_('adding %s\n') % rel)
41 42 names.append(abs)
42 43 if not opts.get('dry_run'):
43 44 rejected = repo.add(names)
44 45 rejected = [p for p in rejected if p in exacts]
45 46 return rejected and 1 or 0
46 47
47 48 def addremove(ui, repo, *pats, **opts):
48 49 """add all new files, delete all missing files
49 50
50 51 Add all new files and remove all missing files from the repository.
51 52
52 53 New files are ignored if they match any of the patterns in .hgignore. As
53 54 with add, these changes take effect at the next commit.
54 55
55 56 Use the -s option to detect renamed files. With a parameter > 0,
56 57 this compares every removed file with every added file and records
57 58 those similar enough as renames. This option takes a percentage
58 59 between 0 (disabled) and 100 (files must be identical) as its
59 60 parameter. Detecting renamed files this way can be expensive.
60 61 """
61 62 try:
62 63 sim = float(opts.get('similarity') or 0)
63 64 except ValueError:
64 65 raise util.Abort(_('similarity must be a number'))
65 66 if sim < 0 or sim > 100:
66 67 raise util.Abort(_('similarity must be between 0 and 100'))
67 68 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
68 69
69 70 def annotate(ui, repo, *pats, **opts):
70 71 """show changeset information per file line
71 72
72 73 List changes in files, showing the revision id responsible for each line
73 74
74 75 This command is useful to discover who did a change or when a change took
75 76 place.
76 77
77 78 Without the -a option, annotate will avoid processing files it
78 79 detects as binary. With -a, annotate will generate an annotation
79 80 anyway, probably with undesirable results.
80 81 """
81 82 datefunc = ui.quiet and util.shortdate or util.datestr
82 83 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
83 84
84 85 if not pats:
85 86 raise util.Abort(_('at least one file name or pattern required'))
86 87
87 88 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
88 89 ('number', lambda x: str(x[0].rev())),
89 90 ('changeset', lambda x: short(x[0].node())),
90 91 ('date', getdate),
91 92 ('follow', lambda x: x[0].path()),
92 93 ]
93 94
94 95 if (not opts['user'] and not opts['changeset'] and not opts['date']
95 96 and not opts['follow']):
96 97 opts['number'] = 1
97 98
98 99 linenumber = opts.get('line_number') is not None
99 100 if (linenumber and (not opts['changeset']) and (not opts['number'])):
100 101 raise util.Abort(_('at least one of -n/-c is required for -l'))
101 102
102 103 funcmap = [func for op, func in opmap if opts.get(op)]
103 104 if linenumber:
104 105 lastfunc = funcmap[-1]
105 106 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
106 107
107 108 ctx = repo.changectx(opts['rev'])
108 109
109 110 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
110 111 node=ctx.node()):
111 112 fctx = ctx.filectx(abs)
112 113 if not opts['text'] and util.binary(fctx.data()):
113 114 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
114 115 continue
115 116
116 117 lines = fctx.annotate(follow=opts.get('follow'),
117 118 linenumber=linenumber)
118 119 pieces = []
119 120
120 121 for f in funcmap:
121 122 l = [f(n) for n, dummy in lines]
122 123 if l:
123 124 m = max(map(len, l))
124 125 pieces.append(["%*s" % (m, x) for x in l])
125 126
126 127 if pieces:
127 128 for p, l in zip(zip(*pieces), lines):
128 129 ui.write("%s: %s" % (" ".join(p), l[1]))
129 130
130 131 def archive(ui, repo, dest, **opts):
131 132 '''create unversioned archive of a repository revision
132 133
133 134 By default, the revision used is the parent of the working
134 135 directory; use "-r" to specify a different revision.
135 136
136 137 To specify the type of archive to create, use "-t". Valid
137 138 types are:
138 139
139 140 "files" (default): a directory full of files
140 141 "tar": tar archive, uncompressed
141 142 "tbz2": tar archive, compressed using bzip2
142 143 "tgz": tar archive, compressed using gzip
143 144 "uzip": zip archive, uncompressed
144 145 "zip": zip archive, compressed using deflate
145 146
146 147 The exact name of the destination archive or directory is given
147 148 using a format string; see "hg help export" for details.
148 149
149 150 Each member added to an archive file has a directory prefix
150 151 prepended. Use "-p" to specify a format string for the prefix.
151 152 The default is the basename of the archive, with suffixes removed.
152 153 '''
153 154
154 155 ctx = repo.changectx(opts['rev'])
155 156 if not ctx:
156 157 raise util.Abort(_('repository has no revisions'))
157 158 node = ctx.node()
158 159 dest = cmdutil.make_filename(repo, dest, node)
159 160 if os.path.realpath(dest) == repo.root:
160 161 raise util.Abort(_('repository root cannot be destination'))
161 162 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
162 163 kind = opts.get('type') or 'files'
163 164 prefix = opts['prefix']
164 165 if dest == '-':
165 166 if kind == 'files':
166 167 raise util.Abort(_('cannot archive plain files to stdout'))
167 168 dest = sys.stdout
168 169 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
169 170 prefix = cmdutil.make_filename(repo, prefix, node)
170 171 archival.archive(repo, dest, node, kind, not opts['no_decode'],
171 172 matchfn, prefix)
172 173
173 174 def backout(ui, repo, node=None, rev=None, **opts):
174 175 '''reverse effect of earlier changeset
175 176
176 177 Commit the backed out changes as a new changeset. The new
177 178 changeset is a child of the backed out changeset.
178 179
179 180 If you back out a changeset other than the tip, a new head is
180 181 created. This head will be the new tip and you should merge this
181 182 backout changeset with another head (current one by default).
182 183
183 184 The --merge option remembers the parent of the working directory
184 185 before starting the backout, then merges the new head with that
185 186 changeset afterwards. This saves you from doing the merge by
186 187 hand. The result of this merge is not committed, as for a normal
187 188 merge.
188 189
189 190 See 'hg help dates' for a list of formats valid for -d/--date.
190 191 '''
191 192 if rev and node:
192 193 raise util.Abort(_("please specify just one revision"))
193 194
194 195 if not rev:
195 196 rev = node
196 197
197 198 if not rev:
198 199 raise util.Abort(_("please specify a revision to backout"))
199 200
200 201 date = opts.get('date')
201 202 if date:
202 203 opts['date'] = util.parsedate(date)
203 204
204 205 cmdutil.bail_if_changed(repo)
205 206 node = repo.lookup(rev)
206 207
207 208 op1, op2 = repo.dirstate.parents()
208 209 a = repo.changelog.ancestor(op1, node)
209 210 if a != node:
210 211 raise util.Abort(_('cannot back out change on a different branch'))
211 212
212 213 p1, p2 = repo.changelog.parents(node)
213 214 if p1 == nullid:
214 215 raise util.Abort(_('cannot back out a change with no parents'))
215 216 if p2 != nullid:
216 217 if not opts['parent']:
217 218 raise util.Abort(_('cannot back out a merge changeset without '
218 219 '--parent'))
219 220 p = repo.lookup(opts['parent'])
220 221 if p not in (p1, p2):
221 222 raise util.Abort(_('%s is not a parent of %s') %
222 223 (short(p), short(node)))
223 224 parent = p
224 225 else:
225 226 if opts['parent']:
226 227 raise util.Abort(_('cannot use --parent on non-merge changeset'))
227 228 parent = p1
228 229
229 230 hg.clean(repo, node, show_stats=False)
230 231 revert_opts = opts.copy()
231 232 revert_opts['date'] = None
232 233 revert_opts['all'] = True
233 234 revert_opts['rev'] = hex(parent)
234 235 revert_opts['no_backup'] = None
235 236 revert(ui, repo, **revert_opts)
236 237 commit_opts = opts.copy()
237 238 commit_opts['addremove'] = False
238 239 if not commit_opts['message'] and not commit_opts['logfile']:
239 240 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
240 241 commit_opts['force_editor'] = True
241 242 commit(ui, repo, **commit_opts)
242 243 def nice(node):
243 244 return '%d:%s' % (repo.changelog.rev(node), short(node))
244 245 ui.status(_('changeset %s backs out changeset %s\n') %
245 246 (nice(repo.changelog.tip()), nice(node)))
246 247 if op1 != node:
247 248 hg.clean(repo, op1, show_stats=False)
248 249 if opts['merge']:
249 250 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
250 251 hg.merge(repo, hex(repo.changelog.tip()))
251 252 else:
252 253 ui.status(_('the backout changeset is a new head - '
253 254 'do not forget to merge\n'))
254 255 ui.status(_('(use "backout --merge" '
255 256 'if you want to auto-merge)\n'))
256 257
257 258 def bisect(ui, repo, rev=None, extra=None,
258 259 reset=None, good=None, bad=None, skip=None, noupdate=None):
259 260 """subdivision search of changesets
260 261
261 262 This command helps to find changesets which introduce problems.
262 263 To use, mark the earliest changeset you know exhibits the problem
263 264 as bad, then mark the latest changeset which is free from the
264 265 problem as good. Bisect will update your working directory to a
265 266 revision for testing. Once you have performed tests, mark the
266 267 working directory as bad or good and bisect will either update to
267 268 another candidate changeset or announce that it has found the bad
268 269 revision.
269 270 """
270 271 # backward compatibility
271 272 if rev in "good bad reset init".split():
272 273 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
273 274 cmd, rev, extra = rev, extra, None
274 275 if cmd == "good":
275 276 good = True
276 277 elif cmd == "bad":
277 278 bad = True
278 279 else:
279 280 reset = True
280 281 elif extra or good + bad + skip + reset > 1:
281 282 raise util.Abort("Incompatible arguments")
282 283
283 284 if reset:
284 285 p = repo.join("bisect.state")
285 286 if os.path.exists(p):
286 287 os.unlink(p)
287 288 return
288 289
289 290 # load state
290 291 state = {'good': [], 'bad': [], 'skip': []}
291 292 if os.path.exists(repo.join("bisect.state")):
292 293 for l in repo.opener("bisect.state"):
293 294 kind, node = l[:-1].split()
294 295 node = repo.lookup(node)
295 296 if kind not in state:
296 297 raise util.Abort(_("unknown bisect kind %s") % kind)
297 298 state[kind].append(node)
298 299
299 300 # update state
300 301 node = repo.lookup(rev or '.')
301 302 if good:
302 303 state['good'].append(node)
303 304 elif bad:
304 305 state['bad'].append(node)
305 306 elif skip:
306 307 state['skip'].append(node)
307 308
308 309 # save state
309 310 f = repo.opener("bisect.state", "w", atomictemp=True)
310 311 wlock = repo.wlock()
311 312 try:
312 313 for kind in state:
313 314 for node in state[kind]:
314 f.write("%s %s\n" % (kind, hg.hex(node)))
315 f.write("%s %s\n" % (kind, hex(node)))
315 316 f.rename()
316 317 finally:
317 318 del wlock
318 319
319 320 if not state['good'] or not state['bad']:
320 321 return
321 322
322 323 # actually bisect
323 324 node, changesets, good = hbisect.bisect(repo.changelog, state)
324 325 if changesets == 0:
325 326 ui.write(_("The first %s revision is:\n") % (good and "good" or "bad"))
326 327 displayer = cmdutil.show_changeset(ui, repo, {})
327 328 displayer.show(changenode=node)
328 329 elif node is not None:
329 330 # compute the approximate number of remaining tests
330 331 tests, size = 0, 2
331 332 while size <= changesets:
332 333 tests, size = tests + 1, size * 2
333 334 rev = repo.changelog.rev(node)
334 335 ui.write(_("Testing changeset %s:%s "
335 336 "(%s changesets remaining, ~%s tests)\n")
336 % (rev, hg.short(node), changesets, tests))
337 % (rev, short(node), changesets, tests))
337 338 if not noupdate:
338 339 cmdutil.bail_if_changed(repo)
339 340 return hg.clean(repo, node)
340 341
341 342 def branch(ui, repo, label=None, **opts):
342 343 """set or show the current branch name
343 344
344 345 With no argument, show the current branch name. With one argument,
345 346 set the working directory branch name (the branch does not exist in
346 347 the repository until the next commit).
347 348
348 349 Unless --force is specified, branch will not let you set a
349 350 branch name that shadows an existing branch.
350 351
351 352 Use the command 'hg update' to switch to an existing branch.
352 353 """
353 354
354 355 if label:
355 356 if not opts.get('force') and label in repo.branchtags():
356 357 if label not in [p.branch() for p in repo.workingctx().parents()]:
357 358 raise util.Abort(_('a branch of the same name already exists'
358 359 ' (use --force to override)'))
359 360 repo.dirstate.setbranch(util.fromlocal(label))
360 361 ui.status(_('marked working directory as branch %s\n') % label)
361 362 else:
362 363 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
363 364
364 365 def branches(ui, repo, active=False):
365 366 """list repository named branches
366 367
367 368 List the repository's named branches, indicating which ones are
368 369 inactive. If active is specified, only show active branches.
369 370
370 371 A branch is considered active if it contains unmerged heads.
371 372
372 373 Use the command 'hg update' to switch to an existing branch.
373 374 """
374 375 b = repo.branchtags()
375 376 heads = dict.fromkeys(repo.heads(), 1)
376 377 l = [((n in heads), repo.changelog.rev(n), n, t) for t, n in b.items()]
377 378 l.sort()
378 379 l.reverse()
379 380 for ishead, r, n, t in l:
380 381 if active and not ishead:
381 382 # If we're only displaying active branches, abort the loop on
382 383 # encountering the first inactive head
383 384 break
384 385 else:
385 386 hexfunc = ui.debugflag and hex or short
386 387 if ui.quiet:
387 388 ui.write("%s\n" % t)
388 389 else:
389 390 spaces = " " * (30 - util.locallen(t))
390 391 # The code only gets here if inactive branches are being
391 392 # displayed or the branch is active.
392 393 isinactive = ((not ishead) and " (inactive)") or ''
393 394 ui.write("%s%s %s:%s%s\n" % (t, spaces, r, hexfunc(n), isinactive))
394 395
395 396 def bundle(ui, repo, fname, dest=None, **opts):
396 397 """create a changegroup file
397 398
398 399 Generate a compressed changegroup file collecting changesets not
399 400 found in the other repository.
400 401
401 402 If no destination repository is specified the destination is
402 403 assumed to have all the nodes specified by one or more --base
403 404 parameters. To create a bundle containing all changesets, use
404 405 --all (or --base null).
405 406
406 407 The bundle file can then be transferred using conventional means and
407 408 applied to another repository with the unbundle or pull command.
408 409 This is useful when direct push and pull are not available or when
409 410 exporting an entire repository is undesirable.
410 411
411 412 Applying bundles preserves all changeset contents including
412 413 permissions, copy/rename information, and revision history.
413 414 """
414 415 revs = opts.get('rev') or None
415 416 if revs:
416 417 revs = [repo.lookup(rev) for rev in revs]
417 418 if opts.get('all'):
418 419 base = ['null']
419 420 else:
420 421 base = opts.get('base')
421 422 if base:
422 423 if dest:
423 424 raise util.Abort(_("--base is incompatible with specifiying "
424 425 "a destination"))
425 426 base = [repo.lookup(rev) for rev in base]
426 427 # create the right base
427 428 # XXX: nodesbetween / changegroup* should be "fixed" instead
428 429 o = []
429 430 has = {nullid: None}
430 431 for n in base:
431 432 has.update(repo.changelog.reachable(n))
432 433 if revs:
433 434 visit = list(revs)
434 435 else:
435 436 visit = repo.changelog.heads()
436 437 seen = {}
437 438 while visit:
438 439 n = visit.pop(0)
439 440 parents = [p for p in repo.changelog.parents(n) if p not in has]
440 441 if len(parents) == 0:
441 442 o.insert(0, n)
442 443 else:
443 444 for p in parents:
444 445 if p not in seen:
445 446 seen[p] = 1
446 447 visit.append(p)
447 448 else:
448 449 cmdutil.setremoteconfig(ui, opts)
449 450 dest, revs, checkout = hg.parseurl(
450 451 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
451 452 other = hg.repository(ui, dest)
452 453 o = repo.findoutgoing(other, force=opts['force'])
453 454
454 455 if revs:
455 456 cg = repo.changegroupsubset(o, revs, 'bundle')
456 457 else:
457 458 cg = repo.changegroup(o, 'bundle')
458 459 changegroup.writebundle(cg, fname, "HG10BZ")
459 460
460 461 def cat(ui, repo, file1, *pats, **opts):
461 462 """output the current or given revision of files
462 463
463 464 Print the specified files as they were at the given revision.
464 465 If no revision is given, the parent of the working directory is used,
465 466 or tip if no revision is checked out.
466 467
467 468 Output may be to a file, in which case the name of the file is
468 469 given using a format string. The formatting rules are the same as
469 470 for the export command, with the following additions:
470 471
471 472 %s basename of file being printed
472 473 %d dirname of file being printed, or '.' if in repo root
473 474 %p root-relative path name of file being printed
474 475 """
475 476 ctx = repo.changectx(opts['rev'])
476 477 err = 1
477 478 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
478 479 ctx.node()):
479 480 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
480 481 data = ctx.filectx(abs).data()
481 482 if opts.get('decode'):
482 483 data = repo.wwritedata(abs, data)
483 484 fp.write(data)
484 485 err = 0
485 486 return err
486 487
487 488 def clone(ui, source, dest=None, **opts):
488 489 """make a copy of an existing repository
489 490
490 491 Create a copy of an existing repository in a new directory.
491 492
492 493 If no destination directory name is specified, it defaults to the
493 494 basename of the source.
494 495
495 496 The location of the source is added to the new repository's
496 497 .hg/hgrc file, as the default to be used for future pulls.
497 498
498 499 For efficiency, hardlinks are used for cloning whenever the source
499 500 and destination are on the same filesystem (note this applies only
500 501 to the repository data, not to the checked out files). Some
501 502 filesystems, such as AFS, implement hardlinking incorrectly, but
502 503 do not report errors. In these cases, use the --pull option to
503 504 avoid hardlinking.
504 505
505 506 You can safely clone repositories and checked out files using full
506 507 hardlinks with
507 508
508 509 $ cp -al REPO REPOCLONE
509 510
510 511 which is the fastest way to clone. However, the operation is not
511 512 atomic (making sure REPO is not modified during the operation is
512 513 up to you) and you have to make sure your editor breaks hardlinks
513 514 (Emacs and most Linux Kernel tools do so).
514 515
515 516 If you use the -r option to clone up to a specific revision, no
516 517 subsequent revisions will be present in the cloned repository.
517 518 This option implies --pull, even on local repositories.
518 519
519 520 See pull for valid source format details.
520 521
521 522 It is possible to specify an ssh:// URL as the destination, but no
522 523 .hg/hgrc and working directory will be created on the remote side.
523 524 Look at the help text for the pull command for important details
524 525 about ssh:// URLs.
525 526 """
526 527 cmdutil.setremoteconfig(ui, opts)
527 528 hg.clone(ui, source, dest,
528 529 pull=opts['pull'],
529 530 stream=opts['uncompressed'],
530 531 rev=opts['rev'],
531 532 update=not opts['noupdate'])
532 533
533 534 def commit(ui, repo, *pats, **opts):
534 535 """commit the specified files or all outstanding changes
535 536
536 537 Commit changes to the given files into the repository.
537 538
538 539 If a list of files is omitted, all changes reported by "hg status"
539 540 will be committed.
540 541
541 542 If no commit message is specified, the configured editor is started to
542 543 enter a message.
543 544
544 545 See 'hg help dates' for a list of formats valid for -d/--date.
545 546 """
546 547 def commitfunc(ui, repo, files, message, match, opts):
547 548 return repo.commit(files, message, opts['user'], opts['date'], match,
548 549 force_editor=opts.get('force_editor'))
549 550 cmdutil.commit(ui, repo, commitfunc, pats, opts)
550 551
551 552 def copy(ui, repo, *pats, **opts):
552 553 """mark files as copied for the next commit
553 554
554 555 Mark dest as having copies of source files. If dest is a
555 556 directory, copies are put in that directory. If dest is a file,
556 557 there can only be one source.
557 558
558 559 By default, this command copies the contents of files as they
559 560 stand in the working directory. If invoked with --after, the
560 561 operation is recorded, but no copying is performed.
561 562
562 563 This command takes effect in the next commit. To undo a copy
563 564 before that, see hg revert.
564 565 """
565 566 wlock = repo.wlock(False)
566 567 try:
567 568 return cmdutil.copy(ui, repo, pats, opts)
568 569 finally:
569 570 del wlock
570 571
571 572 def debugancestor(ui, repo, *args):
572 573 """find the ancestor revision of two revisions in a given index"""
573 574 if len(args) == 3:
574 575 index, rev1, rev2 = args
575 576 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
576 577 elif len(args) == 2:
577 578 if not repo:
578 579 raise util.Abort(_("There is no Mercurial repository here "
579 580 "(.hg not found)"))
580 581 rev1, rev2 = args
581 582 r = repo.changelog
582 583 else:
583 584 raise util.Abort(_('either two or three arguments required'))
584 585 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
585 586 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
586 587
587 588 def debugcomplete(ui, cmd='', **opts):
588 589 """returns the completion list associated with the given command"""
589 590
590 591 if opts['options']:
591 592 options = []
592 593 otables = [globalopts]
593 594 if cmd:
594 595 aliases, entry = cmdutil.findcmd(ui, cmd, table)
595 596 otables.append(entry[1])
596 597 for t in otables:
597 598 for o in t:
598 599 if o[0]:
599 600 options.append('-%s' % o[0])
600 601 options.append('--%s' % o[1])
601 602 ui.write("%s\n" % "\n".join(options))
602 603 return
603 604
604 605 clist = cmdutil.findpossible(ui, cmd, table).keys()
605 606 clist.sort()
606 607 ui.write("%s\n" % "\n".join(clist))
607 608
608 609 def debugfsinfo(ui, path = "."):
609 610 file('.debugfsinfo', 'w').write('')
610 611 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
611 612 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
612 613 ui.write('case-sensitive: %s\n' % (util.checkfolding('.debugfsinfo')
613 614 and 'yes' or 'no'))
614 615 os.unlink('.debugfsinfo')
615 616
616 617 def debugrebuildstate(ui, repo, rev=""):
617 618 """rebuild the dirstate as it would look like for the given revision"""
618 619 if rev == "":
619 620 rev = repo.changelog.tip()
620 621 ctx = repo.changectx(rev)
621 622 files = ctx.manifest()
622 623 wlock = repo.wlock()
623 624 try:
624 625 repo.dirstate.rebuild(rev, files)
625 626 finally:
626 627 del wlock
627 628
628 629 def debugcheckstate(ui, repo):
629 630 """validate the correctness of the current dirstate"""
630 631 parent1, parent2 = repo.dirstate.parents()
631 632 m1 = repo.changectx(parent1).manifest()
632 633 m2 = repo.changectx(parent2).manifest()
633 634 errors = 0
634 635 for f in repo.dirstate:
635 636 state = repo.dirstate[f]
636 637 if state in "nr" and f not in m1:
637 638 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
638 639 errors += 1
639 640 if state in "a" and f in m1:
640 641 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
641 642 errors += 1
642 643 if state in "m" and f not in m1 and f not in m2:
643 644 ui.warn(_("%s in state %s, but not in either manifest\n") %
644 645 (f, state))
645 646 errors += 1
646 647 for f in m1:
647 648 state = repo.dirstate[f]
648 649 if state not in "nrm":
649 650 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
650 651 errors += 1
651 652 if errors:
652 653 error = _(".hg/dirstate inconsistent with current parent's manifest")
653 654 raise util.Abort(error)
654 655
655 656 def showconfig(ui, repo, *values, **opts):
656 657 """show combined config settings from all hgrc files
657 658
658 659 With no args, print names and values of all config items.
659 660
660 661 With one arg of the form section.name, print just the value of
661 662 that config item.
662 663
663 664 With multiple args, print names and values of all config items
664 665 with matching section names."""
665 666
666 667 untrusted = bool(opts.get('untrusted'))
667 668 if values:
668 669 if len([v for v in values if '.' in v]) > 1:
669 670 raise util.Abort(_('only one config item permitted'))
670 671 for section, name, value in ui.walkconfig(untrusted=untrusted):
671 672 sectname = section + '.' + name
672 673 if values:
673 674 for v in values:
674 675 if v == section:
675 676 ui.write('%s=%s\n' % (sectname, value))
676 677 elif v == sectname:
677 678 ui.write(value, '\n')
678 679 else:
679 680 ui.write('%s=%s\n' % (sectname, value))
680 681
681 682 def debugsetparents(ui, repo, rev1, rev2=None):
682 683 """manually set the parents of the current working directory
683 684
684 685 This is useful for writing repository conversion tools, but should
685 686 be used with care.
686 687 """
687 688
688 689 if not rev2:
689 690 rev2 = hex(nullid)
690 691
691 692 wlock = repo.wlock()
692 693 try:
693 694 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
694 695 finally:
695 696 del wlock
696 697
697 698 def debugstate(ui, repo):
698 699 """show the contents of the current dirstate"""
699 700 k = repo.dirstate._map.items()
700 701 k.sort()
701 702 for file_, ent in k:
702 703 if ent[3] == -1:
703 704 # Pad or slice to locale representation
704 705 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(0)))
705 706 timestr = 'unset'
706 707 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
707 708 else:
708 709 timestr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ent[3]))
709 710 if ent[1] & 020000:
710 711 mode = 'lnk'
711 712 else:
712 713 mode = '%3o' % (ent[1] & 0777)
713 714 ui.write("%c %s %10d %s %s\n" % (ent[0], mode, ent[2], timestr, file_))
714 715 for f in repo.dirstate.copies():
715 716 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
716 717
717 718 def debugdata(ui, file_, rev):
718 719 """dump the contents of a data file revision"""
719 720 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
720 721 try:
721 722 ui.write(r.revision(r.lookup(rev)))
722 723 except KeyError:
723 724 raise util.Abort(_('invalid revision identifier %s') % rev)
724 725
725 726 def debugdate(ui, date, range=None, **opts):
726 727 """parse and display a date"""
727 728 if opts["extended"]:
728 729 d = util.parsedate(date, util.extendeddateformats)
729 730 else:
730 731 d = util.parsedate(date)
731 732 ui.write("internal: %s %s\n" % d)
732 733 ui.write("standard: %s\n" % util.datestr(d))
733 734 if range:
734 735 m = util.matchdate(range)
735 736 ui.write("match: %s\n" % m(d[0]))
736 737
737 738 def debugindex(ui, file_):
738 739 """dump the contents of an index file"""
739 740 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
740 741 ui.write(" rev offset length base linkrev" +
741 742 " nodeid p1 p2\n")
742 743 for i in xrange(r.count()):
743 744 node = r.node(i)
744 745 try:
745 746 pp = r.parents(node)
746 747 except:
747 748 pp = [nullid, nullid]
748 749 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
749 750 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
750 751 short(node), short(pp[0]), short(pp[1])))
751 752
752 753 def debugindexdot(ui, file_):
753 754 """dump an index DAG as a .dot file"""
754 755 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
755 756 ui.write("digraph G {\n")
756 757 for i in xrange(r.count()):
757 758 node = r.node(i)
758 759 pp = r.parents(node)
759 760 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
760 761 if pp[1] != nullid:
761 762 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
762 763 ui.write("}\n")
763 764
764 765 def debuginstall(ui):
765 766 '''test Mercurial installation'''
766 767
767 768 def writetemp(contents):
768 769 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
769 770 f = os.fdopen(fd, "wb")
770 771 f.write(contents)
771 772 f.close()
772 773 return name
773 774
774 775 problems = 0
775 776
776 777 # encoding
777 778 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
778 779 try:
779 780 util.fromlocal("test")
780 781 except util.Abort, inst:
781 782 ui.write(" %s\n" % inst)
782 783 ui.write(_(" (check that your locale is properly set)\n"))
783 784 problems += 1
784 785
785 786 # compiled modules
786 787 ui.status(_("Checking extensions...\n"))
787 788 try:
788 789 import bdiff, mpatch, base85
789 790 except Exception, inst:
790 791 ui.write(" %s\n" % inst)
791 792 ui.write(_(" One or more extensions could not be found"))
792 793 ui.write(_(" (check that you compiled the extensions)\n"))
793 794 problems += 1
794 795
795 796 # templates
796 797 ui.status(_("Checking templates...\n"))
797 798 try:
798 799 import templater
799 800 t = templater.templater(templater.templatepath("map-cmdline.default"))
800 801 except Exception, inst:
801 802 ui.write(" %s\n" % inst)
802 803 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
803 804 problems += 1
804 805
805 806 # patch
806 807 ui.status(_("Checking patch...\n"))
807 808 patchproblems = 0
808 809 a = "1\n2\n3\n4\n"
809 810 b = "1\n2\n3\ninsert\n4\n"
810 811 fa = writetemp(a)
811 812 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
812 813 os.path.basename(fa))
813 814 fd = writetemp(d)
814 815
815 816 files = {}
816 817 try:
817 818 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
818 819 except util.Abort, e:
819 820 ui.write(_(" patch call failed:\n"))
820 821 ui.write(" " + str(e) + "\n")
821 822 patchproblems += 1
822 823 else:
823 824 if list(files) != [os.path.basename(fa)]:
824 825 ui.write(_(" unexpected patch output!\n"))
825 826 patchproblems += 1
826 827 a = file(fa).read()
827 828 if a != b:
828 829 ui.write(_(" patch test failed!\n"))
829 830 patchproblems += 1
830 831
831 832 if patchproblems:
832 833 if ui.config('ui', 'patch'):
833 834 ui.write(_(" (Current patch tool may be incompatible with patch,"
834 835 " or misconfigured. Please check your .hgrc file)\n"))
835 836 else:
836 837 ui.write(_(" Internal patcher failure, please report this error"
837 838 " to http://www.selenic.com/mercurial/bts\n"))
838 839 problems += patchproblems
839 840
840 841 os.unlink(fa)
841 842 os.unlink(fd)
842 843
843 844 # editor
844 845 ui.status(_("Checking commit editor...\n"))
845 846 editor = ui.geteditor()
846 847 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
847 848 if not cmdpath:
848 849 if editor == 'vi':
849 850 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
850 851 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
851 852 else:
852 853 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
853 854 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
854 855 problems += 1
855 856
856 857 # check username
857 858 ui.status(_("Checking username...\n"))
858 859 user = os.environ.get("HGUSER")
859 860 if user is None:
860 861 user = ui.config("ui", "username")
861 862 if user is None:
862 863 user = os.environ.get("EMAIL")
863 864 if not user:
864 865 ui.warn(" ")
865 866 ui.username()
866 867 ui.write(_(" (specify a username in your .hgrc file)\n"))
867 868
868 869 if not problems:
869 870 ui.status(_("No problems detected\n"))
870 871 else:
871 872 ui.write(_("%s problems detected,"
872 873 " please check your install!\n") % problems)
873 874
874 875 return problems
875 876
876 877 def debugrename(ui, repo, file1, *pats, **opts):
877 878 """dump rename information"""
878 879
879 880 ctx = repo.changectx(opts.get('rev', 'tip'))
880 881 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
881 882 ctx.node()):
882 883 fctx = ctx.filectx(abs)
883 884 m = fctx.filelog().renamed(fctx.filenode())
884 885 if m:
885 886 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
886 887 else:
887 888 ui.write(_("%s not renamed\n") % rel)
888 889
889 890 def debugwalk(ui, repo, *pats, **opts):
890 891 """show how files match on given patterns"""
891 892 items = list(cmdutil.walk(repo, pats, opts))
892 893 if not items:
893 894 return
894 895 fmt = '%%s %%-%ds %%-%ds %%s' % (
895 896 max([len(abs) for (src, abs, rel, exact) in items]),
896 897 max([len(rel) for (src, abs, rel, exact) in items]))
897 898 for src, abs, rel, exact in items:
898 899 line = fmt % (src, abs, rel, exact and 'exact' or '')
899 900 ui.write("%s\n" % line.rstrip())
900 901
901 902 def diff(ui, repo, *pats, **opts):
902 903 """diff repository (or selected files)
903 904
904 905 Show differences between revisions for the specified files.
905 906
906 907 Differences between files are shown using the unified diff format.
907 908
908 909 NOTE: diff may generate unexpected results for merges, as it will
909 910 default to comparing against the working directory's first parent
910 911 changeset if no revisions are specified.
911 912
912 913 When two revision arguments are given, then changes are shown
913 914 between those revisions. If only one revision is specified then
914 915 that revision is compared to the working directory, and, when no
915 916 revisions are specified, the working directory files are compared
916 917 to its parent.
917 918
918 919 Without the -a option, diff will avoid generating diffs of files
919 920 it detects as binary. With -a, diff will generate a diff anyway,
920 921 probably with undesirable results.
921 922 """
922 923 node1, node2 = cmdutil.revpair(repo, opts['rev'])
923 924
924 925 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
925 926
926 927 patch.diff(repo, node1, node2, fns, match=matchfn,
927 928 opts=patch.diffopts(ui, opts))
928 929
929 930 def export(ui, repo, *changesets, **opts):
930 931 """dump the header and diffs for one or more changesets
931 932
932 933 Print the changeset header and diffs for one or more revisions.
933 934
934 935 The information shown in the changeset header is: author,
935 936 changeset hash, parent(s) and commit comment.
936 937
937 938 NOTE: export may generate unexpected diff output for merge changesets,
938 939 as it will compare the merge changeset against its first parent only.
939 940
940 941 Output may be to a file, in which case the name of the file is
941 942 given using a format string. The formatting rules are as follows:
942 943
943 944 %% literal "%" character
944 945 %H changeset hash (40 bytes of hexadecimal)
945 946 %N number of patches being generated
946 947 %R changeset revision number
947 948 %b basename of the exporting repository
948 949 %h short-form changeset hash (12 bytes of hexadecimal)
949 950 %n zero-padded sequence number, starting at 1
950 951 %r zero-padded changeset revision number
951 952
952 953 Without the -a option, export will avoid generating diffs of files
953 954 it detects as binary. With -a, export will generate a diff anyway,
954 955 probably with undesirable results.
955 956
956 957 With the --switch-parent option, the diff will be against the second
957 958 parent. It can be useful to review a merge.
958 959 """
959 960 if not changesets:
960 961 raise util.Abort(_("export requires at least one changeset"))
961 962 revs = cmdutil.revrange(repo, changesets)
962 963 if len(revs) > 1:
963 964 ui.note(_('exporting patches:\n'))
964 965 else:
965 966 ui.note(_('exporting patch:\n'))
966 967 patch.export(repo, revs, template=opts['output'],
967 968 switch_parent=opts['switch_parent'],
968 969 opts=patch.diffopts(ui, opts))
969 970
970 971 def grep(ui, repo, pattern, *pats, **opts):
971 972 """search for a pattern in specified files and revisions
972 973
973 974 Search revisions of files for a regular expression.
974 975
975 976 This command behaves differently than Unix grep. It only accepts
976 977 Python/Perl regexps. It searches repository history, not the
977 978 working directory. It always prints the revision number in which
978 979 a match appears.
979 980
980 981 By default, grep only prints output for the first revision of a
981 982 file in which it finds a match. To get it to print every revision
982 983 that contains a change in match status ("-" for a match that
983 984 becomes a non-match, or "+" for a non-match that becomes a match),
984 985 use the --all flag.
985 986 """
986 987 reflags = 0
987 988 if opts['ignore_case']:
988 989 reflags |= re.I
989 990 try:
990 991 regexp = re.compile(pattern, reflags)
991 992 except Exception, inst:
992 993 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
993 994 return None
994 995 sep, eol = ':', '\n'
995 996 if opts['print0']:
996 997 sep = eol = '\0'
997 998
998 999 fcache = {}
999 1000 def getfile(fn):
1000 1001 if fn not in fcache:
1001 1002 fcache[fn] = repo.file(fn)
1002 1003 return fcache[fn]
1003 1004
1004 1005 def matchlines(body):
1005 1006 begin = 0
1006 1007 linenum = 0
1007 1008 while True:
1008 1009 match = regexp.search(body, begin)
1009 1010 if not match:
1010 1011 break
1011 1012 mstart, mend = match.span()
1012 1013 linenum += body.count('\n', begin, mstart) + 1
1013 1014 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1014 1015 lend = body.find('\n', mend)
1015 1016 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1016 1017 begin = lend + 1
1017 1018
1018 1019 class linestate(object):
1019 1020 def __init__(self, line, linenum, colstart, colend):
1020 1021 self.line = line
1021 1022 self.linenum = linenum
1022 1023 self.colstart = colstart
1023 1024 self.colend = colend
1024 1025
1025 1026 def __eq__(self, other):
1026 1027 return self.line == other.line
1027 1028
1028 1029 matches = {}
1029 1030 copies = {}
1030 1031 def grepbody(fn, rev, body):
1031 1032 matches[rev].setdefault(fn, [])
1032 1033 m = matches[rev][fn]
1033 1034 for lnum, cstart, cend, line in matchlines(body):
1034 1035 s = linestate(line, lnum, cstart, cend)
1035 1036 m.append(s)
1036 1037
1037 1038 def difflinestates(a, b):
1038 1039 sm = difflib.SequenceMatcher(None, a, b)
1039 1040 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1040 1041 if tag == 'insert':
1041 1042 for i in xrange(blo, bhi):
1042 1043 yield ('+', b[i])
1043 1044 elif tag == 'delete':
1044 1045 for i in xrange(alo, ahi):
1045 1046 yield ('-', a[i])
1046 1047 elif tag == 'replace':
1047 1048 for i in xrange(alo, ahi):
1048 1049 yield ('-', a[i])
1049 1050 for i in xrange(blo, bhi):
1050 1051 yield ('+', b[i])
1051 1052
1052 1053 prev = {}
1053 1054 def display(fn, rev, states, prevstates):
1054 1055 datefunc = ui.quiet and util.shortdate or util.datestr
1055 1056 found = False
1056 1057 filerevmatches = {}
1057 1058 r = prev.get(fn, -1)
1058 1059 if opts['all']:
1059 1060 iter = difflinestates(states, prevstates)
1060 1061 else:
1061 1062 iter = [('', l) for l in prevstates]
1062 1063 for change, l in iter:
1063 1064 cols = [fn, str(r)]
1064 1065 if opts['line_number']:
1065 1066 cols.append(str(l.linenum))
1066 1067 if opts['all']:
1067 1068 cols.append(change)
1068 1069 if opts['user']:
1069 1070 cols.append(ui.shortuser(get(r)[1]))
1070 1071 if opts.get('date'):
1071 1072 cols.append(datefunc(get(r)[2]))
1072 1073 if opts['files_with_matches']:
1073 1074 c = (fn, r)
1074 1075 if c in filerevmatches:
1075 1076 continue
1076 1077 filerevmatches[c] = 1
1077 1078 else:
1078 1079 cols.append(l.line)
1079 1080 ui.write(sep.join(cols), eol)
1080 1081 found = True
1081 1082 return found
1082 1083
1083 1084 fstate = {}
1084 1085 skip = {}
1085 1086 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1086 1087 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1087 1088 found = False
1088 1089 follow = opts.get('follow')
1089 1090 for st, rev, fns in changeiter:
1090 1091 if st == 'window':
1091 1092 matches.clear()
1092 1093 elif st == 'add':
1093 1094 ctx = repo.changectx(rev)
1094 1095 matches[rev] = {}
1095 1096 for fn in fns:
1096 1097 if fn in skip:
1097 1098 continue
1098 1099 try:
1099 1100 grepbody(fn, rev, getfile(fn).read(ctx.filenode(fn)))
1100 1101 fstate.setdefault(fn, [])
1101 1102 if follow:
1102 1103 copied = getfile(fn).renamed(ctx.filenode(fn))
1103 1104 if copied:
1104 1105 copies.setdefault(rev, {})[fn] = copied[0]
1105 1106 except revlog.LookupError:
1106 1107 pass
1107 1108 elif st == 'iter':
1108 1109 states = matches[rev].items()
1109 1110 states.sort()
1110 1111 for fn, m in states:
1111 1112 copy = copies.get(rev, {}).get(fn)
1112 1113 if fn in skip:
1113 1114 if copy:
1114 1115 skip[copy] = True
1115 1116 continue
1116 1117 if fn in prev or fstate[fn]:
1117 1118 r = display(fn, rev, m, fstate[fn])
1118 1119 found = found or r
1119 1120 if r and not opts['all']:
1120 1121 skip[fn] = True
1121 1122 if copy:
1122 1123 skip[copy] = True
1123 1124 fstate[fn] = m
1124 1125 if copy:
1125 1126 fstate[copy] = m
1126 1127 prev[fn] = rev
1127 1128
1128 1129 fstate = fstate.items()
1129 1130 fstate.sort()
1130 1131 for fn, state in fstate:
1131 1132 if fn in skip:
1132 1133 continue
1133 1134 if fn not in copies.get(prev[fn], {}):
1134 1135 found = display(fn, rev, {}, state) or found
1135 1136 return (not found and 1) or 0
1136 1137
1137 1138 def heads(ui, repo, *branchrevs, **opts):
1138 1139 """show current repository heads or show branch heads
1139 1140
1140 1141 With no arguments, show all repository head changesets.
1141 1142
1142 1143 If branch or revisions names are given this will show the heads of
1143 1144 the specified branches or the branches those revisions are tagged
1144 1145 with.
1145 1146
1146 1147 Repository "heads" are changesets that don't have child
1147 1148 changesets. They are where development generally takes place and
1148 1149 are the usual targets for update and merge operations.
1149 1150
1150 1151 Branch heads are changesets that have a given branch tag, but have
1151 1152 no child changesets with that tag. They are usually where
1152 1153 development on the given branch takes place.
1153 1154 """
1154 1155 if opts['rev']:
1155 1156 start = repo.lookup(opts['rev'])
1156 1157 else:
1157 1158 start = None
1158 1159 if not branchrevs:
1159 1160 # Assume we're looking repo-wide heads if no revs were specified.
1160 1161 heads = repo.heads(start)
1161 1162 else:
1162 1163 heads = []
1163 1164 visitedset = util.set()
1164 1165 for branchrev in branchrevs:
1165 1166 branch = repo.changectx(branchrev).branch()
1166 1167 if branch in visitedset:
1167 1168 continue
1168 1169 visitedset.add(branch)
1169 1170 bheads = repo.branchheads(branch, start)
1170 1171 if not bheads:
1171 1172 if branch != branchrev:
1172 1173 ui.warn(_("no changes on branch %s containing %s are "
1173 1174 "reachable from %s\n")
1174 1175 % (branch, branchrev, opts['rev']))
1175 1176 else:
1176 1177 ui.warn(_("no changes on branch %s are reachable from %s\n")
1177 1178 % (branch, opts['rev']))
1178 1179 heads.extend(bheads)
1179 1180 if not heads:
1180 1181 return 1
1181 1182 displayer = cmdutil.show_changeset(ui, repo, opts)
1182 1183 for n in heads:
1183 1184 displayer.show(changenode=n)
1184 1185
1185 1186 def help_(ui, name=None, with_version=False):
1186 1187 """show help for a command, extension, or list of commands
1187 1188
1188 1189 With no arguments, print a list of commands and short help.
1189 1190
1190 1191 Given a command name, print help for that command.
1191 1192
1192 1193 Given an extension name, print help for that extension, and the
1193 1194 commands it provides."""
1194 1195 option_lists = []
1195 1196
1196 1197 def addglobalopts(aliases):
1197 1198 if ui.verbose:
1198 1199 option_lists.append((_("global options:"), globalopts))
1199 1200 if name == 'shortlist':
1200 1201 option_lists.append((_('use "hg help" for the full list '
1201 1202 'of commands'), ()))
1202 1203 else:
1203 1204 if name == 'shortlist':
1204 1205 msg = _('use "hg help" for the full list of commands '
1205 1206 'or "hg -v" for details')
1206 1207 elif aliases:
1207 1208 msg = _('use "hg -v help%s" to show aliases and '
1208 1209 'global options') % (name and " " + name or "")
1209 1210 else:
1210 1211 msg = _('use "hg -v help %s" to show global options') % name
1211 1212 option_lists.append((msg, ()))
1212 1213
1213 1214 def helpcmd(name):
1214 1215 if with_version:
1215 1216 version_(ui)
1216 1217 ui.write('\n')
1217 1218 aliases, i = cmdutil.findcmd(ui, name, table)
1218 1219 # synopsis
1219 1220 ui.write("%s\n" % i[2])
1220 1221
1221 1222 # aliases
1222 1223 if not ui.quiet and len(aliases) > 1:
1223 1224 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1224 1225
1225 1226 # description
1226 1227 doc = i[0].__doc__
1227 1228 if not doc:
1228 1229 doc = _("(No help text available)")
1229 1230 if ui.quiet:
1230 1231 doc = doc.splitlines(0)[0]
1231 1232 ui.write("\n%s\n" % doc.rstrip())
1232 1233
1233 1234 if not ui.quiet:
1234 1235 # options
1235 1236 if i[1]:
1236 1237 option_lists.append((_("options:\n"), i[1]))
1237 1238
1238 1239 addglobalopts(False)
1239 1240
1240 1241 def helplist(header, select=None):
1241 1242 h = {}
1242 1243 cmds = {}
1243 1244 for c, e in table.items():
1244 1245 f = c.split("|", 1)[0]
1245 1246 if select and not select(f):
1246 1247 continue
1247 1248 if name == "shortlist" and not f.startswith("^"):
1248 1249 continue
1249 1250 f = f.lstrip("^")
1250 1251 if not ui.debugflag and f.startswith("debug"):
1251 1252 continue
1252 1253 doc = e[0].__doc__
1253 1254 if not doc:
1254 1255 doc = _("(No help text available)")
1255 1256 h[f] = doc.splitlines(0)[0].rstrip()
1256 1257 cmds[f] = c.lstrip("^")
1257 1258
1258 1259 if not h:
1259 1260 ui.status(_('no commands defined\n'))
1260 1261 return
1261 1262
1262 1263 ui.status(header)
1263 1264 fns = h.keys()
1264 1265 fns.sort()
1265 1266 m = max(map(len, fns))
1266 1267 for f in fns:
1267 1268 if ui.verbose:
1268 1269 commands = cmds[f].replace("|",", ")
1269 1270 ui.write(" %s:\n %s\n"%(commands, h[f]))
1270 1271 else:
1271 1272 ui.write(' %-*s %s\n' % (m, f, h[f]))
1272 1273
1273 1274 if not ui.quiet:
1274 1275 addglobalopts(True)
1275 1276
1276 1277 def helptopic(name):
1277 1278 v = None
1278 1279 for i in help.helptable:
1279 1280 l = i.split('|')
1280 1281 if name in l:
1281 1282 v = i
1282 1283 header = l[-1]
1283 1284 if not v:
1284 1285 raise cmdutil.UnknownCommand(name)
1285 1286
1286 1287 # description
1287 1288 doc = help.helptable[v]
1288 1289 if not doc:
1289 1290 doc = _("(No help text available)")
1290 1291 if callable(doc):
1291 1292 doc = doc()
1292 1293
1293 1294 ui.write("%s\n" % header)
1294 1295 ui.write("%s\n" % doc.rstrip())
1295 1296
1296 1297 def helpext(name):
1297 1298 try:
1298 1299 mod = extensions.find(name)
1299 1300 except KeyError:
1300 1301 raise cmdutil.UnknownCommand(name)
1301 1302
1302 1303 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1303 1304 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1304 1305 for d in doc[1:]:
1305 1306 ui.write(d, '\n')
1306 1307
1307 1308 ui.status('\n')
1308 1309
1309 1310 try:
1310 1311 ct = mod.cmdtable
1311 1312 except AttributeError:
1312 1313 ct = {}
1313 1314
1314 1315 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1315 1316 helplist(_('list of commands:\n\n'), modcmds.has_key)
1316 1317
1317 1318 if name and name != 'shortlist':
1318 1319 i = None
1319 1320 for f in (helpcmd, helptopic, helpext):
1320 1321 try:
1321 1322 f(name)
1322 1323 i = None
1323 1324 break
1324 1325 except cmdutil.UnknownCommand, inst:
1325 1326 i = inst
1326 1327 if i:
1327 1328 raise i
1328 1329
1329 1330 else:
1330 1331 # program name
1331 1332 if ui.verbose or with_version:
1332 1333 version_(ui)
1333 1334 else:
1334 1335 ui.status(_("Mercurial Distributed SCM\n"))
1335 1336 ui.status('\n')
1336 1337
1337 1338 # list of commands
1338 1339 if name == "shortlist":
1339 1340 header = _('basic commands:\n\n')
1340 1341 else:
1341 1342 header = _('list of commands:\n\n')
1342 1343
1343 1344 helplist(header)
1344 1345
1345 1346 # list all option lists
1346 1347 opt_output = []
1347 1348 for title, options in option_lists:
1348 1349 opt_output.append(("\n%s" % title, None))
1349 1350 for shortopt, longopt, default, desc in options:
1350 1351 if "DEPRECATED" in desc and not ui.verbose: continue
1351 1352 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1352 1353 longopt and " --%s" % longopt),
1353 1354 "%s%s" % (desc,
1354 1355 default
1355 1356 and _(" (default: %s)") % default
1356 1357 or "")))
1357 1358
1358 1359 if opt_output:
1359 1360 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1360 1361 for first, second in opt_output:
1361 1362 if second:
1362 1363 ui.write(" %-*s %s\n" % (opts_len, first, second))
1363 1364 else:
1364 1365 ui.write("%s\n" % first)
1365 1366
1366 1367 def identify(ui, repo, source=None,
1367 1368 rev=None, num=None, id=None, branch=None, tags=None):
1368 1369 """identify the working copy or specified revision
1369 1370
1370 1371 With no revision, print a summary of the current state of the repo.
1371 1372
1372 1373 With a path, do a lookup in another repository.
1373 1374
1374 1375 This summary identifies the repository state using one or two parent
1375 1376 hash identifiers, followed by a "+" if there are uncommitted changes
1376 1377 in the working directory, a list of tags for this revision and a branch
1377 1378 name for non-default branches.
1378 1379 """
1379 1380
1380 1381 if not repo and not source:
1381 1382 raise util.Abort(_("There is no Mercurial repository here "
1382 1383 "(.hg not found)"))
1383 1384
1384 1385 hexfunc = ui.debugflag and hex or short
1385 1386 default = not (num or id or branch or tags)
1386 1387 output = []
1387 1388
1388 1389 if source:
1389 1390 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1390 1391 srepo = hg.repository(ui, source)
1391 1392 if not rev and revs:
1392 1393 rev = revs[0]
1393 1394 if not rev:
1394 1395 rev = "tip"
1395 1396 if num or branch or tags:
1396 1397 raise util.Abort(
1397 1398 "can't query remote revision number, branch, or tags")
1398 1399 output = [hexfunc(srepo.lookup(rev))]
1399 1400 elif not rev:
1400 1401 ctx = repo.workingctx()
1401 1402 parents = ctx.parents()
1402 1403 changed = False
1403 1404 if default or id or num:
1404 1405 changed = ctx.files() + ctx.deleted()
1405 1406 if default or id:
1406 1407 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1407 1408 (changed) and "+" or "")]
1408 1409 if num:
1409 1410 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1410 1411 (changed) and "+" or ""))
1411 1412 else:
1412 1413 ctx = repo.changectx(rev)
1413 1414 if default or id:
1414 1415 output = [hexfunc(ctx.node())]
1415 1416 if num:
1416 1417 output.append(str(ctx.rev()))
1417 1418
1418 1419 if not source and default and not ui.quiet:
1419 1420 b = util.tolocal(ctx.branch())
1420 1421 if b != 'default':
1421 1422 output.append("(%s)" % b)
1422 1423
1423 1424 # multiple tags for a single parent separated by '/'
1424 1425 t = "/".join(ctx.tags())
1425 1426 if t:
1426 1427 output.append(t)
1427 1428
1428 1429 if branch:
1429 1430 output.append(util.tolocal(ctx.branch()))
1430 1431
1431 1432 if tags:
1432 1433 output.extend(ctx.tags())
1433 1434
1434 1435 ui.write("%s\n" % ' '.join(output))
1435 1436
1436 1437 def import_(ui, repo, patch1, *patches, **opts):
1437 1438 """import an ordered set of patches
1438 1439
1439 1440 Import a list of patches and commit them individually.
1440 1441
1441 1442 If there are outstanding changes in the working directory, import
1442 1443 will abort unless given the -f flag.
1443 1444
1444 1445 You can import a patch straight from a mail message. Even patches
1445 1446 as attachments work (body part must be type text/plain or
1446 1447 text/x-patch to be used). From and Subject headers of email
1447 1448 message are used as default committer and commit message. All
1448 1449 text/plain body parts before first diff are added to commit
1449 1450 message.
1450 1451
1451 1452 If the imported patch was generated by hg export, user and description
1452 1453 from patch override values from message headers and body. Values
1453 1454 given on command line with -m and -u override these.
1454 1455
1455 1456 If --exact is specified, import will set the working directory
1456 1457 to the parent of each patch before applying it, and will abort
1457 1458 if the resulting changeset has a different ID than the one
1458 1459 recorded in the patch. This may happen due to character set
1459 1460 problems or other deficiencies in the text patch format.
1460 1461
1461 1462 To read a patch from standard input, use patch name "-".
1462 1463 See 'hg help dates' for a list of formats valid for -d/--date.
1463 1464 """
1464 1465 patches = (patch1,) + patches
1465 1466
1466 1467 date = opts.get('date')
1467 1468 if date:
1468 1469 opts['date'] = util.parsedate(date)
1469 1470
1470 1471 if opts.get('exact') or not opts['force']:
1471 1472 cmdutil.bail_if_changed(repo)
1472 1473
1473 1474 d = opts["base"]
1474 1475 strip = opts["strip"]
1475 1476 wlock = lock = None
1476 1477 try:
1477 1478 wlock = repo.wlock()
1478 1479 lock = repo.lock()
1479 1480 for p in patches:
1480 1481 pf = os.path.join(d, p)
1481 1482
1482 1483 if pf == '-':
1483 1484 ui.status(_("applying patch from stdin\n"))
1484 1485 data = patch.extract(ui, sys.stdin)
1485 1486 else:
1486 1487 ui.status(_("applying %s\n") % p)
1487 1488 if os.path.exists(pf):
1488 1489 data = patch.extract(ui, file(pf, 'rb'))
1489 1490 else:
1490 1491 data = patch.extract(ui, urllib.urlopen(pf))
1491 1492 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1492 1493
1493 1494 if tmpname is None:
1494 1495 raise util.Abort(_('no diffs found'))
1495 1496
1496 1497 try:
1497 1498 cmdline_message = cmdutil.logmessage(opts)
1498 1499 if cmdline_message:
1499 1500 # pickup the cmdline msg
1500 1501 message = cmdline_message
1501 1502 elif message:
1502 1503 # pickup the patch msg
1503 1504 message = message.strip()
1504 1505 else:
1505 1506 # launch the editor
1506 1507 message = None
1507 1508 ui.debug(_('message:\n%s\n') % message)
1508 1509
1509 1510 wp = repo.workingctx().parents()
1510 1511 if opts.get('exact'):
1511 1512 if not nodeid or not p1:
1512 1513 raise util.Abort(_('not a mercurial patch'))
1513 1514 p1 = repo.lookup(p1)
1514 1515 p2 = repo.lookup(p2 or hex(nullid))
1515 1516
1516 1517 if p1 != wp[0].node():
1517 1518 hg.clean(repo, p1)
1518 1519 repo.dirstate.setparents(p1, p2)
1519 1520 elif p2:
1520 1521 try:
1521 1522 p1 = repo.lookup(p1)
1522 1523 p2 = repo.lookup(p2)
1523 1524 if p1 == wp[0].node():
1524 1525 repo.dirstate.setparents(p1, p2)
1525 except hg.RepoError:
1526 except RepoError:
1526 1527 pass
1527 1528 if opts.get('exact') or opts.get('import_branch'):
1528 1529 repo.dirstate.setbranch(branch or 'default')
1529 1530
1530 1531 files = {}
1531 1532 try:
1532 1533 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1533 1534 files=files)
1534 1535 finally:
1535 1536 files = patch.updatedir(ui, repo, files)
1536 1537 if not opts.get('no_commit'):
1537 1538 n = repo.commit(files, message, opts.get('user') or user,
1538 1539 opts.get('date') or date)
1539 1540 if opts.get('exact'):
1540 1541 if hex(n) != nodeid:
1541 1542 repo.rollback()
1542 1543 raise util.Abort(_('patch is damaged'
1543 1544 ' or loses information'))
1544 1545 # Force a dirstate write so that the next transaction
1545 1546 # backups an up-do-date file.
1546 1547 repo.dirstate.write()
1547 1548 finally:
1548 1549 os.unlink(tmpname)
1549 1550 finally:
1550 1551 del lock, wlock
1551 1552
1552 1553 def incoming(ui, repo, source="default", **opts):
1553 1554 """show new changesets found in source
1554 1555
1555 1556 Show new changesets found in the specified path/URL or the default
1556 1557 pull location. These are the changesets that would be pulled if a pull
1557 1558 was requested.
1558 1559
1559 1560 For remote repository, using --bundle avoids downloading the changesets
1560 1561 twice if the incoming is followed by a pull.
1561 1562
1562 1563 See pull for valid source format details.
1563 1564 """
1564 1565 limit = cmdutil.loglimit(opts)
1565 1566 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1566 1567 cmdutil.setremoteconfig(ui, opts)
1567 1568
1568 1569 other = hg.repository(ui, source)
1569 1570 ui.status(_('comparing with %s\n') % util.hidepassword(source))
1570 1571 if revs:
1571 1572 revs = [other.lookup(rev) for rev in revs]
1572 1573 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1573 1574 if not incoming:
1574 1575 try:
1575 1576 os.unlink(opts["bundle"])
1576 1577 except:
1577 1578 pass
1578 1579 ui.status(_("no changes found\n"))
1579 1580 return 1
1580 1581
1581 1582 cleanup = None
1582 1583 try:
1583 1584 fname = opts["bundle"]
1584 1585 if fname or not other.local():
1585 1586 # create a bundle (uncompressed if other repo is not local)
1586 1587 if revs is None:
1587 1588 cg = other.changegroup(incoming, "incoming")
1588 1589 else:
1589 1590 cg = other.changegroupsubset(incoming, revs, 'incoming')
1590 1591 bundletype = other.local() and "HG10BZ" or "HG10UN"
1591 1592 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1592 1593 # keep written bundle?
1593 1594 if opts["bundle"]:
1594 1595 cleanup = None
1595 1596 if not other.local():
1596 1597 # use the created uncompressed bundlerepo
1597 1598 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1598 1599
1599 1600 o = other.changelog.nodesbetween(incoming, revs)[0]
1600 1601 if opts['newest_first']:
1601 1602 o.reverse()
1602 1603 displayer = cmdutil.show_changeset(ui, other, opts)
1603 1604 count = 0
1604 1605 for n in o:
1605 1606 if count >= limit:
1606 1607 break
1607 1608 parents = [p for p in other.changelog.parents(n) if p != nullid]
1608 1609 if opts['no_merges'] and len(parents) == 2:
1609 1610 continue
1610 1611 count += 1
1611 1612 displayer.show(changenode=n)
1612 1613 finally:
1613 1614 if hasattr(other, 'close'):
1614 1615 other.close()
1615 1616 if cleanup:
1616 1617 os.unlink(cleanup)
1617 1618
1618 1619 def init(ui, dest=".", **opts):
1619 1620 """create a new repository in the given directory
1620 1621
1621 1622 Initialize a new repository in the given directory. If the given
1622 1623 directory does not exist, it is created.
1623 1624
1624 1625 If no directory is given, the current directory is used.
1625 1626
1626 1627 It is possible to specify an ssh:// URL as the destination.
1627 1628 Look at the help text for the pull command for important details
1628 1629 about ssh:// URLs.
1629 1630 """
1630 1631 cmdutil.setremoteconfig(ui, opts)
1631 1632 hg.repository(ui, dest, create=1)
1632 1633
1633 1634 def locate(ui, repo, *pats, **opts):
1634 1635 """locate files matching specific patterns
1635 1636
1636 1637 Print all files under Mercurial control whose names match the
1637 1638 given patterns.
1638 1639
1639 1640 This command searches the entire repository by default. To search
1640 1641 just the current directory and its subdirectories, use
1641 1642 "--include .".
1642 1643
1643 1644 If no patterns are given to match, this command prints all file
1644 1645 names.
1645 1646
1646 1647 If you want to feed the output of this command into the "xargs"
1647 1648 command, use the "-0" option to both this command and "xargs".
1648 1649 This will avoid the problem of "xargs" treating single filenames
1649 1650 that contain white space as multiple filenames.
1650 1651 """
1651 1652 end = opts['print0'] and '\0' or '\n'
1652 1653 rev = opts['rev']
1653 1654 if rev:
1654 1655 node = repo.lookup(rev)
1655 1656 else:
1656 1657 node = None
1657 1658
1658 1659 ret = 1
1659 1660 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1660 1661 badmatch=util.always,
1661 1662 default='relglob'):
1662 1663 if src == 'b':
1663 1664 continue
1664 1665 if not node and abs not in repo.dirstate:
1665 1666 continue
1666 1667 if opts['fullpath']:
1667 1668 ui.write(os.path.join(repo.root, abs), end)
1668 1669 else:
1669 1670 ui.write(((pats and rel) or abs), end)
1670 1671 ret = 0
1671 1672
1672 1673 return ret
1673 1674
1674 1675 def log(ui, repo, *pats, **opts):
1675 1676 """show revision history of entire repository or files
1676 1677
1677 1678 Print the revision history of the specified files or the entire
1678 1679 project.
1679 1680
1680 1681 File history is shown without following rename or copy history of
1681 1682 files. Use -f/--follow with a file name to follow history across
1682 1683 renames and copies. --follow without a file name will only show
1683 1684 ancestors or descendants of the starting revision. --follow-first
1684 1685 only follows the first parent of merge revisions.
1685 1686
1686 1687 If no revision range is specified, the default is tip:0 unless
1687 1688 --follow is set, in which case the working directory parent is
1688 1689 used as the starting revision.
1689 1690
1690 1691 See 'hg help dates' for a list of formats valid for -d/--date.
1691 1692
1692 1693 By default this command outputs: changeset id and hash, tags,
1693 1694 non-trivial parents, user, date and time, and a summary for each
1694 1695 commit. When the -v/--verbose switch is used, the list of changed
1695 1696 files and full commit message is shown.
1696 1697
1697 1698 NOTE: log -p may generate unexpected diff output for merge
1698 1699 changesets, as it will compare the merge changeset against its
1699 1700 first parent only. Also, the files: list will only reflect files
1700 1701 that are different from BOTH parents.
1701 1702
1702 1703 """
1703 1704
1704 1705 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1705 1706 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1706 1707
1707 1708 limit = cmdutil.loglimit(opts)
1708 1709 count = 0
1709 1710
1710 1711 if opts['copies'] and opts['rev']:
1711 1712 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1712 1713 else:
1713 1714 endrev = repo.changelog.count()
1714 1715 rcache = {}
1715 1716 ncache = {}
1716 1717 def getrenamed(fn, rev):
1717 1718 '''looks up all renames for a file (up to endrev) the first
1718 1719 time the file is given. It indexes on the changerev and only
1719 1720 parses the manifest if linkrev != changerev.
1720 1721 Returns rename info for fn at changerev rev.'''
1721 1722 if fn not in rcache:
1722 1723 rcache[fn] = {}
1723 1724 ncache[fn] = {}
1724 1725 fl = repo.file(fn)
1725 1726 for i in xrange(fl.count()):
1726 1727 node = fl.node(i)
1727 1728 lr = fl.linkrev(node)
1728 1729 renamed = fl.renamed(node)
1729 1730 rcache[fn][lr] = renamed
1730 1731 if renamed:
1731 1732 ncache[fn][node] = renamed
1732 1733 if lr >= endrev:
1733 1734 break
1734 1735 if rev in rcache[fn]:
1735 1736 return rcache[fn][rev]
1736 1737
1737 1738 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1738 1739 # filectx logic.
1739 1740
1740 1741 try:
1741 1742 return repo.changectx(rev).filectx(fn).renamed()
1742 1743 except revlog.LookupError:
1743 1744 pass
1744 1745 return None
1745 1746
1746 1747 df = False
1747 1748 if opts["date"]:
1748 1749 df = util.matchdate(opts["date"])
1749 1750
1750 1751 only_branches = opts['only_branch']
1751 1752
1752 1753 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1753 1754 for st, rev, fns in changeiter:
1754 1755 if st == 'add':
1755 1756 changenode = repo.changelog.node(rev)
1756 1757 parents = [p for p in repo.changelog.parentrevs(rev)
1757 1758 if p != nullrev]
1758 1759 if opts['no_merges'] and len(parents) == 2:
1759 1760 continue
1760 1761 if opts['only_merges'] and len(parents) != 2:
1761 1762 continue
1762 1763
1763 1764 if only_branches:
1764 1765 revbranch = get(rev)[5]['branch']
1765 1766 if revbranch not in only_branches:
1766 1767 continue
1767 1768
1768 1769 if df:
1769 1770 changes = get(rev)
1770 1771 if not df(changes[2][0]):
1771 1772 continue
1772 1773
1773 1774 if opts['keyword']:
1774 1775 changes = get(rev)
1775 1776 miss = 0
1776 1777 for k in [kw.lower() for kw in opts['keyword']]:
1777 1778 if not (k in changes[1].lower() or
1778 1779 k in changes[4].lower() or
1779 1780 k in " ".join(changes[3]).lower()):
1780 1781 miss = 1
1781 1782 break
1782 1783 if miss:
1783 1784 continue
1784 1785
1785 1786 copies = []
1786 1787 if opts.get('copies') and rev:
1787 1788 for fn in get(rev)[3]:
1788 1789 rename = getrenamed(fn, rev)
1789 1790 if rename:
1790 1791 copies.append((fn, rename[0]))
1791 1792 displayer.show(rev, changenode, copies=copies)
1792 1793 elif st == 'iter':
1793 1794 if count == limit: break
1794 1795 if displayer.flush(rev):
1795 1796 count += 1
1796 1797
1797 1798 def manifest(ui, repo, node=None, rev=None):
1798 1799 """output the current or given revision of the project manifest
1799 1800
1800 1801 Print a list of version controlled files for the given revision.
1801 1802 If no revision is given, the parent of the working directory is used,
1802 1803 or tip if no revision is checked out.
1803 1804
1804 1805 The manifest is the list of files being version controlled. If no revision
1805 1806 is given then the first parent of the working directory is used.
1806 1807
1807 1808 With -v flag, print file permissions, symlink and executable bits. With
1808 1809 --debug flag, print file revision hashes.
1809 1810 """
1810 1811
1811 1812 if rev and node:
1812 1813 raise util.Abort(_("please specify just one revision"))
1813 1814
1814 1815 if not node:
1815 1816 node = rev
1816 1817
1817 1818 m = repo.changectx(node).manifest()
1818 1819 files = m.keys()
1819 1820 files.sort()
1820 1821
1821 1822 for f in files:
1822 1823 if ui.debugflag:
1823 1824 ui.write("%40s " % hex(m[f]))
1824 1825 if ui.verbose:
1825 1826 type = m.execf(f) and "*" or m.linkf(f) and "@" or " "
1826 1827 perm = m.execf(f) and "755" or "644"
1827 1828 ui.write("%3s %1s " % (perm, type))
1828 1829 ui.write("%s\n" % f)
1829 1830
1830 1831 def merge(ui, repo, node=None, force=None, rev=None):
1831 1832 """merge working directory with another revision
1832 1833
1833 1834 Merge the contents of the current working directory and the
1834 1835 requested revision. Files that changed between either parent are
1835 1836 marked as changed for the next commit and a commit must be
1836 1837 performed before any further updates are allowed.
1837 1838
1838 1839 If no revision is specified, the working directory's parent is a
1839 1840 head revision, and the repository contains exactly one other head,
1840 1841 the other head is merged with by default. Otherwise, an explicit
1841 1842 revision to merge with must be provided.
1842 1843 """
1843 1844
1844 1845 if rev and node:
1845 1846 raise util.Abort(_("please specify just one revision"))
1846 1847 if not node:
1847 1848 node = rev
1848 1849
1849 1850 if not node:
1850 1851 heads = repo.heads()
1851 1852 if len(heads) > 2:
1852 1853 raise util.Abort(_('repo has %d heads - '
1853 1854 'please merge with an explicit rev') %
1854 1855 len(heads))
1855 1856 parent = repo.dirstate.parents()[0]
1856 1857 if len(heads) == 1:
1857 1858 msg = _('there is nothing to merge')
1858 1859 if parent != repo.lookup(repo.workingctx().branch()):
1859 1860 msg = _('%s - use "hg update" instead') % msg
1860 1861 raise util.Abort(msg)
1861 1862
1862 1863 if parent not in heads:
1863 1864 raise util.Abort(_('working dir not at a head rev - '
1864 1865 'use "hg update" or merge with an explicit rev'))
1865 1866 node = parent == heads[0] and heads[-1] or heads[0]
1866 1867 return hg.merge(repo, node, force=force)
1867 1868
1868 1869 def outgoing(ui, repo, dest=None, **opts):
1869 1870 """show changesets not found in destination
1870 1871
1871 1872 Show changesets not found in the specified destination repository or
1872 1873 the default push location. These are the changesets that would be pushed
1873 1874 if a push was requested.
1874 1875
1875 1876 See pull for valid destination format details.
1876 1877 """
1877 1878 limit = cmdutil.loglimit(opts)
1878 1879 dest, revs, checkout = hg.parseurl(
1879 1880 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1880 1881 cmdutil.setremoteconfig(ui, opts)
1881 1882 if revs:
1882 1883 revs = [repo.lookup(rev) for rev in revs]
1883 1884
1884 1885 other = hg.repository(ui, dest)
1885 1886 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
1886 1887 o = repo.findoutgoing(other, force=opts['force'])
1887 1888 if not o:
1888 1889 ui.status(_("no changes found\n"))
1889 1890 return 1
1890 1891 o = repo.changelog.nodesbetween(o, revs)[0]
1891 1892 if opts['newest_first']:
1892 1893 o.reverse()
1893 1894 displayer = cmdutil.show_changeset(ui, repo, opts)
1894 1895 count = 0
1895 1896 for n in o:
1896 1897 if count >= limit:
1897 1898 break
1898 1899 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1899 1900 if opts['no_merges'] and len(parents) == 2:
1900 1901 continue
1901 1902 count += 1
1902 1903 displayer.show(changenode=n)
1903 1904
1904 1905 def parents(ui, repo, file_=None, **opts):
1905 1906 """show the parents of the working dir or revision
1906 1907
1907 1908 Print the working directory's parent revisions. If a
1908 1909 revision is given via --rev, the parent of that revision
1909 1910 will be printed. If a file argument is given, revision in
1910 1911 which the file was last changed (before the working directory
1911 1912 revision or the argument to --rev if given) is printed.
1912 1913 """
1913 1914 rev = opts.get('rev')
1914 1915 if rev:
1915 1916 ctx = repo.changectx(rev)
1916 1917 else:
1917 1918 ctx = repo.workingctx()
1918 1919
1919 1920 if file_:
1920 1921 files, match, anypats = cmdutil.matchpats(repo, (file_,), opts)
1921 1922 if anypats or len(files) != 1:
1922 1923 raise util.Abort(_('can only specify an explicit file name'))
1923 1924 file_ = files[0]
1924 1925 filenodes = []
1925 1926 for cp in ctx.parents():
1926 1927 if not cp:
1927 1928 continue
1928 1929 try:
1929 1930 filenodes.append(cp.filenode(file_))
1930 1931 except revlog.LookupError:
1931 1932 pass
1932 1933 if not filenodes:
1933 1934 raise util.Abort(_("'%s' not found in manifest!") % file_)
1934 1935 fl = repo.file(file_)
1935 1936 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
1936 1937 else:
1937 1938 p = [cp.node() for cp in ctx.parents()]
1938 1939
1939 1940 displayer = cmdutil.show_changeset(ui, repo, opts)
1940 1941 for n in p:
1941 1942 if n != nullid:
1942 1943 displayer.show(changenode=n)
1943 1944
1944 1945 def paths(ui, repo, search=None):
1945 1946 """show definition of symbolic path names
1946 1947
1947 1948 Show definition of symbolic path name NAME. If no name is given, show
1948 1949 definition of available names.
1949 1950
1950 1951 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1951 1952 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1952 1953 """
1953 1954 if search:
1954 1955 for name, path in ui.configitems("paths"):
1955 1956 if name == search:
1956 1957 ui.write("%s\n" % path)
1957 1958 return
1958 1959 ui.warn(_("not found!\n"))
1959 1960 return 1
1960 1961 else:
1961 1962 for name, path in ui.configitems("paths"):
1962 1963 ui.write("%s = %s\n" % (name, path))
1963 1964
1964 1965 def postincoming(ui, repo, modheads, optupdate, checkout):
1965 1966 if modheads == 0:
1966 1967 return
1967 1968 if optupdate:
1968 1969 if modheads <= 1 or checkout:
1969 1970 return hg.update(repo, checkout)
1970 1971 else:
1971 1972 ui.status(_("not updating, since new heads added\n"))
1972 1973 if modheads > 1:
1973 1974 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
1974 1975 else:
1975 1976 ui.status(_("(run 'hg update' to get a working copy)\n"))
1976 1977
1977 1978 def pull(ui, repo, source="default", **opts):
1978 1979 """pull changes from the specified source
1979 1980
1980 1981 Pull changes from a remote repository to a local one.
1981 1982
1982 1983 This finds all changes from the repository at the specified path
1983 1984 or URL and adds them to the local repository. By default, this
1984 1985 does not update the copy of the project in the working directory.
1985 1986
1986 1987 Valid URLs are of the form:
1987 1988
1988 1989 local/filesystem/path (or file://local/filesystem/path)
1989 1990 http://[user@]host[:port]/[path]
1990 1991 https://[user@]host[:port]/[path]
1991 1992 ssh://[user@]host[:port]/[path]
1992 1993 static-http://host[:port]/[path]
1993 1994
1994 1995 Paths in the local filesystem can either point to Mercurial
1995 1996 repositories or to bundle files (as created by 'hg bundle' or
1996 1997 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
1997 1998 allows access to a Mercurial repository where you simply use a web
1998 1999 server to publish the .hg directory as static content.
1999 2000
2000 2001 An optional identifier after # indicates a particular branch, tag,
2001 2002 or changeset to pull.
2002 2003
2003 2004 Some notes about using SSH with Mercurial:
2004 2005 - SSH requires an accessible shell account on the destination machine
2005 2006 and a copy of hg in the remote path or specified with as remotecmd.
2006 2007 - path is relative to the remote user's home directory by default.
2007 2008 Use an extra slash at the start of a path to specify an absolute path:
2008 2009 ssh://example.com//tmp/repository
2009 2010 - Mercurial doesn't use its own compression via SSH; the right thing
2010 2011 to do is to configure it in your ~/.ssh/config, e.g.:
2011 2012 Host *.mylocalnetwork.example.com
2012 2013 Compression no
2013 2014 Host *
2014 2015 Compression yes
2015 2016 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2016 2017 with the --ssh command line option.
2017 2018 """
2018 2019 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2019 2020 cmdutil.setremoteconfig(ui, opts)
2020 2021
2021 2022 other = hg.repository(ui, source)
2022 2023 ui.status(_('pulling from %s\n') % util.hidepassword(source))
2023 2024 if revs:
2024 2025 try:
2025 2026 revs = [other.lookup(rev) for rev in revs]
2026 2027 except repo.NoCapability:
2027 2028 error = _("Other repository doesn't support revision lookup, "
2028 2029 "so a rev cannot be specified.")
2029 2030 raise util.Abort(error)
2030 2031
2031 2032 modheads = repo.pull(other, heads=revs, force=opts['force'])
2032 2033 return postincoming(ui, repo, modheads, opts['update'], checkout)
2033 2034
2034 2035 def push(ui, repo, dest=None, **opts):
2035 2036 """push changes to the specified destination
2036 2037
2037 2038 Push changes from the local repository to the given destination.
2038 2039
2039 2040 This is the symmetrical operation for pull. It helps to move
2040 2041 changes from the current repository to a different one. If the
2041 2042 destination is local this is identical to a pull in that directory
2042 2043 from the current one.
2043 2044
2044 2045 By default, push will refuse to run if it detects the result would
2045 2046 increase the number of remote heads. This generally indicates the
2046 2047 the client has forgotten to sync and merge before pushing.
2047 2048
2048 2049 Valid URLs are of the form:
2049 2050
2050 2051 local/filesystem/path (or file://local/filesystem/path)
2051 2052 ssh://[user@]host[:port]/[path]
2052 2053 http://[user@]host[:port]/[path]
2053 2054 https://[user@]host[:port]/[path]
2054 2055
2055 2056 An optional identifier after # indicates a particular branch, tag,
2056 2057 or changeset to push.
2057 2058
2058 2059 Look at the help text for the pull command for important details
2059 2060 about ssh:// URLs.
2060 2061
2061 2062 Pushing to http:// and https:// URLs is only possible, if this
2062 2063 feature is explicitly enabled on the remote Mercurial server.
2063 2064 """
2064 2065 dest, revs, checkout = hg.parseurl(
2065 2066 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2066 2067 cmdutil.setremoteconfig(ui, opts)
2067 2068
2068 2069 other = hg.repository(ui, dest)
2069 2070 ui.status('pushing to %s\n' % util.hidepassword(dest))
2070 2071 if revs:
2071 2072 revs = [repo.lookup(rev) for rev in revs]
2072 2073 r = repo.push(other, opts['force'], revs=revs)
2073 2074 return r == 0
2074 2075
2075 2076 def rawcommit(ui, repo, *pats, **opts):
2076 2077 """raw commit interface (DEPRECATED)
2077 2078
2078 2079 (DEPRECATED)
2079 2080 Lowlevel commit, for use in helper scripts.
2080 2081
2081 2082 This command is not intended to be used by normal users, as it is
2082 2083 primarily useful for importing from other SCMs.
2083 2084
2084 2085 This command is now deprecated and will be removed in a future
2085 2086 release, please use debugsetparents and commit instead.
2086 2087 """
2087 2088
2088 2089 ui.warn(_("(the rawcommit command is deprecated)\n"))
2089 2090
2090 2091 message = cmdutil.logmessage(opts)
2091 2092
2092 2093 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2093 2094 if opts['files']:
2094 2095 files += open(opts['files']).read().splitlines()
2095 2096
2096 2097 parents = [repo.lookup(p) for p in opts['parent']]
2097 2098
2098 2099 try:
2099 2100 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2100 2101 except ValueError, inst:
2101 2102 raise util.Abort(str(inst))
2102 2103
2103 2104 def recover(ui, repo):
2104 2105 """roll back an interrupted transaction
2105 2106
2106 2107 Recover from an interrupted commit or pull.
2107 2108
2108 2109 This command tries to fix the repository status after an interrupted
2109 2110 operation. It should only be necessary when Mercurial suggests it.
2110 2111 """
2111 2112 if repo.recover():
2112 2113 return hg.verify(repo)
2113 2114 return 1
2114 2115
2115 2116 def remove(ui, repo, *pats, **opts):
2116 2117 """remove the specified files on the next commit
2117 2118
2118 2119 Schedule the indicated files for removal from the repository.
2119 2120
2120 2121 This only removes files from the current branch, not from the
2121 2122 entire project history. If the files still exist in the working
2122 2123 directory, they will be deleted from it. If invoked with --after,
2123 2124 files are marked as removed, but not actually unlinked unless --force
2124 2125 is also given. Without exact file names, --after will only mark
2125 2126 files as removed if they are no longer in the working directory.
2126 2127
2127 2128 This command schedules the files to be removed at the next commit.
2128 2129 To undo a remove before that, see hg revert.
2129 2130
2130 2131 Modified files and added files are not removed by default. To
2131 2132 remove them, use the -f/--force option.
2132 2133 """
2133 2134 if not opts['after'] and not pats:
2134 2135 raise util.Abort(_('no files specified'))
2135 2136 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2136 2137 exact = dict.fromkeys(files)
2137 2138 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2138 2139 modified, added, removed, deleted, unknown = mardu
2139 2140 remove, forget = [], []
2140 2141 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2141 2142 reason = None
2142 2143 if abs in modified and not opts['force']:
2143 2144 reason = _('is modified (use -f to force removal)')
2144 2145 elif abs in added:
2145 2146 if opts['force']:
2146 2147 forget.append(abs)
2147 2148 continue
2148 2149 reason = _('has been marked for add (use -f to force removal)')
2149 2150 exact = 1 # force the message
2150 2151 elif abs not in repo.dirstate:
2151 2152 reason = _('is not managed')
2152 2153 elif opts['after'] and not exact and abs not in deleted:
2153 2154 continue
2154 2155 elif abs in removed:
2155 2156 continue
2156 2157 if reason:
2157 2158 if exact:
2158 2159 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2159 2160 else:
2160 2161 if ui.verbose or not exact:
2161 2162 ui.status(_('removing %s\n') % rel)
2162 2163 remove.append(abs)
2163 2164 repo.forget(forget)
2164 2165 repo.remove(remove, unlink=opts['force'] or not opts['after'])
2165 2166
2166 2167 def rename(ui, repo, *pats, **opts):
2167 2168 """rename files; equivalent of copy + remove
2168 2169
2169 2170 Mark dest as copies of sources; mark sources for deletion. If
2170 2171 dest is a directory, copies are put in that directory. If dest is
2171 2172 a file, there can only be one source.
2172 2173
2173 2174 By default, this command copies the contents of files as they
2174 2175 stand in the working directory. If invoked with --after, the
2175 2176 operation is recorded, but no copying is performed.
2176 2177
2177 2178 This command takes effect in the next commit. To undo a rename
2178 2179 before that, see hg revert.
2179 2180 """
2180 2181 wlock = repo.wlock(False)
2181 2182 try:
2182 2183 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2183 2184 finally:
2184 2185 del wlock
2185 2186
2186 2187 def revert(ui, repo, *pats, **opts):
2187 2188 """restore individual files or dirs to an earlier state
2188 2189
2189 2190 (use update -r to check out earlier revisions, revert does not
2190 2191 change the working dir parents)
2191 2192
2192 2193 With no revision specified, revert the named files or directories
2193 2194 to the contents they had in the parent of the working directory.
2194 2195 This restores the contents of the affected files to an unmodified
2195 2196 state and unschedules adds, removes, copies, and renames. If the
2196 2197 working directory has two parents, you must explicitly specify the
2197 2198 revision to revert to.
2198 2199
2199 2200 Using the -r option, revert the given files or directories to their
2200 2201 contents as of a specific revision. This can be helpful to "roll
2201 2202 back" some or all of an earlier change.
2202 2203 See 'hg help dates' for a list of formats valid for -d/--date.
2203 2204
2204 2205 Revert modifies the working directory. It does not commit any
2205 2206 changes, or change the parent of the working directory. If you
2206 2207 revert to a revision other than the parent of the working
2207 2208 directory, the reverted files will thus appear modified
2208 2209 afterwards.
2209 2210
2210 2211 If a file has been deleted, it is restored. If the executable
2211 2212 mode of a file was changed, it is reset.
2212 2213
2213 2214 If names are given, all files matching the names are reverted.
2214 2215 If no arguments are given, no files are reverted.
2215 2216
2216 2217 Modified files are saved with a .orig suffix before reverting.
2217 2218 To disable these backups, use --no-backup.
2218 2219 """
2219 2220
2220 2221 if opts["date"]:
2221 2222 if opts["rev"]:
2222 2223 raise util.Abort(_("you can't specify a revision and a date"))
2223 2224 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2224 2225
2225 2226 if not pats and not opts['all']:
2226 2227 raise util.Abort(_('no files or directories specified; '
2227 2228 'use --all to revert the whole repo'))
2228 2229
2229 2230 parent, p2 = repo.dirstate.parents()
2230 2231 if not opts['rev'] and p2 != nullid:
2231 2232 raise util.Abort(_('uncommitted merge - please provide a '
2232 2233 'specific revision'))
2233 2234 ctx = repo.changectx(opts['rev'])
2234 2235 node = ctx.node()
2235 2236 mf = ctx.manifest()
2236 2237 if node == parent:
2237 2238 pmf = mf
2238 2239 else:
2239 2240 pmf = None
2240 2241
2241 2242 # need all matching names in dirstate and manifest of target rev,
2242 2243 # so have to walk both. do not print errors if files exist in one
2243 2244 # but not other.
2244 2245
2245 2246 names = {}
2246 2247
2247 2248 wlock = repo.wlock()
2248 2249 try:
2249 2250 # walk dirstate.
2250 2251 files = []
2251 2252 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2252 2253 badmatch=mf.has_key):
2253 2254 names[abs] = (rel, exact)
2254 2255 if src != 'b':
2255 2256 files.append(abs)
2256 2257
2257 2258 # walk target manifest.
2258 2259
2259 2260 def badmatch(path):
2260 2261 if path in names:
2261 2262 return True
2262 2263 path_ = path + '/'
2263 2264 for f in names:
2264 2265 if f.startswith(path_):
2265 2266 return True
2266 2267 return False
2267 2268
2268 2269 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2269 2270 badmatch=badmatch):
2270 2271 if abs in names or src == 'b':
2271 2272 continue
2272 2273 names[abs] = (rel, exact)
2273 2274
2274 2275 changes = repo.status(files=files, match=names.has_key)[:4]
2275 2276 modified, added, removed, deleted = map(dict.fromkeys, changes)
2276 2277
2277 2278 # if f is a rename, also revert the source
2278 2279 cwd = repo.getcwd()
2279 2280 for f in added:
2280 2281 src = repo.dirstate.copied(f)
2281 2282 if src and src not in names and repo.dirstate[src] == 'r':
2282 2283 removed[src] = None
2283 2284 names[src] = (repo.pathto(src, cwd), True)
2284 2285
2285 2286 def removeforget(abs):
2286 2287 if repo.dirstate[abs] == 'a':
2287 2288 return _('forgetting %s\n')
2288 2289 return _('removing %s\n')
2289 2290
2290 2291 revert = ([], _('reverting %s\n'))
2291 2292 add = ([], _('adding %s\n'))
2292 2293 remove = ([], removeforget)
2293 2294 undelete = ([], _('undeleting %s\n'))
2294 2295
2295 2296 disptable = (
2296 2297 # dispatch table:
2297 2298 # file state
2298 2299 # action if in target manifest
2299 2300 # action if not in target manifest
2300 2301 # make backup if in target manifest
2301 2302 # make backup if not in target manifest
2302 2303 (modified, revert, remove, True, True),
2303 2304 (added, revert, remove, True, False),
2304 2305 (removed, undelete, None, False, False),
2305 2306 (deleted, revert, remove, False, False),
2306 2307 )
2307 2308
2308 2309 entries = names.items()
2309 2310 entries.sort()
2310 2311
2311 2312 for abs, (rel, exact) in entries:
2312 2313 mfentry = mf.get(abs)
2313 2314 target = repo.wjoin(abs)
2314 2315 def handle(xlist, dobackup):
2315 2316 xlist[0].append(abs)
2316 2317 if dobackup and not opts['no_backup'] and util.lexists(target):
2317 2318 bakname = "%s.orig" % rel
2318 2319 ui.note(_('saving current version of %s as %s\n') %
2319 2320 (rel, bakname))
2320 2321 if not opts.get('dry_run'):
2321 2322 util.copyfile(target, bakname)
2322 2323 if ui.verbose or not exact:
2323 2324 msg = xlist[1]
2324 2325 if not isinstance(msg, basestring):
2325 2326 msg = msg(abs)
2326 2327 ui.status(msg % rel)
2327 2328 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2328 2329 if abs not in table: continue
2329 2330 # file has changed in dirstate
2330 2331 if mfentry:
2331 2332 handle(hitlist, backuphit)
2332 2333 elif misslist is not None:
2333 2334 handle(misslist, backupmiss)
2334 2335 break
2335 2336 else:
2336 2337 if abs not in repo.dirstate:
2337 2338 if mfentry:
2338 2339 handle(add, True)
2339 2340 elif exact:
2340 2341 ui.warn(_('file not managed: %s\n') % rel)
2341 2342 continue
2342 2343 # file has not changed in dirstate
2343 2344 if node == parent:
2344 2345 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2345 2346 continue
2346 2347 if pmf is None:
2347 2348 # only need parent manifest in this unlikely case,
2348 2349 # so do not read by default
2349 2350 pmf = repo.changectx(parent).manifest()
2350 2351 if abs in pmf:
2351 2352 if mfentry:
2352 2353 # if version of file is same in parent and target
2353 2354 # manifests, do nothing
2354 2355 if (pmf[abs] != mfentry or
2355 2356 pmf.flags(abs) != mf.flags(abs)):
2356 2357 handle(revert, False)
2357 2358 else:
2358 2359 handle(remove, False)
2359 2360
2360 2361 if not opts.get('dry_run'):
2361 2362 def checkout(f):
2362 2363 fc = ctx[f]
2363 2364 repo.wwrite(f, fc.data(), fc.fileflags())
2364 2365
2365 2366 audit_path = util.path_auditor(repo.root)
2366 2367 for f in remove[0]:
2367 2368 if repo.dirstate[f] == 'a':
2368 2369 repo.dirstate.forget(f)
2369 2370 continue
2370 2371 audit_path(f)
2371 2372 try:
2372 2373 util.unlink(repo.wjoin(f))
2373 2374 except OSError:
2374 2375 pass
2375 2376 repo.dirstate.remove(f)
2376 2377
2377 2378 for f in revert[0]:
2378 2379 checkout(f)
2379 2380
2380 2381 for f in add[0]:
2381 2382 checkout(f)
2382 2383 repo.dirstate.add(f)
2383 2384
2384 2385 normal = repo.dirstate.normallookup
2385 2386 if node == parent and p2 == nullid:
2386 2387 normal = repo.dirstate.normal
2387 2388 for f in undelete[0]:
2388 2389 checkout(f)
2389 2390 normal(f)
2390 2391
2391 2392 finally:
2392 2393 del wlock
2393 2394
2394 2395 def rollback(ui, repo):
2395 2396 """roll back the last transaction
2396 2397
2397 2398 This command should be used with care. There is only one level of
2398 2399 rollback, and there is no way to undo a rollback. It will also
2399 2400 restore the dirstate at the time of the last transaction, losing
2400 2401 any dirstate changes since that time.
2401 2402
2402 2403 Transactions are used to encapsulate the effects of all commands
2403 2404 that create new changesets or propagate existing changesets into a
2404 2405 repository. For example, the following commands are transactional,
2405 2406 and their effects can be rolled back:
2406 2407
2407 2408 commit
2408 2409 import
2409 2410 pull
2410 2411 push (with this repository as destination)
2411 2412 unbundle
2412 2413
2413 2414 This command is not intended for use on public repositories. Once
2414 2415 changes are visible for pull by other users, rolling a transaction
2415 2416 back locally is ineffective (someone else may already have pulled
2416 2417 the changes). Furthermore, a race is possible with readers of the
2417 2418 repository; for example an in-progress pull from the repository
2418 2419 may fail if a rollback is performed.
2419 2420 """
2420 2421 repo.rollback()
2421 2422
2422 2423 def root(ui, repo):
2423 2424 """print the root (top) of the current working dir
2424 2425
2425 2426 Print the root directory of the current repository.
2426 2427 """
2427 2428 ui.write(repo.root + "\n")
2428 2429
2429 2430 def serve(ui, repo, **opts):
2430 2431 """export the repository via HTTP
2431 2432
2432 2433 Start a local HTTP repository browser and pull server.
2433 2434
2434 2435 By default, the server logs accesses to stdout and errors to
2435 2436 stderr. Use the "-A" and "-E" options to log to files.
2436 2437 """
2437 2438
2438 2439 if opts["stdio"]:
2439 2440 if repo is None:
2440 raise hg.RepoError(_("There is no Mercurial repository here"
2441 " (.hg not found)"))
2441 raise RepoError(_("There is no Mercurial repository here"
2442 " (.hg not found)"))
2442 2443 s = sshserver.sshserver(ui, repo)
2443 2444 s.serve_forever()
2444 2445
2445 2446 parentui = ui.parentui or ui
2446 2447 optlist = ("name templates style address port prefix ipv6"
2447 2448 " accesslog errorlog webdir_conf certificate")
2448 2449 for o in optlist.split():
2449 2450 if opts[o]:
2450 2451 parentui.setconfig("web", o, str(opts[o]))
2451 2452 if (repo is not None) and (repo.ui != parentui):
2452 2453 repo.ui.setconfig("web", o, str(opts[o]))
2453 2454
2454 2455 if repo is None and not ui.config("web", "webdir_conf"):
2455 raise hg.RepoError(_("There is no Mercurial repository here"
2456 " (.hg not found)"))
2456 raise RepoError(_("There is no Mercurial repository here"
2457 " (.hg not found)"))
2457 2458
2458 2459 class service:
2459 2460 def init(self):
2460 2461 util.set_signal_handler()
2461 2462 try:
2462 2463 self.httpd = hgweb.server.create_server(parentui, repo)
2463 2464 except socket.error, inst:
2464 2465 raise util.Abort(_('cannot start server: ') + inst.args[1])
2465 2466
2466 2467 if not ui.verbose: return
2467 2468
2468 2469 if self.httpd.prefix:
2469 2470 prefix = self.httpd.prefix.strip('/') + '/'
2470 2471 else:
2471 2472 prefix = ''
2472 2473
2473 2474 if self.httpd.port != 80:
2474 2475 ui.status(_('listening at http://%s:%d/%s\n') %
2475 2476 (self.httpd.addr, self.httpd.port, prefix))
2476 2477 else:
2477 2478 ui.status(_('listening at http://%s/%s\n') %
2478 2479 (self.httpd.addr, prefix))
2479 2480
2480 2481 def run(self):
2481 2482 self.httpd.serve_forever()
2482 2483
2483 2484 service = service()
2484 2485
2485 2486 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2486 2487
2487 2488 def status(ui, repo, *pats, **opts):
2488 2489 """show changed files in the working directory
2489 2490
2490 2491 Show status of files in the repository. If names are given, only
2491 2492 files that match are shown. Files that are clean or ignored or
2492 2493 source of a copy/move operation, are not listed unless -c (clean),
2493 2494 -i (ignored), -C (copies) or -A is given. Unless options described
2494 2495 with "show only ..." are given, the options -mardu are used.
2495 2496
2496 2497 Option -q/--quiet hides untracked (unknown and ignored) files
2497 2498 unless explicitly requested with -u/--unknown or -i/-ignored.
2498 2499
2499 2500 NOTE: status may appear to disagree with diff if permissions have
2500 2501 changed or a merge has occurred. The standard diff format does not
2501 2502 report permission changes and diff only reports changes relative
2502 2503 to one merge parent.
2503 2504
2504 2505 If one revision is given, it is used as the base revision.
2505 2506 If two revisions are given, the difference between them is shown.
2506 2507
2507 2508 The codes used to show the status of files are:
2508 2509 M = modified
2509 2510 A = added
2510 2511 R = removed
2511 2512 C = clean
2512 2513 ! = deleted, but still tracked
2513 2514 ? = not tracked
2514 2515 I = ignored
2515 2516 = the previous added file was copied from here
2516 2517 """
2517 2518
2518 2519 all = opts['all']
2519 2520 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2520 2521
2521 2522 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2522 2523 cwd = (pats and repo.getcwd()) or ''
2523 2524 modified, added, removed, deleted, unknown, ignored, clean = [
2524 2525 n for n in repo.status(node1=node1, node2=node2, files=files,
2525 2526 match=matchfn,
2526 2527 list_ignored=opts['ignored']
2527 2528 or all and not ui.quiet,
2528 2529 list_clean=opts['clean'] or all,
2529 2530 list_unknown=opts['unknown']
2530 2531 or not (ui.quiet or
2531 2532 opts['modified'] or
2532 2533 opts['added'] or
2533 2534 opts['removed'] or
2534 2535 opts['deleted'] or
2535 2536 opts['ignored']))]
2536 2537
2537 2538 changetypes = (('modified', 'M', modified),
2538 2539 ('added', 'A', added),
2539 2540 ('removed', 'R', removed),
2540 2541 ('deleted', '!', deleted),
2541 2542 ('unknown', '?', unknown),
2542 2543 ('ignored', 'I', ignored))
2543 2544
2544 2545 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2545 2546
2546 2547 end = opts['print0'] and '\0' or '\n'
2547 2548
2548 2549 for opt, char, changes in ([ct for ct in explicit_changetypes
2549 2550 if all or opts[ct[0]]]
2550 2551 or changetypes):
2551 2552
2552 2553 if opts['no_status']:
2553 2554 format = "%%s%s" % end
2554 2555 else:
2555 2556 format = "%s %%s%s" % (char, end)
2556 2557
2557 2558 for f in changes:
2558 2559 ui.write(format % repo.pathto(f, cwd))
2559 2560 if ((all or opts.get('copies')) and not opts.get('no_status')):
2560 2561 copied = repo.dirstate.copied(f)
2561 2562 if copied:
2562 2563 ui.write(' %s%s' % (repo.pathto(copied, cwd), end))
2563 2564
2564 2565 def tag(ui, repo, name, rev_=None, **opts):
2565 2566 """add a tag for the current or given revision
2566 2567
2567 2568 Name a particular revision using <name>.
2568 2569
2569 2570 Tags are used to name particular revisions of the repository and are
2570 2571 very useful to compare different revision, to go back to significant
2571 2572 earlier versions or to mark branch points as releases, etc.
2572 2573
2573 2574 If no revision is given, the parent of the working directory is used,
2574 2575 or tip if no revision is checked out.
2575 2576
2576 2577 To facilitate version control, distribution, and merging of tags,
2577 2578 they are stored as a file named ".hgtags" which is managed
2578 2579 similarly to other project files and can be hand-edited if
2579 2580 necessary. The file '.hg/localtags' is used for local tags (not
2580 2581 shared among repositories).
2581 2582
2582 2583 See 'hg help dates' for a list of formats valid for -d/--date.
2583 2584 """
2584 2585 if name in ['tip', '.', 'null']:
2585 2586 raise util.Abort(_("the name '%s' is reserved") % name)
2586 2587 if rev_ is not None:
2587 2588 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2588 2589 "please use 'hg tag [-r REV] NAME' instead\n"))
2589 2590 if opts['rev']:
2590 2591 raise util.Abort(_("use only one form to specify the revision"))
2591 2592 if opts['rev'] and opts['remove']:
2592 2593 raise util.Abort(_("--rev and --remove are incompatible"))
2593 2594 if opts['rev']:
2594 2595 rev_ = opts['rev']
2595 2596 message = opts['message']
2596 2597 if opts['remove']:
2597 2598 tagtype = repo.tagtype(name)
2598 2599
2599 2600 if not tagtype:
2600 2601 raise util.Abort(_('tag %s does not exist') % name)
2601 2602 if opts['local'] and tagtype == 'global':
2602 2603 raise util.Abort(_('%s tag is global') % name)
2603 2604 if not opts['local'] and tagtype == 'local':
2604 2605 raise util.Abort(_('%s tag is local') % name)
2605 2606
2606 2607 rev_ = nullid
2607 2608 if not message:
2608 2609 message = _('Removed tag %s') % name
2609 2610 elif name in repo.tags() and not opts['force']:
2610 2611 raise util.Abort(_('a tag named %s already exists (use -f to force)')
2611 2612 % name)
2612 2613 if not rev_ and repo.dirstate.parents()[1] != nullid:
2613 2614 raise util.Abort(_('uncommitted merge - please provide a '
2614 2615 'specific revision'))
2615 2616 r = repo.changectx(rev_).node()
2616 2617
2617 2618 if not message:
2618 2619 message = _('Added tag %s for changeset %s') % (name, short(r))
2619 2620
2620 2621 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2621 2622
2622 2623 def tags(ui, repo):
2623 2624 """list repository tags
2624 2625
2625 2626 List the repository tags.
2626 2627
2627 2628 This lists both regular and local tags. When the -v/--verbose switch
2628 2629 is used, a third column "local" is printed for local tags.
2629 2630 """
2630 2631
2631 2632 l = repo.tagslist()
2632 2633 l.reverse()
2633 2634 hexfunc = ui.debugflag and hex or short
2634 2635 tagtype = ""
2635 2636
2636 2637 for t, n in l:
2637 2638 if ui.quiet:
2638 2639 ui.write("%s\n" % t)
2639 2640 continue
2640 2641
2641 2642 try:
2642 2643 hn = hexfunc(n)
2643 2644 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2644 2645 except revlog.LookupError:
2645 2646 r = " ?:%s" % hn
2646 2647 else:
2647 2648 spaces = " " * (30 - util.locallen(t))
2648 2649 if ui.verbose:
2649 2650 if repo.tagtype(t) == 'local':
2650 2651 tagtype = " local"
2651 2652 else:
2652 2653 tagtype = ""
2653 2654 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2654 2655
2655 2656 def tip(ui, repo, **opts):
2656 2657 """show the tip revision
2657 2658
2658 2659 Show the tip revision.
2659 2660 """
2660 2661 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2661 2662
2662 2663 def unbundle(ui, repo, fname1, *fnames, **opts):
2663 2664 """apply one or more changegroup files
2664 2665
2665 2666 Apply one or more compressed changegroup files generated by the
2666 2667 bundle command.
2667 2668 """
2668 2669 fnames = (fname1,) + fnames
2669 2670
2670 2671 lock = None
2671 2672 try:
2672 2673 lock = repo.lock()
2673 2674 for fname in fnames:
2674 2675 if os.path.exists(fname):
2675 2676 f = open(fname, "rb")
2676 2677 else:
2677 2678 f = urllib.urlopen(fname)
2678 2679 gen = changegroup.readbundle(f, fname)
2679 2680 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2680 2681 finally:
2681 2682 del lock
2682 2683
2683 2684 return postincoming(ui, repo, modheads, opts['update'], None)
2684 2685
2685 2686 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2686 2687 """update working directory
2687 2688
2688 2689 Update the working directory to the specified revision, or the
2689 2690 tip of the current branch if none is specified.
2690 2691 See 'hg help dates' for a list of formats valid for -d/--date.
2691 2692
2692 2693 If there are no outstanding changes in the working directory and
2693 2694 there is a linear relationship between the current version and the
2694 2695 requested version, the result is the requested version.
2695 2696
2696 2697 To merge the working directory with another revision, use the
2697 2698 merge command.
2698 2699
2699 2700 By default, update will refuse to run if doing so would require
2700 2701 discarding local changes.
2701 2702 """
2702 2703 if rev and node:
2703 2704 raise util.Abort(_("please specify just one revision"))
2704 2705
2705 2706 if not rev:
2706 2707 rev = node
2707 2708
2708 2709 if date:
2709 2710 if rev:
2710 2711 raise util.Abort(_("you can't specify a revision and a date"))
2711 2712 rev = cmdutil.finddate(ui, repo, date)
2712 2713
2713 2714 if clean:
2714 2715 return hg.clean(repo, rev)
2715 2716 else:
2716 2717 return hg.update(repo, rev)
2717 2718
2718 2719 def verify(ui, repo):
2719 2720 """verify the integrity of the repository
2720 2721
2721 2722 Verify the integrity of the current repository.
2722 2723
2723 2724 This will perform an extensive check of the repository's
2724 2725 integrity, validating the hashes and checksums of each entry in
2725 2726 the changelog, manifest, and tracked files, as well as the
2726 2727 integrity of their crosslinks and indices.
2727 2728 """
2728 2729 return hg.verify(repo)
2729 2730
2730 2731 def version_(ui):
2731 2732 """output version and copyright information"""
2732 2733 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2733 2734 % version.get_version())
2734 2735 ui.status(_(
2735 2736 "\nCopyright (C) 2005-2008 Matt Mackall <mpm@selenic.com> and others\n"
2736 2737 "This is free software; see the source for copying conditions. "
2737 2738 "There is NO\nwarranty; "
2738 2739 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2739 2740 ))
2740 2741
2741 2742 # Command options and aliases are listed here, alphabetically
2742 2743
2743 2744 globalopts = [
2744 2745 ('R', 'repository', '',
2745 2746 _('repository root directory or symbolic path name')),
2746 2747 ('', 'cwd', '', _('change working directory')),
2747 2748 ('y', 'noninteractive', None,
2748 2749 _('do not prompt, assume \'yes\' for any required answers')),
2749 2750 ('q', 'quiet', None, _('suppress output')),
2750 2751 ('v', 'verbose', None, _('enable additional output')),
2751 2752 ('', 'config', [], _('set/override config option')),
2752 2753 ('', 'debug', None, _('enable debugging output')),
2753 2754 ('', 'debugger', None, _('start debugger')),
2754 2755 ('', 'encoding', util._encoding, _('set the charset encoding')),
2755 2756 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2756 2757 ('', 'lsprof', None, _('print improved command execution profile')),
2757 2758 ('', 'traceback', None, _('print traceback on exception')),
2758 2759 ('', 'time', None, _('time how long the command takes')),
2759 2760 ('', 'profile', None, _('print command execution profile')),
2760 2761 ('', 'version', None, _('output version information and exit')),
2761 2762 ('h', 'help', None, _('display help and exit')),
2762 2763 ]
2763 2764
2764 2765 dryrunopts = [('n', 'dry-run', None,
2765 2766 _('do not perform actions, just print output'))]
2766 2767
2767 2768 remoteopts = [
2768 2769 ('e', 'ssh', '', _('specify ssh command to use')),
2769 2770 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2770 2771 ]
2771 2772
2772 2773 walkopts = [
2773 2774 ('I', 'include', [], _('include names matching the given patterns')),
2774 2775 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2775 2776 ]
2776 2777
2777 2778 commitopts = [
2778 2779 ('m', 'message', '', _('use <text> as commit message')),
2779 2780 ('l', 'logfile', '', _('read commit message from <file>')),
2780 2781 ]
2781 2782
2782 2783 commitopts2 = [
2783 2784 ('d', 'date', '', _('record datecode as commit date')),
2784 2785 ('u', 'user', '', _('record user as committer')),
2785 2786 ]
2786 2787
2787 2788 templateopts = [
2788 2789 ('', 'style', '', _('display using template map file')),
2789 2790 ('', 'template', '', _('display with template')),
2790 2791 ]
2791 2792
2792 2793 logopts = [
2793 2794 ('p', 'patch', None, _('show patch')),
2794 2795 ('l', 'limit', '', _('limit number of changes displayed')),
2795 2796 ('M', 'no-merges', None, _('do not show merges')),
2796 2797 ] + templateopts
2797 2798
2798 2799 table = {
2799 2800 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2800 2801 "addremove":
2801 2802 (addremove,
2802 2803 [('s', 'similarity', '',
2803 2804 _('guess renamed files by similarity (0<=s<=100)')),
2804 2805 ] + walkopts + dryrunopts,
2805 2806 _('hg addremove [OPTION]... [FILE]...')),
2806 2807 "^annotate|blame":
2807 2808 (annotate,
2808 2809 [('r', 'rev', '', _('annotate the specified revision')),
2809 2810 ('f', 'follow', None, _('follow file copies and renames')),
2810 2811 ('a', 'text', None, _('treat all files as text')),
2811 2812 ('u', 'user', None, _('list the author (long with -v)')),
2812 2813 ('d', 'date', None, _('list the date (short with -q)')),
2813 2814 ('n', 'number', None, _('list the revision number (default)')),
2814 2815 ('c', 'changeset', None, _('list the changeset')),
2815 2816 ('l', 'line-number', None,
2816 2817 _('show line number at the first appearance'))
2817 2818 ] + walkopts,
2818 2819 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2819 2820 "archive":
2820 2821 (archive,
2821 2822 [('', 'no-decode', None, _('do not pass files through decoders')),
2822 2823 ('p', 'prefix', '', _('directory prefix for files in archive')),
2823 2824 ('r', 'rev', '', _('revision to distribute')),
2824 2825 ('t', 'type', '', _('type of distribution to create')),
2825 2826 ] + walkopts,
2826 2827 _('hg archive [OPTION]... DEST')),
2827 2828 "backout":
2828 2829 (backout,
2829 2830 [('', 'merge', None,
2830 2831 _('merge with old dirstate parent after backout')),
2831 2832 ('', 'parent', '', _('parent to choose when backing out merge')),
2832 2833 ('r', 'rev', '', _('revision to backout')),
2833 2834 ] + walkopts + commitopts + commitopts2,
2834 2835 _('hg backout [OPTION]... [-r] REV')),
2835 2836 "bisect":
2836 2837 (bisect,
2837 2838 [('r', 'reset', False, _('reset bisect state')),
2838 2839 ('g', 'good', False, _('mark changeset good')),
2839 2840 ('b', 'bad', False, _('mark changeset bad')),
2840 2841 ('s', 'skip', False, _('skip testing changeset')),
2841 2842 ('U', 'noupdate', False, _('do not update to target'))],
2842 2843 _("hg bisect [-gbsr] [REV]")),
2843 2844 "branch":
2844 2845 (branch,
2845 2846 [('f', 'force', None,
2846 2847 _('set branch name even if it shadows an existing branch'))],
2847 2848 _('hg branch [-f] [NAME]')),
2848 2849 "branches":
2849 2850 (branches,
2850 2851 [('a', 'active', False,
2851 2852 _('show only branches that have unmerged heads'))],
2852 2853 _('hg branches [-a]')),
2853 2854 "bundle":
2854 2855 (bundle,
2855 2856 [('f', 'force', None,
2856 2857 _('run even when remote repository is unrelated')),
2857 2858 ('r', 'rev', [],
2858 2859 _('a changeset you would like to bundle')),
2859 2860 ('', 'base', [],
2860 2861 _('a base changeset to specify instead of a destination')),
2861 2862 ('a', 'all', None,
2862 2863 _('bundle all changesets in the repository')),
2863 2864 ] + remoteopts,
2864 2865 _('hg bundle [-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
2865 2866 "cat":
2866 2867 (cat,
2867 2868 [('o', 'output', '', _('print output to file with formatted name')),
2868 2869 ('r', 'rev', '', _('print the given revision')),
2869 2870 ('', 'decode', None, _('apply any matching decode filter')),
2870 2871 ] + walkopts,
2871 2872 _('hg cat [OPTION]... FILE...')),
2872 2873 "^clone":
2873 2874 (clone,
2874 2875 [('U', 'noupdate', None, _('do not update the new working directory')),
2875 2876 ('r', 'rev', [],
2876 2877 _('a changeset you would like to have after cloning')),
2877 2878 ('', 'pull', None, _('use pull protocol to copy metadata')),
2878 2879 ('', 'uncompressed', None,
2879 2880 _('use uncompressed transfer (fast over LAN)')),
2880 2881 ] + remoteopts,
2881 2882 _('hg clone [OPTION]... SOURCE [DEST]')),
2882 2883 "^commit|ci":
2883 2884 (commit,
2884 2885 [('A', 'addremove', None,
2885 2886 _('mark new/missing files as added/removed before committing')),
2886 2887 ] + walkopts + commitopts + commitopts2,
2887 2888 _('hg commit [OPTION]... [FILE]...')),
2888 2889 "copy|cp":
2889 2890 (copy,
2890 2891 [('A', 'after', None, _('record a copy that has already occurred')),
2891 2892 ('f', 'force', None,
2892 2893 _('forcibly copy over an existing managed file')),
2893 2894 ] + walkopts + dryrunopts,
2894 2895 _('hg copy [OPTION]... [SOURCE]... DEST')),
2895 2896 "debugancestor": (debugancestor, [],
2896 2897 _('hg debugancestor [INDEX] REV1 REV2')),
2897 2898 "debugcheckstate": (debugcheckstate, [], _('hg debugcheckstate')),
2898 2899 "debugcomplete":
2899 2900 (debugcomplete,
2900 2901 [('o', 'options', None, _('show the command options'))],
2901 2902 _('hg debugcomplete [-o] CMD')),
2902 2903 "debugdate":
2903 2904 (debugdate,
2904 2905 [('e', 'extended', None, _('try extended date formats'))],
2905 2906 _('hg debugdate [-e] DATE [RANGE]')),
2906 2907 "debugdata": (debugdata, [], _('hg debugdata FILE REV')),
2907 2908 "debugfsinfo": (debugfsinfo, [], _('hg debugfsinfo [PATH]')),
2908 2909 "debugindex": (debugindex, [], _('hg debugindex FILE')),
2909 2910 "debugindexdot": (debugindexdot, [], _('hg debugindexdot FILE')),
2910 2911 "debuginstall": (debuginstall, [], _('hg debuginstall')),
2911 2912 "debugrawcommit|rawcommit":
2912 2913 (rawcommit,
2913 2914 [('p', 'parent', [], _('parent')),
2914 2915 ('F', 'files', '', _('file list'))
2915 2916 ] + commitopts + commitopts2,
2916 2917 _('hg debugrawcommit [OPTION]... [FILE]...')),
2917 2918 "debugrebuildstate":
2918 2919 (debugrebuildstate,
2919 2920 [('r', 'rev', '', _('revision to rebuild to'))],
2920 2921 _('hg debugrebuildstate [-r REV] [REV]')),
2921 2922 "debugrename":
2922 2923 (debugrename,
2923 2924 [('r', 'rev', '', _('revision to debug'))],
2924 2925 _('hg debugrename [-r REV] FILE')),
2925 2926 "debugsetparents":
2926 2927 (debugsetparents,
2927 2928 [],
2928 2929 _('hg debugsetparents REV1 [REV2]')),
2929 2930 "debugstate": (debugstate, [], _('hg debugstate')),
2930 2931 "debugwalk": (debugwalk, walkopts, _('hg debugwalk [OPTION]... [FILE]...')),
2931 2932 "^diff":
2932 2933 (diff,
2933 2934 [('r', 'rev', [], _('revision')),
2934 2935 ('a', 'text', None, _('treat all files as text')),
2935 2936 ('p', 'show-function', None,
2936 2937 _('show which function each change is in')),
2937 2938 ('g', 'git', None, _('use git extended diff format')),
2938 2939 ('', 'nodates', None, _("don't include dates in diff headers")),
2939 2940 ('w', 'ignore-all-space', None,
2940 2941 _('ignore white space when comparing lines')),
2941 2942 ('b', 'ignore-space-change', None,
2942 2943 _('ignore changes in the amount of white space')),
2943 2944 ('B', 'ignore-blank-lines', None,
2944 2945 _('ignore changes whose lines are all blank')),
2945 2946 ('U', 'unified', 3,
2946 2947 _('number of lines of context to show'))
2947 2948 ] + walkopts,
2948 2949 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2949 2950 "^export":
2950 2951 (export,
2951 2952 [('o', 'output', '', _('print output to file with formatted name')),
2952 2953 ('a', 'text', None, _('treat all files as text')),
2953 2954 ('g', 'git', None, _('use git extended diff format')),
2954 2955 ('', 'nodates', None, _("don't include dates in diff headers")),
2955 2956 ('', 'switch-parent', None, _('diff against the second parent'))],
2956 2957 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2957 2958 "grep":
2958 2959 (grep,
2959 2960 [('0', 'print0', None, _('end fields with NUL')),
2960 2961 ('', 'all', None, _('print all revisions that match')),
2961 2962 ('f', 'follow', None,
2962 2963 _('follow changeset history, or file history across copies and renames')),
2963 2964 ('i', 'ignore-case', None, _('ignore case when matching')),
2964 2965 ('l', 'files-with-matches', None,
2965 2966 _('print only filenames and revs that match')),
2966 2967 ('n', 'line-number', None, _('print matching line numbers')),
2967 2968 ('r', 'rev', [], _('search in given revision range')),
2968 2969 ('u', 'user', None, _('list the author (long with -v)')),
2969 2970 ('d', 'date', None, _('list the date (short with -q)')),
2970 2971 ] + walkopts,
2971 2972 _('hg grep [OPTION]... PATTERN [FILE]...')),
2972 2973 "heads":
2973 2974 (heads,
2974 2975 [('r', 'rev', '', _('show only heads which are descendants of rev')),
2975 2976 ] + templateopts,
2976 2977 _('hg heads [-r REV] [REV]...')),
2977 2978 "help": (help_, [], _('hg help [COMMAND]')),
2978 2979 "identify|id":
2979 2980 (identify,
2980 2981 [('r', 'rev', '', _('identify the specified rev')),
2981 2982 ('n', 'num', None, _('show local revision number')),
2982 2983 ('i', 'id', None, _('show global revision id')),
2983 2984 ('b', 'branch', None, _('show branch')),
2984 2985 ('t', 'tags', None, _('show tags'))],
2985 2986 _('hg identify [-nibt] [-r REV] [SOURCE]')),
2986 2987 "import|patch":
2987 2988 (import_,
2988 2989 [('p', 'strip', 1,
2989 2990 _('directory strip option for patch. This has the same\n'
2990 2991 'meaning as the corresponding patch option')),
2991 2992 ('b', 'base', '', _('base path')),
2992 2993 ('f', 'force', None,
2993 2994 _('skip check for outstanding uncommitted changes')),
2994 2995 ('', 'no-commit', None, _("don't commit, just update the working directory")),
2995 2996 ('', 'exact', None,
2996 2997 _('apply patch to the nodes from which it was generated')),
2997 2998 ('', 'import-branch', None,
2998 2999 _('Use any branch information in patch (implied by --exact)'))] +
2999 3000 commitopts + commitopts2,
3000 3001 _('hg import [OPTION]... PATCH...')),
3001 3002 "incoming|in":
3002 3003 (incoming,
3003 3004 [('f', 'force', None,
3004 3005 _('run even when remote repository is unrelated')),
3005 3006 ('n', 'newest-first', None, _('show newest record first')),
3006 3007 ('', 'bundle', '', _('file to store the bundles into')),
3007 3008 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
3008 3009 ] + logopts + remoteopts,
3009 3010 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
3010 3011 ' [--bundle FILENAME] [SOURCE]')),
3011 3012 "^init":
3012 3013 (init,
3013 3014 remoteopts,
3014 3015 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
3015 3016 "locate":
3016 3017 (locate,
3017 3018 [('r', 'rev', '', _('search the repository as it stood at rev')),
3018 3019 ('0', 'print0', None,
3019 3020 _('end filenames with NUL, for use with xargs')),
3020 3021 ('f', 'fullpath', None,
3021 3022 _('print complete paths from the filesystem root')),
3022 3023 ] + walkopts,
3023 3024 _('hg locate [OPTION]... [PATTERN]...')),
3024 3025 "^log|history":
3025 3026 (log,
3026 3027 [('f', 'follow', None,
3027 3028 _('follow changeset history, or file history across copies and renames')),
3028 3029 ('', 'follow-first', None,
3029 3030 _('only follow the first parent of merge changesets')),
3030 3031 ('d', 'date', '', _('show revs matching date spec')),
3031 3032 ('C', 'copies', None, _('show copied files')),
3032 3033 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3033 3034 ('r', 'rev', [], _('show the specified revision or range')),
3034 3035 ('', 'removed', None, _('include revs where files were removed')),
3035 3036 ('m', 'only-merges', None, _('show only merges')),
3036 3037 ('b', 'only-branch', [],
3037 3038 _('show only changesets within the given named branch')),
3038 3039 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3039 3040 ] + logopts + walkopts,
3040 3041 _('hg log [OPTION]... [FILE]')),
3041 3042 "manifest":
3042 3043 (manifest,
3043 3044 [('r', 'rev', '', _('revision to display'))],
3044 3045 _('hg manifest [-r REV]')),
3045 3046 "^merge":
3046 3047 (merge,
3047 3048 [('f', 'force', None, _('force a merge with outstanding changes')),
3048 3049 ('r', 'rev', '', _('revision to merge')),
3049 3050 ],
3050 3051 _('hg merge [-f] [[-r] REV]')),
3051 3052 "outgoing|out":
3052 3053 (outgoing,
3053 3054 [('f', 'force', None,
3054 3055 _('run even when remote repository is unrelated')),
3055 3056 ('r', 'rev', [], _('a specific revision you would like to push')),
3056 3057 ('n', 'newest-first', None, _('show newest record first')),
3057 3058 ] + logopts + remoteopts,
3058 3059 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3059 3060 "^parents":
3060 3061 (parents,
3061 3062 [('r', 'rev', '', _('show parents from the specified rev')),
3062 3063 ] + templateopts,
3063 3064 _('hg parents [-r REV] [FILE]')),
3064 3065 "paths": (paths, [], _('hg paths [NAME]')),
3065 3066 "^pull":
3066 3067 (pull,
3067 3068 [('u', 'update', None,
3068 3069 _('update to new tip if changesets were pulled')),
3069 3070 ('f', 'force', None,
3070 3071 _('run even when remote repository is unrelated')),
3071 3072 ('r', 'rev', [],
3072 3073 _('a specific revision up to which you would like to pull')),
3073 3074 ] + remoteopts,
3074 3075 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3075 3076 "^push":
3076 3077 (push,
3077 3078 [('f', 'force', None, _('force push')),
3078 3079 ('r', 'rev', [], _('a specific revision you would like to push')),
3079 3080 ] + remoteopts,
3080 3081 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3081 3082 "recover": (recover, [], _('hg recover')),
3082 3083 "^remove|rm":
3083 3084 (remove,
3084 3085 [('A', 'after', None, _('record remove without deleting')),
3085 3086 ('f', 'force', None, _('remove file even if modified')),
3086 3087 ] + walkopts,
3087 3088 _('hg remove [OPTION]... FILE...')),
3088 3089 "rename|mv":
3089 3090 (rename,
3090 3091 [('A', 'after', None, _('record a rename that has already occurred')),
3091 3092 ('f', 'force', None,
3092 3093 _('forcibly copy over an existing managed file')),
3093 3094 ] + walkopts + dryrunopts,
3094 3095 _('hg rename [OPTION]... SOURCE... DEST')),
3095 3096 "revert":
3096 3097 (revert,
3097 3098 [('a', 'all', None, _('revert all changes when no arguments given')),
3098 3099 ('d', 'date', '', _('tipmost revision matching date')),
3099 3100 ('r', 'rev', '', _('revision to revert to')),
3100 3101 ('', 'no-backup', None, _('do not save backup copies of files')),
3101 3102 ] + walkopts + dryrunopts,
3102 3103 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3103 3104 "rollback": (rollback, [], _('hg rollback')),
3104 3105 "root": (root, [], _('hg root')),
3105 3106 "^serve":
3106 3107 (serve,
3107 3108 [('A', 'accesslog', '', _('name of access log file to write to')),
3108 3109 ('d', 'daemon', None, _('run server in background')),
3109 3110 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3110 3111 ('E', 'errorlog', '', _('name of error log file to write to')),
3111 3112 ('p', 'port', 0, _('port to use (default: 8000)')),
3112 3113 ('a', 'address', '', _('address to use')),
3113 3114 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3114 3115 ('n', 'name', '',
3115 3116 _('name to show in web pages (default: working dir)')),
3116 3117 ('', 'webdir-conf', '', _('name of the webdir config file'
3117 3118 ' (serve more than one repo)')),
3118 3119 ('', 'pid-file', '', _('name of file to write process ID to')),
3119 3120 ('', 'stdio', None, _('for remote clients')),
3120 3121 ('t', 'templates', '', _('web templates to use')),
3121 3122 ('', 'style', '', _('template style to use')),
3122 3123 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3123 3124 ('', 'certificate', '', _('SSL certificate file'))],
3124 3125 _('hg serve [OPTION]...')),
3125 3126 "showconfig|debugconfig":
3126 3127 (showconfig,
3127 3128 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3128 3129 _('hg showconfig [-u] [NAME]...')),
3129 3130 "^status|st":
3130 3131 (status,
3131 3132 [('A', 'all', None, _('show status of all files')),
3132 3133 ('m', 'modified', None, _('show only modified files')),
3133 3134 ('a', 'added', None, _('show only added files')),
3134 3135 ('r', 'removed', None, _('show only removed files')),
3135 3136 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3136 3137 ('c', 'clean', None, _('show only files without changes')),
3137 3138 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3138 3139 ('i', 'ignored', None, _('show only ignored files')),
3139 3140 ('n', 'no-status', None, _('hide status prefix')),
3140 3141 ('C', 'copies', None, _('show source of copied files')),
3141 3142 ('0', 'print0', None,
3142 3143 _('end filenames with NUL, for use with xargs')),
3143 3144 ('', 'rev', [], _('show difference from revision')),
3144 3145 ] + walkopts,
3145 3146 _('hg status [OPTION]... [FILE]...')),
3146 3147 "tag":
3147 3148 (tag,
3148 3149 [('f', 'force', None, _('replace existing tag')),
3149 3150 ('l', 'local', None, _('make the tag local')),
3150 3151 ('r', 'rev', '', _('revision to tag')),
3151 3152 ('', 'remove', None, _('remove a tag')),
3152 3153 # -l/--local is already there, commitopts cannot be used
3153 3154 ('m', 'message', '', _('use <text> as commit message')),
3154 3155 ] + commitopts2,
3155 3156 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3156 3157 "tags": (tags, [], _('hg tags')),
3157 3158 "tip":
3158 3159 (tip,
3159 3160 [('p', 'patch', None, _('show patch')),
3160 3161 ] + templateopts,
3161 3162 _('hg tip [-p]')),
3162 3163 "unbundle":
3163 3164 (unbundle,
3164 3165 [('u', 'update', None,
3165 3166 _('update to new tip if changesets were unbundled'))],
3166 3167 _('hg unbundle [-u] FILE...')),
3167 3168 "^update|up|checkout|co":
3168 3169 (update,
3169 3170 [('C', 'clean', None, _('overwrite locally modified files')),
3170 3171 ('d', 'date', '', _('tipmost revision matching date')),
3171 3172 ('r', 'rev', '', _('revision'))],
3172 3173 _('hg update [-C] [-d DATE] [[-r] REV]')),
3173 3174 "verify": (verify, [], _('hg verify')),
3174 3175 "version": (version_, [], _('hg version')),
3175 3176 }
3176 3177
3177 3178 norepo = ("clone init version help debugcomplete debugdata"
3178 3179 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3179 3180 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,416 +1,417 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 from repo import RepoError
9 10 import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex, time
10 11 import util, commands, hg, lock, fancyopts, revlog, version, extensions, hook
11 12 import cmdutil
12 13 import ui as _ui
13 14
14 15 class ParseError(Exception):
15 16 """Exception raised on errors in parsing the command line."""
16 17
17 18 def run():
18 19 "run the command in sys.argv"
19 20 sys.exit(dispatch(sys.argv[1:]))
20 21
21 22 def dispatch(args):
22 23 "run the command specified in args"
23 24 try:
24 25 u = _ui.ui(traceback='--traceback' in args)
25 26 except util.Abort, inst:
26 27 sys.stderr.write(_("abort: %s\n") % inst)
27 28 return -1
28 29 return _runcatch(u, args)
29 30
30 31 def _runcatch(ui, args):
31 32 def catchterm(*args):
32 33 raise util.SignalInterrupt
33 34
34 35 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
35 36 num = getattr(signal, name, None)
36 37 if num: signal.signal(num, catchterm)
37 38
38 39 try:
39 40 try:
40 41 # enter the debugger before command execution
41 42 if '--debugger' in args:
42 43 pdb.set_trace()
43 44 try:
44 45 return _dispatch(ui, args)
45 46 finally:
46 47 ui.flush()
47 48 except:
48 49 # enter the debugger when we hit an exception
49 50 if '--debugger' in args:
50 51 pdb.post_mortem(sys.exc_info()[2])
51 52 ui.print_exc()
52 53 raise
53 54
54 55 except ParseError, inst:
55 56 if inst.args[0]:
56 57 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
57 58 commands.help_(ui, inst.args[0])
58 59 else:
59 60 ui.warn(_("hg: %s\n") % inst.args[1])
60 61 commands.help_(ui, 'shortlist')
61 62 except cmdutil.AmbiguousCommand, inst:
62 63 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
63 64 (inst.args[0], " ".join(inst.args[1])))
64 65 except cmdutil.UnknownCommand, inst:
65 66 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
66 67 commands.help_(ui, 'shortlist')
67 except hg.RepoError, inst:
68 except RepoError, inst:
68 69 ui.warn(_("abort: %s!\n") % inst)
69 70 except lock.LockHeld, inst:
70 71 if inst.errno == errno.ETIMEDOUT:
71 72 reason = _('timed out waiting for lock held by %s') % inst.locker
72 73 else:
73 74 reason = _('lock held by %s') % inst.locker
74 75 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
75 76 except lock.LockUnavailable, inst:
76 77 ui.warn(_("abort: could not lock %s: %s\n") %
77 78 (inst.desc or inst.filename, inst.strerror))
78 79 except revlog.RevlogError, inst:
79 80 ui.warn(_("abort: %s!\n") % inst)
80 81 except util.SignalInterrupt:
81 82 ui.warn(_("killed!\n"))
82 83 except KeyboardInterrupt:
83 84 try:
84 85 ui.warn(_("interrupted!\n"))
85 86 except IOError, inst:
86 87 if inst.errno == errno.EPIPE:
87 88 if ui.debugflag:
88 89 ui.warn(_("\nbroken pipe\n"))
89 90 else:
90 91 raise
91 92 except socket.error, inst:
92 93 ui.warn(_("abort: %s\n") % inst[1])
93 94 except IOError, inst:
94 95 if hasattr(inst, "code"):
95 96 ui.warn(_("abort: %s\n") % inst)
96 97 elif hasattr(inst, "reason"):
97 98 try: # usually it is in the form (errno, strerror)
98 99 reason = inst.reason.args[1]
99 100 except: # it might be anything, for example a string
100 101 reason = inst.reason
101 102 ui.warn(_("abort: error: %s\n") % reason)
102 103 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
103 104 if ui.debugflag:
104 105 ui.warn(_("broken pipe\n"))
105 106 elif getattr(inst, "strerror", None):
106 107 if getattr(inst, "filename", None):
107 108 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
108 109 else:
109 110 ui.warn(_("abort: %s\n") % inst.strerror)
110 111 else:
111 112 raise
112 113 except OSError, inst:
113 114 if getattr(inst, "filename", None):
114 115 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
115 116 else:
116 117 ui.warn(_("abort: %s\n") % inst.strerror)
117 118 except util.UnexpectedOutput, inst:
118 119 ui.warn(_("abort: %s") % inst[0])
119 120 if not isinstance(inst[1], basestring):
120 121 ui.warn(" %r\n" % (inst[1],))
121 122 elif not inst[1]:
122 123 ui.warn(_(" empty string\n"))
123 124 else:
124 125 ui.warn("\n%r\n" % util.ellipsis(inst[1]))
125 126 except ImportError, inst:
126 127 m = str(inst).split()[-1]
127 128 ui.warn(_("abort: could not import module %s!\n") % m)
128 129 if m in "mpatch bdiff".split():
129 130 ui.warn(_("(did you forget to compile extensions?)\n"))
130 131 elif m in "zlib".split():
131 132 ui.warn(_("(is your Python install correct?)\n"))
132 133
133 134 except util.Abort, inst:
134 135 ui.warn(_("abort: %s\n") % inst)
135 136 except MemoryError:
136 137 ui.warn(_("abort: out of memory\n"))
137 138 except SystemExit, inst:
138 139 # Commands shouldn't sys.exit directly, but give a return code.
139 140 # Just in case catch this and and pass exit code to caller.
140 141 return inst.code
141 142 except:
142 143 ui.warn(_("** unknown exception encountered, details follow\n"))
143 144 ui.warn(_("** report bug details to "
144 145 "http://www.selenic.com/mercurial/bts\n"))
145 146 ui.warn(_("** or mercurial@selenic.com\n"))
146 147 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
147 148 % version.get_version())
148 149 raise
149 150
150 151 return -1
151 152
152 153 def _findrepo(p):
153 154 while not os.path.isdir(os.path.join(p, ".hg")):
154 155 oldp, p = p, os.path.dirname(p)
155 156 if p == oldp:
156 157 return None
157 158
158 159 return p
159 160
160 161 def _parse(ui, args):
161 162 options = {}
162 163 cmdoptions = {}
163 164
164 165 try:
165 166 args = fancyopts.fancyopts(args, commands.globalopts, options)
166 167 except fancyopts.getopt.GetoptError, inst:
167 168 raise ParseError(None, inst)
168 169
169 170 if args:
170 171 cmd, args = args[0], args[1:]
171 172 aliases, i = cmdutil.findcmd(ui, cmd, commands.table)
172 173 cmd = aliases[0]
173 174 defaults = ui.config("defaults", cmd)
174 175 if defaults:
175 176 args = shlex.split(defaults) + args
176 177 c = list(i[1])
177 178 else:
178 179 cmd = None
179 180 c = []
180 181
181 182 # combine global options into local
182 183 for o in commands.globalopts:
183 184 c.append((o[0], o[1], options[o[1]], o[3]))
184 185
185 186 try:
186 187 args = fancyopts.fancyopts(args, c, cmdoptions)
187 188 except fancyopts.getopt.GetoptError, inst:
188 189 raise ParseError(cmd, inst)
189 190
190 191 # separate global options back out
191 192 for o in commands.globalopts:
192 193 n = o[1]
193 194 options[n] = cmdoptions[n]
194 195 del cmdoptions[n]
195 196
196 197 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
197 198
198 199 def _parseconfig(config):
199 200 """parse the --config options from the command line"""
200 201 parsed = []
201 202 for cfg in config:
202 203 try:
203 204 name, value = cfg.split('=', 1)
204 205 section, name = name.split('.', 1)
205 206 if not section or not name:
206 207 raise IndexError
207 208 parsed.append((section, name, value))
208 209 except (IndexError, ValueError):
209 210 raise util.Abort(_('malformed --config option: %s') % cfg)
210 211 return parsed
211 212
212 213 def _earlygetopt(aliases, args):
213 214 """Return list of values for an option (or aliases).
214 215
215 216 The values are listed in the order they appear in args.
216 217 The options and values are removed from args.
217 218 """
218 219 try:
219 220 argcount = args.index("--")
220 221 except ValueError:
221 222 argcount = len(args)
222 223 shortopts = [opt for opt in aliases if len(opt) == 2]
223 224 values = []
224 225 pos = 0
225 226 while pos < argcount:
226 227 if args[pos] in aliases:
227 228 if pos + 1 >= argcount:
228 229 # ignore and let getopt report an error if there is no value
229 230 break
230 231 del args[pos]
231 232 values.append(args.pop(pos))
232 233 argcount -= 2
233 234 elif args[pos][:2] in shortopts:
234 235 # short option can have no following space, e.g. hg log -Rfoo
235 236 values.append(args.pop(pos)[2:])
236 237 argcount -= 1
237 238 else:
238 239 pos += 1
239 240 return values
240 241
241 242 _loaded = {}
242 243 def _dispatch(ui, args):
243 244 # read --config before doing anything else
244 245 # (e.g. to change trust settings for reading .hg/hgrc)
245 246 config = _earlygetopt(['--config'], args)
246 247 if config:
247 248 ui.updateopts(config=_parseconfig(config))
248 249
249 250 # check for cwd
250 251 cwd = _earlygetopt(['--cwd'], args)
251 252 if cwd:
252 253 os.chdir(cwd[-1])
253 254
254 255 # read the local repository .hgrc into a local ui object
255 256 path = _findrepo(os.getcwd()) or ""
256 257 if not path:
257 258 lui = ui
258 259 if path:
259 260 try:
260 261 lui = _ui.ui(parentui=ui)
261 262 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
262 263 except IOError:
263 264 pass
264 265
265 266 # now we can expand paths, even ones in .hg/hgrc
266 267 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
267 268 if rpath:
268 269 path = lui.expandpath(rpath[-1])
269 270 lui = _ui.ui(parentui=ui)
270 271 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
271 272
272 273 extensions.loadall(lui)
273 274 for name, module in extensions.extensions():
274 275 if name in _loaded:
275 276 continue
276 277
277 278 # setup extensions
278 279 # TODO this should be generalized to scheme, where extensions can
279 280 # redepend on other extensions. then we should toposort them, and
280 281 # do initialization in correct order
281 282 extsetup = getattr(module, 'extsetup', None)
282 283 if extsetup:
283 284 extsetup()
284 285
285 286 cmdtable = getattr(module, 'cmdtable', {})
286 287 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
287 288 if overrides:
288 289 ui.warn(_("extension '%s' overrides commands: %s\n")
289 290 % (name, " ".join(overrides)))
290 291 commands.table.update(cmdtable)
291 292 _loaded[name] = 1
292 293 # check for fallback encoding
293 294 fallback = lui.config('ui', 'fallbackencoding')
294 295 if fallback:
295 296 util._fallbackencoding = fallback
296 297
297 298 fullargs = args
298 299 cmd, func, args, options, cmdoptions = _parse(lui, args)
299 300
300 301 if options["config"]:
301 302 raise util.Abort(_("Option --config may not be abbreviated!"))
302 303 if options["cwd"]:
303 304 raise util.Abort(_("Option --cwd may not be abbreviated!"))
304 305 if options["repository"]:
305 306 raise util.Abort(_(
306 307 "Option -R has to be separated from other options (i.e. not -qR) "
307 308 "and --repository may only be abbreviated as --repo!"))
308 309
309 310 if options["encoding"]:
310 311 util._encoding = options["encoding"]
311 312 if options["encodingmode"]:
312 313 util._encodingmode = options["encodingmode"]
313 314 if options["time"]:
314 315 def get_times():
315 316 t = os.times()
316 317 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
317 318 t = (t[0], t[1], t[2], t[3], time.clock())
318 319 return t
319 320 s = get_times()
320 321 def print_time():
321 322 t = get_times()
322 323 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
323 324 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
324 325 atexit.register(print_time)
325 326
326 327 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
327 328 not options["noninteractive"], options["traceback"])
328 329
329 330 if options['help']:
330 331 return commands.help_(ui, cmd, options['version'])
331 332 elif options['version']:
332 333 return commands.version_(ui)
333 334 elif not cmd:
334 335 return commands.help_(ui, 'shortlist')
335 336
336 337 repo = None
337 338 if cmd not in commands.norepo.split():
338 339 try:
339 340 repo = hg.repository(ui, path=path)
340 341 ui = repo.ui
341 342 if not repo.local():
342 343 raise util.Abort(_("repository '%s' is not local") % path)
343 344 ui.setconfig("bundle", "mainreporoot", repo.root)
344 except hg.RepoError:
345 except RepoError:
345 346 if cmd not in commands.optionalrepo.split():
346 347 if args and not path: # try to infer -R from command args
347 348 repos = map(_findrepo, args)
348 349 guess = repos[0]
349 350 if guess and repos.count(guess) == len(repos):
350 351 return _dispatch(ui, ['--repository', guess] + fullargs)
351 352 if not path:
352 raise hg.RepoError(_("There is no Mercurial repository here"
353 " (.hg not found)"))
353 raise RepoError(_("There is no Mercurial repository here"
354 " (.hg not found)"))
354 355 raise
355 356 d = lambda: func(ui, repo, *args, **cmdoptions)
356 357 else:
357 358 d = lambda: func(ui, *args, **cmdoptions)
358 359
359 360 # run pre-hook, and abort if it fails
360 361 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
361 362 if ret:
362 363 return ret
363 364 ret = _runcommand(ui, options, cmd, d)
364 365 # run post-hook, passing command result
365 366 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
366 367 result = ret)
367 368 return ret
368 369
369 370 def _runcommand(ui, options, cmd, cmdfunc):
370 371 def checkargs():
371 372 try:
372 373 return cmdfunc()
373 374 except TypeError, inst:
374 375 # was this an argument error?
375 376 tb = traceback.extract_tb(sys.exc_info()[2])
376 377 if len(tb) != 2: # no
377 378 raise
378 379 raise ParseError(cmd, _("invalid arguments"))
379 380
380 381 if options['profile']:
381 382 import hotshot, hotshot.stats
382 383 prof = hotshot.Profile("hg.prof")
383 384 try:
384 385 try:
385 386 return prof.runcall(checkargs)
386 387 except:
387 388 try:
388 389 ui.warn(_('exception raised - generating '
389 390 'profile anyway\n'))
390 391 except:
391 392 pass
392 393 raise
393 394 finally:
394 395 prof.close()
395 396 stats = hotshot.stats.load("hg.prof")
396 397 stats.strip_dirs()
397 398 stats.sort_stats('time', 'calls')
398 399 stats.print_stats(40)
399 400 elif options['lsprof']:
400 401 try:
401 402 from mercurial import lsprof
402 403 except ImportError:
403 404 raise util.Abort(_(
404 405 'lsprof not available - install from '
405 406 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
406 407 p = lsprof.Profiler()
407 408 p.enable(subcalls=True)
408 409 try:
409 410 return checkargs()
410 411 finally:
411 412 p.disable()
412 413 stats = lsprof.Stats(p.getstats())
413 414 stats.sort()
414 415 stats.pprint(top=10, file=sys.stderr, climit=5)
415 416 else:
416 417 return checkargs()
@@ -1,105 +1,106 b''
1 1 # changelog bisection for mercurial
2 2 #
3 3 # Copyright 2007 Matt Mackall
4 4 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
5 5 # Inspired by git bisect, extension skeleton taken from mq.py.
6 6 #
7 7 # This software may be used and distributed according to the terms
8 8 # of the GNU General Public License, incorporated herein by reference.
9 9
10 10 from i18n import _
11 import hg, util
11 from node import short
12 import util
12 13
13 14 def bisect(changelog, state):
14 15 clparents = changelog.parentrevs
15 16 skip = dict.fromkeys([changelog.rev(n) for n in state['skip']])
16 17
17 18 def buildancestors(bad, good):
18 19 # only the earliest bad revision matters
19 20 badrev = min([changelog.rev(n) for n in bad])
20 21 goodrevs = [changelog.rev(n) for n in good]
21 22 # build ancestors array
22 23 ancestors = [[]] * (changelog.count() + 1) # an extra for [-1]
23 24
24 25 # clear good revs from array
25 26 for node in goodrevs:
26 27 ancestors[node] = None
27 28 for rev in xrange(changelog.count(), -1, -1):
28 29 if ancestors[rev] is None:
29 30 for prev in clparents(rev):
30 31 ancestors[prev] = None
31 32
32 33 if ancestors[badrev] is None:
33 34 return badrev, None
34 35 return badrev, ancestors
35 36
36 37 good = 0
37 38 badrev, ancestors = buildancestors(state['bad'], state['good'])
38 39 if not ancestors: # looking for bad to good transition?
39 40 good = 1
40 41 badrev, ancestors = buildancestors(state['good'], state['bad'])
41 42 bad = changelog.node(badrev)
42 43 if not ancestors: # now we're confused
43 44 raise util.Abort(_("Inconsistent state, %s:%s is good and bad")
44 % (badrev, hg.short(bad)))
45 % (badrev, short(bad)))
45 46
46 47 # build children dict
47 48 children = {}
48 49 visit = [badrev]
49 50 candidates = []
50 51 while visit:
51 52 rev = visit.pop(0)
52 53 if ancestors[rev] == []:
53 54 candidates.append(rev)
54 55 for prev in clparents(rev):
55 56 if prev != -1:
56 57 if prev in children:
57 58 children[prev].append(rev)
58 59 else:
59 60 children[prev] = [rev]
60 61 visit.append(prev)
61 62
62 63 candidates.sort()
63 64 # have we narrowed it down to one entry?
64 65 tot = len(candidates)
65 66 if tot == 1:
66 67 return (bad, 0, good)
67 68 perfect = tot / 2
68 69
69 70 # find the best node to test
70 71 best_rev = None
71 72 best_len = -1
72 73 poison = {}
73 74 for rev in candidates:
74 75 if rev in poison:
75 76 for c in children.get(rev, []):
76 77 poison[c] = True # poison children
77 78 continue
78 79
79 80 a = ancestors[rev] or [rev]
80 81 ancestors[rev] = None
81 82
82 83 x = len(a) # number of ancestors
83 84 y = tot - x # number of non-ancestors
84 85 value = min(x, y) # how good is this test?
85 86 if value > best_len and rev not in skip:
86 87 best_len = value
87 88 best_rev = rev
88 89 if value == perfect: # found a perfect candidate? quit early
89 90 break
90 91
91 92 if y < perfect: # all downhill from here?
92 93 for c in children.get(rev, []):
93 94 poison[c] = True # poison children
94 95 continue
95 96
96 97 for c in children.get(rev, []):
97 98 if ancestors[c]:
98 99 ancestors[c] = dict.fromkeys(ancestors[c] + a).keys()
99 100 else:
100 101 ancestors[c] = a + [c]
101 102
102 103 assert best_rev is not None
103 104 best_node = changelog.node(best_rev)
104 105
105 106 return (best_node, tot, good)
@@ -1,313 +1,311 b''
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 from node import bin, hex, nullid, nullrev, short
10 from repo import NoCapability, RepoError
11 9 from i18n import _
12 10 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
13 11 import errno, lock, os, shutil, util, extensions
14 12 import merge as _merge
15 13 import verify as _verify
16 14
17 15 def _local(path):
18 16 return (os.path.isfile(util.drop_scheme('file', path)) and
19 17 bundlerepo or localrepo)
20 18
21 19 def parseurl(url, revs):
22 20 '''parse url#branch, returning url, branch + revs'''
23 21
24 22 if '#' not in url:
25 23 return url, (revs or None), None
26 24
27 25 url, rev = url.split('#', 1)
28 26 return url, revs + [rev], rev
29 27
30 28 schemes = {
31 29 'bundle': bundlerepo,
32 30 'file': _local,
33 31 'http': httprepo,
34 32 'https': httprepo,
35 33 'ssh': sshrepo,
36 34 'static-http': statichttprepo,
37 35 }
38 36
39 37 def _lookup(path):
40 38 scheme = 'file'
41 39 if path:
42 40 c = path.find(':')
43 41 if c > 0:
44 42 scheme = path[:c]
45 43 thing = schemes.get(scheme) or schemes['file']
46 44 try:
47 45 return thing(path)
48 46 except TypeError:
49 47 return thing
50 48
51 49 def islocal(repo):
52 50 '''return true if repo or path is local'''
53 51 if isinstance(repo, str):
54 52 try:
55 53 return _lookup(repo).islocal(repo)
56 54 except AttributeError:
57 55 return False
58 56 return repo.local()
59 57
60 58 def repository(ui, path='', create=False):
61 59 """return a repository object for the specified path"""
62 60 repo = _lookup(path).instance(ui, path, create)
63 61 ui = getattr(repo, "ui", ui)
64 62 for name, module in extensions.extensions():
65 63 hook = getattr(module, 'reposetup', None)
66 64 if hook:
67 65 hook(ui, repo)
68 66 return repo
69 67
70 68 def defaultdest(source):
71 69 '''return default destination of clone if none is given'''
72 70 return os.path.basename(os.path.normpath(source))
73 71
74 72 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
75 73 stream=False):
76 74 """Make a copy of an existing repository.
77 75
78 76 Create a copy of an existing repository in a new directory. The
79 77 source and destination are URLs, as passed to the repository
80 78 function. Returns a pair of repository objects, the source and
81 79 newly created destination.
82 80
83 81 The location of the source is added to the new repository's
84 82 .hg/hgrc file, as the default to be used for future pulls and
85 83 pushes.
86 84
87 85 If an exception is raised, the partly cloned/updated destination
88 86 repository will be deleted.
89 87
90 88 Arguments:
91 89
92 90 source: repository object or URL
93 91
94 92 dest: URL of destination repository to create (defaults to base
95 93 name of source repository)
96 94
97 95 pull: always pull from source repository, even in local case
98 96
99 97 stream: stream raw data uncompressed from repository (fast over
100 98 LAN, slow over WAN)
101 99
102 100 rev: revision to clone up to (implies pull=True)
103 101
104 102 update: update working directory after clone completes, if
105 103 destination is local repository
106 104 """
107 105
108 106 if isinstance(source, str):
109 107 origsource = ui.expandpath(source)
110 108 source, rev, checkout = parseurl(origsource, rev)
111 109 src_repo = repository(ui, source)
112 110 else:
113 111 src_repo = source
114 112 origsource = source = src_repo.url()
115 113 checkout = None
116 114
117 115 if dest is None:
118 116 dest = defaultdest(source)
119 117 ui.status(_("destination directory: %s\n") % dest)
120 118
121 119 def localpath(path):
122 120 if path.startswith('file://localhost/'):
123 121 return path[16:]
124 122 if path.startswith('file://'):
125 123 return path[7:]
126 124 if path.startswith('file:'):
127 125 return path[5:]
128 126 return path
129 127
130 128 dest = localpath(dest)
131 129 source = localpath(source)
132 130
133 131 if os.path.exists(dest):
134 132 raise util.Abort(_("destination '%s' already exists") % dest)
135 133
136 134 class DirCleanup(object):
137 135 def __init__(self, dir_):
138 136 self.rmtree = shutil.rmtree
139 137 self.dir_ = dir_
140 138 def close(self):
141 139 self.dir_ = None
142 140 def __del__(self):
143 141 if self.dir_:
144 142 self.rmtree(self.dir_, True)
145 143
146 144 src_lock = dest_lock = dir_cleanup = None
147 145 try:
148 146 if islocal(dest):
149 147 dir_cleanup = DirCleanup(dest)
150 148
151 149 abspath = origsource
152 150 copy = False
153 151 if src_repo.local() and islocal(dest):
154 152 abspath = os.path.abspath(util.drop_scheme('file', origsource))
155 153 copy = not pull and not rev
156 154
157 155 if copy:
158 156 try:
159 157 # we use a lock here because if we race with commit, we
160 158 # can end up with extra data in the cloned revlogs that's
161 159 # not pointed to by changesets, thus causing verify to
162 160 # fail
163 161 src_lock = src_repo.lock()
164 162 except lock.LockException:
165 163 copy = False
166 164
167 165 if copy:
168 166 def force_copy(src, dst):
169 167 if not os.path.exists(src):
170 168 # Tolerate empty source repository and optional files
171 169 return
172 170 util.copyfiles(src, dst)
173 171
174 172 src_store = os.path.realpath(src_repo.spath)
175 173 if not os.path.exists(dest):
176 174 os.mkdir(dest)
177 175 try:
178 176 dest_path = os.path.realpath(os.path.join(dest, ".hg"))
179 177 os.mkdir(dest_path)
180 178 except OSError, inst:
181 179 if inst.errno == errno.EEXIST:
182 180 dir_cleanup.close()
183 181 raise util.Abort(_("destination '%s' already exists")
184 182 % dest)
185 183 raise
186 184 if src_repo.spath != src_repo.path:
187 185 # XXX racy
188 186 dummy_changelog = os.path.join(dest_path, "00changelog.i")
189 187 # copy the dummy changelog
190 188 force_copy(src_repo.join("00changelog.i"), dummy_changelog)
191 189 dest_store = os.path.join(dest_path, "store")
192 190 os.mkdir(dest_store)
193 191 else:
194 192 dest_store = dest_path
195 193 # copy the requires file
196 194 force_copy(src_repo.join("requires"),
197 195 os.path.join(dest_path, "requires"))
198 196 # we lock here to avoid premature writing to the target
199 197 dest_lock = lock.lock(os.path.join(dest_store, "lock"))
200 198
201 199 files = ("data",
202 200 "00manifest.d", "00manifest.i",
203 201 "00changelog.d", "00changelog.i")
204 202 for f in files:
205 203 src = os.path.join(src_store, f)
206 204 dst = os.path.join(dest_store, f)
207 205 force_copy(src, dst)
208 206
209 207 # we need to re-init the repo after manually copying the data
210 208 # into it
211 209 dest_repo = repository(ui, dest)
212 210
213 211 else:
214 212 try:
215 213 dest_repo = repository(ui, dest, create=True)
216 214 except OSError, inst:
217 215 if inst.errno == errno.EEXIST:
218 216 dir_cleanup.close()
219 217 raise util.Abort(_("destination '%s' already exists")
220 218 % dest)
221 219 raise
222 220
223 221 revs = None
224 222 if rev:
225 223 if 'lookup' not in src_repo.capabilities:
226 224 raise util.Abort(_("src repository does not support revision "
227 225 "lookup and so doesn't support clone by "
228 226 "revision"))
229 227 revs = [src_repo.lookup(r) for r in rev]
230 228
231 229 if dest_repo.local():
232 230 dest_repo.clone(src_repo, heads=revs, stream=stream)
233 231 elif src_repo.local():
234 232 src_repo.push(dest_repo, revs=revs)
235 233 else:
236 234 raise util.Abort(_("clone from remote to remote not supported"))
237 235
238 236 if dir_cleanup:
239 237 dir_cleanup.close()
240 238
241 239 if dest_repo.local():
242 240 fp = dest_repo.opener("hgrc", "w", text=True)
243 241 fp.write("[paths]\n")
244 242 fp.write("default = %s\n" % abspath)
245 243 fp.close()
246 244
247 245 if update:
248 246 if not checkout:
249 247 try:
250 248 checkout = dest_repo.lookup("default")
251 249 except:
252 250 checkout = dest_repo.changelog.tip()
253 251 _update(dest_repo, checkout)
254 252
255 253 return src_repo, dest_repo
256 254 finally:
257 255 del src_lock, dest_lock, dir_cleanup
258 256
259 257 def _showstats(repo, stats):
260 258 stats = ((stats[0], _("updated")),
261 259 (stats[1], _("merged")),
262 260 (stats[2], _("removed")),
263 261 (stats[3], _("unresolved")))
264 262 note = ", ".join([_("%d files %s") % s for s in stats])
265 263 repo.ui.status("%s\n" % note)
266 264
267 265 def _update(repo, node): return update(repo, node)
268 266
269 267 def update(repo, node):
270 268 """update the working directory to node, merging linear changes"""
271 269 pl = repo.parents()
272 270 stats = _merge.update(repo, node, False, False, None)
273 271 _showstats(repo, stats)
274 272 if stats[3]:
275 273 repo.ui.status(_("There are unresolved merges with"
276 274 " locally modified files.\n"))
277 275 if stats[1]:
278 276 repo.ui.status(_("You can finish the partial merge using:\n"))
279 277 else:
280 278 repo.ui.status(_("You can redo the full merge using:\n"))
281 279 # len(pl)==1, otherwise _merge.update() would have raised util.Abort:
282 280 repo.ui.status(_(" hg update %s\n hg update %s\n")
283 281 % (pl[0].rev(), repo.changectx(node).rev()))
284 282 return stats[3] > 0
285 283
286 284 def clean(repo, node, show_stats=True):
287 285 """forcibly switch the working directory to node, clobbering changes"""
288 286 stats = _merge.update(repo, node, False, True, None)
289 287 if show_stats: _showstats(repo, stats)
290 288 return stats[3] > 0
291 289
292 290 def merge(repo, node, force=None, remind=True):
293 291 """branch merge with node, resolving changes"""
294 292 stats = _merge.update(repo, node, True, force, False)
295 293 _showstats(repo, stats)
296 294 if stats[3]:
297 295 pl = repo.parents()
298 296 repo.ui.status(_("There are unresolved merges,"
299 297 " you can redo the full merge using:\n"
300 298 " hg update -C %s\n"
301 299 " hg merge %s\n")
302 300 % (pl[0].rev(), pl[1].rev()))
303 301 elif remind:
304 302 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
305 303 return stats[3] > 0
306 304
307 305 def revert(repo, node, choose):
308 306 """revert changes to revision in node without updating dirstate"""
309 307 return _merge.update(repo, node, False, True, choose)[3] > 0
310 308
311 309 def verify(repo):
312 310 """verify the consistency of a repository"""
313 311 return _verify.verify(repo)
@@ -1,951 +1,952 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os, mimetypes, re
10 10 from mercurial.node import hex, nullid, short
11 from mercurial.repo import RepoError
11 12 from mercurial import mdiff, ui, hg, util, archival, patch, hook
12 13 from mercurial import revlog, templater, templatefilters, changegroup
13 14 from common import get_mtime, style_map, paritygen, countgen, get_contact
14 15 from common import ErrorResponse
15 16 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 17 from request import wsgirequest
17 18 import webcommands, protocol
18 19
19 20 shortcuts = {
20 21 'cl': [('cmd', ['changelog']), ('rev', None)],
21 22 'sl': [('cmd', ['shortlog']), ('rev', None)],
22 23 'cs': [('cmd', ['changeset']), ('node', None)],
23 24 'f': [('cmd', ['file']), ('filenode', None)],
24 25 'fl': [('cmd', ['filelog']), ('filenode', None)],
25 26 'fd': [('cmd', ['filediff']), ('node', None)],
26 27 'fa': [('cmd', ['annotate']), ('filenode', None)],
27 28 'mf': [('cmd', ['manifest']), ('manifest', None)],
28 29 'ca': [('cmd', ['archive']), ('node', None)],
29 30 'tags': [('cmd', ['tags'])],
30 31 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
31 32 'static': [('cmd', ['static']), ('file', None)]
32 33 }
33 34
34 35 def _up(p):
35 36 if p[0] != "/":
36 37 p = "/" + p
37 38 if p[-1] == "/":
38 39 p = p[:-1]
39 40 up = os.path.dirname(p)
40 41 if up == "/":
41 42 return "/"
42 43 return up + "/"
43 44
44 45 def revnavgen(pos, pagelen, limit, nodefunc):
45 46 def seq(factor, limit=None):
46 47 if limit:
47 48 yield limit
48 49 if limit >= 20 and limit <= 40:
49 50 yield 50
50 51 else:
51 52 yield 1 * factor
52 53 yield 3 * factor
53 54 for f in seq(factor * 10):
54 55 yield f
55 56
56 57 def nav(**map):
57 58 l = []
58 59 last = 0
59 60 for f in seq(1, pagelen):
60 61 if f < pagelen or f <= last:
61 62 continue
62 63 if f > limit:
63 64 break
64 65 last = f
65 66 if pos + f < limit:
66 67 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
67 68 if pos - f >= 0:
68 69 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
69 70
70 71 try:
71 72 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
72 73
73 74 for label, node in l:
74 75 yield {"label": label, "node": node}
75 76
76 77 yield {"label": "tip", "node": "tip"}
77 except hg.RepoError:
78 except RepoError:
78 79 pass
79 80
80 81 return nav
81 82
82 83 class hgweb(object):
83 84 def __init__(self, repo, name=None):
84 85 if isinstance(repo, str):
85 86 parentui = ui.ui(report_untrusted=False, interactive=False)
86 87 self.repo = hg.repository(parentui, repo)
87 88 else:
88 89 self.repo = repo
89 90
90 91 hook.redirect(True)
91 92 self.mtime = -1
92 93 self.reponame = name
93 94 self.archives = 'zip', 'gz', 'bz2'
94 95 self.stripecount = 1
95 96 self._capabilities = None
96 97 # a repo owner may set web.templates in .hg/hgrc to get any file
97 98 # readable by the user running the CGI script
98 99 self.templatepath = self.config("web", "templates",
99 100 templater.templatepath(),
100 101 untrusted=False)
101 102
102 103 # The CGI scripts are often run by a user different from the repo owner.
103 104 # Trust the settings from the .hg/hgrc files by default.
104 105 def config(self, section, name, default=None, untrusted=True):
105 106 return self.repo.ui.config(section, name, default,
106 107 untrusted=untrusted)
107 108
108 109 def configbool(self, section, name, default=False, untrusted=True):
109 110 return self.repo.ui.configbool(section, name, default,
110 111 untrusted=untrusted)
111 112
112 113 def configlist(self, section, name, default=None, untrusted=True):
113 114 return self.repo.ui.configlist(section, name, default,
114 115 untrusted=untrusted)
115 116
116 117 def refresh(self):
117 118 mtime = get_mtime(self.repo.root)
118 119 if mtime != self.mtime:
119 120 self.mtime = mtime
120 121 self.repo = hg.repository(self.repo.ui, self.repo.root)
121 122 self.maxchanges = int(self.config("web", "maxchanges", 10))
122 123 self.stripecount = int(self.config("web", "stripes", 1))
123 124 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
124 125 self.maxfiles = int(self.config("web", "maxfiles", 10))
125 126 self.allowpull = self.configbool("web", "allowpull", True)
126 127 self.encoding = self.config("web", "encoding", util._encoding)
127 128 self._capabilities = None
128 129
129 130 def capabilities(self):
130 131 if self._capabilities is not None:
131 132 return self._capabilities
132 133 caps = ['lookup', 'changegroupsubset']
133 134 if self.configbool('server', 'uncompressed'):
134 135 caps.append('stream=%d' % self.repo.changelog.version)
135 136 if changegroup.bundlepriority:
136 137 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
137 138 self._capabilities = caps
138 139 return caps
139 140
140 141 def run(self):
141 142 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
142 143 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
143 144 import mercurial.hgweb.wsgicgi as wsgicgi
144 145 wsgicgi.launch(self)
145 146
146 147 def __call__(self, env, respond):
147 148 req = wsgirequest(env, respond)
148 149 self.run_wsgi(req)
149 150 return req
150 151
151 152 def run_wsgi(self, req):
152 153
153 154 self.refresh()
154 155
155 156 # expand form shortcuts
156 157
157 158 for k in shortcuts.iterkeys():
158 159 if k in req.form:
159 160 for name, value in shortcuts[k]:
160 161 if value is None:
161 162 value = req.form[k]
162 163 req.form[name] = value
163 164 del req.form[k]
164 165
165 166 # work with CGI variables to create coherent structure
166 167 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
167 168
168 169 req.url = req.env['SCRIPT_NAME']
169 170 if not req.url.endswith('/'):
170 171 req.url += '/'
171 172 if 'REPO_NAME' in req.env:
172 173 req.url += req.env['REPO_NAME'] + '/'
173 174
174 175 if req.env.get('PATH_INFO'):
175 176 parts = req.env.get('PATH_INFO').strip('/').split('/')
176 177 repo_parts = req.env.get('REPO_NAME', '').split('/')
177 178 if parts[:len(repo_parts)] == repo_parts:
178 179 parts = parts[len(repo_parts):]
179 180 query = '/'.join(parts)
180 181 else:
181 182 query = req.env['QUERY_STRING'].split('&', 1)[0]
182 183 query = query.split(';', 1)[0]
183 184
184 185 # translate user-visible url structure to internal structure
185 186
186 187 args = query.split('/', 2)
187 188 if 'cmd' not in req.form and args and args[0]:
188 189
189 190 cmd = args.pop(0)
190 191 style = cmd.rfind('-')
191 192 if style != -1:
192 193 req.form['style'] = [cmd[:style]]
193 194 cmd = cmd[style+1:]
194 195
195 196 # avoid accepting e.g. style parameter as command
196 197 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
197 198 req.form['cmd'] = [cmd]
198 199
199 200 if args and args[0]:
200 201 node = args.pop(0)
201 202 req.form['node'] = [node]
202 203 if args:
203 204 req.form['file'] = args
204 205
205 206 if cmd == 'static':
206 207 req.form['file'] = req.form['node']
207 208 elif cmd == 'archive':
208 209 fn = req.form['node'][0]
209 210 for type_, spec in self.archive_specs.iteritems():
210 211 ext = spec[2]
211 212 if fn.endswith(ext):
212 213 req.form['node'] = [fn[:-len(ext)]]
213 214 req.form['type'] = [type_]
214 215
215 216 # process this if it's a protocol request
216 217
217 218 cmd = req.form.get('cmd', [''])[0]
218 219 if cmd in protocol.__all__:
219 220 method = getattr(protocol, cmd)
220 221 method(self, req)
221 222 return
222 223
223 224 # process the web interface request
224 225
225 226 try:
226 227
227 228 tmpl = self.templater(req)
228 229 ctype = tmpl('mimetype', encoding=self.encoding)
229 230 ctype = templater.stringify(ctype)
230 231
231 232 if cmd == '':
232 233 req.form['cmd'] = [tmpl.cache['default']]
233 234 cmd = req.form['cmd'][0]
234 235
235 236 if cmd not in webcommands.__all__:
236 237 msg = 'No such method: %s' % cmd
237 238 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
238 239 elif cmd == 'file' and 'raw' in req.form.get('style', []):
239 240 self.ctype = ctype
240 241 content = webcommands.rawfile(self, req, tmpl)
241 242 else:
242 243 content = getattr(webcommands, cmd)(self, req, tmpl)
243 244 req.respond(HTTP_OK, ctype)
244 245
245 246 req.write(content)
246 247 del tmpl
247 248
248 249 except revlog.LookupError, err:
249 250 req.respond(HTTP_NOT_FOUND, ctype)
250 251 req.write(tmpl('error', error='revision not found: %s' % err.name))
251 except (hg.RepoError, revlog.RevlogError), inst:
252 except (RepoError, revlog.RevlogError), inst:
252 253 req.respond(HTTP_SERVER_ERROR, ctype)
253 254 req.write(tmpl('error', error=str(inst)))
254 255 except ErrorResponse, inst:
255 256 req.respond(inst.code, ctype)
256 257 req.write(tmpl('error', error=inst.message))
257 258
258 259 def templater(self, req):
259 260
260 261 # determine scheme, port and server name
261 262 # this is needed to create absolute urls
262 263
263 264 proto = req.env.get('wsgi.url_scheme')
264 265 if proto == 'https':
265 266 proto = 'https'
266 267 default_port = "443"
267 268 else:
268 269 proto = 'http'
269 270 default_port = "80"
270 271
271 272 port = req.env["SERVER_PORT"]
272 273 port = port != default_port and (":" + port) or ""
273 274 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
274 275 staticurl = self.config("web", "staticurl") or req.url + 'static/'
275 276 if not staticurl.endswith('/'):
276 277 staticurl += '/'
277 278
278 279 # some functions for the templater
279 280
280 281 def header(**map):
281 282 yield tmpl('header', encoding=self.encoding, **map)
282 283
283 284 def footer(**map):
284 285 yield tmpl("footer", **map)
285 286
286 287 def motd(**map):
287 288 yield self.config("web", "motd", "")
288 289
289 290 def sessionvars(**map):
290 291 fields = []
291 292 if 'style' in req.form:
292 293 style = req.form['style'][0]
293 294 if style != self.config('web', 'style', ''):
294 295 fields.append(('style', style))
295 296
296 297 separator = req.url[-1] == '?' and ';' or '?'
297 298 for name, value in fields:
298 299 yield dict(name=name, value=value, separator=separator)
299 300 separator = ';'
300 301
301 302 # figure out which style to use
302 303
303 304 style = self.config("web", "style", "")
304 305 if 'style' in req.form:
305 306 style = req.form['style'][0]
306 307 mapfile = style_map(self.templatepath, style)
307 308
308 309 if not self.reponame:
309 310 self.reponame = (self.config("web", "name")
310 311 or req.env.get('REPO_NAME')
311 312 or req.url.strip('/') or self.repo.root)
312 313
313 314 # create the templater
314 315
315 316 tmpl = templater.templater(mapfile, templatefilters.filters,
316 317 defaults={"url": req.url,
317 318 "staticurl": staticurl,
318 319 "urlbase": urlbase,
319 320 "repo": self.reponame,
320 321 "header": header,
321 322 "footer": footer,
322 323 "motd": motd,
323 324 "sessionvars": sessionvars
324 325 })
325 326 return tmpl
326 327
327 328 def archivelist(self, nodeid):
328 329 allowed = self.configlist("web", "allow_archive")
329 330 for i, spec in self.archive_specs.iteritems():
330 331 if i in allowed or self.configbool("web", "allow" + i):
331 332 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
332 333
333 334 def listfilediffs(self, tmpl, files, changeset):
334 335 for f in files[:self.maxfiles]:
335 336 yield tmpl("filedifflink", node=hex(changeset), file=f)
336 337 if len(files) > self.maxfiles:
337 338 yield tmpl("fileellipses")
338 339
339 340 def siblings(self, siblings=[], hiderev=None, **args):
340 341 siblings = [s for s in siblings if s.node() != nullid]
341 342 if len(siblings) == 1 and siblings[0].rev() == hiderev:
342 343 return
343 344 for s in siblings:
344 345 d = {'node': hex(s.node()), 'rev': s.rev()}
345 346 if hasattr(s, 'path'):
346 347 d['file'] = s.path()
347 348 d.update(args)
348 349 yield d
349 350
350 351 def renamelink(self, fl, node):
351 352 r = fl.renamed(node)
352 353 if r:
353 354 return [dict(file=r[0], node=hex(r[1]))]
354 355 return []
355 356
356 357 def nodetagsdict(self, node):
357 358 return [{"name": i} for i in self.repo.nodetags(node)]
358 359
359 360 def nodebranchdict(self, ctx):
360 361 branches = []
361 362 branch = ctx.branch()
362 363 # If this is an empty repo, ctx.node() == nullid,
363 364 # ctx.branch() == 'default', but branchtags() is
364 365 # an empty dict. Using dict.get avoids a traceback.
365 366 if self.repo.branchtags().get(branch) == ctx.node():
366 367 branches.append({"name": branch})
367 368 return branches
368 369
369 370 def showtag(self, tmpl, t1, node=nullid, **args):
370 371 for t in self.repo.nodetags(node):
371 372 yield tmpl(t1, tag=t, **args)
372 373
373 374 def diff(self, tmpl, node1, node2, files):
374 375 def filterfiles(filters, files):
375 376 l = [x for x in files if x in filters]
376 377
377 378 for t in filters:
378 379 if t and t[-1] != os.sep:
379 380 t += os.sep
380 381 l += [x for x in files if x.startswith(t)]
381 382 return l
382 383
383 384 parity = paritygen(self.stripecount)
384 385 def diffblock(diff, f, fn):
385 386 yield tmpl("diffblock",
386 387 lines=prettyprintlines(diff),
387 388 parity=parity.next(),
388 389 file=f,
389 390 filenode=hex(fn or nullid))
390 391
391 392 blockcount = countgen()
392 393 def prettyprintlines(diff):
393 394 blockno = blockcount.next()
394 395 for lineno, l in enumerate(diff.splitlines(1)):
395 396 if blockno == 0:
396 397 lineno = lineno + 1
397 398 else:
398 399 lineno = "%d.%d" % (blockno, lineno + 1)
399 400 if l.startswith('+'):
400 401 ltype = "difflineplus"
401 402 elif l.startswith('-'):
402 403 ltype = "difflineminus"
403 404 elif l.startswith('@'):
404 405 ltype = "difflineat"
405 406 else:
406 407 ltype = "diffline"
407 408 yield tmpl(ltype,
408 409 line=l,
409 410 lineid="l%s" % lineno,
410 411 linenumber="% 8s" % lineno)
411 412
412 413 r = self.repo
413 414 c1 = r.changectx(node1)
414 415 c2 = r.changectx(node2)
415 416 date1 = util.datestr(c1.date())
416 417 date2 = util.datestr(c2.date())
417 418
418 419 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
419 420 if files:
420 421 modified, added, removed = map(lambda x: filterfiles(files, x),
421 422 (modified, added, removed))
422 423
423 424 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
424 425 for f in modified:
425 426 to = c1.filectx(f).data()
426 427 tn = c2.filectx(f).data()
427 428 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
428 429 opts=diffopts), f, tn)
429 430 for f in added:
430 431 to = None
431 432 tn = c2.filectx(f).data()
432 433 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
433 434 opts=diffopts), f, tn)
434 435 for f in removed:
435 436 to = c1.filectx(f).data()
436 437 tn = None
437 438 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
438 439 opts=diffopts), f, tn)
439 440
440 441 def changelog(self, tmpl, ctx, shortlog=False):
441 442 def changelist(limit=0,**map):
442 443 cl = self.repo.changelog
443 444 l = [] # build a list in forward order for efficiency
444 445 for i in xrange(start, end):
445 446 ctx = self.repo.changectx(i)
446 447 n = ctx.node()
447 448 showtags = self.showtag(tmpl, 'changelogtag', n)
448 449
449 450 l.insert(0, {"parity": parity.next(),
450 451 "author": ctx.user(),
451 452 "parent": self.siblings(ctx.parents(), i - 1),
452 453 "child": self.siblings(ctx.children(), i + 1),
453 454 "changelogtag": showtags,
454 455 "desc": ctx.description(),
455 456 "date": ctx.date(),
456 457 "files": self.listfilediffs(tmpl, ctx.files(), n),
457 458 "rev": i,
458 459 "node": hex(n),
459 460 "tags": self.nodetagsdict(n),
460 461 "branches": self.nodebranchdict(ctx)})
461 462
462 463 if limit > 0:
463 464 l = l[:limit]
464 465
465 466 for e in l:
466 467 yield e
467 468
468 469 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
469 470 cl = self.repo.changelog
470 471 count = cl.count()
471 472 pos = ctx.rev()
472 473 start = max(0, pos - maxchanges + 1)
473 474 end = min(count, start + maxchanges)
474 475 pos = end - 1
475 476 parity = paritygen(self.stripecount, offset=start-end)
476 477
477 478 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
478 479
479 480 return tmpl(shortlog and 'shortlog' or 'changelog',
480 481 changenav=changenav,
481 482 node=hex(cl.tip()),
482 483 rev=pos, changesets=count,
483 484 entries=lambda **x: changelist(limit=0,**x),
484 485 latestentry=lambda **x: changelist(limit=1,**x),
485 486 archives=self.archivelist("tip"))
486 487
487 488 def search(self, tmpl, query):
488 489
489 490 def changelist(**map):
490 491 cl = self.repo.changelog
491 492 count = 0
492 493 qw = query.lower().split()
493 494
494 495 def revgen():
495 496 for i in xrange(cl.count() - 1, 0, -100):
496 497 l = []
497 498 for j in xrange(max(0, i - 100), i + 1):
498 499 ctx = self.repo.changectx(j)
499 500 l.append(ctx)
500 501 l.reverse()
501 502 for e in l:
502 503 yield e
503 504
504 505 for ctx in revgen():
505 506 miss = 0
506 507 for q in qw:
507 508 if not (q in ctx.user().lower() or
508 509 q in ctx.description().lower() or
509 510 q in " ".join(ctx.files()).lower()):
510 511 miss = 1
511 512 break
512 513 if miss:
513 514 continue
514 515
515 516 count += 1
516 517 n = ctx.node()
517 518 showtags = self.showtag(tmpl, 'changelogtag', n)
518 519
519 520 yield tmpl('searchentry',
520 521 parity=parity.next(),
521 522 author=ctx.user(),
522 523 parent=self.siblings(ctx.parents()),
523 524 child=self.siblings(ctx.children()),
524 525 changelogtag=showtags,
525 526 desc=ctx.description(),
526 527 date=ctx.date(),
527 528 files=self.listfilediffs(tmpl, ctx.files(), n),
528 529 rev=ctx.rev(),
529 530 node=hex(n),
530 531 tags=self.nodetagsdict(n),
531 532 branches=self.nodebranchdict(ctx))
532 533
533 534 if count >= self.maxchanges:
534 535 break
535 536
536 537 cl = self.repo.changelog
537 538 parity = paritygen(self.stripecount)
538 539
539 540 return tmpl('search',
540 541 query=query,
541 542 node=hex(cl.tip()),
542 543 entries=changelist,
543 544 archives=self.archivelist("tip"))
544 545
545 546 def changeset(self, tmpl, ctx):
546 547 n = ctx.node()
547 548 showtags = self.showtag(tmpl, 'changesettag', n)
548 549 parents = ctx.parents()
549 550 p1 = parents[0].node()
550 551
551 552 files = []
552 553 parity = paritygen(self.stripecount)
553 554 for f in ctx.files():
554 555 files.append(tmpl("filenodelink",
555 556 node=hex(n), file=f,
556 557 parity=parity.next()))
557 558
558 559 def diff(**map):
559 560 yield self.diff(tmpl, p1, n, None)
560 561
561 562 return tmpl('changeset',
562 563 diff=diff,
563 564 rev=ctx.rev(),
564 565 node=hex(n),
565 566 parent=self.siblings(parents),
566 567 child=self.siblings(ctx.children()),
567 568 changesettag=showtags,
568 569 author=ctx.user(),
569 570 desc=ctx.description(),
570 571 date=ctx.date(),
571 572 files=files,
572 573 archives=self.archivelist(hex(n)),
573 574 tags=self.nodetagsdict(n),
574 575 branches=self.nodebranchdict(ctx))
575 576
576 577 def filelog(self, tmpl, fctx):
577 578 f = fctx.path()
578 579 fl = fctx.filelog()
579 580 count = fl.count()
580 581 pagelen = self.maxshortchanges
581 582 pos = fctx.filerev()
582 583 start = max(0, pos - pagelen + 1)
583 584 end = min(count, start + pagelen)
584 585 pos = end - 1
585 586 parity = paritygen(self.stripecount, offset=start-end)
586 587
587 588 def entries(limit=0, **map):
588 589 l = []
589 590
590 591 for i in xrange(start, end):
591 592 ctx = fctx.filectx(i)
592 593 n = fl.node(i)
593 594
594 595 l.insert(0, {"parity": parity.next(),
595 596 "filerev": i,
596 597 "file": f,
597 598 "node": hex(ctx.node()),
598 599 "author": ctx.user(),
599 600 "date": ctx.date(),
600 601 "rename": self.renamelink(fl, n),
601 602 "parent": self.siblings(fctx.parents()),
602 603 "child": self.siblings(fctx.children()),
603 604 "desc": ctx.description()})
604 605
605 606 if limit > 0:
606 607 l = l[:limit]
607 608
608 609 for e in l:
609 610 yield e
610 611
611 612 nodefunc = lambda x: fctx.filectx(fileid=x)
612 613 nav = revnavgen(pos, pagelen, count, nodefunc)
613 614 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
614 615 entries=lambda **x: entries(limit=0, **x),
615 616 latestentry=lambda **x: entries(limit=1, **x))
616 617
617 618 def filerevision(self, tmpl, fctx):
618 619 f = fctx.path()
619 620 text = fctx.data()
620 621 fl = fctx.filelog()
621 622 n = fctx.filenode()
622 623 parity = paritygen(self.stripecount)
623 624
624 625 if util.binary(text):
625 626 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
626 627 text = '(binary:%s)' % mt
627 628
628 629 def lines():
629 630 for lineno, t in enumerate(text.splitlines(1)):
630 631 yield {"line": t,
631 632 "lineid": "l%d" % (lineno + 1),
632 633 "linenumber": "% 6d" % (lineno + 1),
633 634 "parity": parity.next()}
634 635
635 636 return tmpl("filerevision",
636 637 file=f,
637 638 path=_up(f),
638 639 text=lines(),
639 640 rev=fctx.rev(),
640 641 node=hex(fctx.node()),
641 642 author=fctx.user(),
642 643 date=fctx.date(),
643 644 desc=fctx.description(),
644 645 parent=self.siblings(fctx.parents()),
645 646 child=self.siblings(fctx.children()),
646 647 rename=self.renamelink(fl, n),
647 648 permissions=fctx.manifest().flags(f))
648 649
649 650 def fileannotate(self, tmpl, fctx):
650 651 f = fctx.path()
651 652 n = fctx.filenode()
652 653 fl = fctx.filelog()
653 654 parity = paritygen(self.stripecount)
654 655
655 656 def annotate(**map):
656 657 last = None
657 658 if util.binary(fctx.data()):
658 659 mt = (mimetypes.guess_type(fctx.path())[0]
659 660 or 'application/octet-stream')
660 661 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
661 662 '(binary:%s)' % mt)])
662 663 else:
663 664 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
664 665 for lineno, ((f, targetline), l) in lines:
665 666 fnode = f.filenode()
666 667 name = self.repo.ui.shortuser(f.user())
667 668
668 669 if last != fnode:
669 670 last = fnode
670 671
671 672 yield {"parity": parity.next(),
672 673 "node": hex(f.node()),
673 674 "rev": f.rev(),
674 675 "author": name,
675 676 "file": f.path(),
676 677 "targetline": targetline,
677 678 "line": l,
678 679 "lineid": "l%d" % (lineno + 1),
679 680 "linenumber": "% 6d" % (lineno + 1)}
680 681
681 682 return tmpl("fileannotate",
682 683 file=f,
683 684 annotate=annotate,
684 685 path=_up(f),
685 686 rev=fctx.rev(),
686 687 node=hex(fctx.node()),
687 688 author=fctx.user(),
688 689 date=fctx.date(),
689 690 desc=fctx.description(),
690 691 rename=self.renamelink(fl, n),
691 692 parent=self.siblings(fctx.parents()),
692 693 child=self.siblings(fctx.children()),
693 694 permissions=fctx.manifest().flags(f))
694 695
695 696 def manifest(self, tmpl, ctx, path):
696 697 mf = ctx.manifest()
697 698 node = ctx.node()
698 699
699 700 files = {}
700 701 parity = paritygen(self.stripecount)
701 702
702 703 if path and path[-1] != "/":
703 704 path += "/"
704 705 l = len(path)
705 706 abspath = "/" + path
706 707
707 708 for f, n in mf.items():
708 709 if f[:l] != path:
709 710 continue
710 711 remain = f[l:]
711 712 if "/" in remain:
712 713 short = remain[:remain.index("/") + 1] # bleah
713 714 files[short] = (f, None)
714 715 else:
715 716 short = os.path.basename(remain)
716 717 files[short] = (f, n)
717 718
718 719 if not files:
719 720 raise ErrorResponse(HTTP_NOT_FOUND, 'Path not found: ' + path)
720 721
721 722 def filelist(**map):
722 723 fl = files.keys()
723 724 fl.sort()
724 725 for f in fl:
725 726 full, fnode = files[f]
726 727 if not fnode:
727 728 continue
728 729
729 730 fctx = ctx.filectx(full)
730 731 yield {"file": full,
731 732 "parity": parity.next(),
732 733 "basename": f,
733 734 "date": fctx.changectx().date(),
734 735 "size": fctx.size(),
735 736 "permissions": mf.flags(full)}
736 737
737 738 def dirlist(**map):
738 739 fl = files.keys()
739 740 fl.sort()
740 741 for f in fl:
741 742 full, fnode = files[f]
742 743 if fnode:
743 744 continue
744 745
745 746 yield {"parity": parity.next(),
746 747 "path": "%s%s" % (abspath, f),
747 748 "basename": f[:-1]}
748 749
749 750 return tmpl("manifest",
750 751 rev=ctx.rev(),
751 752 node=hex(node),
752 753 path=abspath,
753 754 up=_up(abspath),
754 755 upparity=parity.next(),
755 756 fentries=filelist,
756 757 dentries=dirlist,
757 758 archives=self.archivelist(hex(node)),
758 759 tags=self.nodetagsdict(node),
759 760 branches=self.nodebranchdict(ctx))
760 761
761 762 def tags(self, tmpl):
762 763 i = self.repo.tagslist()
763 764 i.reverse()
764 765 parity = paritygen(self.stripecount)
765 766
766 767 def entries(notip=False,limit=0, **map):
767 768 count = 0
768 769 for k, n in i:
769 770 if notip and k == "tip":
770 771 continue
771 772 if limit > 0 and count >= limit:
772 773 continue
773 774 count = count + 1
774 775 yield {"parity": parity.next(),
775 776 "tag": k,
776 777 "date": self.repo.changectx(n).date(),
777 778 "node": hex(n)}
778 779
779 780 return tmpl("tags",
780 781 node=hex(self.repo.changelog.tip()),
781 782 entries=lambda **x: entries(False,0, **x),
782 783 entriesnotip=lambda **x: entries(True,0, **x),
783 784 latestentry=lambda **x: entries(True,1, **x))
784 785
785 786 def summary(self, tmpl):
786 787 i = self.repo.tagslist()
787 788 i.reverse()
788 789
789 790 def tagentries(**map):
790 791 parity = paritygen(self.stripecount)
791 792 count = 0
792 793 for k, n in i:
793 794 if k == "tip": # skip tip
794 795 continue;
795 796
796 797 count += 1
797 798 if count > 10: # limit to 10 tags
798 799 break;
799 800
800 801 yield tmpl("tagentry",
801 802 parity=parity.next(),
802 803 tag=k,
803 804 node=hex(n),
804 805 date=self.repo.changectx(n).date())
805 806
806 807
807 808 def branches(**map):
808 809 parity = paritygen(self.stripecount)
809 810
810 811 b = self.repo.branchtags()
811 812 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
812 813 l.sort()
813 814
814 815 for r,n,t in l:
815 816 ctx = self.repo.changectx(n)
816 817
817 818 yield {'parity': parity.next(),
818 819 'branch': t,
819 820 'node': hex(n),
820 821 'date': ctx.date()}
821 822
822 823 def changelist(**map):
823 824 parity = paritygen(self.stripecount, offset=start-end)
824 825 l = [] # build a list in forward order for efficiency
825 826 for i in xrange(start, end):
826 827 ctx = self.repo.changectx(i)
827 828 n = ctx.node()
828 829 hn = hex(n)
829 830
830 831 l.insert(0, tmpl(
831 832 'shortlogentry',
832 833 parity=parity.next(),
833 834 author=ctx.user(),
834 835 desc=ctx.description(),
835 836 date=ctx.date(),
836 837 rev=i,
837 838 node=hn,
838 839 tags=self.nodetagsdict(n),
839 840 branches=self.nodebranchdict(ctx)))
840 841
841 842 yield l
842 843
843 844 cl = self.repo.changelog
844 845 count = cl.count()
845 846 start = max(0, count - self.maxchanges)
846 847 end = min(count, start + self.maxchanges)
847 848
848 849 return tmpl("summary",
849 850 desc=self.config("web", "description", "unknown"),
850 851 owner=get_contact(self.config) or "unknown",
851 852 lastchange=cl.read(cl.tip())[2],
852 853 tags=tagentries,
853 854 branches=branches,
854 855 shortlog=changelist,
855 856 node=hex(cl.tip()),
856 857 archives=self.archivelist("tip"))
857 858
858 859 def filediff(self, tmpl, fctx):
859 860 n = fctx.node()
860 861 path = fctx.path()
861 862 parents = fctx.parents()
862 863 p1 = parents and parents[0].node() or nullid
863 864
864 865 def diff(**map):
865 866 yield self.diff(tmpl, p1, n, [path])
866 867
867 868 return tmpl("filediff",
868 869 file=path,
869 870 node=hex(n),
870 871 rev=fctx.rev(),
871 872 parent=self.siblings(parents),
872 873 child=self.siblings(fctx.children()),
873 874 diff=diff)
874 875
875 876 archive_specs = {
876 877 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
877 878 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
878 879 'zip': ('application/zip', 'zip', '.zip', None),
879 880 }
880 881
881 882 def archive(self, tmpl, req, key, type_):
882 883 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
883 884 cnode = self.repo.lookup(key)
884 885 arch_version = key
885 886 if cnode == key or key == 'tip':
886 887 arch_version = short(cnode)
887 888 name = "%s-%s" % (reponame, arch_version)
888 889 mimetype, artype, extension, encoding = self.archive_specs[type_]
889 890 headers = [
890 891 ('Content-Type', mimetype),
891 892 ('Content-Disposition', 'attachment; filename=%s%s' %
892 893 (name, extension))
893 894 ]
894 895 if encoding:
895 896 headers.append(('Content-Encoding', encoding))
896 897 req.header(headers)
897 898 req.respond(HTTP_OK)
898 899 archival.archive(self.repo, req, cnode, artype, prefix=name)
899 900
900 901 # add tags to things
901 902 # tags -> list of changesets corresponding to tags
902 903 # find tag, changeset, file
903 904
904 905 def cleanpath(self, path):
905 906 path = path.lstrip('/')
906 907 return util.canonpath(self.repo.root, '', path)
907 908
908 909 def changectx(self, req):
909 910 if 'node' in req.form:
910 911 changeid = req.form['node'][0]
911 912 elif 'manifest' in req.form:
912 913 changeid = req.form['manifest'][0]
913 914 else:
914 915 changeid = self.repo.changelog.count() - 1
915 916
916 917 try:
917 918 ctx = self.repo.changectx(changeid)
918 except hg.RepoError:
919 except RepoError:
919 920 man = self.repo.manifest
920 921 mn = man.lookup(changeid)
921 922 ctx = self.repo.changectx(man.linkrev(mn))
922 923
923 924 return ctx
924 925
925 926 def filectx(self, req):
926 927 path = self.cleanpath(req.form['file'][0])
927 928 if 'node' in req.form:
928 929 changeid = req.form['node'][0]
929 930 else:
930 931 changeid = req.form['filenode'][0]
931 932 try:
932 933 ctx = self.repo.changectx(changeid)
933 934 fctx = ctx.filectx(path)
934 except hg.RepoError:
935 except RepoError:
935 936 fctx = self.repo.filectx(path, fileid=changeid)
936 937
937 938 return fctx
938 939
939 940 def check_perm(self, req, op, default):
940 941 '''check permission for operation based on user auth.
941 942 return true if op allowed, else false.
942 943 default is policy to use if no config given.'''
943 944
944 945 user = req.env.get('REMOTE_USER')
945 946
946 947 deny = self.configlist('web', 'deny_' + op)
947 948 if deny and (not user or deny == ['*'] or user in deny):
948 949 return False
949 950
950 951 allow = self.configlist('web', 'allow_' + op)
951 952 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,278 +1,279 b''
1 1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os
10 10 from mercurial.i18n import gettext as _
11 from mercurial.repo import RepoError
11 12 from mercurial import ui, hg, util, templater, templatefilters
12 13 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen,\
13 14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 15 from hgweb_mod import hgweb
15 16 from request import wsgirequest
16 17
17 18 # This is a stopgap
18 19 class hgwebdir(object):
19 20 def __init__(self, config, parentui=None):
20 21 def cleannames(items):
21 22 return [(util.pconvert(name).strip('/'), path)
22 23 for name, path in items]
23 24
24 25 self.parentui = parentui or ui.ui(report_untrusted=False,
25 26 interactive = False)
26 27 self.motd = None
27 28 self.style = None
28 29 self.stripecount = None
29 30 self.repos_sorted = ('name', False)
30 31 if isinstance(config, (list, tuple)):
31 32 self.repos = cleannames(config)
32 33 self.repos_sorted = ('', False)
33 34 elif isinstance(config, dict):
34 35 self.repos = cleannames(config.items())
35 36 self.repos.sort()
36 37 else:
37 38 if isinstance(config, util.configparser):
38 39 cp = config
39 40 else:
40 41 cp = util.configparser()
41 42 cp.read(config)
42 43 self.repos = []
43 44 if cp.has_section('web'):
44 45 if cp.has_option('web', 'motd'):
45 46 self.motd = cp.get('web', 'motd')
46 47 if cp.has_option('web', 'style'):
47 48 self.style = cp.get('web', 'style')
48 49 if cp.has_option('web', 'stripes'):
49 50 self.stripecount = int(cp.get('web', 'stripes'))
50 51 if cp.has_section('paths'):
51 52 self.repos.extend(cleannames(cp.items('paths')))
52 53 if cp.has_section('collections'):
53 54 for prefix, root in cp.items('collections'):
54 55 for path in util.walkrepos(root):
55 56 repo = os.path.normpath(path)
56 57 name = repo
57 58 if name.startswith(prefix):
58 59 name = name[len(prefix):]
59 60 self.repos.append((name.lstrip(os.sep), repo))
60 61 self.repos.sort()
61 62
62 63 def run(self):
63 64 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
64 65 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
65 66 import mercurial.hgweb.wsgicgi as wsgicgi
66 67 wsgicgi.launch(self)
67 68
68 69 def __call__(self, env, respond):
69 70 req = wsgirequest(env, respond)
70 71 self.run_wsgi(req)
71 72 return req
72 73
73 74 def run_wsgi(self, req):
74 75
75 76 try:
76 77 try:
77 78
78 79 virtual = req.env.get("PATH_INFO", "").strip('/')
79 80 tmpl = self.templater(req)
80 81 ctype = tmpl('mimetype', encoding=util._encoding)
81 82 ctype = templater.stringify(ctype)
82 83
83 84 # a static file
84 85 if virtual.startswith('static/') or 'static' in req.form:
85 86 static = os.path.join(templater.templatepath(), 'static')
86 87 if virtual.startswith('static/'):
87 88 fname = virtual[7:]
88 89 else:
89 90 fname = req.form['static'][0]
90 91 req.write(staticfile(static, fname, req))
91 92 return
92 93
93 94 # top-level index
94 95 elif not virtual:
95 96 req.respond(HTTP_OK, ctype)
96 97 req.write(self.makeindex(req, tmpl))
97 98 return
98 99
99 100 # nested indexes and hgwebs
100 101
101 102 repos = dict(self.repos)
102 103 while virtual:
103 104 real = repos.get(virtual)
104 105 if real:
105 106 req.env['REPO_NAME'] = virtual
106 107 try:
107 108 repo = hg.repository(self.parentui, real)
108 109 hgweb(repo).run_wsgi(req)
109 110 return
110 111 except IOError, inst:
111 112 msg = inst.strerror
112 113 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
113 except hg.RepoError, inst:
114 except RepoError, inst:
114 115 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
115 116
116 117 # browse subdirectories
117 118 subdir = virtual + '/'
118 119 if [r for r in repos if r.startswith(subdir)]:
119 120 req.respond(HTTP_OK, ctype)
120 121 req.write(self.makeindex(req, tmpl, subdir))
121 122 return
122 123
123 124 up = virtual.rfind('/')
124 125 if up < 0:
125 126 break
126 127 virtual = virtual[:up]
127 128
128 129 # prefixes not found
129 130 req.respond(HTTP_NOT_FOUND, ctype)
130 131 req.write(tmpl("notfound", repo=virtual))
131 132
132 133 except ErrorResponse, err:
133 134 req.respond(err.code, ctype)
134 135 req.write(tmpl('error', error=err.message or ''))
135 136 finally:
136 137 tmpl = None
137 138
138 139 def makeindex(self, req, tmpl, subdir=""):
139 140
140 141 def archivelist(ui, nodeid, url):
141 142 allowed = ui.configlist("web", "allow_archive", untrusted=True)
142 143 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
143 144 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
144 145 untrusted=True):
145 146 yield {"type" : i[0], "extension": i[1],
146 147 "node": nodeid, "url": url}
147 148
148 149 def entries(sortcolumn="", descending=False, subdir="", **map):
149 150 def sessionvars(**map):
150 151 fields = []
151 152 if 'style' in req.form:
152 153 style = req.form['style'][0]
153 154 if style != get('web', 'style', ''):
154 155 fields.append(('style', style))
155 156
156 157 separator = url[-1] == '?' and ';' or '?'
157 158 for name, value in fields:
158 159 yield dict(name=name, value=value, separator=separator)
159 160 separator = ';'
160 161
161 162 rows = []
162 163 parity = paritygen(self.stripecount)
163 164 for name, path in self.repos:
164 165 if not name.startswith(subdir):
165 166 continue
166 167 name = name[len(subdir):]
167 168
168 169 u = ui.ui(parentui=self.parentui)
169 170 try:
170 171 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
171 172 except Exception, e:
172 173 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
173 174 continue
174 175 def get(section, name, default=None):
175 176 return u.config(section, name, default, untrusted=True)
176 177
177 178 if u.configbool("web", "hidden", untrusted=True):
178 179 continue
179 180
180 181 parts = [req.env['PATH_INFO'].rstrip('/'), name]
181 182 if req.env['SCRIPT_NAME']:
182 183 parts.insert(0, req.env['SCRIPT_NAME'])
183 184 url = ('/'.join(parts).replace("//", "/")) + '/'
184 185
185 186 # update time with local timezone
186 187 try:
187 188 d = (get_mtime(path), util.makedate()[1])
188 189 except OSError:
189 190 continue
190 191
191 192 contact = get_contact(get)
192 193 description = get("web", "description", "")
193 194 name = get("web", "name", name)
194 195 row = dict(contact=contact or "unknown",
195 196 contact_sort=contact.upper() or "unknown",
196 197 name=name,
197 198 name_sort=name,
198 199 url=url,
199 200 description=description or "unknown",
200 201 description_sort=description.upper() or "unknown",
201 202 lastchange=d,
202 203 lastchange_sort=d[1]-d[0],
203 204 sessionvars=sessionvars,
204 205 archives=archivelist(u, "tip", url))
205 206 if (not sortcolumn
206 207 or (sortcolumn, descending) == self.repos_sorted):
207 208 # fast path for unsorted output
208 209 row['parity'] = parity.next()
209 210 yield row
210 211 else:
211 212 rows.append((row["%s_sort" % sortcolumn], row))
212 213 if rows:
213 214 rows.sort()
214 215 if descending:
215 216 rows.reverse()
216 217 for key, row in rows:
217 218 row['parity'] = parity.next()
218 219 yield row
219 220
220 221 sortable = ["name", "description", "contact", "lastchange"]
221 222 sortcolumn, descending = self.repos_sorted
222 223 if 'sort' in req.form:
223 224 sortcolumn = req.form['sort'][0]
224 225 descending = sortcolumn.startswith('-')
225 226 if descending:
226 227 sortcolumn = sortcolumn[1:]
227 228 if sortcolumn not in sortable:
228 229 sortcolumn = ""
229 230
230 231 sort = [("sort_%s" % column,
231 232 "%s%s" % ((not descending and column == sortcolumn)
232 233 and "-" or "", column))
233 234 for column in sortable]
234 235
235 236 return tmpl("index", entries=entries, subdir=subdir,
236 237 sortcolumn=sortcolumn, descending=descending,
237 238 **dict(sort))
238 239
239 240 def templater(self, req):
240 241
241 242 def header(**map):
242 243 yield tmpl('header', encoding=util._encoding, **map)
243 244
244 245 def footer(**map):
245 246 yield tmpl("footer", **map)
246 247
247 248 def motd(**map):
248 249 if self.motd is not None:
249 250 yield self.motd
250 251 else:
251 252 yield config('web', 'motd', '')
252 253
253 254 def config(section, name, default=None, untrusted=True):
254 255 return self.parentui.config(section, name, default, untrusted)
255 256
256 257 url = req.env.get('SCRIPT_NAME', '')
257 258 if not url.endswith('/'):
258 259 url += '/'
259 260
260 261 staticurl = config('web', 'staticurl') or url + 'static/'
261 262 if not staticurl.endswith('/'):
262 263 staticurl += '/'
263 264
264 265 style = self.style
265 266 if style is None:
266 267 style = config('web', 'style', '')
267 268 if 'style' in req.form:
268 269 style = req.form['style'][0]
269 270 if self.stripecount is None:
270 271 self.stripecount = int(config('web', 'stripes', 1))
271 272 mapfile = style_map(templater.templatepath(), style)
272 273 tmpl = templater.templater(mapfile, templatefilters.filters,
273 274 defaults={"header": header,
274 275 "footer": footer,
275 276 "motd": motd,
276 277 "url": url,
277 278 "staticurl": staticurl})
278 279 return tmpl
@@ -1,294 +1,295 b''
1 1 # hgweb/server.py - The standalone hg web server.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
10 10 from mercurial import hg, util
11 from mercurial.repo import RepoError
11 12 from hgweb_mod import hgweb
12 13 from hgwebdir_mod import hgwebdir
13 14 from mercurial.i18n import gettext as _
14 15
15 16 def _splitURI(uri):
16 17 """ Return path and query splited from uri
17 18
18 19 Just like CGI environment, the path is unquoted, the query is
19 20 not.
20 21 """
21 22 if '?' in uri:
22 23 path, query = uri.split('?', 1)
23 24 else:
24 25 path, query = uri, ''
25 26 return urllib.unquote(path), query
26 27
27 28 class _error_logger(object):
28 29 def __init__(self, handler):
29 30 self.handler = handler
30 31 def flush(self):
31 32 pass
32 33 def write(self, str):
33 34 self.writelines(str.split('\n'))
34 35 def writelines(self, seq):
35 36 for msg in seq:
36 37 self.handler.log_error("HG error: %s", msg)
37 38
38 39 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
39 40
40 41 url_scheme = 'http'
41 42
42 43 def __init__(self, *args, **kargs):
43 44 self.protocol_version = 'HTTP/1.1'
44 45 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
45 46
46 47 def _log_any(self, fp, format, *args):
47 48 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
48 49 self.log_date_time_string(),
49 50 format % args))
50 51 fp.flush()
51 52
52 53 def log_error(self, format, *args):
53 54 self._log_any(self.server.errorlog, format, *args)
54 55
55 56 def log_message(self, format, *args):
56 57 self._log_any(self.server.accesslog, format, *args)
57 58
58 59 def do_write(self):
59 60 try:
60 61 self.do_hgweb()
61 62 except socket.error, inst:
62 63 if inst[0] != errno.EPIPE:
63 64 raise
64 65
65 66 def do_POST(self):
66 67 try:
67 68 self.do_write()
68 69 except StandardError, inst:
69 70 self._start_response("500 Internal Server Error", [])
70 71 self._write("Internal Server Error")
71 72 tb = "".join(traceback.format_exception(*sys.exc_info()))
72 73 self.log_error("Exception happened during processing request '%s':\n%s",
73 74 self.path, tb)
74 75
75 76 def do_GET(self):
76 77 self.do_POST()
77 78
78 79 def do_hgweb(self):
79 80 path, query = _splitURI(self.path)
80 81
81 82 env = {}
82 83 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
83 84 env['REQUEST_METHOD'] = self.command
84 85 env['SERVER_NAME'] = self.server.server_name
85 86 env['SERVER_PORT'] = str(self.server.server_port)
86 87 env['REQUEST_URI'] = self.path
87 88 env['SCRIPT_NAME'] = self.server.prefix
88 89 env['PATH_INFO'] = path[len(self.server.prefix):]
89 90 env['REMOTE_HOST'] = self.client_address[0]
90 91 env['REMOTE_ADDR'] = self.client_address[0]
91 92 if query:
92 93 env['QUERY_STRING'] = query
93 94
94 95 if self.headers.typeheader is None:
95 96 env['CONTENT_TYPE'] = self.headers.type
96 97 else:
97 98 env['CONTENT_TYPE'] = self.headers.typeheader
98 99 length = self.headers.getheader('content-length')
99 100 if length:
100 101 env['CONTENT_LENGTH'] = length
101 102 for header in [h for h in self.headers.keys()
102 103 if h not in ('content-type', 'content-length')]:
103 104 hkey = 'HTTP_' + header.replace('-', '_').upper()
104 105 hval = self.headers.getheader(header)
105 106 hval = hval.replace('\n', '').strip()
106 107 if hval:
107 108 env[hkey] = hval
108 109 env['SERVER_PROTOCOL'] = self.request_version
109 110 env['wsgi.version'] = (1, 0)
110 111 env['wsgi.url_scheme'] = self.url_scheme
111 112 env['wsgi.input'] = self.rfile
112 113 env['wsgi.errors'] = _error_logger(self)
113 114 env['wsgi.multithread'] = isinstance(self.server,
114 115 SocketServer.ThreadingMixIn)
115 116 env['wsgi.multiprocess'] = isinstance(self.server,
116 117 SocketServer.ForkingMixIn)
117 118 env['wsgi.run_once'] = 0
118 119
119 120 self.close_connection = True
120 121 self.saved_status = None
121 122 self.saved_headers = []
122 123 self.sent_headers = False
123 124 self.length = None
124 125 self.server.application(env, self._start_response)
125 126
126 127 def send_headers(self):
127 128 if not self.saved_status:
128 129 raise AssertionError("Sending headers before start_response() called")
129 130 saved_status = self.saved_status.split(None, 1)
130 131 saved_status[0] = int(saved_status[0])
131 132 self.send_response(*saved_status)
132 133 should_close = True
133 134 for h in self.saved_headers:
134 135 self.send_header(*h)
135 136 if h[0].lower() == 'content-length':
136 137 should_close = False
137 138 self.length = int(h[1])
138 139 # The value of the Connection header is a list of case-insensitive
139 140 # tokens separated by commas and optional whitespace.
140 141 if 'close' in [token.strip().lower() for token in
141 142 self.headers.get('connection', '').split(',')]:
142 143 should_close = True
143 144 if should_close:
144 145 self.send_header('Connection', 'close')
145 146 self.close_connection = should_close
146 147 self.end_headers()
147 148 self.sent_headers = True
148 149
149 150 def _start_response(self, http_status, headers, exc_info=None):
150 151 code, msg = http_status.split(None, 1)
151 152 code = int(code)
152 153 self.saved_status = http_status
153 154 bad_headers = ('connection', 'transfer-encoding')
154 155 self.saved_headers = [h for h in headers
155 156 if h[0].lower() not in bad_headers]
156 157 return self._write
157 158
158 159 def _write(self, data):
159 160 if not self.saved_status:
160 161 raise AssertionError("data written before start_response() called")
161 162 elif not self.sent_headers:
162 163 self.send_headers()
163 164 if self.length is not None:
164 165 if len(data) > self.length:
165 166 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
166 167 self.length = self.length - len(data)
167 168 self.wfile.write(data)
168 169 self.wfile.flush()
169 170
170 171 class _shgwebhandler(_hgwebhandler):
171 172
172 173 url_scheme = 'https'
173 174
174 175 def setup(self):
175 176 self.connection = self.request
176 177 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
177 178 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
178 179
179 180 def do_write(self):
180 181 from OpenSSL.SSL import SysCallError
181 182 try:
182 183 super(_shgwebhandler, self).do_write()
183 184 except SysCallError, inst:
184 185 if inst.args[0] != errno.EPIPE:
185 186 raise
186 187
187 188 def handle_one_request(self):
188 189 from OpenSSL.SSL import SysCallError, ZeroReturnError
189 190 try:
190 191 super(_shgwebhandler, self).handle_one_request()
191 192 except (SysCallError, ZeroReturnError):
192 193 self.close_connection = True
193 194 pass
194 195
195 196 def create_server(ui, repo):
196 197 use_threads = True
197 198
198 199 def openlog(opt, default):
199 200 if opt and opt != '-':
200 201 return open(opt, 'a')
201 202 return default
202 203
203 204 if repo is None:
204 205 myui = ui
205 206 else:
206 207 myui = repo.ui
207 208 address = myui.config("web", "address", "")
208 209 port = int(myui.config("web", "port", 8000))
209 210 prefix = myui.config("web", "prefix", "")
210 211 if prefix:
211 212 prefix = "/" + prefix.strip("/")
212 213 use_ipv6 = myui.configbool("web", "ipv6")
213 214 webdir_conf = myui.config("web", "webdir_conf")
214 215 ssl_cert = myui.config("web", "certificate")
215 216 accesslog = openlog(myui.config("web", "accesslog", "-"), sys.stdout)
216 217 errorlog = openlog(myui.config("web", "errorlog", "-"), sys.stderr)
217 218
218 219 if use_threads:
219 220 try:
220 221 from threading import activeCount
221 222 except ImportError:
222 223 use_threads = False
223 224
224 225 if use_threads:
225 226 _mixin = SocketServer.ThreadingMixIn
226 227 else:
227 228 if hasattr(os, "fork"):
228 229 _mixin = SocketServer.ForkingMixIn
229 230 else:
230 231 class _mixin:
231 232 pass
232 233
233 234 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
234 235
235 236 # SO_REUSEADDR has broken semantics on windows
236 237 if os.name == 'nt':
237 238 allow_reuse_address = 0
238 239
239 240 def __init__(self, *args, **kargs):
240 241 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
241 242 self.accesslog = accesslog
242 243 self.errorlog = errorlog
243 244 self.daemon_threads = True
244 245 def make_handler():
245 246 if webdir_conf:
246 247 hgwebobj = hgwebdir(webdir_conf, ui)
247 248 elif repo is not None:
248 249 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
249 250 else:
250 raise hg.RepoError(_("There is no Mercurial repository here"
251 " (.hg not found)"))
251 raise RepoError(_("There is no Mercurial repository here"
252 " (.hg not found)"))
252 253 return hgwebobj
253 254 self.application = make_handler()
254 255
255 256 addr = address
256 257 if addr in ('', '::'):
257 258 addr = socket.gethostname()
258 259
259 260 self.addr, self.port = addr, port
260 261 self.prefix = prefix
261 262
262 263 if ssl_cert:
263 264 try:
264 265 from OpenSSL import SSL
265 266 ctx = SSL.Context(SSL.SSLv23_METHOD)
266 267 except ImportError:
267 268 raise util.Abort("SSL support is unavailable")
268 269 ctx.use_privatekey_file(ssl_cert)
269 270 ctx.use_certificate_file(ssl_cert)
270 271 sock = socket.socket(self.address_family, self.socket_type)
271 272 self.socket = SSL.Connection(ctx, sock)
272 273 self.server_bind()
273 274 self.server_activate()
274 275
275 276 class IPv6HTTPServer(MercurialHTTPServer):
276 277 address_family = getattr(socket, 'AF_INET6', None)
277 278
278 279 def __init__(self, *args, **kwargs):
279 280 if self.address_family is None:
280 raise hg.RepoError(_('IPv6 not available on this system'))
281 raise RepoError(_('IPv6 not available on this system'))
281 282 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
282 283
283 284 if ssl_cert:
284 285 handler = _shgwebhandler
285 286 else:
286 287 handler = _hgwebhandler
287 288
288 289 try:
289 290 if use_ipv6:
290 291 return IPv6HTTPServer((address, port), handler)
291 292 else:
292 293 return MercurialHTTPServer((address, port), handler)
293 294 except socket.error, inst:
294 295 raise util.Abort(_('cannot start server: %s') % inst.args[1])
@@ -1,120 +1,121 b''
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import os, mimetypes
9 from mercurial import revlog, util, hg
9 from mercurial import revlog, util
10 from mercurial.repo import RepoError
10 11 from common import staticfile, ErrorResponse, HTTP_OK, HTTP_NOT_FOUND
11 12
12 13 # __all__ is populated with the allowed commands. Be sure to add to it if
13 14 # you're adding a new command, or the new command won't work.
14 15
15 16 __all__ = [
16 17 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
17 18 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
18 19 'archive', 'static',
19 20 ]
20 21
21 22 def log(web, req, tmpl):
22 23 if 'file' in req.form and req.form['file'][0]:
23 24 return filelog(web, req, tmpl)
24 25 else:
25 26 return changelog(web, req, tmpl)
26 27
27 28 def rawfile(web, req, tmpl):
28 29 path = web.cleanpath(req.form.get('file', [''])[0])
29 30 if not path:
30 31 content = web.manifest(tmpl, web.changectx(req), path)
31 32 req.respond(HTTP_OK, web.ctype)
32 33 return content
33 34
34 35 try:
35 36 fctx = web.filectx(req)
36 37 except revlog.LookupError:
37 38 content = web.manifest(tmpl, web.changectx(req), path)
38 39 req.respond(HTTP_OK, web.ctype)
39 40 return content
40 41
41 42 path = fctx.path()
42 43 text = fctx.data()
43 44 mt = mimetypes.guess_type(path)[0]
44 45 if mt is None or util.binary(text):
45 46 mt = mt or 'application/octet-stream'
46 47
47 48 req.respond(HTTP_OK, mt, path, len(text))
48 49 return [text]
49 50
50 51 def file(web, req, tmpl):
51 52 path = web.cleanpath(req.form.get('file', [''])[0])
52 53 if path:
53 54 try:
54 55 return web.filerevision(tmpl, web.filectx(req))
55 56 except revlog.LookupError:
56 57 pass
57 58
58 59 return web.manifest(tmpl, web.changectx(req), path)
59 60
60 61 def changelog(web, req, tmpl, shortlog = False):
61 62 if 'node' in req.form:
62 63 ctx = web.changectx(req)
63 64 else:
64 65 if 'rev' in req.form:
65 66 hi = req.form['rev'][0]
66 67 else:
67 68 hi = web.repo.changelog.count() - 1
68 69 try:
69 70 ctx = web.repo.changectx(hi)
70 except hg.RepoError:
71 except RepoError:
71 72 return web.search(tmpl, hi) # XXX redirect to 404 page?
72 73
73 74 return web.changelog(tmpl, ctx, shortlog = shortlog)
74 75
75 76 def shortlog(web, req, tmpl):
76 77 return changelog(web, req, tmpl, shortlog = True)
77 78
78 79 def changeset(web, req, tmpl):
79 80 return web.changeset(tmpl, web.changectx(req))
80 81
81 82 rev = changeset
82 83
83 84 def manifest(web, req, tmpl):
84 85 return web.manifest(tmpl, web.changectx(req),
85 86 web.cleanpath(req.form['path'][0]))
86 87
87 88 def tags(web, req, tmpl):
88 89 return web.tags(tmpl)
89 90
90 91 def summary(web, req, tmpl):
91 92 return web.summary(tmpl)
92 93
93 94 def filediff(web, req, tmpl):
94 95 return web.filediff(tmpl, web.filectx(req))
95 96
96 97 diff = filediff
97 98
98 99 def annotate(web, req, tmpl):
99 100 return web.fileannotate(tmpl, web.filectx(req))
100 101
101 102 def filelog(web, req, tmpl):
102 103 return web.filelog(tmpl, web.filectx(req))
103 104
104 105 def archive(web, req, tmpl):
105 106 type_ = req.form['type'][0]
106 107 allowed = web.configlist("web", "allow_archive")
107 108 if (type_ in web.archives and (type_ in allowed or
108 109 web.configbool("web", "allow" + type_, False))):
109 110 web.archive(tmpl, req, req.form['node'][0], type_)
110 111 return []
111 112 raise ErrorResponse(HTTP_NOT_FOUND, 'Unsupported archive type: %s' % type_)
112 113
113 114 def static(web, req, tmpl):
114 115 fname = req.form['file'][0]
115 116 # a repo owner may set web.static in .hg/hgrc to get any file
116 117 # readable by the user running the CGI script
117 118 static = web.config("web", "static",
118 119 os.path.join(web.templatepath, "static"),
119 120 untrusted=False)
120 121 return [staticfile(static, fname, req)]
General Comments 0
You need to be logged in to leave comments. Login now