##// END OF EJS Templates
Teach mq about git patches
Brendan Cully -
r2934:2f190e99 default
parent child Browse files
Show More
@@ -1,1980 +1,1981 b''
1 # queue.py - patch queues for mercurial
1 # queue.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 '''patch management and development
8 '''patch management and development
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use "hg help command" for more details):
17 Common tasks (use "hg help command" for more details):
18
18
19 prepare repository to work with patches qinit
19 prepare repository to work with patches qinit
20 create new patch qnew
20 create new patch qnew
21 import existing patch qimport
21 import existing patch qimport
22
22
23 print patch series qseries
23 print patch series qseries
24 print applied patches qapplied
24 print applied patches qapplied
25 print name of top applied patch qtop
25 print name of top applied patch qtop
26
26
27 add known patch to applied stack qpush
27 add known patch to applied stack qpush
28 remove patch from applied stack qpop
28 remove patch from applied stack qpop
29 refresh contents of top applied patch qrefresh
29 refresh contents of top applied patch qrefresh
30 '''
30 '''
31
31
32 from mercurial.demandload import *
32 from mercurial.demandload import *
33 from mercurial.i18n import gettext as _
33 from mercurial.i18n import gettext as _
34 demandload(globals(), "os sys re struct traceback errno bz2")
34 demandload(globals(), "os sys re struct traceback errno bz2")
35 demandload(globals(), "mercurial:cmdutil,commands,hg,patch,revlog,ui,util")
35 demandload(globals(), "mercurial:cmdutil,commands,hg,patch,revlog,ui,util")
36
36
37 commands.norepo += " qclone qversion"
37 commands.norepo += " qclone qversion"
38
38
39 class statusentry:
39 class statusentry:
40 def __init__(self, rev, name=None):
40 def __init__(self, rev, name=None):
41 if not name:
41 if not name:
42 fields = rev.split(':')
42 fields = rev.split(':')
43 if len(fields) == 2:
43 if len(fields) == 2:
44 self.rev, self.name = fields
44 self.rev, self.name = fields
45 else:
45 else:
46 self.rev, self.name = None, None
46 self.rev, self.name = None, None
47 else:
47 else:
48 self.rev, self.name = rev, name
48 self.rev, self.name = rev, name
49
49
50 def __str__(self):
50 def __str__(self):
51 return self.rev + ':' + self.name
51 return self.rev + ':' + self.name
52
52
53 class queue:
53 class queue:
54 def __init__(self, ui, path, patchdir=None):
54 def __init__(self, ui, path, patchdir=None):
55 self.basepath = path
55 self.basepath = path
56 self.path = patchdir or os.path.join(path, "patches")
56 self.path = patchdir or os.path.join(path, "patches")
57 self.opener = util.opener(self.path)
57 self.opener = util.opener(self.path)
58 self.ui = ui
58 self.ui = ui
59 self.applied = []
59 self.applied = []
60 self.full_series = []
60 self.full_series = []
61 self.applied_dirty = 0
61 self.applied_dirty = 0
62 self.series_dirty = 0
62 self.series_dirty = 0
63 self.series_path = "series"
63 self.series_path = "series"
64 self.status_path = "status"
64 self.status_path = "status"
65 self.guards_path = "guards"
65 self.guards_path = "guards"
66 self.active_guards = None
66 self.active_guards = None
67 self.guards_dirty = False
67 self.guards_dirty = False
68 self._diffopts = None
68 self._diffopts = None
69
69
70 if os.path.exists(self.join(self.series_path)):
70 if os.path.exists(self.join(self.series_path)):
71 self.full_series = self.opener(self.series_path).read().splitlines()
71 self.full_series = self.opener(self.series_path).read().splitlines()
72 self.parse_series()
72 self.parse_series()
73
73
74 if os.path.exists(self.join(self.status_path)):
74 if os.path.exists(self.join(self.status_path)):
75 lines = self.opener(self.status_path).read().splitlines()
75 lines = self.opener(self.status_path).read().splitlines()
76 self.applied = [statusentry(l) for l in lines]
76 self.applied = [statusentry(l) for l in lines]
77
77
78 def diffopts(self):
78 def diffopts(self):
79 if self._diffopts is None:
79 if self._diffopts is None:
80 self._diffopts = patch.diffopts(self.ui)
80 self._diffopts = patch.diffopts(self.ui)
81 return self._diffopts
81 return self._diffopts
82
82
83 def join(self, *p):
83 def join(self, *p):
84 return os.path.join(self.path, *p)
84 return os.path.join(self.path, *p)
85
85
86 def find_series(self, patch):
86 def find_series(self, patch):
87 pre = re.compile("(\s*)([^#]+)")
87 pre = re.compile("(\s*)([^#]+)")
88 index = 0
88 index = 0
89 for l in self.full_series:
89 for l in self.full_series:
90 m = pre.match(l)
90 m = pre.match(l)
91 if m:
91 if m:
92 s = m.group(2)
92 s = m.group(2)
93 s = s.rstrip()
93 s = s.rstrip()
94 if s == patch:
94 if s == patch:
95 return index
95 return index
96 index += 1
96 index += 1
97 return None
97 return None
98
98
99 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
99 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
100
100
101 def parse_series(self):
101 def parse_series(self):
102 self.series = []
102 self.series = []
103 self.series_guards = []
103 self.series_guards = []
104 for l in self.full_series:
104 for l in self.full_series:
105 h = l.find('#')
105 h = l.find('#')
106 if h == -1:
106 if h == -1:
107 patch = l
107 patch = l
108 comment = ''
108 comment = ''
109 elif h == 0:
109 elif h == 0:
110 continue
110 continue
111 else:
111 else:
112 patch = l[:h]
112 patch = l[:h]
113 comment = l[h:]
113 comment = l[h:]
114 patch = patch.strip()
114 patch = patch.strip()
115 if patch:
115 if patch:
116 self.series.append(patch)
116 self.series.append(patch)
117 self.series_guards.append(self.guard_re.findall(comment))
117 self.series_guards.append(self.guard_re.findall(comment))
118
118
119 def check_guard(self, guard):
119 def check_guard(self, guard):
120 bad_chars = '# \t\r\n\f'
120 bad_chars = '# \t\r\n\f'
121 first = guard[0]
121 first = guard[0]
122 for c in '-+':
122 for c in '-+':
123 if first == c:
123 if first == c:
124 return (_('guard %r starts with invalid character: %r') %
124 return (_('guard %r starts with invalid character: %r') %
125 (guard, c))
125 (guard, c))
126 for c in bad_chars:
126 for c in bad_chars:
127 if c in guard:
127 if c in guard:
128 return _('invalid character in guard %r: %r') % (guard, c)
128 return _('invalid character in guard %r: %r') % (guard, c)
129
129
130 def set_active(self, guards):
130 def set_active(self, guards):
131 for guard in guards:
131 for guard in guards:
132 bad = self.check_guard(guard)
132 bad = self.check_guard(guard)
133 if bad:
133 if bad:
134 raise util.Abort(bad)
134 raise util.Abort(bad)
135 guards = dict.fromkeys(guards).keys()
135 guards = dict.fromkeys(guards).keys()
136 guards.sort()
136 guards.sort()
137 self.ui.debug('active guards: %s\n' % ' '.join(guards))
137 self.ui.debug('active guards: %s\n' % ' '.join(guards))
138 self.active_guards = guards
138 self.active_guards = guards
139 self.guards_dirty = True
139 self.guards_dirty = True
140
140
141 def active(self):
141 def active(self):
142 if self.active_guards is None:
142 if self.active_guards is None:
143 self.active_guards = []
143 self.active_guards = []
144 try:
144 try:
145 guards = self.opener(self.guards_path).read().split()
145 guards = self.opener(self.guards_path).read().split()
146 except IOError, err:
146 except IOError, err:
147 if err.errno != errno.ENOENT: raise
147 if err.errno != errno.ENOENT: raise
148 guards = []
148 guards = []
149 for i, guard in enumerate(guards):
149 for i, guard in enumerate(guards):
150 bad = self.check_guard(guard)
150 bad = self.check_guard(guard)
151 if bad:
151 if bad:
152 self.ui.warn('%s:%d: %s\n' %
152 self.ui.warn('%s:%d: %s\n' %
153 (self.join(self.guards_path), i + 1, bad))
153 (self.join(self.guards_path), i + 1, bad))
154 else:
154 else:
155 self.active_guards.append(guard)
155 self.active_guards.append(guard)
156 return self.active_guards
156 return self.active_guards
157
157
158 def set_guards(self, idx, guards):
158 def set_guards(self, idx, guards):
159 for g in guards:
159 for g in guards:
160 if len(g) < 2:
160 if len(g) < 2:
161 raise util.Abort(_('guard %r too short') % g)
161 raise util.Abort(_('guard %r too short') % g)
162 if g[0] not in '-+':
162 if g[0] not in '-+':
163 raise util.Abort(_('guard %r starts with invalid char') % g)
163 raise util.Abort(_('guard %r starts with invalid char') % g)
164 bad = self.check_guard(g[1:])
164 bad = self.check_guard(g[1:])
165 if bad:
165 if bad:
166 raise util.Abort(bad)
166 raise util.Abort(bad)
167 drop = self.guard_re.sub('', self.full_series[idx])
167 drop = self.guard_re.sub('', self.full_series[idx])
168 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
168 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
169 self.parse_series()
169 self.parse_series()
170 self.series_dirty = True
170 self.series_dirty = True
171
171
172 def pushable(self, idx):
172 def pushable(self, idx):
173 if isinstance(idx, str):
173 if isinstance(idx, str):
174 idx = self.series.index(idx)
174 idx = self.series.index(idx)
175 patchguards = self.series_guards[idx]
175 patchguards = self.series_guards[idx]
176 if not patchguards:
176 if not patchguards:
177 return True, None
177 return True, None
178 default = False
178 default = False
179 guards = self.active()
179 guards = self.active()
180 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
180 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
181 if exactneg:
181 if exactneg:
182 return False, exactneg[0]
182 return False, exactneg[0]
183 pos = [g for g in patchguards if g[0] == '+']
183 pos = [g for g in patchguards if g[0] == '+']
184 exactpos = [g for g in pos if g[1:] in guards]
184 exactpos = [g for g in pos if g[1:] in guards]
185 if pos:
185 if pos:
186 if exactpos:
186 if exactpos:
187 return True, exactpos[0]
187 return True, exactpos[0]
188 return False, pos
188 return False, pos
189 return True, ''
189 return True, ''
190
190
191 def explain_pushable(self, idx, all_patches=False):
191 def explain_pushable(self, idx, all_patches=False):
192 write = all_patches and self.ui.write or self.ui.warn
192 write = all_patches and self.ui.write or self.ui.warn
193 if all_patches or self.ui.verbose:
193 if all_patches or self.ui.verbose:
194 if isinstance(idx, str):
194 if isinstance(idx, str):
195 idx = self.series.index(idx)
195 idx = self.series.index(idx)
196 pushable, why = self.pushable(idx)
196 pushable, why = self.pushable(idx)
197 if all_patches and pushable:
197 if all_patches and pushable:
198 if why is None:
198 if why is None:
199 write(_('allowing %s - no guards in effect\n') %
199 write(_('allowing %s - no guards in effect\n') %
200 self.series[idx])
200 self.series[idx])
201 else:
201 else:
202 if not why:
202 if not why:
203 write(_('allowing %s - no matching negative guards\n') %
203 write(_('allowing %s - no matching negative guards\n') %
204 self.series[idx])
204 self.series[idx])
205 else:
205 else:
206 write(_('allowing %s - guarded by %r\n') %
206 write(_('allowing %s - guarded by %r\n') %
207 (self.series[idx], why))
207 (self.series[idx], why))
208 if not pushable:
208 if not pushable:
209 if why:
209 if why:
210 write(_('skipping %s - guarded by %r\n') %
210 write(_('skipping %s - guarded by %r\n') %
211 (self.series[idx], ' '.join(why)))
211 (self.series[idx], ' '.join(why)))
212 else:
212 else:
213 write(_('skipping %s - no matching guards\n') %
213 write(_('skipping %s - no matching guards\n') %
214 self.series[idx])
214 self.series[idx])
215
215
216 def save_dirty(self):
216 def save_dirty(self):
217 def write_list(items, path):
217 def write_list(items, path):
218 fp = self.opener(path, 'w')
218 fp = self.opener(path, 'w')
219 for i in items:
219 for i in items:
220 print >> fp, i
220 print >> fp, i
221 fp.close()
221 fp.close()
222 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
222 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
223 if self.series_dirty: write_list(self.full_series, self.series_path)
223 if self.series_dirty: write_list(self.full_series, self.series_path)
224 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
224 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
225
225
226 def readheaders(self, patch):
226 def readheaders(self, patch):
227 def eatdiff(lines):
227 def eatdiff(lines):
228 while lines:
228 while lines:
229 l = lines[-1]
229 l = lines[-1]
230 if (l.startswith("diff -") or
230 if (l.startswith("diff -") or
231 l.startswith("Index:") or
231 l.startswith("Index:") or
232 l.startswith("===========")):
232 l.startswith("===========")):
233 del lines[-1]
233 del lines[-1]
234 else:
234 else:
235 break
235 break
236 def eatempty(lines):
236 def eatempty(lines):
237 while lines:
237 while lines:
238 l = lines[-1]
238 l = lines[-1]
239 if re.match('\s*$', l):
239 if re.match('\s*$', l):
240 del lines[-1]
240 del lines[-1]
241 else:
241 else:
242 break
242 break
243
243
244 pf = self.join(patch)
244 pf = self.join(patch)
245 message = []
245 message = []
246 comments = []
246 comments = []
247 user = None
247 user = None
248 date = None
248 date = None
249 format = None
249 format = None
250 subject = None
250 subject = None
251 diffstart = 0
251 diffstart = 0
252
252
253 for line in file(pf):
253 for line in file(pf):
254 line = line.rstrip()
254 line = line.rstrip()
255 if line.startswith('diff --git'):
256 diffstart = 2
257 break
255 if diffstart:
258 if diffstart:
256 if line.startswith('+++ '):
259 if line.startswith('+++ '):
257 diffstart = 2
260 diffstart = 2
258 break
261 break
259 if line.startswith("--- "):
262 if line.startswith("--- "):
260 diffstart = 1
263 diffstart = 1
261 continue
264 continue
262 elif format == "hgpatch":
265 elif format == "hgpatch":
263 # parse values when importing the result of an hg export
266 # parse values when importing the result of an hg export
264 if line.startswith("# User "):
267 if line.startswith("# User "):
265 user = line[7:]
268 user = line[7:]
266 elif line.startswith("# Date "):
269 elif line.startswith("# Date "):
267 date = line[7:]
270 date = line[7:]
268 elif not line.startswith("# ") and line:
271 elif not line.startswith("# ") and line:
269 message.append(line)
272 message.append(line)
270 format = None
273 format = None
271 elif line == '# HG changeset patch':
274 elif line == '# HG changeset patch':
272 format = "hgpatch"
275 format = "hgpatch"
273 elif (format != "tagdone" and (line.startswith("Subject: ") or
276 elif (format != "tagdone" and (line.startswith("Subject: ") or
274 line.startswith("subject: "))):
277 line.startswith("subject: "))):
275 subject = line[9:]
278 subject = line[9:]
276 format = "tag"
279 format = "tag"
277 elif (format != "tagdone" and (line.startswith("From: ") or
280 elif (format != "tagdone" and (line.startswith("From: ") or
278 line.startswith("from: "))):
281 line.startswith("from: "))):
279 user = line[6:]
282 user = line[6:]
280 format = "tag"
283 format = "tag"
281 elif format == "tag" and line == "":
284 elif format == "tag" and line == "":
282 # when looking for tags (subject: from: etc) they
285 # when looking for tags (subject: from: etc) they
283 # end once you find a blank line in the source
286 # end once you find a blank line in the source
284 format = "tagdone"
287 format = "tagdone"
285 elif message or line:
288 elif message or line:
286 message.append(line)
289 message.append(line)
287 comments.append(line)
290 comments.append(line)
288
291
289 eatdiff(message)
292 eatdiff(message)
290 eatdiff(comments)
293 eatdiff(comments)
291 eatempty(message)
294 eatempty(message)
292 eatempty(comments)
295 eatempty(comments)
293
296
294 # make sure message isn't empty
297 # make sure message isn't empty
295 if format and format.startswith("tag") and subject:
298 if format and format.startswith("tag") and subject:
296 message.insert(0, "")
299 message.insert(0, "")
297 message.insert(0, subject)
300 message.insert(0, subject)
298 return (message, comments, user, date, diffstart > 1)
301 return (message, comments, user, date, diffstart > 1)
299
302
300 def printdiff(self, repo, node1, node2=None, files=None,
303 def printdiff(self, repo, node1, node2=None, files=None,
301 fp=None, changes=None, opts=None):
304 fp=None, changes=None, opts=None):
302 patch.diff(repo, node1, node2, files,
305 patch.diff(repo, node1, node2, files,
303 fp=fp, changes=changes, opts=self.diffopts())
306 fp=fp, changes=changes, opts=self.diffopts())
304
307
305 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
308 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
306 # first try just applying the patch
309 # first try just applying the patch
307 (err, n) = self.apply(repo, [ patch ], update_status=False,
310 (err, n) = self.apply(repo, [ patch ], update_status=False,
308 strict=True, merge=rev, wlock=wlock)
311 strict=True, merge=rev, wlock=wlock)
309
312
310 if err == 0:
313 if err == 0:
311 return (err, n)
314 return (err, n)
312
315
313 if n is None:
316 if n is None:
314 raise util.Abort(_("apply failed for patch %s") % patch)
317 raise util.Abort(_("apply failed for patch %s") % patch)
315
318
316 self.ui.warn("patch didn't work out, merging %s\n" % patch)
319 self.ui.warn("patch didn't work out, merging %s\n" % patch)
317
320
318 # apply failed, strip away that rev and merge.
321 # apply failed, strip away that rev and merge.
319 hg.clean(repo, head, wlock=wlock)
322 hg.clean(repo, head, wlock=wlock)
320 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
323 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
321
324
322 c = repo.changelog.read(rev)
325 c = repo.changelog.read(rev)
323 ret = hg.merge(repo, rev, wlock=wlock)
326 ret = hg.merge(repo, rev, wlock=wlock)
324 if ret:
327 if ret:
325 raise util.Abort(_("update returned %d") % ret)
328 raise util.Abort(_("update returned %d") % ret)
326 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
329 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
327 if n == None:
330 if n == None:
328 raise util.Abort(_("repo commit failed"))
331 raise util.Abort(_("repo commit failed"))
329 try:
332 try:
330 message, comments, user, date, patchfound = mergeq.readheaders(patch)
333 message, comments, user, date, patchfound = mergeq.readheaders(patch)
331 except:
334 except:
332 raise util.Abort(_("unable to read %s") % patch)
335 raise util.Abort(_("unable to read %s") % patch)
333
336
334 patchf = self.opener(patch, "w")
337 patchf = self.opener(patch, "w")
335 if comments:
338 if comments:
336 comments = "\n".join(comments) + '\n\n'
339 comments = "\n".join(comments) + '\n\n'
337 patchf.write(comments)
340 patchf.write(comments)
338 self.printdiff(repo, head, n, fp=patchf)
341 self.printdiff(repo, head, n, fp=patchf)
339 patchf.close()
342 patchf.close()
340 return (0, n)
343 return (0, n)
341
344
342 def qparents(self, repo, rev=None):
345 def qparents(self, repo, rev=None):
343 if rev is None:
346 if rev is None:
344 (p1, p2) = repo.dirstate.parents()
347 (p1, p2) = repo.dirstate.parents()
345 if p2 == revlog.nullid:
348 if p2 == revlog.nullid:
346 return p1
349 return p1
347 if len(self.applied) == 0:
350 if len(self.applied) == 0:
348 return None
351 return None
349 return revlog.bin(self.applied[-1].rev)
352 return revlog.bin(self.applied[-1].rev)
350 pp = repo.changelog.parents(rev)
353 pp = repo.changelog.parents(rev)
351 if pp[1] != revlog.nullid:
354 if pp[1] != revlog.nullid:
352 arevs = [ x.rev for x in self.applied ]
355 arevs = [ x.rev for x in self.applied ]
353 p0 = revlog.hex(pp[0])
356 p0 = revlog.hex(pp[0])
354 p1 = revlog.hex(pp[1])
357 p1 = revlog.hex(pp[1])
355 if p0 in arevs:
358 if p0 in arevs:
356 return pp[0]
359 return pp[0]
357 if p1 in arevs:
360 if p1 in arevs:
358 return pp[1]
361 return pp[1]
359 return pp[0]
362 return pp[0]
360
363
361 def mergepatch(self, repo, mergeq, series, wlock):
364 def mergepatch(self, repo, mergeq, series, wlock):
362 if len(self.applied) == 0:
365 if len(self.applied) == 0:
363 # each of the patches merged in will have two parents. This
366 # each of the patches merged in will have two parents. This
364 # can confuse the qrefresh, qdiff, and strip code because it
367 # can confuse the qrefresh, qdiff, and strip code because it
365 # needs to know which parent is actually in the patch queue.
368 # needs to know which parent is actually in the patch queue.
366 # so, we insert a merge marker with only one parent. This way
369 # so, we insert a merge marker with only one parent. This way
367 # the first patch in the queue is never a merge patch
370 # the first patch in the queue is never a merge patch
368 #
371 #
369 pname = ".hg.patches.merge.marker"
372 pname = ".hg.patches.merge.marker"
370 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
373 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
371 wlock=wlock)
374 wlock=wlock)
372 self.applied.append(statusentry(revlog.hex(n), pname))
375 self.applied.append(statusentry(revlog.hex(n), pname))
373 self.applied_dirty = 1
376 self.applied_dirty = 1
374
377
375 head = self.qparents(repo)
378 head = self.qparents(repo)
376
379
377 for patch in series:
380 for patch in series:
378 patch = mergeq.lookup(patch, strict=True)
381 patch = mergeq.lookup(patch, strict=True)
379 if not patch:
382 if not patch:
380 self.ui.warn("patch %s does not exist\n" % patch)
383 self.ui.warn("patch %s does not exist\n" % patch)
381 return (1, None)
384 return (1, None)
382 pushable, reason = self.pushable(patch)
385 pushable, reason = self.pushable(patch)
383 if not pushable:
386 if not pushable:
384 self.explain_pushable(patch, all_patches=True)
387 self.explain_pushable(patch, all_patches=True)
385 continue
388 continue
386 info = mergeq.isapplied(patch)
389 info = mergeq.isapplied(patch)
387 if not info:
390 if not info:
388 self.ui.warn("patch %s is not applied\n" % patch)
391 self.ui.warn("patch %s is not applied\n" % patch)
389 return (1, None)
392 return (1, None)
390 rev = revlog.bin(info[1])
393 rev = revlog.bin(info[1])
391 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
394 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
392 if head:
395 if head:
393 self.applied.append(statusentry(revlog.hex(head), patch))
396 self.applied.append(statusentry(revlog.hex(head), patch))
394 self.applied_dirty = 1
397 self.applied_dirty = 1
395 if err:
398 if err:
396 return (err, head)
399 return (err, head)
397 return (0, head)
400 return (0, head)
398
401
399 def patch(self, repo, patchfile):
402 def patch(self, repo, patchfile):
400 '''Apply patchfile to the working directory.
403 '''Apply patchfile to the working directory.
401 patchfile: file name of patch'''
404 patchfile: file name of patch'''
402 try:
405 try:
403 (files, fuzz) = patch.patch(patchfile, self.ui, strip=1,
406 (files, fuzz) = patch.patch(patchfile, self.ui, strip=1,
404 cwd=repo.root)
407 cwd=repo.root)
405 except Exception, inst:
408 except Exception, inst:
406 self.ui.note(str(inst) + '\n')
409 self.ui.note(str(inst) + '\n')
407 if not self.ui.verbose:
410 if not self.ui.verbose:
408 self.ui.warn("patch failed, unable to continue (try -v)\n")
411 self.ui.warn("patch failed, unable to continue (try -v)\n")
409 return (False, [], False)
412 return (False, [], False)
410
413
411 return (True, files.keys(), fuzz)
414 return (True, files, fuzz)
412
415
413 def apply(self, repo, series, list=False, update_status=True,
416 def apply(self, repo, series, list=False, update_status=True,
414 strict=False, patchdir=None, merge=None, wlock=None):
417 strict=False, patchdir=None, merge=None, wlock=None):
415 # TODO unify with commands.py
418 # TODO unify with commands.py
416 if not patchdir:
419 if not patchdir:
417 patchdir = self.path
420 patchdir = self.path
418 err = 0
421 err = 0
419 if not wlock:
422 if not wlock:
420 wlock = repo.wlock()
423 wlock = repo.wlock()
421 lock = repo.lock()
424 lock = repo.lock()
422 tr = repo.transaction()
425 tr = repo.transaction()
423 n = None
426 n = None
424 for patch in series:
427 for patchname in series:
425 pushable, reason = self.pushable(patch)
428 pushable, reason = self.pushable(patchname)
426 if not pushable:
429 if not pushable:
427 self.explain_pushable(patch, all_patches=True)
430 self.explain_pushable(patchname, all_patches=True)
428 continue
431 continue
429 self.ui.warn("applying %s\n" % patch)
432 self.ui.warn("applying %s\n" % patchname)
430 pf = os.path.join(patchdir, patch)
433 pf = os.path.join(patchdir, patchname)
431
434
432 try:
435 try:
433 message, comments, user, date, patchfound = self.readheaders(patch)
436 message, comments, user, date, patchfound = self.readheaders(patchname)
434 except:
437 except:
435 self.ui.warn("Unable to read %s\n" % pf)
438 self.ui.warn("Unable to read %s\n" % patchname)
436 err = 1
439 err = 1
437 break
440 break
438
441
439 if not message:
442 if not message:
440 message = "imported patch %s\n" % patch
443 message = "imported patch %s\n" % patchname
441 else:
444 else:
442 if list:
445 if list:
443 message.append("\nimported patch %s" % patch)
446 message.append("\nimported patch %s" % patchname)
444 message = '\n'.join(message)
447 message = '\n'.join(message)
445
448
446 (patcherr, files, fuzz) = self.patch(repo, pf)
449 (patcherr, files, fuzz) = self.patch(repo, pf)
447 patcherr = not patcherr
450 patcherr = not patcherr
448
451
449 if merge and len(files) > 0:
452 if merge and files:
450 # Mark as merged and update dirstate parent info
453 # Mark as merged and update dirstate parent info
451 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
454 repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm')
452 p1, p2 = repo.dirstate.parents()
455 p1, p2 = repo.dirstate.parents()
453 repo.dirstate.setparents(p1, merge)
456 repo.dirstate.setparents(p1, merge)
454 if len(files) > 0:
457 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
455 cwd = repo.getcwd()
456 cfiles = files
457 if cwd:
458 cfiles = [util.pathto(cwd, f) for f in files]
459 cmdutil.addremove(repo, cfiles, wlock=wlock)
460 n = repo.commit(files, message, user, date, force=1, lock=lock,
458 n = repo.commit(files, message, user, date, force=1, lock=lock,
461 wlock=wlock)
459 wlock=wlock)
462
460
463 if n == None:
461 if n == None:
464 raise util.Abort(_("repo commit failed"))
462 raise util.Abort(_("repo commit failed"))
465
463
466 if update_status:
464 if update_status:
467 self.applied.append(statusentry(revlog.hex(n), patch))
465 self.applied.append(statusentry(revlog.hex(n), patchname))
468
466
469 if patcherr:
467 if patcherr:
470 if not patchfound:
468 if not patchfound:
471 self.ui.warn("patch %s is empty\n" % patch)
469 self.ui.warn("patch %s is empty\n" % patchname)
472 err = 0
470 err = 0
473 else:
471 else:
474 self.ui.warn("patch failed, rejects left in working dir\n")
472 self.ui.warn("patch failed, rejects left in working dir\n")
475 err = 1
473 err = 1
476 break
474 break
477
475
478 if fuzz and strict:
476 if fuzz and strict:
479 self.ui.warn("fuzz found when applying patch, stopping\n")
477 self.ui.warn("fuzz found when applying patch, stopping\n")
480 err = 1
478 err = 1
481 break
479 break
482 tr.close()
480 tr.close()
483 return (err, n)
481 return (err, n)
484
482
485 def delete(self, repo, patches, keep=False):
483 def delete(self, repo, patches, keep=False):
486 realpatches = []
484 realpatches = []
487 for patch in patches:
485 for patch in patches:
488 patch = self.lookup(patch, strict=True)
486 patch = self.lookup(patch, strict=True)
489 info = self.isapplied(patch)
487 info = self.isapplied(patch)
490 if info:
488 if info:
491 raise util.Abort(_("cannot delete applied patch %s") % patch)
489 raise util.Abort(_("cannot delete applied patch %s") % patch)
492 if patch not in self.series:
490 if patch not in self.series:
493 raise util.Abort(_("patch %s not in series file") % patch)
491 raise util.Abort(_("patch %s not in series file") % patch)
494 realpatches.append(patch)
492 realpatches.append(patch)
495
493
496 if not keep:
494 if not keep:
497 r = self.qrepo()
495 r = self.qrepo()
498 if r:
496 if r:
499 r.remove(realpatches, True)
497 r.remove(realpatches, True)
500 else:
498 else:
501 os.unlink(self.join(patch))
499 os.unlink(self.join(patch))
502
500
503 indices = [self.find_series(p) for p in realpatches]
501 indices = [self.find_series(p) for p in realpatches]
504 indices.sort()
502 indices.sort()
505 for i in indices[-1::-1]:
503 for i in indices[-1::-1]:
506 del self.full_series[i]
504 del self.full_series[i]
507 self.parse_series()
505 self.parse_series()
508 self.series_dirty = 1
506 self.series_dirty = 1
509
507
510 def check_toppatch(self, repo):
508 def check_toppatch(self, repo):
511 if len(self.applied) > 0:
509 if len(self.applied) > 0:
512 top = revlog.bin(self.applied[-1].rev)
510 top = revlog.bin(self.applied[-1].rev)
513 pp = repo.dirstate.parents()
511 pp = repo.dirstate.parents()
514 if top not in pp:
512 if top not in pp:
515 raise util.Abort(_("queue top not at same revision as working directory"))
513 raise util.Abort(_("queue top not at same revision as working directory"))
516 return top
514 return top
517 return None
515 return None
518 def check_localchanges(self, repo, force=False, refresh=True):
516 def check_localchanges(self, repo, force=False, refresh=True):
519 m, a, r, d = repo.status()[:4]
517 m, a, r, d = repo.status()[:4]
520 if m or a or r or d:
518 if m or a or r or d:
521 if not force:
519 if not force:
522 if refresh:
520 if refresh:
523 raise util.Abort(_("local changes found, refresh first"))
521 raise util.Abort(_("local changes found, refresh first"))
524 else:
522 else:
525 raise util.Abort(_("local changes found"))
523 raise util.Abort(_("local changes found"))
526 return m, a, r, d
524 return m, a, r, d
527 def new(self, repo, patch, msg=None, force=None):
525 def new(self, repo, patch, msg=None, force=None):
528 if os.path.exists(self.join(patch)):
526 if os.path.exists(self.join(patch)):
529 raise util.Abort(_('patch "%s" already exists') % patch)
527 raise util.Abort(_('patch "%s" already exists') % patch)
530 m, a, r, d = self.check_localchanges(repo, force)
528 m, a, r, d = self.check_localchanges(repo, force)
531 commitfiles = m + a + r
529 commitfiles = m + a + r
532 self.check_toppatch(repo)
530 self.check_toppatch(repo)
533 wlock = repo.wlock()
531 wlock = repo.wlock()
534 insert = self.full_series_end()
532 insert = self.full_series_end()
535 if msg:
533 if msg:
536 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
534 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
537 wlock=wlock)
535 wlock=wlock)
538 else:
536 else:
539 n = repo.commit(commitfiles,
537 n = repo.commit(commitfiles,
540 "New patch: %s" % patch, force=True, wlock=wlock)
538 "New patch: %s" % patch, force=True, wlock=wlock)
541 if n == None:
539 if n == None:
542 raise util.Abort(_("repo commit failed"))
540 raise util.Abort(_("repo commit failed"))
543 self.full_series[insert:insert] = [patch]
541 self.full_series[insert:insert] = [patch]
544 self.applied.append(statusentry(revlog.hex(n), patch))
542 self.applied.append(statusentry(revlog.hex(n), patch))
545 self.parse_series()
543 self.parse_series()
546 self.series_dirty = 1
544 self.series_dirty = 1
547 self.applied_dirty = 1
545 self.applied_dirty = 1
548 p = self.opener(patch, "w")
546 p = self.opener(patch, "w")
549 if msg:
547 if msg:
550 msg = msg + "\n"
548 msg = msg + "\n"
551 p.write(msg)
549 p.write(msg)
552 p.close()
550 p.close()
553 wlock = None
551 wlock = None
554 r = self.qrepo()
552 r = self.qrepo()
555 if r: r.add([patch])
553 if r: r.add([patch])
556 if commitfiles:
554 if commitfiles:
557 self.refresh(repo, short=True)
555 self.refresh(repo, short=True)
558
556
559 def strip(self, repo, rev, update=True, backup="all", wlock=None):
557 def strip(self, repo, rev, update=True, backup="all", wlock=None):
560 def limitheads(chlog, stop):
558 def limitheads(chlog, stop):
561 """return the list of all nodes that have no children"""
559 """return the list of all nodes that have no children"""
562 p = {}
560 p = {}
563 h = []
561 h = []
564 stoprev = 0
562 stoprev = 0
565 if stop in chlog.nodemap:
563 if stop in chlog.nodemap:
566 stoprev = chlog.rev(stop)
564 stoprev = chlog.rev(stop)
567
565
568 for r in range(chlog.count() - 1, -1, -1):
566 for r in range(chlog.count() - 1, -1, -1):
569 n = chlog.node(r)
567 n = chlog.node(r)
570 if n not in p:
568 if n not in p:
571 h.append(n)
569 h.append(n)
572 if n == stop:
570 if n == stop:
573 break
571 break
574 if r < stoprev:
572 if r < stoprev:
575 break
573 break
576 for pn in chlog.parents(n):
574 for pn in chlog.parents(n):
577 p[pn] = 1
575 p[pn] = 1
578 return h
576 return h
579
577
580 def bundle(cg):
578 def bundle(cg):
581 backupdir = repo.join("strip-backup")
579 backupdir = repo.join("strip-backup")
582 if not os.path.isdir(backupdir):
580 if not os.path.isdir(backupdir):
583 os.mkdir(backupdir)
581 os.mkdir(backupdir)
584 name = os.path.join(backupdir, "%s" % revlog.short(rev))
582 name = os.path.join(backupdir, "%s" % revlog.short(rev))
585 name = savename(name)
583 name = savename(name)
586 self.ui.warn("saving bundle to %s\n" % name)
584 self.ui.warn("saving bundle to %s\n" % name)
587 # TODO, exclusive open
585 # TODO, exclusive open
588 f = open(name, "wb")
586 f = open(name, "wb")
589 try:
587 try:
590 f.write("HG10")
588 f.write("HG10")
591 z = bz2.BZ2Compressor(9)
589 z = bz2.BZ2Compressor(9)
592 while 1:
590 while 1:
593 chunk = cg.read(4096)
591 chunk = cg.read(4096)
594 if not chunk:
592 if not chunk:
595 break
593 break
596 f.write(z.compress(chunk))
594 f.write(z.compress(chunk))
597 f.write(z.flush())
595 f.write(z.flush())
598 except:
596 except:
599 os.unlink(name)
597 os.unlink(name)
600 raise
598 raise
601 f.close()
599 f.close()
602 return name
600 return name
603
601
604 def stripall(rev, revnum):
602 def stripall(rev, revnum):
605 cl = repo.changelog
603 cl = repo.changelog
606 c = cl.read(rev)
604 c = cl.read(rev)
607 mm = repo.manifest.read(c[0])
605 mm = repo.manifest.read(c[0])
608 seen = {}
606 seen = {}
609
607
610 for x in xrange(revnum, cl.count()):
608 for x in xrange(revnum, cl.count()):
611 c = cl.read(cl.node(x))
609 c = cl.read(cl.node(x))
612 for f in c[3]:
610 for f in c[3]:
613 if f in seen:
611 if f in seen:
614 continue
612 continue
615 seen[f] = 1
613 seen[f] = 1
616 if f in mm:
614 if f in mm:
617 filerev = mm[f]
615 filerev = mm[f]
618 else:
616 else:
619 filerev = 0
617 filerev = 0
620 seen[f] = filerev
618 seen[f] = filerev
621 # we go in two steps here so the strip loop happens in a
619 # we go in two steps here so the strip loop happens in a
622 # sensible order. When stripping many files, this helps keep
620 # sensible order. When stripping many files, this helps keep
623 # our disk access patterns under control.
621 # our disk access patterns under control.
624 seen_list = seen.keys()
622 seen_list = seen.keys()
625 seen_list.sort()
623 seen_list.sort()
626 for f in seen_list:
624 for f in seen_list:
627 ff = repo.file(f)
625 ff = repo.file(f)
628 filerev = seen[f]
626 filerev = seen[f]
629 if filerev != 0:
627 if filerev != 0:
630 if filerev in ff.nodemap:
628 if filerev in ff.nodemap:
631 filerev = ff.rev(filerev)
629 filerev = ff.rev(filerev)
632 else:
630 else:
633 filerev = 0
631 filerev = 0
634 ff.strip(filerev, revnum)
632 ff.strip(filerev, revnum)
635
633
636 if not wlock:
634 if not wlock:
637 wlock = repo.wlock()
635 wlock = repo.wlock()
638 lock = repo.lock()
636 lock = repo.lock()
639 chlog = repo.changelog
637 chlog = repo.changelog
640 # TODO delete the undo files, and handle undo of merge sets
638 # TODO delete the undo files, and handle undo of merge sets
641 pp = chlog.parents(rev)
639 pp = chlog.parents(rev)
642 revnum = chlog.rev(rev)
640 revnum = chlog.rev(rev)
643
641
644 if update:
642 if update:
645 self.check_localchanges(repo, refresh=False)
643 self.check_localchanges(repo, refresh=False)
646 urev = self.qparents(repo, rev)
644 urev = self.qparents(repo, rev)
647 hg.clean(repo, urev, wlock=wlock)
645 hg.clean(repo, urev, wlock=wlock)
648 repo.dirstate.write()
646 repo.dirstate.write()
649
647
650 # save is a list of all the branches we are truncating away
648 # save is a list of all the branches we are truncating away
651 # that we actually want to keep. changegroup will be used
649 # that we actually want to keep. changegroup will be used
652 # to preserve them and add them back after the truncate
650 # to preserve them and add them back after the truncate
653 saveheads = []
651 saveheads = []
654 savebases = {}
652 savebases = {}
655
653
656 heads = limitheads(chlog, rev)
654 heads = limitheads(chlog, rev)
657 seen = {}
655 seen = {}
658
656
659 # search through all the heads, finding those where the revision
657 # search through all the heads, finding those where the revision
660 # we want to strip away is an ancestor. Also look for merges
658 # we want to strip away is an ancestor. Also look for merges
661 # that might be turned into new heads by the strip.
659 # that might be turned into new heads by the strip.
662 while heads:
660 while heads:
663 h = heads.pop()
661 h = heads.pop()
664 n = h
662 n = h
665 while True:
663 while True:
666 seen[n] = 1
664 seen[n] = 1
667 pp = chlog.parents(n)
665 pp = chlog.parents(n)
668 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
666 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
669 if pp[1] not in seen:
667 if pp[1] not in seen:
670 heads.append(pp[1])
668 heads.append(pp[1])
671 if pp[0] == revlog.nullid:
669 if pp[0] == revlog.nullid:
672 break
670 break
673 if chlog.rev(pp[0]) < revnum:
671 if chlog.rev(pp[0]) < revnum:
674 break
672 break
675 n = pp[0]
673 n = pp[0]
676 if n == rev:
674 if n == rev:
677 break
675 break
678 r = chlog.reachable(h, rev)
676 r = chlog.reachable(h, rev)
679 if rev not in r:
677 if rev not in r:
680 saveheads.append(h)
678 saveheads.append(h)
681 for x in r:
679 for x in r:
682 if chlog.rev(x) > revnum:
680 if chlog.rev(x) > revnum:
683 savebases[x] = 1
681 savebases[x] = 1
684
682
685 # create a changegroup for all the branches we need to keep
683 # create a changegroup for all the branches we need to keep
686 if backup == "all":
684 if backup == "all":
687 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
685 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
688 bundle(backupch)
686 bundle(backupch)
689 if saveheads:
687 if saveheads:
690 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
688 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
691 chgrpfile = bundle(backupch)
689 chgrpfile = bundle(backupch)
692
690
693 stripall(rev, revnum)
691 stripall(rev, revnum)
694
692
695 change = chlog.read(rev)
693 change = chlog.read(rev)
696 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
694 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
697 chlog.strip(revnum, revnum)
695 chlog.strip(revnum, revnum)
698 if saveheads:
696 if saveheads:
699 self.ui.status("adding branch\n")
697 self.ui.status("adding branch\n")
700 commands.unbundle(self.ui, repo, chgrpfile, update=False)
698 commands.unbundle(self.ui, repo, chgrpfile, update=False)
701 if backup != "strip":
699 if backup != "strip":
702 os.unlink(chgrpfile)
700 os.unlink(chgrpfile)
703
701
704 def isapplied(self, patch):
702 def isapplied(self, patch):
705 """returns (index, rev, patch)"""
703 """returns (index, rev, patch)"""
706 for i in xrange(len(self.applied)):
704 for i in xrange(len(self.applied)):
707 a = self.applied[i]
705 a = self.applied[i]
708 if a.name == patch:
706 if a.name == patch:
709 return (i, a.rev, a.name)
707 return (i, a.rev, a.name)
710 return None
708 return None
711
709
712 # if the exact patch name does not exist, we try a few
710 # if the exact patch name does not exist, we try a few
713 # variations. If strict is passed, we try only #1
711 # variations. If strict is passed, we try only #1
714 #
712 #
715 # 1) a number to indicate an offset in the series file
713 # 1) a number to indicate an offset in the series file
716 # 2) a unique substring of the patch name was given
714 # 2) a unique substring of the patch name was given
717 # 3) patchname[-+]num to indicate an offset in the series file
715 # 3) patchname[-+]num to indicate an offset in the series file
718 def lookup(self, patch, strict=False):
716 def lookup(self, patch, strict=False):
719 patch = patch and str(patch)
717 patch = patch and str(patch)
720
718
721 def partial_name(s):
719 def partial_name(s):
722 if s in self.series:
720 if s in self.series:
723 return s
721 return s
724 matches = [x for x in self.series if s in x]
722 matches = [x for x in self.series if s in x]
725 if len(matches) > 1:
723 if len(matches) > 1:
726 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
724 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
727 for m in matches:
725 for m in matches:
728 self.ui.warn(' %s\n' % m)
726 self.ui.warn(' %s\n' % m)
729 return None
727 return None
730 if matches:
728 if matches:
731 return matches[0]
729 return matches[0]
732 if len(self.series) > 0 and len(self.applied) > 0:
730 if len(self.series) > 0 and len(self.applied) > 0:
733 if s == 'qtip':
731 if s == 'qtip':
734 return self.series[self.series_end()-1]
732 return self.series[self.series_end()-1]
735 if s == 'qbase':
733 if s == 'qbase':
736 return self.series[0]
734 return self.series[0]
737 return None
735 return None
738 if patch == None:
736 if patch == None:
739 return None
737 return None
740
738
741 # we don't want to return a partial match until we make
739 # we don't want to return a partial match until we make
742 # sure the file name passed in does not exist (checked below)
740 # sure the file name passed in does not exist (checked below)
743 res = partial_name(patch)
741 res = partial_name(patch)
744 if res and res == patch:
742 if res and res == patch:
745 return res
743 return res
746
744
747 if not os.path.isfile(self.join(patch)):
745 if not os.path.isfile(self.join(patch)):
748 try:
746 try:
749 sno = int(patch)
747 sno = int(patch)
750 except(ValueError, OverflowError):
748 except(ValueError, OverflowError):
751 pass
749 pass
752 else:
750 else:
753 if sno < len(self.series):
751 if sno < len(self.series):
754 return self.series[sno]
752 return self.series[sno]
755 if not strict:
753 if not strict:
756 # return any partial match made above
754 # return any partial match made above
757 if res:
755 if res:
758 return res
756 return res
759 minus = patch.rsplit('-', 1)
757 minus = patch.rsplit('-', 1)
760 if len(minus) > 1:
758 if len(minus) > 1:
761 res = partial_name(minus[0])
759 res = partial_name(minus[0])
762 if res:
760 if res:
763 i = self.series.index(res)
761 i = self.series.index(res)
764 try:
762 try:
765 off = int(minus[1] or 1)
763 off = int(minus[1] or 1)
766 except(ValueError, OverflowError):
764 except(ValueError, OverflowError):
767 pass
765 pass
768 else:
766 else:
769 if i - off >= 0:
767 if i - off >= 0:
770 return self.series[i - off]
768 return self.series[i - off]
771 plus = patch.rsplit('+', 1)
769 plus = patch.rsplit('+', 1)
772 if len(plus) > 1:
770 if len(plus) > 1:
773 res = partial_name(plus[0])
771 res = partial_name(plus[0])
774 if res:
772 if res:
775 i = self.series.index(res)
773 i = self.series.index(res)
776 try:
774 try:
777 off = int(plus[1] or 1)
775 off = int(plus[1] or 1)
778 except(ValueError, OverflowError):
776 except(ValueError, OverflowError):
779 pass
777 pass
780 else:
778 else:
781 if i + off < len(self.series):
779 if i + off < len(self.series):
782 return self.series[i + off]
780 return self.series[i + off]
783 raise util.Abort(_("patch %s not in series") % patch)
781 raise util.Abort(_("patch %s not in series") % patch)
784
782
785 def push(self, repo, patch=None, force=False, list=False,
783 def push(self, repo, patch=None, force=False, list=False,
786 mergeq=None, wlock=None):
784 mergeq=None, wlock=None):
787 if not wlock:
785 if not wlock:
788 wlock = repo.wlock()
786 wlock = repo.wlock()
789 patch = self.lookup(patch)
787 patch = self.lookup(patch)
790 if patch and self.isapplied(patch):
788 if patch and self.isapplied(patch):
791 self.ui.warn(_("patch %s is already applied\n") % patch)
789 self.ui.warn(_("patch %s is already applied\n") % patch)
792 sys.exit(1)
790 sys.exit(1)
793 if self.series_end() == len(self.series):
791 if self.series_end() == len(self.series):
794 self.ui.warn(_("patch series fully applied\n"))
792 self.ui.warn(_("patch series fully applied\n"))
795 sys.exit(1)
793 sys.exit(1)
796 if not force:
794 if not force:
797 self.check_localchanges(repo)
795 self.check_localchanges(repo)
798
796
799 self.applied_dirty = 1;
797 self.applied_dirty = 1;
800 start = self.series_end()
798 start = self.series_end()
801 if start > 0:
799 if start > 0:
802 self.check_toppatch(repo)
800 self.check_toppatch(repo)
803 if not patch:
801 if not patch:
804 patch = self.series[start]
802 patch = self.series[start]
805 end = start + 1
803 end = start + 1
806 else:
804 else:
807 end = self.series.index(patch, start) + 1
805 end = self.series.index(patch, start) + 1
808 s = self.series[start:end]
806 s = self.series[start:end]
809 if mergeq:
807 if mergeq:
810 ret = self.mergepatch(repo, mergeq, s, wlock)
808 ret = self.mergepatch(repo, mergeq, s, wlock)
811 else:
809 else:
812 ret = self.apply(repo, s, list, wlock=wlock)
810 ret = self.apply(repo, s, list, wlock=wlock)
813 top = self.applied[-1].name
811 top = self.applied[-1].name
814 if ret[0]:
812 if ret[0]:
815 self.ui.write("Errors during apply, please fix and refresh %s\n" %
813 self.ui.write("Errors during apply, please fix and refresh %s\n" %
816 top)
814 top)
817 else:
815 else:
818 self.ui.write("Now at: %s\n" % top)
816 self.ui.write("Now at: %s\n" % top)
819 return ret[0]
817 return ret[0]
820
818
821 def pop(self, repo, patch=None, force=False, update=True, all=False,
819 def pop(self, repo, patch=None, force=False, update=True, all=False,
822 wlock=None):
820 wlock=None):
823 def getfile(f, rev):
821 def getfile(f, rev):
824 t = repo.file(f).read(rev)
822 t = repo.file(f).read(rev)
825 try:
823 try:
826 repo.wfile(f, "w").write(t)
824 repo.wfile(f, "w").write(t)
827 except IOError:
825 except IOError:
828 try:
826 try:
829 os.makedirs(os.path.dirname(repo.wjoin(f)))
827 os.makedirs(os.path.dirname(repo.wjoin(f)))
830 except OSError, err:
828 except OSError, err:
831 if err.errno != errno.EEXIST: raise
829 if err.errno != errno.EEXIST: raise
832 repo.wfile(f, "w").write(t)
830 repo.wfile(f, "w").write(t)
833
831
834 if not wlock:
832 if not wlock:
835 wlock = repo.wlock()
833 wlock = repo.wlock()
836 if patch:
834 if patch:
837 # index, rev, patch
835 # index, rev, patch
838 info = self.isapplied(patch)
836 info = self.isapplied(patch)
839 if not info:
837 if not info:
840 patch = self.lookup(patch)
838 patch = self.lookup(patch)
841 info = self.isapplied(patch)
839 info = self.isapplied(patch)
842 if not info:
840 if not info:
843 raise util.Abort(_("patch %s is not applied") % patch)
841 raise util.Abort(_("patch %s is not applied") % patch)
844 if len(self.applied) == 0:
842 if len(self.applied) == 0:
845 self.ui.warn(_("no patches applied\n"))
843 self.ui.warn(_("no patches applied\n"))
846 sys.exit(1)
844 sys.exit(1)
847
845
848 if not update:
846 if not update:
849 parents = repo.dirstate.parents()
847 parents = repo.dirstate.parents()
850 rr = [ revlog.bin(x.rev) for x in self.applied ]
848 rr = [ revlog.bin(x.rev) for x in self.applied ]
851 for p in parents:
849 for p in parents:
852 if p in rr:
850 if p in rr:
853 self.ui.warn("qpop: forcing dirstate update\n")
851 self.ui.warn("qpop: forcing dirstate update\n")
854 update = True
852 update = True
855
853
856 if not force and update:
854 if not force and update:
857 self.check_localchanges(repo)
855 self.check_localchanges(repo)
858
856
859 self.applied_dirty = 1;
857 self.applied_dirty = 1;
860 end = len(self.applied)
858 end = len(self.applied)
861 if not patch:
859 if not patch:
862 if all:
860 if all:
863 popi = 0
861 popi = 0
864 else:
862 else:
865 popi = len(self.applied) - 1
863 popi = len(self.applied) - 1
866 else:
864 else:
867 popi = info[0] + 1
865 popi = info[0] + 1
868 if popi >= end:
866 if popi >= end:
869 self.ui.warn("qpop: %s is already at the top\n" % patch)
867 self.ui.warn("qpop: %s is already at the top\n" % patch)
870 return
868 return
871 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
869 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
872
870
873 start = info[0]
871 start = info[0]
874 rev = revlog.bin(info[1])
872 rev = revlog.bin(info[1])
875
873
876 # we know there are no local changes, so we can make a simplified
874 # we know there are no local changes, so we can make a simplified
877 # form of hg.update.
875 # form of hg.update.
878 if update:
876 if update:
879 top = self.check_toppatch(repo)
877 top = self.check_toppatch(repo)
880 qp = self.qparents(repo, rev)
878 qp = self.qparents(repo, rev)
881 changes = repo.changelog.read(qp)
879 changes = repo.changelog.read(qp)
882 mmap = repo.manifest.read(changes[0])
880 mmap = repo.manifest.read(changes[0])
883 m, a, r, d, u = repo.status(qp, top)[:5]
881 m, a, r, d, u = repo.status(qp, top)[:5]
884 if d:
882 if d:
885 raise util.Abort("deletions found between repo revs")
883 raise util.Abort("deletions found between repo revs")
886 for f in m:
884 for f in m:
887 getfile(f, mmap[f])
885 getfile(f, mmap[f])
888 for f in r:
886 for f in r:
889 getfile(f, mmap[f])
887 getfile(f, mmap[f])
890 util.set_exec(repo.wjoin(f), mmap.execf(f))
888 util.set_exec(repo.wjoin(f), mmap.execf(f))
891 repo.dirstate.update(m + r, 'n')
889 repo.dirstate.update(m + r, 'n')
892 for f in a:
890 for f in a:
893 try: os.unlink(repo.wjoin(f))
891 try: os.unlink(repo.wjoin(f))
894 except: raise
892 except: raise
895 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
893 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
896 except: pass
894 except: pass
897 if a:
895 if a:
898 repo.dirstate.forget(a)
896 repo.dirstate.forget(a)
899 repo.dirstate.setparents(qp, revlog.nullid)
897 repo.dirstate.setparents(qp, revlog.nullid)
900 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
898 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
901 del self.applied[start:end]
899 del self.applied[start:end]
902 if len(self.applied):
900 if len(self.applied):
903 self.ui.write("Now at: %s\n" % self.applied[-1].name)
901 self.ui.write("Now at: %s\n" % self.applied[-1].name)
904 else:
902 else:
905 self.ui.write("Patch queue now empty\n")
903 self.ui.write("Patch queue now empty\n")
906
904
907 def diff(self, repo, files):
905 def diff(self, repo, files):
908 top = self.check_toppatch(repo)
906 top = self.check_toppatch(repo)
909 if not top:
907 if not top:
910 self.ui.write("No patches applied\n")
908 self.ui.write("No patches applied\n")
911 return
909 return
912 qp = self.qparents(repo, top)
910 qp = self.qparents(repo, top)
913 self.printdiff(repo, qp, files=files)
911 self.printdiff(repo, qp, files=files)
914
912
915 def refresh(self, repo, msg='', short=False):
913 def refresh(self, repo, msg='', short=False):
916 if len(self.applied) == 0:
914 if len(self.applied) == 0:
917 self.ui.write("No patches applied\n")
915 self.ui.write("No patches applied\n")
918 return
916 return
919 wlock = repo.wlock()
917 wlock = repo.wlock()
920 self.check_toppatch(repo)
918 self.check_toppatch(repo)
921 (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
919 (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
922 top = revlog.bin(top)
920 top = revlog.bin(top)
923 cparents = repo.changelog.parents(top)
921 cparents = repo.changelog.parents(top)
924 patchparent = self.qparents(repo, top)
922 patchparent = self.qparents(repo, top)
925 message, comments, user, date, patchfound = self.readheaders(patch)
923 message, comments, user, date, patchfound = self.readheaders(patch)
926
924
927 patchf = self.opener(patch, "w")
925 patchf = self.opener(patch, "w")
928 msg = msg.rstrip()
926 msg = msg.rstrip()
929 if msg:
927 if msg:
930 if comments:
928 if comments:
931 # Remove existing message.
929 # Remove existing message.
932 ci = 0
930 ci = 0
933 for mi in range(len(message)):
931 for mi in range(len(message)):
934 while message[mi] != comments[ci]:
932 while message[mi] != comments[ci]:
935 ci += 1
933 ci += 1
936 del comments[ci]
934 del comments[ci]
937 comments.append(msg)
935 comments.append(msg)
938 if comments:
936 if comments:
939 comments = "\n".join(comments) + '\n\n'
937 comments = "\n".join(comments) + '\n\n'
940 patchf.write(comments)
938 patchf.write(comments)
941
939
942 tip = repo.changelog.tip()
940 tip = repo.changelog.tip()
943 if top == tip:
941 if top == tip:
944 # if the top of our patch queue is also the tip, there is an
942 # if the top of our patch queue is also the tip, there is an
945 # optimization here. We update the dirstate in place and strip
943 # optimization here. We update the dirstate in place and strip
946 # off the tip commit. Then just commit the current directory
944 # off the tip commit. Then just commit the current directory
947 # tree. We can also send repo.commit the list of files
945 # tree. We can also send repo.commit the list of files
948 # changed to speed up the diff
946 # changed to speed up the diff
949 #
947 #
950 # in short mode, we only diff the files included in the
948 # in short mode, we only diff the files included in the
951 # patch already
949 # patch already
952 #
950 #
953 # this should really read:
951 # this should really read:
954 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
952 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
955 # but we do it backwards to take advantage of manifest/chlog
953 # but we do it backwards to take advantage of manifest/chlog
956 # caching against the next repo.status call
954 # caching against the next repo.status call
957 #
955 #
958 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
956 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
959 if short:
957 if short:
960 filelist = mm + aa + dd
958 filelist = mm + aa + dd
961 else:
959 else:
962 filelist = None
960 filelist = None
963 m, a, r, d, u = repo.status(files=filelist)[:5]
961 m, a, r, d, u = repo.status(files=filelist)[:5]
964
962
965 # we might end up with files that were added between tip and
963 # we might end up with files that were added between tip and
966 # the dirstate parent, but then changed in the local dirstate.
964 # the dirstate parent, but then changed in the local dirstate.
967 # in this case, we want them to only show up in the added section
965 # in this case, we want them to only show up in the added section
968 for x in m:
966 for x in m:
969 if x not in aa:
967 if x not in aa:
970 mm.append(x)
968 mm.append(x)
971 # we might end up with files added by the local dirstate that
969 # we might end up with files added by the local dirstate that
972 # were deleted by the patch. In this case, they should only
970 # were deleted by the patch. In this case, they should only
973 # show up in the changed section.
971 # show up in the changed section.
974 for x in a:
972 for x in a:
975 if x in dd:
973 if x in dd:
976 del dd[dd.index(x)]
974 del dd[dd.index(x)]
977 mm.append(x)
975 mm.append(x)
978 else:
976 else:
979 aa.append(x)
977 aa.append(x)
980 # make sure any files deleted in the local dirstate
978 # make sure any files deleted in the local dirstate
981 # are not in the add or change column of the patch
979 # are not in the add or change column of the patch
982 forget = []
980 forget = []
983 for x in d + r:
981 for x in d + r:
984 if x in aa:
982 if x in aa:
985 del aa[aa.index(x)]
983 del aa[aa.index(x)]
986 forget.append(x)
984 forget.append(x)
987 continue
985 continue
988 elif x in mm:
986 elif x in mm:
989 del mm[mm.index(x)]
987 del mm[mm.index(x)]
990 dd.append(x)
988 dd.append(x)
991
989
992 m = list(util.unique(mm))
990 m = list(util.unique(mm))
993 r = list(util.unique(dd))
991 r = list(util.unique(dd))
994 a = list(util.unique(aa))
992 a = list(util.unique(aa))
995 filelist = list(util.unique(m + r + a))
993 filelist = list(util.unique(m + r + a))
996 self.printdiff(repo, patchparent, files=filelist,
994 self.printdiff(repo, patchparent, files=filelist,
997 changes=(m, a, r, [], u), fp=patchf)
995 changes=(m, a, r, [], u), fp=patchf)
998 patchf.close()
996 patchf.close()
999
997
1000 changes = repo.changelog.read(tip)
998 changes = repo.changelog.read(tip)
1001 repo.dirstate.setparents(*cparents)
999 repo.dirstate.setparents(*cparents)
1000 copies = [(f, repo.dirstate.copied(f)) for f in a]
1002 repo.dirstate.update(a, 'a')
1001 repo.dirstate.update(a, 'a')
1002 for dst, src in copies:
1003 repo.dirstate.copy(src, dst)
1003 repo.dirstate.update(r, 'r')
1004 repo.dirstate.update(r, 'r')
1004 repo.dirstate.update(m, 'n')
1005 repo.dirstate.update(m, 'n')
1005 repo.dirstate.forget(forget)
1006 repo.dirstate.forget(forget)
1006
1007
1007 if not msg:
1008 if not msg:
1008 if not message:
1009 if not message:
1009 message = "patch queue: %s\n" % patch
1010 message = "patch queue: %s\n" % patch
1010 else:
1011 else:
1011 message = "\n".join(message)
1012 message = "\n".join(message)
1012 else:
1013 else:
1013 message = msg
1014 message = msg
1014
1015
1015 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1016 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1016 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1017 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1017 self.applied[-1] = statusentry(revlog.hex(n), patch)
1018 self.applied[-1] = statusentry(revlog.hex(n), patch)
1018 self.applied_dirty = 1
1019 self.applied_dirty = 1
1019 else:
1020 else:
1020 self.printdiff(repo, patchparent, fp=patchf)
1021 self.printdiff(repo, patchparent, fp=patchf)
1021 patchf.close()
1022 patchf.close()
1022 self.pop(repo, force=True, wlock=wlock)
1023 self.pop(repo, force=True, wlock=wlock)
1023 self.push(repo, force=True, wlock=wlock)
1024 self.push(repo, force=True, wlock=wlock)
1024
1025
1025 def init(self, repo, create=False):
1026 def init(self, repo, create=False):
1026 if os.path.isdir(self.path):
1027 if os.path.isdir(self.path):
1027 raise util.Abort(_("patch queue directory already exists"))
1028 raise util.Abort(_("patch queue directory already exists"))
1028 os.mkdir(self.path)
1029 os.mkdir(self.path)
1029 if create:
1030 if create:
1030 return self.qrepo(create=True)
1031 return self.qrepo(create=True)
1031
1032
1032 def unapplied(self, repo, patch=None):
1033 def unapplied(self, repo, patch=None):
1033 if patch and patch not in self.series:
1034 if patch and patch not in self.series:
1034 raise util.Abort(_("patch %s is not in series file") % patch)
1035 raise util.Abort(_("patch %s is not in series file") % patch)
1035 if not patch:
1036 if not patch:
1036 start = self.series_end()
1037 start = self.series_end()
1037 else:
1038 else:
1038 start = self.series.index(patch) + 1
1039 start = self.series.index(patch) + 1
1039 unapplied = []
1040 unapplied = []
1040 for i in xrange(start, len(self.series)):
1041 for i in xrange(start, len(self.series)):
1041 pushable, reason = self.pushable(i)
1042 pushable, reason = self.pushable(i)
1042 if pushable:
1043 if pushable:
1043 unapplied.append((i, self.series[i]))
1044 unapplied.append((i, self.series[i]))
1044 self.explain_pushable(i)
1045 self.explain_pushable(i)
1045 return unapplied
1046 return unapplied
1046
1047
1047 def qseries(self, repo, missing=None, summary=False):
1048 def qseries(self, repo, missing=None, summary=False):
1048 start = self.series_end(all_patches=True)
1049 start = self.series_end(all_patches=True)
1049 if not missing:
1050 if not missing:
1050 for i in range(len(self.series)):
1051 for i in range(len(self.series)):
1051 patch = self.series[i]
1052 patch = self.series[i]
1052 if self.ui.verbose:
1053 if self.ui.verbose:
1053 if i < start:
1054 if i < start:
1054 status = 'A'
1055 status = 'A'
1055 elif self.pushable(i)[0]:
1056 elif self.pushable(i)[0]:
1056 status = 'U'
1057 status = 'U'
1057 else:
1058 else:
1058 status = 'G'
1059 status = 'G'
1059 self.ui.write('%d %s ' % (i, status))
1060 self.ui.write('%d %s ' % (i, status))
1060 if summary:
1061 if summary:
1061 msg = self.readheaders(patch)[0]
1062 msg = self.readheaders(patch)[0]
1062 msg = msg and ': ' + msg[0] or ': '
1063 msg = msg and ': ' + msg[0] or ': '
1063 else:
1064 else:
1064 msg = ''
1065 msg = ''
1065 self.ui.write('%s%s\n' % (patch, msg))
1066 self.ui.write('%s%s\n' % (patch, msg))
1066 else:
1067 else:
1067 msng_list = []
1068 msng_list = []
1068 for root, dirs, files in os.walk(self.path):
1069 for root, dirs, files in os.walk(self.path):
1069 d = root[len(self.path) + 1:]
1070 d = root[len(self.path) + 1:]
1070 for f in files:
1071 for f in files:
1071 fl = os.path.join(d, f)
1072 fl = os.path.join(d, f)
1072 if (fl not in self.series and
1073 if (fl not in self.series and
1073 fl not in (self.status_path, self.series_path)
1074 fl not in (self.status_path, self.series_path)
1074 and not fl.startswith('.')):
1075 and not fl.startswith('.')):
1075 msng_list.append(fl)
1076 msng_list.append(fl)
1076 msng_list.sort()
1077 msng_list.sort()
1077 for x in msng_list:
1078 for x in msng_list:
1078 if self.ui.verbose:
1079 if self.ui.verbose:
1079 self.ui.write("D ")
1080 self.ui.write("D ")
1080 self.ui.write("%s\n" % x)
1081 self.ui.write("%s\n" % x)
1081
1082
1082 def issaveline(self, l):
1083 def issaveline(self, l):
1083 if l.name == '.hg.patches.save.line':
1084 if l.name == '.hg.patches.save.line':
1084 return True
1085 return True
1085
1086
1086 def qrepo(self, create=False):
1087 def qrepo(self, create=False):
1087 if create or os.path.isdir(self.join(".hg")):
1088 if create or os.path.isdir(self.join(".hg")):
1088 return hg.repository(self.ui, path=self.path, create=create)
1089 return hg.repository(self.ui, path=self.path, create=create)
1089
1090
1090 def restore(self, repo, rev, delete=None, qupdate=None):
1091 def restore(self, repo, rev, delete=None, qupdate=None):
1091 c = repo.changelog.read(rev)
1092 c = repo.changelog.read(rev)
1092 desc = c[4].strip()
1093 desc = c[4].strip()
1093 lines = desc.splitlines()
1094 lines = desc.splitlines()
1094 i = 0
1095 i = 0
1095 datastart = None
1096 datastart = None
1096 series = []
1097 series = []
1097 applied = []
1098 applied = []
1098 qpp = None
1099 qpp = None
1099 for i in xrange(0, len(lines)):
1100 for i in xrange(0, len(lines)):
1100 if lines[i] == 'Patch Data:':
1101 if lines[i] == 'Patch Data:':
1101 datastart = i + 1
1102 datastart = i + 1
1102 elif lines[i].startswith('Dirstate:'):
1103 elif lines[i].startswith('Dirstate:'):
1103 l = lines[i].rstrip()
1104 l = lines[i].rstrip()
1104 l = l[10:].split(' ')
1105 l = l[10:].split(' ')
1105 qpp = [ hg.bin(x) for x in l ]
1106 qpp = [ hg.bin(x) for x in l ]
1106 elif datastart != None:
1107 elif datastart != None:
1107 l = lines[i].rstrip()
1108 l = lines[i].rstrip()
1108 se = statusentry(l)
1109 se = statusentry(l)
1109 file_ = se.name
1110 file_ = se.name
1110 if se.rev:
1111 if se.rev:
1111 applied.append(se)
1112 applied.append(se)
1112 series.append(file_)
1113 series.append(file_)
1113 if datastart == None:
1114 if datastart == None:
1114 self.ui.warn("No saved patch data found\n")
1115 self.ui.warn("No saved patch data found\n")
1115 return 1
1116 return 1
1116 self.ui.warn("restoring status: %s\n" % lines[0])
1117 self.ui.warn("restoring status: %s\n" % lines[0])
1117 self.full_series = series
1118 self.full_series = series
1118 self.applied = applied
1119 self.applied = applied
1119 self.parse_series()
1120 self.parse_series()
1120 self.series_dirty = 1
1121 self.series_dirty = 1
1121 self.applied_dirty = 1
1122 self.applied_dirty = 1
1122 heads = repo.changelog.heads()
1123 heads = repo.changelog.heads()
1123 if delete:
1124 if delete:
1124 if rev not in heads:
1125 if rev not in heads:
1125 self.ui.warn("save entry has children, leaving it alone\n")
1126 self.ui.warn("save entry has children, leaving it alone\n")
1126 else:
1127 else:
1127 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1128 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1128 pp = repo.dirstate.parents()
1129 pp = repo.dirstate.parents()
1129 if rev in pp:
1130 if rev in pp:
1130 update = True
1131 update = True
1131 else:
1132 else:
1132 update = False
1133 update = False
1133 self.strip(repo, rev, update=update, backup='strip')
1134 self.strip(repo, rev, update=update, backup='strip')
1134 if qpp:
1135 if qpp:
1135 self.ui.warn("saved queue repository parents: %s %s\n" %
1136 self.ui.warn("saved queue repository parents: %s %s\n" %
1136 (hg.short(qpp[0]), hg.short(qpp[1])))
1137 (hg.short(qpp[0]), hg.short(qpp[1])))
1137 if qupdate:
1138 if qupdate:
1138 print "queue directory updating"
1139 print "queue directory updating"
1139 r = self.qrepo()
1140 r = self.qrepo()
1140 if not r:
1141 if not r:
1141 self.ui.warn("Unable to load queue repository\n")
1142 self.ui.warn("Unable to load queue repository\n")
1142 return 1
1143 return 1
1143 hg.clean(r, qpp[0])
1144 hg.clean(r, qpp[0])
1144
1145
1145 def save(self, repo, msg=None):
1146 def save(self, repo, msg=None):
1146 if len(self.applied) == 0:
1147 if len(self.applied) == 0:
1147 self.ui.warn("save: no patches applied, exiting\n")
1148 self.ui.warn("save: no patches applied, exiting\n")
1148 return 1
1149 return 1
1149 if self.issaveline(self.applied[-1]):
1150 if self.issaveline(self.applied[-1]):
1150 self.ui.warn("status is already saved\n")
1151 self.ui.warn("status is already saved\n")
1151 return 1
1152 return 1
1152
1153
1153 ar = [ ':' + x for x in self.full_series ]
1154 ar = [ ':' + x for x in self.full_series ]
1154 if not msg:
1155 if not msg:
1155 msg = "hg patches saved state"
1156 msg = "hg patches saved state"
1156 else:
1157 else:
1157 msg = "hg patches: " + msg.rstrip('\r\n')
1158 msg = "hg patches: " + msg.rstrip('\r\n')
1158 r = self.qrepo()
1159 r = self.qrepo()
1159 if r:
1160 if r:
1160 pp = r.dirstate.parents()
1161 pp = r.dirstate.parents()
1161 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1162 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1162 msg += "\n\nPatch Data:\n"
1163 msg += "\n\nPatch Data:\n"
1163 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1164 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1164 "\n".join(ar) + '\n' or "")
1165 "\n".join(ar) + '\n' or "")
1165 n = repo.commit(None, text, user=None, force=1)
1166 n = repo.commit(None, text, user=None, force=1)
1166 if not n:
1167 if not n:
1167 self.ui.warn("repo commit failed\n")
1168 self.ui.warn("repo commit failed\n")
1168 return 1
1169 return 1
1169 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1170 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1170 self.applied_dirty = 1
1171 self.applied_dirty = 1
1171
1172
1172 def full_series_end(self):
1173 def full_series_end(self):
1173 if len(self.applied) > 0:
1174 if len(self.applied) > 0:
1174 p = self.applied[-1].name
1175 p = self.applied[-1].name
1175 end = self.find_series(p)
1176 end = self.find_series(p)
1176 if end == None:
1177 if end == None:
1177 return len(self.full_series)
1178 return len(self.full_series)
1178 return end + 1
1179 return end + 1
1179 return 0
1180 return 0
1180
1181
1181 def series_end(self, all_patches=False):
1182 def series_end(self, all_patches=False):
1182 end = 0
1183 end = 0
1183 def next(start):
1184 def next(start):
1184 if all_patches:
1185 if all_patches:
1185 return start
1186 return start
1186 i = start
1187 i = start
1187 while i < len(self.series):
1188 while i < len(self.series):
1188 p, reason = self.pushable(i)
1189 p, reason = self.pushable(i)
1189 if p:
1190 if p:
1190 break
1191 break
1191 self.explain_pushable(i)
1192 self.explain_pushable(i)
1192 i += 1
1193 i += 1
1193 return i
1194 return i
1194 if len(self.applied) > 0:
1195 if len(self.applied) > 0:
1195 p = self.applied[-1].name
1196 p = self.applied[-1].name
1196 try:
1197 try:
1197 end = self.series.index(p)
1198 end = self.series.index(p)
1198 except ValueError:
1199 except ValueError:
1199 return 0
1200 return 0
1200 return next(end + 1)
1201 return next(end + 1)
1201 return next(end)
1202 return next(end)
1202
1203
1203 def qapplied(self, repo, patch=None):
1204 def qapplied(self, repo, patch=None):
1204 if patch and patch not in self.series:
1205 if patch and patch not in self.series:
1205 raise util.Abort(_("patch %s is not in series file") % patch)
1206 raise util.Abort(_("patch %s is not in series file") % patch)
1206 if not patch:
1207 if not patch:
1207 end = len(self.applied)
1208 end = len(self.applied)
1208 else:
1209 else:
1209 end = self.series.index(patch) + 1
1210 end = self.series.index(patch) + 1
1210 for x in xrange(end):
1211 for x in xrange(end):
1211 p = self.appliedname(x)
1212 p = self.appliedname(x)
1212 self.ui.write("%s\n" % p)
1213 self.ui.write("%s\n" % p)
1213
1214
1214 def appliedname(self, index):
1215 def appliedname(self, index):
1215 pname = self.applied[index].name
1216 pname = self.applied[index].name
1216 if not self.ui.verbose:
1217 if not self.ui.verbose:
1217 p = pname
1218 p = pname
1218 else:
1219 else:
1219 p = str(self.series.index(pname)) + " " + p
1220 p = str(self.series.index(pname)) + " " + p
1220 return p
1221 return p
1221
1222
1222 def top(self, repo):
1223 def top(self, repo):
1223 if len(self.applied):
1224 if len(self.applied):
1224 p = self.appliedname(-1)
1225 p = self.appliedname(-1)
1225 self.ui.write(p + '\n')
1226 self.ui.write(p + '\n')
1226 else:
1227 else:
1227 self.ui.write("No patches applied\n")
1228 self.ui.write("No patches applied\n")
1228
1229
1229 def next(self, repo):
1230 def next(self, repo):
1230 end = self.series_end()
1231 end = self.series_end()
1231 if end == len(self.series):
1232 if end == len(self.series):
1232 self.ui.write("All patches applied\n")
1233 self.ui.write("All patches applied\n")
1233 else:
1234 else:
1234 p = self.series[end]
1235 p = self.series[end]
1235 if self.ui.verbose:
1236 if self.ui.verbose:
1236 self.ui.write("%d " % self.series.index(p))
1237 self.ui.write("%d " % self.series.index(p))
1237 self.ui.write(p + '\n')
1238 self.ui.write(p + '\n')
1238
1239
1239 def prev(self, repo):
1240 def prev(self, repo):
1240 if len(self.applied) > 1:
1241 if len(self.applied) > 1:
1241 p = self.appliedname(-2)
1242 p = self.appliedname(-2)
1242 self.ui.write(p + '\n')
1243 self.ui.write(p + '\n')
1243 elif len(self.applied) == 1:
1244 elif len(self.applied) == 1:
1244 self.ui.write("Only one patch applied\n")
1245 self.ui.write("Only one patch applied\n")
1245 else:
1246 else:
1246 self.ui.write("No patches applied\n")
1247 self.ui.write("No patches applied\n")
1247
1248
1248 def qimport(self, repo, files, patch=None, existing=None, force=None):
1249 def qimport(self, repo, files, patch=None, existing=None, force=None):
1249 if len(files) > 1 and patch:
1250 if len(files) > 1 and patch:
1250 raise util.Abort(_('option "-n" not valid when importing multiple '
1251 raise util.Abort(_('option "-n" not valid when importing multiple '
1251 'files'))
1252 'files'))
1252 i = 0
1253 i = 0
1253 added = []
1254 added = []
1254 for filename in files:
1255 for filename in files:
1255 if existing:
1256 if existing:
1256 if not patch:
1257 if not patch:
1257 patch = filename
1258 patch = filename
1258 if not os.path.isfile(self.join(patch)):
1259 if not os.path.isfile(self.join(patch)):
1259 raise util.Abort(_("patch %s does not exist") % patch)
1260 raise util.Abort(_("patch %s does not exist") % patch)
1260 else:
1261 else:
1261 try:
1262 try:
1262 text = file(filename).read()
1263 text = file(filename).read()
1263 except IOError:
1264 except IOError:
1264 raise util.Abort(_("unable to read %s") % patch)
1265 raise util.Abort(_("unable to read %s") % patch)
1265 if not patch:
1266 if not patch:
1266 patch = os.path.split(filename)[1]
1267 patch = os.path.split(filename)[1]
1267 if not force and os.path.exists(self.join(patch)):
1268 if not force and os.path.exists(self.join(patch)):
1268 raise util.Abort(_('patch "%s" already exists') % patch)
1269 raise util.Abort(_('patch "%s" already exists') % patch)
1269 patchf = self.opener(patch, "w")
1270 patchf = self.opener(patch, "w")
1270 patchf.write(text)
1271 patchf.write(text)
1271 if patch in self.series:
1272 if patch in self.series:
1272 raise util.Abort(_('patch %s is already in the series file')
1273 raise util.Abort(_('patch %s is already in the series file')
1273 % patch)
1274 % patch)
1274 index = self.full_series_end() + i
1275 index = self.full_series_end() + i
1275 self.full_series[index:index] = [patch]
1276 self.full_series[index:index] = [patch]
1276 self.parse_series()
1277 self.parse_series()
1277 self.ui.warn("adding %s to series file\n" % patch)
1278 self.ui.warn("adding %s to series file\n" % patch)
1278 i += 1
1279 i += 1
1279 added.append(patch)
1280 added.append(patch)
1280 patch = None
1281 patch = None
1281 self.series_dirty = 1
1282 self.series_dirty = 1
1282 qrepo = self.qrepo()
1283 qrepo = self.qrepo()
1283 if qrepo:
1284 if qrepo:
1284 qrepo.add(added)
1285 qrepo.add(added)
1285
1286
1286 def delete(ui, repo, patch, *patches, **opts):
1287 def delete(ui, repo, patch, *patches, **opts):
1287 """remove patches from queue
1288 """remove patches from queue
1288
1289
1289 The patches must not be applied.
1290 The patches must not be applied.
1290 With -k, the patch files are preserved in the patch directory."""
1291 With -k, the patch files are preserved in the patch directory."""
1291 q = repo.mq
1292 q = repo.mq
1292 q.delete(repo, (patch,) + patches, keep=opts.get('keep'))
1293 q.delete(repo, (patch,) + patches, keep=opts.get('keep'))
1293 q.save_dirty()
1294 q.save_dirty()
1294 return 0
1295 return 0
1295
1296
1296 def applied(ui, repo, patch=None, **opts):
1297 def applied(ui, repo, patch=None, **opts):
1297 """print the patches already applied"""
1298 """print the patches already applied"""
1298 repo.mq.qapplied(repo, patch)
1299 repo.mq.qapplied(repo, patch)
1299 return 0
1300 return 0
1300
1301
1301 def unapplied(ui, repo, patch=None, **opts):
1302 def unapplied(ui, repo, patch=None, **opts):
1302 """print the patches not yet applied"""
1303 """print the patches not yet applied"""
1303 for i, p in repo.mq.unapplied(repo, patch):
1304 for i, p in repo.mq.unapplied(repo, patch):
1304 if ui.verbose:
1305 if ui.verbose:
1305 ui.write("%d " % i)
1306 ui.write("%d " % i)
1306 ui.write("%s\n" % p)
1307 ui.write("%s\n" % p)
1307
1308
1308 def qimport(ui, repo, *filename, **opts):
1309 def qimport(ui, repo, *filename, **opts):
1309 """import a patch"""
1310 """import a patch"""
1310 q = repo.mq
1311 q = repo.mq
1311 q.qimport(repo, filename, patch=opts['name'],
1312 q.qimport(repo, filename, patch=opts['name'],
1312 existing=opts['existing'], force=opts['force'])
1313 existing=opts['existing'], force=opts['force'])
1313 q.save_dirty()
1314 q.save_dirty()
1314 return 0
1315 return 0
1315
1316
1316 def init(ui, repo, **opts):
1317 def init(ui, repo, **opts):
1317 """init a new queue repository
1318 """init a new queue repository
1318
1319
1319 The queue repository is unversioned by default. If -c is
1320 The queue repository is unversioned by default. If -c is
1320 specified, qinit will create a separate nested repository
1321 specified, qinit will create a separate nested repository
1321 for patches. Use qcommit to commit changes to this queue
1322 for patches. Use qcommit to commit changes to this queue
1322 repository."""
1323 repository."""
1323 q = repo.mq
1324 q = repo.mq
1324 r = q.init(repo, create=opts['create_repo'])
1325 r = q.init(repo, create=opts['create_repo'])
1325 q.save_dirty()
1326 q.save_dirty()
1326 if r:
1327 if r:
1327 fp = r.wopener('.hgignore', 'w')
1328 fp = r.wopener('.hgignore', 'w')
1328 print >> fp, 'syntax: glob'
1329 print >> fp, 'syntax: glob'
1329 print >> fp, 'status'
1330 print >> fp, 'status'
1330 fp.close()
1331 fp.close()
1331 r.wopener('series', 'w').close()
1332 r.wopener('series', 'w').close()
1332 r.add(['.hgignore', 'series'])
1333 r.add(['.hgignore', 'series'])
1333 return 0
1334 return 0
1334
1335
1335 def clone(ui, source, dest=None, **opts):
1336 def clone(ui, source, dest=None, **opts):
1336 '''clone main and patch repository at same time
1337 '''clone main and patch repository at same time
1337
1338
1338 If source is local, destination will have no patches applied. If
1339 If source is local, destination will have no patches applied. If
1339 source is remote, this command can not check if patches are
1340 source is remote, this command can not check if patches are
1340 applied in source, so cannot guarantee that patches are not
1341 applied in source, so cannot guarantee that patches are not
1341 applied in destination. If you clone remote repository, be sure
1342 applied in destination. If you clone remote repository, be sure
1342 before that it has no patches applied.
1343 before that it has no patches applied.
1343
1344
1344 Source patch repository is looked for in <src>/.hg/patches by
1345 Source patch repository is looked for in <src>/.hg/patches by
1345 default. Use -p <url> to change.
1346 default. Use -p <url> to change.
1346 '''
1347 '''
1347 commands.setremoteconfig(ui, opts)
1348 commands.setremoteconfig(ui, opts)
1348 if dest is None:
1349 if dest is None:
1349 dest = hg.defaultdest(source)
1350 dest = hg.defaultdest(source)
1350 sr = hg.repository(ui, ui.expandpath(source))
1351 sr = hg.repository(ui, ui.expandpath(source))
1351 qbase, destrev = None, None
1352 qbase, destrev = None, None
1352 if sr.local():
1353 if sr.local():
1353 reposetup(ui, sr)
1354 reposetup(ui, sr)
1354 if sr.mq.applied:
1355 if sr.mq.applied:
1355 qbase = revlog.bin(sr.mq.applied[0].rev)
1356 qbase = revlog.bin(sr.mq.applied[0].rev)
1356 if not hg.islocal(dest):
1357 if not hg.islocal(dest):
1357 destrev = sr.parents(qbase)[0]
1358 destrev = sr.parents(qbase)[0]
1358 ui.note(_('cloning main repo\n'))
1359 ui.note(_('cloning main repo\n'))
1359 sr, dr = hg.clone(ui, sr, dest,
1360 sr, dr = hg.clone(ui, sr, dest,
1360 pull=opts['pull'],
1361 pull=opts['pull'],
1361 rev=destrev,
1362 rev=destrev,
1362 update=False,
1363 update=False,
1363 stream=opts['uncompressed'])
1364 stream=opts['uncompressed'])
1364 ui.note(_('cloning patch repo\n'))
1365 ui.note(_('cloning patch repo\n'))
1365 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1366 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1366 dr.url() + '/.hg/patches',
1367 dr.url() + '/.hg/patches',
1367 pull=opts['pull'],
1368 pull=opts['pull'],
1368 update=not opts['noupdate'],
1369 update=not opts['noupdate'],
1369 stream=opts['uncompressed'])
1370 stream=opts['uncompressed'])
1370 if dr.local():
1371 if dr.local():
1371 if qbase:
1372 if qbase:
1372 ui.note(_('stripping applied patches from destination repo\n'))
1373 ui.note(_('stripping applied patches from destination repo\n'))
1373 reposetup(ui, dr)
1374 reposetup(ui, dr)
1374 dr.mq.strip(dr, qbase, update=False, backup=None)
1375 dr.mq.strip(dr, qbase, update=False, backup=None)
1375 if not opts['noupdate']:
1376 if not opts['noupdate']:
1376 ui.note(_('updating destination repo\n'))
1377 ui.note(_('updating destination repo\n'))
1377 hg.update(dr, dr.changelog.tip())
1378 hg.update(dr, dr.changelog.tip())
1378
1379
1379 def commit(ui, repo, *pats, **opts):
1380 def commit(ui, repo, *pats, **opts):
1380 """commit changes in the queue repository"""
1381 """commit changes in the queue repository"""
1381 q = repo.mq
1382 q = repo.mq
1382 r = q.qrepo()
1383 r = q.qrepo()
1383 if not r: raise util.Abort('no queue repository')
1384 if not r: raise util.Abort('no queue repository')
1384 commands.commit(r.ui, r, *pats, **opts)
1385 commands.commit(r.ui, r, *pats, **opts)
1385
1386
1386 def series(ui, repo, **opts):
1387 def series(ui, repo, **opts):
1387 """print the entire series file"""
1388 """print the entire series file"""
1388 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1389 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1389 return 0
1390 return 0
1390
1391
1391 def top(ui, repo, **opts):
1392 def top(ui, repo, **opts):
1392 """print the name of the current patch"""
1393 """print the name of the current patch"""
1393 repo.mq.top(repo)
1394 repo.mq.top(repo)
1394 return 0
1395 return 0
1395
1396
1396 def next(ui, repo, **opts):
1397 def next(ui, repo, **opts):
1397 """print the name of the next patch"""
1398 """print the name of the next patch"""
1398 repo.mq.next(repo)
1399 repo.mq.next(repo)
1399 return 0
1400 return 0
1400
1401
1401 def prev(ui, repo, **opts):
1402 def prev(ui, repo, **opts):
1402 """print the name of the previous patch"""
1403 """print the name of the previous patch"""
1403 repo.mq.prev(repo)
1404 repo.mq.prev(repo)
1404 return 0
1405 return 0
1405
1406
1406 def new(ui, repo, patch, **opts):
1407 def new(ui, repo, patch, **opts):
1407 """create a new patch
1408 """create a new patch
1408
1409
1409 qnew creates a new patch on top of the currently-applied patch
1410 qnew creates a new patch on top of the currently-applied patch
1410 (if any). It will refuse to run if there are any outstanding
1411 (if any). It will refuse to run if there are any outstanding
1411 changes unless -f is specified, in which case the patch will
1412 changes unless -f is specified, in which case the patch will
1412 be initialised with them.
1413 be initialised with them.
1413
1414
1414 -m or -l set the patch header as well as the commit message.
1415 -m or -l set the patch header as well as the commit message.
1415 If neither is specified, the patch header is empty and the
1416 If neither is specified, the patch header is empty and the
1416 commit message is 'New patch: PATCH'"""
1417 commit message is 'New patch: PATCH'"""
1417 q = repo.mq
1418 q = repo.mq
1418 message = commands.logmessage(opts)
1419 message = commands.logmessage(opts)
1419 q.new(repo, patch, msg=message, force=opts['force'])
1420 q.new(repo, patch, msg=message, force=opts['force'])
1420 q.save_dirty()
1421 q.save_dirty()
1421 return 0
1422 return 0
1422
1423
1423 def refresh(ui, repo, **opts):
1424 def refresh(ui, repo, **opts):
1424 """update the current patch"""
1425 """update the current patch"""
1425 q = repo.mq
1426 q = repo.mq
1426 message = commands.logmessage(opts)
1427 message = commands.logmessage(opts)
1427 if opts['edit']:
1428 if opts['edit']:
1428 if message:
1429 if message:
1429 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1430 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1430 patch = q.applied[-1].name
1431 patch = q.applied[-1].name
1431 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1432 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1432 message = ui.edit('\n'.join(message), user or ui.username())
1433 message = ui.edit('\n'.join(message), user or ui.username())
1433 q.refresh(repo, msg=message, short=opts['short'])
1434 q.refresh(repo, msg=message, short=opts['short'])
1434 q.save_dirty()
1435 q.save_dirty()
1435 return 0
1436 return 0
1436
1437
1437 def diff(ui, repo, *files, **opts):
1438 def diff(ui, repo, *files, **opts):
1438 """diff of the current patch"""
1439 """diff of the current patch"""
1439 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1440 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1440 repo.mq.diff(repo, list(files))
1441 repo.mq.diff(repo, list(files))
1441 return 0
1442 return 0
1442
1443
1443 def fold(ui, repo, *files, **opts):
1444 def fold(ui, repo, *files, **opts):
1444 """fold the named patches into the current patch
1445 """fold the named patches into the current patch
1445
1446
1446 Patches must not yet be applied. Each patch will be successively
1447 Patches must not yet be applied. Each patch will be successively
1447 applied to the current patch in the order given. If all the
1448 applied to the current patch in the order given. If all the
1448 patches apply successfully, the current patch will be refreshed
1449 patches apply successfully, the current patch will be refreshed
1449 with the new cumulative patch, and the folded patches will
1450 with the new cumulative patch, and the folded patches will
1450 be deleted. With -k/--keep, the folded patch files will not
1451 be deleted. With -k/--keep, the folded patch files will not
1451 be removed afterwards.
1452 be removed afterwards.
1452
1453
1453 The header for each folded patch will be concatenated with
1454 The header for each folded patch will be concatenated with
1454 the current patch header, separated by a line of '* * *'."""
1455 the current patch header, separated by a line of '* * *'."""
1455
1456
1456 q = repo.mq
1457 q = repo.mq
1457
1458
1458 if not files:
1459 if not files:
1459 raise util.Abort(_('qfold requires at least one patch name'))
1460 raise util.Abort(_('qfold requires at least one patch name'))
1460 if not q.check_toppatch(repo):
1461 if not q.check_toppatch(repo):
1461 raise util.Abort(_('No patches applied\n'))
1462 raise util.Abort(_('No patches applied\n'))
1462
1463
1463 message = commands.logmessage(opts)
1464 message = commands.logmessage(opts)
1464 if opts['edit']:
1465 if opts['edit']:
1465 if message:
1466 if message:
1466 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1467 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1467
1468
1468 parent = q.lookup('qtip')
1469 parent = q.lookup('qtip')
1469 patches = []
1470 patches = []
1470 messages = []
1471 messages = []
1471 for f in files:
1472 for f in files:
1472 patch = q.lookup(f)
1473 patch = q.lookup(f)
1473 if patch in patches or patch == parent:
1474 if patch in patches or patch == parent:
1474 ui.warn(_('Skipping already folded patch %s') % patch)
1475 ui.warn(_('Skipping already folded patch %s') % patch)
1475 if q.isapplied(patch):
1476 if q.isapplied(patch):
1476 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1477 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1477 patches.append(patch)
1478 patches.append(patch)
1478
1479
1479 for patch in patches:
1480 for patch in patches:
1480 if not message:
1481 if not message:
1481 messages.append(q.readheaders(patch)[0])
1482 messages.append(q.readheaders(patch)[0])
1482 pf = q.join(patch)
1483 pf = q.join(patch)
1483 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1484 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1484 if not patchsuccess:
1485 if not patchsuccess:
1485 raise util.Abort(_('Error folding patch %s') % patch)
1486 raise util.Abort(_('Error folding patch %s') % patch)
1486
1487
1487 if not message:
1488 if not message:
1488 message, comments, user = q.readheaders(parent)[0:3]
1489 message, comments, user = q.readheaders(parent)[0:3]
1489 for msg in messages:
1490 for msg in messages:
1490 message.append('* * *')
1491 message.append('* * *')
1491 message.extend(msg)
1492 message.extend(msg)
1492 message = '\n'.join(message)
1493 message = '\n'.join(message)
1493
1494
1494 if opts['edit']:
1495 if opts['edit']:
1495 message = ui.edit(message, user or ui.username())
1496 message = ui.edit(message, user or ui.username())
1496
1497
1497 q.refresh(repo, msg=message)
1498 q.refresh(repo, msg=message)
1498
1499
1499 for patch in patches:
1500 for patch in patches:
1500 q.delete(repo, patch, keep=opts['keep'])
1501 q.delete(repo, patch, keep=opts['keep'])
1501
1502
1502 q.save_dirty()
1503 q.save_dirty()
1503
1504
1504 def guard(ui, repo, *args, **opts):
1505 def guard(ui, repo, *args, **opts):
1505 '''set or print guards for a patch
1506 '''set or print guards for a patch
1506
1507
1507 guards control whether a patch can be pushed. a patch with no
1508 guards control whether a patch can be pushed. a patch with no
1508 guards is aways pushed. a patch with posative guard ("+foo") is
1509 guards is aways pushed. a patch with posative guard ("+foo") is
1509 pushed only if qselect command enables guard "foo". a patch with
1510 pushed only if qselect command enables guard "foo". a patch with
1510 nagative guard ("-foo") is never pushed if qselect command enables
1511 nagative guard ("-foo") is never pushed if qselect command enables
1511 guard "foo".
1512 guard "foo".
1512
1513
1513 with no arguments, default is to print current active guards.
1514 with no arguments, default is to print current active guards.
1514 with arguments, set active guards for patch.
1515 with arguments, set active guards for patch.
1515
1516
1516 to set nagative guard "-foo" on topmost patch ("--" is needed so
1517 to set nagative guard "-foo" on topmost patch ("--" is needed so
1517 hg will not interpret "-foo" as argument):
1518 hg will not interpret "-foo" as argument):
1518 hg qguard -- -foo
1519 hg qguard -- -foo
1519
1520
1520 to set guards on other patch:
1521 to set guards on other patch:
1521 hg qguard other.patch +2.6.17 -stable
1522 hg qguard other.patch +2.6.17 -stable
1522 '''
1523 '''
1523 def status(idx):
1524 def status(idx):
1524 guards = q.series_guards[idx] or ['unguarded']
1525 guards = q.series_guards[idx] or ['unguarded']
1525 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1526 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1526 q = repo.mq
1527 q = repo.mq
1527 patch = None
1528 patch = None
1528 args = list(args)
1529 args = list(args)
1529 if opts['list']:
1530 if opts['list']:
1530 if args or opts['none']:
1531 if args or opts['none']:
1531 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1532 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1532 for i in xrange(len(q.series)):
1533 for i in xrange(len(q.series)):
1533 status(i)
1534 status(i)
1534 return
1535 return
1535 if not args or args[0][0:1] in '-+':
1536 if not args or args[0][0:1] in '-+':
1536 if not q.applied:
1537 if not q.applied:
1537 raise util.Abort(_('no patches applied'))
1538 raise util.Abort(_('no patches applied'))
1538 patch = q.applied[-1].name
1539 patch = q.applied[-1].name
1539 if patch is None and args[0][0:1] not in '-+':
1540 if patch is None and args[0][0:1] not in '-+':
1540 patch = args.pop(0)
1541 patch = args.pop(0)
1541 if patch is None:
1542 if patch is None:
1542 raise util.Abort(_('no patch to work with'))
1543 raise util.Abort(_('no patch to work with'))
1543 if args or opts['none']:
1544 if args or opts['none']:
1544 q.set_guards(q.find_series(patch), args)
1545 q.set_guards(q.find_series(patch), args)
1545 q.save_dirty()
1546 q.save_dirty()
1546 else:
1547 else:
1547 status(q.series.index(q.lookup(patch)))
1548 status(q.series.index(q.lookup(patch)))
1548
1549
1549 def header(ui, repo, patch=None):
1550 def header(ui, repo, patch=None):
1550 """Print the header of the topmost or specified patch"""
1551 """Print the header of the topmost or specified patch"""
1551 q = repo.mq
1552 q = repo.mq
1552
1553
1553 if patch:
1554 if patch:
1554 patch = q.lookup(patch)
1555 patch = q.lookup(patch)
1555 else:
1556 else:
1556 if not q.applied:
1557 if not q.applied:
1557 ui.write('No patches applied\n')
1558 ui.write('No patches applied\n')
1558 return
1559 return
1559 patch = q.lookup('qtip')
1560 patch = q.lookup('qtip')
1560 message = repo.mq.readheaders(patch)[0]
1561 message = repo.mq.readheaders(patch)[0]
1561
1562
1562 ui.write('\n'.join(message) + '\n')
1563 ui.write('\n'.join(message) + '\n')
1563
1564
1564 def lastsavename(path):
1565 def lastsavename(path):
1565 (directory, base) = os.path.split(path)
1566 (directory, base) = os.path.split(path)
1566 names = os.listdir(directory)
1567 names = os.listdir(directory)
1567 namere = re.compile("%s.([0-9]+)" % base)
1568 namere = re.compile("%s.([0-9]+)" % base)
1568 maxindex = None
1569 maxindex = None
1569 maxname = None
1570 maxname = None
1570 for f in names:
1571 for f in names:
1571 m = namere.match(f)
1572 m = namere.match(f)
1572 if m:
1573 if m:
1573 index = int(m.group(1))
1574 index = int(m.group(1))
1574 if maxindex == None or index > maxindex:
1575 if maxindex == None or index > maxindex:
1575 maxindex = index
1576 maxindex = index
1576 maxname = f
1577 maxname = f
1577 if maxname:
1578 if maxname:
1578 return (os.path.join(directory, maxname), maxindex)
1579 return (os.path.join(directory, maxname), maxindex)
1579 return (None, None)
1580 return (None, None)
1580
1581
1581 def savename(path):
1582 def savename(path):
1582 (last, index) = lastsavename(path)
1583 (last, index) = lastsavename(path)
1583 if last is None:
1584 if last is None:
1584 index = 0
1585 index = 0
1585 newpath = path + ".%d" % (index + 1)
1586 newpath = path + ".%d" % (index + 1)
1586 return newpath
1587 return newpath
1587
1588
1588 def push(ui, repo, patch=None, **opts):
1589 def push(ui, repo, patch=None, **opts):
1589 """push the next patch onto the stack"""
1590 """push the next patch onto the stack"""
1590 q = repo.mq
1591 q = repo.mq
1591 mergeq = None
1592 mergeq = None
1592
1593
1593 if opts['all']:
1594 if opts['all']:
1594 patch = q.series[-1]
1595 patch = q.series[-1]
1595 if opts['merge']:
1596 if opts['merge']:
1596 if opts['name']:
1597 if opts['name']:
1597 newpath = opts['name']
1598 newpath = opts['name']
1598 else:
1599 else:
1599 newpath, i = lastsavename(q.path)
1600 newpath, i = lastsavename(q.path)
1600 if not newpath:
1601 if not newpath:
1601 ui.warn("no saved queues found, please use -n\n")
1602 ui.warn("no saved queues found, please use -n\n")
1602 return 1
1603 return 1
1603 mergeq = queue(ui, repo.join(""), newpath)
1604 mergeq = queue(ui, repo.join(""), newpath)
1604 ui.warn("merging with queue at: %s\n" % mergeq.path)
1605 ui.warn("merging with queue at: %s\n" % mergeq.path)
1605 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1606 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1606 mergeq=mergeq)
1607 mergeq=mergeq)
1607 q.save_dirty()
1608 q.save_dirty()
1608 return ret
1609 return ret
1609
1610
1610 def pop(ui, repo, patch=None, **opts):
1611 def pop(ui, repo, patch=None, **opts):
1611 """pop the current patch off the stack"""
1612 """pop the current patch off the stack"""
1612 localupdate = True
1613 localupdate = True
1613 if opts['name']:
1614 if opts['name']:
1614 q = queue(ui, repo.join(""), repo.join(opts['name']))
1615 q = queue(ui, repo.join(""), repo.join(opts['name']))
1615 ui.warn('using patch queue: %s\n' % q.path)
1616 ui.warn('using patch queue: %s\n' % q.path)
1616 localupdate = False
1617 localupdate = False
1617 else:
1618 else:
1618 q = repo.mq
1619 q = repo.mq
1619 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1620 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1620 q.save_dirty()
1621 q.save_dirty()
1621 return 0
1622 return 0
1622
1623
1623 def rename(ui, repo, patch, name=None, **opts):
1624 def rename(ui, repo, patch, name=None, **opts):
1624 """rename a patch
1625 """rename a patch
1625
1626
1626 With one argument, renames the current patch to PATCH1.
1627 With one argument, renames the current patch to PATCH1.
1627 With two arguments, renames PATCH1 to PATCH2."""
1628 With two arguments, renames PATCH1 to PATCH2."""
1628
1629
1629 q = repo.mq
1630 q = repo.mq
1630
1631
1631 if not name:
1632 if not name:
1632 name = patch
1633 name = patch
1633 patch = None
1634 patch = None
1634
1635
1635 if name in q.series:
1636 if name in q.series:
1636 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1637 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1637
1638
1638 absdest = q.join(name)
1639 absdest = q.join(name)
1639 if os.path.exists(absdest):
1640 if os.path.exists(absdest):
1640 raise util.Abort(_('%s already exists') % absdest)
1641 raise util.Abort(_('%s already exists') % absdest)
1641
1642
1642 if patch:
1643 if patch:
1643 patch = q.lookup(patch)
1644 patch = q.lookup(patch)
1644 else:
1645 else:
1645 if not q.applied:
1646 if not q.applied:
1646 ui.write(_('No patches applied\n'))
1647 ui.write(_('No patches applied\n'))
1647 return
1648 return
1648 patch = q.lookup('qtip')
1649 patch = q.lookup('qtip')
1649
1650
1650 if ui.verbose:
1651 if ui.verbose:
1651 ui.write('Renaming %s to %s\n' % (patch, name))
1652 ui.write('Renaming %s to %s\n' % (patch, name))
1652 i = q.find_series(patch)
1653 i = q.find_series(patch)
1653 q.full_series[i] = name
1654 q.full_series[i] = name
1654 q.parse_series()
1655 q.parse_series()
1655 q.series_dirty = 1
1656 q.series_dirty = 1
1656
1657
1657 info = q.isapplied(patch)
1658 info = q.isapplied(patch)
1658 if info:
1659 if info:
1659 q.applied[info[0]] = statusentry(info[1], name)
1660 q.applied[info[0]] = statusentry(info[1], name)
1660 q.applied_dirty = 1
1661 q.applied_dirty = 1
1661
1662
1662 util.rename(q.join(patch), absdest)
1663 util.rename(q.join(patch), absdest)
1663 r = q.qrepo()
1664 r = q.qrepo()
1664 if r:
1665 if r:
1665 wlock = r.wlock()
1666 wlock = r.wlock()
1666 if r.dirstate.state(name) == 'r':
1667 if r.dirstate.state(name) == 'r':
1667 r.undelete([name], wlock)
1668 r.undelete([name], wlock)
1668 r.copy(patch, name, wlock)
1669 r.copy(patch, name, wlock)
1669 r.remove([patch], False, wlock)
1670 r.remove([patch], False, wlock)
1670
1671
1671 q.save_dirty()
1672 q.save_dirty()
1672
1673
1673 def restore(ui, repo, rev, **opts):
1674 def restore(ui, repo, rev, **opts):
1674 """restore the queue state saved by a rev"""
1675 """restore the queue state saved by a rev"""
1675 rev = repo.lookup(rev)
1676 rev = repo.lookup(rev)
1676 q = repo.mq
1677 q = repo.mq
1677 q.restore(repo, rev, delete=opts['delete'],
1678 q.restore(repo, rev, delete=opts['delete'],
1678 qupdate=opts['update'])
1679 qupdate=opts['update'])
1679 q.save_dirty()
1680 q.save_dirty()
1680 return 0
1681 return 0
1681
1682
1682 def save(ui, repo, **opts):
1683 def save(ui, repo, **opts):
1683 """save current queue state"""
1684 """save current queue state"""
1684 q = repo.mq
1685 q = repo.mq
1685 message = commands.logmessage(opts)
1686 message = commands.logmessage(opts)
1686 ret = q.save(repo, msg=message)
1687 ret = q.save(repo, msg=message)
1687 if ret:
1688 if ret:
1688 return ret
1689 return ret
1689 q.save_dirty()
1690 q.save_dirty()
1690 if opts['copy']:
1691 if opts['copy']:
1691 path = q.path
1692 path = q.path
1692 if opts['name']:
1693 if opts['name']:
1693 newpath = os.path.join(q.basepath, opts['name'])
1694 newpath = os.path.join(q.basepath, opts['name'])
1694 if os.path.exists(newpath):
1695 if os.path.exists(newpath):
1695 if not os.path.isdir(newpath):
1696 if not os.path.isdir(newpath):
1696 raise util.Abort(_('destination %s exists and is not '
1697 raise util.Abort(_('destination %s exists and is not '
1697 'a directory') % newpath)
1698 'a directory') % newpath)
1698 if not opts['force']:
1699 if not opts['force']:
1699 raise util.Abort(_('destination %s exists, '
1700 raise util.Abort(_('destination %s exists, '
1700 'use -f to force') % newpath)
1701 'use -f to force') % newpath)
1701 else:
1702 else:
1702 newpath = savename(path)
1703 newpath = savename(path)
1703 ui.warn("copy %s to %s\n" % (path, newpath))
1704 ui.warn("copy %s to %s\n" % (path, newpath))
1704 util.copyfiles(path, newpath)
1705 util.copyfiles(path, newpath)
1705 if opts['empty']:
1706 if opts['empty']:
1706 try:
1707 try:
1707 os.unlink(q.join(q.status_path))
1708 os.unlink(q.join(q.status_path))
1708 except:
1709 except:
1709 pass
1710 pass
1710 return 0
1711 return 0
1711
1712
1712 def strip(ui, repo, rev, **opts):
1713 def strip(ui, repo, rev, **opts):
1713 """strip a revision and all later revs on the same branch"""
1714 """strip a revision and all later revs on the same branch"""
1714 rev = repo.lookup(rev)
1715 rev = repo.lookup(rev)
1715 backup = 'all'
1716 backup = 'all'
1716 if opts['backup']:
1717 if opts['backup']:
1717 backup = 'strip'
1718 backup = 'strip'
1718 elif opts['nobackup']:
1719 elif opts['nobackup']:
1719 backup = 'none'
1720 backup = 'none'
1720 repo.mq.strip(repo, rev, backup=backup)
1721 repo.mq.strip(repo, rev, backup=backup)
1721 return 0
1722 return 0
1722
1723
1723 def select(ui, repo, *args, **opts):
1724 def select(ui, repo, *args, **opts):
1724 '''set or print guarded patches to push
1725 '''set or print guarded patches to push
1725
1726
1726 use qguard command to set or print guards on patch. then use
1727 use qguard command to set or print guards on patch. then use
1727 qselect to tell mq which guards to use. example:
1728 qselect to tell mq which guards to use. example:
1728
1729
1729 qguard foo.patch -stable (nagative guard)
1730 qguard foo.patch -stable (nagative guard)
1730 qguard bar.patch +stable (posative guard)
1731 qguard bar.patch +stable (posative guard)
1731 qselect stable
1732 qselect stable
1732
1733
1733 this sets "stable" guard. mq will skip foo.patch (because it has
1734 this sets "stable" guard. mq will skip foo.patch (because it has
1734 nagative match) but push bar.patch (because it has posative
1735 nagative match) but push bar.patch (because it has posative
1735 match). patch is pushed if any posative guards match and no
1736 match). patch is pushed if any posative guards match and no
1736 nagative guards match.
1737 nagative guards match.
1737
1738
1738 with no arguments, default is to print current active guards.
1739 with no arguments, default is to print current active guards.
1739 with arguments, set active guards as given.
1740 with arguments, set active guards as given.
1740
1741
1741 use -n/--none to deactivate guards (no other arguments needed).
1742 use -n/--none to deactivate guards (no other arguments needed).
1742 when no guards active, patches with posative guards are skipped,
1743 when no guards active, patches with posative guards are skipped,
1743 patches with nagative guards are pushed.
1744 patches with nagative guards are pushed.
1744
1745
1745 qselect can change guards of applied patches. it does not pop
1746 qselect can change guards of applied patches. it does not pop
1746 guarded patches by default. use --pop to pop back to last applied
1747 guarded patches by default. use --pop to pop back to last applied
1747 patch that is not guarded. use --reapply (implies --pop) to push
1748 patch that is not guarded. use --reapply (implies --pop) to push
1748 back to current patch afterwards, but skip guarded patches.
1749 back to current patch afterwards, but skip guarded patches.
1749
1750
1750 use -s/--series to print list of all guards in series file (no
1751 use -s/--series to print list of all guards in series file (no
1751 other arguments needed). use -v for more information.'''
1752 other arguments needed). use -v for more information.'''
1752
1753
1753 q = repo.mq
1754 q = repo.mq
1754 guards = q.active()
1755 guards = q.active()
1755 if args or opts['none']:
1756 if args or opts['none']:
1756 old_unapplied = q.unapplied(repo)
1757 old_unapplied = q.unapplied(repo)
1757 old_guarded = [i for i in xrange(len(q.applied)) if
1758 old_guarded = [i for i in xrange(len(q.applied)) if
1758 not q.pushable(i)[0]]
1759 not q.pushable(i)[0]]
1759 q.set_active(args)
1760 q.set_active(args)
1760 q.save_dirty()
1761 q.save_dirty()
1761 if not args:
1762 if not args:
1762 ui.status(_('guards deactivated\n'))
1763 ui.status(_('guards deactivated\n'))
1763 if not opts['pop'] and not opts['reapply']:
1764 if not opts['pop'] and not opts['reapply']:
1764 unapplied = q.unapplied(repo)
1765 unapplied = q.unapplied(repo)
1765 guarded = [i for i in xrange(len(q.applied))
1766 guarded = [i for i in xrange(len(q.applied))
1766 if not q.pushable(i)[0]]
1767 if not q.pushable(i)[0]]
1767 if len(unapplied) != len(old_unapplied):
1768 if len(unapplied) != len(old_unapplied):
1768 ui.status(_('number of unguarded, unapplied patches has '
1769 ui.status(_('number of unguarded, unapplied patches has '
1769 'changed from %d to %d\n') %
1770 'changed from %d to %d\n') %
1770 (len(old_unapplied), len(unapplied)))
1771 (len(old_unapplied), len(unapplied)))
1771 if len(guarded) != len(old_guarded):
1772 if len(guarded) != len(old_guarded):
1772 ui.status(_('number of guarded, applied patches has changed '
1773 ui.status(_('number of guarded, applied patches has changed '
1773 'from %d to %d\n') %
1774 'from %d to %d\n') %
1774 (len(old_guarded), len(guarded)))
1775 (len(old_guarded), len(guarded)))
1775 elif opts['series']:
1776 elif opts['series']:
1776 guards = {}
1777 guards = {}
1777 noguards = 0
1778 noguards = 0
1778 for gs in q.series_guards:
1779 for gs in q.series_guards:
1779 if not gs:
1780 if not gs:
1780 noguards += 1
1781 noguards += 1
1781 for g in gs:
1782 for g in gs:
1782 guards.setdefault(g, 0)
1783 guards.setdefault(g, 0)
1783 guards[g] += 1
1784 guards[g] += 1
1784 if ui.verbose:
1785 if ui.verbose:
1785 guards['NONE'] = noguards
1786 guards['NONE'] = noguards
1786 guards = guards.items()
1787 guards = guards.items()
1787 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1788 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1788 if guards:
1789 if guards:
1789 ui.note(_('guards in series file:\n'))
1790 ui.note(_('guards in series file:\n'))
1790 for guard, count in guards:
1791 for guard, count in guards:
1791 ui.note('%2d ' % count)
1792 ui.note('%2d ' % count)
1792 ui.write(guard, '\n')
1793 ui.write(guard, '\n')
1793 else:
1794 else:
1794 ui.note(_('no guards in series file\n'))
1795 ui.note(_('no guards in series file\n'))
1795 else:
1796 else:
1796 if guards:
1797 if guards:
1797 ui.note(_('active guards:\n'))
1798 ui.note(_('active guards:\n'))
1798 for g in guards:
1799 for g in guards:
1799 ui.write(g, '\n')
1800 ui.write(g, '\n')
1800 else:
1801 else:
1801 ui.write(_('no active guards\n'))
1802 ui.write(_('no active guards\n'))
1802 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1803 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1803 popped = False
1804 popped = False
1804 if opts['pop'] or opts['reapply']:
1805 if opts['pop'] or opts['reapply']:
1805 for i in xrange(len(q.applied)):
1806 for i in xrange(len(q.applied)):
1806 pushable, reason = q.pushable(i)
1807 pushable, reason = q.pushable(i)
1807 if not pushable:
1808 if not pushable:
1808 ui.status(_('popping guarded patches\n'))
1809 ui.status(_('popping guarded patches\n'))
1809 popped = True
1810 popped = True
1810 if i == 0:
1811 if i == 0:
1811 q.pop(repo, all=True)
1812 q.pop(repo, all=True)
1812 else:
1813 else:
1813 q.pop(repo, i-1)
1814 q.pop(repo, i-1)
1814 break
1815 break
1815 if popped:
1816 if popped:
1816 try:
1817 try:
1817 if reapply:
1818 if reapply:
1818 ui.status(_('reapplying unguarded patches\n'))
1819 ui.status(_('reapplying unguarded patches\n'))
1819 q.push(repo, reapply)
1820 q.push(repo, reapply)
1820 finally:
1821 finally:
1821 q.save_dirty()
1822 q.save_dirty()
1822
1823
1823 def reposetup(ui, repo):
1824 def reposetup(ui, repo):
1824 class mqrepo(repo.__class__):
1825 class mqrepo(repo.__class__):
1825 def abort_if_wdir_patched(self, errmsg, force=False):
1826 def abort_if_wdir_patched(self, errmsg, force=False):
1826 if self.mq.applied and not force:
1827 if self.mq.applied and not force:
1827 parent = revlog.hex(self.dirstate.parents()[0])
1828 parent = revlog.hex(self.dirstate.parents()[0])
1828 if parent in [s.rev for s in self.mq.applied]:
1829 if parent in [s.rev for s in self.mq.applied]:
1829 raise util.Abort(errmsg)
1830 raise util.Abort(errmsg)
1830
1831
1831 def commit(self, *args, **opts):
1832 def commit(self, *args, **opts):
1832 if len(args) >= 6:
1833 if len(args) >= 6:
1833 force = args[5]
1834 force = args[5]
1834 else:
1835 else:
1835 force = opts.get('force')
1836 force = opts.get('force')
1836 self.abort_if_wdir_patched(
1837 self.abort_if_wdir_patched(
1837 _('cannot commit over an applied mq patch'),
1838 _('cannot commit over an applied mq patch'),
1838 force)
1839 force)
1839
1840
1840 return super(mqrepo, self).commit(*args, **opts)
1841 return super(mqrepo, self).commit(*args, **opts)
1841
1842
1842 def push(self, remote, force=False, revs=None):
1843 def push(self, remote, force=False, revs=None):
1843 if self.mq.applied and not force:
1844 if self.mq.applied and not force:
1844 raise util.Abort(_('source has mq patches applied'))
1845 raise util.Abort(_('source has mq patches applied'))
1845 return super(mqrepo, self).push(remote, force, revs)
1846 return super(mqrepo, self).push(remote, force, revs)
1846
1847
1847 def tags(self):
1848 def tags(self):
1848 if self.tagscache:
1849 if self.tagscache:
1849 return self.tagscache
1850 return self.tagscache
1850
1851
1851 tagscache = super(mqrepo, self).tags()
1852 tagscache = super(mqrepo, self).tags()
1852
1853
1853 q = self.mq
1854 q = self.mq
1854 if not q.applied:
1855 if not q.applied:
1855 return tagscache
1856 return tagscache
1856
1857
1857 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1858 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1858 mqtags.append((mqtags[-1][0], 'qtip'))
1859 mqtags.append((mqtags[-1][0], 'qtip'))
1859 mqtags.append((mqtags[0][0], 'qbase'))
1860 mqtags.append((mqtags[0][0], 'qbase'))
1860 for patch in mqtags:
1861 for patch in mqtags:
1861 if patch[1] in tagscache:
1862 if patch[1] in tagscache:
1862 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1863 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1863 else:
1864 else:
1864 tagscache[patch[1]] = revlog.bin(patch[0])
1865 tagscache[patch[1]] = revlog.bin(patch[0])
1865
1866
1866 return tagscache
1867 return tagscache
1867
1868
1868 if repo.local():
1869 if repo.local():
1869 repo.__class__ = mqrepo
1870 repo.__class__ = mqrepo
1870 repo.mq = queue(ui, repo.join(""))
1871 repo.mq = queue(ui, repo.join(""))
1871
1872
1872 cmdtable = {
1873 cmdtable = {
1873 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1874 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1874 "qclone": (clone,
1875 "qclone": (clone,
1875 [('', 'pull', None, _('use pull protocol to copy metadata')),
1876 [('', 'pull', None, _('use pull protocol to copy metadata')),
1876 ('U', 'noupdate', None, _('do not update the new working directories')),
1877 ('U', 'noupdate', None, _('do not update the new working directories')),
1877 ('', 'uncompressed', None,
1878 ('', 'uncompressed', None,
1878 _('use uncompressed transfer (fast over LAN)')),
1879 _('use uncompressed transfer (fast over LAN)')),
1879 ('e', 'ssh', '', _('specify ssh command to use')),
1880 ('e', 'ssh', '', _('specify ssh command to use')),
1880 ('p', 'patches', '', _('location of source patch repo')),
1881 ('p', 'patches', '', _('location of source patch repo')),
1881 ('', 'remotecmd', '',
1882 ('', 'remotecmd', '',
1882 _('specify hg command to run on the remote side'))],
1883 _('specify hg command to run on the remote side'))],
1883 'hg qclone [OPTION]... SOURCE [DEST]'),
1884 'hg qclone [OPTION]... SOURCE [DEST]'),
1884 "qcommit|qci":
1885 "qcommit|qci":
1885 (commit,
1886 (commit,
1886 commands.table["^commit|ci"][1],
1887 commands.table["^commit|ci"][1],
1887 'hg qcommit [OPTION]... [FILE]...'),
1888 'hg qcommit [OPTION]... [FILE]...'),
1888 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1889 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1889 "qdelete|qremove|qrm":
1890 "qdelete|qremove|qrm":
1890 (delete,
1891 (delete,
1891 [('k', 'keep', None, _('keep patch file'))],
1892 [('k', 'keep', None, _('keep patch file'))],
1892 'hg qdelete [-k] PATCH'),
1893 'hg qdelete [-k] PATCH'),
1893 'qfold':
1894 'qfold':
1894 (fold,
1895 (fold,
1895 [('e', 'edit', None, _('edit patch header')),
1896 [('e', 'edit', None, _('edit patch header')),
1896 ('k', 'keep', None, _('keep folded patch files')),
1897 ('k', 'keep', None, _('keep folded patch files')),
1897 ('m', 'message', '', _('set patch header to <text>')),
1898 ('m', 'message', '', _('set patch header to <text>')),
1898 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1899 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1899 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1900 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1900 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
1901 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
1901 ('n', 'none', None, _('drop all guards'))],
1902 ('n', 'none', None, _('drop all guards'))],
1902 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
1903 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
1903 'qheader': (header, [],
1904 'qheader': (header, [],
1904 _('hg qheader [PATCH]')),
1905 _('hg qheader [PATCH]')),
1905 "^qimport":
1906 "^qimport":
1906 (qimport,
1907 (qimport,
1907 [('e', 'existing', None, 'import file in patch dir'),
1908 [('e', 'existing', None, 'import file in patch dir'),
1908 ('n', 'name', '', 'patch file name'),
1909 ('n', 'name', '', 'patch file name'),
1909 ('f', 'force', None, 'overwrite existing files')],
1910 ('f', 'force', None, 'overwrite existing files')],
1910 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1911 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1911 "^qinit":
1912 "^qinit":
1912 (init,
1913 (init,
1913 [('c', 'create-repo', None, 'create queue repository')],
1914 [('c', 'create-repo', None, 'create queue repository')],
1914 'hg qinit [-c]'),
1915 'hg qinit [-c]'),
1915 "qnew":
1916 "qnew":
1916 (new,
1917 (new,
1917 [('m', 'message', '', _('use <text> as commit message')),
1918 [('m', 'message', '', _('use <text> as commit message')),
1918 ('l', 'logfile', '', _('read the commit message from <file>')),
1919 ('l', 'logfile', '', _('read the commit message from <file>')),
1919 ('f', 'force', None, _('import uncommitted changes into patch'))],
1920 ('f', 'force', None, _('import uncommitted changes into patch'))],
1920 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1921 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1921 "qnext": (next, [], 'hg qnext'),
1922 "qnext": (next, [], 'hg qnext'),
1922 "qprev": (prev, [], 'hg qprev'),
1923 "qprev": (prev, [], 'hg qprev'),
1923 "^qpop":
1924 "^qpop":
1924 (pop,
1925 (pop,
1925 [('a', 'all', None, 'pop all patches'),
1926 [('a', 'all', None, 'pop all patches'),
1926 ('n', 'name', '', 'queue name to pop'),
1927 ('n', 'name', '', 'queue name to pop'),
1927 ('f', 'force', None, 'forget any local changes')],
1928 ('f', 'force', None, 'forget any local changes')],
1928 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1929 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1929 "^qpush":
1930 "^qpush":
1930 (push,
1931 (push,
1931 [('f', 'force', None, 'apply if the patch has rejects'),
1932 [('f', 'force', None, 'apply if the patch has rejects'),
1932 ('l', 'list', None, 'list patch name in commit text'),
1933 ('l', 'list', None, 'list patch name in commit text'),
1933 ('a', 'all', None, 'apply all patches'),
1934 ('a', 'all', None, 'apply all patches'),
1934 ('m', 'merge', None, 'merge from another queue'),
1935 ('m', 'merge', None, 'merge from another queue'),
1935 ('n', 'name', '', 'merge queue name')],
1936 ('n', 'name', '', 'merge queue name')],
1936 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1937 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1937 "^qrefresh":
1938 "^qrefresh":
1938 (refresh,
1939 (refresh,
1939 [('e', 'edit', None, _('edit commit message')),
1940 [('e', 'edit', None, _('edit commit message')),
1940 ('m', 'message', '', _('change commit message with <text>')),
1941 ('m', 'message', '', _('change commit message with <text>')),
1941 ('l', 'logfile', '', _('change commit message with <file> content')),
1942 ('l', 'logfile', '', _('change commit message with <file> content')),
1942 ('s', 'short', None, 'short refresh')],
1943 ('s', 'short', None, 'short refresh')],
1943 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1944 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1944 'qrename|qmv':
1945 'qrename|qmv':
1945 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1946 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1946 "qrestore":
1947 "qrestore":
1947 (restore,
1948 (restore,
1948 [('d', 'delete', None, 'delete save entry'),
1949 [('d', 'delete', None, 'delete save entry'),
1949 ('u', 'update', None, 'update queue working dir')],
1950 ('u', 'update', None, 'update queue working dir')],
1950 'hg qrestore [-d] [-u] REV'),
1951 'hg qrestore [-d] [-u] REV'),
1951 "qsave":
1952 "qsave":
1952 (save,
1953 (save,
1953 [('m', 'message', '', _('use <text> as commit message')),
1954 [('m', 'message', '', _('use <text> as commit message')),
1954 ('l', 'logfile', '', _('read the commit message from <file>')),
1955 ('l', 'logfile', '', _('read the commit message from <file>')),
1955 ('c', 'copy', None, 'copy patch directory'),
1956 ('c', 'copy', None, 'copy patch directory'),
1956 ('n', 'name', '', 'copy directory name'),
1957 ('n', 'name', '', 'copy directory name'),
1957 ('e', 'empty', None, 'clear queue status file'),
1958 ('e', 'empty', None, 'clear queue status file'),
1958 ('f', 'force', None, 'force copy')],
1959 ('f', 'force', None, 'force copy')],
1959 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1960 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1960 "qselect": (select,
1961 "qselect": (select,
1961 [('n', 'none', None, _('disable all guards')),
1962 [('n', 'none', None, _('disable all guards')),
1962 ('s', 'series', None, _('list all guards in series file')),
1963 ('s', 'series', None, _('list all guards in series file')),
1963 ('', 'pop', None,
1964 ('', 'pop', None,
1964 _('pop to before first guarded applied patch')),
1965 _('pop to before first guarded applied patch')),
1965 ('', 'reapply', None, _('pop, then reapply patches'))],
1966 ('', 'reapply', None, _('pop, then reapply patches'))],
1966 'hg qselect [OPTION...] [GUARD...]'),
1967 'hg qselect [OPTION...] [GUARD...]'),
1967 "qseries":
1968 "qseries":
1968 (series,
1969 (series,
1969 [('m', 'missing', None, 'print patches not in series'),
1970 [('m', 'missing', None, 'print patches not in series'),
1970 ('s', 'summary', None, _('print first line of patch header'))],
1971 ('s', 'summary', None, _('print first line of patch header'))],
1971 'hg qseries [-m]'),
1972 'hg qseries [-m]'),
1972 "^strip":
1973 "^strip":
1973 (strip,
1974 (strip,
1974 [('f', 'force', None, 'force multi-head removal'),
1975 [('f', 'force', None, 'force multi-head removal'),
1975 ('b', 'backup', None, 'bundle unrelated changesets'),
1976 ('b', 'backup', None, 'bundle unrelated changesets'),
1976 ('n', 'nobackup', None, 'no backups')],
1977 ('n', 'nobackup', None, 'no backups')],
1977 'hg strip [-f] [-b] [-n] REV'),
1978 'hg strip [-f] [-b] [-n] REV'),
1978 "qtop": (top, [], 'hg qtop'),
1979 "qtop": (top, [], 'hg qtop'),
1979 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1980 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1980 }
1981 }
@@ -1,496 +1,539 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import demandload
8 from demandload import demandload
9 from i18n import gettext as _
9 from i18n import gettext as _
10 from node import *
10 from node import *
11 demandload(globals(), "cmdutil mdiff util")
11 demandload(globals(), "cmdutil mdiff util")
12 demandload(globals(), "cStringIO email.Parser os re shutil sys tempfile")
12 demandload(globals(), "cStringIO email.Parser os re shutil sys tempfile")
13
13
14 # helper functions
14 # helper functions
15
15
16 def copyfile(src, dst, basedir=None):
16 def copyfile(src, dst, basedir=None):
17 if not basedir:
17 if not basedir:
18 basedir = os.getcwd()
18 basedir = os.getcwd()
19
19
20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
21 if os.path.exists(absdst):
21 if os.path.exists(absdst):
22 raise util.Abort(_("cannot create %s: destination already exists") %
22 raise util.Abort(_("cannot create %s: destination already exists") %
23 dst)
23 dst)
24
24
25 targetdir = os.path.dirname(absdst)
25 targetdir = os.path.dirname(absdst)
26 if not os.path.isdir(targetdir):
26 if not os.path.isdir(targetdir):
27 os.makedirs(targetdir)
27 os.makedirs(targetdir)
28 try:
28 try:
29 shutil.copyfile(abssrc, absdst)
29 shutil.copyfile(abssrc, absdst)
30 shutil.copymode(abssrc, absdst)
30 shutil.copymode(abssrc, absdst)
31 except shutil.Error, inst:
31 except shutil.Error, inst:
32 raise util.Abort(str(inst))
32 raise util.Abort(str(inst))
33
33
34 # public functions
34 # public functions
35
35
36 def extract(ui, fileobj):
36 def extract(ui, fileobj):
37 '''extract patch from data read from fileobj.
37 '''extract patch from data read from fileobj.
38
38
39 patch can be normal patch or contained in email message.
39 patch can be normal patch or contained in email message.
40
40
41 return tuple (filename, message, user, date). any item in returned
41 return tuple (filename, message, user, date). any item in returned
42 tuple can be None. if filename is None, fileobj did not contain
42 tuple can be None. if filename is None, fileobj did not contain
43 patch. caller must unlink filename when done.'''
43 patch. caller must unlink filename when done.'''
44
44
45 # attempt to detect the start of a patch
45 # attempt to detect the start of a patch
46 # (this heuristic is borrowed from quilt)
46 # (this heuristic is borrowed from quilt)
47 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
47 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
48 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
48 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
49 '(---|\*\*\*)[ \t])', re.MULTILINE)
49 '(---|\*\*\*)[ \t])', re.MULTILINE)
50
50
51 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
51 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
52 tmpfp = os.fdopen(fd, 'w')
52 tmpfp = os.fdopen(fd, 'w')
53 try:
53 try:
54 hgpatch = False
54 hgpatch = False
55
55
56 msg = email.Parser.Parser().parse(fileobj)
56 msg = email.Parser.Parser().parse(fileobj)
57
57
58 message = msg['Subject']
58 message = msg['Subject']
59 user = msg['From']
59 user = msg['From']
60 # should try to parse msg['Date']
60 # should try to parse msg['Date']
61 date = None
61 date = None
62
62
63 if message:
63 if message:
64 message = message.replace('\n\t', ' ')
64 message = message.replace('\n\t', ' ')
65 ui.debug('Subject: %s\n' % message)
65 ui.debug('Subject: %s\n' % message)
66 if user:
66 if user:
67 ui.debug('From: %s\n' % user)
67 ui.debug('From: %s\n' % user)
68 diffs_seen = 0
68 diffs_seen = 0
69 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
69 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
70
70
71 for part in msg.walk():
71 for part in msg.walk():
72 content_type = part.get_content_type()
72 content_type = part.get_content_type()
73 ui.debug('Content-Type: %s\n' % content_type)
73 ui.debug('Content-Type: %s\n' % content_type)
74 if content_type not in ok_types:
74 if content_type not in ok_types:
75 continue
75 continue
76 payload = part.get_payload(decode=True)
76 payload = part.get_payload(decode=True)
77 m = diffre.search(payload)
77 m = diffre.search(payload)
78 if m:
78 if m:
79 ui.debug(_('found patch at byte %d\n') % m.start(0))
79 ui.debug(_('found patch at byte %d\n') % m.start(0))
80 diffs_seen += 1
80 diffs_seen += 1
81 cfp = cStringIO.StringIO()
81 cfp = cStringIO.StringIO()
82 if message:
82 if message:
83 cfp.write(message)
83 cfp.write(message)
84 cfp.write('\n')
84 cfp.write('\n')
85 for line in payload[:m.start(0)].splitlines():
85 for line in payload[:m.start(0)].splitlines():
86 if line.startswith('# HG changeset patch'):
86 if line.startswith('# HG changeset patch'):
87 ui.debug(_('patch generated by hg export\n'))
87 ui.debug(_('patch generated by hg export\n'))
88 hgpatch = True
88 hgpatch = True
89 # drop earlier commit message content
89 # drop earlier commit message content
90 cfp.seek(0)
90 cfp.seek(0)
91 cfp.truncate()
91 cfp.truncate()
92 elif hgpatch:
92 elif hgpatch:
93 if line.startswith('# User '):
93 if line.startswith('# User '):
94 user = line[7:]
94 user = line[7:]
95 ui.debug('From: %s\n' % user)
95 ui.debug('From: %s\n' % user)
96 elif line.startswith("# Date "):
96 elif line.startswith("# Date "):
97 date = line[7:]
97 date = line[7:]
98 if not line.startswith('# '):
98 if not line.startswith('# '):
99 cfp.write(line)
99 cfp.write(line)
100 cfp.write('\n')
100 cfp.write('\n')
101 message = cfp.getvalue()
101 message = cfp.getvalue()
102 if tmpfp:
102 if tmpfp:
103 tmpfp.write(payload)
103 tmpfp.write(payload)
104 if not payload.endswith('\n'):
104 if not payload.endswith('\n'):
105 tmpfp.write('\n')
105 tmpfp.write('\n')
106 elif not diffs_seen and message and content_type == 'text/plain':
106 elif not diffs_seen and message and content_type == 'text/plain':
107 message += '\n' + payload
107 message += '\n' + payload
108 except:
108 except:
109 tmpfp.close()
109 tmpfp.close()
110 os.unlink(tmpname)
110 os.unlink(tmpname)
111 raise
111 raise
112
112
113 tmpfp.close()
113 tmpfp.close()
114 if not diffs_seen:
114 if not diffs_seen:
115 os.unlink(tmpname)
115 os.unlink(tmpname)
116 return None, message, user, date
116 return None, message, user, date
117 return tmpname, message, user, date
117 return tmpname, message, user, date
118
118
119 def readgitpatch(patchname):
119 def readgitpatch(patchname):
120 """extract git-style metadata about patches from <patchname>"""
120 """extract git-style metadata about patches from <patchname>"""
121 class gitpatch:
121 class gitpatch:
122 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
122 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
123 def __init__(self, path):
123 def __init__(self, path):
124 self.path = path
124 self.path = path
125 self.oldpath = None
125 self.oldpath = None
126 self.mode = None
126 self.mode = None
127 self.op = 'MODIFY'
127 self.op = 'MODIFY'
128 self.copymod = False
128 self.copymod = False
129 self.lineno = 0
129 self.lineno = 0
130
130
131 # Filter patch for git information
131 # Filter patch for git information
132 gitre = re.compile('diff --git a/(.*) b/(.*)')
132 gitre = re.compile('diff --git a/(.*) b/(.*)')
133 pf = file(patchname)
133 pf = file(patchname)
134 gp = None
134 gp = None
135 gitpatches = []
135 gitpatches = []
136 # Can have a git patch with only metadata, causing patch to complain
136 # Can have a git patch with only metadata, causing patch to complain
137 dopatch = False
137 dopatch = False
138
138
139 lineno = 0
139 lineno = 0
140 for line in pf:
140 for line in pf:
141 lineno += 1
141 lineno += 1
142 if line.startswith('diff --git'):
142 if line.startswith('diff --git'):
143 m = gitre.match(line)
143 m = gitre.match(line)
144 if m:
144 if m:
145 if gp:
145 if gp:
146 gitpatches.append(gp)
146 gitpatches.append(gp)
147 src, dst = m.group(1,2)
147 src, dst = m.group(1,2)
148 gp = gitpatch(dst)
148 gp = gitpatch(dst)
149 gp.lineno = lineno
149 gp.lineno = lineno
150 elif gp:
150 elif gp:
151 if line.startswith('--- '):
151 if line.startswith('--- '):
152 if gp.op in ('COPY', 'RENAME'):
152 if gp.op in ('COPY', 'RENAME'):
153 gp.copymod = True
153 gp.copymod = True
154 dopatch = 'filter'
154 dopatch = 'filter'
155 gitpatches.append(gp)
155 gitpatches.append(gp)
156 gp = None
156 gp = None
157 if not dopatch:
157 if not dopatch:
158 dopatch = True
158 dopatch = True
159 continue
159 continue
160 if line.startswith('rename from '):
160 if line.startswith('rename from '):
161 gp.op = 'RENAME'
161 gp.op = 'RENAME'
162 gp.oldpath = line[12:].rstrip()
162 gp.oldpath = line[12:].rstrip()
163 elif line.startswith('rename to '):
163 elif line.startswith('rename to '):
164 gp.path = line[10:].rstrip()
164 gp.path = line[10:].rstrip()
165 elif line.startswith('copy from '):
165 elif line.startswith('copy from '):
166 gp.op = 'COPY'
166 gp.op = 'COPY'
167 gp.oldpath = line[10:].rstrip()
167 gp.oldpath = line[10:].rstrip()
168 elif line.startswith('copy to '):
168 elif line.startswith('copy to '):
169 gp.path = line[8:].rstrip()
169 gp.path = line[8:].rstrip()
170 elif line.startswith('deleted file'):
170 elif line.startswith('deleted file'):
171 gp.op = 'DELETE'
171 gp.op = 'DELETE'
172 elif line.startswith('new file mode '):
172 elif line.startswith('new file mode '):
173 gp.op = 'ADD'
173 gp.op = 'ADD'
174 gp.mode = int(line.rstrip()[-3:], 8)
174 gp.mode = int(line.rstrip()[-3:], 8)
175 elif line.startswith('new mode '):
175 elif line.startswith('new mode '):
176 gp.mode = int(line.rstrip()[-3:], 8)
176 gp.mode = int(line.rstrip()[-3:], 8)
177 if gp:
177 if gp:
178 gitpatches.append(gp)
178 gitpatches.append(gp)
179
179
180 if not gitpatches:
180 if not gitpatches:
181 dopatch = True
181 dopatch = True
182
182
183 return (dopatch, gitpatches)
183 return (dopatch, gitpatches)
184
184
185 def dogitpatch(patchname, gitpatches):
185 def dogitpatch(patchname, gitpatches):
186 """Preprocess git patch so that vanilla patch can handle it"""
186 """Preprocess git patch so that vanilla patch can handle it"""
187 pf = file(patchname)
187 pf = file(patchname)
188 pfline = 1
188 pfline = 1
189
189
190 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
190 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
191 tmpfp = os.fdopen(fd, 'w')
191 tmpfp = os.fdopen(fd, 'w')
192
192
193 try:
193 try:
194 for i in range(len(gitpatches)):
194 for i in range(len(gitpatches)):
195 p = gitpatches[i]
195 p = gitpatches[i]
196 if not p.copymod:
196 if not p.copymod:
197 continue
197 continue
198
198
199 copyfile(p.oldpath, p.path)
199 copyfile(p.oldpath, p.path)
200
200
201 # rewrite patch hunk
201 # rewrite patch hunk
202 while pfline < p.lineno:
202 while pfline < p.lineno:
203 tmpfp.write(pf.readline())
203 tmpfp.write(pf.readline())
204 pfline += 1
204 pfline += 1
205 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
205 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
206 line = pf.readline()
206 line = pf.readline()
207 pfline += 1
207 pfline += 1
208 while not line.startswith('--- a/'):
208 while not line.startswith('--- a/'):
209 tmpfp.write(line)
209 tmpfp.write(line)
210 line = pf.readline()
210 line = pf.readline()
211 pfline += 1
211 pfline += 1
212 tmpfp.write('--- a/%s\n' % p.path)
212 tmpfp.write('--- a/%s\n' % p.path)
213
213
214 line = pf.readline()
214 line = pf.readline()
215 while line:
215 while line:
216 tmpfp.write(line)
216 tmpfp.write(line)
217 line = pf.readline()
217 line = pf.readline()
218 except:
218 except:
219 tmpfp.close()
219 tmpfp.close()
220 os.unlink(patchname)
220 os.unlink(patchname)
221 raise
221 raise
222
222
223 tmpfp.close()
223 tmpfp.close()
224 return patchname
224 return patchname
225
225
226 def patch(patchname, ui, strip=1, cwd=None):
226 def patch(patchname, ui, strip=1, cwd=None):
227 """apply the patch <patchname> to the working directory.
227 """apply the patch <patchname> to the working directory.
228 a list of patched files is returned"""
228 a list of patched files is returned"""
229
229
230 (dopatch, gitpatches) = readgitpatch(patchname)
230 (dopatch, gitpatches) = readgitpatch(patchname)
231
231
232 files = {}
232 files = {}
233 fuzz = False
233 fuzz = False
234 if dopatch:
234 if dopatch:
235 if dopatch == 'filter':
235 if dopatch == 'filter':
236 patchname = dogitpatch(patchname, gitpatches)
236 patchname = dogitpatch(patchname, gitpatches)
237 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
237 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
238 args = []
238 args = []
239 if cwd:
239 if cwd:
240 args.append('-d %s' % util.shellquote(cwd))
240 args.append('-d %s' % util.shellquote(cwd))
241 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
241 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
242 util.shellquote(patchname)))
242 util.shellquote(patchname)))
243
243
244 if dopatch == 'filter':
244 if dopatch == 'filter':
245 False and os.unlink(patchname)
245 False and os.unlink(patchname)
246
246
247 for line in fp:
247 for line in fp:
248 line = line.rstrip()
248 line = line.rstrip()
249 ui.note(line + '\n')
249 ui.note(line + '\n')
250 if line.startswith('patching file '):
250 if line.startswith('patching file '):
251 pf = util.parse_patch_output(line)
251 pf = util.parse_patch_output(line)
252 printed_file = False
252 printed_file = False
253 files.setdefault(pf, (None, None))
253 files.setdefault(pf, (None, None))
254 elif line.find('with fuzz') >= 0:
254 elif line.find('with fuzz') >= 0:
255 fuzz = True
255 fuzz = True
256 if not printed_file:
256 if not printed_file:
257 ui.warn(pf + '\n')
257 ui.warn(pf + '\n')
258 printed_file = True
258 printed_file = True
259 ui.warn(line + '\n')
259 ui.warn(line + '\n')
260 elif line.find('saving rejects to file') >= 0:
260 elif line.find('saving rejects to file') >= 0:
261 ui.warn(line + '\n')
261 ui.warn(line + '\n')
262 elif line.find('FAILED') >= 0:
262 elif line.find('FAILED') >= 0:
263 if not printed_file:
263 if not printed_file:
264 ui.warn(pf + '\n')
264 ui.warn(pf + '\n')
265 printed_file = True
265 printed_file = True
266 ui.warn(line + '\n')
266 ui.warn(line + '\n')
267
267
268 code = fp.close()
268 code = fp.close()
269 if code:
269 if code:
270 raise util.Abort(_("patch command failed: %s") %
270 raise util.Abort(_("patch command failed: %s") %
271 util.explain_exit(code)[0])
271 util.explain_exit(code)[0])
272
272
273 for gp in gitpatches:
273 for gp in gitpatches:
274 files[gp.path] = (gp.op, gp)
274 files[gp.path] = (gp.op, gp)
275
275
276 return (files, fuzz)
276 return (files, fuzz)
277
277
278 def diffopts(ui, opts={}):
278 def diffopts(ui, opts={}):
279 return mdiff.diffopts(
279 return mdiff.diffopts(
280 text=opts.get('text'),
280 text=opts.get('text'),
281 git=(opts.get('git') or
281 git=(opts.get('git') or
282 ui.configbool('diff', 'git', None)),
282 ui.configbool('diff', 'git', None)),
283 showfunc=(opts.get('show_function') or
283 showfunc=(opts.get('show_function') or
284 ui.configbool('diff', 'showfunc', None)),
284 ui.configbool('diff', 'showfunc', None)),
285 ignorews=(opts.get('ignore_all_space') or
285 ignorews=(opts.get('ignore_all_space') or
286 ui.configbool('diff', 'ignorews', None)),
286 ui.configbool('diff', 'ignorews', None)),
287 ignorewsamount=(opts.get('ignore_space_change') or
287 ignorewsamount=(opts.get('ignore_space_change') or
288 ui.configbool('diff', 'ignorewsamount', None)),
288 ui.configbool('diff', 'ignorewsamount', None)),
289 ignoreblanklines=(opts.get('ignore_blank_lines') or
289 ignoreblanklines=(opts.get('ignore_blank_lines') or
290 ui.configbool('diff', 'ignoreblanklines', None)))
290 ui.configbool('diff', 'ignoreblanklines', None)))
291
291
292 def updatedir(ui, repo, patches, wlock=None):
292 def updatedir(ui, repo, patches, wlock=None):
293 '''Update dirstate after patch application according to metadata'''
293 '''Update dirstate after patch application according to metadata'''
294 if not patches:
294 if not patches:
295 return
295 return
296 copies = []
296 copies = []
297 removes = []
297 removes = []
298 cfiles = patches.keys()
298 cfiles = patches.keys()
299 copts = {'after': False, 'force': False}
299 copts = {'after': False, 'force': False}
300 cwd = repo.getcwd()
300 cwd = repo.getcwd()
301 if cwd:
301 if cwd:
302 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
302 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
303 for f in patches:
303 for f in patches:
304 ctype, gp = patches[f]
304 ctype, gp = patches[f]
305 if ctype == 'RENAME':
305 if ctype == 'RENAME':
306 copies.append((gp.oldpath, gp.path, gp.copymod))
306 copies.append((gp.oldpath, gp.path, gp.copymod))
307 removes.append(gp.oldpath)
307 removes.append(gp.oldpath)
308 elif ctype == 'COPY':
308 elif ctype == 'COPY':
309 copies.append((gp.oldpath, gp.path, gp.copymod))
309 copies.append((gp.oldpath, gp.path, gp.copymod))
310 elif ctype == 'DELETE':
310 elif ctype == 'DELETE':
311 removes.append(gp.path)
311 removes.append(gp.path)
312 for src, dst, after in copies:
312 for src, dst, after in copies:
313 if not after:
313 if not after:
314 copyfile(src, dst, repo.root)
314 copyfile(src, dst, repo.root)
315 repo.copy(src, dst, wlock=wlock)
315 repo.copy(src, dst, wlock=wlock)
316 if removes:
316 if removes:
317 repo.remove(removes, True, wlock=wlock)
317 repo.remove(removes, True, wlock=wlock)
318 for f in patches:
318 for f in patches:
319 ctype, gp = patches[f]
319 ctype, gp = patches[f]
320 if gp and gp.mode:
320 if gp and gp.mode:
321 x = gp.mode & 0100 != 0
321 x = gp.mode & 0100 != 0
322 dst = os.path.join(repo.root, gp.path)
322 dst = os.path.join(repo.root, gp.path)
323 util.set_exec(dst, x)
323 util.set_exec(dst, x)
324 cmdutil.addremove(repo, cfiles, wlock=wlock)
324 cmdutil.addremove(repo, cfiles, wlock=wlock)
325 files = patches.keys()
325 files = patches.keys()
326 files.extend([r for r in removes if r not in files])
326 files.extend([r for r in removes if r not in files])
327 files.sort()
327 files.sort()
328
328
329 return files
329 return files
330
330
331 def diff(repo, node1=None, node2=None, files=None, match=util.always,
331 def diff(repo, node1=None, node2=None, files=None, match=util.always,
332 fp=None, changes=None, opts=None):
332 fp=None, changes=None, opts=None):
333 '''print diff of changes to files between two nodes, or node and
333 '''print diff of changes to files between two nodes, or node and
334 working directory.
334 working directory.
335
335
336 if node1 is None, use first dirstate parent instead.
336 if node1 is None, use first dirstate parent instead.
337 if node2 is None, compare node1 with working directory.'''
337 if node2 is None, compare node1 with working directory.'''
338
338
339 if opts is None:
339 if opts is None:
340 opts = mdiff.defaultopts
340 opts = mdiff.defaultopts
341 if fp is None:
341 if fp is None:
342 fp = repo.ui
342 fp = repo.ui
343
343
344 if not node1:
344 if not node1:
345 node1 = repo.dirstate.parents()[0]
345 node1 = repo.dirstate.parents()[0]
346
347 clcache = {}
348 def getchangelog(n):
349 if n not in clcache:
350 clcache[n] = repo.changelog.read(n)
351 return clcache[n]
352 mcache = {}
353 def getmanifest(n):
354 if n not in mcache:
355 mcache[n] = repo.manifest.read(n)
356 return mcache[n]
357 fcache = {}
358 def getfile(f):
359 if f not in fcache:
360 fcache[f] = repo.file(f)
361 return fcache[f]
362
346 # reading the data for node1 early allows it to play nicely
363 # reading the data for node1 early allows it to play nicely
347 # with repo.status and the revlog cache.
364 # with repo.status and the revlog cache.
348 change = repo.changelog.read(node1)
365 change = getchangelog(node1)
349 mmap = repo.manifest.read(change[0])
366 mmap = getmanifest(change[0])
350 date1 = util.datestr(change[2])
367 date1 = util.datestr(change[2])
351
368
352 if not changes:
369 if not changes:
353 changes = repo.status(node1, node2, files, match=match)[:5]
370 changes = repo.status(node1, node2, files, match=match)[:5]
354 modified, added, removed, deleted, unknown = changes
371 modified, added, removed, deleted, unknown = changes
355 if files:
372 if files:
356 def filterfiles(filters):
373 def filterfiles(filters):
357 l = [x for x in filters if x in files]
374 l = [x for x in filters if x in files]
358
375
359 for t in files:
376 for t in files:
360 if not t.endswith("/"):
377 if not t.endswith("/"):
361 t += "/"
378 t += "/"
362 l += [x for x in filters if x.startswith(t)]
379 l += [x for x in filters if x.startswith(t)]
363 return l
380 return l
364
381
365 modified, added, removed = map(filterfiles, (modified, added, removed))
382 modified, added, removed = map(filterfiles, (modified, added, removed))
366
383
367 if not modified and not added and not removed:
384 if not modified and not added and not removed:
368 return
385 return
369
386
387 def renamedbetween(f, n1, n2):
388 r1, r2 = map(repo.changelog.rev, (n1, n2))
389 src = None
390 while r2 > r1:
391 cl = getchangelog(n2)[0]
392 m = getmanifest(cl)
393 try:
394 src = getfile(f).renamed(m[f])
395 except KeyError:
396 return None
397 if src:
398 f = src[0]
399 n2 = repo.changelog.parents(n2)[0]
400 r2 = repo.changelog.rev(n2)
401 return src
402
370 if node2:
403 if node2:
371 change = repo.changelog.read(node2)
404 change = getchangelog(node2)
372 mmap2 = repo.manifest.read(change[0])
405 mmap2 = getmanifest(change[0])
373 _date2 = util.datestr(change[2])
406 _date2 = util.datestr(change[2])
374 def date2(f):
407 def date2(f):
375 return _date2
408 return _date2
376 def read(f):
409 def read(f):
377 return repo.file(f).read(mmap2[f])
410 return getfile(f).read(mmap2[f])
378 def renamed(f):
411 def renamed(f):
379 src = repo.file(f).renamed(mmap2[f])
412 return renamedbetween(f, node1, node2)
380 return src and src[0] or None
381 else:
413 else:
382 tz = util.makedate()[1]
414 tz = util.makedate()[1]
383 _date2 = util.datestr()
415 _date2 = util.datestr()
384 def date2(f):
416 def date2(f):
385 try:
417 try:
386 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
418 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
387 except OSError, err:
419 except OSError, err:
388 if err.errno != errno.ENOENT: raise
420 if err.errno != errno.ENOENT: raise
389 return _date2
421 return _date2
390 def read(f):
422 def read(f):
391 return repo.wread(f)
423 return repo.wread(f)
392 def renamed(f):
424 def renamed(f):
393 return repo.dirstate.copies.get(f)
425 src = repo.dirstate.copies.get(f)
426 parent = repo.dirstate.parents()[0]
427 if src:
428 f = src[0]
429 of = renamedbetween(f, node1, parent)
430 if of:
431 return of
432 elif src:
433 cl = getchangelog(parent)[0]
434 return (src, getmanifest(cl)[src])
435 else:
436 return None
394
437
395 if repo.ui.quiet:
438 if repo.ui.quiet:
396 r = None
439 r = None
397 else:
440 else:
398 hexfunc = repo.ui.verbose and hex or short
441 hexfunc = repo.ui.verbose and hex or short
399 r = [hexfunc(node) for node in [node1, node2] if node]
442 r = [hexfunc(node) for node in [node1, node2] if node]
400
443
401 if opts.git:
444 if opts.git:
402 copied = {}
445 copied = {}
403 for f in added:
446 for f in added:
404 src = renamed(f)
447 src = renamed(f)
405 if src:
448 if src:
406 copied[f] = src
449 copied[f] = src
407 srcs = [x[1] for x in copied.items()]
450 srcs = [x[1][0] for x in copied.items()]
408
451
409 all = modified + added + removed
452 all = modified + added + removed
410 all.sort()
453 all.sort()
411 for f in all:
454 for f in all:
412 to = None
455 to = None
413 tn = None
456 tn = None
414 dodiff = True
457 dodiff = True
415 if f in mmap:
458 if f in mmap:
416 to = repo.file(f).read(mmap[f])
459 to = getfile(f).read(mmap[f])
417 if f not in removed:
460 if f not in removed:
418 tn = read(f)
461 tn = read(f)
419 if opts.git:
462 if opts.git:
420 def gitmode(x):
463 def gitmode(x):
421 return x and '100755' or '100644'
464 return x and '100755' or '100644'
422 def addmodehdr(header, omode, nmode):
465 def addmodehdr(header, omode, nmode):
423 if omode != nmode:
466 if omode != nmode:
424 header.append('old mode %s\n' % omode)
467 header.append('old mode %s\n' % omode)
425 header.append('new mode %s\n' % nmode)
468 header.append('new mode %s\n' % nmode)
426
469
427 a, b = f, f
470 a, b = f, f
428 header = []
471 header = []
429 if f in added:
472 if f in added:
430 if node2:
473 if node2:
431 mode = gitmode(mmap2.execf(f))
474 mode = gitmode(mmap2.execf(f))
432 else:
475 else:
433 mode = gitmode(util.is_exec(repo.wjoin(f), None))
476 mode = gitmode(util.is_exec(repo.wjoin(f), None))
434 if f in copied:
477 if f in copied:
435 a = copied[f]
478 a, arev = copied[f]
436 omode = gitmode(mmap.execf(a))
479 omode = gitmode(mmap.execf(a))
437 addmodehdr(header, omode, mode)
480 addmodehdr(header, omode, mode)
438 op = a in removed and 'rename' or 'copy'
481 op = a in removed and 'rename' or 'copy'
439 header.append('%s from %s\n' % (op, a))
482 header.append('%s from %s\n' % (op, a))
440 header.append('%s to %s\n' % (op, f))
483 header.append('%s to %s\n' % (op, f))
441 to = repo.file(a).read(mmap[a])
484 to = getfile(a).read(arev)
442 else:
485 else:
443 header.append('new file mode %s\n' % mode)
486 header.append('new file mode %s\n' % mode)
444 elif f in removed:
487 elif f in removed:
445 if f in srcs:
488 if f in srcs:
446 dodiff = False
489 dodiff = False
447 else:
490 else:
448 mode = gitmode(mmap.execf(f))
491 mode = gitmode(mmap.execf(f))
449 header.append('deleted file mode %s\n' % mode)
492 header.append('deleted file mode %s\n' % mode)
450 else:
493 else:
451 omode = gitmode(mmap.execf(f))
494 omode = gitmode(mmap.execf(f))
452 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
495 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
453 addmodehdr(header, omode, nmode)
496 addmodehdr(header, omode, nmode)
454 r = None
497 r = None
455 if dodiff:
498 if dodiff:
456 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
499 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
457 fp.write(''.join(header))
500 fp.write(''.join(header))
458 if dodiff:
501 if dodiff:
459 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
502 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
460
503
461 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
504 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
462 opts=None):
505 opts=None):
463 '''export changesets as hg patches.'''
506 '''export changesets as hg patches.'''
464
507
465 total = len(revs)
508 total = len(revs)
466 revwidth = max(map(len, revs))
509 revwidth = max(map(len, revs))
467
510
468 def single(node, seqno, fp):
511 def single(node, seqno, fp):
469 parents = [p for p in repo.changelog.parents(node) if p != nullid]
512 parents = [p for p in repo.changelog.parents(node) if p != nullid]
470 if switch_parent:
513 if switch_parent:
471 parents.reverse()
514 parents.reverse()
472 prev = (parents and parents[0]) or nullid
515 prev = (parents and parents[0]) or nullid
473 change = repo.changelog.read(node)
516 change = repo.changelog.read(node)
474
517
475 if not fp:
518 if not fp:
476 fp = cmdutil.make_file(repo, template, node, total=total,
519 fp = cmdutil.make_file(repo, template, node, total=total,
477 seqno=seqno, revwidth=revwidth)
520 seqno=seqno, revwidth=revwidth)
478 if fp not in (sys.stdout, repo.ui):
521 if fp not in (sys.stdout, repo.ui):
479 repo.ui.note("%s\n" % fp.name)
522 repo.ui.note("%s\n" % fp.name)
480
523
481 fp.write("# HG changeset patch\n")
524 fp.write("# HG changeset patch\n")
482 fp.write("# User %s\n" % change[1])
525 fp.write("# User %s\n" % change[1])
483 fp.write("# Date %d %d\n" % change[2])
526 fp.write("# Date %d %d\n" % change[2])
484 fp.write("# Node ID %s\n" % hex(node))
527 fp.write("# Node ID %s\n" % hex(node))
485 fp.write("# Parent %s\n" % hex(prev))
528 fp.write("# Parent %s\n" % hex(prev))
486 if len(parents) > 1:
529 if len(parents) > 1:
487 fp.write("# Parent %s\n" % hex(parents[1]))
530 fp.write("# Parent %s\n" % hex(parents[1]))
488 fp.write(change[4].rstrip())
531 fp.write(change[4].rstrip())
489 fp.write("\n\n")
532 fp.write("\n\n")
490
533
491 diff(repo, prev, node, fp=fp, opts=opts)
534 diff(repo, prev, node, fp=fp, opts=opts)
492 if fp not in (sys.stdout, repo.ui):
535 if fp not in (sys.stdout, repo.ui):
493 fp.close()
536 fp.close()
494
537
495 for seqno, cset in enumerate(revs):
538 for seqno, cset in enumerate(revs):
496 single(cset, seqno, fp)
539 single(cset, seqno, fp)
@@ -1,128 +1,153 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 HGRCPATH=$HGTMP/.hgrc; export HGRCPATH
3 HGRCPATH=$HGTMP/.hgrc; export HGRCPATH
4 echo "[extensions]" >> $HGTMP/.hgrc
4 echo "[extensions]" >> $HGTMP/.hgrc
5 echo "mq=" >> $HGTMP/.hgrc
5 echo "mq=" >> $HGTMP/.hgrc
6
6
7 echo % help
7 echo % help
8 hg help mq
8 hg help mq
9
9
10 hg init a
10 hg init a
11 cd a
11 cd a
12 echo a > a
12 echo a > a
13 hg ci -Ama
13 hg ci -Ama
14
14
15 hg clone . ../k
15 hg clone . ../k
16
16
17 mkdir b
17 mkdir b
18 echo z > b/z
18 echo z > b/z
19 hg ci -Ama
19 hg ci -Ama
20
20
21 echo % qinit
21 echo % qinit
22
22
23 hg qinit
23 hg qinit
24
24
25 cd ..
25 cd ..
26 hg init b
26 hg init b
27
27
28 echo % -R qinit
28 echo % -R qinit
29
29
30 hg -R b qinit
30 hg -R b qinit
31
31
32 hg init c
32 hg init c
33
33
34 echo % qinit -c
34 echo % qinit -c
35
35
36 hg --cwd c qinit -c
36 hg --cwd c qinit -c
37 hg -R c/.hg/patches st
37 hg -R c/.hg/patches st
38
38
39 echo % qnew implies add
39 echo % qnew implies add
40
40
41 hg -R c qnew test.patch
41 hg -R c qnew test.patch
42 hg -R c/.hg/patches st
42 hg -R c/.hg/patches st
43
43
44 cd a
44 cd a
45
45
46 echo % qnew -m
46 echo % qnew -m
47
47
48 hg qnew -m 'foo bar' test.patch
48 hg qnew -m 'foo bar' test.patch
49 cat .hg/patches/test.patch
49 cat .hg/patches/test.patch
50
50
51 echo % qrefresh
51 echo % qrefresh
52
52
53 echo a >> a
53 echo a >> a
54 hg qrefresh
54 hg qrefresh
55 sed -e "s/^\(diff -r \)\([a-f0-9]* \)/\1 x/" \
55 sed -e "s/^\(diff -r \)\([a-f0-9]* \)/\1 x/" \
56 -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
56 -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
57 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/test.patch
57 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/test.patch
58
58
59 echo % qpop
59 echo % qpop
60
60
61 hg qpop
61 hg qpop
62
62
63 echo % qpush
63 echo % qpush
64
64
65 hg qpush
65 hg qpush
66
66
67 cd ..
67 cd ..
68
68
69 echo % pop/push outside repo
69 echo % pop/push outside repo
70
70
71 hg -R a qpop
71 hg -R a qpop
72 hg -R a qpush
72 hg -R a qpush
73
73
74 cd a
74 cd a
75 hg qnew test2.patch
75 hg qnew test2.patch
76
76
77 echo % qrefresh in subdir
77 echo % qrefresh in subdir
78
78
79 cd b
79 cd b
80 echo a > a
80 echo a > a
81 hg add a
81 hg add a
82 hg qrefresh
82 hg qrefresh
83
83
84 echo % pop/push -a in subdir
84 echo % pop/push -a in subdir
85
85
86 hg qpop -a
86 hg qpop -a
87 hg --traceback qpush -a
87 hg --traceback qpush -a
88
88
89 echo % qseries
89 echo % qseries
90 hg qseries
90 hg qseries
91
91
92 echo % qapplied
92 echo % qapplied
93 hg qapplied
93 hg qapplied
94
94
95 echo % qtop
95 echo % qtop
96 hg qtop
96 hg qtop
97
97
98 echo % qprev
98 echo % qprev
99 hg qprev
99 hg qprev
100
100
101 echo % qnext
101 echo % qnext
102 hg qnext
102 hg qnext
103
103
104 echo % pop, qnext, qprev, qapplied
104 echo % pop, qnext, qprev, qapplied
105 hg qpop
105 hg qpop
106 hg qnext
106 hg qnext
107 hg qprev
107 hg qprev
108 hg qapplied
108 hg qapplied
109
109
110 echo % commit should fail
110 echo % commit should fail
111 hg commit
111 hg commit
112
112
113 echo % push should fail
113 echo % push should fail
114 hg push ../../k
114 hg push ../../k
115
115
116 echo % qunapplied
116 echo % qunapplied
117 hg qunapplied
117 hg qunapplied
118
118
119 echo % push should succeed
119 echo % push should succeed
120 hg qpop -a
120 hg qpop -a
121 hg push ../../k
121 hg push ../../k
122
122
123 echo % strip
123 echo % strip
124 cd ../../b
124 cd ../../b
125 echo x>x
125 echo x>x
126 hg ci -Ama
126 hg ci -Ama
127 hg strip tip 2>&1 | sed 's/\(saving bundle to \).*/\1/'
127 hg strip tip 2>&1 | sed 's/\(saving bundle to \).*/\1/'
128 hg unbundle .hg/strip-backup/*
128 hg unbundle .hg/strip-backup/*
129
130 cat >>$HGTMP/.hgrc <<EOF
131 [diff]
132 git = True
133 EOF
134 cd ..
135 hg init git
136 cd git
137 hg qinit
138
139 hg qnew -m'new file' new
140 echo foo > new
141 chmod +x new
142 hg add new
143 hg qrefresh
144 sed -e "s/\(\(---\|+++\) [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/new
145
146 hg qnew -m'copy file' copy
147 hg cp new copy
148 hg qrefresh
149 sed -e "s/\(\(---\|+++\) [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/copy
150
151 hg qpop
152 hg qpush
153 hg qdiff
@@ -1,129 +1,148 b''
1 % help
1 % help
2 mq extension - patch management and development
2 mq extension - patch management and development
3
3
4 This extension lets you work with a stack of patches in a Mercurial
4 This extension lets you work with a stack of patches in a Mercurial
5 repository. It manages two stacks of patches - all known patches, and
5 repository. It manages two stacks of patches - all known patches, and
6 applied patches (subset of known patches).
6 applied patches (subset of known patches).
7
7
8 Known patches are represented as patch files in the .hg/patches
8 Known patches are represented as patch files in the .hg/patches
9 directory. Applied patches are both patch files and changesets.
9 directory. Applied patches are both patch files and changesets.
10
10
11 Common tasks (use "hg help command" for more details):
11 Common tasks (use "hg help command" for more details):
12
12
13 prepare repository to work with patches qinit
13 prepare repository to work with patches qinit
14 create new patch qnew
14 create new patch qnew
15 import existing patch qimport
15 import existing patch qimport
16
16
17 print patch series qseries
17 print patch series qseries
18 print applied patches qapplied
18 print applied patches qapplied
19 print name of top applied patch qtop
19 print name of top applied patch qtop
20
20
21 add known patch to applied stack qpush
21 add known patch to applied stack qpush
22 remove patch from applied stack qpop
22 remove patch from applied stack qpop
23 refresh contents of top applied patch qrefresh
23 refresh contents of top applied patch qrefresh
24
24
25 list of commands (use "hg help -v mq" to show aliases and global options):
25 list of commands (use "hg help -v mq" to show aliases and global options):
26
26
27 qapplied print the patches already applied
27 qapplied print the patches already applied
28 qclone clone main and patch repository at same time
28 qclone clone main and patch repository at same time
29 qcommit commit changes in the queue repository
29 qcommit commit changes in the queue repository
30 qdelete remove patches from queue
30 qdelete remove patches from queue
31 qdiff diff of the current patch
31 qdiff diff of the current patch
32 qfold fold the named patches into the current patch
32 qfold fold the named patches into the current patch
33 qguard set or print guards for a patch
33 qguard set or print guards for a patch
34 qheader Print the header of the topmost or specified patch
34 qheader Print the header of the topmost or specified patch
35 qimport import a patch
35 qimport import a patch
36 qinit init a new queue repository
36 qinit init a new queue repository
37 qnew create a new patch
37 qnew create a new patch
38 qnext print the name of the next patch
38 qnext print the name of the next patch
39 qpop pop the current patch off the stack
39 qpop pop the current patch off the stack
40 qprev print the name of the previous patch
40 qprev print the name of the previous patch
41 qpush push the next patch onto the stack
41 qpush push the next patch onto the stack
42 qrefresh update the current patch
42 qrefresh update the current patch
43 qrename rename a patch
43 qrename rename a patch
44 qrestore restore the queue state saved by a rev
44 qrestore restore the queue state saved by a rev
45 qsave save current queue state
45 qsave save current queue state
46 qselect set or print guarded patches to push
46 qselect set or print guarded patches to push
47 qseries print the entire series file
47 qseries print the entire series file
48 qtop print the name of the current patch
48 qtop print the name of the current patch
49 qunapplied print the patches not yet applied
49 qunapplied print the patches not yet applied
50 strip strip a revision and all later revs on the same branch
50 strip strip a revision and all later revs on the same branch
51 adding a
51 adding a
52 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
52 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
53 adding b/z
53 adding b/z
54 % qinit
54 % qinit
55 % -R qinit
55 % -R qinit
56 % qinit -c
56 % qinit -c
57 A .hgignore
57 A .hgignore
58 A series
58 A series
59 % qnew implies add
59 % qnew implies add
60 A .hgignore
60 A .hgignore
61 A series
61 A series
62 A test.patch
62 A test.patch
63 % qnew -m
63 % qnew -m
64 foo bar
64 foo bar
65 % qrefresh
65 % qrefresh
66 foo bar
66 foo bar
67
67
68 diff -r xa
68 diff -r xa
69 --- a/a
69 --- a/a
70 +++ b/a
70 +++ b/a
71 @@ -1,1 +1,2 @@ a
71 @@ -1,1 +1,2 @@ a
72 a
72 a
73 +a
73 +a
74 % qpop
74 % qpop
75 Patch queue now empty
75 Patch queue now empty
76 % qpush
76 % qpush
77 applying test.patch
77 applying test.patch
78 Now at: test.patch
78 Now at: test.patch
79 % pop/push outside repo
79 % pop/push outside repo
80 Patch queue now empty
80 Patch queue now empty
81 applying test.patch
81 applying test.patch
82 Now at: test.patch
82 Now at: test.patch
83 % qrefresh in subdir
83 % qrefresh in subdir
84 % pop/push -a in subdir
84 % pop/push -a in subdir
85 Patch queue now empty
85 Patch queue now empty
86 applying test.patch
86 applying test.patch
87 applying test2.patch
87 applying test2.patch
88 Now at: test2.patch
88 Now at: test2.patch
89 % qseries
89 % qseries
90 test.patch
90 test.patch
91 test2.patch
91 test2.patch
92 % qapplied
92 % qapplied
93 test.patch
93 test.patch
94 test2.patch
94 test2.patch
95 % qtop
95 % qtop
96 test2.patch
96 test2.patch
97 % qprev
97 % qprev
98 test.patch
98 test.patch
99 % qnext
99 % qnext
100 All patches applied
100 All patches applied
101 % pop, qnext, qprev, qapplied
101 % pop, qnext, qprev, qapplied
102 Now at: test.patch
102 Now at: test.patch
103 test2.patch
103 test2.patch
104 Only one patch applied
104 Only one patch applied
105 test.patch
105 test.patch
106 % commit should fail
106 % commit should fail
107 abort: cannot commit over an applied mq patch
107 abort: cannot commit over an applied mq patch
108 % push should fail
108 % push should fail
109 pushing to ../../k
109 pushing to ../../k
110 abort: source has mq patches applied
110 abort: source has mq patches applied
111 % qunapplied
111 % qunapplied
112 test2.patch
112 test2.patch
113 % push should succeed
113 % push should succeed
114 Patch queue now empty
114 Patch queue now empty
115 pushing to ../../k
115 pushing to ../../k
116 searching for changes
116 searching for changes
117 adding changesets
117 adding changesets
118 adding manifests
118 adding manifests
119 adding file changes
119 adding file changes
120 added 1 changesets with 1 changes to 1 files
120 added 1 changesets with 1 changes to 1 files
121 % strip
121 % strip
122 adding x
122 adding x
123 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
123 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
124 saving bundle to
124 saving bundle to
125 adding changesets
125 adding changesets
126 adding manifests
126 adding manifests
127 adding file changes
127 adding file changes
128 added 1 changesets with 1 changes to 1 files
128 added 1 changesets with 1 changes to 1 files
129 (run 'hg update' to get a working copy)
129 (run 'hg update' to get a working copy)
130 new file
131
132 diff --git a/new b/new
133 new file mode 100755
134 --- /dev/null
135 +++ b/new
136 @@ -0,0 +1,1 @@
137 +foo
138 copy file
139
140 diff --git a/new b/copy
141 copy from new
142 copy to copy
143 Now at: new
144 applying copy
145 Now at: copy
146 diff --git a/new b/copy
147 copy from new
148 copy to copy
General Comments 0
You need to be logged in to leave comments. Login now