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