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