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