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