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