##// END OF EJS Templates
mq: add join method
Vadim Gelfer -
r2819:766ecdc8 default
parent child Browse files
Show More
@@ -1,1680 +1,1680 b''
1
1
2 # queue.py - patch queues for mercurial
2 # queue.py - patch queues for mercurial
3 #
3 #
4 # Copyright 2005 Chris Mason <mason@suse.com>
4 # Copyright 2005 Chris Mason <mason@suse.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 '''patch management and development
9 '''patch management and development
10
10
11 This extension lets you work with a stack of patches in a Mercurial
11 This extension lets you work with a stack of patches in a Mercurial
12 repository. It manages two stacks of patches - all known patches, and
12 repository. It manages two stacks of patches - all known patches, and
13 applied patches (subset of known patches).
13 applied patches (subset of known patches).
14
14
15 Known patches are represented as patch files in the .hg/patches
15 Known patches are represented as patch files in the .hg/patches
16 directory. Applied patches are both patch files and changesets.
16 directory. Applied patches are both patch files and changesets.
17
17
18 Common tasks (use "hg help command" for more details):
18 Common tasks (use "hg help command" for more details):
19
19
20 prepare repository to work with patches qinit
20 prepare repository to work with patches qinit
21 create new patch qnew
21 create new patch qnew
22 import existing patch qimport
22 import existing patch qimport
23
23
24 print patch series qseries
24 print patch series qseries
25 print applied patches qapplied
25 print applied patches qapplied
26 print name of top applied patch qtop
26 print name of top applied patch qtop
27
27
28 add known patch to applied stack qpush
28 add known patch to applied stack qpush
29 remove patch from applied stack qpop
29 remove patch from applied stack qpop
30 refresh contents of top applied patch qrefresh
30 refresh contents of top applied patch qrefresh
31 '''
31 '''
32
32
33 from mercurial.demandload import *
33 from mercurial.demandload import *
34 demandload(globals(), "os sys re struct traceback errno bz2")
34 demandload(globals(), "os sys re struct traceback errno bz2")
35 from mercurial.i18n import gettext as _
35 from mercurial.i18n import gettext as _
36 from mercurial import ui, hg, revlog, commands, util
36 from mercurial import ui, hg, revlog, commands, util
37
37
38 versionstr = "0.45"
38 versionstr = "0.45"
39
39
40 commands.norepo += " qclone qversion"
40 commands.norepo += " qclone qversion"
41
41
42 class statusentry:
42 class statusentry:
43 def __init__(self, rev, name=None):
43 def __init__(self, rev, name=None):
44 if not name:
44 if not name:
45 fields = rev.split(':')
45 fields = rev.split(':')
46 if len(fields) == 2:
46 if len(fields) == 2:
47 self.rev, self.name = fields
47 self.rev, self.name = fields
48 else:
48 else:
49 self.rev, self.name = None, None
49 self.rev, self.name = None, None
50 else:
50 else:
51 self.rev, self.name = rev, name
51 self.rev, self.name = rev, name
52
52
53 def __str__(self):
53 def __str__(self):
54 return self.rev + ':' + self.name
54 return self.rev + ':' + self.name
55
55
56 class queue:
56 class queue:
57 def __init__(self, ui, path, patchdir=None):
57 def __init__(self, ui, path, patchdir=None):
58 self.basepath = path
58 self.basepath = path
59 if patchdir:
59 self.path = patchdir or os.path.join(path, "patches")
60 self.path = patchdir
61 else:
62 self.path = os.path.join(path, "patches")
63 self.opener = util.opener(self.path)
60 self.opener = util.opener(self.path)
64 self.ui = ui
61 self.ui = ui
65 self.applied = []
62 self.applied = []
66 self.full_series = []
63 self.full_series = []
67 self.applied_dirty = 0
64 self.applied_dirty = 0
68 self.series_dirty = 0
65 self.series_dirty = 0
69 self.series_path = "series"
66 self.series_path = "series"
70 self.status_path = "status"
67 self.status_path = "status"
71
68
72 if os.path.exists(os.path.join(self.path, self.series_path)):
69 if os.path.exists(self.join(self.series_path)):
73 self.full_series = self.opener(self.series_path).read().splitlines()
70 self.full_series = self.opener(self.series_path).read().splitlines()
74 self.parse_series()
71 self.parse_series()
75
72
76 if os.path.exists(os.path.join(self.path, self.status_path)):
73 if os.path.exists(self.join(self.status_path)):
77 self.applied = [statusentry(l)
74 self.applied = [statusentry(l)
78 for l in self.opener(self.status_path).read().splitlines()]
75 for l in self.opener(self.status_path).read().splitlines()]
79
76
77 def join(self, *p):
78 return os.path.join(self.path, *p)
79
80 def find_series(self, patch):
80 def find_series(self, patch):
81 pre = re.compile("(\s*)([^#]+)")
81 pre = re.compile("(\s*)([^#]+)")
82 index = 0
82 index = 0
83 for l in self.full_series:
83 for l in self.full_series:
84 m = pre.match(l)
84 m = pre.match(l)
85 if m:
85 if m:
86 s = m.group(2)
86 s = m.group(2)
87 s = s.rstrip()
87 s = s.rstrip()
88 if s == patch:
88 if s == patch:
89 return index
89 return index
90 index += 1
90 index += 1
91 return None
91 return None
92
92
93 def parse_series(self):
93 def parse_series(self):
94 self.series = []
94 self.series = []
95 for l in self.full_series:
95 for l in self.full_series:
96 s = l.split('#', 1)[0].strip()
96 s = l.split('#', 1)[0].strip()
97 if s:
97 if s:
98 self.series.append(s)
98 self.series.append(s)
99
99
100 def save_dirty(self):
100 def save_dirty(self):
101 def write_list(items, path):
101 def write_list(items, path):
102 fp = self.opener(path, 'w')
102 fp = self.opener(path, 'w')
103 for i in items:
103 for i in items:
104 print >> fp, i
104 print >> fp, i
105 fp.close()
105 fp.close()
106 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
106 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
107 if self.series_dirty: write_list(self.full_series, self.series_path)
107 if self.series_dirty: write_list(self.full_series, self.series_path)
108
108
109 def readheaders(self, patch):
109 def readheaders(self, patch):
110 def eatdiff(lines):
110 def eatdiff(lines):
111 while lines:
111 while lines:
112 l = lines[-1]
112 l = lines[-1]
113 if (l.startswith("diff -") or
113 if (l.startswith("diff -") or
114 l.startswith("Index:") or
114 l.startswith("Index:") or
115 l.startswith("===========")):
115 l.startswith("===========")):
116 del lines[-1]
116 del lines[-1]
117 else:
117 else:
118 break
118 break
119 def eatempty(lines):
119 def eatempty(lines):
120 while lines:
120 while lines:
121 l = lines[-1]
121 l = lines[-1]
122 if re.match('\s*$', l):
122 if re.match('\s*$', l):
123 del lines[-1]
123 del lines[-1]
124 else:
124 else:
125 break
125 break
126
126
127 pf = os.path.join(self.path, patch)
127 pf = self.join(patch)
128 message = []
128 message = []
129 comments = []
129 comments = []
130 user = None
130 user = None
131 date = None
131 date = None
132 format = None
132 format = None
133 subject = None
133 subject = None
134 diffstart = 0
134 diffstart = 0
135
135
136 for line in file(pf):
136 for line in file(pf):
137 line = line.rstrip()
137 line = line.rstrip()
138 if diffstart:
138 if diffstart:
139 if line.startswith('+++ '):
139 if line.startswith('+++ '):
140 diffstart = 2
140 diffstart = 2
141 break
141 break
142 if line.startswith("--- "):
142 if line.startswith("--- "):
143 diffstart = 1
143 diffstart = 1
144 continue
144 continue
145 elif format == "hgpatch":
145 elif format == "hgpatch":
146 # parse values when importing the result of an hg export
146 # parse values when importing the result of an hg export
147 if line.startswith("# User "):
147 if line.startswith("# User "):
148 user = line[7:]
148 user = line[7:]
149 elif line.startswith("# Date "):
149 elif line.startswith("# Date "):
150 date = line[7:]
150 date = line[7:]
151 elif not line.startswith("# ") and line:
151 elif not line.startswith("# ") and line:
152 message.append(line)
152 message.append(line)
153 format = None
153 format = None
154 elif line == '# HG changeset patch':
154 elif line == '# HG changeset patch':
155 format = "hgpatch"
155 format = "hgpatch"
156 elif (format != "tagdone" and (line.startswith("Subject: ") or
156 elif (format != "tagdone" and (line.startswith("Subject: ") or
157 line.startswith("subject: "))):
157 line.startswith("subject: "))):
158 subject = line[9:]
158 subject = line[9:]
159 format = "tag"
159 format = "tag"
160 elif (format != "tagdone" and (line.startswith("From: ") or
160 elif (format != "tagdone" and (line.startswith("From: ") or
161 line.startswith("from: "))):
161 line.startswith("from: "))):
162 user = line[6:]
162 user = line[6:]
163 format = "tag"
163 format = "tag"
164 elif format == "tag" and line == "":
164 elif format == "tag" and line == "":
165 # when looking for tags (subject: from: etc) they
165 # when looking for tags (subject: from: etc) they
166 # end once you find a blank line in the source
166 # end once you find a blank line in the source
167 format = "tagdone"
167 format = "tagdone"
168 elif message or line:
168 elif message or line:
169 message.append(line)
169 message.append(line)
170 comments.append(line)
170 comments.append(line)
171
171
172 eatdiff(message)
172 eatdiff(message)
173 eatdiff(comments)
173 eatdiff(comments)
174 eatempty(message)
174 eatempty(message)
175 eatempty(comments)
175 eatempty(comments)
176
176
177 # make sure message isn't empty
177 # make sure message isn't empty
178 if format and format.startswith("tag") and subject:
178 if format and format.startswith("tag") and subject:
179 message.insert(0, "")
179 message.insert(0, "")
180 message.insert(0, subject)
180 message.insert(0, subject)
181 return (message, comments, user, date, diffstart > 1)
181 return (message, comments, user, date, diffstart > 1)
182
182
183 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
183 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
184 # first try just applying the patch
184 # first try just applying the patch
185 (err, n) = self.apply(repo, [ patch ], update_status=False,
185 (err, n) = self.apply(repo, [ patch ], update_status=False,
186 strict=True, merge=rev, wlock=wlock)
186 strict=True, merge=rev, wlock=wlock)
187
187
188 if err == 0:
188 if err == 0:
189 return (err, n)
189 return (err, n)
190
190
191 if n is None:
191 if n is None:
192 raise util.Abort(_("apply failed for patch %s") % patch)
192 raise util.Abort(_("apply failed for patch %s") % patch)
193
193
194 self.ui.warn("patch didn't work out, merging %s\n" % patch)
194 self.ui.warn("patch didn't work out, merging %s\n" % patch)
195
195
196 # apply failed, strip away that rev and merge.
196 # apply failed, strip away that rev and merge.
197 hg.update(repo, head, allow=False, force=True, wlock=wlock)
197 hg.update(repo, head, allow=False, force=True, wlock=wlock)
198 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
198 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
199
199
200 c = repo.changelog.read(rev)
200 c = repo.changelog.read(rev)
201 ret = hg.update(repo, rev, allow=True, wlock=wlock)
201 ret = hg.update(repo, rev, allow=True, wlock=wlock)
202 if ret:
202 if ret:
203 raise util.Abort(_("update returned %d") % ret)
203 raise util.Abort(_("update returned %d") % ret)
204 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
204 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
205 if n == None:
205 if n == None:
206 raise util.Abort(_("repo commit failed"))
206 raise util.Abort(_("repo commit failed"))
207 try:
207 try:
208 message, comments, user, date, patchfound = mergeq.readheaders(patch)
208 message, comments, user, date, patchfound = mergeq.readheaders(patch)
209 except:
209 except:
210 raise util.Abort(_("unable to read %s") % patch)
210 raise util.Abort(_("unable to read %s") % patch)
211
211
212 patchf = self.opener(patch, "w")
212 patchf = self.opener(patch, "w")
213 if comments:
213 if comments:
214 comments = "\n".join(comments) + '\n\n'
214 comments = "\n".join(comments) + '\n\n'
215 patchf.write(comments)
215 patchf.write(comments)
216 commands.dodiff(patchf, self.ui, repo, head, n)
216 commands.dodiff(patchf, self.ui, repo, head, n)
217 patchf.close()
217 patchf.close()
218 return (0, n)
218 return (0, n)
219
219
220 def qparents(self, repo, rev=None):
220 def qparents(self, repo, rev=None):
221 if rev is None:
221 if rev is None:
222 (p1, p2) = repo.dirstate.parents()
222 (p1, p2) = repo.dirstate.parents()
223 if p2 == revlog.nullid:
223 if p2 == revlog.nullid:
224 return p1
224 return p1
225 if len(self.applied) == 0:
225 if len(self.applied) == 0:
226 return None
226 return None
227 return revlog.bin(self.applied[-1].rev)
227 return revlog.bin(self.applied[-1].rev)
228 pp = repo.changelog.parents(rev)
228 pp = repo.changelog.parents(rev)
229 if pp[1] != revlog.nullid:
229 if pp[1] != revlog.nullid:
230 arevs = [ x.rev for x in self.applied ]
230 arevs = [ x.rev for x in self.applied ]
231 p0 = revlog.hex(pp[0])
231 p0 = revlog.hex(pp[0])
232 p1 = revlog.hex(pp[1])
232 p1 = revlog.hex(pp[1])
233 if p0 in arevs:
233 if p0 in arevs:
234 return pp[0]
234 return pp[0]
235 if p1 in arevs:
235 if p1 in arevs:
236 return pp[1]
236 return pp[1]
237 return pp[0]
237 return pp[0]
238
238
239 def mergepatch(self, repo, mergeq, series, wlock):
239 def mergepatch(self, repo, mergeq, series, wlock):
240 if len(self.applied) == 0:
240 if len(self.applied) == 0:
241 # each of the patches merged in will have two parents. This
241 # each of the patches merged in will have two parents. This
242 # can confuse the qrefresh, qdiff, and strip code because it
242 # can confuse the qrefresh, qdiff, and strip code because it
243 # needs to know which parent is actually in the patch queue.
243 # needs to know which parent is actually in the patch queue.
244 # so, we insert a merge marker with only one parent. This way
244 # so, we insert a merge marker with only one parent. This way
245 # the first patch in the queue is never a merge patch
245 # the first patch in the queue is never a merge patch
246 #
246 #
247 pname = ".hg.patches.merge.marker"
247 pname = ".hg.patches.merge.marker"
248 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
248 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
249 wlock=wlock)
249 wlock=wlock)
250 self.applied.append(statusentry(revlog.hex(n), pname))
250 self.applied.append(statusentry(revlog.hex(n), pname))
251 self.applied_dirty = 1
251 self.applied_dirty = 1
252
252
253 head = self.qparents(repo)
253 head = self.qparents(repo)
254
254
255 for patch in series:
255 for patch in series:
256 patch = mergeq.lookup(patch, strict=True)
256 patch = mergeq.lookup(patch, strict=True)
257 if not patch:
257 if not patch:
258 self.ui.warn("patch %s does not exist\n" % patch)
258 self.ui.warn("patch %s does not exist\n" % patch)
259 return (1, None)
259 return (1, None)
260
260
261 info = mergeq.isapplied(patch)
261 info = mergeq.isapplied(patch)
262 if not info:
262 if not info:
263 self.ui.warn("patch %s is not applied\n" % patch)
263 self.ui.warn("patch %s is not applied\n" % patch)
264 return (1, None)
264 return (1, None)
265 rev = revlog.bin(info[1])
265 rev = revlog.bin(info[1])
266 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
266 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
267 if head:
267 if head:
268 self.applied.append(statusentry(revlog.hex(head), patch))
268 self.applied.append(statusentry(revlog.hex(head), patch))
269 self.applied_dirty = 1
269 self.applied_dirty = 1
270 if err:
270 if err:
271 return (err, head)
271 return (err, head)
272 return (0, head)
272 return (0, head)
273
273
274 def patch(self, repo, patchfile):
274 def patch(self, repo, patchfile):
275 '''Apply patchfile to the working directory.
275 '''Apply patchfile to the working directory.
276 patchfile: file name of patch'''
276 patchfile: file name of patch'''
277 try:
277 try:
278 pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
278 pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
279 f = os.popen("%s -d %s -p1 --no-backup-if-mismatch < %s" %
279 f = os.popen("%s -d %s -p1 --no-backup-if-mismatch < %s" %
280 (pp, util.shellquote(repo.root), util.shellquote(patchfile)))
280 (pp, util.shellquote(repo.root), util.shellquote(patchfile)))
281 except:
281 except:
282 self.ui.warn("patch failed, unable to continue (try -v)\n")
282 self.ui.warn("patch failed, unable to continue (try -v)\n")
283 return (None, [], False)
283 return (None, [], False)
284 files = []
284 files = []
285 fuzz = False
285 fuzz = False
286 for l in f:
286 for l in f:
287 l = l.rstrip('\r\n');
287 l = l.rstrip('\r\n');
288 if self.ui.verbose:
288 if self.ui.verbose:
289 self.ui.warn(l + "\n")
289 self.ui.warn(l + "\n")
290 if l[:14] == 'patching file ':
290 if l[:14] == 'patching file ':
291 pf = os.path.normpath(util.parse_patch_output(l))
291 pf = os.path.normpath(util.parse_patch_output(l))
292 if pf not in files:
292 if pf not in files:
293 files.append(pf)
293 files.append(pf)
294 printed_file = False
294 printed_file = False
295 file_str = l
295 file_str = l
296 elif l.find('with fuzz') >= 0:
296 elif l.find('with fuzz') >= 0:
297 if not printed_file:
297 if not printed_file:
298 self.ui.warn(file_str + '\n')
298 self.ui.warn(file_str + '\n')
299 printed_file = True
299 printed_file = True
300 self.ui.warn(l + '\n')
300 self.ui.warn(l + '\n')
301 fuzz = True
301 fuzz = True
302 elif l.find('saving rejects to file') >= 0:
302 elif l.find('saving rejects to file') >= 0:
303 self.ui.warn(l + '\n')
303 self.ui.warn(l + '\n')
304 elif l.find('FAILED') >= 0:
304 elif l.find('FAILED') >= 0:
305 if not printed_file:
305 if not printed_file:
306 self.ui.warn(file_str + '\n')
306 self.ui.warn(file_str + '\n')
307 printed_file = True
307 printed_file = True
308 self.ui.warn(l + '\n')
308 self.ui.warn(l + '\n')
309
309
310 return (not f.close(), files, fuzz)
310 return (not f.close(), files, fuzz)
311
311
312 def apply(self, repo, series, list=False, update_status=True,
312 def apply(self, repo, series, list=False, update_status=True,
313 strict=False, patchdir=None, merge=None, wlock=None):
313 strict=False, patchdir=None, merge=None, wlock=None):
314 # TODO unify with commands.py
314 # TODO unify with commands.py
315 if not patchdir:
315 if not patchdir:
316 patchdir = self.path
316 patchdir = self.path
317 err = 0
317 err = 0
318 if not wlock:
318 if not wlock:
319 wlock = repo.wlock()
319 wlock = repo.wlock()
320 lock = repo.lock()
320 lock = repo.lock()
321 tr = repo.transaction()
321 tr = repo.transaction()
322 n = None
322 n = None
323 for patch in series:
323 for patch in series:
324 self.ui.warn("applying %s\n" % patch)
324 self.ui.warn("applying %s\n" % patch)
325 pf = os.path.join(patchdir, patch)
325 pf = os.path.join(patchdir, patch)
326
326
327 try:
327 try:
328 message, comments, user, date, patchfound = self.readheaders(patch)
328 message, comments, user, date, patchfound = self.readheaders(patch)
329 except:
329 except:
330 self.ui.warn("Unable to read %s\n" % pf)
330 self.ui.warn("Unable to read %s\n" % pf)
331 err = 1
331 err = 1
332 break
332 break
333
333
334 if not message:
334 if not message:
335 message = "imported patch %s\n" % patch
335 message = "imported patch %s\n" % patch
336 else:
336 else:
337 if list:
337 if list:
338 message.append("\nimported patch %s" % patch)
338 message.append("\nimported patch %s" % patch)
339 message = '\n'.join(message)
339 message = '\n'.join(message)
340
340
341 (patcherr, files, fuzz) = self.patch(repo, pf)
341 (patcherr, files, fuzz) = self.patch(repo, pf)
342 patcherr = not patcherr
342 patcherr = not patcherr
343
343
344 if merge and len(files) > 0:
344 if merge and len(files) > 0:
345 # Mark as merged and update dirstate parent info
345 # Mark as merged and update dirstate parent info
346 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
346 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
347 p1, p2 = repo.dirstate.parents()
347 p1, p2 = repo.dirstate.parents()
348 repo.dirstate.setparents(p1, merge)
348 repo.dirstate.setparents(p1, merge)
349 if len(files) > 0:
349 if len(files) > 0:
350 cwd = repo.getcwd()
350 cwd = repo.getcwd()
351 cfiles = files
351 cfiles = files
352 if cwd:
352 if cwd:
353 cfiles = [util.pathto(cwd, f) for f in files]
353 cfiles = [util.pathto(cwd, f) for f in files]
354 commands.addremove_lock(self.ui, repo, cfiles,
354 commands.addremove_lock(self.ui, repo, cfiles,
355 opts={}, wlock=wlock)
355 opts={}, wlock=wlock)
356 n = repo.commit(files, message, user, date, force=1, lock=lock,
356 n = repo.commit(files, message, user, date, force=1, lock=lock,
357 wlock=wlock)
357 wlock=wlock)
358
358
359 if n == None:
359 if n == None:
360 raise util.Abort(_("repo commit failed"))
360 raise util.Abort(_("repo commit failed"))
361
361
362 if update_status:
362 if update_status:
363 self.applied.append(statusentry(revlog.hex(n), patch))
363 self.applied.append(statusentry(revlog.hex(n), patch))
364
364
365 if patcherr:
365 if patcherr:
366 if not patchfound:
366 if not patchfound:
367 self.ui.warn("patch %s is empty\n" % patch)
367 self.ui.warn("patch %s is empty\n" % patch)
368 err = 0
368 err = 0
369 else:
369 else:
370 self.ui.warn("patch failed, rejects left in working dir\n")
370 self.ui.warn("patch failed, rejects left in working dir\n")
371 err = 1
371 err = 1
372 break
372 break
373
373
374 if fuzz and strict:
374 if fuzz and strict:
375 self.ui.warn("fuzz found when applying patch, stopping\n")
375 self.ui.warn("fuzz found when applying patch, stopping\n")
376 err = 1
376 err = 1
377 break
377 break
378 tr.close()
378 tr.close()
379 return (err, n)
379 return (err, n)
380
380
381 def delete(self, repo, patch, force=False):
381 def delete(self, repo, patch, force=False):
382 patch = self.lookup(patch, strict=True)
382 patch = self.lookup(patch, strict=True)
383 info = self.isapplied(patch)
383 info = self.isapplied(patch)
384 if info:
384 if info:
385 raise util.Abort(_("cannot delete applied patch %s") % patch)
385 raise util.Abort(_("cannot delete applied patch %s") % patch)
386 if patch not in self.series:
386 if patch not in self.series:
387 raise util.Abort(_("patch %s not in series file") % patch)
387 raise util.Abort(_("patch %s not in series file") % patch)
388 if force:
388 if force:
389 r = self.qrepo()
389 r = self.qrepo()
390 if r:
390 if r:
391 r.remove([patch], True)
391 r.remove([patch], True)
392 else:
392 else:
393 os.unlink(os.path.join(self.path, patch))
393 os.unlink(self.join(patch))
394 i = self.find_series(patch)
394 i = self.find_series(patch)
395 del self.full_series[i]
395 del self.full_series[i]
396 self.parse_series()
396 self.parse_series()
397 self.series_dirty = 1
397 self.series_dirty = 1
398
398
399 def check_toppatch(self, repo):
399 def check_toppatch(self, repo):
400 if len(self.applied) > 0:
400 if len(self.applied) > 0:
401 top = revlog.bin(self.applied[-1].rev)
401 top = revlog.bin(self.applied[-1].rev)
402 pp = repo.dirstate.parents()
402 pp = repo.dirstate.parents()
403 if top not in pp:
403 if top not in pp:
404 raise util.Abort(_("queue top not at same revision as working directory"))
404 raise util.Abort(_("queue top not at same revision as working directory"))
405 return top
405 return top
406 return None
406 return None
407 def check_localchanges(self, repo):
407 def check_localchanges(self, repo):
408 (c, a, r, d, u) = repo.changes(None, None)
408 (c, a, r, d, u) = repo.changes(None, None)
409 if c or a or d or r:
409 if c or a or d or r:
410 raise util.Abort(_("local changes found, refresh first"))
410 raise util.Abort(_("local changes found, refresh first"))
411 def new(self, repo, patch, msg=None, force=None):
411 def new(self, repo, patch, msg=None, force=None):
412 if os.path.exists(os.path.join(self.path, patch)):
412 if os.path.exists(self.join(patch)):
413 raise util.Abort(_('patch "%s" already exists') % patch)
413 raise util.Abort(_('patch "%s" already exists') % patch)
414 commitfiles = []
414 commitfiles = []
415 (c, a, r, d, u) = repo.changes(None, None)
415 (c, a, r, d, u) = repo.changes(None, None)
416 if c or a or d or r:
416 if c or a or d or r:
417 if not force:
417 if not force:
418 raise util.Abort(_("local changes found, refresh first"))
418 raise util.Abort(_("local changes found, refresh first"))
419 commitfiles = c + a + r
419 commitfiles = c + a + r
420 self.check_toppatch(repo)
420 self.check_toppatch(repo)
421 wlock = repo.wlock()
421 wlock = repo.wlock()
422 insert = self.full_series_end()
422 insert = self.full_series_end()
423 if msg:
423 if msg:
424 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
424 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
425 wlock=wlock)
425 wlock=wlock)
426 else:
426 else:
427 n = repo.commit(commitfiles,
427 n = repo.commit(commitfiles,
428 "New patch: %s" % patch, force=True, wlock=wlock)
428 "New patch: %s" % patch, force=True, wlock=wlock)
429 if n == None:
429 if n == None:
430 raise util.Abort(_("repo commit failed"))
430 raise util.Abort(_("repo commit failed"))
431 self.full_series[insert:insert] = [patch]
431 self.full_series[insert:insert] = [patch]
432 self.applied.append(statusentry(revlog.hex(n), patch))
432 self.applied.append(statusentry(revlog.hex(n), patch))
433 self.parse_series()
433 self.parse_series()
434 self.series_dirty = 1
434 self.series_dirty = 1
435 self.applied_dirty = 1
435 self.applied_dirty = 1
436 p = self.opener(patch, "w")
436 p = self.opener(patch, "w")
437 if msg:
437 if msg:
438 msg = msg + "\n"
438 msg = msg + "\n"
439 p.write(msg)
439 p.write(msg)
440 p.close()
440 p.close()
441 wlock = None
441 wlock = None
442 r = self.qrepo()
442 r = self.qrepo()
443 if r: r.add([patch])
443 if r: r.add([patch])
444 if commitfiles:
444 if commitfiles:
445 self.refresh(repo, msg=None, short=True)
445 self.refresh(repo, msg=None, short=True)
446
446
447 def strip(self, repo, rev, update=True, backup="all", wlock=None):
447 def strip(self, repo, rev, update=True, backup="all", wlock=None):
448 def limitheads(chlog, stop):
448 def limitheads(chlog, stop):
449 """return the list of all nodes that have no children"""
449 """return the list of all nodes that have no children"""
450 p = {}
450 p = {}
451 h = []
451 h = []
452 stoprev = 0
452 stoprev = 0
453 if stop in chlog.nodemap:
453 if stop in chlog.nodemap:
454 stoprev = chlog.rev(stop)
454 stoprev = chlog.rev(stop)
455
455
456 for r in range(chlog.count() - 1, -1, -1):
456 for r in range(chlog.count() - 1, -1, -1):
457 n = chlog.node(r)
457 n = chlog.node(r)
458 if n not in p:
458 if n not in p:
459 h.append(n)
459 h.append(n)
460 if n == stop:
460 if n == stop:
461 break
461 break
462 if r < stoprev:
462 if r < stoprev:
463 break
463 break
464 for pn in chlog.parents(n):
464 for pn in chlog.parents(n):
465 p[pn] = 1
465 p[pn] = 1
466 return h
466 return h
467
467
468 def bundle(cg):
468 def bundle(cg):
469 backupdir = repo.join("strip-backup")
469 backupdir = repo.join("strip-backup")
470 if not os.path.isdir(backupdir):
470 if not os.path.isdir(backupdir):
471 os.mkdir(backupdir)
471 os.mkdir(backupdir)
472 name = os.path.join(backupdir, "%s" % revlog.short(rev))
472 name = os.path.join(backupdir, "%s" % revlog.short(rev))
473 name = savename(name)
473 name = savename(name)
474 self.ui.warn("saving bundle to %s\n" % name)
474 self.ui.warn("saving bundle to %s\n" % name)
475 # TODO, exclusive open
475 # TODO, exclusive open
476 f = open(name, "wb")
476 f = open(name, "wb")
477 try:
477 try:
478 f.write("HG10")
478 f.write("HG10")
479 z = bz2.BZ2Compressor(9)
479 z = bz2.BZ2Compressor(9)
480 while 1:
480 while 1:
481 chunk = cg.read(4096)
481 chunk = cg.read(4096)
482 if not chunk:
482 if not chunk:
483 break
483 break
484 f.write(z.compress(chunk))
484 f.write(z.compress(chunk))
485 f.write(z.flush())
485 f.write(z.flush())
486 except:
486 except:
487 os.unlink(name)
487 os.unlink(name)
488 raise
488 raise
489 f.close()
489 f.close()
490 return name
490 return name
491
491
492 def stripall(rev, revnum):
492 def stripall(rev, revnum):
493 cl = repo.changelog
493 cl = repo.changelog
494 c = cl.read(rev)
494 c = cl.read(rev)
495 mm = repo.manifest.read(c[0])
495 mm = repo.manifest.read(c[0])
496 seen = {}
496 seen = {}
497
497
498 for x in xrange(revnum, cl.count()):
498 for x in xrange(revnum, cl.count()):
499 c = cl.read(cl.node(x))
499 c = cl.read(cl.node(x))
500 for f in c[3]:
500 for f in c[3]:
501 if f in seen:
501 if f in seen:
502 continue
502 continue
503 seen[f] = 1
503 seen[f] = 1
504 if f in mm:
504 if f in mm:
505 filerev = mm[f]
505 filerev = mm[f]
506 else:
506 else:
507 filerev = 0
507 filerev = 0
508 seen[f] = filerev
508 seen[f] = filerev
509 # we go in two steps here so the strip loop happens in a
509 # we go in two steps here so the strip loop happens in a
510 # sensible order. When stripping many files, this helps keep
510 # sensible order. When stripping many files, this helps keep
511 # our disk access patterns under control.
511 # our disk access patterns under control.
512 seen_list = seen.keys()
512 seen_list = seen.keys()
513 seen_list.sort()
513 seen_list.sort()
514 for f in seen_list:
514 for f in seen_list:
515 ff = repo.file(f)
515 ff = repo.file(f)
516 filerev = seen[f]
516 filerev = seen[f]
517 if filerev != 0:
517 if filerev != 0:
518 if filerev in ff.nodemap:
518 if filerev in ff.nodemap:
519 filerev = ff.rev(filerev)
519 filerev = ff.rev(filerev)
520 else:
520 else:
521 filerev = 0
521 filerev = 0
522 ff.strip(filerev, revnum)
522 ff.strip(filerev, revnum)
523
523
524 if not wlock:
524 if not wlock:
525 wlock = repo.wlock()
525 wlock = repo.wlock()
526 lock = repo.lock()
526 lock = repo.lock()
527 chlog = repo.changelog
527 chlog = repo.changelog
528 # TODO delete the undo files, and handle undo of merge sets
528 # TODO delete the undo files, and handle undo of merge sets
529 pp = chlog.parents(rev)
529 pp = chlog.parents(rev)
530 revnum = chlog.rev(rev)
530 revnum = chlog.rev(rev)
531
531
532 if update:
532 if update:
533 (c, a, r, d, u) = repo.changes(None, None)
533 (c, a, r, d, u) = repo.changes(None, None)
534 if c or a or d or r:
534 if c or a or d or r:
535 raise util.Abort(_("local changes found"))
535 raise util.Abort(_("local changes found"))
536 urev = self.qparents(repo, rev)
536 urev = self.qparents(repo, rev)
537 hg.update(repo, urev, allow=False, force=True, wlock=wlock)
537 hg.update(repo, urev, allow=False, force=True, wlock=wlock)
538 repo.dirstate.write()
538 repo.dirstate.write()
539
539
540 # save is a list of all the branches we are truncating away
540 # save is a list of all the branches we are truncating away
541 # that we actually want to keep. changegroup will be used
541 # that we actually want to keep. changegroup will be used
542 # to preserve them and add them back after the truncate
542 # to preserve them and add them back after the truncate
543 saveheads = []
543 saveheads = []
544 savebases = {}
544 savebases = {}
545
545
546 heads = limitheads(chlog, rev)
546 heads = limitheads(chlog, rev)
547 seen = {}
547 seen = {}
548
548
549 # search through all the heads, finding those where the revision
549 # search through all the heads, finding those where the revision
550 # we want to strip away is an ancestor. Also look for merges
550 # we want to strip away is an ancestor. Also look for merges
551 # that might be turned into new heads by the strip.
551 # that might be turned into new heads by the strip.
552 while heads:
552 while heads:
553 h = heads.pop()
553 h = heads.pop()
554 n = h
554 n = h
555 while True:
555 while True:
556 seen[n] = 1
556 seen[n] = 1
557 pp = chlog.parents(n)
557 pp = chlog.parents(n)
558 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
558 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
559 if pp[1] not in seen:
559 if pp[1] not in seen:
560 heads.append(pp[1])
560 heads.append(pp[1])
561 if pp[0] == revlog.nullid:
561 if pp[0] == revlog.nullid:
562 break
562 break
563 if chlog.rev(pp[0]) < revnum:
563 if chlog.rev(pp[0]) < revnum:
564 break
564 break
565 n = pp[0]
565 n = pp[0]
566 if n == rev:
566 if n == rev:
567 break
567 break
568 r = chlog.reachable(h, rev)
568 r = chlog.reachable(h, rev)
569 if rev not in r:
569 if rev not in r:
570 saveheads.append(h)
570 saveheads.append(h)
571 for x in r:
571 for x in r:
572 if chlog.rev(x) > revnum:
572 if chlog.rev(x) > revnum:
573 savebases[x] = 1
573 savebases[x] = 1
574
574
575 # create a changegroup for all the branches we need to keep
575 # create a changegroup for all the branches we need to keep
576 if backup == "all":
576 if backup == "all":
577 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
577 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
578 bundle(backupch)
578 bundle(backupch)
579 if saveheads:
579 if saveheads:
580 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
580 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
581 chgrpfile = bundle(backupch)
581 chgrpfile = bundle(backupch)
582
582
583 stripall(rev, revnum)
583 stripall(rev, revnum)
584
584
585 change = chlog.read(rev)
585 change = chlog.read(rev)
586 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
586 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
587 chlog.strip(revnum, revnum)
587 chlog.strip(revnum, revnum)
588 if saveheads:
588 if saveheads:
589 self.ui.status("adding branch\n")
589 self.ui.status("adding branch\n")
590 commands.unbundle(self.ui, repo, chgrpfile, update=False)
590 commands.unbundle(self.ui, repo, chgrpfile, update=False)
591 if backup != "strip":
591 if backup != "strip":
592 os.unlink(chgrpfile)
592 os.unlink(chgrpfile)
593
593
594 def isapplied(self, patch):
594 def isapplied(self, patch):
595 """returns (index, rev, patch)"""
595 """returns (index, rev, patch)"""
596 for i in xrange(len(self.applied)):
596 for i in xrange(len(self.applied)):
597 a = self.applied[i]
597 a = self.applied[i]
598 if a.name == patch:
598 if a.name == patch:
599 return (i, a.rev, a.name)
599 return (i, a.rev, a.name)
600 return None
600 return None
601
601
602 # if the exact patch name does not exist, we try a few
602 # if the exact patch name does not exist, we try a few
603 # variations. If strict is passed, we try only #1
603 # variations. If strict is passed, we try only #1
604 #
604 #
605 # 1) a number to indicate an offset in the series file
605 # 1) a number to indicate an offset in the series file
606 # 2) a unique substring of the patch name was given
606 # 2) a unique substring of the patch name was given
607 # 3) patchname[-+]num to indicate an offset in the series file
607 # 3) patchname[-+]num to indicate an offset in the series file
608 def lookup(self, patch, strict=False):
608 def lookup(self, patch, strict=False):
609 def partial_name(s):
609 def partial_name(s):
610 if s in self.series:
610 if s in self.series:
611 return s
611 return s
612 matches = [x for x in self.series if s in x]
612 matches = [x for x in self.series if s in x]
613 if len(matches) > 1:
613 if len(matches) > 1:
614 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
614 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
615 for m in matches:
615 for m in matches:
616 self.ui.warn(' %s\n' % m)
616 self.ui.warn(' %s\n' % m)
617 return None
617 return None
618 if matches:
618 if matches:
619 return matches[0]
619 return matches[0]
620 if len(self.series) > 0 and len(self.applied) > 0:
620 if len(self.series) > 0 and len(self.applied) > 0:
621 if s == 'qtip':
621 if s == 'qtip':
622 return self.series[self.series_end()-1]
622 return self.series[self.series_end()-1]
623 if s == 'qbase':
623 if s == 'qbase':
624 return self.series[0]
624 return self.series[0]
625 return None
625 return None
626 if patch == None:
626 if patch == None:
627 return None
627 return None
628
628
629 # we don't want to return a partial match until we make
629 # we don't want to return a partial match until we make
630 # sure the file name passed in does not exist (checked below)
630 # sure the file name passed in does not exist (checked below)
631 res = partial_name(patch)
631 res = partial_name(patch)
632 if res and res == patch:
632 if res and res == patch:
633 return res
633 return res
634
634
635 if not os.path.isfile(os.path.join(self.path, patch)):
635 if not os.path.isfile(self.join(patch)):
636 try:
636 try:
637 sno = int(patch)
637 sno = int(patch)
638 except(ValueError, OverflowError):
638 except(ValueError, OverflowError):
639 pass
639 pass
640 else:
640 else:
641 if sno < len(self.series):
641 if sno < len(self.series):
642 patch = self.series[sno]
642 patch = self.series[sno]
643 return patch
643 return patch
644 if not strict:
644 if not strict:
645 # return any partial match made above
645 # return any partial match made above
646 if res:
646 if res:
647 return res
647 return res
648 minus = patch.rsplit('-', 1)
648 minus = patch.rsplit('-', 1)
649 if len(minus) > 1:
649 if len(minus) > 1:
650 res = partial_name(minus[0])
650 res = partial_name(minus[0])
651 if res:
651 if res:
652 i = self.series.index(res)
652 i = self.series.index(res)
653 try:
653 try:
654 off = int(minus[1] or 1)
654 off = int(minus[1] or 1)
655 except(ValueError, OverflowError):
655 except(ValueError, OverflowError):
656 pass
656 pass
657 else:
657 else:
658 if i - off >= 0:
658 if i - off >= 0:
659 return self.series[i - off]
659 return self.series[i - off]
660 plus = patch.rsplit('+', 1)
660 plus = patch.rsplit('+', 1)
661 if len(plus) > 1:
661 if len(plus) > 1:
662 res = partial_name(plus[0])
662 res = partial_name(plus[0])
663 if res:
663 if res:
664 i = self.series.index(res)
664 i = self.series.index(res)
665 try:
665 try:
666 off = int(plus[1] or 1)
666 off = int(plus[1] or 1)
667 except(ValueError, OverflowError):
667 except(ValueError, OverflowError):
668 pass
668 pass
669 else:
669 else:
670 if i + off < len(self.series):
670 if i + off < len(self.series):
671 return self.series[i + off]
671 return self.series[i + off]
672 raise util.Abort(_("patch %s not in series") % patch)
672 raise util.Abort(_("patch %s not in series") % patch)
673
673
674 def push(self, repo, patch=None, force=False, list=False,
674 def push(self, repo, patch=None, force=False, list=False,
675 mergeq=None, wlock=None):
675 mergeq=None, wlock=None):
676 if not wlock:
676 if not wlock:
677 wlock = repo.wlock()
677 wlock = repo.wlock()
678 patch = self.lookup(patch)
678 patch = self.lookup(patch)
679 if patch and self.isapplied(patch):
679 if patch and self.isapplied(patch):
680 self.ui.warn(_("patch %s is already applied\n") % patch)
680 self.ui.warn(_("patch %s is already applied\n") % patch)
681 sys.exit(1)
681 sys.exit(1)
682 if self.series_end() == len(self.series):
682 if self.series_end() == len(self.series):
683 self.ui.warn(_("patch series fully applied\n"))
683 self.ui.warn(_("patch series fully applied\n"))
684 sys.exit(1)
684 sys.exit(1)
685 if not force:
685 if not force:
686 self.check_localchanges(repo)
686 self.check_localchanges(repo)
687
687
688 self.applied_dirty = 1;
688 self.applied_dirty = 1;
689 start = self.series_end()
689 start = self.series_end()
690 if start > 0:
690 if start > 0:
691 self.check_toppatch(repo)
691 self.check_toppatch(repo)
692 if not patch:
692 if not patch:
693 patch = self.series[start]
693 patch = self.series[start]
694 end = start + 1
694 end = start + 1
695 else:
695 else:
696 end = self.series.index(patch, start) + 1
696 end = self.series.index(patch, start) + 1
697 s = self.series[start:end]
697 s = self.series[start:end]
698 if mergeq:
698 if mergeq:
699 ret = self.mergepatch(repo, mergeq, s, wlock)
699 ret = self.mergepatch(repo, mergeq, s, wlock)
700 else:
700 else:
701 ret = self.apply(repo, s, list, wlock=wlock)
701 ret = self.apply(repo, s, list, wlock=wlock)
702 top = self.applied[-1].name
702 top = self.applied[-1].name
703 if ret[0]:
703 if ret[0]:
704 self.ui.write("Errors during apply, please fix and refresh %s\n" %
704 self.ui.write("Errors during apply, please fix and refresh %s\n" %
705 top)
705 top)
706 else:
706 else:
707 self.ui.write("Now at: %s\n" % top)
707 self.ui.write("Now at: %s\n" % top)
708 return ret[0]
708 return ret[0]
709
709
710 def pop(self, repo, patch=None, force=False, update=True, all=False,
710 def pop(self, repo, patch=None, force=False, update=True, all=False,
711 wlock=None):
711 wlock=None):
712 def getfile(f, rev):
712 def getfile(f, rev):
713 t = repo.file(f).read(rev)
713 t = repo.file(f).read(rev)
714 try:
714 try:
715 repo.wfile(f, "w").write(t)
715 repo.wfile(f, "w").write(t)
716 except IOError:
716 except IOError:
717 try:
717 try:
718 os.makedirs(os.path.dirname(repo.wjoin(f)))
718 os.makedirs(os.path.dirname(repo.wjoin(f)))
719 except OSError, err:
719 except OSError, err:
720 if err.errno != errno.EEXIST: raise
720 if err.errno != errno.EEXIST: raise
721 repo.wfile(f, "w").write(t)
721 repo.wfile(f, "w").write(t)
722
722
723 if not wlock:
723 if not wlock:
724 wlock = repo.wlock()
724 wlock = repo.wlock()
725 if patch:
725 if patch:
726 # index, rev, patch
726 # index, rev, patch
727 info = self.isapplied(patch)
727 info = self.isapplied(patch)
728 if not info:
728 if not info:
729 patch = self.lookup(patch)
729 patch = self.lookup(patch)
730 info = self.isapplied(patch)
730 info = self.isapplied(patch)
731 if not info:
731 if not info:
732 raise util.Abort(_("patch %s is not applied") % patch)
732 raise util.Abort(_("patch %s is not applied") % patch)
733 if len(self.applied) == 0:
733 if len(self.applied) == 0:
734 self.ui.warn(_("no patches applied\n"))
734 self.ui.warn(_("no patches applied\n"))
735 sys.exit(1)
735 sys.exit(1)
736
736
737 if not update:
737 if not update:
738 parents = repo.dirstate.parents()
738 parents = repo.dirstate.parents()
739 rr = [ revlog.bin(x.rev) for x in self.applied ]
739 rr = [ revlog.bin(x.rev) for x in self.applied ]
740 for p in parents:
740 for p in parents:
741 if p in rr:
741 if p in rr:
742 self.ui.warn("qpop: forcing dirstate update\n")
742 self.ui.warn("qpop: forcing dirstate update\n")
743 update = True
743 update = True
744
744
745 if not force and update:
745 if not force and update:
746 self.check_localchanges(repo)
746 self.check_localchanges(repo)
747
747
748 self.applied_dirty = 1;
748 self.applied_dirty = 1;
749 end = len(self.applied)
749 end = len(self.applied)
750 if not patch:
750 if not patch:
751 if all:
751 if all:
752 popi = 0
752 popi = 0
753 else:
753 else:
754 popi = len(self.applied) - 1
754 popi = len(self.applied) - 1
755 else:
755 else:
756 popi = info[0] + 1
756 popi = info[0] + 1
757 if popi >= end:
757 if popi >= end:
758 self.ui.warn("qpop: %s is already at the top\n" % patch)
758 self.ui.warn("qpop: %s is already at the top\n" % patch)
759 return
759 return
760 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
760 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
761
761
762 start = info[0]
762 start = info[0]
763 rev = revlog.bin(info[1])
763 rev = revlog.bin(info[1])
764
764
765 # we know there are no local changes, so we can make a simplified
765 # we know there are no local changes, so we can make a simplified
766 # form of hg.update.
766 # form of hg.update.
767 if update:
767 if update:
768 top = self.check_toppatch(repo)
768 top = self.check_toppatch(repo)
769 qp = self.qparents(repo, rev)
769 qp = self.qparents(repo, rev)
770 changes = repo.changelog.read(qp)
770 changes = repo.changelog.read(qp)
771 mf1 = repo.manifest.readflags(changes[0])
771 mf1 = repo.manifest.readflags(changes[0])
772 mmap = repo.manifest.read(changes[0])
772 mmap = repo.manifest.read(changes[0])
773 (c, a, r, d, u) = repo.changes(qp, top)
773 (c, a, r, d, u) = repo.changes(qp, top)
774 if d:
774 if d:
775 raise util.Abort("deletions found between repo revs")
775 raise util.Abort("deletions found between repo revs")
776 for f in c:
776 for f in c:
777 getfile(f, mmap[f])
777 getfile(f, mmap[f])
778 for f in r:
778 for f in r:
779 getfile(f, mmap[f])
779 getfile(f, mmap[f])
780 util.set_exec(repo.wjoin(f), mf1[f])
780 util.set_exec(repo.wjoin(f), mf1[f])
781 repo.dirstate.update(c + r, 'n')
781 repo.dirstate.update(c + r, 'n')
782 for f in a:
782 for f in a:
783 try: os.unlink(repo.wjoin(f))
783 try: os.unlink(repo.wjoin(f))
784 except: raise
784 except: raise
785 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
785 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
786 except: pass
786 except: pass
787 if a:
787 if a:
788 repo.dirstate.forget(a)
788 repo.dirstate.forget(a)
789 repo.dirstate.setparents(qp, revlog.nullid)
789 repo.dirstate.setparents(qp, revlog.nullid)
790 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
790 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
791 del self.applied[start:end]
791 del self.applied[start:end]
792 if len(self.applied):
792 if len(self.applied):
793 self.ui.write("Now at: %s\n" % self.applied[-1].name)
793 self.ui.write("Now at: %s\n" % self.applied[-1].name)
794 else:
794 else:
795 self.ui.write("Patch queue now empty\n")
795 self.ui.write("Patch queue now empty\n")
796
796
797 def diff(self, repo, files):
797 def diff(self, repo, files):
798 top = self.check_toppatch(repo)
798 top = self.check_toppatch(repo)
799 if not top:
799 if not top:
800 self.ui.write("No patches applied\n")
800 self.ui.write("No patches applied\n")
801 return
801 return
802 qp = self.qparents(repo, top)
802 qp = self.qparents(repo, top)
803 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
803 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
804
804
805 def refresh(self, repo, msg=None, short=False):
805 def refresh(self, repo, msg=None, short=False):
806 if len(self.applied) == 0:
806 if len(self.applied) == 0:
807 self.ui.write("No patches applied\n")
807 self.ui.write("No patches applied\n")
808 return
808 return
809 wlock = repo.wlock()
809 wlock = repo.wlock()
810 self.check_toppatch(repo)
810 self.check_toppatch(repo)
811 (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
811 (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
812 top = revlog.bin(top)
812 top = revlog.bin(top)
813 cparents = repo.changelog.parents(top)
813 cparents = repo.changelog.parents(top)
814 patchparent = self.qparents(repo, top)
814 patchparent = self.qparents(repo, top)
815 message, comments, user, date, patchfound = self.readheaders(patch)
815 message, comments, user, date, patchfound = self.readheaders(patch)
816
816
817 patchf = self.opener(patch, "w")
817 patchf = self.opener(patch, "w")
818 msg = msg.rstrip()
818 msg = msg.rstrip()
819 if msg:
819 if msg:
820 if comments:
820 if comments:
821 # Remove existing message.
821 # Remove existing message.
822 ci = 0
822 ci = 0
823 for mi in range(len(message)):
823 for mi in range(len(message)):
824 while message[mi] != comments[ci]:
824 while message[mi] != comments[ci]:
825 ci += 1
825 ci += 1
826 del comments[ci]
826 del comments[ci]
827 comments.append(msg)
827 comments.append(msg)
828 if comments:
828 if comments:
829 comments = "\n".join(comments) + '\n\n'
829 comments = "\n".join(comments) + '\n\n'
830 patchf.write(comments)
830 patchf.write(comments)
831
831
832 tip = repo.changelog.tip()
832 tip = repo.changelog.tip()
833 if top == tip:
833 if top == tip:
834 # if the top of our patch queue is also the tip, there is an
834 # if the top of our patch queue is also the tip, there is an
835 # optimization here. We update the dirstate in place and strip
835 # optimization here. We update the dirstate in place and strip
836 # off the tip commit. Then just commit the current directory
836 # off the tip commit. Then just commit the current directory
837 # tree. We can also send repo.commit the list of files
837 # tree. We can also send repo.commit the list of files
838 # changed to speed up the diff
838 # changed to speed up the diff
839 #
839 #
840 # in short mode, we only diff the files included in the
840 # in short mode, we only diff the files included in the
841 # patch already
841 # patch already
842 #
842 #
843 # this should really read:
843 # this should really read:
844 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
844 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
845 # but we do it backwards to take advantage of manifest/chlog
845 # but we do it backwards to take advantage of manifest/chlog
846 # caching against the next repo.changes call
846 # caching against the next repo.changes call
847 #
847 #
848 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
848 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
849 if short:
849 if short:
850 filelist = cc + aa + dd
850 filelist = cc + aa + dd
851 else:
851 else:
852 filelist = None
852 filelist = None
853 (c, a, r, d, u) = repo.changes(None, None, filelist)
853 (c, a, r, d, u) = repo.changes(None, None, filelist)
854
854
855 # we might end up with files that were added between tip and
855 # we might end up with files that were added between tip and
856 # the dirstate parent, but then changed in the local dirstate.
856 # the dirstate parent, but then changed in the local dirstate.
857 # in this case, we want them to only show up in the added section
857 # in this case, we want them to only show up in the added section
858 for x in c:
858 for x in c:
859 if x not in aa:
859 if x not in aa:
860 cc.append(x)
860 cc.append(x)
861 # we might end up with files added by the local dirstate that
861 # we might end up with files added by the local dirstate that
862 # were deleted by the patch. In this case, they should only
862 # were deleted by the patch. In this case, they should only
863 # show up in the changed section.
863 # show up in the changed section.
864 for x in a:
864 for x in a:
865 if x in dd:
865 if x in dd:
866 del dd[dd.index(x)]
866 del dd[dd.index(x)]
867 cc.append(x)
867 cc.append(x)
868 else:
868 else:
869 aa.append(x)
869 aa.append(x)
870 # make sure any files deleted in the local dirstate
870 # make sure any files deleted in the local dirstate
871 # are not in the add or change column of the patch
871 # are not in the add or change column of the patch
872 forget = []
872 forget = []
873 for x in d + r:
873 for x in d + r:
874 if x in aa:
874 if x in aa:
875 del aa[aa.index(x)]
875 del aa[aa.index(x)]
876 forget.append(x)
876 forget.append(x)
877 continue
877 continue
878 elif x in cc:
878 elif x in cc:
879 del cc[cc.index(x)]
879 del cc[cc.index(x)]
880 dd.append(x)
880 dd.append(x)
881
881
882 c = list(util.unique(cc))
882 c = list(util.unique(cc))
883 r = list(util.unique(dd))
883 r = list(util.unique(dd))
884 a = list(util.unique(aa))
884 a = list(util.unique(aa))
885 filelist = list(util.unique(c + r + a ))
885 filelist = list(util.unique(c + r + a ))
886 commands.dodiff(patchf, self.ui, repo, patchparent, None,
886 commands.dodiff(patchf, self.ui, repo, patchparent, None,
887 filelist, changes=(c, a, r, [], u))
887 filelist, changes=(c, a, r, [], u))
888 patchf.close()
888 patchf.close()
889
889
890 changes = repo.changelog.read(tip)
890 changes = repo.changelog.read(tip)
891 repo.dirstate.setparents(*cparents)
891 repo.dirstate.setparents(*cparents)
892 repo.dirstate.update(a, 'a')
892 repo.dirstate.update(a, 'a')
893 repo.dirstate.update(r, 'r')
893 repo.dirstate.update(r, 'r')
894 repo.dirstate.update(c, 'n')
894 repo.dirstate.update(c, 'n')
895 repo.dirstate.forget(forget)
895 repo.dirstate.forget(forget)
896
896
897 if not msg:
897 if not msg:
898 if not message:
898 if not message:
899 message = "patch queue: %s\n" % patch
899 message = "patch queue: %s\n" % patch
900 else:
900 else:
901 message = "\n".join(message)
901 message = "\n".join(message)
902 else:
902 else:
903 message = msg
903 message = msg
904
904
905 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
905 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
906 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
906 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
907 self.applied[-1] = statusentry(revlog.hex(n), patch)
907 self.applied[-1] = statusentry(revlog.hex(n), patch)
908 self.applied_dirty = 1
908 self.applied_dirty = 1
909 else:
909 else:
910 commands.dodiff(patchf, self.ui, repo, patchparent, None)
910 commands.dodiff(patchf, self.ui, repo, patchparent, None)
911 patchf.close()
911 patchf.close()
912 self.pop(repo, force=True, wlock=wlock)
912 self.pop(repo, force=True, wlock=wlock)
913 self.push(repo, force=True, wlock=wlock)
913 self.push(repo, force=True, wlock=wlock)
914
914
915 def init(self, repo, create=False):
915 def init(self, repo, create=False):
916 if os.path.isdir(self.path):
916 if os.path.isdir(self.path):
917 raise util.Abort(_("patch queue directory already exists"))
917 raise util.Abort(_("patch queue directory already exists"))
918 os.mkdir(self.path)
918 os.mkdir(self.path)
919 if create:
919 if create:
920 return self.qrepo(create=True)
920 return self.qrepo(create=True)
921
921
922 def unapplied(self, repo, patch=None):
922 def unapplied(self, repo, patch=None):
923 if patch and patch not in self.series:
923 if patch and patch not in self.series:
924 raise util.Abort(_("patch %s is not in series file") % patch)
924 raise util.Abort(_("patch %s is not in series file") % patch)
925 if not patch:
925 if not patch:
926 start = self.series_end()
926 start = self.series_end()
927 else:
927 else:
928 start = self.series.index(patch) + 1
928 start = self.series.index(patch) + 1
929 return [(i, self.series[i]) for i in xrange(start, len(self.series))]
929 return [(i, self.series[i]) for i in xrange(start, len(self.series))]
930
930
931 def qseries(self, repo, missing=None, summary=False):
931 def qseries(self, repo, missing=None, summary=False):
932 start = self.series_end()
932 start = self.series_end()
933 if not missing:
933 if not missing:
934 for i in range(len(self.series)):
934 for i in range(len(self.series)):
935 patch = self.series[i]
935 patch = self.series[i]
936 if self.ui.verbose:
936 if self.ui.verbose:
937 if i < start:
937 if i < start:
938 status = 'A'
938 status = 'A'
939 else:
939 else:
940 status = 'U'
940 status = 'U'
941 self.ui.write('%d %s ' % (i, status))
941 self.ui.write('%d %s ' % (i, status))
942 if summary:
942 if summary:
943 msg = self.readheaders(patch)[0]
943 msg = self.readheaders(patch)[0]
944 msg = msg and ': ' + msg[0] or ': '
944 msg = msg and ': ' + msg[0] or ': '
945 else:
945 else:
946 msg = ''
946 msg = ''
947 self.ui.write('%s%s\n' % (patch, msg))
947 self.ui.write('%s%s\n' % (patch, msg))
948 else:
948 else:
949 msng_list = []
949 msng_list = []
950 for root, dirs, files in os.walk(self.path):
950 for root, dirs, files in os.walk(self.path):
951 d = root[len(self.path) + 1:]
951 d = root[len(self.path) + 1:]
952 for f in files:
952 for f in files:
953 fl = os.path.join(d, f)
953 fl = os.path.join(d, f)
954 if (fl not in self.series and
954 if (fl not in self.series and
955 fl not in (self.status_path, self.series_path)
955 fl not in (self.status_path, self.series_path)
956 and not fl.startswith('.')):
956 and not fl.startswith('.')):
957 msng_list.append(fl)
957 msng_list.append(fl)
958 msng_list.sort()
958 msng_list.sort()
959 for x in msng_list:
959 for x in msng_list:
960 if self.ui.verbose:
960 if self.ui.verbose:
961 self.ui.write("D ")
961 self.ui.write("D ")
962 self.ui.write("%s\n" % x)
962 self.ui.write("%s\n" % x)
963
963
964 def issaveline(self, l):
964 def issaveline(self, l):
965 if l.name == '.hg.patches.save.line':
965 if l.name == '.hg.patches.save.line':
966 return True
966 return True
967
967
968 def qrepo(self, create=False):
968 def qrepo(self, create=False):
969 if create or os.path.isdir(os.path.join(self.path, ".hg")):
969 if create or os.path.isdir(self.join(".hg")):
970 return hg.repository(self.ui, path=self.path, create=create)
970 return hg.repository(self.ui, path=self.path, create=create)
971
971
972 def restore(self, repo, rev, delete=None, qupdate=None):
972 def restore(self, repo, rev, delete=None, qupdate=None):
973 c = repo.changelog.read(rev)
973 c = repo.changelog.read(rev)
974 desc = c[4].strip()
974 desc = c[4].strip()
975 lines = desc.splitlines()
975 lines = desc.splitlines()
976 i = 0
976 i = 0
977 datastart = None
977 datastart = None
978 series = []
978 series = []
979 applied = []
979 applied = []
980 qpp = None
980 qpp = None
981 for i in xrange(0, len(lines)):
981 for i in xrange(0, len(lines)):
982 if lines[i] == 'Patch Data:':
982 if lines[i] == 'Patch Data:':
983 datastart = i + 1
983 datastart = i + 1
984 elif lines[i].startswith('Dirstate:'):
984 elif lines[i].startswith('Dirstate:'):
985 l = lines[i].rstrip()
985 l = lines[i].rstrip()
986 l = l[10:].split(' ')
986 l = l[10:].split(' ')
987 qpp = [ hg.bin(x) for x in l ]
987 qpp = [ hg.bin(x) for x in l ]
988 elif datastart != None:
988 elif datastart != None:
989 l = lines[i].rstrip()
989 l = lines[i].rstrip()
990 se = statusentry(l)
990 se = statusentry(l)
991 file_ = se.name
991 file_ = se.name
992 if se.rev:
992 if se.rev:
993 applied.append(se)
993 applied.append(se)
994 series.append(file_)
994 series.append(file_)
995 if datastart == None:
995 if datastart == None:
996 self.ui.warn("No saved patch data found\n")
996 self.ui.warn("No saved patch data found\n")
997 return 1
997 return 1
998 self.ui.warn("restoring status: %s\n" % lines[0])
998 self.ui.warn("restoring status: %s\n" % lines[0])
999 self.full_series = series
999 self.full_series = series
1000 self.applied = applied
1000 self.applied = applied
1001 self.parse_series()
1001 self.parse_series()
1002 self.series_dirty = 1
1002 self.series_dirty = 1
1003 self.applied_dirty = 1
1003 self.applied_dirty = 1
1004 heads = repo.changelog.heads()
1004 heads = repo.changelog.heads()
1005 if delete:
1005 if delete:
1006 if rev not in heads:
1006 if rev not in heads:
1007 self.ui.warn("save entry has children, leaving it alone\n")
1007 self.ui.warn("save entry has children, leaving it alone\n")
1008 else:
1008 else:
1009 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1009 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1010 pp = repo.dirstate.parents()
1010 pp = repo.dirstate.parents()
1011 if rev in pp:
1011 if rev in pp:
1012 update = True
1012 update = True
1013 else:
1013 else:
1014 update = False
1014 update = False
1015 self.strip(repo, rev, update=update, backup='strip')
1015 self.strip(repo, rev, update=update, backup='strip')
1016 if qpp:
1016 if qpp:
1017 self.ui.warn("saved queue repository parents: %s %s\n" %
1017 self.ui.warn("saved queue repository parents: %s %s\n" %
1018 (hg.short(qpp[0]), hg.short(qpp[1])))
1018 (hg.short(qpp[0]), hg.short(qpp[1])))
1019 if qupdate:
1019 if qupdate:
1020 print "queue directory updating"
1020 print "queue directory updating"
1021 r = self.qrepo()
1021 r = self.qrepo()
1022 if not r:
1022 if not r:
1023 self.ui.warn("Unable to load queue repository\n")
1023 self.ui.warn("Unable to load queue repository\n")
1024 return 1
1024 return 1
1025 hg.update(r, qpp[0], allow=False, force=True)
1025 hg.update(r, qpp[0], allow=False, force=True)
1026
1026
1027 def save(self, repo, msg=None):
1027 def save(self, repo, msg=None):
1028 if len(self.applied) == 0:
1028 if len(self.applied) == 0:
1029 self.ui.warn("save: no patches applied, exiting\n")
1029 self.ui.warn("save: no patches applied, exiting\n")
1030 return 1
1030 return 1
1031 if self.issaveline(self.applied[-1]):
1031 if self.issaveline(self.applied[-1]):
1032 self.ui.warn("status is already saved\n")
1032 self.ui.warn("status is already saved\n")
1033 return 1
1033 return 1
1034
1034
1035 ar = [ ':' + x for x in self.full_series ]
1035 ar = [ ':' + x for x in self.full_series ]
1036 if not msg:
1036 if not msg:
1037 msg = "hg patches saved state"
1037 msg = "hg patches saved state"
1038 else:
1038 else:
1039 msg = "hg patches: " + msg.rstrip('\r\n')
1039 msg = "hg patches: " + msg.rstrip('\r\n')
1040 r = self.qrepo()
1040 r = self.qrepo()
1041 if r:
1041 if r:
1042 pp = r.dirstate.parents()
1042 pp = r.dirstate.parents()
1043 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1043 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1044 msg += "\n\nPatch Data:\n"
1044 msg += "\n\nPatch Data:\n"
1045 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1045 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1046 "\n".join(ar) + '\n' or "")
1046 "\n".join(ar) + '\n' or "")
1047 n = repo.commit(None, text, user=None, force=1)
1047 n = repo.commit(None, text, user=None, force=1)
1048 if not n:
1048 if not n:
1049 self.ui.warn("repo commit failed\n")
1049 self.ui.warn("repo commit failed\n")
1050 return 1
1050 return 1
1051 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1051 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1052 self.applied_dirty = 1
1052 self.applied_dirty = 1
1053
1053
1054 def full_series_end(self):
1054 def full_series_end(self):
1055 if len(self.applied) > 0:
1055 if len(self.applied) > 0:
1056 p = self.applied[-1].name
1056 p = self.applied[-1].name
1057 end = self.find_series(p)
1057 end = self.find_series(p)
1058 if end == None:
1058 if end == None:
1059 return len(self.full_series)
1059 return len(self.full_series)
1060 return end + 1
1060 return end + 1
1061 return 0
1061 return 0
1062
1062
1063 def series_end(self):
1063 def series_end(self):
1064 end = 0
1064 end = 0
1065 if len(self.applied) > 0:
1065 if len(self.applied) > 0:
1066 p = self.applied[-1].name
1066 p = self.applied[-1].name
1067 try:
1067 try:
1068 end = self.series.index(p)
1068 end = self.series.index(p)
1069 except ValueError:
1069 except ValueError:
1070 return 0
1070 return 0
1071 return end + 1
1071 return end + 1
1072 return end
1072 return end
1073
1073
1074 def qapplied(self, repo, patch=None):
1074 def qapplied(self, repo, patch=None):
1075 if patch and patch not in self.series:
1075 if patch and patch not in self.series:
1076 raise util.Abort(_("patch %s is not in series file") % patch)
1076 raise util.Abort(_("patch %s is not in series file") % patch)
1077 if not patch:
1077 if not patch:
1078 end = len(self.applied)
1078 end = len(self.applied)
1079 else:
1079 else:
1080 end = self.series.index(patch) + 1
1080 end = self.series.index(patch) + 1
1081 for x in xrange(end):
1081 for x in xrange(end):
1082 p = self.appliedname(x)
1082 p = self.appliedname(x)
1083 self.ui.write("%s\n" % p)
1083 self.ui.write("%s\n" % p)
1084
1084
1085 def appliedname(self, index):
1085 def appliedname(self, index):
1086 pname = self.applied[index].name
1086 pname = self.applied[index].name
1087 if not self.ui.verbose:
1087 if not self.ui.verbose:
1088 p = pname
1088 p = pname
1089 else:
1089 else:
1090 p = str(self.series.index(pname)) + " " + p
1090 p = str(self.series.index(pname)) + " " + p
1091 return p
1091 return p
1092
1092
1093 def top(self, repo):
1093 def top(self, repo):
1094 if len(self.applied):
1094 if len(self.applied):
1095 p = self.appliedname(-1)
1095 p = self.appliedname(-1)
1096 self.ui.write(p + '\n')
1096 self.ui.write(p + '\n')
1097 else:
1097 else:
1098 self.ui.write("No patches applied\n")
1098 self.ui.write("No patches applied\n")
1099
1099
1100 def next(self, repo):
1100 def next(self, repo):
1101 end = self.series_end()
1101 end = self.series_end()
1102 if end == len(self.series):
1102 if end == len(self.series):
1103 self.ui.write("All patches applied\n")
1103 self.ui.write("All patches applied\n")
1104 else:
1104 else:
1105 p = self.series[end]
1105 p = self.series[end]
1106 if self.ui.verbose:
1106 if self.ui.verbose:
1107 self.ui.write("%d " % self.series.index(p))
1107 self.ui.write("%d " % self.series.index(p))
1108 self.ui.write(p + '\n')
1108 self.ui.write(p + '\n')
1109
1109
1110 def prev(self, repo):
1110 def prev(self, repo):
1111 if len(self.applied) > 1:
1111 if len(self.applied) > 1:
1112 p = self.appliedname(-2)
1112 p = self.appliedname(-2)
1113 self.ui.write(p + '\n')
1113 self.ui.write(p + '\n')
1114 elif len(self.applied) == 1:
1114 elif len(self.applied) == 1:
1115 self.ui.write("Only one patch applied\n")
1115 self.ui.write("Only one patch applied\n")
1116 else:
1116 else:
1117 self.ui.write("No patches applied\n")
1117 self.ui.write("No patches applied\n")
1118
1118
1119 def qimport(self, repo, files, patch=None, existing=None, force=None):
1119 def qimport(self, repo, files, patch=None, existing=None, force=None):
1120 if len(files) > 1 and patch:
1120 if len(files) > 1 and patch:
1121 raise util.Abort(_('option "-n" not valid when importing multiple '
1121 raise util.Abort(_('option "-n" not valid when importing multiple '
1122 'files'))
1122 'files'))
1123 i = 0
1123 i = 0
1124 added = []
1124 added = []
1125 for filename in files:
1125 for filename in files:
1126 if existing:
1126 if existing:
1127 if not patch:
1127 if not patch:
1128 patch = filename
1128 patch = filename
1129 if not os.path.isfile(os.path.join(self.path, patch)):
1129 if not os.path.isfile(self.join(patch)):
1130 raise util.Abort(_("patch %s does not exist") % patch)
1130 raise util.Abort(_("patch %s does not exist") % patch)
1131 else:
1131 else:
1132 try:
1132 try:
1133 text = file(filename).read()
1133 text = file(filename).read()
1134 except IOError:
1134 except IOError:
1135 raise util.Abort(_("unable to read %s") % patch)
1135 raise util.Abort(_("unable to read %s") % patch)
1136 if not patch:
1136 if not patch:
1137 patch = os.path.split(filename)[1]
1137 patch = os.path.split(filename)[1]
1138 if not force and os.path.exists(os.path.join(self.path, patch)):
1138 if not force and os.path.exists(self.join(patch)):
1139 raise util.Abort(_('patch "%s" already exists') % patch)
1139 raise util.Abort(_('patch "%s" already exists') % patch)
1140 patchf = self.opener(patch, "w")
1140 patchf = self.opener(patch, "w")
1141 patchf.write(text)
1141 patchf.write(text)
1142 if patch in self.series:
1142 if patch in self.series:
1143 raise util.Abort(_('patch %s is already in the series file')
1143 raise util.Abort(_('patch %s is already in the series file')
1144 % patch)
1144 % patch)
1145 index = self.full_series_end() + i
1145 index = self.full_series_end() + i
1146 self.full_series[index:index] = [patch]
1146 self.full_series[index:index] = [patch]
1147 self.parse_series()
1147 self.parse_series()
1148 self.ui.warn("adding %s to series file\n" % patch)
1148 self.ui.warn("adding %s to series file\n" % patch)
1149 i += 1
1149 i += 1
1150 added.append(patch)
1150 added.append(patch)
1151 patch = None
1151 patch = None
1152 self.series_dirty = 1
1152 self.series_dirty = 1
1153 qrepo = self.qrepo()
1153 qrepo = self.qrepo()
1154 if qrepo:
1154 if qrepo:
1155 qrepo.add(added)
1155 qrepo.add(added)
1156
1156
1157 def delete(ui, repo, patch, **opts):
1157 def delete(ui, repo, patch, **opts):
1158 """remove a patch from the series file
1158 """remove a patch from the series file
1159
1159
1160 The patch must not be applied.
1160 The patch must not be applied.
1161 With -f, deletes the patch file as well as the series entry."""
1161 With -f, deletes the patch file as well as the series entry."""
1162 q = repo.mq
1162 q = repo.mq
1163 q.delete(repo, patch, force=opts.get('force'))
1163 q.delete(repo, patch, force=opts.get('force'))
1164 q.save_dirty()
1164 q.save_dirty()
1165 return 0
1165 return 0
1166
1166
1167 def applied(ui, repo, patch=None, **opts):
1167 def applied(ui, repo, patch=None, **opts):
1168 """print the patches already applied"""
1168 """print the patches already applied"""
1169 repo.mq.qapplied(repo, patch)
1169 repo.mq.qapplied(repo, patch)
1170 return 0
1170 return 0
1171
1171
1172 def unapplied(ui, repo, patch=None, **opts):
1172 def unapplied(ui, repo, patch=None, **opts):
1173 """print the patches not yet applied"""
1173 """print the patches not yet applied"""
1174 for i, p in repo.mq.unapplied(repo, patch):
1174 for i, p in repo.mq.unapplied(repo, patch):
1175 if ui.verbose:
1175 if ui.verbose:
1176 ui.write("%d " % i)
1176 ui.write("%d " % i)
1177 ui.write("%s\n" % p)
1177 ui.write("%s\n" % p)
1178
1178
1179 def qimport(ui, repo, *filename, **opts):
1179 def qimport(ui, repo, *filename, **opts):
1180 """import a patch"""
1180 """import a patch"""
1181 q = repo.mq
1181 q = repo.mq
1182 q.qimport(repo, filename, patch=opts['name'],
1182 q.qimport(repo, filename, patch=opts['name'],
1183 existing=opts['existing'], force=opts['force'])
1183 existing=opts['existing'], force=opts['force'])
1184 q.save_dirty()
1184 q.save_dirty()
1185 return 0
1185 return 0
1186
1186
1187 def init(ui, repo, **opts):
1187 def init(ui, repo, **opts):
1188 """init a new queue repository
1188 """init a new queue repository
1189
1189
1190 The queue repository is unversioned by default. If -c is
1190 The queue repository is unversioned by default. If -c is
1191 specified, qinit will create a separate nested repository
1191 specified, qinit will create a separate nested repository
1192 for patches. Use qcommit to commit changes to this queue
1192 for patches. Use qcommit to commit changes to this queue
1193 repository."""
1193 repository."""
1194 q = repo.mq
1194 q = repo.mq
1195 r = q.init(repo, create=opts['create_repo'])
1195 r = q.init(repo, create=opts['create_repo'])
1196 q.save_dirty()
1196 q.save_dirty()
1197 if r:
1197 if r:
1198 fp = r.wopener('.hgignore', 'w')
1198 fp = r.wopener('.hgignore', 'w')
1199 print >> fp, 'syntax: glob'
1199 print >> fp, 'syntax: glob'
1200 print >> fp, 'status'
1200 print >> fp, 'status'
1201 fp.close()
1201 fp.close()
1202 r.wopener('series', 'w').close()
1202 r.wopener('series', 'w').close()
1203 r.add(['.hgignore', 'series'])
1203 r.add(['.hgignore', 'series'])
1204 return 0
1204 return 0
1205
1205
1206 def clone(ui, source, dest=None, **opts):
1206 def clone(ui, source, dest=None, **opts):
1207 '''clone main and patch repository at same time
1207 '''clone main and patch repository at same time
1208
1208
1209 If source is local, destination will have no patches applied. If
1209 If source is local, destination will have no patches applied. If
1210 source is remote, this command can not check if patches are
1210 source is remote, this command can not check if patches are
1211 applied in source, so cannot guarantee that patches are not
1211 applied in source, so cannot guarantee that patches are not
1212 applied in destination. If you clone remote repository, be sure
1212 applied in destination. If you clone remote repository, be sure
1213 before that it has no patches applied.
1213 before that it has no patches applied.
1214
1214
1215 Source patch repository is looked for in <src>/.hg/patches by
1215 Source patch repository is looked for in <src>/.hg/patches by
1216 default. Use -p <url> to change.
1216 default. Use -p <url> to change.
1217 '''
1217 '''
1218 commands.setremoteconfig(ui, opts)
1218 commands.setremoteconfig(ui, opts)
1219 if dest is None:
1219 if dest is None:
1220 dest = hg.defaultdest(source)
1220 dest = hg.defaultdest(source)
1221 sr = hg.repository(ui, ui.expandpath(source))
1221 sr = hg.repository(ui, ui.expandpath(source))
1222 qbase, destrev = None, None
1222 qbase, destrev = None, None
1223 if sr.local():
1223 if sr.local():
1224 reposetup(ui, sr)
1224 reposetup(ui, sr)
1225 if sr.mq.applied:
1225 if sr.mq.applied:
1226 qbase = revlog.bin(sr.mq.applied[0].rev)
1226 qbase = revlog.bin(sr.mq.applied[0].rev)
1227 if not hg.islocal(dest):
1227 if not hg.islocal(dest):
1228 destrev = sr.parents(qbase)[0]
1228 destrev = sr.parents(qbase)[0]
1229 ui.note(_('cloning main repo\n'))
1229 ui.note(_('cloning main repo\n'))
1230 sr, dr = hg.clone(ui, sr, dest,
1230 sr, dr = hg.clone(ui, sr, dest,
1231 pull=opts['pull'],
1231 pull=opts['pull'],
1232 rev=destrev,
1232 rev=destrev,
1233 update=False,
1233 update=False,
1234 stream=opts['uncompressed'])
1234 stream=opts['uncompressed'])
1235 ui.note(_('cloning patch repo\n'))
1235 ui.note(_('cloning patch repo\n'))
1236 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1236 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1237 dr.url() + '/.hg/patches',
1237 dr.url() + '/.hg/patches',
1238 pull=opts['pull'],
1238 pull=opts['pull'],
1239 update=not opts['noupdate'],
1239 update=not opts['noupdate'],
1240 stream=opts['uncompressed'])
1240 stream=opts['uncompressed'])
1241 if dr.local():
1241 if dr.local():
1242 if qbase:
1242 if qbase:
1243 ui.note(_('stripping applied patches from destination repo\n'))
1243 ui.note(_('stripping applied patches from destination repo\n'))
1244 reposetup(ui, dr)
1244 reposetup(ui, dr)
1245 dr.mq.strip(dr, qbase, update=False, backup=None)
1245 dr.mq.strip(dr, qbase, update=False, backup=None)
1246 if not opts['noupdate']:
1246 if not opts['noupdate']:
1247 ui.note(_('updating destination repo\n'))
1247 ui.note(_('updating destination repo\n'))
1248 hg.update(dr, dr.changelog.tip())
1248 hg.update(dr, dr.changelog.tip())
1249
1249
1250 def commit(ui, repo, *pats, **opts):
1250 def commit(ui, repo, *pats, **opts):
1251 """commit changes in the queue repository"""
1251 """commit changes in the queue repository"""
1252 q = repo.mq
1252 q = repo.mq
1253 r = q.qrepo()
1253 r = q.qrepo()
1254 if not r: raise util.Abort('no queue repository')
1254 if not r: raise util.Abort('no queue repository')
1255 commands.commit(r.ui, r, *pats, **opts)
1255 commands.commit(r.ui, r, *pats, **opts)
1256
1256
1257 def series(ui, repo, **opts):
1257 def series(ui, repo, **opts):
1258 """print the entire series file"""
1258 """print the entire series file"""
1259 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1259 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1260 return 0
1260 return 0
1261
1261
1262 def top(ui, repo, **opts):
1262 def top(ui, repo, **opts):
1263 """print the name of the current patch"""
1263 """print the name of the current patch"""
1264 repo.mq.top(repo)
1264 repo.mq.top(repo)
1265 return 0
1265 return 0
1266
1266
1267 def next(ui, repo, **opts):
1267 def next(ui, repo, **opts):
1268 """print the name of the next patch"""
1268 """print the name of the next patch"""
1269 repo.mq.next(repo)
1269 repo.mq.next(repo)
1270 return 0
1270 return 0
1271
1271
1272 def prev(ui, repo, **opts):
1272 def prev(ui, repo, **opts):
1273 """print the name of the previous patch"""
1273 """print the name of the previous patch"""
1274 repo.mq.prev(repo)
1274 repo.mq.prev(repo)
1275 return 0
1275 return 0
1276
1276
1277 def new(ui, repo, patch, **opts):
1277 def new(ui, repo, patch, **opts):
1278 """create a new patch
1278 """create a new patch
1279
1279
1280 qnew creates a new patch on top of the currently-applied patch
1280 qnew creates a new patch on top of the currently-applied patch
1281 (if any). It will refuse to run if there are any outstanding
1281 (if any). It will refuse to run if there are any outstanding
1282 changes unless -f is specified, in which case the patch will
1282 changes unless -f is specified, in which case the patch will
1283 be initialised with them.
1283 be initialised with them.
1284
1284
1285 -m or -l set the patch header as well as the commit message.
1285 -m or -l set the patch header as well as the commit message.
1286 If neither is specified, the patch header is empty and the
1286 If neither is specified, the patch header is empty and the
1287 commit message is 'New patch: PATCH'"""
1287 commit message is 'New patch: PATCH'"""
1288 q = repo.mq
1288 q = repo.mq
1289 message = commands.logmessage(opts)
1289 message = commands.logmessage(opts)
1290 q.new(repo, patch, msg=message, force=opts['force'])
1290 q.new(repo, patch, msg=message, force=opts['force'])
1291 q.save_dirty()
1291 q.save_dirty()
1292 return 0
1292 return 0
1293
1293
1294 def refresh(ui, repo, **opts):
1294 def refresh(ui, repo, **opts):
1295 """update the current patch"""
1295 """update the current patch"""
1296 q = repo.mq
1296 q = repo.mq
1297 message = commands.logmessage(opts)
1297 message = commands.logmessage(opts)
1298 if opts['edit']:
1298 if opts['edit']:
1299 if message:
1299 if message:
1300 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1300 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1301 patch = q.applied[-1].name
1301 patch = q.applied[-1].name
1302 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1302 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1303 message = ui.edit('\n'.join(message), user or ui.username())
1303 message = ui.edit('\n'.join(message), user or ui.username())
1304 q.refresh(repo, msg=message, short=opts['short'])
1304 q.refresh(repo, msg=message, short=opts['short'])
1305 q.save_dirty()
1305 q.save_dirty()
1306 return 0
1306 return 0
1307
1307
1308 def diff(ui, repo, *files, **opts):
1308 def diff(ui, repo, *files, **opts):
1309 """diff of the current patch"""
1309 """diff of the current patch"""
1310 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1310 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1311 repo.mq.diff(repo, list(files))
1311 repo.mq.diff(repo, list(files))
1312 return 0
1312 return 0
1313
1313
1314 def fold(ui, repo, *files, **opts):
1314 def fold(ui, repo, *files, **opts):
1315 """fold the named patches into the current patch
1315 """fold the named patches into the current patch
1316
1316
1317 Patches must not yet be applied. Each patch will be successively
1317 Patches must not yet be applied. Each patch will be successively
1318 applied to the current patch in the order given. If all the
1318 applied to the current patch in the order given. If all the
1319 patches apply successfully, the current patch will be refreshed
1319 patches apply successfully, the current patch will be refreshed
1320 with the new cumulative patch, and the folded patches will
1320 with the new cumulative patch, and the folded patches will
1321 be deleted. With -f/--force, the folded patch files will
1321 be deleted. With -f/--force, the folded patch files will
1322 be removed afterwards.
1322 be removed afterwards.
1323
1323
1324 The header for each folded patch will be concatenated with
1324 The header for each folded patch will be concatenated with
1325 the current patch header, separated by a line of '* * *'."""
1325 the current patch header, separated by a line of '* * *'."""
1326
1326
1327 q = repo.mq
1327 q = repo.mq
1328
1328
1329 if not files:
1329 if not files:
1330 raise util.Abort(_('qfold requires at least one patch name'))
1330 raise util.Abort(_('qfold requires at least one patch name'))
1331 if not q.check_toppatch(repo):
1331 if not q.check_toppatch(repo):
1332 raise util.Abort(_('No patches applied\n'))
1332 raise util.Abort(_('No patches applied\n'))
1333
1333
1334 message = commands.logmessage(opts)
1334 message = commands.logmessage(opts)
1335 if opts['edit']:
1335 if opts['edit']:
1336 if message:
1336 if message:
1337 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1337 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1338
1338
1339 parent = q.lookup('qtip')
1339 parent = q.lookup('qtip')
1340 patches = []
1340 patches = []
1341 messages = []
1341 messages = []
1342 for f in files:
1342 for f in files:
1343 patch = q.lookup(f)
1343 patch = q.lookup(f)
1344 if patch in patches or patch == parent:
1344 if patch in patches or patch == parent:
1345 ui.warn(_('Skipping already folded patch %s') % patch)
1345 ui.warn(_('Skipping already folded patch %s') % patch)
1346 if q.isapplied(patch):
1346 if q.isapplied(patch):
1347 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1347 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1348 patches.append(patch)
1348 patches.append(patch)
1349
1349
1350 for patch in patches:
1350 for patch in patches:
1351 if not message:
1351 if not message:
1352 messages.append(q.readheaders(patch)[0])
1352 messages.append(q.readheaders(patch)[0])
1353 pf = os.path.join(q.path, patch)
1353 pf = q.join(patch)
1354 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1354 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1355 if not patchsuccess:
1355 if not patchsuccess:
1356 raise util.Abort(_('Error folding patch %s') % patch)
1356 raise util.Abort(_('Error folding patch %s') % patch)
1357
1357
1358 if not message:
1358 if not message:
1359 message, comments, user = q.readheaders(parent)[0:3]
1359 message, comments, user = q.readheaders(parent)[0:3]
1360 for msg in messages:
1360 for msg in messages:
1361 message.append('* * *')
1361 message.append('* * *')
1362 message.extend(msg)
1362 message.extend(msg)
1363 message = '\n'.join(message)
1363 message = '\n'.join(message)
1364
1364
1365 if opts['edit']:
1365 if opts['edit']:
1366 message = ui.edit(message, user or ui.username())
1366 message = ui.edit(message, user or ui.username())
1367
1367
1368 q.refresh(repo, msg=message)
1368 q.refresh(repo, msg=message)
1369
1369
1370 for patch in patches:
1370 for patch in patches:
1371 q.delete(repo, patch, force=opts['force'])
1371 q.delete(repo, patch, force=opts['force'])
1372
1372
1373 q.save_dirty()
1373 q.save_dirty()
1374
1374
1375 def header(ui, repo, patch=None):
1375 def header(ui, repo, patch=None):
1376 """Print the header of the topmost or specified patch"""
1376 """Print the header of the topmost or specified patch"""
1377 q = repo.mq
1377 q = repo.mq
1378
1378
1379 if patch:
1379 if patch:
1380 patch = q.lookup(patch)
1380 patch = q.lookup(patch)
1381 else:
1381 else:
1382 if not q.applied:
1382 if not q.applied:
1383 ui.write('No patches applied\n')
1383 ui.write('No patches applied\n')
1384 return
1384 return
1385 patch = q.lookup('qtip')
1385 patch = q.lookup('qtip')
1386 message = repo.mq.readheaders(patch)[0]
1386 message = repo.mq.readheaders(patch)[0]
1387
1387
1388 ui.write('\n'.join(message) + '\n')
1388 ui.write('\n'.join(message) + '\n')
1389
1389
1390 def lastsavename(path):
1390 def lastsavename(path):
1391 (directory, base) = os.path.split(path)
1391 (directory, base) = os.path.split(path)
1392 names = os.listdir(directory)
1392 names = os.listdir(directory)
1393 namere = re.compile("%s.([0-9]+)" % base)
1393 namere = re.compile("%s.([0-9]+)" % base)
1394 maxindex = None
1394 maxindex = None
1395 maxname = None
1395 maxname = None
1396 for f in names:
1396 for f in names:
1397 m = namere.match(f)
1397 m = namere.match(f)
1398 if m:
1398 if m:
1399 index = int(m.group(1))
1399 index = int(m.group(1))
1400 if maxindex == None or index > maxindex:
1400 if maxindex == None or index > maxindex:
1401 maxindex = index
1401 maxindex = index
1402 maxname = f
1402 maxname = f
1403 if maxname:
1403 if maxname:
1404 return (os.path.join(directory, maxname), maxindex)
1404 return (os.path.join(directory, maxname), maxindex)
1405 return (None, None)
1405 return (None, None)
1406
1406
1407 def savename(path):
1407 def savename(path):
1408 (last, index) = lastsavename(path)
1408 (last, index) = lastsavename(path)
1409 if last is None:
1409 if last is None:
1410 index = 0
1410 index = 0
1411 newpath = path + ".%d" % (index + 1)
1411 newpath = path + ".%d" % (index + 1)
1412 return newpath
1412 return newpath
1413
1413
1414 def push(ui, repo, patch=None, **opts):
1414 def push(ui, repo, patch=None, **opts):
1415 """push the next patch onto the stack"""
1415 """push the next patch onto the stack"""
1416 q = repo.mq
1416 q = repo.mq
1417 mergeq = None
1417 mergeq = None
1418
1418
1419 if opts['all']:
1419 if opts['all']:
1420 patch = q.series[-1]
1420 patch = q.series[-1]
1421 if opts['merge']:
1421 if opts['merge']:
1422 if opts['name']:
1422 if opts['name']:
1423 newpath = opts['name']
1423 newpath = opts['name']
1424 else:
1424 else:
1425 newpath, i = lastsavename(q.path)
1425 newpath, i = lastsavename(q.path)
1426 if not newpath:
1426 if not newpath:
1427 ui.warn("no saved queues found, please use -n\n")
1427 ui.warn("no saved queues found, please use -n\n")
1428 return 1
1428 return 1
1429 mergeq = queue(ui, repo.join(""), newpath)
1429 mergeq = queue(ui, repo.join(""), newpath)
1430 ui.warn("merging with queue at: %s\n" % mergeq.path)
1430 ui.warn("merging with queue at: %s\n" % mergeq.path)
1431 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1431 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1432 mergeq=mergeq)
1432 mergeq=mergeq)
1433 q.save_dirty()
1433 q.save_dirty()
1434 return ret
1434 return ret
1435
1435
1436 def pop(ui, repo, patch=None, **opts):
1436 def pop(ui, repo, patch=None, **opts):
1437 """pop the current patch off the stack"""
1437 """pop the current patch off the stack"""
1438 localupdate = True
1438 localupdate = True
1439 if opts['name']:
1439 if opts['name']:
1440 q = queue(ui, repo.join(""), repo.join(opts['name']))
1440 q = queue(ui, repo.join(""), repo.join(opts['name']))
1441 ui.warn('using patch queue: %s\n' % q.path)
1441 ui.warn('using patch queue: %s\n' % q.path)
1442 localupdate = False
1442 localupdate = False
1443 else:
1443 else:
1444 q = repo.mq
1444 q = repo.mq
1445 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1445 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1446 q.save_dirty()
1446 q.save_dirty()
1447 return 0
1447 return 0
1448
1448
1449 def rename(ui, repo, patch, name=None, **opts):
1449 def rename(ui, repo, patch, name=None, **opts):
1450 """rename a patch
1450 """rename a patch
1451
1451
1452 With one argument, renames the current patch to PATCH1.
1452 With one argument, renames the current patch to PATCH1.
1453 With two arguments, renames PATCH1 to PATCH2."""
1453 With two arguments, renames PATCH1 to PATCH2."""
1454
1454
1455 q = repo.mq
1455 q = repo.mq
1456
1456
1457 if not name:
1457 if not name:
1458 name = patch
1458 name = patch
1459 patch = None
1459 patch = None
1460
1460
1461 if name in q.series:
1461 if name in q.series:
1462 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1462 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1463
1463
1464 absdest = os.path.join(q.path, name)
1464 absdest = q.join(name)
1465 if os.path.exists(absdest):
1465 if os.path.exists(absdest):
1466 raise util.Abort(_('%s already exists') % absdest)
1466 raise util.Abort(_('%s already exists') % absdest)
1467
1467
1468 if patch:
1468 if patch:
1469 patch = q.lookup(patch)
1469 patch = q.lookup(patch)
1470 else:
1470 else:
1471 if not q.applied:
1471 if not q.applied:
1472 ui.write(_('No patches applied\n'))
1472 ui.write(_('No patches applied\n'))
1473 return
1473 return
1474 patch = q.lookup('qtip')
1474 patch = q.lookup('qtip')
1475
1475
1476 if ui.verbose:
1476 if ui.verbose:
1477 ui.write('Renaming %s to %s\n' % (patch, name))
1477 ui.write('Renaming %s to %s\n' % (patch, name))
1478 i = q.find_series(patch)
1478 i = q.find_series(patch)
1479 q.full_series[i] = name
1479 q.full_series[i] = name
1480 q.parse_series()
1480 q.parse_series()
1481 q.series_dirty = 1
1481 q.series_dirty = 1
1482
1482
1483 info = q.isapplied(patch)
1483 info = q.isapplied(patch)
1484 if info:
1484 if info:
1485 q.applied[info[0]] = statusentry(info[1], name)
1485 q.applied[info[0]] = statusentry(info[1], name)
1486 q.applied_dirty = 1
1486 q.applied_dirty = 1
1487
1487
1488 util.rename(os.path.join(q.path, patch), absdest)
1488 util.rename(q.join(patch), absdest)
1489 r = q.qrepo()
1489 r = q.qrepo()
1490 if r:
1490 if r:
1491 wlock = r.wlock()
1491 wlock = r.wlock()
1492 if r.dirstate.state(name) == 'r':
1492 if r.dirstate.state(name) == 'r':
1493 r.undelete([name], wlock)
1493 r.undelete([name], wlock)
1494 r.copy(patch, name, wlock)
1494 r.copy(patch, name, wlock)
1495 r.remove([patch], False, wlock)
1495 r.remove([patch], False, wlock)
1496
1496
1497 q.save_dirty()
1497 q.save_dirty()
1498
1498
1499 def restore(ui, repo, rev, **opts):
1499 def restore(ui, repo, rev, **opts):
1500 """restore the queue state saved by a rev"""
1500 """restore the queue state saved by a rev"""
1501 rev = repo.lookup(rev)
1501 rev = repo.lookup(rev)
1502 q = repo.mq
1502 q = repo.mq
1503 q.restore(repo, rev, delete=opts['delete'],
1503 q.restore(repo, rev, delete=opts['delete'],
1504 qupdate=opts['update'])
1504 qupdate=opts['update'])
1505 q.save_dirty()
1505 q.save_dirty()
1506 return 0
1506 return 0
1507
1507
1508 def save(ui, repo, **opts):
1508 def save(ui, repo, **opts):
1509 """save current queue state"""
1509 """save current queue state"""
1510 q = repo.mq
1510 q = repo.mq
1511 message = commands.logmessage(opts)
1511 message = commands.logmessage(opts)
1512 ret = q.save(repo, msg=message)
1512 ret = q.save(repo, msg=message)
1513 if ret:
1513 if ret:
1514 return ret
1514 return ret
1515 q.save_dirty()
1515 q.save_dirty()
1516 if opts['copy']:
1516 if opts['copy']:
1517 path = q.path
1517 path = q.path
1518 if opts['name']:
1518 if opts['name']:
1519 newpath = os.path.join(q.basepath, opts['name'])
1519 newpath = os.path.join(q.basepath, opts['name'])
1520 if os.path.exists(newpath):
1520 if os.path.exists(newpath):
1521 if not os.path.isdir(newpath):
1521 if not os.path.isdir(newpath):
1522 raise util.Abort(_('destination %s exists and is not '
1522 raise util.Abort(_('destination %s exists and is not '
1523 'a directory') % newpath)
1523 'a directory') % newpath)
1524 if not opts['force']:
1524 if not opts['force']:
1525 raise util.Abort(_('destination %s exists, '
1525 raise util.Abort(_('destination %s exists, '
1526 'use -f to force') % newpath)
1526 'use -f to force') % newpath)
1527 else:
1527 else:
1528 newpath = savename(path)
1528 newpath = savename(path)
1529 ui.warn("copy %s to %s\n" % (path, newpath))
1529 ui.warn("copy %s to %s\n" % (path, newpath))
1530 util.copyfiles(path, newpath)
1530 util.copyfiles(path, newpath)
1531 if opts['empty']:
1531 if opts['empty']:
1532 try:
1532 try:
1533 os.unlink(os.path.join(q.path, q.status_path))
1533 os.unlink(q.join(q.status_path))
1534 except:
1534 except:
1535 pass
1535 pass
1536 return 0
1536 return 0
1537
1537
1538 def strip(ui, repo, rev, **opts):
1538 def strip(ui, repo, rev, **opts):
1539 """strip a revision and all later revs on the same branch"""
1539 """strip a revision and all later revs on the same branch"""
1540 rev = repo.lookup(rev)
1540 rev = repo.lookup(rev)
1541 backup = 'all'
1541 backup = 'all'
1542 if opts['backup']:
1542 if opts['backup']:
1543 backup = 'strip'
1543 backup = 'strip'
1544 elif opts['nobackup']:
1544 elif opts['nobackup']:
1545 backup = 'none'
1545 backup = 'none'
1546 repo.mq.strip(repo, rev, backup=backup)
1546 repo.mq.strip(repo, rev, backup=backup)
1547 return 0
1547 return 0
1548
1548
1549 def version(ui, q=None):
1549 def version(ui, q=None):
1550 """print the version number of the mq extension"""
1550 """print the version number of the mq extension"""
1551 ui.write("mq version %s\n" % versionstr)
1551 ui.write("mq version %s\n" % versionstr)
1552 return 0
1552 return 0
1553
1553
1554 def reposetup(ui, repo):
1554 def reposetup(ui, repo):
1555 class mqrepo(repo.__class__):
1555 class mqrepo(repo.__class__):
1556 def tags(self):
1556 def tags(self):
1557 if self.tagscache:
1557 if self.tagscache:
1558 return self.tagscache
1558 return self.tagscache
1559
1559
1560 tagscache = super(mqrepo, self).tags()
1560 tagscache = super(mqrepo, self).tags()
1561
1561
1562 q = self.mq
1562 q = self.mq
1563 if not q.applied:
1563 if not q.applied:
1564 return tagscache
1564 return tagscache
1565
1565
1566 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1566 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1567 mqtags.append((mqtags[-1][0], 'qtip'))
1567 mqtags.append((mqtags[-1][0], 'qtip'))
1568 mqtags.append((mqtags[0][0], 'qbase'))
1568 mqtags.append((mqtags[0][0], 'qbase'))
1569 for patch in mqtags:
1569 for patch in mqtags:
1570 if patch[1] in tagscache:
1570 if patch[1] in tagscache:
1571 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1571 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1572 else:
1572 else:
1573 tagscache[patch[1]] = revlog.bin(patch[0])
1573 tagscache[patch[1]] = revlog.bin(patch[0])
1574
1574
1575 return tagscache
1575 return tagscache
1576
1576
1577 repo.__class__ = mqrepo
1577 repo.__class__ = mqrepo
1578 repo.mq = queue(ui, repo.join(""))
1578 repo.mq = queue(ui, repo.join(""))
1579
1579
1580 cmdtable = {
1580 cmdtable = {
1581 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1581 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1582 "qclone": (clone,
1582 "qclone": (clone,
1583 [('', 'pull', None, _('use pull protocol to copy metadata')),
1583 [('', 'pull', None, _('use pull protocol to copy metadata')),
1584 ('U', 'noupdate', None, _('do not update the new working directories')),
1584 ('U', 'noupdate', None, _('do not update the new working directories')),
1585 ('', 'uncompressed', None,
1585 ('', 'uncompressed', None,
1586 _('use uncompressed transfer (fast over LAN)')),
1586 _('use uncompressed transfer (fast over LAN)')),
1587 ('e', 'ssh', '', _('specify ssh command to use')),
1587 ('e', 'ssh', '', _('specify ssh command to use')),
1588 ('p', 'patches', '', _('location of source patch repo')),
1588 ('p', 'patches', '', _('location of source patch repo')),
1589 ('', 'remotecmd', '',
1589 ('', 'remotecmd', '',
1590 _('specify hg command to run on the remote side'))],
1590 _('specify hg command to run on the remote side'))],
1591 'hg qclone [OPTION]... SOURCE [DEST]'),
1591 'hg qclone [OPTION]... SOURCE [DEST]'),
1592 "qcommit|qci":
1592 "qcommit|qci":
1593 (commit,
1593 (commit,
1594 commands.table["^commit|ci"][1],
1594 commands.table["^commit|ci"][1],
1595 'hg qcommit [OPTION]... [FILE]...'),
1595 'hg qcommit [OPTION]... [FILE]...'),
1596 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1596 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1597 "qdelete":
1597 "qdelete":
1598 (delete,
1598 (delete,
1599 [('f', 'force', None, _('delete patch file'))],
1599 [('f', 'force', None, _('delete patch file'))],
1600 'hg qdelete [-f] PATCH'),
1600 'hg qdelete [-f] PATCH'),
1601 'qfold':
1601 'qfold':
1602 (fold,
1602 (fold,
1603 [('e', 'edit', None, _('edit patch header')),
1603 [('e', 'edit', None, _('edit patch header')),
1604 ('f', 'force', None, _('delete folded patch files')),
1604 ('f', 'force', None, _('delete folded patch files')),
1605 ('m', 'message', '', _('set patch header to <text>')),
1605 ('m', 'message', '', _('set patch header to <text>')),
1606 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1606 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1607 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1607 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1608 'qheader': (header, [],
1608 'qheader': (header, [],
1609 _('hg qheader [PATCH]')),
1609 _('hg qheader [PATCH]')),
1610 "^qimport":
1610 "^qimport":
1611 (qimport,
1611 (qimport,
1612 [('e', 'existing', None, 'import file in patch dir'),
1612 [('e', 'existing', None, 'import file in patch dir'),
1613 ('n', 'name', '', 'patch file name'),
1613 ('n', 'name', '', 'patch file name'),
1614 ('f', 'force', None, 'overwrite existing files')],
1614 ('f', 'force', None, 'overwrite existing files')],
1615 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1615 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1616 "^qinit":
1616 "^qinit":
1617 (init,
1617 (init,
1618 [('c', 'create-repo', None, 'create queue repository')],
1618 [('c', 'create-repo', None, 'create queue repository')],
1619 'hg qinit [-c]'),
1619 'hg qinit [-c]'),
1620 "qnew":
1620 "qnew":
1621 (new,
1621 (new,
1622 [('m', 'message', '', _('use <text> as commit message')),
1622 [('m', 'message', '', _('use <text> as commit message')),
1623 ('l', 'logfile', '', _('read the commit message from <file>')),
1623 ('l', 'logfile', '', _('read the commit message from <file>')),
1624 ('f', 'force', None, _('import uncommitted changes into patch'))],
1624 ('f', 'force', None, _('import uncommitted changes into patch'))],
1625 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1625 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1626 "qnext": (next, [], 'hg qnext'),
1626 "qnext": (next, [], 'hg qnext'),
1627 "qprev": (prev, [], 'hg qprev'),
1627 "qprev": (prev, [], 'hg qprev'),
1628 "^qpop":
1628 "^qpop":
1629 (pop,
1629 (pop,
1630 [('a', 'all', None, 'pop all patches'),
1630 [('a', 'all', None, 'pop all patches'),
1631 ('n', 'name', '', 'queue name to pop'),
1631 ('n', 'name', '', 'queue name to pop'),
1632 ('f', 'force', None, 'forget any local changes')],
1632 ('f', 'force', None, 'forget any local changes')],
1633 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1633 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1634 "^qpush":
1634 "^qpush":
1635 (push,
1635 (push,
1636 [('f', 'force', None, 'apply if the patch has rejects'),
1636 [('f', 'force', None, 'apply if the patch has rejects'),
1637 ('l', 'list', None, 'list patch name in commit text'),
1637 ('l', 'list', None, 'list patch name in commit text'),
1638 ('a', 'all', None, 'apply all patches'),
1638 ('a', 'all', None, 'apply all patches'),
1639 ('m', 'merge', None, 'merge from another queue'),
1639 ('m', 'merge', None, 'merge from another queue'),
1640 ('n', 'name', '', 'merge queue name')],
1640 ('n', 'name', '', 'merge queue name')],
1641 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1641 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1642 "^qrefresh":
1642 "^qrefresh":
1643 (refresh,
1643 (refresh,
1644 [('e', 'edit', None, _('edit commit message')),
1644 [('e', 'edit', None, _('edit commit message')),
1645 ('m', 'message', '', _('change commit message with <text>')),
1645 ('m', 'message', '', _('change commit message with <text>')),
1646 ('l', 'logfile', '', _('change commit message with <file> content')),
1646 ('l', 'logfile', '', _('change commit message with <file> content')),
1647 ('s', 'short', None, 'short refresh')],
1647 ('s', 'short', None, 'short refresh')],
1648 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1648 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1649 'qrename|qmv':
1649 'qrename|qmv':
1650 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1650 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1651 "qrestore":
1651 "qrestore":
1652 (restore,
1652 (restore,
1653 [('d', 'delete', None, 'delete save entry'),
1653 [('d', 'delete', None, 'delete save entry'),
1654 ('u', 'update', None, 'update queue working dir')],
1654 ('u', 'update', None, 'update queue working dir')],
1655 'hg qrestore [-d] [-u] REV'),
1655 'hg qrestore [-d] [-u] REV'),
1656 "qsave":
1656 "qsave":
1657 (save,
1657 (save,
1658 [('m', 'message', '', _('use <text> as commit message')),
1658 [('m', 'message', '', _('use <text> as commit message')),
1659 ('l', 'logfile', '', _('read the commit message from <file>')),
1659 ('l', 'logfile', '', _('read the commit message from <file>')),
1660 ('c', 'copy', None, 'copy patch directory'),
1660 ('c', 'copy', None, 'copy patch directory'),
1661 ('n', 'name', '', 'copy directory name'),
1661 ('n', 'name', '', 'copy directory name'),
1662 ('e', 'empty', None, 'clear queue status file'),
1662 ('e', 'empty', None, 'clear queue status file'),
1663 ('f', 'force', None, 'force copy')],
1663 ('f', 'force', None, 'force copy')],
1664 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1664 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1665 "qseries":
1665 "qseries":
1666 (series,
1666 (series,
1667 [('m', 'missing', None, 'print patches not in series'),
1667 [('m', 'missing', None, 'print patches not in series'),
1668 ('s', 'summary', None, _('print first line of patch header'))],
1668 ('s', 'summary', None, _('print first line of patch header'))],
1669 'hg qseries [-m]'),
1669 'hg qseries [-m]'),
1670 "^strip":
1670 "^strip":
1671 (strip,
1671 (strip,
1672 [('f', 'force', None, 'force multi-head removal'),
1672 [('f', 'force', None, 'force multi-head removal'),
1673 ('b', 'backup', None, 'bundle unrelated changesets'),
1673 ('b', 'backup', None, 'bundle unrelated changesets'),
1674 ('n', 'nobackup', None, 'no backups')],
1674 ('n', 'nobackup', None, 'no backups')],
1675 'hg strip [-f] [-b] [-n] REV'),
1675 'hg strip [-f] [-b] [-n] REV'),
1676 "qtop": (top, [], 'hg qtop'),
1676 "qtop": (top, [], 'hg qtop'),
1677 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1677 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1678 "qversion": (version, [], 'hg qversion')
1678 "qversion": (version, [], 'hg qversion')
1679 }
1679 }
1680
1680
General Comments 0
You need to be logged in to leave comments. Login now