##// END OF EJS Templates
replace "i in range(len(xs))" with "i, x in enumerate(xs)"...
Martin Geisler -
r8632:9e055cfd default
parent child Browse files
Show More
@@ -1,359 +1,358 b''
1 # Minimal support for git commands on an hg repository
1 # Minimal support for git commands on an hg repository
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 '''browsing the repository in a graphical way
8 '''browsing the repository in a graphical way
9
9
10 The hgk extension allows browsing the history of a repository in a
10 The hgk extension allows browsing the history of a repository in a
11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
12 distributed with Mercurial.)
12 distributed with Mercurial.)
13
13
14 hgk consists of two parts: a Tcl script that does the displaying and
14 hgk consists of two parts: a Tcl script that does the displaying and
15 querying of information, and an extension to mercurial named hgk.py,
15 querying of information, and an extension to mercurial named hgk.py,
16 which provides hooks for hgk to get information. hgk can be found in
16 which provides hooks for hgk to get information. hgk can be found in
17 the contrib directory, and hgk.py can be found in the hgext directory.
17 the contrib directory, and hgk.py can be found in the hgext directory.
18
18
19 To load the hgext.py extension, add it to your .hgrc file (you have to
19 To load the hgext.py extension, add it to your .hgrc file (you have to
20 use your global $HOME/.hgrc file, not one in a repository). You can
20 use your global $HOME/.hgrc file, not one in a repository). You can
21 specify an absolute path:
21 specify an absolute path:
22
22
23 [extensions]
23 [extensions]
24 hgk=/usr/local/lib/hgk.py
24 hgk=/usr/local/lib/hgk.py
25
25
26 Mercurial can also scan the default python library path for a file
26 Mercurial can also scan the default python library path for a file
27 named 'hgk.py' if you set hgk empty:
27 named 'hgk.py' if you set hgk empty:
28
28
29 [extensions]
29 [extensions]
30 hgk=
30 hgk=
31
31
32 The hg view command will launch the hgk Tcl script. For this command
32 The hg view command will launch the hgk Tcl script. For this command
33 to work, hgk must be in your search path. Alternately, you can specify
33 to work, hgk must be in your search path. Alternately, you can specify
34 the path to hgk in your .hgrc file:
34 the path to hgk in your .hgrc file:
35
35
36 [hgk]
36 [hgk]
37 path=/location/of/hgk
37 path=/location/of/hgk
38
38
39 hgk can make use of the extdiff extension to visualize revisions.
39 hgk can make use of the extdiff extension to visualize revisions.
40 Assuming you had already configured extdiff vdiff command, just add:
40 Assuming you had already configured extdiff vdiff command, just add:
41
41
42 [hgk]
42 [hgk]
43 vdiff=vdiff
43 vdiff=vdiff
44
44
45 Revisions context menu will now display additional entries to fire
45 Revisions context menu will now display additional entries to fire
46 vdiff on hovered and selected revisions.'''
46 vdiff on hovered and selected revisions.'''
47
47
48 import os
48 import os
49 from mercurial import commands, util, patch, revlog, cmdutil
49 from mercurial import commands, util, patch, revlog, cmdutil
50 from mercurial.node import nullid, nullrev, short
50 from mercurial.node import nullid, nullrev, short
51 from mercurial.i18n import _
51 from mercurial.i18n import _
52
52
53 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
53 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
54 """diff trees from two commits"""
54 """diff trees from two commits"""
55 def __difftree(repo, node1, node2, files=[]):
55 def __difftree(repo, node1, node2, files=[]):
56 assert node2 is not None
56 assert node2 is not None
57 mmap = repo[node1].manifest()
57 mmap = repo[node1].manifest()
58 mmap2 = repo[node2].manifest()
58 mmap2 = repo[node2].manifest()
59 m = cmdutil.match(repo, files)
59 m = cmdutil.match(repo, files)
60 modified, added, removed = repo.status(node1, node2, m)[:3]
60 modified, added, removed = repo.status(node1, node2, m)[:3]
61 empty = short(nullid)
61 empty = short(nullid)
62
62
63 for f in modified:
63 for f in modified:
64 # TODO get file permissions
64 # TODO get file permissions
65 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
65 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
66 (short(mmap[f]), short(mmap2[f]), f, f))
66 (short(mmap[f]), short(mmap2[f]), f, f))
67 for f in added:
67 for f in added:
68 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
68 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
69 (empty, short(mmap2[f]), f, f))
69 (empty, short(mmap2[f]), f, f))
70 for f in removed:
70 for f in removed:
71 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
71 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
72 (short(mmap[f]), empty, f, f))
72 (short(mmap[f]), empty, f, f))
73 ##
73 ##
74
74
75 while True:
75 while True:
76 if opts['stdin']:
76 if opts['stdin']:
77 try:
77 try:
78 line = raw_input().split(' ')
78 line = raw_input().split(' ')
79 node1 = line[0]
79 node1 = line[0]
80 if len(line) > 1:
80 if len(line) > 1:
81 node2 = line[1]
81 node2 = line[1]
82 else:
82 else:
83 node2 = None
83 node2 = None
84 except EOFError:
84 except EOFError:
85 break
85 break
86 node1 = repo.lookup(node1)
86 node1 = repo.lookup(node1)
87 if node2:
87 if node2:
88 node2 = repo.lookup(node2)
88 node2 = repo.lookup(node2)
89 else:
89 else:
90 node2 = node1
90 node2 = node1
91 node1 = repo.changelog.parents(node1)[0]
91 node1 = repo.changelog.parents(node1)[0]
92 if opts['patch']:
92 if opts['patch']:
93 if opts['pretty']:
93 if opts['pretty']:
94 catcommit(ui, repo, node2, "")
94 catcommit(ui, repo, node2, "")
95 m = cmdutil.match(repo, files)
95 m = cmdutil.match(repo, files)
96 chunks = patch.diff(repo, node1, node2, match=m,
96 chunks = patch.diff(repo, node1, node2, match=m,
97 opts=patch.diffopts(ui, {'git': True}))
97 opts=patch.diffopts(ui, {'git': True}))
98 for chunk in chunks:
98 for chunk in chunks:
99 ui.write(chunk)
99 ui.write(chunk)
100 else:
100 else:
101 __difftree(repo, node1, node2, files=files)
101 __difftree(repo, node1, node2, files=files)
102 if not opts['stdin']:
102 if not opts['stdin']:
103 break
103 break
104
104
105 def catcommit(ui, repo, n, prefix, ctx=None):
105 def catcommit(ui, repo, n, prefix, ctx=None):
106 nlprefix = '\n' + prefix;
106 nlprefix = '\n' + prefix;
107 if ctx is None:
107 if ctx is None:
108 ctx = repo[n]
108 ctx = repo[n]
109 ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
109 ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
110 for p in ctx.parents():
110 for p in ctx.parents():
111 ui.write("parent %s\n" % p)
111 ui.write("parent %s\n" % p)
112
112
113 date = ctx.date()
113 date = ctx.date()
114 description = ctx.description().replace("\0", "")
114 description = ctx.description().replace("\0", "")
115 lines = description.splitlines()
115 lines = description.splitlines()
116 if lines and lines[-1].startswith('committer:'):
116 if lines and lines[-1].startswith('committer:'):
117 committer = lines[-1].split(': ')[1].rstrip()
117 committer = lines[-1].split(': ')[1].rstrip()
118 else:
118 else:
119 committer = ctx.user()
119 committer = ctx.user()
120
120
121 ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
121 ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
122 ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
122 ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
123 ui.write("revision %d\n" % ctx.rev())
123 ui.write("revision %d\n" % ctx.rev())
124 ui.write("branch %s\n\n" % ctx.branch())
124 ui.write("branch %s\n\n" % ctx.branch())
125
125
126 if prefix != "":
126 if prefix != "":
127 ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
127 ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
128 else:
128 else:
129 ui.write(description + "\n")
129 ui.write(description + "\n")
130 if prefix:
130 if prefix:
131 ui.write('\0')
131 ui.write('\0')
132
132
133 def base(ui, repo, node1, node2):
133 def base(ui, repo, node1, node2):
134 """output common ancestor information"""
134 """output common ancestor information"""
135 node1 = repo.lookup(node1)
135 node1 = repo.lookup(node1)
136 node2 = repo.lookup(node2)
136 node2 = repo.lookup(node2)
137 n = repo.changelog.ancestor(node1, node2)
137 n = repo.changelog.ancestor(node1, node2)
138 ui.write(short(n) + "\n")
138 ui.write(short(n) + "\n")
139
139
140 def catfile(ui, repo, type=None, r=None, **opts):
140 def catfile(ui, repo, type=None, r=None, **opts):
141 """cat a specific revision"""
141 """cat a specific revision"""
142 # in stdin mode, every line except the commit is prefixed with two
142 # in stdin mode, every line except the commit is prefixed with two
143 # spaces. This way the our caller can find the commit without magic
143 # spaces. This way the our caller can find the commit without magic
144 # strings
144 # strings
145 #
145 #
146 prefix = ""
146 prefix = ""
147 if opts['stdin']:
147 if opts['stdin']:
148 try:
148 try:
149 (type, r) = raw_input().split(' ');
149 (type, r) = raw_input().split(' ');
150 prefix = " "
150 prefix = " "
151 except EOFError:
151 except EOFError:
152 return
152 return
153
153
154 else:
154 else:
155 if not type or not r:
155 if not type or not r:
156 ui.warn(_("cat-file: type or revision not supplied\n"))
156 ui.warn(_("cat-file: type or revision not supplied\n"))
157 commands.help_(ui, 'cat-file')
157 commands.help_(ui, 'cat-file')
158
158
159 while r:
159 while r:
160 if type != "commit":
160 if type != "commit":
161 ui.warn(_("aborting hg cat-file only understands commits\n"))
161 ui.warn(_("aborting hg cat-file only understands commits\n"))
162 return 1;
162 return 1;
163 n = repo.lookup(r)
163 n = repo.lookup(r)
164 catcommit(ui, repo, n, prefix)
164 catcommit(ui, repo, n, prefix)
165 if opts['stdin']:
165 if opts['stdin']:
166 try:
166 try:
167 (type, r) = raw_input().split(' ');
167 (type, r) = raw_input().split(' ');
168 except EOFError:
168 except EOFError:
169 break
169 break
170 else:
170 else:
171 break
171 break
172
172
173 # git rev-tree is a confusing thing. You can supply a number of
173 # git rev-tree is a confusing thing. You can supply a number of
174 # commit sha1s on the command line, and it walks the commit history
174 # commit sha1s on the command line, and it walks the commit history
175 # telling you which commits are reachable from the supplied ones via
175 # telling you which commits are reachable from the supplied ones via
176 # a bitmask based on arg position.
176 # a bitmask based on arg position.
177 # you can specify a commit to stop at by starting the sha1 with ^
177 # you can specify a commit to stop at by starting the sha1 with ^
178 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
178 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
179 def chlogwalk():
179 def chlogwalk():
180 count = len(repo)
180 count = len(repo)
181 i = count
181 i = count
182 l = [0] * 100
182 l = [0] * 100
183 chunk = 100
183 chunk = 100
184 while True:
184 while True:
185 if chunk > i:
185 if chunk > i:
186 chunk = i
186 chunk = i
187 i = 0
187 i = 0
188 else:
188 else:
189 i -= chunk
189 i -= chunk
190
190
191 for x in xrange(chunk):
191 for x in xrange(chunk):
192 if i + x >= count:
192 if i + x >= count:
193 l[chunk - x:] = [0] * (chunk - x)
193 l[chunk - x:] = [0] * (chunk - x)
194 break
194 break
195 if full != None:
195 if full != None:
196 l[x] = repo[i + x]
196 l[x] = repo[i + x]
197 l[x].changeset() # force reading
197 l[x].changeset() # force reading
198 else:
198 else:
199 l[x] = 1
199 l[x] = 1
200 for x in xrange(chunk-1, -1, -1):
200 for x in xrange(chunk-1, -1, -1):
201 if l[x] != 0:
201 if l[x] != 0:
202 yield (i + x, full != None and l[x] or None)
202 yield (i + x, full != None and l[x] or None)
203 if i == 0:
203 if i == 0:
204 break
204 break
205
205
206 # calculate and return the reachability bitmask for sha
206 # calculate and return the reachability bitmask for sha
207 def is_reachable(ar, reachable, sha):
207 def is_reachable(ar, reachable, sha):
208 if len(ar) == 0:
208 if len(ar) == 0:
209 return 1
209 return 1
210 mask = 0
210 mask = 0
211 for i in xrange(len(ar)):
211 for i in xrange(len(ar)):
212 if sha in reachable[i]:
212 if sha in reachable[i]:
213 mask |= 1 << i
213 mask |= 1 << i
214
214
215 return mask
215 return mask
216
216
217 reachable = []
217 reachable = []
218 stop_sha1 = []
218 stop_sha1 = []
219 want_sha1 = []
219 want_sha1 = []
220 count = 0
220 count = 0
221
221
222 # figure out which commits they are asking for and which ones they
222 # figure out which commits they are asking for and which ones they
223 # want us to stop on
223 # want us to stop on
224 for i in xrange(len(args)):
224 for i, arg in enumerate(args):
225 if args[i].startswith('^'):
225 if arg.startswith('^'):
226 s = repo.lookup(args[i][1:])
226 s = repo.lookup(arg[1:])
227 stop_sha1.append(s)
227 stop_sha1.append(s)
228 want_sha1.append(s)
228 want_sha1.append(s)
229 elif args[i] != 'HEAD':
229 elif arg != 'HEAD':
230 want_sha1.append(repo.lookup(args[i]))
230 want_sha1.append(repo.lookup(arg))
231
231
232 # calculate the graph for the supplied commits
232 # calculate the graph for the supplied commits
233 for i in xrange(len(want_sha1)):
233 for i, n in enumerate(want_sha1):
234 reachable.append(set());
234 reachable.append(set());
235 n = want_sha1[i];
236 visit = [n];
235 visit = [n];
237 reachable[i].add(n)
236 reachable[i].add(n)
238 while visit:
237 while visit:
239 n = visit.pop(0)
238 n = visit.pop(0)
240 if n in stop_sha1:
239 if n in stop_sha1:
241 continue
240 continue
242 for p in repo.changelog.parents(n):
241 for p in repo.changelog.parents(n):
243 if p not in reachable[i]:
242 if p not in reachable[i]:
244 reachable[i].add(p)
243 reachable[i].add(p)
245 visit.append(p)
244 visit.append(p)
246 if p in stop_sha1:
245 if p in stop_sha1:
247 continue
246 continue
248
247
249 # walk the repository looking for commits that are in our
248 # walk the repository looking for commits that are in our
250 # reachability graph
249 # reachability graph
251 for i, ctx in chlogwalk():
250 for i, ctx in chlogwalk():
252 n = repo.changelog.node(i)
251 n = repo.changelog.node(i)
253 mask = is_reachable(want_sha1, reachable, n)
252 mask = is_reachable(want_sha1, reachable, n)
254 if mask:
253 if mask:
255 parentstr = ""
254 parentstr = ""
256 if parents:
255 if parents:
257 pp = repo.changelog.parents(n)
256 pp = repo.changelog.parents(n)
258 if pp[0] != nullid:
257 if pp[0] != nullid:
259 parentstr += " " + short(pp[0])
258 parentstr += " " + short(pp[0])
260 if pp[1] != nullid:
259 if pp[1] != nullid:
261 parentstr += " " + short(pp[1])
260 parentstr += " " + short(pp[1])
262 if not full:
261 if not full:
263 ui.write("%s%s\n" % (short(n), parentstr))
262 ui.write("%s%s\n" % (short(n), parentstr))
264 elif full == "commit":
263 elif full == "commit":
265 ui.write("%s%s\n" % (short(n), parentstr))
264 ui.write("%s%s\n" % (short(n), parentstr))
266 catcommit(ui, repo, n, ' ', ctx)
265 catcommit(ui, repo, n, ' ', ctx)
267 else:
266 else:
268 (p1, p2) = repo.changelog.parents(n)
267 (p1, p2) = repo.changelog.parents(n)
269 (h, h1, h2) = map(short, (n, p1, p2))
268 (h, h1, h2) = map(short, (n, p1, p2))
270 (i1, i2) = map(repo.changelog.rev, (p1, p2))
269 (i1, i2) = map(repo.changelog.rev, (p1, p2))
271
270
272 date = ctx.date()[0]
271 date = ctx.date()[0]
273 ui.write("%s %s:%s" % (date, h, mask))
272 ui.write("%s %s:%s" % (date, h, mask))
274 mask = is_reachable(want_sha1, reachable, p1)
273 mask = is_reachable(want_sha1, reachable, p1)
275 if i1 != nullrev and mask > 0:
274 if i1 != nullrev and mask > 0:
276 ui.write("%s:%s " % (h1, mask)),
275 ui.write("%s:%s " % (h1, mask)),
277 mask = is_reachable(want_sha1, reachable, p2)
276 mask = is_reachable(want_sha1, reachable, p2)
278 if i2 != nullrev and mask > 0:
277 if i2 != nullrev and mask > 0:
279 ui.write("%s:%s " % (h2, mask))
278 ui.write("%s:%s " % (h2, mask))
280 ui.write("\n")
279 ui.write("\n")
281 if maxnr and count >= maxnr:
280 if maxnr and count >= maxnr:
282 break
281 break
283 count += 1
282 count += 1
284
283
285 def revparse(ui, repo, *revs, **opts):
284 def revparse(ui, repo, *revs, **opts):
286 """parse given revisions"""
285 """parse given revisions"""
287 def revstr(rev):
286 def revstr(rev):
288 if rev == 'HEAD':
287 if rev == 'HEAD':
289 rev = 'tip'
288 rev = 'tip'
290 return revlog.hex(repo.lookup(rev))
289 return revlog.hex(repo.lookup(rev))
291
290
292 for r in revs:
291 for r in revs:
293 revrange = r.split(':', 1)
292 revrange = r.split(':', 1)
294 ui.write('%s\n' % revstr(revrange[0]))
293 ui.write('%s\n' % revstr(revrange[0]))
295 if len(revrange) == 2:
294 if len(revrange) == 2:
296 ui.write('^%s\n' % revstr(revrange[1]))
295 ui.write('^%s\n' % revstr(revrange[1]))
297
296
298 # git rev-list tries to order things by date, and has the ability to stop
297 # git rev-list tries to order things by date, and has the ability to stop
299 # at a given commit without walking the whole repo. TODO add the stop
298 # at a given commit without walking the whole repo. TODO add the stop
300 # parameter
299 # parameter
301 def revlist(ui, repo, *revs, **opts):
300 def revlist(ui, repo, *revs, **opts):
302 """print revisions"""
301 """print revisions"""
303 if opts['header']:
302 if opts['header']:
304 full = "commit"
303 full = "commit"
305 else:
304 else:
306 full = None
305 full = None
307 copy = [x for x in revs]
306 copy = [x for x in revs]
308 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
307 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
309
308
310 def config(ui, repo, **opts):
309 def config(ui, repo, **opts):
311 """print extension options"""
310 """print extension options"""
312 def writeopt(name, value):
311 def writeopt(name, value):
313 ui.write('k=%s\nv=%s\n' % (name, value))
312 ui.write('k=%s\nv=%s\n' % (name, value))
314
313
315 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
314 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
316
315
317
316
318 def view(ui, repo, *etc, **opts):
317 def view(ui, repo, *etc, **opts):
319 "start interactive history viewer"
318 "start interactive history viewer"
320 os.chdir(repo.root)
319 os.chdir(repo.root)
321 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
320 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
322 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
321 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
323 ui.debug(_("running %s\n") % cmd)
322 ui.debug(_("running %s\n") % cmd)
324 util.system(cmd)
323 util.system(cmd)
325
324
326 cmdtable = {
325 cmdtable = {
327 "^view":
326 "^view":
328 (view,
327 (view,
329 [('l', 'limit', '', _('limit number of changes displayed'))],
328 [('l', 'limit', '', _('limit number of changes displayed'))],
330 _('hg view [-l LIMIT] [REVRANGE]')),
329 _('hg view [-l LIMIT] [REVRANGE]')),
331 "debug-diff-tree":
330 "debug-diff-tree":
332 (difftree,
331 (difftree,
333 [('p', 'patch', None, _('generate patch')),
332 [('p', 'patch', None, _('generate patch')),
334 ('r', 'recursive', None, _('recursive')),
333 ('r', 'recursive', None, _('recursive')),
335 ('P', 'pretty', None, _('pretty')),
334 ('P', 'pretty', None, _('pretty')),
336 ('s', 'stdin', None, _('stdin')),
335 ('s', 'stdin', None, _('stdin')),
337 ('C', 'copy', None, _('detect copies')),
336 ('C', 'copy', None, _('detect copies')),
338 ('S', 'search', "", _('search'))],
337 ('S', 'search', "", _('search'))],
339 _('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')),
338 _('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')),
340 "debug-cat-file":
339 "debug-cat-file":
341 (catfile,
340 (catfile,
342 [('s', 'stdin', None, _('stdin'))],
341 [('s', 'stdin', None, _('stdin'))],
343 _('hg debug-cat-file [OPTION]... TYPE FILE')),
342 _('hg debug-cat-file [OPTION]... TYPE FILE')),
344 "debug-config":
343 "debug-config":
345 (config, [], _('hg debug-config')),
344 (config, [], _('hg debug-config')),
346 "debug-merge-base":
345 "debug-merge-base":
347 (base, [], _('hg debug-merge-base node node')),
346 (base, [], _('hg debug-merge-base node node')),
348 "debug-rev-parse":
347 "debug-rev-parse":
349 (revparse,
348 (revparse,
350 [('', 'default', '', _('ignored'))],
349 [('', 'default', '', _('ignored'))],
351 _('hg debug-rev-parse REV')),
350 _('hg debug-rev-parse REV')),
352 "debug-rev-list":
351 "debug-rev-list":
353 (revlist,
352 (revlist,
354 [('H', 'header', None, _('header')),
353 [('H', 'header', None, _('header')),
355 ('t', 'topo-order', None, _('topo-order')),
354 ('t', 'topo-order', None, _('topo-order')),
356 ('p', 'parents', None, _('parents')),
355 ('p', 'parents', None, _('parents')),
357 ('n', 'max-count', 0, _('max-count'))],
356 ('n', 'max-count', 0, _('max-count'))],
358 _('hg debug-rev-list [options] revs')),
357 _('hg debug-rev-list [options] revs')),
359 }
358 }
@@ -1,2637 +1,2636 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 '''patch management and development
8 '''patch management and development
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use "hg help command" for more details):
17 Common tasks (use "hg help command" for more details):
18
18
19 prepare repository to work with patches qinit
19 prepare repository to work with patches qinit
20 create new patch qnew
20 create new patch qnew
21 import existing patch qimport
21 import existing patch qimport
22
22
23 print patch series qseries
23 print patch series qseries
24 print applied patches qapplied
24 print applied patches qapplied
25 print name of top applied patch qtop
25 print name of top applied patch qtop
26
26
27 add known patch to applied stack qpush
27 add known patch to applied stack qpush
28 remove patch from applied stack qpop
28 remove patch from applied stack qpop
29 refresh contents of top applied patch qrefresh
29 refresh contents of top applied patch qrefresh
30 '''
30 '''
31
31
32 from mercurial.i18n import _
32 from mercurial.i18n import _
33 from mercurial.node import bin, hex, short, nullid, nullrev
33 from mercurial.node import bin, hex, short, nullid, nullrev
34 from mercurial.lock import release
34 from mercurial.lock import release
35 from mercurial import commands, cmdutil, hg, patch, util
35 from mercurial import commands, cmdutil, hg, patch, util
36 from mercurial import repair, extensions, url, error
36 from mercurial import repair, extensions, url, error
37 import os, sys, re, errno
37 import os, sys, re, errno
38
38
39 commands.norepo += " qclone"
39 commands.norepo += " qclone"
40
40
41 # Patch names looks like unix-file names.
41 # Patch names looks like unix-file names.
42 # They must be joinable with queue directory and result in the patch path.
42 # They must be joinable with queue directory and result in the patch path.
43 normname = util.normpath
43 normname = util.normpath
44
44
45 class statusentry:
45 class statusentry:
46 def __init__(self, rev, name=None):
46 def __init__(self, rev, name=None):
47 if not name:
47 if not name:
48 fields = rev.split(':', 1)
48 fields = rev.split(':', 1)
49 if len(fields) == 2:
49 if len(fields) == 2:
50 self.rev, self.name = fields
50 self.rev, self.name = fields
51 else:
51 else:
52 self.rev, self.name = None, None
52 self.rev, self.name = None, None
53 else:
53 else:
54 self.rev, self.name = rev, name
54 self.rev, self.name = rev, name
55
55
56 def __str__(self):
56 def __str__(self):
57 return self.rev + ':' + self.name
57 return self.rev + ':' + self.name
58
58
59 class patchheader(object):
59 class patchheader(object):
60 def __init__(self, message, comments, user, date, haspatch):
60 def __init__(self, message, comments, user, date, haspatch):
61 self.message = message
61 self.message = message
62 self.comments = comments
62 self.comments = comments
63 self.user = user
63 self.user = user
64 self.date = date
64 self.date = date
65 self.haspatch = haspatch
65 self.haspatch = haspatch
66
66
67 def setuser(self, user):
67 def setuser(self, user):
68 if not self.setheader(['From: ', '# User '], user):
68 if not self.setheader(['From: ', '# User '], user):
69 try:
69 try:
70 patchheaderat = self.comments.index('# HG changeset patch')
70 patchheaderat = self.comments.index('# HG changeset patch')
71 self.comments.insert(patchheaderat + 1,'# User ' + user)
71 self.comments.insert(patchheaderat + 1,'# User ' + user)
72 except ValueError:
72 except ValueError:
73 self.comments = ['From: ' + user, ''] + self.comments
73 self.comments = ['From: ' + user, ''] + self.comments
74 self.user = user
74 self.user = user
75
75
76 def setdate(self, date):
76 def setdate(self, date):
77 if self.setheader(['# Date '], date):
77 if self.setheader(['# Date '], date):
78 self.date = date
78 self.date = date
79
79
80 def setmessage(self, message):
80 def setmessage(self, message):
81 if self.comments:
81 if self.comments:
82 self._delmsg()
82 self._delmsg()
83 self.message = [message]
83 self.message = [message]
84 self.comments += self.message
84 self.comments += self.message
85
85
86 def setheader(self, prefixes, new):
86 def setheader(self, prefixes, new):
87 '''Update all references to a field in the patch header.
87 '''Update all references to a field in the patch header.
88 If none found, add it email style.'''
88 If none found, add it email style.'''
89 res = False
89 res = False
90 for prefix in prefixes:
90 for prefix in prefixes:
91 for i in xrange(len(self.comments)):
91 for i in xrange(len(self.comments)):
92 if self.comments[i].startswith(prefix):
92 if self.comments[i].startswith(prefix):
93 self.comments[i] = prefix + new
93 self.comments[i] = prefix + new
94 res = True
94 res = True
95 break
95 break
96 return res
96 return res
97
97
98 def __str__(self):
98 def __str__(self):
99 if not self.comments:
99 if not self.comments:
100 return ''
100 return ''
101 return '\n'.join(self.comments) + '\n\n'
101 return '\n'.join(self.comments) + '\n\n'
102
102
103 def _delmsg(self):
103 def _delmsg(self):
104 '''Remove existing message, keeping the rest of the comments fields.
104 '''Remove existing message, keeping the rest of the comments fields.
105 If comments contains 'subject: ', message will prepend
105 If comments contains 'subject: ', message will prepend
106 the field and a blank line.'''
106 the field and a blank line.'''
107 if self.message:
107 if self.message:
108 subj = 'subject: ' + self.message[0].lower()
108 subj = 'subject: ' + self.message[0].lower()
109 for i in xrange(len(self.comments)):
109 for i in xrange(len(self.comments)):
110 if subj == self.comments[i].lower():
110 if subj == self.comments[i].lower():
111 del self.comments[i]
111 del self.comments[i]
112 self.message = self.message[2:]
112 self.message = self.message[2:]
113 break
113 break
114 ci = 0
114 ci = 0
115 for mi in xrange(len(self.message)):
115 for mi in self.message:
116 while self.message[mi] != self.comments[ci]:
116 while mi != self.comments[ci]:
117 ci += 1
117 ci += 1
118 del self.comments[ci]
118 del self.comments[ci]
119
119
120 class queue:
120 class queue:
121 def __init__(self, ui, path, patchdir=None):
121 def __init__(self, ui, path, patchdir=None):
122 self.basepath = path
122 self.basepath = path
123 self.path = patchdir or os.path.join(path, "patches")
123 self.path = patchdir or os.path.join(path, "patches")
124 self.opener = util.opener(self.path)
124 self.opener = util.opener(self.path)
125 self.ui = ui
125 self.ui = ui
126 self.applied_dirty = 0
126 self.applied_dirty = 0
127 self.series_dirty = 0
127 self.series_dirty = 0
128 self.series_path = "series"
128 self.series_path = "series"
129 self.status_path = "status"
129 self.status_path = "status"
130 self.guards_path = "guards"
130 self.guards_path = "guards"
131 self.active_guards = None
131 self.active_guards = None
132 self.guards_dirty = False
132 self.guards_dirty = False
133 self._diffopts = None
133 self._diffopts = None
134
134
135 @util.propertycache
135 @util.propertycache
136 def applied(self):
136 def applied(self):
137 if os.path.exists(self.join(self.status_path)):
137 if os.path.exists(self.join(self.status_path)):
138 lines = self.opener(self.status_path).read().splitlines()
138 lines = self.opener(self.status_path).read().splitlines()
139 return [statusentry(l) for l in lines]
139 return [statusentry(l) for l in lines]
140 return []
140 return []
141
141
142 @util.propertycache
142 @util.propertycache
143 def full_series(self):
143 def full_series(self):
144 if os.path.exists(self.join(self.series_path)):
144 if os.path.exists(self.join(self.series_path)):
145 return self.opener(self.series_path).read().splitlines()
145 return self.opener(self.series_path).read().splitlines()
146 return []
146 return []
147
147
148 @util.propertycache
148 @util.propertycache
149 def series(self):
149 def series(self):
150 self.parse_series()
150 self.parse_series()
151 return self.series
151 return self.series
152
152
153 @util.propertycache
153 @util.propertycache
154 def series_guards(self):
154 def series_guards(self):
155 self.parse_series()
155 self.parse_series()
156 return self.series_guards
156 return self.series_guards
157
157
158 def invalidate(self):
158 def invalidate(self):
159 for a in 'applied full_series series series_guards'.split():
159 for a in 'applied full_series series series_guards'.split():
160 if a in self.__dict__:
160 if a in self.__dict__:
161 delattr(self, a)
161 delattr(self, a)
162 self.applied_dirty = 0
162 self.applied_dirty = 0
163 self.series_dirty = 0
163 self.series_dirty = 0
164 self.guards_dirty = False
164 self.guards_dirty = False
165 self.active_guards = None
165 self.active_guards = None
166
166
167 def diffopts(self):
167 def diffopts(self):
168 if self._diffopts is None:
168 if self._diffopts is None:
169 self._diffopts = patch.diffopts(self.ui)
169 self._diffopts = patch.diffopts(self.ui)
170 return self._diffopts
170 return self._diffopts
171
171
172 def join(self, *p):
172 def join(self, *p):
173 return os.path.join(self.path, *p)
173 return os.path.join(self.path, *p)
174
174
175 def find_series(self, patch):
175 def find_series(self, patch):
176 pre = re.compile("(\s*)([^#]+)")
176 pre = re.compile("(\s*)([^#]+)")
177 index = 0
177 index = 0
178 for l in self.full_series:
178 for l in self.full_series:
179 m = pre.match(l)
179 m = pre.match(l)
180 if m:
180 if m:
181 s = m.group(2)
181 s = m.group(2)
182 s = s.rstrip()
182 s = s.rstrip()
183 if s == patch:
183 if s == patch:
184 return index
184 return index
185 index += 1
185 index += 1
186 return None
186 return None
187
187
188 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
188 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
189
189
190 def parse_series(self):
190 def parse_series(self):
191 self.series = []
191 self.series = []
192 self.series_guards = []
192 self.series_guards = []
193 for l in self.full_series:
193 for l in self.full_series:
194 h = l.find('#')
194 h = l.find('#')
195 if h == -1:
195 if h == -1:
196 patch = l
196 patch = l
197 comment = ''
197 comment = ''
198 elif h == 0:
198 elif h == 0:
199 continue
199 continue
200 else:
200 else:
201 patch = l[:h]
201 patch = l[:h]
202 comment = l[h:]
202 comment = l[h:]
203 patch = patch.strip()
203 patch = patch.strip()
204 if patch:
204 if patch:
205 if patch in self.series:
205 if patch in self.series:
206 raise util.Abort(_('%s appears more than once in %s') %
206 raise util.Abort(_('%s appears more than once in %s') %
207 (patch, self.join(self.series_path)))
207 (patch, self.join(self.series_path)))
208 self.series.append(patch)
208 self.series.append(patch)
209 self.series_guards.append(self.guard_re.findall(comment))
209 self.series_guards.append(self.guard_re.findall(comment))
210
210
211 def check_guard(self, guard):
211 def check_guard(self, guard):
212 if not guard:
212 if not guard:
213 return _('guard cannot be an empty string')
213 return _('guard cannot be an empty string')
214 bad_chars = '# \t\r\n\f'
214 bad_chars = '# \t\r\n\f'
215 first = guard[0]
215 first = guard[0]
216 if first in '-+':
216 if first in '-+':
217 return (_('guard %r starts with invalid character: %r') %
217 return (_('guard %r starts with invalid character: %r') %
218 (guard, first))
218 (guard, first))
219 for c in bad_chars:
219 for c in bad_chars:
220 if c in guard:
220 if c in guard:
221 return _('invalid character in guard %r: %r') % (guard, c)
221 return _('invalid character in guard %r: %r') % (guard, c)
222
222
223 def set_active(self, guards):
223 def set_active(self, guards):
224 for guard in guards:
224 for guard in guards:
225 bad = self.check_guard(guard)
225 bad = self.check_guard(guard)
226 if bad:
226 if bad:
227 raise util.Abort(bad)
227 raise util.Abort(bad)
228 guards = sorted(set(guards))
228 guards = sorted(set(guards))
229 self.ui.debug(_('active guards: %s\n') % ' '.join(guards))
229 self.ui.debug(_('active guards: %s\n') % ' '.join(guards))
230 self.active_guards = guards
230 self.active_guards = guards
231 self.guards_dirty = True
231 self.guards_dirty = True
232
232
233 def active(self):
233 def active(self):
234 if self.active_guards is None:
234 if self.active_guards is None:
235 self.active_guards = []
235 self.active_guards = []
236 try:
236 try:
237 guards = self.opener(self.guards_path).read().split()
237 guards = self.opener(self.guards_path).read().split()
238 except IOError, err:
238 except IOError, err:
239 if err.errno != errno.ENOENT: raise
239 if err.errno != errno.ENOENT: raise
240 guards = []
240 guards = []
241 for i, guard in enumerate(guards):
241 for i, guard in enumerate(guards):
242 bad = self.check_guard(guard)
242 bad = self.check_guard(guard)
243 if bad:
243 if bad:
244 self.ui.warn('%s:%d: %s\n' %
244 self.ui.warn('%s:%d: %s\n' %
245 (self.join(self.guards_path), i + 1, bad))
245 (self.join(self.guards_path), i + 1, bad))
246 else:
246 else:
247 self.active_guards.append(guard)
247 self.active_guards.append(guard)
248 return self.active_guards
248 return self.active_guards
249
249
250 def set_guards(self, idx, guards):
250 def set_guards(self, idx, guards):
251 for g in guards:
251 for g in guards:
252 if len(g) < 2:
252 if len(g) < 2:
253 raise util.Abort(_('guard %r too short') % g)
253 raise util.Abort(_('guard %r too short') % g)
254 if g[0] not in '-+':
254 if g[0] not in '-+':
255 raise util.Abort(_('guard %r starts with invalid char') % g)
255 raise util.Abort(_('guard %r starts with invalid char') % g)
256 bad = self.check_guard(g[1:])
256 bad = self.check_guard(g[1:])
257 if bad:
257 if bad:
258 raise util.Abort(bad)
258 raise util.Abort(bad)
259 drop = self.guard_re.sub('', self.full_series[idx])
259 drop = self.guard_re.sub('', self.full_series[idx])
260 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
260 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
261 self.parse_series()
261 self.parse_series()
262 self.series_dirty = True
262 self.series_dirty = True
263
263
264 def pushable(self, idx):
264 def pushable(self, idx):
265 if isinstance(idx, str):
265 if isinstance(idx, str):
266 idx = self.series.index(idx)
266 idx = self.series.index(idx)
267 patchguards = self.series_guards[idx]
267 patchguards = self.series_guards[idx]
268 if not patchguards:
268 if not patchguards:
269 return True, None
269 return True, None
270 guards = self.active()
270 guards = self.active()
271 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
271 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
272 if exactneg:
272 if exactneg:
273 return False, exactneg[0]
273 return False, exactneg[0]
274 pos = [g for g in patchguards if g[0] == '+']
274 pos = [g for g in patchguards if g[0] == '+']
275 exactpos = [g for g in pos if g[1:] in guards]
275 exactpos = [g for g in pos if g[1:] in guards]
276 if pos:
276 if pos:
277 if exactpos:
277 if exactpos:
278 return True, exactpos[0]
278 return True, exactpos[0]
279 return False, pos
279 return False, pos
280 return True, ''
280 return True, ''
281
281
282 def explain_pushable(self, idx, all_patches=False):
282 def explain_pushable(self, idx, all_patches=False):
283 write = all_patches and self.ui.write or self.ui.warn
283 write = all_patches and self.ui.write or self.ui.warn
284 if all_patches or self.ui.verbose:
284 if all_patches or self.ui.verbose:
285 if isinstance(idx, str):
285 if isinstance(idx, str):
286 idx = self.series.index(idx)
286 idx = self.series.index(idx)
287 pushable, why = self.pushable(idx)
287 pushable, why = self.pushable(idx)
288 if all_patches and pushable:
288 if all_patches and pushable:
289 if why is None:
289 if why is None:
290 write(_('allowing %s - no guards in effect\n') %
290 write(_('allowing %s - no guards in effect\n') %
291 self.series[idx])
291 self.series[idx])
292 else:
292 else:
293 if not why:
293 if not why:
294 write(_('allowing %s - no matching negative guards\n') %
294 write(_('allowing %s - no matching negative guards\n') %
295 self.series[idx])
295 self.series[idx])
296 else:
296 else:
297 write(_('allowing %s - guarded by %r\n') %
297 write(_('allowing %s - guarded by %r\n') %
298 (self.series[idx], why))
298 (self.series[idx], why))
299 if not pushable:
299 if not pushable:
300 if why:
300 if why:
301 write(_('skipping %s - guarded by %r\n') %
301 write(_('skipping %s - guarded by %r\n') %
302 (self.series[idx], why))
302 (self.series[idx], why))
303 else:
303 else:
304 write(_('skipping %s - no matching guards\n') %
304 write(_('skipping %s - no matching guards\n') %
305 self.series[idx])
305 self.series[idx])
306
306
307 def save_dirty(self):
307 def save_dirty(self):
308 def write_list(items, path):
308 def write_list(items, path):
309 fp = self.opener(path, 'w')
309 fp = self.opener(path, 'w')
310 for i in items:
310 for i in items:
311 fp.write("%s\n" % i)
311 fp.write("%s\n" % i)
312 fp.close()
312 fp.close()
313 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
313 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
314 if self.series_dirty: write_list(self.full_series, self.series_path)
314 if self.series_dirty: write_list(self.full_series, self.series_path)
315 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
315 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
316
316
317 def readheaders(self, patch):
317 def readheaders(self, patch):
318 def eatdiff(lines):
318 def eatdiff(lines):
319 while lines:
319 while lines:
320 l = lines[-1]
320 l = lines[-1]
321 if (l.startswith("diff -") or
321 if (l.startswith("diff -") or
322 l.startswith("Index:") or
322 l.startswith("Index:") or
323 l.startswith("===========")):
323 l.startswith("===========")):
324 del lines[-1]
324 del lines[-1]
325 else:
325 else:
326 break
326 break
327 def eatempty(lines):
327 def eatempty(lines):
328 while lines:
328 while lines:
329 l = lines[-1]
329 l = lines[-1]
330 if re.match('\s*$', l):
330 if re.match('\s*$', l):
331 del lines[-1]
331 del lines[-1]
332 else:
332 else:
333 break
333 break
334
334
335 pf = self.join(patch)
335 pf = self.join(patch)
336 message = []
336 message = []
337 comments = []
337 comments = []
338 user = None
338 user = None
339 date = None
339 date = None
340 format = None
340 format = None
341 subject = None
341 subject = None
342 diffstart = 0
342 diffstart = 0
343
343
344 for line in file(pf):
344 for line in file(pf):
345 line = line.rstrip()
345 line = line.rstrip()
346 if line.startswith('diff --git'):
346 if line.startswith('diff --git'):
347 diffstart = 2
347 diffstart = 2
348 break
348 break
349 if diffstart:
349 if diffstart:
350 if line.startswith('+++ '):
350 if line.startswith('+++ '):
351 diffstart = 2
351 diffstart = 2
352 break
352 break
353 if line.startswith("--- "):
353 if line.startswith("--- "):
354 diffstart = 1
354 diffstart = 1
355 continue
355 continue
356 elif format == "hgpatch":
356 elif format == "hgpatch":
357 # parse values when importing the result of an hg export
357 # parse values when importing the result of an hg export
358 if line.startswith("# User "):
358 if line.startswith("# User "):
359 user = line[7:]
359 user = line[7:]
360 elif line.startswith("# Date "):
360 elif line.startswith("# Date "):
361 date = line[7:]
361 date = line[7:]
362 elif not line.startswith("# ") and line:
362 elif not line.startswith("# ") and line:
363 message.append(line)
363 message.append(line)
364 format = None
364 format = None
365 elif line == '# HG changeset patch':
365 elif line == '# HG changeset patch':
366 format = "hgpatch"
366 format = "hgpatch"
367 elif (format != "tagdone" and (line.startswith("Subject: ") or
367 elif (format != "tagdone" and (line.startswith("Subject: ") or
368 line.startswith("subject: "))):
368 line.startswith("subject: "))):
369 subject = line[9:]
369 subject = line[9:]
370 format = "tag"
370 format = "tag"
371 elif (format != "tagdone" and (line.startswith("From: ") or
371 elif (format != "tagdone" and (line.startswith("From: ") or
372 line.startswith("from: "))):
372 line.startswith("from: "))):
373 user = line[6:]
373 user = line[6:]
374 format = "tag"
374 format = "tag"
375 elif format == "tag" and line == "":
375 elif format == "tag" and line == "":
376 # when looking for tags (subject: from: etc) they
376 # when looking for tags (subject: from: etc) they
377 # end once you find a blank line in the source
377 # end once you find a blank line in the source
378 format = "tagdone"
378 format = "tagdone"
379 elif message or line:
379 elif message or line:
380 message.append(line)
380 message.append(line)
381 comments.append(line)
381 comments.append(line)
382
382
383 eatdiff(message)
383 eatdiff(message)
384 eatdiff(comments)
384 eatdiff(comments)
385 eatempty(message)
385 eatempty(message)
386 eatempty(comments)
386 eatempty(comments)
387
387
388 # make sure message isn't empty
388 # make sure message isn't empty
389 if format and format.startswith("tag") and subject:
389 if format and format.startswith("tag") and subject:
390 message.insert(0, "")
390 message.insert(0, "")
391 message.insert(0, subject)
391 message.insert(0, subject)
392 return patchheader(message, comments, user, date, diffstart > 1)
392 return patchheader(message, comments, user, date, diffstart > 1)
393
393
394 def removeundo(self, repo):
394 def removeundo(self, repo):
395 undo = repo.sjoin('undo')
395 undo = repo.sjoin('undo')
396 if not os.path.exists(undo):
396 if not os.path.exists(undo):
397 return
397 return
398 try:
398 try:
399 os.unlink(undo)
399 os.unlink(undo)
400 except OSError, inst:
400 except OSError, inst:
401 self.ui.warn(_('error removing undo: %s\n') % str(inst))
401 self.ui.warn(_('error removing undo: %s\n') % str(inst))
402
402
403 def printdiff(self, repo, node1, node2=None, files=None,
403 def printdiff(self, repo, node1, node2=None, files=None,
404 fp=None, changes=None, opts={}):
404 fp=None, changes=None, opts={}):
405 m = cmdutil.match(repo, files, opts)
405 m = cmdutil.match(repo, files, opts)
406 chunks = patch.diff(repo, node1, node2, m, changes, self.diffopts())
406 chunks = patch.diff(repo, node1, node2, m, changes, self.diffopts())
407 write = fp is None and repo.ui.write or fp.write
407 write = fp is None and repo.ui.write or fp.write
408 for chunk in chunks:
408 for chunk in chunks:
409 write(chunk)
409 write(chunk)
410
410
411 def mergeone(self, repo, mergeq, head, patch, rev):
411 def mergeone(self, repo, mergeq, head, patch, rev):
412 # first try just applying the patch
412 # first try just applying the patch
413 (err, n) = self.apply(repo, [ patch ], update_status=False,
413 (err, n) = self.apply(repo, [ patch ], update_status=False,
414 strict=True, merge=rev)
414 strict=True, merge=rev)
415
415
416 if err == 0:
416 if err == 0:
417 return (err, n)
417 return (err, n)
418
418
419 if n is None:
419 if n is None:
420 raise util.Abort(_("apply failed for patch %s") % patch)
420 raise util.Abort(_("apply failed for patch %s") % patch)
421
421
422 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
422 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
423
423
424 # apply failed, strip away that rev and merge.
424 # apply failed, strip away that rev and merge.
425 hg.clean(repo, head)
425 hg.clean(repo, head)
426 self.strip(repo, n, update=False, backup='strip')
426 self.strip(repo, n, update=False, backup='strip')
427
427
428 ctx = repo[rev]
428 ctx = repo[rev]
429 ret = hg.merge(repo, rev)
429 ret = hg.merge(repo, rev)
430 if ret:
430 if ret:
431 raise util.Abort(_("update returned %d") % ret)
431 raise util.Abort(_("update returned %d") % ret)
432 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
432 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
433 if n is None:
433 if n is None:
434 raise util.Abort(_("repo commit failed"))
434 raise util.Abort(_("repo commit failed"))
435 try:
435 try:
436 ph = mergeq.readheaders(patch)
436 ph = mergeq.readheaders(patch)
437 except:
437 except:
438 raise util.Abort(_("unable to read %s") % patch)
438 raise util.Abort(_("unable to read %s") % patch)
439
439
440 patchf = self.opener(patch, "w")
440 patchf = self.opener(patch, "w")
441 comments = str(ph)
441 comments = str(ph)
442 if comments:
442 if comments:
443 patchf.write(comments)
443 patchf.write(comments)
444 self.printdiff(repo, head, n, fp=patchf)
444 self.printdiff(repo, head, n, fp=patchf)
445 patchf.close()
445 patchf.close()
446 self.removeundo(repo)
446 self.removeundo(repo)
447 return (0, n)
447 return (0, n)
448
448
449 def qparents(self, repo, rev=None):
449 def qparents(self, repo, rev=None):
450 if rev is None:
450 if rev is None:
451 (p1, p2) = repo.dirstate.parents()
451 (p1, p2) = repo.dirstate.parents()
452 if p2 == nullid:
452 if p2 == nullid:
453 return p1
453 return p1
454 if len(self.applied) == 0:
454 if len(self.applied) == 0:
455 return None
455 return None
456 return bin(self.applied[-1].rev)
456 return bin(self.applied[-1].rev)
457 pp = repo.changelog.parents(rev)
457 pp = repo.changelog.parents(rev)
458 if pp[1] != nullid:
458 if pp[1] != nullid:
459 arevs = [ x.rev for x in self.applied ]
459 arevs = [ x.rev for x in self.applied ]
460 p0 = hex(pp[0])
460 p0 = hex(pp[0])
461 p1 = hex(pp[1])
461 p1 = hex(pp[1])
462 if p0 in arevs:
462 if p0 in arevs:
463 return pp[0]
463 return pp[0]
464 if p1 in arevs:
464 if p1 in arevs:
465 return pp[1]
465 return pp[1]
466 return pp[0]
466 return pp[0]
467
467
468 def mergepatch(self, repo, mergeq, series):
468 def mergepatch(self, repo, mergeq, series):
469 if len(self.applied) == 0:
469 if len(self.applied) == 0:
470 # each of the patches merged in will have two parents. This
470 # each of the patches merged in will have two parents. This
471 # can confuse the qrefresh, qdiff, and strip code because it
471 # can confuse the qrefresh, qdiff, and strip code because it
472 # needs to know which parent is actually in the patch queue.
472 # needs to know which parent is actually in the patch queue.
473 # so, we insert a merge marker with only one parent. This way
473 # so, we insert a merge marker with only one parent. This way
474 # the first patch in the queue is never a merge patch
474 # the first patch in the queue is never a merge patch
475 #
475 #
476 pname = ".hg.patches.merge.marker"
476 pname = ".hg.patches.merge.marker"
477 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
477 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
478 self.removeundo(repo)
478 self.removeundo(repo)
479 self.applied.append(statusentry(hex(n), pname))
479 self.applied.append(statusentry(hex(n), pname))
480 self.applied_dirty = 1
480 self.applied_dirty = 1
481
481
482 head = self.qparents(repo)
482 head = self.qparents(repo)
483
483
484 for patch in series:
484 for patch in series:
485 patch = mergeq.lookup(patch, strict=True)
485 patch = mergeq.lookup(patch, strict=True)
486 if not patch:
486 if not patch:
487 self.ui.warn(_("patch %s does not exist\n") % patch)
487 self.ui.warn(_("patch %s does not exist\n") % patch)
488 return (1, None)
488 return (1, None)
489 pushable, reason = self.pushable(patch)
489 pushable, reason = self.pushable(patch)
490 if not pushable:
490 if not pushable:
491 self.explain_pushable(patch, all_patches=True)
491 self.explain_pushable(patch, all_patches=True)
492 continue
492 continue
493 info = mergeq.isapplied(patch)
493 info = mergeq.isapplied(patch)
494 if not info:
494 if not info:
495 self.ui.warn(_("patch %s is not applied\n") % patch)
495 self.ui.warn(_("patch %s is not applied\n") % patch)
496 return (1, None)
496 return (1, None)
497 rev = bin(info[1])
497 rev = bin(info[1])
498 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
498 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
499 if head:
499 if head:
500 self.applied.append(statusentry(hex(head), patch))
500 self.applied.append(statusentry(hex(head), patch))
501 self.applied_dirty = 1
501 self.applied_dirty = 1
502 if err:
502 if err:
503 return (err, head)
503 return (err, head)
504 self.save_dirty()
504 self.save_dirty()
505 return (0, head)
505 return (0, head)
506
506
507 def patch(self, repo, patchfile):
507 def patch(self, repo, patchfile):
508 '''Apply patchfile to the working directory.
508 '''Apply patchfile to the working directory.
509 patchfile: file name of patch'''
509 patchfile: file name of patch'''
510 files = {}
510 files = {}
511 try:
511 try:
512 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
512 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
513 files=files)
513 files=files)
514 except Exception, inst:
514 except Exception, inst:
515 self.ui.note(str(inst) + '\n')
515 self.ui.note(str(inst) + '\n')
516 if not self.ui.verbose:
516 if not self.ui.verbose:
517 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
517 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
518 return (False, files, False)
518 return (False, files, False)
519
519
520 return (True, files, fuzz)
520 return (True, files, fuzz)
521
521
522 def apply(self, repo, series, list=False, update_status=True,
522 def apply(self, repo, series, list=False, update_status=True,
523 strict=False, patchdir=None, merge=None, all_files={}):
523 strict=False, patchdir=None, merge=None, all_files={}):
524 wlock = lock = tr = None
524 wlock = lock = tr = None
525 try:
525 try:
526 wlock = repo.wlock()
526 wlock = repo.wlock()
527 lock = repo.lock()
527 lock = repo.lock()
528 tr = repo.transaction()
528 tr = repo.transaction()
529 try:
529 try:
530 ret = self._apply(repo, series, list, update_status,
530 ret = self._apply(repo, series, list, update_status,
531 strict, patchdir, merge, all_files=all_files)
531 strict, patchdir, merge, all_files=all_files)
532 tr.close()
532 tr.close()
533 self.save_dirty()
533 self.save_dirty()
534 return ret
534 return ret
535 except:
535 except:
536 try:
536 try:
537 tr.abort()
537 tr.abort()
538 finally:
538 finally:
539 repo.invalidate()
539 repo.invalidate()
540 repo.dirstate.invalidate()
540 repo.dirstate.invalidate()
541 raise
541 raise
542 finally:
542 finally:
543 del tr
543 del tr
544 release(lock, wlock)
544 release(lock, wlock)
545 self.removeundo(repo)
545 self.removeundo(repo)
546
546
547 def _apply(self, repo, series, list=False, update_status=True,
547 def _apply(self, repo, series, list=False, update_status=True,
548 strict=False, patchdir=None, merge=None, all_files={}):
548 strict=False, patchdir=None, merge=None, all_files={}):
549 # TODO unify with commands.py
549 # TODO unify with commands.py
550 if not patchdir:
550 if not patchdir:
551 patchdir = self.path
551 patchdir = self.path
552 err = 0
552 err = 0
553 n = None
553 n = None
554 for patchname in series:
554 for patchname in series:
555 pushable, reason = self.pushable(patchname)
555 pushable, reason = self.pushable(patchname)
556 if not pushable:
556 if not pushable:
557 self.explain_pushable(patchname, all_patches=True)
557 self.explain_pushable(patchname, all_patches=True)
558 continue
558 continue
559 self.ui.warn(_("applying %s\n") % patchname)
559 self.ui.warn(_("applying %s\n") % patchname)
560 pf = os.path.join(patchdir, patchname)
560 pf = os.path.join(patchdir, patchname)
561
561
562 try:
562 try:
563 ph = self.readheaders(patchname)
563 ph = self.readheaders(patchname)
564 except:
564 except:
565 self.ui.warn(_("Unable to read %s\n") % patchname)
565 self.ui.warn(_("Unable to read %s\n") % patchname)
566 err = 1
566 err = 1
567 break
567 break
568
568
569 message = ph.message
569 message = ph.message
570 if not message:
570 if not message:
571 message = _("imported patch %s\n") % patchname
571 message = _("imported patch %s\n") % patchname
572 else:
572 else:
573 if list:
573 if list:
574 message.append(_("\nimported patch %s") % patchname)
574 message.append(_("\nimported patch %s") % patchname)
575 message = '\n'.join(message)
575 message = '\n'.join(message)
576
576
577 if ph.haspatch:
577 if ph.haspatch:
578 (patcherr, files, fuzz) = self.patch(repo, pf)
578 (patcherr, files, fuzz) = self.patch(repo, pf)
579 all_files.update(files)
579 all_files.update(files)
580 patcherr = not patcherr
580 patcherr = not patcherr
581 else:
581 else:
582 self.ui.warn(_("patch %s is empty\n") % patchname)
582 self.ui.warn(_("patch %s is empty\n") % patchname)
583 patcherr, files, fuzz = 0, [], 0
583 patcherr, files, fuzz = 0, [], 0
584
584
585 if merge and files:
585 if merge and files:
586 # Mark as removed/merged and update dirstate parent info
586 # Mark as removed/merged and update dirstate parent info
587 removed = []
587 removed = []
588 merged = []
588 merged = []
589 for f in files:
589 for f in files:
590 if os.path.exists(repo.wjoin(f)):
590 if os.path.exists(repo.wjoin(f)):
591 merged.append(f)
591 merged.append(f)
592 else:
592 else:
593 removed.append(f)
593 removed.append(f)
594 for f in removed:
594 for f in removed:
595 repo.dirstate.remove(f)
595 repo.dirstate.remove(f)
596 for f in merged:
596 for f in merged:
597 repo.dirstate.merge(f)
597 repo.dirstate.merge(f)
598 p1, p2 = repo.dirstate.parents()
598 p1, p2 = repo.dirstate.parents()
599 repo.dirstate.setparents(p1, merge)
599 repo.dirstate.setparents(p1, merge)
600
600
601 files = patch.updatedir(self.ui, repo, files)
601 files = patch.updatedir(self.ui, repo, files)
602 match = cmdutil.matchfiles(repo, files or [])
602 match = cmdutil.matchfiles(repo, files or [])
603 n = repo.commit(files, message, ph.user, ph.date, match=match,
603 n = repo.commit(files, message, ph.user, ph.date, match=match,
604 force=True)
604 force=True)
605
605
606 if n is None:
606 if n is None:
607 raise util.Abort(_("repo commit failed"))
607 raise util.Abort(_("repo commit failed"))
608
608
609 if update_status:
609 if update_status:
610 self.applied.append(statusentry(hex(n), patchname))
610 self.applied.append(statusentry(hex(n), patchname))
611
611
612 if patcherr:
612 if patcherr:
613 self.ui.warn(_("patch failed, rejects left in working dir\n"))
613 self.ui.warn(_("patch failed, rejects left in working dir\n"))
614 err = 1
614 err = 1
615 break
615 break
616
616
617 if fuzz and strict:
617 if fuzz and strict:
618 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
618 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
619 err = 1
619 err = 1
620 break
620 break
621 return (err, n)
621 return (err, n)
622
622
623 def _clean_series(self, patches):
623 def _clean_series(self, patches):
624 for i in sorted([self.find_series(p) for p in patches], reverse=True):
624 for i in sorted([self.find_series(p) for p in patches], reverse=True):
625 del self.full_series[i]
625 del self.full_series[i]
626 self.parse_series()
626 self.parse_series()
627 self.series_dirty = 1
627 self.series_dirty = 1
628
628
629 def finish(self, repo, revs):
629 def finish(self, repo, revs):
630 firstrev = repo[self.applied[0].rev].rev()
630 firstrev = repo[self.applied[0].rev].rev()
631 appliedbase = 0
631 appliedbase = 0
632 patches = []
632 patches = []
633 for rev in sorted(revs):
633 for rev in sorted(revs):
634 if rev < firstrev:
634 if rev < firstrev:
635 raise util.Abort(_('revision %d is not managed') % rev)
635 raise util.Abort(_('revision %d is not managed') % rev)
636 base = bin(self.applied[appliedbase].rev)
636 base = bin(self.applied[appliedbase].rev)
637 node = repo.changelog.node(rev)
637 node = repo.changelog.node(rev)
638 if node != base:
638 if node != base:
639 raise util.Abort(_('cannot delete revision %d above '
639 raise util.Abort(_('cannot delete revision %d above '
640 'applied patches') % rev)
640 'applied patches') % rev)
641 patches.append(self.applied[appliedbase].name)
641 patches.append(self.applied[appliedbase].name)
642 appliedbase += 1
642 appliedbase += 1
643
643
644 r = self.qrepo()
644 r = self.qrepo()
645 if r:
645 if r:
646 r.remove(patches, True)
646 r.remove(patches, True)
647 else:
647 else:
648 for p in patches:
648 for p in patches:
649 os.unlink(self.join(p))
649 os.unlink(self.join(p))
650
650
651 del self.applied[:appliedbase]
651 del self.applied[:appliedbase]
652 self.applied_dirty = 1
652 self.applied_dirty = 1
653 self._clean_series(patches)
653 self._clean_series(patches)
654
654
655 def delete(self, repo, patches, opts):
655 def delete(self, repo, patches, opts):
656 if not patches and not opts.get('rev'):
656 if not patches and not opts.get('rev'):
657 raise util.Abort(_('qdelete requires at least one revision or '
657 raise util.Abort(_('qdelete requires at least one revision or '
658 'patch name'))
658 'patch name'))
659
659
660 realpatches = []
660 realpatches = []
661 for patch in patches:
661 for patch in patches:
662 patch = self.lookup(patch, strict=True)
662 patch = self.lookup(patch, strict=True)
663 info = self.isapplied(patch)
663 info = self.isapplied(patch)
664 if info:
664 if info:
665 raise util.Abort(_("cannot delete applied patch %s") % patch)
665 raise util.Abort(_("cannot delete applied patch %s") % patch)
666 if patch not in self.series:
666 if patch not in self.series:
667 raise util.Abort(_("patch %s not in series file") % patch)
667 raise util.Abort(_("patch %s not in series file") % patch)
668 realpatches.append(patch)
668 realpatches.append(patch)
669
669
670 appliedbase = 0
670 appliedbase = 0
671 if opts.get('rev'):
671 if opts.get('rev'):
672 if not self.applied:
672 if not self.applied:
673 raise util.Abort(_('no patches applied'))
673 raise util.Abort(_('no patches applied'))
674 revs = cmdutil.revrange(repo, opts['rev'])
674 revs = cmdutil.revrange(repo, opts['rev'])
675 if len(revs) > 1 and revs[0] > revs[1]:
675 if len(revs) > 1 and revs[0] > revs[1]:
676 revs.reverse()
676 revs.reverse()
677 for rev in revs:
677 for rev in revs:
678 if appliedbase >= len(self.applied):
678 if appliedbase >= len(self.applied):
679 raise util.Abort(_("revision %d is not managed") % rev)
679 raise util.Abort(_("revision %d is not managed") % rev)
680
680
681 base = bin(self.applied[appliedbase].rev)
681 base = bin(self.applied[appliedbase].rev)
682 node = repo.changelog.node(rev)
682 node = repo.changelog.node(rev)
683 if node != base:
683 if node != base:
684 raise util.Abort(_("cannot delete revision %d above "
684 raise util.Abort(_("cannot delete revision %d above "
685 "applied patches") % rev)
685 "applied patches") % rev)
686 realpatches.append(self.applied[appliedbase].name)
686 realpatches.append(self.applied[appliedbase].name)
687 appliedbase += 1
687 appliedbase += 1
688
688
689 if not opts.get('keep'):
689 if not opts.get('keep'):
690 r = self.qrepo()
690 r = self.qrepo()
691 if r:
691 if r:
692 r.remove(realpatches, True)
692 r.remove(realpatches, True)
693 else:
693 else:
694 for p in realpatches:
694 for p in realpatches:
695 os.unlink(self.join(p))
695 os.unlink(self.join(p))
696
696
697 if appliedbase:
697 if appliedbase:
698 del self.applied[:appliedbase]
698 del self.applied[:appliedbase]
699 self.applied_dirty = 1
699 self.applied_dirty = 1
700 self._clean_series(realpatches)
700 self._clean_series(realpatches)
701
701
702 def check_toppatch(self, repo):
702 def check_toppatch(self, repo):
703 if len(self.applied) > 0:
703 if len(self.applied) > 0:
704 top = bin(self.applied[-1].rev)
704 top = bin(self.applied[-1].rev)
705 pp = repo.dirstate.parents()
705 pp = repo.dirstate.parents()
706 if top not in pp:
706 if top not in pp:
707 raise util.Abort(_("working directory revision is not qtip"))
707 raise util.Abort(_("working directory revision is not qtip"))
708 return top
708 return top
709 return None
709 return None
710 def check_localchanges(self, repo, force=False, refresh=True):
710 def check_localchanges(self, repo, force=False, refresh=True):
711 m, a, r, d = repo.status()[:4]
711 m, a, r, d = repo.status()[:4]
712 if m or a or r or d:
712 if m or a or r or d:
713 if not force:
713 if not force:
714 if refresh:
714 if refresh:
715 raise util.Abort(_("local changes found, refresh first"))
715 raise util.Abort(_("local changes found, refresh first"))
716 else:
716 else:
717 raise util.Abort(_("local changes found"))
717 raise util.Abort(_("local changes found"))
718 return m, a, r, d
718 return m, a, r, d
719
719
720 _reserved = ('series', 'status', 'guards')
720 _reserved = ('series', 'status', 'guards')
721 def check_reserved_name(self, name):
721 def check_reserved_name(self, name):
722 if (name in self._reserved or name.startswith('.hg')
722 if (name in self._reserved or name.startswith('.hg')
723 or name.startswith('.mq')):
723 or name.startswith('.mq')):
724 raise util.Abort(_('"%s" cannot be used as the name of a patch')
724 raise util.Abort(_('"%s" cannot be used as the name of a patch')
725 % name)
725 % name)
726
726
727 def new(self, repo, patchfn, *pats, **opts):
727 def new(self, repo, patchfn, *pats, **opts):
728 """options:
728 """options:
729 msg: a string or a no-argument function returning a string
729 msg: a string or a no-argument function returning a string
730 """
730 """
731 msg = opts.get('msg')
731 msg = opts.get('msg')
732 force = opts.get('force')
732 force = opts.get('force')
733 user = opts.get('user')
733 user = opts.get('user')
734 date = opts.get('date')
734 date = opts.get('date')
735 if date:
735 if date:
736 date = util.parsedate(date)
736 date = util.parsedate(date)
737 self.check_reserved_name(patchfn)
737 self.check_reserved_name(patchfn)
738 if os.path.exists(self.join(patchfn)):
738 if os.path.exists(self.join(patchfn)):
739 raise util.Abort(_('patch "%s" already exists') % patchfn)
739 raise util.Abort(_('patch "%s" already exists') % patchfn)
740 if opts.get('include') or opts.get('exclude') or pats:
740 if opts.get('include') or opts.get('exclude') or pats:
741 match = cmdutil.match(repo, pats, opts)
741 match = cmdutil.match(repo, pats, opts)
742 # detect missing files in pats
742 # detect missing files in pats
743 def badfn(f, msg):
743 def badfn(f, msg):
744 raise util.Abort('%s: %s' % (f, msg))
744 raise util.Abort('%s: %s' % (f, msg))
745 match.bad = badfn
745 match.bad = badfn
746 m, a, r, d = repo.status(match=match)[:4]
746 m, a, r, d = repo.status(match=match)[:4]
747 else:
747 else:
748 m, a, r, d = self.check_localchanges(repo, force)
748 m, a, r, d = self.check_localchanges(repo, force)
749 match = cmdutil.matchfiles(repo, m + a + r)
749 match = cmdutil.matchfiles(repo, m + a + r)
750 commitfiles = m + a + r
750 commitfiles = m + a + r
751 self.check_toppatch(repo)
751 self.check_toppatch(repo)
752 insert = self.full_series_end()
752 insert = self.full_series_end()
753 wlock = repo.wlock()
753 wlock = repo.wlock()
754 try:
754 try:
755 # if patch file write fails, abort early
755 # if patch file write fails, abort early
756 p = self.opener(patchfn, "w")
756 p = self.opener(patchfn, "w")
757 try:
757 try:
758 if date:
758 if date:
759 p.write("# HG changeset patch\n")
759 p.write("# HG changeset patch\n")
760 if user:
760 if user:
761 p.write("# User " + user + "\n")
761 p.write("# User " + user + "\n")
762 p.write("# Date %d %d\n\n" % date)
762 p.write("# Date %d %d\n\n" % date)
763 elif user:
763 elif user:
764 p.write("From: " + user + "\n\n")
764 p.write("From: " + user + "\n\n")
765
765
766 if hasattr(msg, '__call__'):
766 if hasattr(msg, '__call__'):
767 msg = msg()
767 msg = msg()
768 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
768 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
769 n = repo.commit(commitfiles, commitmsg, user, date, match=match, force=True)
769 n = repo.commit(commitfiles, commitmsg, user, date, match=match, force=True)
770 if n is None:
770 if n is None:
771 raise util.Abort(_("repo commit failed"))
771 raise util.Abort(_("repo commit failed"))
772 try:
772 try:
773 self.full_series[insert:insert] = [patchfn]
773 self.full_series[insert:insert] = [patchfn]
774 self.applied.append(statusentry(hex(n), patchfn))
774 self.applied.append(statusentry(hex(n), patchfn))
775 self.parse_series()
775 self.parse_series()
776 self.series_dirty = 1
776 self.series_dirty = 1
777 self.applied_dirty = 1
777 self.applied_dirty = 1
778 if msg:
778 if msg:
779 msg = msg + "\n\n"
779 msg = msg + "\n\n"
780 p.write(msg)
780 p.write(msg)
781 if commitfiles:
781 if commitfiles:
782 diffopts = self.diffopts()
782 diffopts = self.diffopts()
783 if opts.get('git'): diffopts.git = True
783 if opts.get('git'): diffopts.git = True
784 parent = self.qparents(repo, n)
784 parent = self.qparents(repo, n)
785 chunks = patch.diff(repo, node1=parent, node2=n,
785 chunks = patch.diff(repo, node1=parent, node2=n,
786 match=match, opts=diffopts)
786 match=match, opts=diffopts)
787 for chunk in chunks:
787 for chunk in chunks:
788 p.write(chunk)
788 p.write(chunk)
789 p.close()
789 p.close()
790 wlock.release()
790 wlock.release()
791 wlock = None
791 wlock = None
792 r = self.qrepo()
792 r = self.qrepo()
793 if r: r.add([patchfn])
793 if r: r.add([patchfn])
794 except:
794 except:
795 repo.rollback()
795 repo.rollback()
796 raise
796 raise
797 except Exception:
797 except Exception:
798 patchpath = self.join(patchfn)
798 patchpath = self.join(patchfn)
799 try:
799 try:
800 os.unlink(patchpath)
800 os.unlink(patchpath)
801 except:
801 except:
802 self.ui.warn(_('error unlinking %s\n') % patchpath)
802 self.ui.warn(_('error unlinking %s\n') % patchpath)
803 raise
803 raise
804 self.removeundo(repo)
804 self.removeundo(repo)
805 finally:
805 finally:
806 release(wlock)
806 release(wlock)
807
807
808 def strip(self, repo, rev, update=True, backup="all", force=None):
808 def strip(self, repo, rev, update=True, backup="all", force=None):
809 wlock = lock = None
809 wlock = lock = None
810 try:
810 try:
811 wlock = repo.wlock()
811 wlock = repo.wlock()
812 lock = repo.lock()
812 lock = repo.lock()
813
813
814 if update:
814 if update:
815 self.check_localchanges(repo, force=force, refresh=False)
815 self.check_localchanges(repo, force=force, refresh=False)
816 urev = self.qparents(repo, rev)
816 urev = self.qparents(repo, rev)
817 hg.clean(repo, urev)
817 hg.clean(repo, urev)
818 repo.dirstate.write()
818 repo.dirstate.write()
819
819
820 self.removeundo(repo)
820 self.removeundo(repo)
821 repair.strip(self.ui, repo, rev, backup)
821 repair.strip(self.ui, repo, rev, backup)
822 # strip may have unbundled a set of backed up revisions after
822 # strip may have unbundled a set of backed up revisions after
823 # the actual strip
823 # the actual strip
824 self.removeundo(repo)
824 self.removeundo(repo)
825 finally:
825 finally:
826 release(lock, wlock)
826 release(lock, wlock)
827
827
828 def isapplied(self, patch):
828 def isapplied(self, patch):
829 """returns (index, rev, patch)"""
829 """returns (index, rev, patch)"""
830 for i in xrange(len(self.applied)):
830 for i, a in enumerate(self.applied):
831 a = self.applied[i]
832 if a.name == patch:
831 if a.name == patch:
833 return (i, a.rev, a.name)
832 return (i, a.rev, a.name)
834 return None
833 return None
835
834
836 # if the exact patch name does not exist, we try a few
835 # if the exact patch name does not exist, we try a few
837 # variations. If strict is passed, we try only #1
836 # variations. If strict is passed, we try only #1
838 #
837 #
839 # 1) a number to indicate an offset in the series file
838 # 1) a number to indicate an offset in the series file
840 # 2) a unique substring of the patch name was given
839 # 2) a unique substring of the patch name was given
841 # 3) patchname[-+]num to indicate an offset in the series file
840 # 3) patchname[-+]num to indicate an offset in the series file
842 def lookup(self, patch, strict=False):
841 def lookup(self, patch, strict=False):
843 patch = patch and str(patch)
842 patch = patch and str(patch)
844
843
845 def partial_name(s):
844 def partial_name(s):
846 if s in self.series:
845 if s in self.series:
847 return s
846 return s
848 matches = [x for x in self.series if s in x]
847 matches = [x for x in self.series if s in x]
849 if len(matches) > 1:
848 if len(matches) > 1:
850 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
849 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
851 for m in matches:
850 for m in matches:
852 self.ui.warn(' %s\n' % m)
851 self.ui.warn(' %s\n' % m)
853 return None
852 return None
854 if matches:
853 if matches:
855 return matches[0]
854 return matches[0]
856 if len(self.series) > 0 and len(self.applied) > 0:
855 if len(self.series) > 0 and len(self.applied) > 0:
857 if s == 'qtip':
856 if s == 'qtip':
858 return self.series[self.series_end(True)-1]
857 return self.series[self.series_end(True)-1]
859 if s == 'qbase':
858 if s == 'qbase':
860 return self.series[0]
859 return self.series[0]
861 return None
860 return None
862
861
863 if patch is None:
862 if patch is None:
864 return None
863 return None
865 if patch in self.series:
864 if patch in self.series:
866 return patch
865 return patch
867
866
868 if not os.path.isfile(self.join(patch)):
867 if not os.path.isfile(self.join(patch)):
869 try:
868 try:
870 sno = int(patch)
869 sno = int(patch)
871 except(ValueError, OverflowError):
870 except(ValueError, OverflowError):
872 pass
871 pass
873 else:
872 else:
874 if -len(self.series) <= sno < len(self.series):
873 if -len(self.series) <= sno < len(self.series):
875 return self.series[sno]
874 return self.series[sno]
876
875
877 if not strict:
876 if not strict:
878 res = partial_name(patch)
877 res = partial_name(patch)
879 if res:
878 if res:
880 return res
879 return res
881 minus = patch.rfind('-')
880 minus = patch.rfind('-')
882 if minus >= 0:
881 if minus >= 0:
883 res = partial_name(patch[:minus])
882 res = partial_name(patch[:minus])
884 if res:
883 if res:
885 i = self.series.index(res)
884 i = self.series.index(res)
886 try:
885 try:
887 off = int(patch[minus+1:] or 1)
886 off = int(patch[minus+1:] or 1)
888 except(ValueError, OverflowError):
887 except(ValueError, OverflowError):
889 pass
888 pass
890 else:
889 else:
891 if i - off >= 0:
890 if i - off >= 0:
892 return self.series[i - off]
891 return self.series[i - off]
893 plus = patch.rfind('+')
892 plus = patch.rfind('+')
894 if plus >= 0:
893 if plus >= 0:
895 res = partial_name(patch[:plus])
894 res = partial_name(patch[:plus])
896 if res:
895 if res:
897 i = self.series.index(res)
896 i = self.series.index(res)
898 try:
897 try:
899 off = int(patch[plus+1:] or 1)
898 off = int(patch[plus+1:] or 1)
900 except(ValueError, OverflowError):
899 except(ValueError, OverflowError):
901 pass
900 pass
902 else:
901 else:
903 if i + off < len(self.series):
902 if i + off < len(self.series):
904 return self.series[i + off]
903 return self.series[i + off]
905 raise util.Abort(_("patch %s not in series") % patch)
904 raise util.Abort(_("patch %s not in series") % patch)
906
905
907 def push(self, repo, patch=None, force=False, list=False,
906 def push(self, repo, patch=None, force=False, list=False,
908 mergeq=None, all=False):
907 mergeq=None, all=False):
909 wlock = repo.wlock()
908 wlock = repo.wlock()
910 if repo.dirstate.parents()[0] not in repo.heads():
909 if repo.dirstate.parents()[0] not in repo.heads():
911 self.ui.status(_("(working directory not at a head)\n"))
910 self.ui.status(_("(working directory not at a head)\n"))
912
911
913 if not self.series:
912 if not self.series:
914 self.ui.warn(_('no patches in series\n'))
913 self.ui.warn(_('no patches in series\n'))
915 return 0
914 return 0
916
915
917 try:
916 try:
918 patch = self.lookup(patch)
917 patch = self.lookup(patch)
919 # Suppose our series file is: A B C and the current 'top'
918 # Suppose our series file is: A B C and the current 'top'
920 # patch is B. qpush C should be performed (moving forward)
919 # patch is B. qpush C should be performed (moving forward)
921 # qpush B is a NOP (no change) qpush A is an error (can't
920 # qpush B is a NOP (no change) qpush A is an error (can't
922 # go backwards with qpush)
921 # go backwards with qpush)
923 if patch:
922 if patch:
924 info = self.isapplied(patch)
923 info = self.isapplied(patch)
925 if info:
924 if info:
926 if info[0] < len(self.applied) - 1:
925 if info[0] < len(self.applied) - 1:
927 raise util.Abort(
926 raise util.Abort(
928 _("cannot push to a previous patch: %s") % patch)
927 _("cannot push to a previous patch: %s") % patch)
929 self.ui.warn(
928 self.ui.warn(
930 _('qpush: %s is already at the top\n') % patch)
929 _('qpush: %s is already at the top\n') % patch)
931 return
930 return
932 pushable, reason = self.pushable(patch)
931 pushable, reason = self.pushable(patch)
933 if not pushable:
932 if not pushable:
934 if reason:
933 if reason:
935 reason = _('guarded by %r') % reason
934 reason = _('guarded by %r') % reason
936 else:
935 else:
937 reason = _('no matching guards')
936 reason = _('no matching guards')
938 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
937 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
939 return 1
938 return 1
940 elif all:
939 elif all:
941 patch = self.series[-1]
940 patch = self.series[-1]
942 if self.isapplied(patch):
941 if self.isapplied(patch):
943 self.ui.warn(_('all patches are currently applied\n'))
942 self.ui.warn(_('all patches are currently applied\n'))
944 return 0
943 return 0
945
944
946 # Following the above example, starting at 'top' of B:
945 # Following the above example, starting at 'top' of B:
947 # qpush should be performed (pushes C), but a subsequent
946 # qpush should be performed (pushes C), but a subsequent
948 # qpush without an argument is an error (nothing to
947 # qpush without an argument is an error (nothing to
949 # apply). This allows a loop of "...while hg qpush..." to
948 # apply). This allows a loop of "...while hg qpush..." to
950 # work as it detects an error when done
949 # work as it detects an error when done
951 start = self.series_end()
950 start = self.series_end()
952 if start == len(self.series):
951 if start == len(self.series):
953 self.ui.warn(_('patch series already fully applied\n'))
952 self.ui.warn(_('patch series already fully applied\n'))
954 return 1
953 return 1
955 if not force:
954 if not force:
956 self.check_localchanges(repo)
955 self.check_localchanges(repo)
957
956
958 self.applied_dirty = 1
957 self.applied_dirty = 1
959 if start > 0:
958 if start > 0:
960 self.check_toppatch(repo)
959 self.check_toppatch(repo)
961 if not patch:
960 if not patch:
962 patch = self.series[start]
961 patch = self.series[start]
963 end = start + 1
962 end = start + 1
964 else:
963 else:
965 end = self.series.index(patch, start) + 1
964 end = self.series.index(patch, start) + 1
966 s = self.series[start:end]
965 s = self.series[start:end]
967 all_files = {}
966 all_files = {}
968 try:
967 try:
969 if mergeq:
968 if mergeq:
970 ret = self.mergepatch(repo, mergeq, s)
969 ret = self.mergepatch(repo, mergeq, s)
971 else:
970 else:
972 ret = self.apply(repo, s, list, all_files=all_files)
971 ret = self.apply(repo, s, list, all_files=all_files)
973 except:
972 except:
974 self.ui.warn(_('cleaning up working directory...'))
973 self.ui.warn(_('cleaning up working directory...'))
975 node = repo.dirstate.parents()[0]
974 node = repo.dirstate.parents()[0]
976 hg.revert(repo, node, None)
975 hg.revert(repo, node, None)
977 unknown = repo.status(unknown=True)[4]
976 unknown = repo.status(unknown=True)[4]
978 # only remove unknown files that we know we touched or
977 # only remove unknown files that we know we touched or
979 # created while patching
978 # created while patching
980 for f in unknown:
979 for f in unknown:
981 if f in all_files:
980 if f in all_files:
982 util.unlink(repo.wjoin(f))
981 util.unlink(repo.wjoin(f))
983 self.ui.warn(_('done\n'))
982 self.ui.warn(_('done\n'))
984 raise
983 raise
985 top = self.applied[-1].name
984 top = self.applied[-1].name
986 if ret[0]:
985 if ret[0]:
987 self.ui.write(_("errors during apply, please fix and "
986 self.ui.write(_("errors during apply, please fix and "
988 "refresh %s\n") % top)
987 "refresh %s\n") % top)
989 else:
988 else:
990 self.ui.write(_("now at: %s\n") % top)
989 self.ui.write(_("now at: %s\n") % top)
991 return ret[0]
990 return ret[0]
992 finally:
991 finally:
993 wlock.release()
992 wlock.release()
994
993
995 def pop(self, repo, patch=None, force=False, update=True, all=False):
994 def pop(self, repo, patch=None, force=False, update=True, all=False):
996 def getfile(f, rev, flags):
995 def getfile(f, rev, flags):
997 t = repo.file(f).read(rev)
996 t = repo.file(f).read(rev)
998 repo.wwrite(f, t, flags)
997 repo.wwrite(f, t, flags)
999
998
1000 wlock = repo.wlock()
999 wlock = repo.wlock()
1001 try:
1000 try:
1002 if patch:
1001 if patch:
1003 # index, rev, patch
1002 # index, rev, patch
1004 info = self.isapplied(patch)
1003 info = self.isapplied(patch)
1005 if not info:
1004 if not info:
1006 patch = self.lookup(patch)
1005 patch = self.lookup(patch)
1007 info = self.isapplied(patch)
1006 info = self.isapplied(patch)
1008 if not info:
1007 if not info:
1009 raise util.Abort(_("patch %s is not applied") % patch)
1008 raise util.Abort(_("patch %s is not applied") % patch)
1010
1009
1011 if len(self.applied) == 0:
1010 if len(self.applied) == 0:
1012 # Allow qpop -a to work repeatedly,
1011 # Allow qpop -a to work repeatedly,
1013 # but not qpop without an argument
1012 # but not qpop without an argument
1014 self.ui.warn(_("no patches applied\n"))
1013 self.ui.warn(_("no patches applied\n"))
1015 return not all
1014 return not all
1016
1015
1017 if all:
1016 if all:
1018 start = 0
1017 start = 0
1019 elif patch:
1018 elif patch:
1020 start = info[0] + 1
1019 start = info[0] + 1
1021 else:
1020 else:
1022 start = len(self.applied) - 1
1021 start = len(self.applied) - 1
1023
1022
1024 if start >= len(self.applied):
1023 if start >= len(self.applied):
1025 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1024 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1026 return
1025 return
1027
1026
1028 if not update:
1027 if not update:
1029 parents = repo.dirstate.parents()
1028 parents = repo.dirstate.parents()
1030 rr = [ bin(x.rev) for x in self.applied ]
1029 rr = [ bin(x.rev) for x in self.applied ]
1031 for p in parents:
1030 for p in parents:
1032 if p in rr:
1031 if p in rr:
1033 self.ui.warn(_("qpop: forcing dirstate update\n"))
1032 self.ui.warn(_("qpop: forcing dirstate update\n"))
1034 update = True
1033 update = True
1035 else:
1034 else:
1036 parents = [p.hex() for p in repo[None].parents()]
1035 parents = [p.hex() for p in repo[None].parents()]
1037 needupdate = False
1036 needupdate = False
1038 for entry in self.applied[start:]:
1037 for entry in self.applied[start:]:
1039 if entry.rev in parents:
1038 if entry.rev in parents:
1040 needupdate = True
1039 needupdate = True
1041 break
1040 break
1042 update = needupdate
1041 update = needupdate
1043
1042
1044 if not force and update:
1043 if not force and update:
1045 self.check_localchanges(repo)
1044 self.check_localchanges(repo)
1046
1045
1047 self.applied_dirty = 1
1046 self.applied_dirty = 1
1048 end = len(self.applied)
1047 end = len(self.applied)
1049 rev = bin(self.applied[start].rev)
1048 rev = bin(self.applied[start].rev)
1050 if update:
1049 if update:
1051 top = self.check_toppatch(repo)
1050 top = self.check_toppatch(repo)
1052
1051
1053 try:
1052 try:
1054 heads = repo.changelog.heads(rev)
1053 heads = repo.changelog.heads(rev)
1055 except error.LookupError:
1054 except error.LookupError:
1056 node = short(rev)
1055 node = short(rev)
1057 raise util.Abort(_('trying to pop unknown node %s') % node)
1056 raise util.Abort(_('trying to pop unknown node %s') % node)
1058
1057
1059 if heads != [bin(self.applied[-1].rev)]:
1058 if heads != [bin(self.applied[-1].rev)]:
1060 raise util.Abort(_("popping would remove a revision not "
1059 raise util.Abort(_("popping would remove a revision not "
1061 "managed by this patch queue"))
1060 "managed by this patch queue"))
1062
1061
1063 # we know there are no local changes, so we can make a simplified
1062 # we know there are no local changes, so we can make a simplified
1064 # form of hg.update.
1063 # form of hg.update.
1065 if update:
1064 if update:
1066 qp = self.qparents(repo, rev)
1065 qp = self.qparents(repo, rev)
1067 changes = repo.changelog.read(qp)
1066 changes = repo.changelog.read(qp)
1068 mmap = repo.manifest.read(changes[0])
1067 mmap = repo.manifest.read(changes[0])
1069 m, a, r, d = repo.status(qp, top)[:4]
1068 m, a, r, d = repo.status(qp, top)[:4]
1070 if d:
1069 if d:
1071 raise util.Abort(_("deletions found between repo revs"))
1070 raise util.Abort(_("deletions found between repo revs"))
1072 for f in m:
1071 for f in m:
1073 getfile(f, mmap[f], mmap.flags(f))
1072 getfile(f, mmap[f], mmap.flags(f))
1074 for f in r:
1073 for f in r:
1075 getfile(f, mmap[f], mmap.flags(f))
1074 getfile(f, mmap[f], mmap.flags(f))
1076 for f in m + r:
1075 for f in m + r:
1077 repo.dirstate.normal(f)
1076 repo.dirstate.normal(f)
1078 for f in a:
1077 for f in a:
1079 try:
1078 try:
1080 os.unlink(repo.wjoin(f))
1079 os.unlink(repo.wjoin(f))
1081 except OSError, e:
1080 except OSError, e:
1082 if e.errno != errno.ENOENT:
1081 if e.errno != errno.ENOENT:
1083 raise
1082 raise
1084 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1083 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1085 except: pass
1084 except: pass
1086 repo.dirstate.forget(f)
1085 repo.dirstate.forget(f)
1087 repo.dirstate.setparents(qp, nullid)
1086 repo.dirstate.setparents(qp, nullid)
1088 del self.applied[start:end]
1087 del self.applied[start:end]
1089 self.strip(repo, rev, update=False, backup='strip')
1088 self.strip(repo, rev, update=False, backup='strip')
1090 if len(self.applied):
1089 if len(self.applied):
1091 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1090 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1092 else:
1091 else:
1093 self.ui.write(_("patch queue now empty\n"))
1092 self.ui.write(_("patch queue now empty\n"))
1094 finally:
1093 finally:
1095 wlock.release()
1094 wlock.release()
1096
1095
1097 def diff(self, repo, pats, opts):
1096 def diff(self, repo, pats, opts):
1098 top = self.check_toppatch(repo)
1097 top = self.check_toppatch(repo)
1099 if not top:
1098 if not top:
1100 self.ui.write(_("no patches applied\n"))
1099 self.ui.write(_("no patches applied\n"))
1101 return
1100 return
1102 qp = self.qparents(repo, top)
1101 qp = self.qparents(repo, top)
1103 self._diffopts = patch.diffopts(self.ui, opts)
1102 self._diffopts = patch.diffopts(self.ui, opts)
1104 self.printdiff(repo, qp, files=pats, opts=opts)
1103 self.printdiff(repo, qp, files=pats, opts=opts)
1105
1104
1106 def refresh(self, repo, pats=None, **opts):
1105 def refresh(self, repo, pats=None, **opts):
1107 if len(self.applied) == 0:
1106 if len(self.applied) == 0:
1108 self.ui.write(_("no patches applied\n"))
1107 self.ui.write(_("no patches applied\n"))
1109 return 1
1108 return 1
1110 msg = opts.get('msg', '').rstrip()
1109 msg = opts.get('msg', '').rstrip()
1111 newuser = opts.get('user')
1110 newuser = opts.get('user')
1112 newdate = opts.get('date')
1111 newdate = opts.get('date')
1113 if newdate:
1112 if newdate:
1114 newdate = '%d %d' % util.parsedate(newdate)
1113 newdate = '%d %d' % util.parsedate(newdate)
1115 wlock = repo.wlock()
1114 wlock = repo.wlock()
1116 try:
1115 try:
1117 self.check_toppatch(repo)
1116 self.check_toppatch(repo)
1118 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1117 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1119 top = bin(top)
1118 top = bin(top)
1120 if repo.changelog.heads(top) != [top]:
1119 if repo.changelog.heads(top) != [top]:
1121 raise util.Abort(_("cannot refresh a revision with children"))
1120 raise util.Abort(_("cannot refresh a revision with children"))
1122 cparents = repo.changelog.parents(top)
1121 cparents = repo.changelog.parents(top)
1123 patchparent = self.qparents(repo, top)
1122 patchparent = self.qparents(repo, top)
1124 ph = self.readheaders(patchfn)
1123 ph = self.readheaders(patchfn)
1125
1124
1126 patchf = self.opener(patchfn, 'r')
1125 patchf = self.opener(patchfn, 'r')
1127
1126
1128 # if the patch was a git patch, refresh it as a git patch
1127 # if the patch was a git patch, refresh it as a git patch
1129 for line in patchf:
1128 for line in patchf:
1130 if line.startswith('diff --git'):
1129 if line.startswith('diff --git'):
1131 self.diffopts().git = True
1130 self.diffopts().git = True
1132 break
1131 break
1133
1132
1134 if msg:
1133 if msg:
1135 ph.setmessage(msg)
1134 ph.setmessage(msg)
1136 if newuser:
1135 if newuser:
1137 ph.setuser(newuser)
1136 ph.setuser(newuser)
1138 if newdate:
1137 if newdate:
1139 ph.setdate(newdate)
1138 ph.setdate(newdate)
1140
1139
1141 # only commit new patch when write is complete
1140 # only commit new patch when write is complete
1142 patchf = self.opener(patchfn, 'w', atomictemp=True)
1141 patchf = self.opener(patchfn, 'w', atomictemp=True)
1143
1142
1144 patchf.seek(0)
1143 patchf.seek(0)
1145 patchf.truncate()
1144 patchf.truncate()
1146
1145
1147 comments = str(ph)
1146 comments = str(ph)
1148 if comments:
1147 if comments:
1149 patchf.write(comments)
1148 patchf.write(comments)
1150
1149
1151 if opts.get('git'):
1150 if opts.get('git'):
1152 self.diffopts().git = True
1151 self.diffopts().git = True
1153 tip = repo.changelog.tip()
1152 tip = repo.changelog.tip()
1154 if top == tip:
1153 if top == tip:
1155 # if the top of our patch queue is also the tip, there is an
1154 # if the top of our patch queue is also the tip, there is an
1156 # optimization here. We update the dirstate in place and strip
1155 # optimization here. We update the dirstate in place and strip
1157 # off the tip commit. Then just commit the current directory
1156 # off the tip commit. Then just commit the current directory
1158 # tree. We can also send repo.commit the list of files
1157 # tree. We can also send repo.commit the list of files
1159 # changed to speed up the diff
1158 # changed to speed up the diff
1160 #
1159 #
1161 # in short mode, we only diff the files included in the
1160 # in short mode, we only diff the files included in the
1162 # patch already plus specified files
1161 # patch already plus specified files
1163 #
1162 #
1164 # this should really read:
1163 # this should really read:
1165 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1164 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1166 # but we do it backwards to take advantage of manifest/chlog
1165 # but we do it backwards to take advantage of manifest/chlog
1167 # caching against the next repo.status call
1166 # caching against the next repo.status call
1168 #
1167 #
1169 mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4]
1168 mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4]
1170 changes = repo.changelog.read(tip)
1169 changes = repo.changelog.read(tip)
1171 man = repo.manifest.read(changes[0])
1170 man = repo.manifest.read(changes[0])
1172 aaa = aa[:]
1171 aaa = aa[:]
1173 matchfn = cmdutil.match(repo, pats, opts)
1172 matchfn = cmdutil.match(repo, pats, opts)
1174 if opts.get('short'):
1173 if opts.get('short'):
1175 # if amending a patch, we start with existing
1174 # if amending a patch, we start with existing
1176 # files plus specified files - unfiltered
1175 # files plus specified files - unfiltered
1177 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1176 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1178 # filter with inc/exl options
1177 # filter with inc/exl options
1179 matchfn = cmdutil.match(repo, opts=opts)
1178 matchfn = cmdutil.match(repo, opts=opts)
1180 else:
1179 else:
1181 match = cmdutil.matchall(repo)
1180 match = cmdutil.matchall(repo)
1182 m, a, r, d = repo.status(match=match)[:4]
1181 m, a, r, d = repo.status(match=match)[:4]
1183
1182
1184 # we might end up with files that were added between
1183 # we might end up with files that were added between
1185 # tip and the dirstate parent, but then changed in the
1184 # tip and the dirstate parent, but then changed in the
1186 # local dirstate. in this case, we want them to only
1185 # local dirstate. in this case, we want them to only
1187 # show up in the added section
1186 # show up in the added section
1188 for x in m:
1187 for x in m:
1189 if x not in aa:
1188 if x not in aa:
1190 mm.append(x)
1189 mm.append(x)
1191 # we might end up with files added by the local dirstate that
1190 # we might end up with files added by the local dirstate that
1192 # were deleted by the patch. In this case, they should only
1191 # were deleted by the patch. In this case, they should only
1193 # show up in the changed section.
1192 # show up in the changed section.
1194 for x in a:
1193 for x in a:
1195 if x in dd:
1194 if x in dd:
1196 del dd[dd.index(x)]
1195 del dd[dd.index(x)]
1197 mm.append(x)
1196 mm.append(x)
1198 else:
1197 else:
1199 aa.append(x)
1198 aa.append(x)
1200 # make sure any files deleted in the local dirstate
1199 # make sure any files deleted in the local dirstate
1201 # are not in the add or change column of the patch
1200 # are not in the add or change column of the patch
1202 forget = []
1201 forget = []
1203 for x in d + r:
1202 for x in d + r:
1204 if x in aa:
1203 if x in aa:
1205 del aa[aa.index(x)]
1204 del aa[aa.index(x)]
1206 forget.append(x)
1205 forget.append(x)
1207 continue
1206 continue
1208 elif x in mm:
1207 elif x in mm:
1209 del mm[mm.index(x)]
1208 del mm[mm.index(x)]
1210 dd.append(x)
1209 dd.append(x)
1211
1210
1212 m = list(set(mm))
1211 m = list(set(mm))
1213 r = list(set(dd))
1212 r = list(set(dd))
1214 a = list(set(aa))
1213 a = list(set(aa))
1215 c = [filter(matchfn, l) for l in (m, a, r)]
1214 c = [filter(matchfn, l) for l in (m, a, r)]
1216 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1215 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1217 chunks = patch.diff(repo, patchparent, match=match,
1216 chunks = patch.diff(repo, patchparent, match=match,
1218 changes=c, opts=self.diffopts())
1217 changes=c, opts=self.diffopts())
1219 for chunk in chunks:
1218 for chunk in chunks:
1220 patchf.write(chunk)
1219 patchf.write(chunk)
1221
1220
1222 try:
1221 try:
1223 if self.diffopts().git:
1222 if self.diffopts().git:
1224 copies = {}
1223 copies = {}
1225 for dst in a:
1224 for dst in a:
1226 src = repo.dirstate.copied(dst)
1225 src = repo.dirstate.copied(dst)
1227 # during qfold, the source file for copies may
1226 # during qfold, the source file for copies may
1228 # be removed. Treat this as a simple add.
1227 # be removed. Treat this as a simple add.
1229 if src is not None and src in repo.dirstate:
1228 if src is not None and src in repo.dirstate:
1230 copies.setdefault(src, []).append(dst)
1229 copies.setdefault(src, []).append(dst)
1231 repo.dirstate.add(dst)
1230 repo.dirstate.add(dst)
1232 # remember the copies between patchparent and tip
1231 # remember the copies between patchparent and tip
1233 for dst in aaa:
1232 for dst in aaa:
1234 f = repo.file(dst)
1233 f = repo.file(dst)
1235 src = f.renamed(man[dst])
1234 src = f.renamed(man[dst])
1236 if src:
1235 if src:
1237 copies.setdefault(src[0], []).extend(copies.get(dst, []))
1236 copies.setdefault(src[0], []).extend(copies.get(dst, []))
1238 if dst in a:
1237 if dst in a:
1239 copies[src[0]].append(dst)
1238 copies[src[0]].append(dst)
1240 # we can't copy a file created by the patch itself
1239 # we can't copy a file created by the patch itself
1241 if dst in copies:
1240 if dst in copies:
1242 del copies[dst]
1241 del copies[dst]
1243 for src, dsts in copies.iteritems():
1242 for src, dsts in copies.iteritems():
1244 for dst in dsts:
1243 for dst in dsts:
1245 repo.dirstate.copy(src, dst)
1244 repo.dirstate.copy(src, dst)
1246 else:
1245 else:
1247 for dst in a:
1246 for dst in a:
1248 repo.dirstate.add(dst)
1247 repo.dirstate.add(dst)
1249 # Drop useless copy information
1248 # Drop useless copy information
1250 for f in list(repo.dirstate.copies()):
1249 for f in list(repo.dirstate.copies()):
1251 repo.dirstate.copy(None, f)
1250 repo.dirstate.copy(None, f)
1252 for f in r:
1251 for f in r:
1253 repo.dirstate.remove(f)
1252 repo.dirstate.remove(f)
1254 # if the patch excludes a modified file, mark that
1253 # if the patch excludes a modified file, mark that
1255 # file with mtime=0 so status can see it.
1254 # file with mtime=0 so status can see it.
1256 mm = []
1255 mm = []
1257 for i in xrange(len(m)-1, -1, -1):
1256 for i in xrange(len(m)-1, -1, -1):
1258 if not matchfn(m[i]):
1257 if not matchfn(m[i]):
1259 mm.append(m[i])
1258 mm.append(m[i])
1260 del m[i]
1259 del m[i]
1261 for f in m:
1260 for f in m:
1262 repo.dirstate.normal(f)
1261 repo.dirstate.normal(f)
1263 for f in mm:
1262 for f in mm:
1264 repo.dirstate.normallookup(f)
1263 repo.dirstate.normallookup(f)
1265 for f in forget:
1264 for f in forget:
1266 repo.dirstate.forget(f)
1265 repo.dirstate.forget(f)
1267
1266
1268 if not msg:
1267 if not msg:
1269 if not ph.message:
1268 if not ph.message:
1270 message = "[mq]: %s\n" % patchfn
1269 message = "[mq]: %s\n" % patchfn
1271 else:
1270 else:
1272 message = "\n".join(ph.message)
1271 message = "\n".join(ph.message)
1273 else:
1272 else:
1274 message = msg
1273 message = msg
1275
1274
1276 user = ph.user or changes[1]
1275 user = ph.user or changes[1]
1277
1276
1278 # assumes strip can roll itself back if interrupted
1277 # assumes strip can roll itself back if interrupted
1279 repo.dirstate.setparents(*cparents)
1278 repo.dirstate.setparents(*cparents)
1280 self.applied.pop()
1279 self.applied.pop()
1281 self.applied_dirty = 1
1280 self.applied_dirty = 1
1282 self.strip(repo, top, update=False,
1281 self.strip(repo, top, update=False,
1283 backup='strip')
1282 backup='strip')
1284 except:
1283 except:
1285 repo.dirstate.invalidate()
1284 repo.dirstate.invalidate()
1286 raise
1285 raise
1287
1286
1288 try:
1287 try:
1289 # might be nice to attempt to roll back strip after this
1288 # might be nice to attempt to roll back strip after this
1290 patchf.rename()
1289 patchf.rename()
1291 n = repo.commit(match.files(), message, user, ph.date,
1290 n = repo.commit(match.files(), message, user, ph.date,
1292 match=match, force=1)
1291 match=match, force=1)
1293 self.applied.append(statusentry(hex(n), patchfn))
1292 self.applied.append(statusentry(hex(n), patchfn))
1294 except:
1293 except:
1295 ctx = repo[cparents[0]]
1294 ctx = repo[cparents[0]]
1296 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1295 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1297 self.save_dirty()
1296 self.save_dirty()
1298 self.ui.warn(_('refresh interrupted while patch was popped! '
1297 self.ui.warn(_('refresh interrupted while patch was popped! '
1299 '(revert --all, qpush to recover)\n'))
1298 '(revert --all, qpush to recover)\n'))
1300 raise
1299 raise
1301 else:
1300 else:
1302 self.printdiff(repo, patchparent, fp=patchf)
1301 self.printdiff(repo, patchparent, fp=patchf)
1303 patchf.rename()
1302 patchf.rename()
1304 added = repo.status()[1]
1303 added = repo.status()[1]
1305 for a in added:
1304 for a in added:
1306 f = repo.wjoin(a)
1305 f = repo.wjoin(a)
1307 try:
1306 try:
1308 os.unlink(f)
1307 os.unlink(f)
1309 except OSError, e:
1308 except OSError, e:
1310 if e.errno != errno.ENOENT:
1309 if e.errno != errno.ENOENT:
1311 raise
1310 raise
1312 try: os.removedirs(os.path.dirname(f))
1311 try: os.removedirs(os.path.dirname(f))
1313 except: pass
1312 except: pass
1314 # forget the file copies in the dirstate
1313 # forget the file copies in the dirstate
1315 # push should readd the files later on
1314 # push should readd the files later on
1316 repo.dirstate.forget(a)
1315 repo.dirstate.forget(a)
1317 self.pop(repo, force=True)
1316 self.pop(repo, force=True)
1318 self.push(repo, force=True)
1317 self.push(repo, force=True)
1319 finally:
1318 finally:
1320 wlock.release()
1319 wlock.release()
1321 self.removeundo(repo)
1320 self.removeundo(repo)
1322
1321
1323 def init(self, repo, create=False):
1322 def init(self, repo, create=False):
1324 if not create and os.path.isdir(self.path):
1323 if not create and os.path.isdir(self.path):
1325 raise util.Abort(_("patch queue directory already exists"))
1324 raise util.Abort(_("patch queue directory already exists"))
1326 try:
1325 try:
1327 os.mkdir(self.path)
1326 os.mkdir(self.path)
1328 except OSError, inst:
1327 except OSError, inst:
1329 if inst.errno != errno.EEXIST or not create:
1328 if inst.errno != errno.EEXIST or not create:
1330 raise
1329 raise
1331 if create:
1330 if create:
1332 return self.qrepo(create=True)
1331 return self.qrepo(create=True)
1333
1332
1334 def unapplied(self, repo, patch=None):
1333 def unapplied(self, repo, patch=None):
1335 if patch and patch not in self.series:
1334 if patch and patch not in self.series:
1336 raise util.Abort(_("patch %s is not in series file") % patch)
1335 raise util.Abort(_("patch %s is not in series file") % patch)
1337 if not patch:
1336 if not patch:
1338 start = self.series_end()
1337 start = self.series_end()
1339 else:
1338 else:
1340 start = self.series.index(patch) + 1
1339 start = self.series.index(patch) + 1
1341 unapplied = []
1340 unapplied = []
1342 for i in xrange(start, len(self.series)):
1341 for i in xrange(start, len(self.series)):
1343 pushable, reason = self.pushable(i)
1342 pushable, reason = self.pushable(i)
1344 if pushable:
1343 if pushable:
1345 unapplied.append((i, self.series[i]))
1344 unapplied.append((i, self.series[i]))
1346 self.explain_pushable(i)
1345 self.explain_pushable(i)
1347 return unapplied
1346 return unapplied
1348
1347
1349 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1348 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1350 summary=False):
1349 summary=False):
1351 def displayname(patchname):
1350 def displayname(patchname):
1352 if summary:
1351 if summary:
1353 ph = self.readheaders(patchname)
1352 ph = self.readheaders(patchname)
1354 msg = ph.message
1353 msg = ph.message
1355 msg = msg and ': ' + msg[0] or ': '
1354 msg = msg and ': ' + msg[0] or ': '
1356 else:
1355 else:
1357 msg = ''
1356 msg = ''
1358 return '%s%s' % (patchname, msg)
1357 return '%s%s' % (patchname, msg)
1359
1358
1360 applied = set([p.name for p in self.applied])
1359 applied = set([p.name for p in self.applied])
1361 if length is None:
1360 if length is None:
1362 length = len(self.series) - start
1361 length = len(self.series) - start
1363 if not missing:
1362 if not missing:
1364 for i in xrange(start, start+length):
1363 for i in xrange(start, start+length):
1365 patch = self.series[i]
1364 patch = self.series[i]
1366 if patch in applied:
1365 if patch in applied:
1367 stat = 'A'
1366 stat = 'A'
1368 elif self.pushable(i)[0]:
1367 elif self.pushable(i)[0]:
1369 stat = 'U'
1368 stat = 'U'
1370 else:
1369 else:
1371 stat = 'G'
1370 stat = 'G'
1372 pfx = ''
1371 pfx = ''
1373 if self.ui.verbose:
1372 if self.ui.verbose:
1374 pfx = '%d %s ' % (i, stat)
1373 pfx = '%d %s ' % (i, stat)
1375 elif status and status != stat:
1374 elif status and status != stat:
1376 continue
1375 continue
1377 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1376 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1378 else:
1377 else:
1379 msng_list = []
1378 msng_list = []
1380 for root, dirs, files in os.walk(self.path):
1379 for root, dirs, files in os.walk(self.path):
1381 d = root[len(self.path) + 1:]
1380 d = root[len(self.path) + 1:]
1382 for f in files:
1381 for f in files:
1383 fl = os.path.join(d, f)
1382 fl = os.path.join(d, f)
1384 if (fl not in self.series and
1383 if (fl not in self.series and
1385 fl not in (self.status_path, self.series_path,
1384 fl not in (self.status_path, self.series_path,
1386 self.guards_path)
1385 self.guards_path)
1387 and not fl.startswith('.')):
1386 and not fl.startswith('.')):
1388 msng_list.append(fl)
1387 msng_list.append(fl)
1389 for x in sorted(msng_list):
1388 for x in sorted(msng_list):
1390 pfx = self.ui.verbose and ('D ') or ''
1389 pfx = self.ui.verbose and ('D ') or ''
1391 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1390 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1392
1391
1393 def issaveline(self, l):
1392 def issaveline(self, l):
1394 if l.name == '.hg.patches.save.line':
1393 if l.name == '.hg.patches.save.line':
1395 return True
1394 return True
1396
1395
1397 def qrepo(self, create=False):
1396 def qrepo(self, create=False):
1398 if create or os.path.isdir(self.join(".hg")):
1397 if create or os.path.isdir(self.join(".hg")):
1399 return hg.repository(self.ui, path=self.path, create=create)
1398 return hg.repository(self.ui, path=self.path, create=create)
1400
1399
1401 def restore(self, repo, rev, delete=None, qupdate=None):
1400 def restore(self, repo, rev, delete=None, qupdate=None):
1402 c = repo.changelog.read(rev)
1401 c = repo.changelog.read(rev)
1403 desc = c[4].strip()
1402 desc = c[4].strip()
1404 lines = desc.splitlines()
1403 lines = desc.splitlines()
1405 i = 0
1404 i = 0
1406 datastart = None
1405 datastart = None
1407 series = []
1406 series = []
1408 applied = []
1407 applied = []
1409 qpp = None
1408 qpp = None
1410 for i in xrange(len(lines)):
1409 for i, line in enumerate(lines):
1411 if lines[i] == 'Patch Data:':
1410 if line == 'Patch Data:':
1412 datastart = i + 1
1411 datastart = i + 1
1413 elif lines[i].startswith('Dirstate:'):
1412 elif line.startswith('Dirstate:'):
1414 l = lines[i].rstrip()
1413 l = line.rstrip()
1415 l = l[10:].split(' ')
1414 l = l[10:].split(' ')
1416 qpp = [ bin(x) for x in l ]
1415 qpp = [ bin(x) for x in l ]
1417 elif datastart != None:
1416 elif datastart != None:
1418 l = lines[i].rstrip()
1417 l = line.rstrip()
1419 se = statusentry(l)
1418 se = statusentry(l)
1420 file_ = se.name
1419 file_ = se.name
1421 if se.rev:
1420 if se.rev:
1422 applied.append(se)
1421 applied.append(se)
1423 else:
1422 else:
1424 series.append(file_)
1423 series.append(file_)
1425 if datastart is None:
1424 if datastart is None:
1426 self.ui.warn(_("No saved patch data found\n"))
1425 self.ui.warn(_("No saved patch data found\n"))
1427 return 1
1426 return 1
1428 self.ui.warn(_("restoring status: %s\n") % lines[0])
1427 self.ui.warn(_("restoring status: %s\n") % lines[0])
1429 self.full_series = series
1428 self.full_series = series
1430 self.applied = applied
1429 self.applied = applied
1431 self.parse_series()
1430 self.parse_series()
1432 self.series_dirty = 1
1431 self.series_dirty = 1
1433 self.applied_dirty = 1
1432 self.applied_dirty = 1
1434 heads = repo.changelog.heads()
1433 heads = repo.changelog.heads()
1435 if delete:
1434 if delete:
1436 if rev not in heads:
1435 if rev not in heads:
1437 self.ui.warn(_("save entry has children, leaving it alone\n"))
1436 self.ui.warn(_("save entry has children, leaving it alone\n"))
1438 else:
1437 else:
1439 self.ui.warn(_("removing save entry %s\n") % short(rev))
1438 self.ui.warn(_("removing save entry %s\n") % short(rev))
1440 pp = repo.dirstate.parents()
1439 pp = repo.dirstate.parents()
1441 if rev in pp:
1440 if rev in pp:
1442 update = True
1441 update = True
1443 else:
1442 else:
1444 update = False
1443 update = False
1445 self.strip(repo, rev, update=update, backup='strip')
1444 self.strip(repo, rev, update=update, backup='strip')
1446 if qpp:
1445 if qpp:
1447 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1446 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1448 (short(qpp[0]), short(qpp[1])))
1447 (short(qpp[0]), short(qpp[1])))
1449 if qupdate:
1448 if qupdate:
1450 self.ui.status(_("queue directory updating\n"))
1449 self.ui.status(_("queue directory updating\n"))
1451 r = self.qrepo()
1450 r = self.qrepo()
1452 if not r:
1451 if not r:
1453 self.ui.warn(_("Unable to load queue repository\n"))
1452 self.ui.warn(_("Unable to load queue repository\n"))
1454 return 1
1453 return 1
1455 hg.clean(r, qpp[0])
1454 hg.clean(r, qpp[0])
1456
1455
1457 def save(self, repo, msg=None):
1456 def save(self, repo, msg=None):
1458 if len(self.applied) == 0:
1457 if len(self.applied) == 0:
1459 self.ui.warn(_("save: no patches applied, exiting\n"))
1458 self.ui.warn(_("save: no patches applied, exiting\n"))
1460 return 1
1459 return 1
1461 if self.issaveline(self.applied[-1]):
1460 if self.issaveline(self.applied[-1]):
1462 self.ui.warn(_("status is already saved\n"))
1461 self.ui.warn(_("status is already saved\n"))
1463 return 1
1462 return 1
1464
1463
1465 ar = [ ':' + x for x in self.full_series ]
1464 ar = [ ':' + x for x in self.full_series ]
1466 if not msg:
1465 if not msg:
1467 msg = _("hg patches saved state")
1466 msg = _("hg patches saved state")
1468 else:
1467 else:
1469 msg = "hg patches: " + msg.rstrip('\r\n')
1468 msg = "hg patches: " + msg.rstrip('\r\n')
1470 r = self.qrepo()
1469 r = self.qrepo()
1471 if r:
1470 if r:
1472 pp = r.dirstate.parents()
1471 pp = r.dirstate.parents()
1473 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1472 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1474 msg += "\n\nPatch Data:\n"
1473 msg += "\n\nPatch Data:\n"
1475 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1474 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1476 "\n".join(ar) + '\n' or "")
1475 "\n".join(ar) + '\n' or "")
1477 n = repo.commit(None, text, user=None, force=1)
1476 n = repo.commit(None, text, user=None, force=1)
1478 if not n:
1477 if not n:
1479 self.ui.warn(_("repo commit failed\n"))
1478 self.ui.warn(_("repo commit failed\n"))
1480 return 1
1479 return 1
1481 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1480 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1482 self.applied_dirty = 1
1481 self.applied_dirty = 1
1483 self.removeundo(repo)
1482 self.removeundo(repo)
1484
1483
1485 def full_series_end(self):
1484 def full_series_end(self):
1486 if len(self.applied) > 0:
1485 if len(self.applied) > 0:
1487 p = self.applied[-1].name
1486 p = self.applied[-1].name
1488 end = self.find_series(p)
1487 end = self.find_series(p)
1489 if end is None:
1488 if end is None:
1490 return len(self.full_series)
1489 return len(self.full_series)
1491 return end + 1
1490 return end + 1
1492 return 0
1491 return 0
1493
1492
1494 def series_end(self, all_patches=False):
1493 def series_end(self, all_patches=False):
1495 """If all_patches is False, return the index of the next pushable patch
1494 """If all_patches is False, return the index of the next pushable patch
1496 in the series, or the series length. If all_patches is True, return the
1495 in the series, or the series length. If all_patches is True, return the
1497 index of the first patch past the last applied one.
1496 index of the first patch past the last applied one.
1498 """
1497 """
1499 end = 0
1498 end = 0
1500 def next(start):
1499 def next(start):
1501 if all_patches:
1500 if all_patches:
1502 return start
1501 return start
1503 i = start
1502 i = start
1504 while i < len(self.series):
1503 while i < len(self.series):
1505 p, reason = self.pushable(i)
1504 p, reason = self.pushable(i)
1506 if p:
1505 if p:
1507 break
1506 break
1508 self.explain_pushable(i)
1507 self.explain_pushable(i)
1509 i += 1
1508 i += 1
1510 return i
1509 return i
1511 if len(self.applied) > 0:
1510 if len(self.applied) > 0:
1512 p = self.applied[-1].name
1511 p = self.applied[-1].name
1513 try:
1512 try:
1514 end = self.series.index(p)
1513 end = self.series.index(p)
1515 except ValueError:
1514 except ValueError:
1516 return 0
1515 return 0
1517 return next(end + 1)
1516 return next(end + 1)
1518 return next(end)
1517 return next(end)
1519
1518
1520 def appliedname(self, index):
1519 def appliedname(self, index):
1521 pname = self.applied[index].name
1520 pname = self.applied[index].name
1522 if not self.ui.verbose:
1521 if not self.ui.verbose:
1523 p = pname
1522 p = pname
1524 else:
1523 else:
1525 p = str(self.series.index(pname)) + " " + pname
1524 p = str(self.series.index(pname)) + " " + pname
1526 return p
1525 return p
1527
1526
1528 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1527 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1529 force=None, git=False):
1528 force=None, git=False):
1530 def checkseries(patchname):
1529 def checkseries(patchname):
1531 if patchname in self.series:
1530 if patchname in self.series:
1532 raise util.Abort(_('patch %s is already in the series file')
1531 raise util.Abort(_('patch %s is already in the series file')
1533 % patchname)
1532 % patchname)
1534 def checkfile(patchname):
1533 def checkfile(patchname):
1535 if not force and os.path.exists(self.join(patchname)):
1534 if not force and os.path.exists(self.join(patchname)):
1536 raise util.Abort(_('patch "%s" already exists')
1535 raise util.Abort(_('patch "%s" already exists')
1537 % patchname)
1536 % patchname)
1538
1537
1539 if rev:
1538 if rev:
1540 if files:
1539 if files:
1541 raise util.Abort(_('option "-r" not valid when importing '
1540 raise util.Abort(_('option "-r" not valid when importing '
1542 'files'))
1541 'files'))
1543 rev = cmdutil.revrange(repo, rev)
1542 rev = cmdutil.revrange(repo, rev)
1544 rev.sort(lambda x, y: cmp(y, x))
1543 rev.sort(lambda x, y: cmp(y, x))
1545 if (len(files) > 1 or len(rev) > 1) and patchname:
1544 if (len(files) > 1 or len(rev) > 1) and patchname:
1546 raise util.Abort(_('option "-n" not valid when importing multiple '
1545 raise util.Abort(_('option "-n" not valid when importing multiple '
1547 'patches'))
1546 'patches'))
1548 i = 0
1547 i = 0
1549 added = []
1548 added = []
1550 if rev:
1549 if rev:
1551 # If mq patches are applied, we can only import revisions
1550 # If mq patches are applied, we can only import revisions
1552 # that form a linear path to qbase.
1551 # that form a linear path to qbase.
1553 # Otherwise, they should form a linear path to a head.
1552 # Otherwise, they should form a linear path to a head.
1554 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1553 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1555 if len(heads) > 1:
1554 if len(heads) > 1:
1556 raise util.Abort(_('revision %d is the root of more than one '
1555 raise util.Abort(_('revision %d is the root of more than one '
1557 'branch') % rev[-1])
1556 'branch') % rev[-1])
1558 if self.applied:
1557 if self.applied:
1559 base = hex(repo.changelog.node(rev[0]))
1558 base = hex(repo.changelog.node(rev[0]))
1560 if base in [n.rev for n in self.applied]:
1559 if base in [n.rev for n in self.applied]:
1561 raise util.Abort(_('revision %d is already managed')
1560 raise util.Abort(_('revision %d is already managed')
1562 % rev[0])
1561 % rev[0])
1563 if heads != [bin(self.applied[-1].rev)]:
1562 if heads != [bin(self.applied[-1].rev)]:
1564 raise util.Abort(_('revision %d is not the parent of '
1563 raise util.Abort(_('revision %d is not the parent of '
1565 'the queue') % rev[0])
1564 'the queue') % rev[0])
1566 base = repo.changelog.rev(bin(self.applied[0].rev))
1565 base = repo.changelog.rev(bin(self.applied[0].rev))
1567 lastparent = repo.changelog.parentrevs(base)[0]
1566 lastparent = repo.changelog.parentrevs(base)[0]
1568 else:
1567 else:
1569 if heads != [repo.changelog.node(rev[0])]:
1568 if heads != [repo.changelog.node(rev[0])]:
1570 raise util.Abort(_('revision %d has unmanaged children')
1569 raise util.Abort(_('revision %d has unmanaged children')
1571 % rev[0])
1570 % rev[0])
1572 lastparent = None
1571 lastparent = None
1573
1572
1574 if git:
1573 if git:
1575 self.diffopts().git = True
1574 self.diffopts().git = True
1576
1575
1577 for r in rev:
1576 for r in rev:
1578 p1, p2 = repo.changelog.parentrevs(r)
1577 p1, p2 = repo.changelog.parentrevs(r)
1579 n = repo.changelog.node(r)
1578 n = repo.changelog.node(r)
1580 if p2 != nullrev:
1579 if p2 != nullrev:
1581 raise util.Abort(_('cannot import merge revision %d') % r)
1580 raise util.Abort(_('cannot import merge revision %d') % r)
1582 if lastparent and lastparent != r:
1581 if lastparent and lastparent != r:
1583 raise util.Abort(_('revision %d is not the parent of %d')
1582 raise util.Abort(_('revision %d is not the parent of %d')
1584 % (r, lastparent))
1583 % (r, lastparent))
1585 lastparent = p1
1584 lastparent = p1
1586
1585
1587 if not patchname:
1586 if not patchname:
1588 patchname = normname('%d.diff' % r)
1587 patchname = normname('%d.diff' % r)
1589 self.check_reserved_name(patchname)
1588 self.check_reserved_name(patchname)
1590 checkseries(patchname)
1589 checkseries(patchname)
1591 checkfile(patchname)
1590 checkfile(patchname)
1592 self.full_series.insert(0, patchname)
1591 self.full_series.insert(0, patchname)
1593
1592
1594 patchf = self.opener(patchname, "w")
1593 patchf = self.opener(patchname, "w")
1595 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1594 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1596 patchf.close()
1595 patchf.close()
1597
1596
1598 se = statusentry(hex(n), patchname)
1597 se = statusentry(hex(n), patchname)
1599 self.applied.insert(0, se)
1598 self.applied.insert(0, se)
1600
1599
1601 added.append(patchname)
1600 added.append(patchname)
1602 patchname = None
1601 patchname = None
1603 self.parse_series()
1602 self.parse_series()
1604 self.applied_dirty = 1
1603 self.applied_dirty = 1
1605
1604
1606 for filename in files:
1605 for filename in files:
1607 if existing:
1606 if existing:
1608 if filename == '-':
1607 if filename == '-':
1609 raise util.Abort(_('-e is incompatible with import from -'))
1608 raise util.Abort(_('-e is incompatible with import from -'))
1610 if not patchname:
1609 if not patchname:
1611 patchname = normname(filename)
1610 patchname = normname(filename)
1612 self.check_reserved_name(patchname)
1611 self.check_reserved_name(patchname)
1613 if not os.path.isfile(self.join(patchname)):
1612 if not os.path.isfile(self.join(patchname)):
1614 raise util.Abort(_("patch %s does not exist") % patchname)
1613 raise util.Abort(_("patch %s does not exist") % patchname)
1615 else:
1614 else:
1616 try:
1615 try:
1617 if filename == '-':
1616 if filename == '-':
1618 if not patchname:
1617 if not patchname:
1619 raise util.Abort(_('need --name to import a patch from -'))
1618 raise util.Abort(_('need --name to import a patch from -'))
1620 text = sys.stdin.read()
1619 text = sys.stdin.read()
1621 else:
1620 else:
1622 text = url.open(self.ui, filename).read()
1621 text = url.open(self.ui, filename).read()
1623 except (OSError, IOError):
1622 except (OSError, IOError):
1624 raise util.Abort(_("unable to read %s") % filename)
1623 raise util.Abort(_("unable to read %s") % filename)
1625 if not patchname:
1624 if not patchname:
1626 patchname = normname(os.path.basename(filename))
1625 patchname = normname(os.path.basename(filename))
1627 self.check_reserved_name(patchname)
1626 self.check_reserved_name(patchname)
1628 checkfile(patchname)
1627 checkfile(patchname)
1629 patchf = self.opener(patchname, "w")
1628 patchf = self.opener(patchname, "w")
1630 patchf.write(text)
1629 patchf.write(text)
1631 if not force:
1630 if not force:
1632 checkseries(patchname)
1631 checkseries(patchname)
1633 if patchname not in self.series:
1632 if patchname not in self.series:
1634 index = self.full_series_end() + i
1633 index = self.full_series_end() + i
1635 self.full_series[index:index] = [patchname]
1634 self.full_series[index:index] = [patchname]
1636 self.parse_series()
1635 self.parse_series()
1637 self.ui.warn(_("adding %s to series file\n") % patchname)
1636 self.ui.warn(_("adding %s to series file\n") % patchname)
1638 i += 1
1637 i += 1
1639 added.append(patchname)
1638 added.append(patchname)
1640 patchname = None
1639 patchname = None
1641 self.series_dirty = 1
1640 self.series_dirty = 1
1642 qrepo = self.qrepo()
1641 qrepo = self.qrepo()
1643 if qrepo:
1642 if qrepo:
1644 qrepo.add(added)
1643 qrepo.add(added)
1645
1644
1646 def delete(ui, repo, *patches, **opts):
1645 def delete(ui, repo, *patches, **opts):
1647 """remove patches from queue
1646 """remove patches from queue
1648
1647
1649 The patches must not be applied, unless they are arguments to the
1648 The patches must not be applied, unless they are arguments to the
1650 -r/--rev parameter. At least one patch or revision is required.
1649 -r/--rev parameter. At least one patch or revision is required.
1651
1650
1652 With --rev, mq will stop managing the named revisions (converting
1651 With --rev, mq will stop managing the named revisions (converting
1653 them to regular mercurial changesets). The qfinish command should
1652 them to regular mercurial changesets). The qfinish command should
1654 be used as an alternative for qdelete -r, as the latter option is
1653 be used as an alternative for qdelete -r, as the latter option is
1655 deprecated.
1654 deprecated.
1656
1655
1657 With -k/--keep, the patch files are preserved in the patch
1656 With -k/--keep, the patch files are preserved in the patch
1658 directory."""
1657 directory."""
1659 q = repo.mq
1658 q = repo.mq
1660 q.delete(repo, patches, opts)
1659 q.delete(repo, patches, opts)
1661 q.save_dirty()
1660 q.save_dirty()
1662 return 0
1661 return 0
1663
1662
1664 def applied(ui, repo, patch=None, **opts):
1663 def applied(ui, repo, patch=None, **opts):
1665 """print the patches already applied"""
1664 """print the patches already applied"""
1666 q = repo.mq
1665 q = repo.mq
1667 if patch:
1666 if patch:
1668 if patch not in q.series:
1667 if patch not in q.series:
1669 raise util.Abort(_("patch %s is not in series file") % patch)
1668 raise util.Abort(_("patch %s is not in series file") % patch)
1670 end = q.series.index(patch) + 1
1669 end = q.series.index(patch) + 1
1671 else:
1670 else:
1672 end = q.series_end(True)
1671 end = q.series_end(True)
1673 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1672 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1674
1673
1675 def unapplied(ui, repo, patch=None, **opts):
1674 def unapplied(ui, repo, patch=None, **opts):
1676 """print the patches not yet applied"""
1675 """print the patches not yet applied"""
1677 q = repo.mq
1676 q = repo.mq
1678 if patch:
1677 if patch:
1679 if patch not in q.series:
1678 if patch not in q.series:
1680 raise util.Abort(_("patch %s is not in series file") % patch)
1679 raise util.Abort(_("patch %s is not in series file") % patch)
1681 start = q.series.index(patch) + 1
1680 start = q.series.index(patch) + 1
1682 else:
1681 else:
1683 start = q.series_end(True)
1682 start = q.series_end(True)
1684 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1683 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1685
1684
1686 def qimport(ui, repo, *filename, **opts):
1685 def qimport(ui, repo, *filename, **opts):
1687 """import a patch
1686 """import a patch
1688
1687
1689 The patch is inserted into the series after the last applied
1688 The patch is inserted into the series after the last applied
1690 patch. If no patches have been applied, qimport prepends the patch
1689 patch. If no patches have been applied, qimport prepends the patch
1691 to the series.
1690 to the series.
1692
1691
1693 The patch will have the same name as its source file unless you
1692 The patch will have the same name as its source file unless you
1694 give it a new one with -n/--name.
1693 give it a new one with -n/--name.
1695
1694
1696 You can register an existing patch inside the patch directory with
1695 You can register an existing patch inside the patch directory with
1697 the -e/--existing flag.
1696 the -e/--existing flag.
1698
1697
1699 With -f/--force, an existing patch of the same name will be
1698 With -f/--force, an existing patch of the same name will be
1700 overwritten.
1699 overwritten.
1701
1700
1702 An existing changeset may be placed under mq control with -r/--rev
1701 An existing changeset may be placed under mq control with -r/--rev
1703 (e.g. qimport --rev tip -n patch will place tip under mq control).
1702 (e.g. qimport --rev tip -n patch will place tip under mq control).
1704 With -g/--git, patches imported with --rev will use the git diff
1703 With -g/--git, patches imported with --rev will use the git diff
1705 format. See the diffs help topic for information on why this is
1704 format. See the diffs help topic for information on why this is
1706 important for preserving rename/copy information and permission
1705 important for preserving rename/copy information and permission
1707 changes.
1706 changes.
1708
1707
1709 To import a patch from standard input, pass - as the patch file.
1708 To import a patch from standard input, pass - as the patch file.
1710 When importing from standard input, a patch name must be specified
1709 When importing from standard input, a patch name must be specified
1711 using the --name flag.
1710 using the --name flag.
1712 """
1711 """
1713 q = repo.mq
1712 q = repo.mq
1714 q.qimport(repo, filename, patchname=opts['name'],
1713 q.qimport(repo, filename, patchname=opts['name'],
1715 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1714 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1716 git=opts['git'])
1715 git=opts['git'])
1717 q.save_dirty()
1716 q.save_dirty()
1718
1717
1719 if opts.get('push') and not opts.get('rev'):
1718 if opts.get('push') and not opts.get('rev'):
1720 return q.push(repo, None)
1719 return q.push(repo, None)
1721 return 0
1720 return 0
1722
1721
1723 def init(ui, repo, **opts):
1722 def init(ui, repo, **opts):
1724 """init a new queue repository
1723 """init a new queue repository
1725
1724
1726 The queue repository is unversioned by default. If
1725 The queue repository is unversioned by default. If
1727 -c/--create-repo is specified, qinit will create a separate nested
1726 -c/--create-repo is specified, qinit will create a separate nested
1728 repository for patches (qinit -c may also be run later to convert
1727 repository for patches (qinit -c may also be run later to convert
1729 an unversioned patch repository into a versioned one). You can use
1728 an unversioned patch repository into a versioned one). You can use
1730 qcommit to commit changes to this queue repository."""
1729 qcommit to commit changes to this queue repository."""
1731 q = repo.mq
1730 q = repo.mq
1732 r = q.init(repo, create=opts['create_repo'])
1731 r = q.init(repo, create=opts['create_repo'])
1733 q.save_dirty()
1732 q.save_dirty()
1734 if r:
1733 if r:
1735 if not os.path.exists(r.wjoin('.hgignore')):
1734 if not os.path.exists(r.wjoin('.hgignore')):
1736 fp = r.wopener('.hgignore', 'w')
1735 fp = r.wopener('.hgignore', 'w')
1737 fp.write('^\\.hg\n')
1736 fp.write('^\\.hg\n')
1738 fp.write('^\\.mq\n')
1737 fp.write('^\\.mq\n')
1739 fp.write('syntax: glob\n')
1738 fp.write('syntax: glob\n')
1740 fp.write('status\n')
1739 fp.write('status\n')
1741 fp.write('guards\n')
1740 fp.write('guards\n')
1742 fp.close()
1741 fp.close()
1743 if not os.path.exists(r.wjoin('series')):
1742 if not os.path.exists(r.wjoin('series')):
1744 r.wopener('series', 'w').close()
1743 r.wopener('series', 'w').close()
1745 r.add(['.hgignore', 'series'])
1744 r.add(['.hgignore', 'series'])
1746 commands.add(ui, r)
1745 commands.add(ui, r)
1747 return 0
1746 return 0
1748
1747
1749 def clone(ui, source, dest=None, **opts):
1748 def clone(ui, source, dest=None, **opts):
1750 '''clone main and patch repository at same time
1749 '''clone main and patch repository at same time
1751
1750
1752 If source is local, destination will have no patches applied. If
1751 If source is local, destination will have no patches applied. If
1753 source is remote, this command can not check if patches are
1752 source is remote, this command can not check if patches are
1754 applied in source, so cannot guarantee that patches are not
1753 applied in source, so cannot guarantee that patches are not
1755 applied in destination. If you clone remote repository, be sure
1754 applied in destination. If you clone remote repository, be sure
1756 before that it has no patches applied.
1755 before that it has no patches applied.
1757
1756
1758 Source patch repository is looked for in <src>/.hg/patches by
1757 Source patch repository is looked for in <src>/.hg/patches by
1759 default. Use -p <url> to change.
1758 default. Use -p <url> to change.
1760
1759
1761 The patch directory must be a nested mercurial repository, as
1760 The patch directory must be a nested mercurial repository, as
1762 would be created by qinit -c.
1761 would be created by qinit -c.
1763 '''
1762 '''
1764 def patchdir(repo):
1763 def patchdir(repo):
1765 url = repo.url()
1764 url = repo.url()
1766 if url.endswith('/'):
1765 if url.endswith('/'):
1767 url = url[:-1]
1766 url = url[:-1]
1768 return url + '/.hg/patches'
1767 return url + '/.hg/patches'
1769 if dest is None:
1768 if dest is None:
1770 dest = hg.defaultdest(source)
1769 dest = hg.defaultdest(source)
1771 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1770 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1772 if opts['patches']:
1771 if opts['patches']:
1773 patchespath = ui.expandpath(opts['patches'])
1772 patchespath = ui.expandpath(opts['patches'])
1774 else:
1773 else:
1775 patchespath = patchdir(sr)
1774 patchespath = patchdir(sr)
1776 try:
1775 try:
1777 hg.repository(ui, patchespath)
1776 hg.repository(ui, patchespath)
1778 except error.RepoError:
1777 except error.RepoError:
1779 raise util.Abort(_('versioned patch repository not found'
1778 raise util.Abort(_('versioned patch repository not found'
1780 ' (see qinit -c)'))
1779 ' (see qinit -c)'))
1781 qbase, destrev = None, None
1780 qbase, destrev = None, None
1782 if sr.local():
1781 if sr.local():
1783 if sr.mq.applied:
1782 if sr.mq.applied:
1784 qbase = bin(sr.mq.applied[0].rev)
1783 qbase = bin(sr.mq.applied[0].rev)
1785 if not hg.islocal(dest):
1784 if not hg.islocal(dest):
1786 heads = set(sr.heads())
1785 heads = set(sr.heads())
1787 destrev = list(heads.difference(sr.heads(qbase)))
1786 destrev = list(heads.difference(sr.heads(qbase)))
1788 destrev.append(sr.changelog.parents(qbase)[0])
1787 destrev.append(sr.changelog.parents(qbase)[0])
1789 elif sr.capable('lookup'):
1788 elif sr.capable('lookup'):
1790 try:
1789 try:
1791 qbase = sr.lookup('qbase')
1790 qbase = sr.lookup('qbase')
1792 except error.RepoError:
1791 except error.RepoError:
1793 pass
1792 pass
1794 ui.note(_('cloning main repository\n'))
1793 ui.note(_('cloning main repository\n'))
1795 sr, dr = hg.clone(ui, sr.url(), dest,
1794 sr, dr = hg.clone(ui, sr.url(), dest,
1796 pull=opts['pull'],
1795 pull=opts['pull'],
1797 rev=destrev,
1796 rev=destrev,
1798 update=False,
1797 update=False,
1799 stream=opts['uncompressed'])
1798 stream=opts['uncompressed'])
1800 ui.note(_('cloning patch repository\n'))
1799 ui.note(_('cloning patch repository\n'))
1801 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1800 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1802 pull=opts['pull'], update=not opts['noupdate'],
1801 pull=opts['pull'], update=not opts['noupdate'],
1803 stream=opts['uncompressed'])
1802 stream=opts['uncompressed'])
1804 if dr.local():
1803 if dr.local():
1805 if qbase:
1804 if qbase:
1806 ui.note(_('stripping applied patches from destination '
1805 ui.note(_('stripping applied patches from destination '
1807 'repository\n'))
1806 'repository\n'))
1808 dr.mq.strip(dr, qbase, update=False, backup=None)
1807 dr.mq.strip(dr, qbase, update=False, backup=None)
1809 if not opts['noupdate']:
1808 if not opts['noupdate']:
1810 ui.note(_('updating destination repository\n'))
1809 ui.note(_('updating destination repository\n'))
1811 hg.update(dr, dr.changelog.tip())
1810 hg.update(dr, dr.changelog.tip())
1812
1811
1813 def commit(ui, repo, *pats, **opts):
1812 def commit(ui, repo, *pats, **opts):
1814 """commit changes in the queue repository"""
1813 """commit changes in the queue repository"""
1815 q = repo.mq
1814 q = repo.mq
1816 r = q.qrepo()
1815 r = q.qrepo()
1817 if not r: raise util.Abort('no queue repository')
1816 if not r: raise util.Abort('no queue repository')
1818 commands.commit(r.ui, r, *pats, **opts)
1817 commands.commit(r.ui, r, *pats, **opts)
1819
1818
1820 def series(ui, repo, **opts):
1819 def series(ui, repo, **opts):
1821 """print the entire series file"""
1820 """print the entire series file"""
1822 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1821 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1823 return 0
1822 return 0
1824
1823
1825 def top(ui, repo, **opts):
1824 def top(ui, repo, **opts):
1826 """print the name of the current patch"""
1825 """print the name of the current patch"""
1827 q = repo.mq
1826 q = repo.mq
1828 t = q.applied and q.series_end(True) or 0
1827 t = q.applied and q.series_end(True) or 0
1829 if t:
1828 if t:
1830 return q.qseries(repo, start=t-1, length=1, status='A',
1829 return q.qseries(repo, start=t-1, length=1, status='A',
1831 summary=opts.get('summary'))
1830 summary=opts.get('summary'))
1832 else:
1831 else:
1833 ui.write(_("no patches applied\n"))
1832 ui.write(_("no patches applied\n"))
1834 return 1
1833 return 1
1835
1834
1836 def next(ui, repo, **opts):
1835 def next(ui, repo, **opts):
1837 """print the name of the next patch"""
1836 """print the name of the next patch"""
1838 q = repo.mq
1837 q = repo.mq
1839 end = q.series_end()
1838 end = q.series_end()
1840 if end == len(q.series):
1839 if end == len(q.series):
1841 ui.write(_("all patches applied\n"))
1840 ui.write(_("all patches applied\n"))
1842 return 1
1841 return 1
1843 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1842 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1844
1843
1845 def prev(ui, repo, **opts):
1844 def prev(ui, repo, **opts):
1846 """print the name of the previous patch"""
1845 """print the name of the previous patch"""
1847 q = repo.mq
1846 q = repo.mq
1848 l = len(q.applied)
1847 l = len(q.applied)
1849 if l == 1:
1848 if l == 1:
1850 ui.write(_("only one patch applied\n"))
1849 ui.write(_("only one patch applied\n"))
1851 return 1
1850 return 1
1852 if not l:
1851 if not l:
1853 ui.write(_("no patches applied\n"))
1852 ui.write(_("no patches applied\n"))
1854 return 1
1853 return 1
1855 return q.qseries(repo, start=l-2, length=1, status='A',
1854 return q.qseries(repo, start=l-2, length=1, status='A',
1856 summary=opts.get('summary'))
1855 summary=opts.get('summary'))
1857
1856
1858 def setupheaderopts(ui, opts):
1857 def setupheaderopts(ui, opts):
1859 def do(opt,val):
1858 def do(opt,val):
1860 if not opts[opt] and opts['current' + opt]:
1859 if not opts[opt] and opts['current' + opt]:
1861 opts[opt] = val
1860 opts[opt] = val
1862 do('user', ui.username())
1861 do('user', ui.username())
1863 do('date', "%d %d" % util.makedate())
1862 do('date', "%d %d" % util.makedate())
1864
1863
1865 def new(ui, repo, patch, *args, **opts):
1864 def new(ui, repo, patch, *args, **opts):
1866 """create a new patch
1865 """create a new patch
1867
1866
1868 qnew creates a new patch on top of the currently-applied patch (if
1867 qnew creates a new patch on top of the currently-applied patch (if
1869 any). It will refuse to run if there are any outstanding changes
1868 any). It will refuse to run if there are any outstanding changes
1870 unless -f/--force is specified, in which case the patch will be
1869 unless -f/--force is specified, in which case the patch will be
1871 initialized with them. You may also use -I/--include,
1870 initialized with them. You may also use -I/--include,
1872 -X/--exclude, and/or a list of files after the patch name to add
1871 -X/--exclude, and/or a list of files after the patch name to add
1873 only changes to matching files to the new patch, leaving the rest
1872 only changes to matching files to the new patch, leaving the rest
1874 as uncommitted modifications.
1873 as uncommitted modifications.
1875
1874
1876 -u/--user and -d/--date can be used to set the (given) user and
1875 -u/--user and -d/--date can be used to set the (given) user and
1877 date, respectively. -U/--currentuser and -D/--currentdate set user
1876 date, respectively. -U/--currentuser and -D/--currentdate set user
1878 to current user and date to current date.
1877 to current user and date to current date.
1879
1878
1880 -e/--edit, -m/--message or -l/--logfile set the patch header as
1879 -e/--edit, -m/--message or -l/--logfile set the patch header as
1881 well as the commit message. If none is specified, the header is
1880 well as the commit message. If none is specified, the header is
1882 empty and the commit message is '[mq]: PATCH'.
1881 empty and the commit message is '[mq]: PATCH'.
1883
1882
1884 Use the -g/--git option to keep the patch in the git extended diff
1883 Use the -g/--git option to keep the patch in the git extended diff
1885 format. Read the diffs help topic for more information on why this
1884 format. Read the diffs help topic for more information on why this
1886 is important for preserving permission changes and copy/rename
1885 is important for preserving permission changes and copy/rename
1887 information.
1886 information.
1888 """
1887 """
1889 msg = cmdutil.logmessage(opts)
1888 msg = cmdutil.logmessage(opts)
1890 def getmsg(): return ui.edit(msg, ui.username())
1889 def getmsg(): return ui.edit(msg, ui.username())
1891 q = repo.mq
1890 q = repo.mq
1892 opts['msg'] = msg
1891 opts['msg'] = msg
1893 if opts.get('edit'):
1892 if opts.get('edit'):
1894 opts['msg'] = getmsg
1893 opts['msg'] = getmsg
1895 else:
1894 else:
1896 opts['msg'] = msg
1895 opts['msg'] = msg
1897 setupheaderopts(ui, opts)
1896 setupheaderopts(ui, opts)
1898 q.new(repo, patch, *args, **opts)
1897 q.new(repo, patch, *args, **opts)
1899 q.save_dirty()
1898 q.save_dirty()
1900 return 0
1899 return 0
1901
1900
1902 def refresh(ui, repo, *pats, **opts):
1901 def refresh(ui, repo, *pats, **opts):
1903 """update the current patch
1902 """update the current patch
1904
1903
1905 If any file patterns are provided, the refreshed patch will
1904 If any file patterns are provided, the refreshed patch will
1906 contain only the modifications that match those patterns; the
1905 contain only the modifications that match those patterns; the
1907 remaining modifications will remain in the working directory.
1906 remaining modifications will remain in the working directory.
1908
1907
1909 If -s/--short is specified, files currently included in the patch
1908 If -s/--short is specified, files currently included in the patch
1910 will be refreshed just like matched files and remain in the patch.
1909 will be refreshed just like matched files and remain in the patch.
1911
1910
1912 hg add/remove/copy/rename work as usual, though you might want to
1911 hg add/remove/copy/rename work as usual, though you might want to
1913 use git-style patches (-g/--git or [diff] git=1) to track copies
1912 use git-style patches (-g/--git or [diff] git=1) to track copies
1914 and renames. See the diffs help topic for more information on the
1913 and renames. See the diffs help topic for more information on the
1915 git diff format.
1914 git diff format.
1916 """
1915 """
1917 q = repo.mq
1916 q = repo.mq
1918 message = cmdutil.logmessage(opts)
1917 message = cmdutil.logmessage(opts)
1919 if opts['edit']:
1918 if opts['edit']:
1920 if not q.applied:
1919 if not q.applied:
1921 ui.write(_("no patches applied\n"))
1920 ui.write(_("no patches applied\n"))
1922 return 1
1921 return 1
1923 if message:
1922 if message:
1924 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1923 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1925 patch = q.applied[-1].name
1924 patch = q.applied[-1].name
1926 ph = q.readheaders(patch)
1925 ph = q.readheaders(patch)
1927 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
1926 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
1928 setupheaderopts(ui, opts)
1927 setupheaderopts(ui, opts)
1929 ret = q.refresh(repo, pats, msg=message, **opts)
1928 ret = q.refresh(repo, pats, msg=message, **opts)
1930 q.save_dirty()
1929 q.save_dirty()
1931 return ret
1930 return ret
1932
1931
1933 def diff(ui, repo, *pats, **opts):
1932 def diff(ui, repo, *pats, **opts):
1934 """diff of the current patch and subsequent modifications
1933 """diff of the current patch and subsequent modifications
1935
1934
1936 Shows a diff which includes the current patch as well as any
1935 Shows a diff which includes the current patch as well as any
1937 changes which have been made in the working directory since the
1936 changes which have been made in the working directory since the
1938 last refresh (thus showing what the current patch would become
1937 last refresh (thus showing what the current patch would become
1939 after a qrefresh).
1938 after a qrefresh).
1940
1939
1941 Use 'hg diff' if you only want to see the changes made since the
1940 Use 'hg diff' if you only want to see the changes made since the
1942 last qrefresh, or 'hg export qtip' if you want to see changes made
1941 last qrefresh, or 'hg export qtip' if you want to see changes made
1943 by the current patch without including changes made since the
1942 by the current patch without including changes made since the
1944 qrefresh.
1943 qrefresh.
1945 """
1944 """
1946 repo.mq.diff(repo, pats, opts)
1945 repo.mq.diff(repo, pats, opts)
1947 return 0
1946 return 0
1948
1947
1949 def fold(ui, repo, *files, **opts):
1948 def fold(ui, repo, *files, **opts):
1950 """fold the named patches into the current patch
1949 """fold the named patches into the current patch
1951
1950
1952 Patches must not yet be applied. Each patch will be successively
1951 Patches must not yet be applied. Each patch will be successively
1953 applied to the current patch in the order given. If all the
1952 applied to the current patch in the order given. If all the
1954 patches apply successfully, the current patch will be refreshed
1953 patches apply successfully, the current patch will be refreshed
1955 with the new cumulative patch, and the folded patches will be
1954 with the new cumulative patch, and the folded patches will be
1956 deleted. With -k/--keep, the folded patch files will not be
1955 deleted. With -k/--keep, the folded patch files will not be
1957 removed afterwards.
1956 removed afterwards.
1958
1957
1959 The header for each folded patch will be concatenated with the
1958 The header for each folded patch will be concatenated with the
1960 current patch header, separated by a line of '* * *'."""
1959 current patch header, separated by a line of '* * *'."""
1961
1960
1962 q = repo.mq
1961 q = repo.mq
1963
1962
1964 if not files:
1963 if not files:
1965 raise util.Abort(_('qfold requires at least one patch name'))
1964 raise util.Abort(_('qfold requires at least one patch name'))
1966 if not q.check_toppatch(repo):
1965 if not q.check_toppatch(repo):
1967 raise util.Abort(_('No patches applied'))
1966 raise util.Abort(_('No patches applied'))
1968 q.check_localchanges(repo)
1967 q.check_localchanges(repo)
1969
1968
1970 message = cmdutil.logmessage(opts)
1969 message = cmdutil.logmessage(opts)
1971 if opts['edit']:
1970 if opts['edit']:
1972 if message:
1971 if message:
1973 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1972 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1974
1973
1975 parent = q.lookup('qtip')
1974 parent = q.lookup('qtip')
1976 patches = []
1975 patches = []
1977 messages = []
1976 messages = []
1978 for f in files:
1977 for f in files:
1979 p = q.lookup(f)
1978 p = q.lookup(f)
1980 if p in patches or p == parent:
1979 if p in patches or p == parent:
1981 ui.warn(_('Skipping already folded patch %s') % p)
1980 ui.warn(_('Skipping already folded patch %s') % p)
1982 if q.isapplied(p):
1981 if q.isapplied(p):
1983 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1982 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1984 patches.append(p)
1983 patches.append(p)
1985
1984
1986 for p in patches:
1985 for p in patches:
1987 if not message:
1986 if not message:
1988 ph = q.readheaders(p)
1987 ph = q.readheaders(p)
1989 if ph.message:
1988 if ph.message:
1990 messages.append(ph.message)
1989 messages.append(ph.message)
1991 pf = q.join(p)
1990 pf = q.join(p)
1992 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1991 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1993 if not patchsuccess:
1992 if not patchsuccess:
1994 raise util.Abort(_('Error folding patch %s') % p)
1993 raise util.Abort(_('Error folding patch %s') % p)
1995 patch.updatedir(ui, repo, files)
1994 patch.updatedir(ui, repo, files)
1996
1995
1997 if not message:
1996 if not message:
1998 ph = q.readheaders(parent)
1997 ph = q.readheaders(parent)
1999 message, user = ph.message, ph.user
1998 message, user = ph.message, ph.user
2000 for msg in messages:
1999 for msg in messages:
2001 message.append('* * *')
2000 message.append('* * *')
2002 message.extend(msg)
2001 message.extend(msg)
2003 message = '\n'.join(message)
2002 message = '\n'.join(message)
2004
2003
2005 if opts['edit']:
2004 if opts['edit']:
2006 message = ui.edit(message, user or ui.username())
2005 message = ui.edit(message, user or ui.username())
2007
2006
2008 q.refresh(repo, msg=message)
2007 q.refresh(repo, msg=message)
2009 q.delete(repo, patches, opts)
2008 q.delete(repo, patches, opts)
2010 q.save_dirty()
2009 q.save_dirty()
2011
2010
2012 def goto(ui, repo, patch, **opts):
2011 def goto(ui, repo, patch, **opts):
2013 '''push or pop patches until named patch is at top of stack'''
2012 '''push or pop patches until named patch is at top of stack'''
2014 q = repo.mq
2013 q = repo.mq
2015 patch = q.lookup(patch)
2014 patch = q.lookup(patch)
2016 if q.isapplied(patch):
2015 if q.isapplied(patch):
2017 ret = q.pop(repo, patch, force=opts['force'])
2016 ret = q.pop(repo, patch, force=opts['force'])
2018 else:
2017 else:
2019 ret = q.push(repo, patch, force=opts['force'])
2018 ret = q.push(repo, patch, force=opts['force'])
2020 q.save_dirty()
2019 q.save_dirty()
2021 return ret
2020 return ret
2022
2021
2023 def guard(ui, repo, *args, **opts):
2022 def guard(ui, repo, *args, **opts):
2024 '''set or print guards for a patch
2023 '''set or print guards for a patch
2025
2024
2026 Guards control whether a patch can be pushed. A patch with no
2025 Guards control whether a patch can be pushed. A patch with no
2027 guards is always pushed. A patch with a positive guard ("+foo") is
2026 guards is always pushed. A patch with a positive guard ("+foo") is
2028 pushed only if the qselect command has activated it. A patch with
2027 pushed only if the qselect command has activated it. A patch with
2029 a negative guard ("-foo") is never pushed if the qselect command
2028 a negative guard ("-foo") is never pushed if the qselect command
2030 has activated it.
2029 has activated it.
2031
2030
2032 With no arguments, print the currently active guards.
2031 With no arguments, print the currently active guards.
2033 With arguments, set guards for the named patch.
2032 With arguments, set guards for the named patch.
2034 NOTE: Specifying negative guards now requires '--'.
2033 NOTE: Specifying negative guards now requires '--'.
2035
2034
2036 To set guards on another patch:
2035 To set guards on another patch:
2037 hg qguard -- other.patch +2.6.17 -stable
2036 hg qguard -- other.patch +2.6.17 -stable
2038 '''
2037 '''
2039 def status(idx):
2038 def status(idx):
2040 guards = q.series_guards[idx] or ['unguarded']
2039 guards = q.series_guards[idx] or ['unguarded']
2041 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2040 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2042 q = repo.mq
2041 q = repo.mq
2043 patch = None
2042 patch = None
2044 args = list(args)
2043 args = list(args)
2045 if opts['list']:
2044 if opts['list']:
2046 if args or opts['none']:
2045 if args or opts['none']:
2047 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2046 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2048 for i in xrange(len(q.series)):
2047 for i in xrange(len(q.series)):
2049 status(i)
2048 status(i)
2050 return
2049 return
2051 if not args or args[0][0:1] in '-+':
2050 if not args or args[0][0:1] in '-+':
2052 if not q.applied:
2051 if not q.applied:
2053 raise util.Abort(_('no patches applied'))
2052 raise util.Abort(_('no patches applied'))
2054 patch = q.applied[-1].name
2053 patch = q.applied[-1].name
2055 if patch is None and args[0][0:1] not in '-+':
2054 if patch is None and args[0][0:1] not in '-+':
2056 patch = args.pop(0)
2055 patch = args.pop(0)
2057 if patch is None:
2056 if patch is None:
2058 raise util.Abort(_('no patch to work with'))
2057 raise util.Abort(_('no patch to work with'))
2059 if args or opts['none']:
2058 if args or opts['none']:
2060 idx = q.find_series(patch)
2059 idx = q.find_series(patch)
2061 if idx is None:
2060 if idx is None:
2062 raise util.Abort(_('no patch named %s') % patch)
2061 raise util.Abort(_('no patch named %s') % patch)
2063 q.set_guards(idx, args)
2062 q.set_guards(idx, args)
2064 q.save_dirty()
2063 q.save_dirty()
2065 else:
2064 else:
2066 status(q.series.index(q.lookup(patch)))
2065 status(q.series.index(q.lookup(patch)))
2067
2066
2068 def header(ui, repo, patch=None):
2067 def header(ui, repo, patch=None):
2069 """print the header of the topmost or specified patch"""
2068 """print the header of the topmost or specified patch"""
2070 q = repo.mq
2069 q = repo.mq
2071
2070
2072 if patch:
2071 if patch:
2073 patch = q.lookup(patch)
2072 patch = q.lookup(patch)
2074 else:
2073 else:
2075 if not q.applied:
2074 if not q.applied:
2076 ui.write('no patches applied\n')
2075 ui.write('no patches applied\n')
2077 return 1
2076 return 1
2078 patch = q.lookup('qtip')
2077 patch = q.lookup('qtip')
2079 ph = repo.mq.readheaders(patch)
2078 ph = repo.mq.readheaders(patch)
2080
2079
2081 ui.write('\n'.join(ph.message) + '\n')
2080 ui.write('\n'.join(ph.message) + '\n')
2082
2081
2083 def lastsavename(path):
2082 def lastsavename(path):
2084 (directory, base) = os.path.split(path)
2083 (directory, base) = os.path.split(path)
2085 names = os.listdir(directory)
2084 names = os.listdir(directory)
2086 namere = re.compile("%s.([0-9]+)" % base)
2085 namere = re.compile("%s.([0-9]+)" % base)
2087 maxindex = None
2086 maxindex = None
2088 maxname = None
2087 maxname = None
2089 for f in names:
2088 for f in names:
2090 m = namere.match(f)
2089 m = namere.match(f)
2091 if m:
2090 if m:
2092 index = int(m.group(1))
2091 index = int(m.group(1))
2093 if maxindex is None or index > maxindex:
2092 if maxindex is None or index > maxindex:
2094 maxindex = index
2093 maxindex = index
2095 maxname = f
2094 maxname = f
2096 if maxname:
2095 if maxname:
2097 return (os.path.join(directory, maxname), maxindex)
2096 return (os.path.join(directory, maxname), maxindex)
2098 return (None, None)
2097 return (None, None)
2099
2098
2100 def savename(path):
2099 def savename(path):
2101 (last, index) = lastsavename(path)
2100 (last, index) = lastsavename(path)
2102 if last is None:
2101 if last is None:
2103 index = 0
2102 index = 0
2104 newpath = path + ".%d" % (index + 1)
2103 newpath = path + ".%d" % (index + 1)
2105 return newpath
2104 return newpath
2106
2105
2107 def push(ui, repo, patch=None, **opts):
2106 def push(ui, repo, patch=None, **opts):
2108 """push the next patch onto the stack
2107 """push the next patch onto the stack
2109
2108
2110 When -f/--force is applied, all local changes in patched files
2109 When -f/--force is applied, all local changes in patched files
2111 will be lost.
2110 will be lost.
2112 """
2111 """
2113 q = repo.mq
2112 q = repo.mq
2114 mergeq = None
2113 mergeq = None
2115
2114
2116 if opts['merge']:
2115 if opts['merge']:
2117 if opts['name']:
2116 if opts['name']:
2118 newpath = repo.join(opts['name'])
2117 newpath = repo.join(opts['name'])
2119 else:
2118 else:
2120 newpath, i = lastsavename(q.path)
2119 newpath, i = lastsavename(q.path)
2121 if not newpath:
2120 if not newpath:
2122 ui.warn(_("no saved queues found, please use -n\n"))
2121 ui.warn(_("no saved queues found, please use -n\n"))
2123 return 1
2122 return 1
2124 mergeq = queue(ui, repo.join(""), newpath)
2123 mergeq = queue(ui, repo.join(""), newpath)
2125 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2124 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2126 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2125 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2127 mergeq=mergeq, all=opts.get('all'))
2126 mergeq=mergeq, all=opts.get('all'))
2128 return ret
2127 return ret
2129
2128
2130 def pop(ui, repo, patch=None, **opts):
2129 def pop(ui, repo, patch=None, **opts):
2131 """pop the current patch off the stack
2130 """pop the current patch off the stack
2132
2131
2133 By default, pops off the top of the patch stack. If given a patch
2132 By default, pops off the top of the patch stack. If given a patch
2134 name, keeps popping off patches until the named patch is at the
2133 name, keeps popping off patches until the named patch is at the
2135 top of the stack.
2134 top of the stack.
2136 """
2135 """
2137 localupdate = True
2136 localupdate = True
2138 if opts['name']:
2137 if opts['name']:
2139 q = queue(ui, repo.join(""), repo.join(opts['name']))
2138 q = queue(ui, repo.join(""), repo.join(opts['name']))
2140 ui.warn(_('using patch queue: %s\n') % q.path)
2139 ui.warn(_('using patch queue: %s\n') % q.path)
2141 localupdate = False
2140 localupdate = False
2142 else:
2141 else:
2143 q = repo.mq
2142 q = repo.mq
2144 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2143 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2145 all=opts['all'])
2144 all=opts['all'])
2146 q.save_dirty()
2145 q.save_dirty()
2147 return ret
2146 return ret
2148
2147
2149 def rename(ui, repo, patch, name=None, **opts):
2148 def rename(ui, repo, patch, name=None, **opts):
2150 """rename a patch
2149 """rename a patch
2151
2150
2152 With one argument, renames the current patch to PATCH1.
2151 With one argument, renames the current patch to PATCH1.
2153 With two arguments, renames PATCH1 to PATCH2."""
2152 With two arguments, renames PATCH1 to PATCH2."""
2154
2153
2155 q = repo.mq
2154 q = repo.mq
2156
2155
2157 if not name:
2156 if not name:
2158 name = patch
2157 name = patch
2159 patch = None
2158 patch = None
2160
2159
2161 if patch:
2160 if patch:
2162 patch = q.lookup(patch)
2161 patch = q.lookup(patch)
2163 else:
2162 else:
2164 if not q.applied:
2163 if not q.applied:
2165 ui.write(_('no patches applied\n'))
2164 ui.write(_('no patches applied\n'))
2166 return
2165 return
2167 patch = q.lookup('qtip')
2166 patch = q.lookup('qtip')
2168 absdest = q.join(name)
2167 absdest = q.join(name)
2169 if os.path.isdir(absdest):
2168 if os.path.isdir(absdest):
2170 name = normname(os.path.join(name, os.path.basename(patch)))
2169 name = normname(os.path.join(name, os.path.basename(patch)))
2171 absdest = q.join(name)
2170 absdest = q.join(name)
2172 if os.path.exists(absdest):
2171 if os.path.exists(absdest):
2173 raise util.Abort(_('%s already exists') % absdest)
2172 raise util.Abort(_('%s already exists') % absdest)
2174
2173
2175 if name in q.series:
2174 if name in q.series:
2176 raise util.Abort(_('A patch named %s already exists in the series file') % name)
2175 raise util.Abort(_('A patch named %s already exists in the series file') % name)
2177
2176
2178 if ui.verbose:
2177 if ui.verbose:
2179 ui.write('renaming %s to %s\n' % (patch, name))
2178 ui.write('renaming %s to %s\n' % (patch, name))
2180 i = q.find_series(patch)
2179 i = q.find_series(patch)
2181 guards = q.guard_re.findall(q.full_series[i])
2180 guards = q.guard_re.findall(q.full_series[i])
2182 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2181 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2183 q.parse_series()
2182 q.parse_series()
2184 q.series_dirty = 1
2183 q.series_dirty = 1
2185
2184
2186 info = q.isapplied(patch)
2185 info = q.isapplied(patch)
2187 if info:
2186 if info:
2188 q.applied[info[0]] = statusentry(info[1], name)
2187 q.applied[info[0]] = statusentry(info[1], name)
2189 q.applied_dirty = 1
2188 q.applied_dirty = 1
2190
2189
2191 util.rename(q.join(patch), absdest)
2190 util.rename(q.join(patch), absdest)
2192 r = q.qrepo()
2191 r = q.qrepo()
2193 if r:
2192 if r:
2194 wlock = r.wlock()
2193 wlock = r.wlock()
2195 try:
2194 try:
2196 if r.dirstate[patch] == 'a':
2195 if r.dirstate[patch] == 'a':
2197 r.dirstate.forget(patch)
2196 r.dirstate.forget(patch)
2198 r.dirstate.add(name)
2197 r.dirstate.add(name)
2199 else:
2198 else:
2200 if r.dirstate[name] == 'r':
2199 if r.dirstate[name] == 'r':
2201 r.undelete([name])
2200 r.undelete([name])
2202 r.copy(patch, name)
2201 r.copy(patch, name)
2203 r.remove([patch], False)
2202 r.remove([patch], False)
2204 finally:
2203 finally:
2205 wlock.release()
2204 wlock.release()
2206
2205
2207 q.save_dirty()
2206 q.save_dirty()
2208
2207
2209 def restore(ui, repo, rev, **opts):
2208 def restore(ui, repo, rev, **opts):
2210 """restore the queue state saved by a revision"""
2209 """restore the queue state saved by a revision"""
2211 rev = repo.lookup(rev)
2210 rev = repo.lookup(rev)
2212 q = repo.mq
2211 q = repo.mq
2213 q.restore(repo, rev, delete=opts['delete'],
2212 q.restore(repo, rev, delete=opts['delete'],
2214 qupdate=opts['update'])
2213 qupdate=opts['update'])
2215 q.save_dirty()
2214 q.save_dirty()
2216 return 0
2215 return 0
2217
2216
2218 def save(ui, repo, **opts):
2217 def save(ui, repo, **opts):
2219 """save current queue state"""
2218 """save current queue state"""
2220 q = repo.mq
2219 q = repo.mq
2221 message = cmdutil.logmessage(opts)
2220 message = cmdutil.logmessage(opts)
2222 ret = q.save(repo, msg=message)
2221 ret = q.save(repo, msg=message)
2223 if ret:
2222 if ret:
2224 return ret
2223 return ret
2225 q.save_dirty()
2224 q.save_dirty()
2226 if opts['copy']:
2225 if opts['copy']:
2227 path = q.path
2226 path = q.path
2228 if opts['name']:
2227 if opts['name']:
2229 newpath = os.path.join(q.basepath, opts['name'])
2228 newpath = os.path.join(q.basepath, opts['name'])
2230 if os.path.exists(newpath):
2229 if os.path.exists(newpath):
2231 if not os.path.isdir(newpath):
2230 if not os.path.isdir(newpath):
2232 raise util.Abort(_('destination %s exists and is not '
2231 raise util.Abort(_('destination %s exists and is not '
2233 'a directory') % newpath)
2232 'a directory') % newpath)
2234 if not opts['force']:
2233 if not opts['force']:
2235 raise util.Abort(_('destination %s exists, '
2234 raise util.Abort(_('destination %s exists, '
2236 'use -f to force') % newpath)
2235 'use -f to force') % newpath)
2237 else:
2236 else:
2238 newpath = savename(path)
2237 newpath = savename(path)
2239 ui.warn(_("copy %s to %s\n") % (path, newpath))
2238 ui.warn(_("copy %s to %s\n") % (path, newpath))
2240 util.copyfiles(path, newpath)
2239 util.copyfiles(path, newpath)
2241 if opts['empty']:
2240 if opts['empty']:
2242 try:
2241 try:
2243 os.unlink(q.join(q.status_path))
2242 os.unlink(q.join(q.status_path))
2244 except:
2243 except:
2245 pass
2244 pass
2246 return 0
2245 return 0
2247
2246
2248 def strip(ui, repo, rev, **opts):
2247 def strip(ui, repo, rev, **opts):
2249 """strip a revision and all its descendants from the repository
2248 """strip a revision and all its descendants from the repository
2250
2249
2251 If one of the working directory's parent revisions is stripped, the
2250 If one of the working directory's parent revisions is stripped, the
2252 working directory will be updated to the parent of the stripped
2251 working directory will be updated to the parent of the stripped
2253 revision.
2252 revision.
2254 """
2253 """
2255 backup = 'all'
2254 backup = 'all'
2256 if opts['backup']:
2255 if opts['backup']:
2257 backup = 'strip'
2256 backup = 'strip'
2258 elif opts['nobackup']:
2257 elif opts['nobackup']:
2259 backup = 'none'
2258 backup = 'none'
2260
2259
2261 rev = repo.lookup(rev)
2260 rev = repo.lookup(rev)
2262 p = repo.dirstate.parents()
2261 p = repo.dirstate.parents()
2263 cl = repo.changelog
2262 cl = repo.changelog
2264 update = True
2263 update = True
2265 if p[0] == nullid:
2264 if p[0] == nullid:
2266 update = False
2265 update = False
2267 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2266 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2268 update = False
2267 update = False
2269 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2268 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2270 update = False
2269 update = False
2271
2270
2272 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2271 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2273 return 0
2272 return 0
2274
2273
2275 def select(ui, repo, *args, **opts):
2274 def select(ui, repo, *args, **opts):
2276 '''set or print guarded patches to push
2275 '''set or print guarded patches to push
2277
2276
2278 Use the qguard command to set or print guards on patch, then use
2277 Use the qguard command to set or print guards on patch, then use
2279 qselect to tell mq which guards to use. A patch will be pushed if
2278 qselect to tell mq which guards to use. A patch will be pushed if
2280 it has no guards or any positive guards match the currently
2279 it has no guards or any positive guards match the currently
2281 selected guard, but will not be pushed if any negative guards
2280 selected guard, but will not be pushed if any negative guards
2282 match the current guard. For example:
2281 match the current guard. For example:
2283
2282
2284 qguard foo.patch -stable (negative guard)
2283 qguard foo.patch -stable (negative guard)
2285 qguard bar.patch +stable (positive guard)
2284 qguard bar.patch +stable (positive guard)
2286 qselect stable
2285 qselect stable
2287
2286
2288 This activates the "stable" guard. mq will skip foo.patch (because
2287 This activates the "stable" guard. mq will skip foo.patch (because
2289 it has a negative match) but push bar.patch (because it has a
2288 it has a negative match) but push bar.patch (because it has a
2290 positive match).
2289 positive match).
2291
2290
2292 With no arguments, prints the currently active guards.
2291 With no arguments, prints the currently active guards.
2293 With one argument, sets the active guard.
2292 With one argument, sets the active guard.
2294
2293
2295 Use -n/--none to deactivate guards (no other arguments needed).
2294 Use -n/--none to deactivate guards (no other arguments needed).
2296 When no guards are active, patches with positive guards are
2295 When no guards are active, patches with positive guards are
2297 skipped and patches with negative guards are pushed.
2296 skipped and patches with negative guards are pushed.
2298
2297
2299 qselect can change the guards on applied patches. It does not pop
2298 qselect can change the guards on applied patches. It does not pop
2300 guarded patches by default. Use --pop to pop back to the last
2299 guarded patches by default. Use --pop to pop back to the last
2301 applied patch that is not guarded. Use --reapply (which implies
2300 applied patch that is not guarded. Use --reapply (which implies
2302 --pop) to push back to the current patch afterwards, but skip
2301 --pop) to push back to the current patch afterwards, but skip
2303 guarded patches.
2302 guarded patches.
2304
2303
2305 Use -s/--series to print a list of all guards in the series file
2304 Use -s/--series to print a list of all guards in the series file
2306 (no other arguments needed). Use -v for more information.'''
2305 (no other arguments needed). Use -v for more information.'''
2307
2306
2308 q = repo.mq
2307 q = repo.mq
2309 guards = q.active()
2308 guards = q.active()
2310 if args or opts['none']:
2309 if args or opts['none']:
2311 old_unapplied = q.unapplied(repo)
2310 old_unapplied = q.unapplied(repo)
2312 old_guarded = [i for i in xrange(len(q.applied)) if
2311 old_guarded = [i for i in xrange(len(q.applied)) if
2313 not q.pushable(i)[0]]
2312 not q.pushable(i)[0]]
2314 q.set_active(args)
2313 q.set_active(args)
2315 q.save_dirty()
2314 q.save_dirty()
2316 if not args:
2315 if not args:
2317 ui.status(_('guards deactivated\n'))
2316 ui.status(_('guards deactivated\n'))
2318 if not opts['pop'] and not opts['reapply']:
2317 if not opts['pop'] and not opts['reapply']:
2319 unapplied = q.unapplied(repo)
2318 unapplied = q.unapplied(repo)
2320 guarded = [i for i in xrange(len(q.applied))
2319 guarded = [i for i in xrange(len(q.applied))
2321 if not q.pushable(i)[0]]
2320 if not q.pushable(i)[0]]
2322 if len(unapplied) != len(old_unapplied):
2321 if len(unapplied) != len(old_unapplied):
2323 ui.status(_('number of unguarded, unapplied patches has '
2322 ui.status(_('number of unguarded, unapplied patches has '
2324 'changed from %d to %d\n') %
2323 'changed from %d to %d\n') %
2325 (len(old_unapplied), len(unapplied)))
2324 (len(old_unapplied), len(unapplied)))
2326 if len(guarded) != len(old_guarded):
2325 if len(guarded) != len(old_guarded):
2327 ui.status(_('number of guarded, applied patches has changed '
2326 ui.status(_('number of guarded, applied patches has changed '
2328 'from %d to %d\n') %
2327 'from %d to %d\n') %
2329 (len(old_guarded), len(guarded)))
2328 (len(old_guarded), len(guarded)))
2330 elif opts['series']:
2329 elif opts['series']:
2331 guards = {}
2330 guards = {}
2332 noguards = 0
2331 noguards = 0
2333 for gs in q.series_guards:
2332 for gs in q.series_guards:
2334 if not gs:
2333 if not gs:
2335 noguards += 1
2334 noguards += 1
2336 for g in gs:
2335 for g in gs:
2337 guards.setdefault(g, 0)
2336 guards.setdefault(g, 0)
2338 guards[g] += 1
2337 guards[g] += 1
2339 if ui.verbose:
2338 if ui.verbose:
2340 guards['NONE'] = noguards
2339 guards['NONE'] = noguards
2341 guards = guards.items()
2340 guards = guards.items()
2342 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2341 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2343 if guards:
2342 if guards:
2344 ui.note(_('guards in series file:\n'))
2343 ui.note(_('guards in series file:\n'))
2345 for guard, count in guards:
2344 for guard, count in guards:
2346 ui.note('%2d ' % count)
2345 ui.note('%2d ' % count)
2347 ui.write(guard, '\n')
2346 ui.write(guard, '\n')
2348 else:
2347 else:
2349 ui.note(_('no guards in series file\n'))
2348 ui.note(_('no guards in series file\n'))
2350 else:
2349 else:
2351 if guards:
2350 if guards:
2352 ui.note(_('active guards:\n'))
2351 ui.note(_('active guards:\n'))
2353 for g in guards:
2352 for g in guards:
2354 ui.write(g, '\n')
2353 ui.write(g, '\n')
2355 else:
2354 else:
2356 ui.write(_('no active guards\n'))
2355 ui.write(_('no active guards\n'))
2357 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2356 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2358 popped = False
2357 popped = False
2359 if opts['pop'] or opts['reapply']:
2358 if opts['pop'] or opts['reapply']:
2360 for i in xrange(len(q.applied)):
2359 for i in xrange(len(q.applied)):
2361 pushable, reason = q.pushable(i)
2360 pushable, reason = q.pushable(i)
2362 if not pushable:
2361 if not pushable:
2363 ui.status(_('popping guarded patches\n'))
2362 ui.status(_('popping guarded patches\n'))
2364 popped = True
2363 popped = True
2365 if i == 0:
2364 if i == 0:
2366 q.pop(repo, all=True)
2365 q.pop(repo, all=True)
2367 else:
2366 else:
2368 q.pop(repo, i-1)
2367 q.pop(repo, i-1)
2369 break
2368 break
2370 if popped:
2369 if popped:
2371 try:
2370 try:
2372 if reapply:
2371 if reapply:
2373 ui.status(_('reapplying unguarded patches\n'))
2372 ui.status(_('reapplying unguarded patches\n'))
2374 q.push(repo, reapply)
2373 q.push(repo, reapply)
2375 finally:
2374 finally:
2376 q.save_dirty()
2375 q.save_dirty()
2377
2376
2378 def finish(ui, repo, *revrange, **opts):
2377 def finish(ui, repo, *revrange, **opts):
2379 """move applied patches into repository history
2378 """move applied patches into repository history
2380
2379
2381 Finishes the specified revisions (corresponding to applied
2380 Finishes the specified revisions (corresponding to applied
2382 patches) by moving them out of mq control into regular repository
2381 patches) by moving them out of mq control into regular repository
2383 history.
2382 history.
2384
2383
2385 Accepts a revision range or the -a/--applied option. If --applied
2384 Accepts a revision range or the -a/--applied option. If --applied
2386 is specified, all applied mq revisions are removed from mq
2385 is specified, all applied mq revisions are removed from mq
2387 control. Otherwise, the given revisions must be at the base of the
2386 control. Otherwise, the given revisions must be at the base of the
2388 stack of applied patches.
2387 stack of applied patches.
2389
2388
2390 This can be especially useful if your changes have been applied to
2389 This can be especially useful if your changes have been applied to
2391 an upstream repository, or if you are about to push your changes
2390 an upstream repository, or if you are about to push your changes
2392 to upstream.
2391 to upstream.
2393 """
2392 """
2394 if not opts['applied'] and not revrange:
2393 if not opts['applied'] and not revrange:
2395 raise util.Abort(_('no revisions specified'))
2394 raise util.Abort(_('no revisions specified'))
2396 elif opts['applied']:
2395 elif opts['applied']:
2397 revrange = ('qbase:qtip',) + revrange
2396 revrange = ('qbase:qtip',) + revrange
2398
2397
2399 q = repo.mq
2398 q = repo.mq
2400 if not q.applied:
2399 if not q.applied:
2401 ui.status(_('no patches applied\n'))
2400 ui.status(_('no patches applied\n'))
2402 return 0
2401 return 0
2403
2402
2404 revs = cmdutil.revrange(repo, revrange)
2403 revs = cmdutil.revrange(repo, revrange)
2405 q.finish(repo, revs)
2404 q.finish(repo, revs)
2406 q.save_dirty()
2405 q.save_dirty()
2407 return 0
2406 return 0
2408
2407
2409 def reposetup(ui, repo):
2408 def reposetup(ui, repo):
2410 class mqrepo(repo.__class__):
2409 class mqrepo(repo.__class__):
2411 @util.propertycache
2410 @util.propertycache
2412 def mq(self):
2411 def mq(self):
2413 return queue(self.ui, self.join(""))
2412 return queue(self.ui, self.join(""))
2414
2413
2415 def abort_if_wdir_patched(self, errmsg, force=False):
2414 def abort_if_wdir_patched(self, errmsg, force=False):
2416 if self.mq.applied and not force:
2415 if self.mq.applied and not force:
2417 parent = hex(self.dirstate.parents()[0])
2416 parent = hex(self.dirstate.parents()[0])
2418 if parent in [s.rev for s in self.mq.applied]:
2417 if parent in [s.rev for s in self.mq.applied]:
2419 raise util.Abort(errmsg)
2418 raise util.Abort(errmsg)
2420
2419
2421 def commit(self, *args, **opts):
2420 def commit(self, *args, **opts):
2422 if len(args) >= 6:
2421 if len(args) >= 6:
2423 force = args[5]
2422 force = args[5]
2424 else:
2423 else:
2425 force = opts.get('force')
2424 force = opts.get('force')
2426 self.abort_if_wdir_patched(
2425 self.abort_if_wdir_patched(
2427 _('cannot commit over an applied mq patch'),
2426 _('cannot commit over an applied mq patch'),
2428 force)
2427 force)
2429
2428
2430 return super(mqrepo, self).commit(*args, **opts)
2429 return super(mqrepo, self).commit(*args, **opts)
2431
2430
2432 def push(self, remote, force=False, revs=None):
2431 def push(self, remote, force=False, revs=None):
2433 if self.mq.applied and not force and not revs:
2432 if self.mq.applied and not force and not revs:
2434 raise util.Abort(_('source has mq patches applied'))
2433 raise util.Abort(_('source has mq patches applied'))
2435 return super(mqrepo, self).push(remote, force, revs)
2434 return super(mqrepo, self).push(remote, force, revs)
2436
2435
2437 def tags(self):
2436 def tags(self):
2438 if self.tagscache:
2437 if self.tagscache:
2439 return self.tagscache
2438 return self.tagscache
2440
2439
2441 tagscache = super(mqrepo, self).tags()
2440 tagscache = super(mqrepo, self).tags()
2442
2441
2443 q = self.mq
2442 q = self.mq
2444 if not q.applied:
2443 if not q.applied:
2445 return tagscache
2444 return tagscache
2446
2445
2447 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2446 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2448
2447
2449 if mqtags[-1][0] not in self.changelog.nodemap:
2448 if mqtags[-1][0] not in self.changelog.nodemap:
2450 self.ui.warn(_('mq status file refers to unknown node %s\n')
2449 self.ui.warn(_('mq status file refers to unknown node %s\n')
2451 % short(mqtags[-1][0]))
2450 % short(mqtags[-1][0]))
2452 return tagscache
2451 return tagscache
2453
2452
2454 mqtags.append((mqtags[-1][0], 'qtip'))
2453 mqtags.append((mqtags[-1][0], 'qtip'))
2455 mqtags.append((mqtags[0][0], 'qbase'))
2454 mqtags.append((mqtags[0][0], 'qbase'))
2456 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2455 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2457 for patch in mqtags:
2456 for patch in mqtags:
2458 if patch[1] in tagscache:
2457 if patch[1] in tagscache:
2459 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2458 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2460 % patch[1])
2459 % patch[1])
2461 else:
2460 else:
2462 tagscache[patch[1]] = patch[0]
2461 tagscache[patch[1]] = patch[0]
2463
2462
2464 return tagscache
2463 return tagscache
2465
2464
2466 def _branchtags(self, partial, lrev):
2465 def _branchtags(self, partial, lrev):
2467 q = self.mq
2466 q = self.mq
2468 if not q.applied:
2467 if not q.applied:
2469 return super(mqrepo, self)._branchtags(partial, lrev)
2468 return super(mqrepo, self)._branchtags(partial, lrev)
2470
2469
2471 cl = self.changelog
2470 cl = self.changelog
2472 qbasenode = bin(q.applied[0].rev)
2471 qbasenode = bin(q.applied[0].rev)
2473 if qbasenode not in cl.nodemap:
2472 if qbasenode not in cl.nodemap:
2474 self.ui.warn(_('mq status file refers to unknown node %s\n')
2473 self.ui.warn(_('mq status file refers to unknown node %s\n')
2475 % short(qbasenode))
2474 % short(qbasenode))
2476 return super(mqrepo, self)._branchtags(partial, lrev)
2475 return super(mqrepo, self)._branchtags(partial, lrev)
2477
2476
2478 qbase = cl.rev(qbasenode)
2477 qbase = cl.rev(qbasenode)
2479 start = lrev + 1
2478 start = lrev + 1
2480 if start < qbase:
2479 if start < qbase:
2481 # update the cache (excluding the patches) and save it
2480 # update the cache (excluding the patches) and save it
2482 self._updatebranchcache(partial, lrev+1, qbase)
2481 self._updatebranchcache(partial, lrev+1, qbase)
2483 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2482 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2484 start = qbase
2483 start = qbase
2485 # if start = qbase, the cache is as updated as it should be.
2484 # if start = qbase, the cache is as updated as it should be.
2486 # if start > qbase, the cache includes (part of) the patches.
2485 # if start > qbase, the cache includes (part of) the patches.
2487 # we might as well use it, but we won't save it.
2486 # we might as well use it, but we won't save it.
2488
2487
2489 # update the cache up to the tip
2488 # update the cache up to the tip
2490 self._updatebranchcache(partial, start, len(cl))
2489 self._updatebranchcache(partial, start, len(cl))
2491
2490
2492 return partial
2491 return partial
2493
2492
2494 if repo.local():
2493 if repo.local():
2495 repo.__class__ = mqrepo
2494 repo.__class__ = mqrepo
2496
2495
2497 def mqimport(orig, ui, repo, *args, **kwargs):
2496 def mqimport(orig, ui, repo, *args, **kwargs):
2498 if hasattr(repo, 'abort_if_wdir_patched'):
2497 if hasattr(repo, 'abort_if_wdir_patched'):
2499 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2498 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2500 kwargs.get('force'))
2499 kwargs.get('force'))
2501 return orig(ui, repo, *args, **kwargs)
2500 return orig(ui, repo, *args, **kwargs)
2502
2501
2503 def uisetup(ui):
2502 def uisetup(ui):
2504 extensions.wrapcommand(commands.table, 'import', mqimport)
2503 extensions.wrapcommand(commands.table, 'import', mqimport)
2505
2504
2506 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2505 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2507
2506
2508 cmdtable = {
2507 cmdtable = {
2509 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2508 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2510 "qclone":
2509 "qclone":
2511 (clone,
2510 (clone,
2512 [('', 'pull', None, _('use pull protocol to copy metadata')),
2511 [('', 'pull', None, _('use pull protocol to copy metadata')),
2513 ('U', 'noupdate', None, _('do not update the new working directories')),
2512 ('U', 'noupdate', None, _('do not update the new working directories')),
2514 ('', 'uncompressed', None,
2513 ('', 'uncompressed', None,
2515 _('use uncompressed transfer (fast over LAN)')),
2514 _('use uncompressed transfer (fast over LAN)')),
2516 ('p', 'patches', '', _('location of source patch repository')),
2515 ('p', 'patches', '', _('location of source patch repository')),
2517 ] + commands.remoteopts,
2516 ] + commands.remoteopts,
2518 _('hg qclone [OPTION]... SOURCE [DEST]')),
2517 _('hg qclone [OPTION]... SOURCE [DEST]')),
2519 "qcommit|qci":
2518 "qcommit|qci":
2520 (commit,
2519 (commit,
2521 commands.table["^commit|ci"][1],
2520 commands.table["^commit|ci"][1],
2522 _('hg qcommit [OPTION]... [FILE]...')),
2521 _('hg qcommit [OPTION]... [FILE]...')),
2523 "^qdiff":
2522 "^qdiff":
2524 (diff,
2523 (diff,
2525 commands.diffopts + commands.diffopts2 + commands.walkopts,
2524 commands.diffopts + commands.diffopts2 + commands.walkopts,
2526 _('hg qdiff [OPTION]... [FILE]...')),
2525 _('hg qdiff [OPTION]... [FILE]...')),
2527 "qdelete|qremove|qrm":
2526 "qdelete|qremove|qrm":
2528 (delete,
2527 (delete,
2529 [('k', 'keep', None, _('keep patch file')),
2528 [('k', 'keep', None, _('keep patch file')),
2530 ('r', 'rev', [], _('stop managing a revision'))],
2529 ('r', 'rev', [], _('stop managing a revision'))],
2531 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2530 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2532 'qfold':
2531 'qfold':
2533 (fold,
2532 (fold,
2534 [('e', 'edit', None, _('edit patch header')),
2533 [('e', 'edit', None, _('edit patch header')),
2535 ('k', 'keep', None, _('keep folded patch files')),
2534 ('k', 'keep', None, _('keep folded patch files')),
2536 ] + commands.commitopts,
2535 ] + commands.commitopts,
2537 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2536 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2538 'qgoto':
2537 'qgoto':
2539 (goto,
2538 (goto,
2540 [('f', 'force', None, _('overwrite any local changes'))],
2539 [('f', 'force', None, _('overwrite any local changes'))],
2541 _('hg qgoto [OPTION]... PATCH')),
2540 _('hg qgoto [OPTION]... PATCH')),
2542 'qguard':
2541 'qguard':
2543 (guard,
2542 (guard,
2544 [('l', 'list', None, _('list all patches and guards')),
2543 [('l', 'list', None, _('list all patches and guards')),
2545 ('n', 'none', None, _('drop all guards'))],
2544 ('n', 'none', None, _('drop all guards'))],
2546 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2545 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2547 'qheader': (header, [], _('hg qheader [PATCH]')),
2546 'qheader': (header, [], _('hg qheader [PATCH]')),
2548 "^qimport":
2547 "^qimport":
2549 (qimport,
2548 (qimport,
2550 [('e', 'existing', None, _('import file in patch directory')),
2549 [('e', 'existing', None, _('import file in patch directory')),
2551 ('n', 'name', '', _('patch file name')),
2550 ('n', 'name', '', _('patch file name')),
2552 ('f', 'force', None, _('overwrite existing files')),
2551 ('f', 'force', None, _('overwrite existing files')),
2553 ('r', 'rev', [], _('place existing revisions under mq control')),
2552 ('r', 'rev', [], _('place existing revisions under mq control')),
2554 ('g', 'git', None, _('use git extended diff format')),
2553 ('g', 'git', None, _('use git extended diff format')),
2555 ('P', 'push', None, _('qpush after importing'))],
2554 ('P', 'push', None, _('qpush after importing'))],
2556 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2555 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2557 "^qinit":
2556 "^qinit":
2558 (init,
2557 (init,
2559 [('c', 'create-repo', None, _('create queue repository'))],
2558 [('c', 'create-repo', None, _('create queue repository'))],
2560 _('hg qinit [-c]')),
2559 _('hg qinit [-c]')),
2561 "qnew":
2560 "qnew":
2562 (new,
2561 (new,
2563 [('e', 'edit', None, _('edit commit message')),
2562 [('e', 'edit', None, _('edit commit message')),
2564 ('f', 'force', None, _('import uncommitted changes into patch')),
2563 ('f', 'force', None, _('import uncommitted changes into patch')),
2565 ('g', 'git', None, _('use git extended diff format')),
2564 ('g', 'git', None, _('use git extended diff format')),
2566 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2565 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2567 ('u', 'user', '', _('add "From: <given user>" to patch')),
2566 ('u', 'user', '', _('add "From: <given user>" to patch')),
2568 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2567 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2569 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2568 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2570 ] + commands.walkopts + commands.commitopts,
2569 ] + commands.walkopts + commands.commitopts,
2571 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2570 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2572 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2571 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2573 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2572 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2574 "^qpop":
2573 "^qpop":
2575 (pop,
2574 (pop,
2576 [('a', 'all', None, _('pop all patches')),
2575 [('a', 'all', None, _('pop all patches')),
2577 ('n', 'name', '', _('queue name to pop')),
2576 ('n', 'name', '', _('queue name to pop')),
2578 ('f', 'force', None, _('forget any local changes'))],
2577 ('f', 'force', None, _('forget any local changes'))],
2579 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2578 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2580 "^qpush":
2579 "^qpush":
2581 (push,
2580 (push,
2582 [('f', 'force', None, _('apply if the patch has rejects')),
2581 [('f', 'force', None, _('apply if the patch has rejects')),
2583 ('l', 'list', None, _('list patch name in commit text')),
2582 ('l', 'list', None, _('list patch name in commit text')),
2584 ('a', 'all', None, _('apply all patches')),
2583 ('a', 'all', None, _('apply all patches')),
2585 ('m', 'merge', None, _('merge from another queue')),
2584 ('m', 'merge', None, _('merge from another queue')),
2586 ('n', 'name', '', _('merge queue name'))],
2585 ('n', 'name', '', _('merge queue name'))],
2587 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2586 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2588 "^qrefresh":
2587 "^qrefresh":
2589 (refresh,
2588 (refresh,
2590 [('e', 'edit', None, _('edit commit message')),
2589 [('e', 'edit', None, _('edit commit message')),
2591 ('g', 'git', None, _('use git extended diff format')),
2590 ('g', 'git', None, _('use git extended diff format')),
2592 ('s', 'short', None, _('refresh only files already in the patch and specified files')),
2591 ('s', 'short', None, _('refresh only files already in the patch and specified files')),
2593 ('U', 'currentuser', None, _('add/update "From: <current user>" in patch')),
2592 ('U', 'currentuser', None, _('add/update "From: <current user>" in patch')),
2594 ('u', 'user', '', _('add/update "From: <given user>" in patch')),
2593 ('u', 'user', '', _('add/update "From: <given user>" in patch')),
2595 ('D', 'currentdate', None, _('update "Date: <current date>" in patch (if present)')),
2594 ('D', 'currentdate', None, _('update "Date: <current date>" in patch (if present)')),
2596 ('d', 'date', '', _('update "Date: <given date>" in patch (if present)'))
2595 ('d', 'date', '', _('update "Date: <given date>" in patch (if present)'))
2597 ] + commands.walkopts + commands.commitopts,
2596 ] + commands.walkopts + commands.commitopts,
2598 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2597 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2599 'qrename|qmv':
2598 'qrename|qmv':
2600 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2599 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2601 "qrestore":
2600 "qrestore":
2602 (restore,
2601 (restore,
2603 [('d', 'delete', None, _('delete save entry')),
2602 [('d', 'delete', None, _('delete save entry')),
2604 ('u', 'update', None, _('update queue working directory'))],
2603 ('u', 'update', None, _('update queue working directory'))],
2605 _('hg qrestore [-d] [-u] REV')),
2604 _('hg qrestore [-d] [-u] REV')),
2606 "qsave":
2605 "qsave":
2607 (save,
2606 (save,
2608 [('c', 'copy', None, _('copy patch directory')),
2607 [('c', 'copy', None, _('copy patch directory')),
2609 ('n', 'name', '', _('copy directory name')),
2608 ('n', 'name', '', _('copy directory name')),
2610 ('e', 'empty', None, _('clear queue status file')),
2609 ('e', 'empty', None, _('clear queue status file')),
2611 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2610 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2612 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2611 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2613 "qselect":
2612 "qselect":
2614 (select,
2613 (select,
2615 [('n', 'none', None, _('disable all guards')),
2614 [('n', 'none', None, _('disable all guards')),
2616 ('s', 'series', None, _('list all guards in series file')),
2615 ('s', 'series', None, _('list all guards in series file')),
2617 ('', 'pop', None, _('pop to before first guarded applied patch')),
2616 ('', 'pop', None, _('pop to before first guarded applied patch')),
2618 ('', 'reapply', None, _('pop, then reapply patches'))],
2617 ('', 'reapply', None, _('pop, then reapply patches'))],
2619 _('hg qselect [OPTION]... [GUARD]...')),
2618 _('hg qselect [OPTION]... [GUARD]...')),
2620 "qseries":
2619 "qseries":
2621 (series,
2620 (series,
2622 [('m', 'missing', None, _('print patches not in series')),
2621 [('m', 'missing', None, _('print patches not in series')),
2623 ] + seriesopts,
2622 ] + seriesopts,
2624 _('hg qseries [-ms]')),
2623 _('hg qseries [-ms]')),
2625 "^strip":
2624 "^strip":
2626 (strip,
2625 (strip,
2627 [('f', 'force', None, _('force removal with local changes')),
2626 [('f', 'force', None, _('force removal with local changes')),
2628 ('b', 'backup', None, _('bundle unrelated changesets')),
2627 ('b', 'backup', None, _('bundle unrelated changesets')),
2629 ('n', 'nobackup', None, _('no backups'))],
2628 ('n', 'nobackup', None, _('no backups'))],
2630 _('hg strip [-f] [-b] [-n] REV')),
2629 _('hg strip [-f] [-b] [-n] REV')),
2631 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2630 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2632 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2631 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2633 "qfinish":
2632 "qfinish":
2634 (finish,
2633 (finish,
2635 [('a', 'applied', None, _('finish all applied changesets'))],
2634 [('a', 'applied', None, _('finish all applied changesets'))],
2636 _('hg qfinish [-a] [REV...]')),
2635 _('hg qfinish [-a] [REV...]')),
2637 }
2636 }
@@ -1,270 +1,269 b''
1 # mdiff.py - diff and patch routines for mercurial
1 # mdiff.py - diff and patch routines for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import bdiff, mpatch, util
9 import bdiff, mpatch, util
10 import re, struct
10 import re, struct
11
11
12 def splitnewlines(text):
12 def splitnewlines(text):
13 '''like str.splitlines, but only split on newlines.'''
13 '''like str.splitlines, but only split on newlines.'''
14 lines = [l + '\n' for l in text.split('\n')]
14 lines = [l + '\n' for l in text.split('\n')]
15 if lines:
15 if lines:
16 if lines[-1] == '\n':
16 if lines[-1] == '\n':
17 lines.pop()
17 lines.pop()
18 else:
18 else:
19 lines[-1] = lines[-1][:-1]
19 lines[-1] = lines[-1][:-1]
20 return lines
20 return lines
21
21
22 class diffopts(object):
22 class diffopts(object):
23 '''context is the number of context lines
23 '''context is the number of context lines
24 text treats all files as text
24 text treats all files as text
25 showfunc enables diff -p output
25 showfunc enables diff -p output
26 git enables the git extended patch format
26 git enables the git extended patch format
27 nodates removes dates from diff headers
27 nodates removes dates from diff headers
28 ignorews ignores all whitespace changes in the diff
28 ignorews ignores all whitespace changes in the diff
29 ignorewsamount ignores changes in the amount of whitespace
29 ignorewsamount ignores changes in the amount of whitespace
30 ignoreblanklines ignores changes whose lines are all blank'''
30 ignoreblanklines ignores changes whose lines are all blank'''
31
31
32 defaults = {
32 defaults = {
33 'context': 3,
33 'context': 3,
34 'text': False,
34 'text': False,
35 'showfunc': False,
35 'showfunc': False,
36 'git': False,
36 'git': False,
37 'nodates': False,
37 'nodates': False,
38 'ignorews': False,
38 'ignorews': False,
39 'ignorewsamount': False,
39 'ignorewsamount': False,
40 'ignoreblanklines': False,
40 'ignoreblanklines': False,
41 }
41 }
42
42
43 __slots__ = defaults.keys()
43 __slots__ = defaults.keys()
44
44
45 def __init__(self, **opts):
45 def __init__(self, **opts):
46 for k in self.__slots__:
46 for k in self.__slots__:
47 v = opts.get(k)
47 v = opts.get(k)
48 if v is None:
48 if v is None:
49 v = self.defaults[k]
49 v = self.defaults[k]
50 setattr(self, k, v)
50 setattr(self, k, v)
51
51
52 try:
52 try:
53 self.context = int(self.context)
53 self.context = int(self.context)
54 except ValueError:
54 except ValueError:
55 raise util.Abort(_('diff context lines count must be '
55 raise util.Abort(_('diff context lines count must be '
56 'an integer, not %r') % self.context)
56 'an integer, not %r') % self.context)
57
57
58 defaultopts = diffopts()
58 defaultopts = diffopts()
59
59
60 def wsclean(opts, text):
60 def wsclean(opts, text):
61 if opts.ignorews:
61 if opts.ignorews:
62 text = re.sub('[ \t]+', '', text)
62 text = re.sub('[ \t]+', '', text)
63 elif opts.ignorewsamount:
63 elif opts.ignorewsamount:
64 text = re.sub('[ \t]+', ' ', text)
64 text = re.sub('[ \t]+', ' ', text)
65 text = re.sub('[ \t]+\n', '\n', text)
65 text = re.sub('[ \t]+\n', '\n', text)
66 if opts.ignoreblanklines:
66 if opts.ignoreblanklines:
67 text = re.sub('\n+', '', text)
67 text = re.sub('\n+', '', text)
68 return text
68 return text
69
69
70 def diffline(revs, a, b, opts):
70 def diffline(revs, a, b, opts):
71 parts = ['diff']
71 parts = ['diff']
72 if opts.git:
72 if opts.git:
73 parts.append('--git')
73 parts.append('--git')
74 if revs and not opts.git:
74 if revs and not opts.git:
75 parts.append(' '.join(["-r %s" % rev for rev in revs]))
75 parts.append(' '.join(["-r %s" % rev for rev in revs]))
76 if opts.git:
76 if opts.git:
77 parts.append('a/%s' % a)
77 parts.append('a/%s' % a)
78 parts.append('b/%s' % b)
78 parts.append('b/%s' % b)
79 else:
79 else:
80 parts.append(a)
80 parts.append(a)
81 return ' '.join(parts) + '\n'
81 return ' '.join(parts) + '\n'
82
82
83 def unidiff(a, ad, b, bd, fn1, fn2, r=None, opts=defaultopts):
83 def unidiff(a, ad, b, bd, fn1, fn2, r=None, opts=defaultopts):
84 def datetag(date, addtab=True):
84 def datetag(date, addtab=True):
85 if not opts.git and not opts.nodates:
85 if not opts.git and not opts.nodates:
86 return '\t%s\n' % date
86 return '\t%s\n' % date
87 if addtab and ' ' in fn1:
87 if addtab and ' ' in fn1:
88 return '\t\n'
88 return '\t\n'
89 return '\n'
89 return '\n'
90
90
91 if not a and not b: return ""
91 if not a and not b: return ""
92 epoch = util.datestr((0, 0))
92 epoch = util.datestr((0, 0))
93
93
94 if not opts.text and (util.binary(a) or util.binary(b)):
94 if not opts.text and (util.binary(a) or util.binary(b)):
95 if a and b and len(a) == len(b) and a == b:
95 if a and b and len(a) == len(b) and a == b:
96 return ""
96 return ""
97 l = ['Binary file %s has changed\n' % fn1]
97 l = ['Binary file %s has changed\n' % fn1]
98 elif not a:
98 elif not a:
99 b = splitnewlines(b)
99 b = splitnewlines(b)
100 if a is None:
100 if a is None:
101 l1 = '--- /dev/null%s' % datetag(epoch, False)
101 l1 = '--- /dev/null%s' % datetag(epoch, False)
102 else:
102 else:
103 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
103 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
104 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
104 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
105 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
105 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
106 l = [l1, l2, l3] + ["+" + e for e in b]
106 l = [l1, l2, l3] + ["+" + e for e in b]
107 elif not b:
107 elif not b:
108 a = splitnewlines(a)
108 a = splitnewlines(a)
109 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
109 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
110 if b is None:
110 if b is None:
111 l2 = '+++ /dev/null%s' % datetag(epoch, False)
111 l2 = '+++ /dev/null%s' % datetag(epoch, False)
112 else:
112 else:
113 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
113 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
114 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
114 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
115 l = [l1, l2, l3] + ["-" + e for e in a]
115 l = [l1, l2, l3] + ["-" + e for e in a]
116 else:
116 else:
117 al = splitnewlines(a)
117 al = splitnewlines(a)
118 bl = splitnewlines(b)
118 bl = splitnewlines(b)
119 l = list(bunidiff(a, b, al, bl, "a/" + fn1, "b/" + fn2, opts=opts))
119 l = list(bunidiff(a, b, al, bl, "a/" + fn1, "b/" + fn2, opts=opts))
120 if not l: return ""
120 if not l: return ""
121 # difflib uses a space, rather than a tab
121 # difflib uses a space, rather than a tab
122 l[0] = "%s%s" % (l[0][:-2], datetag(ad))
122 l[0] = "%s%s" % (l[0][:-2], datetag(ad))
123 l[1] = "%s%s" % (l[1][:-2], datetag(bd))
123 l[1] = "%s%s" % (l[1][:-2], datetag(bd))
124
124
125 for ln in xrange(len(l)):
125 for ln in xrange(len(l)):
126 if l[ln][-1] != '\n':
126 if l[ln][-1] != '\n':
127 l[ln] += "\n\ No newline at end of file\n"
127 l[ln] += "\n\ No newline at end of file\n"
128
128
129 if r:
129 if r:
130 l.insert(0, diffline(r, fn1, fn2, opts))
130 l.insert(0, diffline(r, fn1, fn2, opts))
131
131
132 return "".join(l)
132 return "".join(l)
133
133
134 # somewhat self contained replacement for difflib.unified_diff
134 # somewhat self contained replacement for difflib.unified_diff
135 # t1 and t2 are the text to be diffed
135 # t1 and t2 are the text to be diffed
136 # l1 and l2 are the text broken up into lines
136 # l1 and l2 are the text broken up into lines
137 # header1 and header2 are the filenames for the diff output
137 # header1 and header2 are the filenames for the diff output
138 def bunidiff(t1, t2, l1, l2, header1, header2, opts=defaultopts):
138 def bunidiff(t1, t2, l1, l2, header1, header2, opts=defaultopts):
139 def contextend(l, len):
139 def contextend(l, len):
140 ret = l + opts.context
140 ret = l + opts.context
141 if ret > len:
141 if ret > len:
142 ret = len
142 ret = len
143 return ret
143 return ret
144
144
145 def contextstart(l):
145 def contextstart(l):
146 ret = l - opts.context
146 ret = l - opts.context
147 if ret < 0:
147 if ret < 0:
148 return 0
148 return 0
149 return ret
149 return ret
150
150
151 def yieldhunk(hunk, header):
151 def yieldhunk(hunk, header):
152 if header:
152 if header:
153 for x in header:
153 for x in header:
154 yield x
154 yield x
155 (astart, a2, bstart, b2, delta) = hunk
155 (astart, a2, bstart, b2, delta) = hunk
156 aend = contextend(a2, len(l1))
156 aend = contextend(a2, len(l1))
157 alen = aend - astart
157 alen = aend - astart
158 blen = b2 - bstart + aend - a2
158 blen = b2 - bstart + aend - a2
159
159
160 func = ""
160 func = ""
161 if opts.showfunc:
161 if opts.showfunc:
162 # walk backwards from the start of the context
162 # walk backwards from the start of the context
163 # to find a line starting with an alphanumeric char.
163 # to find a line starting with an alphanumeric char.
164 for x in xrange(astart - 1, -1, -1):
164 for x in xrange(astart - 1, -1, -1):
165 t = l1[x].rstrip()
165 t = l1[x].rstrip()
166 if funcre.match(t):
166 if funcre.match(t):
167 func = ' ' + t[:40]
167 func = ' ' + t[:40]
168 break
168 break
169
169
170 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen,
170 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen,
171 bstart + 1, blen, func)
171 bstart + 1, blen, func)
172 for x in delta:
172 for x in delta:
173 yield x
173 yield x
174 for x in xrange(a2, aend):
174 for x in xrange(a2, aend):
175 yield ' ' + l1[x]
175 yield ' ' + l1[x]
176
176
177 header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ]
177 header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ]
178
178
179 if opts.showfunc:
179 if opts.showfunc:
180 funcre = re.compile('\w')
180 funcre = re.compile('\w')
181
181
182 # bdiff.blocks gives us the matching sequences in the files. The loop
182 # bdiff.blocks gives us the matching sequences in the files. The loop
183 # below finds the spaces between those matching sequences and translates
183 # below finds the spaces between those matching sequences and translates
184 # them into diff output.
184 # them into diff output.
185 #
185 #
186 diff = bdiff.blocks(t1, t2)
186 diff = bdiff.blocks(t1, t2)
187 hunk = None
187 hunk = None
188 for i in xrange(len(diff)):
188 for i, s1 in enumerate(diff):
189 # The first match is special.
189 # The first match is special.
190 # we've either found a match starting at line 0 or a match later
190 # we've either found a match starting at line 0 or a match later
191 # in the file. If it starts later, old and new below will both be
191 # in the file. If it starts later, old and new below will both be
192 # empty and we'll continue to the next match.
192 # empty and we'll continue to the next match.
193 if i > 0:
193 if i > 0:
194 s = diff[i-1]
194 s = diff[i-1]
195 else:
195 else:
196 s = [0, 0, 0, 0]
196 s = [0, 0, 0, 0]
197 delta = []
197 delta = []
198 s1 = diff[i]
199 a1 = s[1]
198 a1 = s[1]
200 a2 = s1[0]
199 a2 = s1[0]
201 b1 = s[3]
200 b1 = s[3]
202 b2 = s1[2]
201 b2 = s1[2]
203
202
204 old = l1[a1:a2]
203 old = l1[a1:a2]
205 new = l2[b1:b2]
204 new = l2[b1:b2]
206
205
207 # bdiff sometimes gives huge matches past eof, this check eats them,
206 # bdiff sometimes gives huge matches past eof, this check eats them,
208 # and deals with the special first match case described above
207 # and deals with the special first match case described above
209 if not old and not new:
208 if not old and not new:
210 continue
209 continue
211
210
212 if opts.ignorews or opts.ignorewsamount or opts.ignoreblanklines:
211 if opts.ignorews or opts.ignorewsamount or opts.ignoreblanklines:
213 if wsclean(opts, "".join(old)) == wsclean(opts, "".join(new)):
212 if wsclean(opts, "".join(old)) == wsclean(opts, "".join(new)):
214 continue
213 continue
215
214
216 astart = contextstart(a1)
215 astart = contextstart(a1)
217 bstart = contextstart(b1)
216 bstart = contextstart(b1)
218 prev = None
217 prev = None
219 if hunk:
218 if hunk:
220 # join with the previous hunk if it falls inside the context
219 # join with the previous hunk if it falls inside the context
221 if astart < hunk[1] + opts.context + 1:
220 if astart < hunk[1] + opts.context + 1:
222 prev = hunk
221 prev = hunk
223 astart = hunk[1]
222 astart = hunk[1]
224 bstart = hunk[3]
223 bstart = hunk[3]
225 else:
224 else:
226 for x in yieldhunk(hunk, header):
225 for x in yieldhunk(hunk, header):
227 yield x
226 yield x
228 # we only want to yield the header if the files differ, and
227 # we only want to yield the header if the files differ, and
229 # we only want to yield it once.
228 # we only want to yield it once.
230 header = None
229 header = None
231 if prev:
230 if prev:
232 # we've joined the previous hunk, record the new ending points.
231 # we've joined the previous hunk, record the new ending points.
233 hunk[1] = a2
232 hunk[1] = a2
234 hunk[3] = b2
233 hunk[3] = b2
235 delta = hunk[4]
234 delta = hunk[4]
236 else:
235 else:
237 # create a new hunk
236 # create a new hunk
238 hunk = [ astart, a2, bstart, b2, delta ]
237 hunk = [ astart, a2, bstart, b2, delta ]
239
238
240 delta[len(delta):] = [ ' ' + x for x in l1[astart:a1] ]
239 delta[len(delta):] = [ ' ' + x for x in l1[astart:a1] ]
241 delta[len(delta):] = [ '-' + x for x in old ]
240 delta[len(delta):] = [ '-' + x for x in old ]
242 delta[len(delta):] = [ '+' + x for x in new ]
241 delta[len(delta):] = [ '+' + x for x in new ]
243
242
244 if hunk:
243 if hunk:
245 for x in yieldhunk(hunk, header):
244 for x in yieldhunk(hunk, header):
246 yield x
245 yield x
247
246
248 def patchtext(bin):
247 def patchtext(bin):
249 pos = 0
248 pos = 0
250 t = []
249 t = []
251 while pos < len(bin):
250 while pos < len(bin):
252 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
251 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
253 pos += 12
252 pos += 12
254 t.append(bin[pos:pos + l])
253 t.append(bin[pos:pos + l])
255 pos += l
254 pos += l
256 return "".join(t)
255 return "".join(t)
257
256
258 def patch(a, bin):
257 def patch(a, bin):
259 return mpatch.patches(a, [bin])
258 return mpatch.patches(a, [bin])
260
259
261 # similar to difflib.SequenceMatcher.get_matching_blocks
260 # similar to difflib.SequenceMatcher.get_matching_blocks
262 def get_matching_blocks(a, b):
261 def get_matching_blocks(a, b):
263 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
262 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
264
263
265 def trivialdiffheader(length):
264 def trivialdiffheader(length):
266 return struct.pack(">lll", 0, 0, length)
265 return struct.pack(">lll", 0, 0, length)
267
266
268 patches = mpatch.patches
267 patches = mpatch.patches
269 patchedsize = mpatch.patchedsize
268 patchedsize = mpatch.patchedsize
270 textdiff = bdiff.bdiff
269 textdiff = bdiff.bdiff
@@ -1,1395 +1,1394 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
8
8
9 from i18n import _
9 from i18n import _
10 from node import hex, nullid, short
10 from node import hex, nullid, short
11 import base85, cmdutil, mdiff, util, diffhelpers, copies
11 import base85, cmdutil, mdiff, util, diffhelpers, copies
12 import cStringIO, email.Parser, os, re, math
12 import cStringIO, email.Parser, os, re, math
13 import sys, tempfile, zlib
13 import sys, tempfile, zlib
14
14
15 gitre = re.compile('diff --git a/(.*) b/(.*)')
15 gitre = re.compile('diff --git a/(.*) b/(.*)')
16
16
17 class PatchError(Exception):
17 class PatchError(Exception):
18 pass
18 pass
19
19
20 class NoHunks(PatchError):
20 class NoHunks(PatchError):
21 pass
21 pass
22
22
23 # helper functions
23 # helper functions
24
24
25 def copyfile(src, dst, basedir):
25 def copyfile(src, dst, basedir):
26 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
26 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
27 if os.path.exists(absdst):
27 if os.path.exists(absdst):
28 raise util.Abort(_("cannot create %s: destination already exists") %
28 raise util.Abort(_("cannot create %s: destination already exists") %
29 dst)
29 dst)
30
30
31 dstdir = os.path.dirname(absdst)
31 dstdir = os.path.dirname(absdst)
32 if dstdir and not os.path.isdir(dstdir):
32 if dstdir and not os.path.isdir(dstdir):
33 try:
33 try:
34 os.makedirs(dstdir)
34 os.makedirs(dstdir)
35 except IOError:
35 except IOError:
36 raise util.Abort(
36 raise util.Abort(
37 _("cannot create %s: unable to create destination directory")
37 _("cannot create %s: unable to create destination directory")
38 % dst)
38 % dst)
39
39
40 util.copyfile(abssrc, absdst)
40 util.copyfile(abssrc, absdst)
41
41
42 # public functions
42 # public functions
43
43
44 def extract(ui, fileobj):
44 def extract(ui, fileobj):
45 '''extract patch from data read from fileobj.
45 '''extract patch from data read from fileobj.
46
46
47 patch can be a normal patch or contained in an email message.
47 patch can be a normal patch or contained in an email message.
48
48
49 return tuple (filename, message, user, date, node, p1, p2).
49 return tuple (filename, message, user, date, node, p1, p2).
50 Any item in the returned tuple can be None. If filename is None,
50 Any item in the returned tuple can be None. If filename is None,
51 fileobj did not contain a patch. Caller must unlink filename when done.'''
51 fileobj did not contain a patch. Caller must unlink filename when done.'''
52
52
53 # attempt to detect the start of a patch
53 # attempt to detect the start of a patch
54 # (this heuristic is borrowed from quilt)
54 # (this heuristic is borrowed from quilt)
55 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
55 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
56 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
56 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
57 r'(---|\*\*\*)[ \t])', re.MULTILINE)
57 r'(---|\*\*\*)[ \t])', re.MULTILINE)
58
58
59 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
59 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
60 tmpfp = os.fdopen(fd, 'w')
60 tmpfp = os.fdopen(fd, 'w')
61 try:
61 try:
62 msg = email.Parser.Parser().parse(fileobj)
62 msg = email.Parser.Parser().parse(fileobj)
63
63
64 subject = msg['Subject']
64 subject = msg['Subject']
65 user = msg['From']
65 user = msg['From']
66 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
66 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
67 # should try to parse msg['Date']
67 # should try to parse msg['Date']
68 date = None
68 date = None
69 nodeid = None
69 nodeid = None
70 branch = None
70 branch = None
71 parents = []
71 parents = []
72
72
73 if subject:
73 if subject:
74 if subject.startswith('[PATCH'):
74 if subject.startswith('[PATCH'):
75 pend = subject.find(']')
75 pend = subject.find(']')
76 if pend >= 0:
76 if pend >= 0:
77 subject = subject[pend+1:].lstrip()
77 subject = subject[pend+1:].lstrip()
78 subject = subject.replace('\n\t', ' ')
78 subject = subject.replace('\n\t', ' ')
79 ui.debug('Subject: %s\n' % subject)
79 ui.debug('Subject: %s\n' % subject)
80 if user:
80 if user:
81 ui.debug('From: %s\n' % user)
81 ui.debug('From: %s\n' % user)
82 diffs_seen = 0
82 diffs_seen = 0
83 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
83 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
84 message = ''
84 message = ''
85 for part in msg.walk():
85 for part in msg.walk():
86 content_type = part.get_content_type()
86 content_type = part.get_content_type()
87 ui.debug('Content-Type: %s\n' % content_type)
87 ui.debug('Content-Type: %s\n' % content_type)
88 if content_type not in ok_types:
88 if content_type not in ok_types:
89 continue
89 continue
90 payload = part.get_payload(decode=True)
90 payload = part.get_payload(decode=True)
91 m = diffre.search(payload)
91 m = diffre.search(payload)
92 if m:
92 if m:
93 hgpatch = False
93 hgpatch = False
94 ignoretext = False
94 ignoretext = False
95
95
96 ui.debug(_('found patch at byte %d\n') % m.start(0))
96 ui.debug(_('found patch at byte %d\n') % m.start(0))
97 diffs_seen += 1
97 diffs_seen += 1
98 cfp = cStringIO.StringIO()
98 cfp = cStringIO.StringIO()
99 for line in payload[:m.start(0)].splitlines():
99 for line in payload[:m.start(0)].splitlines():
100 if line.startswith('# HG changeset patch'):
100 if line.startswith('# HG changeset patch'):
101 ui.debug(_('patch generated by hg export\n'))
101 ui.debug(_('patch generated by hg export\n'))
102 hgpatch = True
102 hgpatch = True
103 # drop earlier commit message content
103 # drop earlier commit message content
104 cfp.seek(0)
104 cfp.seek(0)
105 cfp.truncate()
105 cfp.truncate()
106 subject = None
106 subject = None
107 elif hgpatch:
107 elif hgpatch:
108 if line.startswith('# User '):
108 if line.startswith('# User '):
109 user = line[7:]
109 user = line[7:]
110 ui.debug('From: %s\n' % user)
110 ui.debug('From: %s\n' % user)
111 elif line.startswith("# Date "):
111 elif line.startswith("# Date "):
112 date = line[7:]
112 date = line[7:]
113 elif line.startswith("# Branch "):
113 elif line.startswith("# Branch "):
114 branch = line[9:]
114 branch = line[9:]
115 elif line.startswith("# Node ID "):
115 elif line.startswith("# Node ID "):
116 nodeid = line[10:]
116 nodeid = line[10:]
117 elif line.startswith("# Parent "):
117 elif line.startswith("# Parent "):
118 parents.append(line[10:])
118 parents.append(line[10:])
119 elif line == '---' and gitsendmail:
119 elif line == '---' and gitsendmail:
120 ignoretext = True
120 ignoretext = True
121 if not line.startswith('# ') and not ignoretext:
121 if not line.startswith('# ') and not ignoretext:
122 cfp.write(line)
122 cfp.write(line)
123 cfp.write('\n')
123 cfp.write('\n')
124 message = cfp.getvalue()
124 message = cfp.getvalue()
125 if tmpfp:
125 if tmpfp:
126 tmpfp.write(payload)
126 tmpfp.write(payload)
127 if not payload.endswith('\n'):
127 if not payload.endswith('\n'):
128 tmpfp.write('\n')
128 tmpfp.write('\n')
129 elif not diffs_seen and message and content_type == 'text/plain':
129 elif not diffs_seen and message and content_type == 'text/plain':
130 message += '\n' + payload
130 message += '\n' + payload
131 except:
131 except:
132 tmpfp.close()
132 tmpfp.close()
133 os.unlink(tmpname)
133 os.unlink(tmpname)
134 raise
134 raise
135
135
136 if subject and not message.startswith(subject):
136 if subject and not message.startswith(subject):
137 message = '%s\n%s' % (subject, message)
137 message = '%s\n%s' % (subject, message)
138 tmpfp.close()
138 tmpfp.close()
139 if not diffs_seen:
139 if not diffs_seen:
140 os.unlink(tmpname)
140 os.unlink(tmpname)
141 return None, message, user, date, branch, None, None, None
141 return None, message, user, date, branch, None, None, None
142 p1 = parents and parents.pop(0) or None
142 p1 = parents and parents.pop(0) or None
143 p2 = parents and parents.pop(0) or None
143 p2 = parents and parents.pop(0) or None
144 return tmpname, message, user, date, branch, nodeid, p1, p2
144 return tmpname, message, user, date, branch, nodeid, p1, p2
145
145
146 GP_PATCH = 1 << 0 # we have to run patch
146 GP_PATCH = 1 << 0 # we have to run patch
147 GP_FILTER = 1 << 1 # there's some copy/rename operation
147 GP_FILTER = 1 << 1 # there's some copy/rename operation
148 GP_BINARY = 1 << 2 # there's a binary patch
148 GP_BINARY = 1 << 2 # there's a binary patch
149
149
150 class patchmeta:
150 class patchmeta:
151 """Patched file metadata
151 """Patched file metadata
152
152
153 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
153 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
154 or COPY. 'path' is patched file path. 'oldpath' is set to the
154 or COPY. 'path' is patched file path. 'oldpath' is set to the
155 origin file when 'op' is either COPY or RENAME, None otherwise. If
155 origin file when 'op' is either COPY or RENAME, None otherwise. If
156 file mode is changed, 'mode' is a tuple (islink, isexec) where
156 file mode is changed, 'mode' is a tuple (islink, isexec) where
157 'islink' is True if the file is a symlink and 'isexec' is True if
157 'islink' is True if the file is a symlink and 'isexec' is True if
158 the file is executable. Otherwise, 'mode' is None.
158 the file is executable. Otherwise, 'mode' is None.
159 """
159 """
160 def __init__(self, path):
160 def __init__(self, path):
161 self.path = path
161 self.path = path
162 self.oldpath = None
162 self.oldpath = None
163 self.mode = None
163 self.mode = None
164 self.op = 'MODIFY'
164 self.op = 'MODIFY'
165 self.lineno = 0
165 self.lineno = 0
166 self.binary = False
166 self.binary = False
167
167
168 def setmode(self, mode):
168 def setmode(self, mode):
169 islink = mode & 020000
169 islink = mode & 020000
170 isexec = mode & 0100
170 isexec = mode & 0100
171 self.mode = (islink, isexec)
171 self.mode = (islink, isexec)
172
172
173 def readgitpatch(lr):
173 def readgitpatch(lr):
174 """extract git-style metadata about patches from <patchname>"""
174 """extract git-style metadata about patches from <patchname>"""
175
175
176 # Filter patch for git information
176 # Filter patch for git information
177 gp = None
177 gp = None
178 gitpatches = []
178 gitpatches = []
179 # Can have a git patch with only metadata, causing patch to complain
179 # Can have a git patch with only metadata, causing patch to complain
180 dopatch = 0
180 dopatch = 0
181
181
182 lineno = 0
182 lineno = 0
183 for line in lr:
183 for line in lr:
184 lineno += 1
184 lineno += 1
185 if line.startswith('diff --git'):
185 if line.startswith('diff --git'):
186 m = gitre.match(line)
186 m = gitre.match(line)
187 if m:
187 if m:
188 if gp:
188 if gp:
189 gitpatches.append(gp)
189 gitpatches.append(gp)
190 src, dst = m.group(1, 2)
190 src, dst = m.group(1, 2)
191 gp = patchmeta(dst)
191 gp = patchmeta(dst)
192 gp.lineno = lineno
192 gp.lineno = lineno
193 elif gp:
193 elif gp:
194 if line.startswith('--- '):
194 if line.startswith('--- '):
195 if gp.op in ('COPY', 'RENAME'):
195 if gp.op in ('COPY', 'RENAME'):
196 dopatch |= GP_FILTER
196 dopatch |= GP_FILTER
197 gitpatches.append(gp)
197 gitpatches.append(gp)
198 gp = None
198 gp = None
199 dopatch |= GP_PATCH
199 dopatch |= GP_PATCH
200 continue
200 continue
201 if line.startswith('rename from '):
201 if line.startswith('rename from '):
202 gp.op = 'RENAME'
202 gp.op = 'RENAME'
203 gp.oldpath = line[12:].rstrip()
203 gp.oldpath = line[12:].rstrip()
204 elif line.startswith('rename to '):
204 elif line.startswith('rename to '):
205 gp.path = line[10:].rstrip()
205 gp.path = line[10:].rstrip()
206 elif line.startswith('copy from '):
206 elif line.startswith('copy from '):
207 gp.op = 'COPY'
207 gp.op = 'COPY'
208 gp.oldpath = line[10:].rstrip()
208 gp.oldpath = line[10:].rstrip()
209 elif line.startswith('copy to '):
209 elif line.startswith('copy to '):
210 gp.path = line[8:].rstrip()
210 gp.path = line[8:].rstrip()
211 elif line.startswith('deleted file'):
211 elif line.startswith('deleted file'):
212 gp.op = 'DELETE'
212 gp.op = 'DELETE'
213 # is the deleted file a symlink?
213 # is the deleted file a symlink?
214 gp.setmode(int(line.rstrip()[-6:], 8))
214 gp.setmode(int(line.rstrip()[-6:], 8))
215 elif line.startswith('new file mode '):
215 elif line.startswith('new file mode '):
216 gp.op = 'ADD'
216 gp.op = 'ADD'
217 gp.setmode(int(line.rstrip()[-6:], 8))
217 gp.setmode(int(line.rstrip()[-6:], 8))
218 elif line.startswith('new mode '):
218 elif line.startswith('new mode '):
219 gp.setmode(int(line.rstrip()[-6:], 8))
219 gp.setmode(int(line.rstrip()[-6:], 8))
220 elif line.startswith('GIT binary patch'):
220 elif line.startswith('GIT binary patch'):
221 dopatch |= GP_BINARY
221 dopatch |= GP_BINARY
222 gp.binary = True
222 gp.binary = True
223 if gp:
223 if gp:
224 gitpatches.append(gp)
224 gitpatches.append(gp)
225
225
226 if not gitpatches:
226 if not gitpatches:
227 dopatch = GP_PATCH
227 dopatch = GP_PATCH
228
228
229 return (dopatch, gitpatches)
229 return (dopatch, gitpatches)
230
230
231 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
231 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
232 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
232 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
233 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
233 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
234
234
235 class patchfile:
235 class patchfile:
236 def __init__(self, ui, fname, opener, missing=False):
236 def __init__(self, ui, fname, opener, missing=False):
237 self.fname = fname
237 self.fname = fname
238 self.opener = opener
238 self.opener = opener
239 self.ui = ui
239 self.ui = ui
240 self.lines = []
240 self.lines = []
241 self.exists = False
241 self.exists = False
242 self.missing = missing
242 self.missing = missing
243 if not missing:
243 if not missing:
244 try:
244 try:
245 self.lines = self.readlines(fname)
245 self.lines = self.readlines(fname)
246 self.exists = True
246 self.exists = True
247 except IOError:
247 except IOError:
248 pass
248 pass
249 else:
249 else:
250 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
250 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
251
251
252 self.hash = {}
252 self.hash = {}
253 self.dirty = 0
253 self.dirty = 0
254 self.offset = 0
254 self.offset = 0
255 self.rej = []
255 self.rej = []
256 self.fileprinted = False
256 self.fileprinted = False
257 self.printfile(False)
257 self.printfile(False)
258 self.hunks = 0
258 self.hunks = 0
259
259
260 def readlines(self, fname):
260 def readlines(self, fname):
261 fp = self.opener(fname, 'r')
261 fp = self.opener(fname, 'r')
262 try:
262 try:
263 return fp.readlines()
263 return fp.readlines()
264 finally:
264 finally:
265 fp.close()
265 fp.close()
266
266
267 def writelines(self, fname, lines):
267 def writelines(self, fname, lines):
268 fp = self.opener(fname, 'w')
268 fp = self.opener(fname, 'w')
269 try:
269 try:
270 fp.writelines(lines)
270 fp.writelines(lines)
271 finally:
271 finally:
272 fp.close()
272 fp.close()
273
273
274 def unlink(self, fname):
274 def unlink(self, fname):
275 os.unlink(fname)
275 os.unlink(fname)
276
276
277 def printfile(self, warn):
277 def printfile(self, warn):
278 if self.fileprinted:
278 if self.fileprinted:
279 return
279 return
280 if warn or self.ui.verbose:
280 if warn or self.ui.verbose:
281 self.fileprinted = True
281 self.fileprinted = True
282 s = _("patching file %s\n") % self.fname
282 s = _("patching file %s\n") % self.fname
283 if warn:
283 if warn:
284 self.ui.warn(s)
284 self.ui.warn(s)
285 else:
285 else:
286 self.ui.note(s)
286 self.ui.note(s)
287
287
288
288
289 def findlines(self, l, linenum):
289 def findlines(self, l, linenum):
290 # looks through the hash and finds candidate lines. The
290 # looks through the hash and finds candidate lines. The
291 # result is a list of line numbers sorted based on distance
291 # result is a list of line numbers sorted based on distance
292 # from linenum
292 # from linenum
293 def sorter(a, b):
293 def sorter(a, b):
294 vala = abs(a - linenum)
294 vala = abs(a - linenum)
295 valb = abs(b - linenum)
295 valb = abs(b - linenum)
296 return cmp(vala, valb)
296 return cmp(vala, valb)
297
297
298 try:
298 try:
299 cand = self.hash[l]
299 cand = self.hash[l]
300 except:
300 except:
301 return []
301 return []
302
302
303 if len(cand) > 1:
303 if len(cand) > 1:
304 # resort our list of potentials forward then back.
304 # resort our list of potentials forward then back.
305 cand.sort(sorter)
305 cand.sort(sorter)
306 return cand
306 return cand
307
307
308 def hashlines(self):
308 def hashlines(self):
309 self.hash = {}
309 self.hash = {}
310 for x in xrange(len(self.lines)):
310 for x, s in enumerate(self.lines):
311 s = self.lines[x]
312 self.hash.setdefault(s, []).append(x)
311 self.hash.setdefault(s, []).append(x)
313
312
314 def write_rej(self):
313 def write_rej(self):
315 # our rejects are a little different from patch(1). This always
314 # our rejects are a little different from patch(1). This always
316 # creates rejects in the same form as the original patch. A file
315 # creates rejects in the same form as the original patch. A file
317 # header is inserted so that you can run the reject through patch again
316 # header is inserted so that you can run the reject through patch again
318 # without having to type the filename.
317 # without having to type the filename.
319
318
320 if not self.rej:
319 if not self.rej:
321 return
320 return
322
321
323 fname = self.fname + ".rej"
322 fname = self.fname + ".rej"
324 self.ui.warn(
323 self.ui.warn(
325 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
324 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
326 (len(self.rej), self.hunks, fname))
325 (len(self.rej), self.hunks, fname))
327
326
328 def rejlines():
327 def rejlines():
329 base = os.path.basename(self.fname)
328 base = os.path.basename(self.fname)
330 yield "--- %s\n+++ %s\n" % (base, base)
329 yield "--- %s\n+++ %s\n" % (base, base)
331 for x in self.rej:
330 for x in self.rej:
332 for l in x.hunk:
331 for l in x.hunk:
333 yield l
332 yield l
334 if l[-1] != '\n':
333 if l[-1] != '\n':
335 yield "\n\ No newline at end of file\n"
334 yield "\n\ No newline at end of file\n"
336
335
337 self.writelines(fname, rejlines())
336 self.writelines(fname, rejlines())
338
337
339 def write(self, dest=None):
338 def write(self, dest=None):
340 if not self.dirty:
339 if not self.dirty:
341 return
340 return
342 if not dest:
341 if not dest:
343 dest = self.fname
342 dest = self.fname
344 self.writelines(dest, self.lines)
343 self.writelines(dest, self.lines)
345
344
346 def close(self):
345 def close(self):
347 self.write()
346 self.write()
348 self.write_rej()
347 self.write_rej()
349
348
350 def apply(self, h, reverse):
349 def apply(self, h, reverse):
351 if not h.complete():
350 if not h.complete():
352 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
351 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
353 (h.number, h.desc, len(h.a), h.lena, len(h.b),
352 (h.number, h.desc, len(h.a), h.lena, len(h.b),
354 h.lenb))
353 h.lenb))
355
354
356 self.hunks += 1
355 self.hunks += 1
357 if reverse:
356 if reverse:
358 h.reverse()
357 h.reverse()
359
358
360 if self.missing:
359 if self.missing:
361 self.rej.append(h)
360 self.rej.append(h)
362 return -1
361 return -1
363
362
364 if self.exists and h.createfile():
363 if self.exists and h.createfile():
365 self.ui.warn(_("file %s already exists\n") % self.fname)
364 self.ui.warn(_("file %s already exists\n") % self.fname)
366 self.rej.append(h)
365 self.rej.append(h)
367 return -1
366 return -1
368
367
369 if isinstance(h, githunk):
368 if isinstance(h, githunk):
370 if h.rmfile():
369 if h.rmfile():
371 self.unlink(self.fname)
370 self.unlink(self.fname)
372 else:
371 else:
373 self.lines[:] = h.new()
372 self.lines[:] = h.new()
374 self.offset += len(h.new())
373 self.offset += len(h.new())
375 self.dirty = 1
374 self.dirty = 1
376 return 0
375 return 0
377
376
378 # fast case first, no offsets, no fuzz
377 # fast case first, no offsets, no fuzz
379 old = h.old()
378 old = h.old()
380 # patch starts counting at 1 unless we are adding the file
379 # patch starts counting at 1 unless we are adding the file
381 if h.starta == 0:
380 if h.starta == 0:
382 start = 0
381 start = 0
383 else:
382 else:
384 start = h.starta + self.offset - 1
383 start = h.starta + self.offset - 1
385 orig_start = start
384 orig_start = start
386 if diffhelpers.testhunk(old, self.lines, start) == 0:
385 if diffhelpers.testhunk(old, self.lines, start) == 0:
387 if h.rmfile():
386 if h.rmfile():
388 self.unlink(self.fname)
387 self.unlink(self.fname)
389 else:
388 else:
390 self.lines[start : start + h.lena] = h.new()
389 self.lines[start : start + h.lena] = h.new()
391 self.offset += h.lenb - h.lena
390 self.offset += h.lenb - h.lena
392 self.dirty = 1
391 self.dirty = 1
393 return 0
392 return 0
394
393
395 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
394 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
396 self.hashlines()
395 self.hashlines()
397 if h.hunk[-1][0] != ' ':
396 if h.hunk[-1][0] != ' ':
398 # if the hunk tried to put something at the bottom of the file
397 # if the hunk tried to put something at the bottom of the file
399 # override the start line and use eof here
398 # override the start line and use eof here
400 search_start = len(self.lines)
399 search_start = len(self.lines)
401 else:
400 else:
402 search_start = orig_start
401 search_start = orig_start
403
402
404 for fuzzlen in xrange(3):
403 for fuzzlen in xrange(3):
405 for toponly in [ True, False ]:
404 for toponly in [ True, False ]:
406 old = h.old(fuzzlen, toponly)
405 old = h.old(fuzzlen, toponly)
407
406
408 cand = self.findlines(old[0][1:], search_start)
407 cand = self.findlines(old[0][1:], search_start)
409 for l in cand:
408 for l in cand:
410 if diffhelpers.testhunk(old, self.lines, l) == 0:
409 if diffhelpers.testhunk(old, self.lines, l) == 0:
411 newlines = h.new(fuzzlen, toponly)
410 newlines = h.new(fuzzlen, toponly)
412 self.lines[l : l + len(old)] = newlines
411 self.lines[l : l + len(old)] = newlines
413 self.offset += len(newlines) - len(old)
412 self.offset += len(newlines) - len(old)
414 self.dirty = 1
413 self.dirty = 1
415 if fuzzlen:
414 if fuzzlen:
416 fuzzstr = "with fuzz %d " % fuzzlen
415 fuzzstr = "with fuzz %d " % fuzzlen
417 f = self.ui.warn
416 f = self.ui.warn
418 self.printfile(True)
417 self.printfile(True)
419 else:
418 else:
420 fuzzstr = ""
419 fuzzstr = ""
421 f = self.ui.note
420 f = self.ui.note
422 offset = l - orig_start - fuzzlen
421 offset = l - orig_start - fuzzlen
423 if offset == 1:
422 if offset == 1:
424 msg = _("Hunk #%d succeeded at %d %s"
423 msg = _("Hunk #%d succeeded at %d %s"
425 "(offset %d line).\n")
424 "(offset %d line).\n")
426 else:
425 else:
427 msg = _("Hunk #%d succeeded at %d %s"
426 msg = _("Hunk #%d succeeded at %d %s"
428 "(offset %d lines).\n")
427 "(offset %d lines).\n")
429 f(msg % (h.number, l+1, fuzzstr, offset))
428 f(msg % (h.number, l+1, fuzzstr, offset))
430 return fuzzlen
429 return fuzzlen
431 self.printfile(True)
430 self.printfile(True)
432 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
431 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
433 self.rej.append(h)
432 self.rej.append(h)
434 return -1
433 return -1
435
434
436 class hunk:
435 class hunk:
437 def __init__(self, desc, num, lr, context, create=False, remove=False):
436 def __init__(self, desc, num, lr, context, create=False, remove=False):
438 self.number = num
437 self.number = num
439 self.desc = desc
438 self.desc = desc
440 self.hunk = [ desc ]
439 self.hunk = [ desc ]
441 self.a = []
440 self.a = []
442 self.b = []
441 self.b = []
443 if context:
442 if context:
444 self.read_context_hunk(lr)
443 self.read_context_hunk(lr)
445 else:
444 else:
446 self.read_unified_hunk(lr)
445 self.read_unified_hunk(lr)
447 self.create = create
446 self.create = create
448 self.remove = remove and not create
447 self.remove = remove and not create
449
448
450 def read_unified_hunk(self, lr):
449 def read_unified_hunk(self, lr):
451 m = unidesc.match(self.desc)
450 m = unidesc.match(self.desc)
452 if not m:
451 if not m:
453 raise PatchError(_("bad hunk #%d") % self.number)
452 raise PatchError(_("bad hunk #%d") % self.number)
454 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
453 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
455 if self.lena is None:
454 if self.lena is None:
456 self.lena = 1
455 self.lena = 1
457 else:
456 else:
458 self.lena = int(self.lena)
457 self.lena = int(self.lena)
459 if self.lenb is None:
458 if self.lenb is None:
460 self.lenb = 1
459 self.lenb = 1
461 else:
460 else:
462 self.lenb = int(self.lenb)
461 self.lenb = int(self.lenb)
463 self.starta = int(self.starta)
462 self.starta = int(self.starta)
464 self.startb = int(self.startb)
463 self.startb = int(self.startb)
465 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
464 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
466 # if we hit eof before finishing out the hunk, the last line will
465 # if we hit eof before finishing out the hunk, the last line will
467 # be zero length. Lets try to fix it up.
466 # be zero length. Lets try to fix it up.
468 while len(self.hunk[-1]) == 0:
467 while len(self.hunk[-1]) == 0:
469 del self.hunk[-1]
468 del self.hunk[-1]
470 del self.a[-1]
469 del self.a[-1]
471 del self.b[-1]
470 del self.b[-1]
472 self.lena -= 1
471 self.lena -= 1
473 self.lenb -= 1
472 self.lenb -= 1
474
473
475 def read_context_hunk(self, lr):
474 def read_context_hunk(self, lr):
476 self.desc = lr.readline()
475 self.desc = lr.readline()
477 m = contextdesc.match(self.desc)
476 m = contextdesc.match(self.desc)
478 if not m:
477 if not m:
479 raise PatchError(_("bad hunk #%d") % self.number)
478 raise PatchError(_("bad hunk #%d") % self.number)
480 foo, self.starta, foo2, aend, foo3 = m.groups()
479 foo, self.starta, foo2, aend, foo3 = m.groups()
481 self.starta = int(self.starta)
480 self.starta = int(self.starta)
482 if aend is None:
481 if aend is None:
483 aend = self.starta
482 aend = self.starta
484 self.lena = int(aend) - self.starta
483 self.lena = int(aend) - self.starta
485 if self.starta:
484 if self.starta:
486 self.lena += 1
485 self.lena += 1
487 for x in xrange(self.lena):
486 for x in xrange(self.lena):
488 l = lr.readline()
487 l = lr.readline()
489 if l.startswith('---'):
488 if l.startswith('---'):
490 lr.push(l)
489 lr.push(l)
491 break
490 break
492 s = l[2:]
491 s = l[2:]
493 if l.startswith('- ') or l.startswith('! '):
492 if l.startswith('- ') or l.startswith('! '):
494 u = '-' + s
493 u = '-' + s
495 elif l.startswith(' '):
494 elif l.startswith(' '):
496 u = ' ' + s
495 u = ' ' + s
497 else:
496 else:
498 raise PatchError(_("bad hunk #%d old text line %d") %
497 raise PatchError(_("bad hunk #%d old text line %d") %
499 (self.number, x))
498 (self.number, x))
500 self.a.append(u)
499 self.a.append(u)
501 self.hunk.append(u)
500 self.hunk.append(u)
502
501
503 l = lr.readline()
502 l = lr.readline()
504 if l.startswith('\ '):
503 if l.startswith('\ '):
505 s = self.a[-1][:-1]
504 s = self.a[-1][:-1]
506 self.a[-1] = s
505 self.a[-1] = s
507 self.hunk[-1] = s
506 self.hunk[-1] = s
508 l = lr.readline()
507 l = lr.readline()
509 m = contextdesc.match(l)
508 m = contextdesc.match(l)
510 if not m:
509 if not m:
511 raise PatchError(_("bad hunk #%d") % self.number)
510 raise PatchError(_("bad hunk #%d") % self.number)
512 foo, self.startb, foo2, bend, foo3 = m.groups()
511 foo, self.startb, foo2, bend, foo3 = m.groups()
513 self.startb = int(self.startb)
512 self.startb = int(self.startb)
514 if bend is None:
513 if bend is None:
515 bend = self.startb
514 bend = self.startb
516 self.lenb = int(bend) - self.startb
515 self.lenb = int(bend) - self.startb
517 if self.startb:
516 if self.startb:
518 self.lenb += 1
517 self.lenb += 1
519 hunki = 1
518 hunki = 1
520 for x in xrange(self.lenb):
519 for x in xrange(self.lenb):
521 l = lr.readline()
520 l = lr.readline()
522 if l.startswith('\ '):
521 if l.startswith('\ '):
523 s = self.b[-1][:-1]
522 s = self.b[-1][:-1]
524 self.b[-1] = s
523 self.b[-1] = s
525 self.hunk[hunki-1] = s
524 self.hunk[hunki-1] = s
526 continue
525 continue
527 if not l:
526 if not l:
528 lr.push(l)
527 lr.push(l)
529 break
528 break
530 s = l[2:]
529 s = l[2:]
531 if l.startswith('+ ') or l.startswith('! '):
530 if l.startswith('+ ') or l.startswith('! '):
532 u = '+' + s
531 u = '+' + s
533 elif l.startswith(' '):
532 elif l.startswith(' '):
534 u = ' ' + s
533 u = ' ' + s
535 elif len(self.b) == 0:
534 elif len(self.b) == 0:
536 # this can happen when the hunk does not add any lines
535 # this can happen when the hunk does not add any lines
537 lr.push(l)
536 lr.push(l)
538 break
537 break
539 else:
538 else:
540 raise PatchError(_("bad hunk #%d old text line %d") %
539 raise PatchError(_("bad hunk #%d old text line %d") %
541 (self.number, x))
540 (self.number, x))
542 self.b.append(s)
541 self.b.append(s)
543 while True:
542 while True:
544 if hunki >= len(self.hunk):
543 if hunki >= len(self.hunk):
545 h = ""
544 h = ""
546 else:
545 else:
547 h = self.hunk[hunki]
546 h = self.hunk[hunki]
548 hunki += 1
547 hunki += 1
549 if h == u:
548 if h == u:
550 break
549 break
551 elif h.startswith('-'):
550 elif h.startswith('-'):
552 continue
551 continue
553 else:
552 else:
554 self.hunk.insert(hunki-1, u)
553 self.hunk.insert(hunki-1, u)
555 break
554 break
556
555
557 if not self.a:
556 if not self.a:
558 # this happens when lines were only added to the hunk
557 # this happens when lines were only added to the hunk
559 for x in self.hunk:
558 for x in self.hunk:
560 if x.startswith('-') or x.startswith(' '):
559 if x.startswith('-') or x.startswith(' '):
561 self.a.append(x)
560 self.a.append(x)
562 if not self.b:
561 if not self.b:
563 # this happens when lines were only deleted from the hunk
562 # this happens when lines were only deleted from the hunk
564 for x in self.hunk:
563 for x in self.hunk:
565 if x.startswith('+') or x.startswith(' '):
564 if x.startswith('+') or x.startswith(' '):
566 self.b.append(x[1:])
565 self.b.append(x[1:])
567 # @@ -start,len +start,len @@
566 # @@ -start,len +start,len @@
568 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
567 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
569 self.startb, self.lenb)
568 self.startb, self.lenb)
570 self.hunk[0] = self.desc
569 self.hunk[0] = self.desc
571
570
572 def reverse(self):
571 def reverse(self):
573 self.create, self.remove = self.remove, self.create
572 self.create, self.remove = self.remove, self.create
574 origlena = self.lena
573 origlena = self.lena
575 origstarta = self.starta
574 origstarta = self.starta
576 self.lena = self.lenb
575 self.lena = self.lenb
577 self.starta = self.startb
576 self.starta = self.startb
578 self.lenb = origlena
577 self.lenb = origlena
579 self.startb = origstarta
578 self.startb = origstarta
580 self.a = []
579 self.a = []
581 self.b = []
580 self.b = []
582 # self.hunk[0] is the @@ description
581 # self.hunk[0] is the @@ description
583 for x in xrange(1, len(self.hunk)):
582 for x in xrange(1, len(self.hunk)):
584 o = self.hunk[x]
583 o = self.hunk[x]
585 if o.startswith('-'):
584 if o.startswith('-'):
586 n = '+' + o[1:]
585 n = '+' + o[1:]
587 self.b.append(o[1:])
586 self.b.append(o[1:])
588 elif o.startswith('+'):
587 elif o.startswith('+'):
589 n = '-' + o[1:]
588 n = '-' + o[1:]
590 self.a.append(n)
589 self.a.append(n)
591 else:
590 else:
592 n = o
591 n = o
593 self.b.append(o[1:])
592 self.b.append(o[1:])
594 self.a.append(o)
593 self.a.append(o)
595 self.hunk[x] = o
594 self.hunk[x] = o
596
595
597 def fix_newline(self):
596 def fix_newline(self):
598 diffhelpers.fix_newline(self.hunk, self.a, self.b)
597 diffhelpers.fix_newline(self.hunk, self.a, self.b)
599
598
600 def complete(self):
599 def complete(self):
601 return len(self.a) == self.lena and len(self.b) == self.lenb
600 return len(self.a) == self.lena and len(self.b) == self.lenb
602
601
603 def createfile(self):
602 def createfile(self):
604 return self.starta == 0 and self.lena == 0 and self.create
603 return self.starta == 0 and self.lena == 0 and self.create
605
604
606 def rmfile(self):
605 def rmfile(self):
607 return self.startb == 0 and self.lenb == 0 and self.remove
606 return self.startb == 0 and self.lenb == 0 and self.remove
608
607
609 def fuzzit(self, l, fuzz, toponly):
608 def fuzzit(self, l, fuzz, toponly):
610 # this removes context lines from the top and bottom of list 'l'. It
609 # this removes context lines from the top and bottom of list 'l'. It
611 # checks the hunk to make sure only context lines are removed, and then
610 # checks the hunk to make sure only context lines are removed, and then
612 # returns a new shortened list of lines.
611 # returns a new shortened list of lines.
613 fuzz = min(fuzz, len(l)-1)
612 fuzz = min(fuzz, len(l)-1)
614 if fuzz:
613 if fuzz:
615 top = 0
614 top = 0
616 bot = 0
615 bot = 0
617 hlen = len(self.hunk)
616 hlen = len(self.hunk)
618 for x in xrange(hlen-1):
617 for x in xrange(hlen-1):
619 # the hunk starts with the @@ line, so use x+1
618 # the hunk starts with the @@ line, so use x+1
620 if self.hunk[x+1][0] == ' ':
619 if self.hunk[x+1][0] == ' ':
621 top += 1
620 top += 1
622 else:
621 else:
623 break
622 break
624 if not toponly:
623 if not toponly:
625 for x in xrange(hlen-1):
624 for x in xrange(hlen-1):
626 if self.hunk[hlen-bot-1][0] == ' ':
625 if self.hunk[hlen-bot-1][0] == ' ':
627 bot += 1
626 bot += 1
628 else:
627 else:
629 break
628 break
630
629
631 # top and bot now count context in the hunk
630 # top and bot now count context in the hunk
632 # adjust them if either one is short
631 # adjust them if either one is short
633 context = max(top, bot, 3)
632 context = max(top, bot, 3)
634 if bot < context:
633 if bot < context:
635 bot = max(0, fuzz - (context - bot))
634 bot = max(0, fuzz - (context - bot))
636 else:
635 else:
637 bot = min(fuzz, bot)
636 bot = min(fuzz, bot)
638 if top < context:
637 if top < context:
639 top = max(0, fuzz - (context - top))
638 top = max(0, fuzz - (context - top))
640 else:
639 else:
641 top = min(fuzz, top)
640 top = min(fuzz, top)
642
641
643 return l[top:len(l)-bot]
642 return l[top:len(l)-bot]
644 return l
643 return l
645
644
646 def old(self, fuzz=0, toponly=False):
645 def old(self, fuzz=0, toponly=False):
647 return self.fuzzit(self.a, fuzz, toponly)
646 return self.fuzzit(self.a, fuzz, toponly)
648
647
649 def newctrl(self):
648 def newctrl(self):
650 res = []
649 res = []
651 for x in self.hunk:
650 for x in self.hunk:
652 c = x[0]
651 c = x[0]
653 if c == ' ' or c == '+':
652 if c == ' ' or c == '+':
654 res.append(x)
653 res.append(x)
655 return res
654 return res
656
655
657 def new(self, fuzz=0, toponly=False):
656 def new(self, fuzz=0, toponly=False):
658 return self.fuzzit(self.b, fuzz, toponly)
657 return self.fuzzit(self.b, fuzz, toponly)
659
658
660 class githunk(object):
659 class githunk(object):
661 """A git hunk"""
660 """A git hunk"""
662 def __init__(self, gitpatch):
661 def __init__(self, gitpatch):
663 self.gitpatch = gitpatch
662 self.gitpatch = gitpatch
664 self.text = None
663 self.text = None
665 self.hunk = []
664 self.hunk = []
666
665
667 def createfile(self):
666 def createfile(self):
668 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
667 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
669
668
670 def rmfile(self):
669 def rmfile(self):
671 return self.gitpatch.op == 'DELETE'
670 return self.gitpatch.op == 'DELETE'
672
671
673 def complete(self):
672 def complete(self):
674 return self.text is not None
673 return self.text is not None
675
674
676 def new(self):
675 def new(self):
677 return [self.text]
676 return [self.text]
678
677
679 class binhunk(githunk):
678 class binhunk(githunk):
680 'A binary patch file. Only understands literals so far.'
679 'A binary patch file. Only understands literals so far.'
681 def __init__(self, gitpatch):
680 def __init__(self, gitpatch):
682 super(binhunk, self).__init__(gitpatch)
681 super(binhunk, self).__init__(gitpatch)
683 self.hunk = ['GIT binary patch\n']
682 self.hunk = ['GIT binary patch\n']
684
683
685 def extract(self, lr):
684 def extract(self, lr):
686 line = lr.readline()
685 line = lr.readline()
687 self.hunk.append(line)
686 self.hunk.append(line)
688 while line and not line.startswith('literal '):
687 while line and not line.startswith('literal '):
689 line = lr.readline()
688 line = lr.readline()
690 self.hunk.append(line)
689 self.hunk.append(line)
691 if not line:
690 if not line:
692 raise PatchError(_('could not extract binary patch'))
691 raise PatchError(_('could not extract binary patch'))
693 size = int(line[8:].rstrip())
692 size = int(line[8:].rstrip())
694 dec = []
693 dec = []
695 line = lr.readline()
694 line = lr.readline()
696 self.hunk.append(line)
695 self.hunk.append(line)
697 while len(line) > 1:
696 while len(line) > 1:
698 l = line[0]
697 l = line[0]
699 if l <= 'Z' and l >= 'A':
698 if l <= 'Z' and l >= 'A':
700 l = ord(l) - ord('A') + 1
699 l = ord(l) - ord('A') + 1
701 else:
700 else:
702 l = ord(l) - ord('a') + 27
701 l = ord(l) - ord('a') + 27
703 dec.append(base85.b85decode(line[1:-1])[:l])
702 dec.append(base85.b85decode(line[1:-1])[:l])
704 line = lr.readline()
703 line = lr.readline()
705 self.hunk.append(line)
704 self.hunk.append(line)
706 text = zlib.decompress(''.join(dec))
705 text = zlib.decompress(''.join(dec))
707 if len(text) != size:
706 if len(text) != size:
708 raise PatchError(_('binary patch is %d bytes, not %d') %
707 raise PatchError(_('binary patch is %d bytes, not %d') %
709 len(text), size)
708 len(text), size)
710 self.text = text
709 self.text = text
711
710
712 class symlinkhunk(githunk):
711 class symlinkhunk(githunk):
713 """A git symlink hunk"""
712 """A git symlink hunk"""
714 def __init__(self, gitpatch, hunk):
713 def __init__(self, gitpatch, hunk):
715 super(symlinkhunk, self).__init__(gitpatch)
714 super(symlinkhunk, self).__init__(gitpatch)
716 self.hunk = hunk
715 self.hunk = hunk
717
716
718 def complete(self):
717 def complete(self):
719 return True
718 return True
720
719
721 def fix_newline(self):
720 def fix_newline(self):
722 return
721 return
723
722
724 def parsefilename(str):
723 def parsefilename(str):
725 # --- filename \t|space stuff
724 # --- filename \t|space stuff
726 s = str[4:].rstrip('\r\n')
725 s = str[4:].rstrip('\r\n')
727 i = s.find('\t')
726 i = s.find('\t')
728 if i < 0:
727 if i < 0:
729 i = s.find(' ')
728 i = s.find(' ')
730 if i < 0:
729 if i < 0:
731 return s
730 return s
732 return s[:i]
731 return s[:i]
733
732
734 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
733 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
735 def pathstrip(path, count=1):
734 def pathstrip(path, count=1):
736 pathlen = len(path)
735 pathlen = len(path)
737 i = 0
736 i = 0
738 if count == 0:
737 if count == 0:
739 return '', path.rstrip()
738 return '', path.rstrip()
740 while count > 0:
739 while count > 0:
741 i = path.find('/', i)
740 i = path.find('/', i)
742 if i == -1:
741 if i == -1:
743 raise PatchError(_("unable to strip away %d dirs from %s") %
742 raise PatchError(_("unable to strip away %d dirs from %s") %
744 (count, path))
743 (count, path))
745 i += 1
744 i += 1
746 # consume '//' in the path
745 # consume '//' in the path
747 while i < pathlen - 1 and path[i] == '/':
746 while i < pathlen - 1 and path[i] == '/':
748 i += 1
747 i += 1
749 count -= 1
748 count -= 1
750 return path[:i].lstrip(), path[i:].rstrip()
749 return path[:i].lstrip(), path[i:].rstrip()
751
750
752 nulla = afile_orig == "/dev/null"
751 nulla = afile_orig == "/dev/null"
753 nullb = bfile_orig == "/dev/null"
752 nullb = bfile_orig == "/dev/null"
754 abase, afile = pathstrip(afile_orig, strip)
753 abase, afile = pathstrip(afile_orig, strip)
755 gooda = not nulla and util.lexists(afile)
754 gooda = not nulla and util.lexists(afile)
756 bbase, bfile = pathstrip(bfile_orig, strip)
755 bbase, bfile = pathstrip(bfile_orig, strip)
757 if afile == bfile:
756 if afile == bfile:
758 goodb = gooda
757 goodb = gooda
759 else:
758 else:
760 goodb = not nullb and os.path.exists(bfile)
759 goodb = not nullb and os.path.exists(bfile)
761 createfunc = hunk.createfile
760 createfunc = hunk.createfile
762 if reverse:
761 if reverse:
763 createfunc = hunk.rmfile
762 createfunc = hunk.rmfile
764 missing = not goodb and not gooda and not createfunc()
763 missing = not goodb and not gooda and not createfunc()
765 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
764 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
766 # diff is between a file and its backup. In this case, the original
765 # diff is between a file and its backup. In this case, the original
767 # file should be patched (see original mpatch code).
766 # file should be patched (see original mpatch code).
768 isbackup = (abase == bbase and bfile.startswith(afile))
767 isbackup = (abase == bbase and bfile.startswith(afile))
769 fname = None
768 fname = None
770 if not missing:
769 if not missing:
771 if gooda and goodb:
770 if gooda and goodb:
772 fname = isbackup and afile or bfile
771 fname = isbackup and afile or bfile
773 elif gooda:
772 elif gooda:
774 fname = afile
773 fname = afile
775
774
776 if not fname:
775 if not fname:
777 if not nullb:
776 if not nullb:
778 fname = isbackup and afile or bfile
777 fname = isbackup and afile or bfile
779 elif not nulla:
778 elif not nulla:
780 fname = afile
779 fname = afile
781 else:
780 else:
782 raise PatchError(_("undefined source and destination files"))
781 raise PatchError(_("undefined source and destination files"))
783
782
784 return fname, missing
783 return fname, missing
785
784
786 class linereader:
785 class linereader:
787 # simple class to allow pushing lines back into the input stream
786 # simple class to allow pushing lines back into the input stream
788 def __init__(self, fp):
787 def __init__(self, fp):
789 self.fp = fp
788 self.fp = fp
790 self.buf = []
789 self.buf = []
791
790
792 def push(self, line):
791 def push(self, line):
793 if line is not None:
792 if line is not None:
794 self.buf.append(line)
793 self.buf.append(line)
795
794
796 def readline(self):
795 def readline(self):
797 if self.buf:
796 if self.buf:
798 return self.buf.pop(0)
797 return self.buf.pop(0)
799 return self.fp.readline()
798 return self.fp.readline()
800
799
801 def __iter__(self):
800 def __iter__(self):
802 while 1:
801 while 1:
803 l = self.readline()
802 l = self.readline()
804 if not l:
803 if not l:
805 break
804 break
806 yield l
805 yield l
807
806
808 def scangitpatch(lr, firstline):
807 def scangitpatch(lr, firstline):
809 """
808 """
810 Git patches can emit:
809 Git patches can emit:
811 - rename a to b
810 - rename a to b
812 - change b
811 - change b
813 - copy a to c
812 - copy a to c
814 - change c
813 - change c
815
814
816 We cannot apply this sequence as-is, the renamed 'a' could not be
815 We cannot apply this sequence as-is, the renamed 'a' could not be
817 found for it would have been renamed already. And we cannot copy
816 found for it would have been renamed already. And we cannot copy
818 from 'b' instead because 'b' would have been changed already. So
817 from 'b' instead because 'b' would have been changed already. So
819 we scan the git patch for copy and rename commands so we can
818 we scan the git patch for copy and rename commands so we can
820 perform the copies ahead of time.
819 perform the copies ahead of time.
821 """
820 """
822 pos = 0
821 pos = 0
823 try:
822 try:
824 pos = lr.fp.tell()
823 pos = lr.fp.tell()
825 fp = lr.fp
824 fp = lr.fp
826 except IOError:
825 except IOError:
827 fp = cStringIO.StringIO(lr.fp.read())
826 fp = cStringIO.StringIO(lr.fp.read())
828 gitlr = linereader(fp)
827 gitlr = linereader(fp)
829 gitlr.push(firstline)
828 gitlr.push(firstline)
830 (dopatch, gitpatches) = readgitpatch(gitlr)
829 (dopatch, gitpatches) = readgitpatch(gitlr)
831 fp.seek(pos)
830 fp.seek(pos)
832 return dopatch, gitpatches
831 return dopatch, gitpatches
833
832
834 def iterhunks(ui, fp, sourcefile=None):
833 def iterhunks(ui, fp, sourcefile=None):
835 """Read a patch and yield the following events:
834 """Read a patch and yield the following events:
836 - ("file", afile, bfile, firsthunk): select a new target file.
835 - ("file", afile, bfile, firsthunk): select a new target file.
837 - ("hunk", hunk): a new hunk is ready to be applied, follows a
836 - ("hunk", hunk): a new hunk is ready to be applied, follows a
838 "file" event.
837 "file" event.
839 - ("git", gitchanges): current diff is in git format, gitchanges
838 - ("git", gitchanges): current diff is in git format, gitchanges
840 maps filenames to gitpatch records. Unique event.
839 maps filenames to gitpatch records. Unique event.
841 """
840 """
842 changed = {}
841 changed = {}
843 current_hunk = None
842 current_hunk = None
844 afile = ""
843 afile = ""
845 bfile = ""
844 bfile = ""
846 state = None
845 state = None
847 hunknum = 0
846 hunknum = 0
848 emitfile = False
847 emitfile = False
849 git = False
848 git = False
850
849
851 # our states
850 # our states
852 BFILE = 1
851 BFILE = 1
853 context = None
852 context = None
854 lr = linereader(fp)
853 lr = linereader(fp)
855 dopatch = True
854 dopatch = True
856 # gitworkdone is True if a git operation (copy, rename, ...) was
855 # gitworkdone is True if a git operation (copy, rename, ...) was
857 # performed already for the current file. Useful when the file
856 # performed already for the current file. Useful when the file
858 # section may have no hunk.
857 # section may have no hunk.
859 gitworkdone = False
858 gitworkdone = False
860
859
861 while True:
860 while True:
862 newfile = False
861 newfile = False
863 x = lr.readline()
862 x = lr.readline()
864 if not x:
863 if not x:
865 break
864 break
866 if current_hunk:
865 if current_hunk:
867 if x.startswith('\ '):
866 if x.startswith('\ '):
868 current_hunk.fix_newline()
867 current_hunk.fix_newline()
869 yield 'hunk', current_hunk
868 yield 'hunk', current_hunk
870 current_hunk = None
869 current_hunk = None
871 gitworkdone = False
870 gitworkdone = False
872 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
871 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
873 ((context is not False) and x.startswith('***************')))):
872 ((context is not False) and x.startswith('***************')))):
874 try:
873 try:
875 if context is None and x.startswith('***************'):
874 if context is None and x.startswith('***************'):
876 context = True
875 context = True
877 gpatch = changed.get(bfile)
876 gpatch = changed.get(bfile)
878 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
877 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
879 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
878 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
880 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
879 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
881 if remove:
880 if remove:
882 gpatch = changed.get(afile[2:])
881 gpatch = changed.get(afile[2:])
883 if gpatch and gpatch.mode[0]:
882 if gpatch and gpatch.mode[0]:
884 current_hunk = symlinkhunk(gpatch, current_hunk)
883 current_hunk = symlinkhunk(gpatch, current_hunk)
885 except PatchError, err:
884 except PatchError, err:
886 ui.debug(err)
885 ui.debug(err)
887 current_hunk = None
886 current_hunk = None
888 continue
887 continue
889 hunknum += 1
888 hunknum += 1
890 if emitfile:
889 if emitfile:
891 emitfile = False
890 emitfile = False
892 yield 'file', (afile, bfile, current_hunk)
891 yield 'file', (afile, bfile, current_hunk)
893 elif state == BFILE and x.startswith('GIT binary patch'):
892 elif state == BFILE and x.startswith('GIT binary patch'):
894 current_hunk = binhunk(changed[bfile])
893 current_hunk = binhunk(changed[bfile])
895 hunknum += 1
894 hunknum += 1
896 if emitfile:
895 if emitfile:
897 emitfile = False
896 emitfile = False
898 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
897 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
899 current_hunk.extract(lr)
898 current_hunk.extract(lr)
900 elif x.startswith('diff --git'):
899 elif x.startswith('diff --git'):
901 # check for git diff, scanning the whole patch file if needed
900 # check for git diff, scanning the whole patch file if needed
902 m = gitre.match(x)
901 m = gitre.match(x)
903 if m:
902 if m:
904 afile, bfile = m.group(1, 2)
903 afile, bfile = m.group(1, 2)
905 if not git:
904 if not git:
906 git = True
905 git = True
907 dopatch, gitpatches = scangitpatch(lr, x)
906 dopatch, gitpatches = scangitpatch(lr, x)
908 yield 'git', gitpatches
907 yield 'git', gitpatches
909 for gp in gitpatches:
908 for gp in gitpatches:
910 changed[gp.path] = gp
909 changed[gp.path] = gp
911 # else error?
910 # else error?
912 # copy/rename + modify should modify target, not source
911 # copy/rename + modify should modify target, not source
913 gp = changed.get(bfile)
912 gp = changed.get(bfile)
914 if gp and gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD'):
913 if gp and gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD'):
915 afile = bfile
914 afile = bfile
916 gitworkdone = True
915 gitworkdone = True
917 newfile = True
916 newfile = True
918 elif x.startswith('---'):
917 elif x.startswith('---'):
919 # check for a unified diff
918 # check for a unified diff
920 l2 = lr.readline()
919 l2 = lr.readline()
921 if not l2.startswith('+++'):
920 if not l2.startswith('+++'):
922 lr.push(l2)
921 lr.push(l2)
923 continue
922 continue
924 newfile = True
923 newfile = True
925 context = False
924 context = False
926 afile = parsefilename(x)
925 afile = parsefilename(x)
927 bfile = parsefilename(l2)
926 bfile = parsefilename(l2)
928 elif x.startswith('***'):
927 elif x.startswith('***'):
929 # check for a context diff
928 # check for a context diff
930 l2 = lr.readline()
929 l2 = lr.readline()
931 if not l2.startswith('---'):
930 if not l2.startswith('---'):
932 lr.push(l2)
931 lr.push(l2)
933 continue
932 continue
934 l3 = lr.readline()
933 l3 = lr.readline()
935 lr.push(l3)
934 lr.push(l3)
936 if not l3.startswith("***************"):
935 if not l3.startswith("***************"):
937 lr.push(l2)
936 lr.push(l2)
938 continue
937 continue
939 newfile = True
938 newfile = True
940 context = True
939 context = True
941 afile = parsefilename(x)
940 afile = parsefilename(x)
942 bfile = parsefilename(l2)
941 bfile = parsefilename(l2)
943
942
944 if newfile:
943 if newfile:
945 emitfile = True
944 emitfile = True
946 state = BFILE
945 state = BFILE
947 hunknum = 0
946 hunknum = 0
948 if current_hunk:
947 if current_hunk:
949 if current_hunk.complete():
948 if current_hunk.complete():
950 yield 'hunk', current_hunk
949 yield 'hunk', current_hunk
951 else:
950 else:
952 raise PatchError(_("malformed patch %s %s") % (afile,
951 raise PatchError(_("malformed patch %s %s") % (afile,
953 current_hunk.desc))
952 current_hunk.desc))
954
953
955 if hunknum == 0 and dopatch and not gitworkdone:
954 if hunknum == 0 and dopatch and not gitworkdone:
956 raise NoHunks
955 raise NoHunks
957
956
958 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False):
957 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False):
959 """reads a patch from fp and tries to apply it. The dict 'changed' is
958 """reads a patch from fp and tries to apply it. The dict 'changed' is
960 filled in with all of the filenames changed by the patch. Returns 0
959 filled in with all of the filenames changed by the patch. Returns 0
961 for a clean patch, -1 if any rejects were found and 1 if there was
960 for a clean patch, -1 if any rejects were found and 1 if there was
962 any fuzz."""
961 any fuzz."""
963
962
964 rejects = 0
963 rejects = 0
965 err = 0
964 err = 0
966 current_file = None
965 current_file = None
967 gitpatches = None
966 gitpatches = None
968 opener = util.opener(os.getcwd())
967 opener = util.opener(os.getcwd())
969
968
970 def closefile():
969 def closefile():
971 if not current_file:
970 if not current_file:
972 return 0
971 return 0
973 current_file.close()
972 current_file.close()
974 return len(current_file.rej)
973 return len(current_file.rej)
975
974
976 for state, values in iterhunks(ui, fp, sourcefile):
975 for state, values in iterhunks(ui, fp, sourcefile):
977 if state == 'hunk':
976 if state == 'hunk':
978 if not current_file:
977 if not current_file:
979 continue
978 continue
980 current_hunk = values
979 current_hunk = values
981 ret = current_file.apply(current_hunk, reverse)
980 ret = current_file.apply(current_hunk, reverse)
982 if ret >= 0:
981 if ret >= 0:
983 changed.setdefault(current_file.fname, None)
982 changed.setdefault(current_file.fname, None)
984 if ret > 0:
983 if ret > 0:
985 err = 1
984 err = 1
986 elif state == 'file':
985 elif state == 'file':
987 rejects += closefile()
986 rejects += closefile()
988 afile, bfile, first_hunk = values
987 afile, bfile, first_hunk = values
989 try:
988 try:
990 if sourcefile:
989 if sourcefile:
991 current_file = patchfile(ui, sourcefile, opener)
990 current_file = patchfile(ui, sourcefile, opener)
992 else:
991 else:
993 current_file, missing = selectfile(afile, bfile, first_hunk,
992 current_file, missing = selectfile(afile, bfile, first_hunk,
994 strip, reverse)
993 strip, reverse)
995 current_file = patchfile(ui, current_file, opener, missing)
994 current_file = patchfile(ui, current_file, opener, missing)
996 except PatchError, err:
995 except PatchError, err:
997 ui.warn(str(err) + '\n')
996 ui.warn(str(err) + '\n')
998 current_file, current_hunk = None, None
997 current_file, current_hunk = None, None
999 rejects += 1
998 rejects += 1
1000 continue
999 continue
1001 elif state == 'git':
1000 elif state == 'git':
1002 gitpatches = values
1001 gitpatches = values
1003 cwd = os.getcwd()
1002 cwd = os.getcwd()
1004 for gp in gitpatches:
1003 for gp in gitpatches:
1005 if gp.op in ('COPY', 'RENAME'):
1004 if gp.op in ('COPY', 'RENAME'):
1006 copyfile(gp.oldpath, gp.path, cwd)
1005 copyfile(gp.oldpath, gp.path, cwd)
1007 changed[gp.path] = gp
1006 changed[gp.path] = gp
1008 else:
1007 else:
1009 raise util.Abort(_('unsupported parser state: %s') % state)
1008 raise util.Abort(_('unsupported parser state: %s') % state)
1010
1009
1011 rejects += closefile()
1010 rejects += closefile()
1012
1011
1013 if rejects:
1012 if rejects:
1014 return -1
1013 return -1
1015 return err
1014 return err
1016
1015
1017 def diffopts(ui, opts={}, untrusted=False):
1016 def diffopts(ui, opts={}, untrusted=False):
1018 def get(key, name=None, getter=ui.configbool):
1017 def get(key, name=None, getter=ui.configbool):
1019 return (opts.get(key) or
1018 return (opts.get(key) or
1020 getter('diff', name or key, None, untrusted=untrusted))
1019 getter('diff', name or key, None, untrusted=untrusted))
1021 return mdiff.diffopts(
1020 return mdiff.diffopts(
1022 text=opts.get('text'),
1021 text=opts.get('text'),
1023 git=get('git'),
1022 git=get('git'),
1024 nodates=get('nodates'),
1023 nodates=get('nodates'),
1025 showfunc=get('show_function', 'showfunc'),
1024 showfunc=get('show_function', 'showfunc'),
1026 ignorews=get('ignore_all_space', 'ignorews'),
1025 ignorews=get('ignore_all_space', 'ignorews'),
1027 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1026 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1028 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1027 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1029 context=get('unified', getter=ui.config))
1028 context=get('unified', getter=ui.config))
1030
1029
1031 def updatedir(ui, repo, patches, similarity=0):
1030 def updatedir(ui, repo, patches, similarity=0):
1032 '''Update dirstate after patch application according to metadata'''
1031 '''Update dirstate after patch application according to metadata'''
1033 if not patches:
1032 if not patches:
1034 return
1033 return
1035 copies = []
1034 copies = []
1036 removes = set()
1035 removes = set()
1037 cfiles = patches.keys()
1036 cfiles = patches.keys()
1038 cwd = repo.getcwd()
1037 cwd = repo.getcwd()
1039 if cwd:
1038 if cwd:
1040 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1039 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1041 for f in patches:
1040 for f in patches:
1042 gp = patches[f]
1041 gp = patches[f]
1043 if not gp:
1042 if not gp:
1044 continue
1043 continue
1045 if gp.op == 'RENAME':
1044 if gp.op == 'RENAME':
1046 copies.append((gp.oldpath, gp.path))
1045 copies.append((gp.oldpath, gp.path))
1047 removes.add(gp.oldpath)
1046 removes.add(gp.oldpath)
1048 elif gp.op == 'COPY':
1047 elif gp.op == 'COPY':
1049 copies.append((gp.oldpath, gp.path))
1048 copies.append((gp.oldpath, gp.path))
1050 elif gp.op == 'DELETE':
1049 elif gp.op == 'DELETE':
1051 removes.add(gp.path)
1050 removes.add(gp.path)
1052 for src, dst in copies:
1051 for src, dst in copies:
1053 repo.copy(src, dst)
1052 repo.copy(src, dst)
1054 if (not similarity) and removes:
1053 if (not similarity) and removes:
1055 repo.remove(sorted(removes), True)
1054 repo.remove(sorted(removes), True)
1056 for f in patches:
1055 for f in patches:
1057 gp = patches[f]
1056 gp = patches[f]
1058 if gp and gp.mode:
1057 if gp and gp.mode:
1059 islink, isexec = gp.mode
1058 islink, isexec = gp.mode
1060 dst = repo.wjoin(gp.path)
1059 dst = repo.wjoin(gp.path)
1061 # patch won't create empty files
1060 # patch won't create empty files
1062 if gp.op == 'ADD' and not os.path.exists(dst):
1061 if gp.op == 'ADD' and not os.path.exists(dst):
1063 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1062 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1064 repo.wwrite(gp.path, '', flags)
1063 repo.wwrite(gp.path, '', flags)
1065 elif gp.op != 'DELETE':
1064 elif gp.op != 'DELETE':
1066 util.set_flags(dst, islink, isexec)
1065 util.set_flags(dst, islink, isexec)
1067 cmdutil.addremove(repo, cfiles, similarity=similarity)
1066 cmdutil.addremove(repo, cfiles, similarity=similarity)
1068 files = patches.keys()
1067 files = patches.keys()
1069 files.extend([r for r in removes if r not in files])
1068 files.extend([r for r in removes if r not in files])
1070 return sorted(files)
1069 return sorted(files)
1071
1070
1072 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1071 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1073 """use <patcher> to apply <patchname> to the working directory.
1072 """use <patcher> to apply <patchname> to the working directory.
1074 returns whether patch was applied with fuzz factor."""
1073 returns whether patch was applied with fuzz factor."""
1075
1074
1076 fuzz = False
1075 fuzz = False
1077 if cwd:
1076 if cwd:
1078 args.append('-d %s' % util.shellquote(cwd))
1077 args.append('-d %s' % util.shellquote(cwd))
1079 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1078 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1080 util.shellquote(patchname)))
1079 util.shellquote(patchname)))
1081
1080
1082 for line in fp:
1081 for line in fp:
1083 line = line.rstrip()
1082 line = line.rstrip()
1084 ui.note(line + '\n')
1083 ui.note(line + '\n')
1085 if line.startswith('patching file '):
1084 if line.startswith('patching file '):
1086 pf = util.parse_patch_output(line)
1085 pf = util.parse_patch_output(line)
1087 printed_file = False
1086 printed_file = False
1088 files.setdefault(pf, None)
1087 files.setdefault(pf, None)
1089 elif line.find('with fuzz') >= 0:
1088 elif line.find('with fuzz') >= 0:
1090 fuzz = True
1089 fuzz = True
1091 if not printed_file:
1090 if not printed_file:
1092 ui.warn(pf + '\n')
1091 ui.warn(pf + '\n')
1093 printed_file = True
1092 printed_file = True
1094 ui.warn(line + '\n')
1093 ui.warn(line + '\n')
1095 elif line.find('saving rejects to file') >= 0:
1094 elif line.find('saving rejects to file') >= 0:
1096 ui.warn(line + '\n')
1095 ui.warn(line + '\n')
1097 elif line.find('FAILED') >= 0:
1096 elif line.find('FAILED') >= 0:
1098 if not printed_file:
1097 if not printed_file:
1099 ui.warn(pf + '\n')
1098 ui.warn(pf + '\n')
1100 printed_file = True
1099 printed_file = True
1101 ui.warn(line + '\n')
1100 ui.warn(line + '\n')
1102 code = fp.close()
1101 code = fp.close()
1103 if code:
1102 if code:
1104 raise PatchError(_("patch command failed: %s") %
1103 raise PatchError(_("patch command failed: %s") %
1105 util.explain_exit(code)[0])
1104 util.explain_exit(code)[0])
1106 return fuzz
1105 return fuzz
1107
1106
1108 def internalpatch(patchobj, ui, strip, cwd, files={}):
1107 def internalpatch(patchobj, ui, strip, cwd, files={}):
1109 """use builtin patch to apply <patchobj> to the working directory.
1108 """use builtin patch to apply <patchobj> to the working directory.
1110 returns whether patch was applied with fuzz factor."""
1109 returns whether patch was applied with fuzz factor."""
1111 try:
1110 try:
1112 fp = file(patchobj, 'rb')
1111 fp = file(patchobj, 'rb')
1113 except TypeError:
1112 except TypeError:
1114 fp = patchobj
1113 fp = patchobj
1115 if cwd:
1114 if cwd:
1116 curdir = os.getcwd()
1115 curdir = os.getcwd()
1117 os.chdir(cwd)
1116 os.chdir(cwd)
1118 try:
1117 try:
1119 ret = applydiff(ui, fp, files, strip=strip)
1118 ret = applydiff(ui, fp, files, strip=strip)
1120 finally:
1119 finally:
1121 if cwd:
1120 if cwd:
1122 os.chdir(curdir)
1121 os.chdir(curdir)
1123 if ret < 0:
1122 if ret < 0:
1124 raise PatchError
1123 raise PatchError
1125 return ret > 0
1124 return ret > 0
1126
1125
1127 def patch(patchname, ui, strip=1, cwd=None, files={}):
1126 def patch(patchname, ui, strip=1, cwd=None, files={}):
1128 """apply <patchname> to the working directory.
1127 """apply <patchname> to the working directory.
1129 returns whether patch was applied with fuzz factor."""
1128 returns whether patch was applied with fuzz factor."""
1130 patcher = ui.config('ui', 'patch')
1129 patcher = ui.config('ui', 'patch')
1131 args = []
1130 args = []
1132 try:
1131 try:
1133 if patcher:
1132 if patcher:
1134 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1133 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1135 files)
1134 files)
1136 else:
1135 else:
1137 try:
1136 try:
1138 return internalpatch(patchname, ui, strip, cwd, files)
1137 return internalpatch(patchname, ui, strip, cwd, files)
1139 except NoHunks:
1138 except NoHunks:
1140 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
1139 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
1141 ui.debug(_('no valid hunks found; trying with %r instead\n') %
1140 ui.debug(_('no valid hunks found; trying with %r instead\n') %
1142 patcher)
1141 patcher)
1143 if util.needbinarypatch():
1142 if util.needbinarypatch():
1144 args.append('--binary')
1143 args.append('--binary')
1145 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1144 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1146 files)
1145 files)
1147 except PatchError, err:
1146 except PatchError, err:
1148 s = str(err)
1147 s = str(err)
1149 if s:
1148 if s:
1150 raise util.Abort(s)
1149 raise util.Abort(s)
1151 else:
1150 else:
1152 raise util.Abort(_('patch failed to apply'))
1151 raise util.Abort(_('patch failed to apply'))
1153
1152
1154 def b85diff(to, tn):
1153 def b85diff(to, tn):
1155 '''print base85-encoded binary diff'''
1154 '''print base85-encoded binary diff'''
1156 def gitindex(text):
1155 def gitindex(text):
1157 if not text:
1156 if not text:
1158 return '0' * 40
1157 return '0' * 40
1159 l = len(text)
1158 l = len(text)
1160 s = util.sha1('blob %d\0' % l)
1159 s = util.sha1('blob %d\0' % l)
1161 s.update(text)
1160 s.update(text)
1162 return s.hexdigest()
1161 return s.hexdigest()
1163
1162
1164 def fmtline(line):
1163 def fmtline(line):
1165 l = len(line)
1164 l = len(line)
1166 if l <= 26:
1165 if l <= 26:
1167 l = chr(ord('A') + l - 1)
1166 l = chr(ord('A') + l - 1)
1168 else:
1167 else:
1169 l = chr(l - 26 + ord('a') - 1)
1168 l = chr(l - 26 + ord('a') - 1)
1170 return '%c%s\n' % (l, base85.b85encode(line, True))
1169 return '%c%s\n' % (l, base85.b85encode(line, True))
1171
1170
1172 def chunk(text, csize=52):
1171 def chunk(text, csize=52):
1173 l = len(text)
1172 l = len(text)
1174 i = 0
1173 i = 0
1175 while i < l:
1174 while i < l:
1176 yield text[i:i+csize]
1175 yield text[i:i+csize]
1177 i += csize
1176 i += csize
1178
1177
1179 tohash = gitindex(to)
1178 tohash = gitindex(to)
1180 tnhash = gitindex(tn)
1179 tnhash = gitindex(tn)
1181 if tohash == tnhash:
1180 if tohash == tnhash:
1182 return ""
1181 return ""
1183
1182
1184 # TODO: deltas
1183 # TODO: deltas
1185 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1184 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1186 (tohash, tnhash, len(tn))]
1185 (tohash, tnhash, len(tn))]
1187 for l in chunk(zlib.compress(tn)):
1186 for l in chunk(zlib.compress(tn)):
1188 ret.append(fmtline(l))
1187 ret.append(fmtline(l))
1189 ret.append('\n')
1188 ret.append('\n')
1190 return ''.join(ret)
1189 return ''.join(ret)
1191
1190
1192 def _addmodehdr(header, omode, nmode):
1191 def _addmodehdr(header, omode, nmode):
1193 if omode != nmode:
1192 if omode != nmode:
1194 header.append('old mode %s\n' % omode)
1193 header.append('old mode %s\n' % omode)
1195 header.append('new mode %s\n' % nmode)
1194 header.append('new mode %s\n' % nmode)
1196
1195
1197 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None):
1196 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None):
1198 '''yields diff of changes to files between two nodes, or node and
1197 '''yields diff of changes to files between two nodes, or node and
1199 working directory.
1198 working directory.
1200
1199
1201 if node1 is None, use first dirstate parent instead.
1200 if node1 is None, use first dirstate parent instead.
1202 if node2 is None, compare node1 with working directory.'''
1201 if node2 is None, compare node1 with working directory.'''
1203
1202
1204 if opts is None:
1203 if opts is None:
1205 opts = mdiff.defaultopts
1204 opts = mdiff.defaultopts
1206
1205
1207 if not node1:
1206 if not node1:
1208 node1 = repo.dirstate.parents()[0]
1207 node1 = repo.dirstate.parents()[0]
1209
1208
1210 flcache = {}
1209 flcache = {}
1211 def getfilectx(f, ctx):
1210 def getfilectx(f, ctx):
1212 flctx = ctx.filectx(f, filelog=flcache.get(f))
1211 flctx = ctx.filectx(f, filelog=flcache.get(f))
1213 if f not in flcache:
1212 if f not in flcache:
1214 flcache[f] = flctx._filelog
1213 flcache[f] = flctx._filelog
1215 return flctx
1214 return flctx
1216
1215
1217 ctx1 = repo[node1]
1216 ctx1 = repo[node1]
1218 ctx2 = repo[node2]
1217 ctx2 = repo[node2]
1219
1218
1220 if not changes:
1219 if not changes:
1221 changes = repo.status(ctx1, ctx2, match=match)
1220 changes = repo.status(ctx1, ctx2, match=match)
1222 modified, added, removed = changes[:3]
1221 modified, added, removed = changes[:3]
1223
1222
1224 if not modified and not added and not removed:
1223 if not modified and not added and not removed:
1225 return
1224 return
1226
1225
1227 date1 = util.datestr(ctx1.date())
1226 date1 = util.datestr(ctx1.date())
1228 man1 = ctx1.manifest()
1227 man1 = ctx1.manifest()
1229
1228
1230 if repo.ui.quiet:
1229 if repo.ui.quiet:
1231 r = None
1230 r = None
1232 else:
1231 else:
1233 hexfunc = repo.ui.debugflag and hex or short
1232 hexfunc = repo.ui.debugflag and hex or short
1234 r = [hexfunc(node) for node in [node1, node2] if node]
1233 r = [hexfunc(node) for node in [node1, node2] if node]
1235
1234
1236 if opts.git:
1235 if opts.git:
1237 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1236 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1238 copy = copy.copy()
1237 copy = copy.copy()
1239 for k, v in copy.items():
1238 for k, v in copy.items():
1240 copy[v] = k
1239 copy[v] = k
1241
1240
1242 gone = set()
1241 gone = set()
1243 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1242 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1244
1243
1245 for f in sorted(modified + added + removed):
1244 for f in sorted(modified + added + removed):
1246 to = None
1245 to = None
1247 tn = None
1246 tn = None
1248 dodiff = True
1247 dodiff = True
1249 header = []
1248 header = []
1250 if f in man1:
1249 if f in man1:
1251 to = getfilectx(f, ctx1).data()
1250 to = getfilectx(f, ctx1).data()
1252 if f not in removed:
1251 if f not in removed:
1253 tn = getfilectx(f, ctx2).data()
1252 tn = getfilectx(f, ctx2).data()
1254 a, b = f, f
1253 a, b = f, f
1255 if opts.git:
1254 if opts.git:
1256 if f in added:
1255 if f in added:
1257 mode = gitmode[ctx2.flags(f)]
1256 mode = gitmode[ctx2.flags(f)]
1258 if f in copy:
1257 if f in copy:
1259 a = copy[f]
1258 a = copy[f]
1260 omode = gitmode[man1.flags(a)]
1259 omode = gitmode[man1.flags(a)]
1261 _addmodehdr(header, omode, mode)
1260 _addmodehdr(header, omode, mode)
1262 if a in removed and a not in gone:
1261 if a in removed and a not in gone:
1263 op = 'rename'
1262 op = 'rename'
1264 gone.add(a)
1263 gone.add(a)
1265 else:
1264 else:
1266 op = 'copy'
1265 op = 'copy'
1267 header.append('%s from %s\n' % (op, a))
1266 header.append('%s from %s\n' % (op, a))
1268 header.append('%s to %s\n' % (op, f))
1267 header.append('%s to %s\n' % (op, f))
1269 to = getfilectx(a, ctx1).data()
1268 to = getfilectx(a, ctx1).data()
1270 else:
1269 else:
1271 header.append('new file mode %s\n' % mode)
1270 header.append('new file mode %s\n' % mode)
1272 if util.binary(tn):
1271 if util.binary(tn):
1273 dodiff = 'binary'
1272 dodiff = 'binary'
1274 elif f in removed:
1273 elif f in removed:
1275 # have we already reported a copy above?
1274 # have we already reported a copy above?
1276 if f in copy and copy[f] in added and copy[copy[f]] == f:
1275 if f in copy and copy[f] in added and copy[copy[f]] == f:
1277 dodiff = False
1276 dodiff = False
1278 else:
1277 else:
1279 header.append('deleted file mode %s\n' %
1278 header.append('deleted file mode %s\n' %
1280 gitmode[man1.flags(f)])
1279 gitmode[man1.flags(f)])
1281 else:
1280 else:
1282 omode = gitmode[man1.flags(f)]
1281 omode = gitmode[man1.flags(f)]
1283 nmode = gitmode[ctx2.flags(f)]
1282 nmode = gitmode[ctx2.flags(f)]
1284 _addmodehdr(header, omode, nmode)
1283 _addmodehdr(header, omode, nmode)
1285 if util.binary(to) or util.binary(tn):
1284 if util.binary(to) or util.binary(tn):
1286 dodiff = 'binary'
1285 dodiff = 'binary'
1287 r = None
1286 r = None
1288 header.insert(0, mdiff.diffline(r, a, b, opts))
1287 header.insert(0, mdiff.diffline(r, a, b, opts))
1289 if dodiff:
1288 if dodiff:
1290 if dodiff == 'binary':
1289 if dodiff == 'binary':
1291 text = b85diff(to, tn)
1290 text = b85diff(to, tn)
1292 else:
1291 else:
1293 text = mdiff.unidiff(to, date1,
1292 text = mdiff.unidiff(to, date1,
1294 # ctx2 date may be dynamic
1293 # ctx2 date may be dynamic
1295 tn, util.datestr(ctx2.date()),
1294 tn, util.datestr(ctx2.date()),
1296 a, b, r, opts=opts)
1295 a, b, r, opts=opts)
1297 if header and (text or len(header) > 1):
1296 if header and (text or len(header) > 1):
1298 yield ''.join(header)
1297 yield ''.join(header)
1299 if text:
1298 if text:
1300 yield text
1299 yield text
1301
1300
1302 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1301 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1303 opts=None):
1302 opts=None):
1304 '''export changesets as hg patches.'''
1303 '''export changesets as hg patches.'''
1305
1304
1306 total = len(revs)
1305 total = len(revs)
1307 revwidth = max([len(str(rev)) for rev in revs])
1306 revwidth = max([len(str(rev)) for rev in revs])
1308
1307
1309 def single(rev, seqno, fp):
1308 def single(rev, seqno, fp):
1310 ctx = repo[rev]
1309 ctx = repo[rev]
1311 node = ctx.node()
1310 node = ctx.node()
1312 parents = [p.node() for p in ctx.parents() if p]
1311 parents = [p.node() for p in ctx.parents() if p]
1313 branch = ctx.branch()
1312 branch = ctx.branch()
1314 if switch_parent:
1313 if switch_parent:
1315 parents.reverse()
1314 parents.reverse()
1316 prev = (parents and parents[0]) or nullid
1315 prev = (parents and parents[0]) or nullid
1317
1316
1318 if not fp:
1317 if not fp:
1319 fp = cmdutil.make_file(repo, template, node, total=total,
1318 fp = cmdutil.make_file(repo, template, node, total=total,
1320 seqno=seqno, revwidth=revwidth,
1319 seqno=seqno, revwidth=revwidth,
1321 mode='ab')
1320 mode='ab')
1322 if fp != sys.stdout and hasattr(fp, 'name'):
1321 if fp != sys.stdout and hasattr(fp, 'name'):
1323 repo.ui.note("%s\n" % fp.name)
1322 repo.ui.note("%s\n" % fp.name)
1324
1323
1325 fp.write("# HG changeset patch\n")
1324 fp.write("# HG changeset patch\n")
1326 fp.write("# User %s\n" % ctx.user())
1325 fp.write("# User %s\n" % ctx.user())
1327 fp.write("# Date %d %d\n" % ctx.date())
1326 fp.write("# Date %d %d\n" % ctx.date())
1328 if branch and (branch != 'default'):
1327 if branch and (branch != 'default'):
1329 fp.write("# Branch %s\n" % branch)
1328 fp.write("# Branch %s\n" % branch)
1330 fp.write("# Node ID %s\n" % hex(node))
1329 fp.write("# Node ID %s\n" % hex(node))
1331 fp.write("# Parent %s\n" % hex(prev))
1330 fp.write("# Parent %s\n" % hex(prev))
1332 if len(parents) > 1:
1331 if len(parents) > 1:
1333 fp.write("# Parent %s\n" % hex(parents[1]))
1332 fp.write("# Parent %s\n" % hex(parents[1]))
1334 fp.write(ctx.description().rstrip())
1333 fp.write(ctx.description().rstrip())
1335 fp.write("\n\n")
1334 fp.write("\n\n")
1336
1335
1337 for chunk in diff(repo, prev, node, opts=opts):
1336 for chunk in diff(repo, prev, node, opts=opts):
1338 fp.write(chunk)
1337 fp.write(chunk)
1339
1338
1340 for seqno, rev in enumerate(revs):
1339 for seqno, rev in enumerate(revs):
1341 single(rev, seqno+1, fp)
1340 single(rev, seqno+1, fp)
1342
1341
1343 def diffstatdata(lines):
1342 def diffstatdata(lines):
1344 filename, adds, removes = None, 0, 0
1343 filename, adds, removes = None, 0, 0
1345 for line in lines:
1344 for line in lines:
1346 if line.startswith('diff'):
1345 if line.startswith('diff'):
1347 if filename:
1346 if filename:
1348 yield (filename, adds, removes)
1347 yield (filename, adds, removes)
1349 # set numbers to 0 anyway when starting new file
1348 # set numbers to 0 anyway when starting new file
1350 adds, removes = 0, 0
1349 adds, removes = 0, 0
1351 if line.startswith('diff --git'):
1350 if line.startswith('diff --git'):
1352 filename = gitre.search(line).group(1)
1351 filename = gitre.search(line).group(1)
1353 else:
1352 else:
1354 # format: "diff -r ... -r ... file name"
1353 # format: "diff -r ... -r ... file name"
1355 filename = line.split(None, 5)[-1]
1354 filename = line.split(None, 5)[-1]
1356 elif line.startswith('+') and not line.startswith('+++'):
1355 elif line.startswith('+') and not line.startswith('+++'):
1357 adds += 1
1356 adds += 1
1358 elif line.startswith('-') and not line.startswith('---'):
1357 elif line.startswith('-') and not line.startswith('---'):
1359 removes += 1
1358 removes += 1
1360 if filename:
1359 if filename:
1361 yield (filename, adds, removes)
1360 yield (filename, adds, removes)
1362
1361
1363 def diffstat(lines, width=80):
1362 def diffstat(lines, width=80):
1364 output = []
1363 output = []
1365 stats = list(diffstatdata(lines))
1364 stats = list(diffstatdata(lines))
1366
1365
1367 maxtotal, maxname = 0, 0
1366 maxtotal, maxname = 0, 0
1368 totaladds, totalremoves = 0, 0
1367 totaladds, totalremoves = 0, 0
1369 for filename, adds, removes in stats:
1368 for filename, adds, removes in stats:
1370 totaladds += adds
1369 totaladds += adds
1371 totalremoves += removes
1370 totalremoves += removes
1372 maxname = max(maxname, len(filename))
1371 maxname = max(maxname, len(filename))
1373 maxtotal = max(maxtotal, adds+removes)
1372 maxtotal = max(maxtotal, adds+removes)
1374
1373
1375 countwidth = len(str(maxtotal))
1374 countwidth = len(str(maxtotal))
1376 graphwidth = width - countwidth - maxname
1375 graphwidth = width - countwidth - maxname
1377 if graphwidth < 10:
1376 if graphwidth < 10:
1378 graphwidth = 10
1377 graphwidth = 10
1379
1378
1380 factor = max(int(math.ceil(float(maxtotal) / graphwidth)), 1)
1379 factor = max(int(math.ceil(float(maxtotal) / graphwidth)), 1)
1381
1380
1382 for filename, adds, removes in stats:
1381 for filename, adds, removes in stats:
1383 # If diffstat runs out of room it doesn't print anything, which
1382 # If diffstat runs out of room it doesn't print anything, which
1384 # isn't very useful, so always print at least one + or - if there
1383 # isn't very useful, so always print at least one + or - if there
1385 # were at least some changes
1384 # were at least some changes
1386 pluses = '+' * max(adds/factor, int(bool(adds)))
1385 pluses = '+' * max(adds/factor, int(bool(adds)))
1387 minuses = '-' * max(removes/factor, int(bool(removes)))
1386 minuses = '-' * max(removes/factor, int(bool(removes)))
1388 output.append(' %-*s | %*.d %s%s\n' % (maxname, filename, countwidth,
1387 output.append(' %-*s | %*.d %s%s\n' % (maxname, filename, countwidth,
1389 adds+removes, pluses, minuses))
1388 adds+removes, pluses, minuses))
1390
1389
1391 if stats:
1390 if stats:
1392 output.append(' %d files changed, %d insertions(+), %d deletions(-)\n'
1391 output.append(' %d files changed, %d insertions(+), %d deletions(-)\n'
1393 % (len(stats), totaladds, totalremoves))
1392 % (len(stats), totaladds, totalremoves))
1394
1393
1395 return ''.join(output)
1394 return ''.join(output)
@@ -1,74 +1,74 b''
1 # base85.py: pure python base85 codec
1 # base85.py: pure python base85 codec
2 #
2 #
3 # Copyright (C) 2009 Brendan Cully <brendan@kublai.com>
3 # Copyright (C) 2009 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 import struct
8 import struct
9
9
10 _b85chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
10 _b85chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
11 "abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"
11 "abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"
12 _b85chars2 = [(a + b) for a in _b85chars for b in _b85chars]
12 _b85chars2 = [(a + b) for a in _b85chars for b in _b85chars]
13 _b85dec = {}
13 _b85dec = {}
14
14
15 def _mkb85dec():
15 def _mkb85dec():
16 for i in range(len(_b85chars)):
16 for i, c in enumerate(_b85chars):
17 _b85dec[_b85chars[i]] = i
17 _b85dec[c] = i
18
18
19 def b85encode(text, pad=False):
19 def b85encode(text, pad=False):
20 """encode text in base85 format"""
20 """encode text in base85 format"""
21 l = len(text)
21 l = len(text)
22 r = l % 4
22 r = l % 4
23 if r:
23 if r:
24 text += '\0' * (4 - r)
24 text += '\0' * (4 - r)
25 longs = len(text) >> 2
25 longs = len(text) >> 2
26 words = struct.unpack('>%dL' % (longs), text)
26 words = struct.unpack('>%dL' % (longs), text)
27
27
28 out = ''.join(_b85chars[(word / 52200625) % 85] +
28 out = ''.join(_b85chars[(word / 52200625) % 85] +
29 _b85chars2[(word / 7225) % 7225] +
29 _b85chars2[(word / 7225) % 7225] +
30 _b85chars2[word % 7225]
30 _b85chars2[word % 7225]
31 for word in words)
31 for word in words)
32
32
33 if pad:
33 if pad:
34 return out
34 return out
35
35
36 # Trim padding
36 # Trim padding
37 olen = l % 4
37 olen = l % 4
38 if olen:
38 if olen:
39 olen += 1
39 olen += 1
40 olen += l / 4 * 5
40 olen += l / 4 * 5
41 return out[:olen]
41 return out[:olen]
42
42
43 def b85decode(text):
43 def b85decode(text):
44 """decode base85-encoded text"""
44 """decode base85-encoded text"""
45 if not _b85dec:
45 if not _b85dec:
46 _mkb85dec()
46 _mkb85dec()
47
47
48 l = len(text)
48 l = len(text)
49 out = []
49 out = []
50 for i in range(0, len(text), 5):
50 for i in range(0, len(text), 5):
51 chunk = text[i:i+5]
51 chunk = text[i:i+5]
52 acc = 0
52 acc = 0
53 for j in range(len(chunk)):
53 for j, c in enumerate(chunk):
54 try:
54 try:
55 acc = acc * 85 + _b85dec[chunk[j]]
55 acc = acc * 85 + _b85dec[c]
56 except KeyError:
56 except KeyError:
57 raise TypeError('Bad base85 character at byte %d' % (i + j))
57 raise TypeError('Bad base85 character at byte %d' % (i + j))
58 if acc > 4294967295:
58 if acc > 4294967295:
59 raise OverflowError('Base85 overflow in hunk starting at byte %d' % i)
59 raise OverflowError('Base85 overflow in hunk starting at byte %d' % i)
60 out.append(acc)
60 out.append(acc)
61
61
62 # Pad final chunk if necessary
62 # Pad final chunk if necessary
63 cl = l % 5
63 cl = l % 5
64 if cl:
64 if cl:
65 acc *= 85 ** (5 - cl)
65 acc *= 85 ** (5 - cl)
66 if cl > 1:
66 if cl > 1:
67 acc += 0xffffff >> (cl - 2) * 8
67 acc += 0xffffff >> (cl - 2) * 8
68 out[-1] = acc
68 out[-1] = acc
69
69
70 out = struct.pack('>%dL' % (len(out)), *out)
70 out = struct.pack('>%dL' % (len(out)), *out)
71 if cl:
71 if cl:
72 out = out[:-(5 - cl)]
72 out = out[:-(5 - cl)]
73
73
74 return out
74 return out
General Comments 0
You need to be logged in to leave comments. Login now