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 |
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 |