comparison hgext/mq.py @ 8653:aa011d123f71

mq: initializing patchheader class directly from patch content The patch header needs only be read in order to instantiate the class, and as such it makes more sense to do it within the class.
author Cédric Duval <cedricduval@free.fr>
date Sat, 30 May 2009 19:18:29 +0200
parents 9e055cfdd620
children f6cc3638f468
comparison
equal deleted inserted replaced
8652:64614c7e4bd9 8653:aa011d123f71
55 55
56 def __str__(self): 56 def __str__(self):
57 return self.rev + ':' + self.name 57 return self.rev + ':' + self.name
58 58
59 class patchheader(object): 59 class patchheader(object):
60 def __init__(self, message, comments, user, date, haspatch): 60 def __init__(self, pf):
61 self.message = message
62 self.comments = comments
63 self.user = user
64 self.date = date
65 self.haspatch = haspatch
66
67 def setuser(self, user):
68 if not self.setheader(['From: ', '# User '], user):
69 try:
70 patchheaderat = self.comments.index('# HG changeset patch')
71 self.comments.insert(patchheaderat + 1,'# User ' + user)
72 except ValueError:
73 self.comments = ['From: ' + user, ''] + self.comments
74 self.user = user
75
76 def setdate(self, date):
77 if self.setheader(['# Date '], date):
78 self.date = date
79
80 def setmessage(self, message):
81 if self.comments:
82 self._delmsg()
83 self.message = [message]
84 self.comments += self.message
85
86 def setheader(self, prefixes, new):
87 '''Update all references to a field in the patch header.
88 If none found, add it email style.'''
89 res = False
90 for prefix in prefixes:
91 for i in xrange(len(self.comments)):
92 if self.comments[i].startswith(prefix):
93 self.comments[i] = prefix + new
94 res = True
95 break
96 return res
97
98 def __str__(self):
99 if not self.comments:
100 return ''
101 return '\n'.join(self.comments) + '\n\n'
102
103 def _delmsg(self):
104 '''Remove existing message, keeping the rest of the comments fields.
105 If comments contains 'subject: ', message will prepend
106 the field and a blank line.'''
107 if self.message:
108 subj = 'subject: ' + self.message[0].lower()
109 for i in xrange(len(self.comments)):
110 if subj == self.comments[i].lower():
111 del self.comments[i]
112 self.message = self.message[2:]
113 break
114 ci = 0
115 for mi in self.message:
116 while mi != self.comments[ci]:
117 ci += 1
118 del self.comments[ci]
119
120 class queue:
121 def __init__(self, ui, path, patchdir=None):
122 self.basepath = path
123 self.path = patchdir or os.path.join(path, "patches")
124 self.opener = util.opener(self.path)
125 self.ui = ui
126 self.applied_dirty = 0
127 self.series_dirty = 0
128 self.series_path = "series"
129 self.status_path = "status"
130 self.guards_path = "guards"
131 self.active_guards = None
132 self.guards_dirty = False
133 self._diffopts = None
134
135 @util.propertycache
136 def applied(self):
137 if os.path.exists(self.join(self.status_path)):
138 lines = self.opener(self.status_path).read().splitlines()
139 return [statusentry(l) for l in lines]
140 return []
141
142 @util.propertycache
143 def full_series(self):
144 if os.path.exists(self.join(self.series_path)):
145 return self.opener(self.series_path).read().splitlines()
146 return []
147
148 @util.propertycache
149 def series(self):
150 self.parse_series()
151 return self.series
152
153 @util.propertycache
154 def series_guards(self):
155 self.parse_series()
156 return self.series_guards
157
158 def invalidate(self):
159 for a in 'applied full_series series series_guards'.split():
160 if a in self.__dict__:
161 delattr(self, a)
162 self.applied_dirty = 0
163 self.series_dirty = 0
164 self.guards_dirty = False
165 self.active_guards = None
166
167 def diffopts(self):
168 if self._diffopts is None:
169 self._diffopts = patch.diffopts(self.ui)
170 return self._diffopts
171
172 def join(self, *p):
173 return os.path.join(self.path, *p)
174
175 def find_series(self, patch):
176 pre = re.compile("(\s*)([^#]+)")
177 index = 0
178 for l in self.full_series:
179 m = pre.match(l)
180 if m:
181 s = m.group(2)
182 s = s.rstrip()
183 if s == patch:
184 return index
185 index += 1
186 return None
187
188 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
189
190 def parse_series(self):
191 self.series = []
192 self.series_guards = []
193 for l in self.full_series:
194 h = l.find('#')
195 if h == -1:
196 patch = l
197 comment = ''
198 elif h == 0:
199 continue
200 else:
201 patch = l[:h]
202 comment = l[h:]
203 patch = patch.strip()
204 if patch:
205 if patch in self.series:
206 raise util.Abort(_('%s appears more than once in %s') %
207 (patch, self.join(self.series_path)))
208 self.series.append(patch)
209 self.series_guards.append(self.guard_re.findall(comment))
210
211 def check_guard(self, guard):
212 if not guard:
213 return _('guard cannot be an empty string')
214 bad_chars = '# \t\r\n\f'
215 first = guard[0]
216 if first in '-+':
217 return (_('guard %r starts with invalid character: %r') %
218 (guard, first))
219 for c in bad_chars:
220 if c in guard:
221 return _('invalid character in guard %r: %r') % (guard, c)
222
223 def set_active(self, guards):
224 for guard in guards:
225 bad = self.check_guard(guard)
226 if bad:
227 raise util.Abort(bad)
228 guards = sorted(set(guards))
229 self.ui.debug(_('active guards: %s\n') % ' '.join(guards))
230 self.active_guards = guards
231 self.guards_dirty = True
232
233 def active(self):
234 if self.active_guards is None:
235 self.active_guards = []
236 try:
237 guards = self.opener(self.guards_path).read().split()
238 except IOError, err:
239 if err.errno != errno.ENOENT: raise
240 guards = []
241 for i, guard in enumerate(guards):
242 bad = self.check_guard(guard)
243 if bad:
244 self.ui.warn('%s:%d: %s\n' %
245 (self.join(self.guards_path), i + 1, bad))
246 else:
247 self.active_guards.append(guard)
248 return self.active_guards
249
250 def set_guards(self, idx, guards):
251 for g in guards:
252 if len(g) < 2:
253 raise util.Abort(_('guard %r too short') % g)
254 if g[0] not in '-+':
255 raise util.Abort(_('guard %r starts with invalid char') % g)
256 bad = self.check_guard(g[1:])
257 if bad:
258 raise util.Abort(bad)
259 drop = self.guard_re.sub('', self.full_series[idx])
260 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
261 self.parse_series()
262 self.series_dirty = True
263
264 def pushable(self, idx):
265 if isinstance(idx, str):
266 idx = self.series.index(idx)
267 patchguards = self.series_guards[idx]
268 if not patchguards:
269 return True, None
270 guards = self.active()
271 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
272 if exactneg:
273 return False, exactneg[0]
274 pos = [g for g in patchguards if g[0] == '+']
275 exactpos = [g for g in pos if g[1:] in guards]
276 if pos:
277 if exactpos:
278 return True, exactpos[0]
279 return False, pos
280 return True, ''
281
282 def explain_pushable(self, idx, all_patches=False):
283 write = all_patches and self.ui.write or self.ui.warn
284 if all_patches or self.ui.verbose:
285 if isinstance(idx, str):
286 idx = self.series.index(idx)
287 pushable, why = self.pushable(idx)
288 if all_patches and pushable:
289 if why is None:
290 write(_('allowing %s - no guards in effect\n') %
291 self.series[idx])
292 else:
293 if not why:
294 write(_('allowing %s - no matching negative guards\n') %
295 self.series[idx])
296 else:
297 write(_('allowing %s - guarded by %r\n') %
298 (self.series[idx], why))
299 if not pushable:
300 if why:
301 write(_('skipping %s - guarded by %r\n') %
302 (self.series[idx], why))
303 else:
304 write(_('skipping %s - no matching guards\n') %
305 self.series[idx])
306
307 def save_dirty(self):
308 def write_list(items, path):
309 fp = self.opener(path, 'w')
310 for i in items:
311 fp.write("%s\n" % i)
312 fp.close()
313 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
314 if self.series_dirty: write_list(self.full_series, self.series_path)
315 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
316
317 def readheaders(self, patch):
318 def eatdiff(lines): 61 def eatdiff(lines):
319 while lines: 62 while lines:
320 l = lines[-1] 63 l = lines[-1]
321 if (l.startswith("diff -") or 64 if (l.startswith("diff -") or
322 l.startswith("Index:") or 65 l.startswith("Index:") or
330 if re.match('\s*$', l): 73 if re.match('\s*$', l):
331 del lines[-1] 74 del lines[-1]
332 else: 75 else:
333 break 76 break
334 77
335 pf = self.join(patch)
336 message = [] 78 message = []
337 comments = [] 79 comments = []
338 user = None 80 user = None
339 date = None 81 date = None
340 format = None 82 format = None
387 129
388 # make sure message isn't empty 130 # make sure message isn't empty
389 if format and format.startswith("tag") and subject: 131 if format and format.startswith("tag") and subject:
390 message.insert(0, "") 132 message.insert(0, "")
391 message.insert(0, subject) 133 message.insert(0, subject)
392 return patchheader(message, comments, user, date, diffstart > 1) 134
135 self.message = message
136 self.comments = comments
137 self.user = user
138 self.date = date
139 self.haspatch = diffstart > 1
140
141 def setuser(self, user):
142 if not self.setheader(['From: ', '# User '], user):
143 try:
144 patchheaderat = self.comments.index('# HG changeset patch')
145 self.comments.insert(patchheaderat + 1,'# User ' + user)
146 except ValueError:
147 self.comments = ['From: ' + user, ''] + self.comments
148 self.user = user
149
150 def setdate(self, date):
151 if self.setheader(['# Date '], date):
152 self.date = date
153
154 def setmessage(self, message):
155 if self.comments:
156 self._delmsg()
157 self.message = [message]
158 self.comments += self.message
159
160 def setheader(self, prefixes, new):
161 '''Update all references to a field in the patch header.
162 If none found, add it email style.'''
163 res = False
164 for prefix in prefixes:
165 for i in xrange(len(self.comments)):
166 if self.comments[i].startswith(prefix):
167 self.comments[i] = prefix + new
168 res = True
169 break
170 return res
171
172 def __str__(self):
173 if not self.comments:
174 return ''
175 return '\n'.join(self.comments) + '\n\n'
176
177 def _delmsg(self):
178 '''Remove existing message, keeping the rest of the comments fields.
179 If comments contains 'subject: ', message will prepend
180 the field and a blank line.'''
181 if self.message:
182 subj = 'subject: ' + self.message[0].lower()
183 for i in xrange(len(self.comments)):
184 if subj == self.comments[i].lower():
185 del self.comments[i]
186 self.message = self.message[2:]
187 break
188 ci = 0
189 for mi in self.message:
190 while mi != self.comments[ci]:
191 ci += 1
192 del self.comments[ci]
193
194 class queue:
195 def __init__(self, ui, path, patchdir=None):
196 self.basepath = path
197 self.path = patchdir or os.path.join(path, "patches")
198 self.opener = util.opener(self.path)
199 self.ui = ui
200 self.applied_dirty = 0
201 self.series_dirty = 0
202 self.series_path = "series"
203 self.status_path = "status"
204 self.guards_path = "guards"
205 self.active_guards = None
206 self.guards_dirty = False
207 self._diffopts = None
208
209 @util.propertycache
210 def applied(self):
211 if os.path.exists(self.join(self.status_path)):
212 lines = self.opener(self.status_path).read().splitlines()
213 return [statusentry(l) for l in lines]
214 return []
215
216 @util.propertycache
217 def full_series(self):
218 if os.path.exists(self.join(self.series_path)):
219 return self.opener(self.series_path).read().splitlines()
220 return []
221
222 @util.propertycache
223 def series(self):
224 self.parse_series()
225 return self.series
226
227 @util.propertycache
228 def series_guards(self):
229 self.parse_series()
230 return self.series_guards
231
232 def invalidate(self):
233 for a in 'applied full_series series series_guards'.split():
234 if a in self.__dict__:
235 delattr(self, a)
236 self.applied_dirty = 0
237 self.series_dirty = 0
238 self.guards_dirty = False
239 self.active_guards = None
240
241 def diffopts(self):
242 if self._diffopts is None:
243 self._diffopts = patch.diffopts(self.ui)
244 return self._diffopts
245
246 def join(self, *p):
247 return os.path.join(self.path, *p)
248
249 def find_series(self, patch):
250 pre = re.compile("(\s*)([^#]+)")
251 index = 0
252 for l in self.full_series:
253 m = pre.match(l)
254 if m:
255 s = m.group(2)
256 s = s.rstrip()
257 if s == patch:
258 return index
259 index += 1
260 return None
261
262 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
263
264 def parse_series(self):
265 self.series = []
266 self.series_guards = []
267 for l in self.full_series:
268 h = l.find('#')
269 if h == -1:
270 patch = l
271 comment = ''
272 elif h == 0:
273 continue
274 else:
275 patch = l[:h]
276 comment = l[h:]
277 patch = patch.strip()
278 if patch:
279 if patch in self.series:
280 raise util.Abort(_('%s appears more than once in %s') %
281 (patch, self.join(self.series_path)))
282 self.series.append(patch)
283 self.series_guards.append(self.guard_re.findall(comment))
284
285 def check_guard(self, guard):
286 if not guard:
287 return _('guard cannot be an empty string')
288 bad_chars = '# \t\r\n\f'
289 first = guard[0]
290 if first in '-+':
291 return (_('guard %r starts with invalid character: %r') %
292 (guard, first))
293 for c in bad_chars:
294 if c in guard:
295 return _('invalid character in guard %r: %r') % (guard, c)
296
297 def set_active(self, guards):
298 for guard in guards:
299 bad = self.check_guard(guard)
300 if bad:
301 raise util.Abort(bad)
302 guards = sorted(set(guards))
303 self.ui.debug(_('active guards: %s\n') % ' '.join(guards))
304 self.active_guards = guards
305 self.guards_dirty = True
306
307 def active(self):
308 if self.active_guards is None:
309 self.active_guards = []
310 try:
311 guards = self.opener(self.guards_path).read().split()
312 except IOError, err:
313 if err.errno != errno.ENOENT: raise
314 guards = []
315 for i, guard in enumerate(guards):
316 bad = self.check_guard(guard)
317 if bad:
318 self.ui.warn('%s:%d: %s\n' %
319 (self.join(self.guards_path), i + 1, bad))
320 else:
321 self.active_guards.append(guard)
322 return self.active_guards
323
324 def set_guards(self, idx, guards):
325 for g in guards:
326 if len(g) < 2:
327 raise util.Abort(_('guard %r too short') % g)
328 if g[0] not in '-+':
329 raise util.Abort(_('guard %r starts with invalid char') % g)
330 bad = self.check_guard(g[1:])
331 if bad:
332 raise util.Abort(bad)
333 drop = self.guard_re.sub('', self.full_series[idx])
334 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
335 self.parse_series()
336 self.series_dirty = True
337
338 def pushable(self, idx):
339 if isinstance(idx, str):
340 idx = self.series.index(idx)
341 patchguards = self.series_guards[idx]
342 if not patchguards:
343 return True, None
344 guards = self.active()
345 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
346 if exactneg:
347 return False, exactneg[0]
348 pos = [g for g in patchguards if g[0] == '+']
349 exactpos = [g for g in pos if g[1:] in guards]
350 if pos:
351 if exactpos:
352 return True, exactpos[0]
353 return False, pos
354 return True, ''
355
356 def explain_pushable(self, idx, all_patches=False):
357 write = all_patches and self.ui.write or self.ui.warn
358 if all_patches or self.ui.verbose:
359 if isinstance(idx, str):
360 idx = self.series.index(idx)
361 pushable, why = self.pushable(idx)
362 if all_patches and pushable:
363 if why is None:
364 write(_('allowing %s - no guards in effect\n') %
365 self.series[idx])
366 else:
367 if not why:
368 write(_('allowing %s - no matching negative guards\n') %
369 self.series[idx])
370 else:
371 write(_('allowing %s - guarded by %r\n') %
372 (self.series[idx], why))
373 if not pushable:
374 if why:
375 write(_('skipping %s - guarded by %r\n') %
376 (self.series[idx], why))
377 else:
378 write(_('skipping %s - no matching guards\n') %
379 self.series[idx])
380
381 def save_dirty(self):
382 def write_list(items, path):
383 fp = self.opener(path, 'w')
384 for i in items:
385 fp.write("%s\n" % i)
386 fp.close()
387 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
388 if self.series_dirty: write_list(self.full_series, self.series_path)
389 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
393 390
394 def removeundo(self, repo): 391 def removeundo(self, repo):
395 undo = repo.sjoin('undo') 392 undo = repo.sjoin('undo')
396 if not os.path.exists(undo): 393 if not os.path.exists(undo):
397 return 394 return
431 raise util.Abort(_("update returned %d") % ret) 428 raise util.Abort(_("update returned %d") % ret)
432 n = repo.commit(None, ctx.description(), ctx.user(), force=1) 429 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
433 if n is None: 430 if n is None:
434 raise util.Abort(_("repo commit failed")) 431 raise util.Abort(_("repo commit failed"))
435 try: 432 try:
436 ph = mergeq.readheaders(patch) 433 ph = patchheader(mergeq.join(patch))
437 except: 434 except:
438 raise util.Abort(_("unable to read %s") % patch) 435 raise util.Abort(_("unable to read %s") % patch)
439 436
440 patchf = self.opener(patch, "w") 437 patchf = self.opener(patch, "w")
441 comments = str(ph) 438 comments = str(ph)
558 continue 555 continue
559 self.ui.warn(_("applying %s\n") % patchname) 556 self.ui.warn(_("applying %s\n") % patchname)
560 pf = os.path.join(patchdir, patchname) 557 pf = os.path.join(patchdir, patchname)
561 558
562 try: 559 try:
563 ph = self.readheaders(patchname) 560 ph = patchheader(self.join(patchname))
564 except: 561 except:
565 self.ui.warn(_("Unable to read %s\n") % patchname) 562 self.ui.warn(_("Unable to read %s\n") % patchname)
566 err = 1 563 err = 1
567 break 564 break
568 565
1118 top = bin(top) 1115 top = bin(top)
1119 if repo.changelog.heads(top) != [top]: 1116 if repo.changelog.heads(top) != [top]:
1120 raise util.Abort(_("cannot refresh a revision with children")) 1117 raise util.Abort(_("cannot refresh a revision with children"))
1121 cparents = repo.changelog.parents(top) 1118 cparents = repo.changelog.parents(top)
1122 patchparent = self.qparents(repo, top) 1119 patchparent = self.qparents(repo, top)
1123 ph = self.readheaders(patchfn) 1120 ph = patchheader(self.join(patchfn))
1124 1121
1125 patchf = self.opener(patchfn, 'r') 1122 patchf = self.opener(patchfn, 'r')
1126 1123
1127 # if the patch was a git patch, refresh it as a git patch 1124 # if the patch was a git patch, refresh it as a git patch
1128 for line in patchf: 1125 for line in patchf:
1347 1344
1348 def qseries(self, repo, missing=None, start=0, length=None, status=None, 1345 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1349 summary=False): 1346 summary=False):
1350 def displayname(patchname): 1347 def displayname(patchname):
1351 if summary: 1348 if summary:
1352 ph = self.readheaders(patchname) 1349 ph = patchheader(self.join(patchname))
1353 msg = ph.message 1350 msg = ph.message
1354 msg = msg and ': ' + msg[0] or ': ' 1351 msg = msg and ': ' + msg[0] or ': '
1355 else: 1352 else:
1356 msg = '' 1353 msg = ''
1357 return '%s%s' % (patchname, msg) 1354 return '%s%s' % (patchname, msg)
1920 ui.write(_("no patches applied\n")) 1917 ui.write(_("no patches applied\n"))
1921 return 1 1918 return 1
1922 if message: 1919 if message:
1923 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"')) 1920 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1924 patch = q.applied[-1].name 1921 patch = q.applied[-1].name
1925 ph = q.readheaders(patch) 1922 ph = patchheader(q.join(patch))
1926 message = ui.edit('\n'.join(ph.message), ph.user or ui.username()) 1923 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
1927 setupheaderopts(ui, opts) 1924 setupheaderopts(ui, opts)
1928 ret = q.refresh(repo, pats, msg=message, **opts) 1925 ret = q.refresh(repo, pats, msg=message, **opts)
1929 q.save_dirty() 1926 q.save_dirty()
1930 return ret 1927 return ret
1982 raise util.Abort(_('qfold cannot fold already applied patch %s') % p) 1979 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1983 patches.append(p) 1980 patches.append(p)
1984 1981
1985 for p in patches: 1982 for p in patches:
1986 if not message: 1983 if not message:
1987 ph = q.readheaders(p) 1984 ph = patchheader(q.join(p))
1988 if ph.message: 1985 if ph.message:
1989 messages.append(ph.message) 1986 messages.append(ph.message)
1990 pf = q.join(p) 1987 pf = q.join(p)
1991 (patchsuccess, files, fuzz) = q.patch(repo, pf) 1988 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1992 if not patchsuccess: 1989 if not patchsuccess:
1993 raise util.Abort(_('Error folding patch %s') % p) 1990 raise util.Abort(_('Error folding patch %s') % p)
1994 patch.updatedir(ui, repo, files) 1991 patch.updatedir(ui, repo, files)
1995 1992
1996 if not message: 1993 if not message:
1997 ph = q.readheaders(parent) 1994 ph = patchheader(q.join(parent))
1998 message, user = ph.message, ph.user 1995 message, user = ph.message, ph.user
1999 for msg in messages: 1996 for msg in messages:
2000 message.append('* * *') 1997 message.append('* * *')
2001 message.extend(msg) 1998 message.extend(msg)
2002 message = '\n'.join(message) 1999 message = '\n'.join(message)
2073 else: 2070 else:
2074 if not q.applied: 2071 if not q.applied:
2075 ui.write('no patches applied\n') 2072 ui.write('no patches applied\n')
2076 return 1 2073 return 1
2077 patch = q.lookup('qtip') 2074 patch = q.lookup('qtip')
2078 ph = repo.mq.readheaders(patch) 2075 ph = patchheader(repo.mq.join(patch))
2079 2076
2080 ui.write('\n'.join(ph.message) + '\n') 2077 ui.write('\n'.join(ph.message) + '\n')
2081 2078
2082 def lastsavename(path): 2079 def lastsavename(path):
2083 (directory, base) = os.path.split(path) 2080 (directory, base) = os.path.split(path)