##// END OF EJS Templates
mq: fix variables shadowing builtin
Benoit Boissinot -
r2794:86c54b7c default
parent child Browse files
Show More
@@ -1,1681 +1,1680 b''
1
1
2 # queue.py - patch queues for mercurial
2 # queue.py - patch queues for mercurial
3 #
3 #
4 # Copyright 2005 Chris Mason <mason@suse.com>
4 # Copyright 2005 Chris Mason <mason@suse.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 '''patch management and development
9 '''patch management and development
10
10
11 This extension lets you work with a stack of patches in a Mercurial
11 This extension lets you work with a stack of patches in a Mercurial
12 repository. It manages two stacks of patches - all known patches, and
12 repository. It manages two stacks of patches - all known patches, and
13 applied patches (subset of known patches).
13 applied patches (subset of known patches).
14
14
15 Known patches are represented as patch files in the .hg/patches
15 Known patches are represented as patch files in the .hg/patches
16 directory. Applied patches are both patch files and changesets.
16 directory. Applied patches are both patch files and changesets.
17
17
18 Common tasks (use "hg help command" for more details):
18 Common tasks (use "hg help command" for more details):
19
19
20 prepare repository to work with patches qinit
20 prepare repository to work with patches qinit
21 create new patch qnew
21 create new patch qnew
22 import existing patch qimport
22 import existing patch qimport
23
23
24 print patch series qseries
24 print patch series qseries
25 print applied patches qapplied
25 print applied patches qapplied
26 print name of top applied patch qtop
26 print name of top applied patch qtop
27
27
28 add known patch to applied stack qpush
28 add known patch to applied stack qpush
29 remove patch from applied stack qpop
29 remove patch from applied stack qpop
30 refresh contents of top applied patch qrefresh
30 refresh contents of top applied patch qrefresh
31 '''
31 '''
32
32
33 from mercurial.demandload import *
33 from mercurial.demandload import *
34 demandload(globals(), "os sys re struct traceback errno bz2")
34 demandload(globals(), "os sys re struct traceback errno bz2")
35 from mercurial.i18n import gettext as _
35 from mercurial.i18n import gettext as _
36 from mercurial import ui, hg, revlog, commands, util
36 from mercurial import ui, hg, revlog, commands, util
37
37
38 versionstr = "0.45"
38 versionstr = "0.45"
39
39
40 commands.norepo += " qclone qversion"
40 commands.norepo += " qclone qversion"
41
41
42 class StatusEntry:
42 class StatusEntry:
43 def __init__(self, rev, name=None):
43 def __init__(self, rev, name=None):
44 if not name:
44 if not name:
45 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 repo.update(head, allow=False, force=True, wlock=wlock)
193 repo.update(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 = repo.update(rev, allow=True, wlock=wlock)
197 ret = repo.update(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 list = seen.keys()
508 seen_list = seen.keys()
509 list.sort()
509 seen_list.sort()
510 for f in 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 repo.update(urev, allow=False, force=True, wlock=wlock)
533 repo.update(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 tip = chlog.tip()
542 tip = chlog.tip()
543 heads = limitheads(chlog, rev)
543 heads = limitheads(chlog, rev)
544 seen = {}
544 seen = {}
545
545
546 # search through all the heads, finding those where the revision
546 # search through all the heads, finding those where the revision
547 # we want to strip away is an ancestor. Also look for merges
547 # we want to strip away is an ancestor. Also look for merges
548 # that might be turned into new heads by the strip.
548 # that might be turned into new heads by the strip.
549 while heads:
549 while heads:
550 h = heads.pop()
550 h = heads.pop()
551 n = h
551 n = h
552 while True:
552 while True:
553 seen[n] = 1
553 seen[n] = 1
554 pp = chlog.parents(n)
554 pp = chlog.parents(n)
555 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
555 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
556 if pp[1] not in seen:
556 if pp[1] not in seen:
557 heads.append(pp[1])
557 heads.append(pp[1])
558 if pp[0] == revlog.nullid:
558 if pp[0] == revlog.nullid:
559 break
559 break
560 if chlog.rev(pp[0]) < revnum:
560 if chlog.rev(pp[0]) < revnum:
561 break
561 break
562 n = pp[0]
562 n = pp[0]
563 if n == rev:
563 if n == rev:
564 break
564 break
565 r = chlog.reachable(h, rev)
565 r = chlog.reachable(h, rev)
566 if rev not in r:
566 if rev not in r:
567 saveheads.append(h)
567 saveheads.append(h)
568 for x in r:
568 for x in r:
569 if chlog.rev(x) > revnum:
569 if chlog.rev(x) > revnum:
570 savebases[x] = 1
570 savebases[x] = 1
571
571
572 # create a changegroup for all the branches we need to keep
572 # create a changegroup for all the branches we need to keep
573 if backup is "all":
573 if backup is "all":
574 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
574 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
575 bundle(backupch)
575 bundle(backupch)
576 if saveheads:
576 if saveheads:
577 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
577 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
578 chgrpfile = bundle(backupch)
578 chgrpfile = bundle(backupch)
579
579
580 stripall(rev, revnum)
580 stripall(rev, revnum)
581
581
582 change = chlog.read(rev)
582 change = chlog.read(rev)
583 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
583 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
584 chlog.strip(revnum, revnum)
584 chlog.strip(revnum, revnum)
585 if saveheads:
585 if saveheads:
586 self.ui.status("adding branch\n")
586 self.ui.status("adding branch\n")
587 commands.unbundle(self.ui, repo, chgrpfile, update=False)
587 commands.unbundle(self.ui, repo, chgrpfile, update=False)
588 if backup is not "strip":
588 if backup is not "strip":
589 os.unlink(chgrpfile)
589 os.unlink(chgrpfile)
590
590
591 def isapplied(self, patch):
591 def isapplied(self, patch):
592 """returns (index, rev, patch)"""
592 """returns (index, rev, patch)"""
593 for i in xrange(len(self.applied)):
593 for i in xrange(len(self.applied)):
594 a = self.applied[i]
594 a = self.applied[i]
595 if a.name == patch:
595 if a.name == patch:
596 return (i, a.rev, a.name)
596 return (i, a.rev, a.name)
597 return None
597 return None
598
598
599 # if the exact patch name does not exist, we try a few
599 # if the exact patch name does not exist, we try a few
600 # variations. If strict is passed, we try only #1
600 # variations. If strict is passed, we try only #1
601 #
601 #
602 # 1) a number to indicate an offset in the series file
602 # 1) a number to indicate an offset in the series file
603 # 2) a unique substring of the patch name was given
603 # 2) a unique substring of the patch name was given
604 # 3) patchname[-+]num to indicate an offset in the series file
604 # 3) patchname[-+]num to indicate an offset in the series file
605 def lookup(self, patch, strict=False):
605 def lookup(self, patch, strict=False):
606 def partial_name(s):
606 def partial_name(s):
607 if s in self.series:
607 if s in self.series:
608 return s
608 return s
609 matches = [x for x in self.series if s in x]
609 matches = [x for x in self.series if s in x]
610 if len(matches) > 1:
610 if len(matches) > 1:
611 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
611 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
612 for m in matches:
612 for m in matches:
613 self.ui.warn(' %s\n' % m)
613 self.ui.warn(' %s\n' % m)
614 return None
614 return None
615 if matches:
615 if matches:
616 return matches[0]
616 return matches[0]
617 if len(self.series) > 0 and len(self.applied) > 0:
617 if len(self.series) > 0 and len(self.applied) > 0:
618 if s == 'qtip':
618 if s == 'qtip':
619 return self.series[self.series_end()-1]
619 return self.series[self.series_end()-1]
620 if s == 'qbase':
620 if s == 'qbase':
621 return self.series[0]
621 return self.series[0]
622 return None
622 return None
623 if patch == None:
623 if patch == None:
624 return None
624 return None
625
625
626 # we don't want to return a partial match until we make
626 # we don't want to return a partial match until we make
627 # sure the file name passed in does not exist (checked below)
627 # sure the file name passed in does not exist (checked below)
628 res = partial_name(patch)
628 res = partial_name(patch)
629 if res and res == patch:
629 if res and res == patch:
630 return res
630 return res
631
631
632 if not os.path.isfile(os.path.join(self.path, patch)):
632 if not os.path.isfile(os.path.join(self.path, patch)):
633 try:
633 try:
634 sno = int(patch)
634 sno = int(patch)
635 except(ValueError, OverflowError):
635 except(ValueError, OverflowError):
636 pass
636 pass
637 else:
637 else:
638 if sno < len(self.series):
638 if sno < len(self.series):
639 patch = self.series[sno]
639 patch = self.series[sno]
640 return patch
640 return patch
641 if not strict:
641 if not strict:
642 # return any partial match made above
642 # return any partial match made above
643 if res:
643 if res:
644 return res
644 return res
645 minus = patch.rsplit('-', 1)
645 minus = patch.rsplit('-', 1)
646 if len(minus) > 1:
646 if len(minus) > 1:
647 res = partial_name(minus[0])
647 res = partial_name(minus[0])
648 if res:
648 if res:
649 i = self.series.index(res)
649 i = self.series.index(res)
650 try:
650 try:
651 off = int(minus[1] or 1)
651 off = int(minus[1] or 1)
652 except(ValueError, OverflowError):
652 except(ValueError, OverflowError):
653 pass
653 pass
654 else:
654 else:
655 if i - off >= 0:
655 if i - off >= 0:
656 return self.series[i - off]
656 return self.series[i - off]
657 plus = patch.rsplit('+', 1)
657 plus = patch.rsplit('+', 1)
658 if len(plus) > 1:
658 if len(plus) > 1:
659 res = partial_name(plus[0])
659 res = partial_name(plus[0])
660 if res:
660 if res:
661 i = self.series.index(res)
661 i = self.series.index(res)
662 try:
662 try:
663 off = int(plus[1] or 1)
663 off = int(plus[1] or 1)
664 except(ValueError, OverflowError):
664 except(ValueError, OverflowError):
665 pass
665 pass
666 else:
666 else:
667 if i + off < len(self.series):
667 if i + off < len(self.series):
668 return self.series[i + off]
668 return self.series[i + off]
669 raise util.Abort(_("patch %s not in series") % patch)
669 raise util.Abort(_("patch %s not in series") % patch)
670
670
671 def push(self, repo, patch=None, force=False, list=False,
671 def push(self, repo, patch=None, force=False, list=False,
672 mergeq=None, wlock=None):
672 mergeq=None, wlock=None):
673 if not wlock:
673 if not wlock:
674 wlock = repo.wlock()
674 wlock = repo.wlock()
675 patch = self.lookup(patch)
675 patch = self.lookup(patch)
676 if patch and self.isapplied(patch):
676 if patch and self.isapplied(patch):
677 self.ui.warn(_("patch %s is already applied\n") % patch)
677 self.ui.warn(_("patch %s is already applied\n") % patch)
678 sys.exit(1)
678 sys.exit(1)
679 if self.series_end() == len(self.series):
679 if self.series_end() == len(self.series):
680 self.ui.warn(_("patch series fully applied\n"))
680 self.ui.warn(_("patch series fully applied\n"))
681 sys.exit(1)
681 sys.exit(1)
682 if not force:
682 if not force:
683 self.check_localchanges(repo)
683 self.check_localchanges(repo)
684
684
685 self.applied_dirty = 1;
685 self.applied_dirty = 1;
686 start = self.series_end()
686 start = self.series_end()
687 if start > 0:
687 if start > 0:
688 self.check_toppatch(repo)
688 self.check_toppatch(repo)
689 if not patch:
689 if not patch:
690 patch = self.series[start]
690 patch = self.series[start]
691 end = start + 1
691 end = start + 1
692 else:
692 else:
693 end = self.series.index(patch, start) + 1
693 end = self.series.index(patch, start) + 1
694 s = self.series[start:end]
694 s = self.series[start:end]
695 if mergeq:
695 if mergeq:
696 ret = self.mergepatch(repo, mergeq, s, wlock)
696 ret = self.mergepatch(repo, mergeq, s, wlock)
697 else:
697 else:
698 ret = self.apply(repo, s, list, wlock=wlock)
698 ret = self.apply(repo, s, list, wlock=wlock)
699 top = self.applied[-1].name
699 top = self.applied[-1].name
700 if ret[0]:
700 if ret[0]:
701 self.ui.write("Errors during apply, please fix and refresh %s\n" %
701 self.ui.write("Errors during apply, please fix and refresh %s\n" %
702 top)
702 top)
703 else:
703 else:
704 self.ui.write("Now at: %s\n" % top)
704 self.ui.write("Now at: %s\n" % top)
705 return ret[0]
705 return ret[0]
706
706
707 def pop(self, repo, patch=None, force=False, update=True, all=False,
707 def pop(self, repo, patch=None, force=False, update=True, all=False,
708 wlock=None):
708 wlock=None):
709 def getfile(f, rev):
709 def getfile(f, rev):
710 t = repo.file(f).read(rev)
710 t = repo.file(f).read(rev)
711 try:
711 try:
712 repo.wfile(f, "w").write(t)
712 repo.wfile(f, "w").write(t)
713 except IOError:
713 except IOError:
714 try:
714 try:
715 os.makedirs(os.path.dirname(repo.wjoin(f)))
715 os.makedirs(os.path.dirname(repo.wjoin(f)))
716 except OSError, err:
716 except OSError, err:
717 if err.errno != errno.EEXIST: raise
717 if err.errno != errno.EEXIST: raise
718 repo.wfile(f, "w").write(t)
718 repo.wfile(f, "w").write(t)
719
719
720 if not wlock:
720 if not wlock:
721 wlock = repo.wlock()
721 wlock = repo.wlock()
722 if patch:
722 if patch:
723 # index, rev, patch
723 # index, rev, patch
724 info = self.isapplied(patch)
724 info = self.isapplied(patch)
725 if not info:
725 if not info:
726 patch = self.lookup(patch)
726 patch = self.lookup(patch)
727 info = self.isapplied(patch)
727 info = self.isapplied(patch)
728 if not info:
728 if not info:
729 raise util.Abort(_("patch %s is not applied") % patch)
729 raise util.Abort(_("patch %s is not applied") % patch)
730 if len(self.applied) == 0:
730 if len(self.applied) == 0:
731 self.ui.warn(_("no patches applied\n"))
731 self.ui.warn(_("no patches applied\n"))
732 sys.exit(1)
732 sys.exit(1)
733
733
734 if not update:
734 if not update:
735 parents = repo.dirstate.parents()
735 parents = repo.dirstate.parents()
736 rr = [ revlog.bin(x.rev) for x in self.applied ]
736 rr = [ revlog.bin(x.rev) for x in self.applied ]
737 for p in parents:
737 for p in parents:
738 if p in rr:
738 if p in rr:
739 self.ui.warn("qpop: forcing dirstate update\n")
739 self.ui.warn("qpop: forcing dirstate update\n")
740 update = True
740 update = True
741
741
742 if not force and update:
742 if not force and update:
743 self.check_localchanges(repo)
743 self.check_localchanges(repo)
744
744
745 self.applied_dirty = 1;
745 self.applied_dirty = 1;
746 end = len(self.applied)
746 end = len(self.applied)
747 if not patch:
747 if not patch:
748 if all:
748 if all:
749 popi = 0
749 popi = 0
750 else:
750 else:
751 popi = len(self.applied) - 1
751 popi = len(self.applied) - 1
752 else:
752 else:
753 popi = info[0] + 1
753 popi = info[0] + 1
754 if popi >= end:
754 if popi >= end:
755 self.ui.warn("qpop: %s is already at the top\n" % patch)
755 self.ui.warn("qpop: %s is already at the top\n" % patch)
756 return
756 return
757 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
757 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
758
758
759 start = info[0]
759 start = info[0]
760 rev = revlog.bin(info[1])
760 rev = revlog.bin(info[1])
761
761
762 # we know there are no local changes, so we can make a simplified
762 # we know there are no local changes, so we can make a simplified
763 # form of hg.update.
763 # form of hg.update.
764 if update:
764 if update:
765 top = self.check_toppatch(repo)
765 top = self.check_toppatch(repo)
766 qp = self.qparents(repo, rev)
766 qp = self.qparents(repo, rev)
767 changes = repo.changelog.read(qp)
767 changes = repo.changelog.read(qp)
768 mf1 = repo.manifest.readflags(changes[0])
768 mf1 = repo.manifest.readflags(changes[0])
769 mmap = repo.manifest.read(changes[0])
769 mmap = repo.manifest.read(changes[0])
770 (c, a, r, d, u) = repo.changes(qp, top)
770 (c, a, r, d, u) = repo.changes(qp, top)
771 if d:
771 if d:
772 raise util.Abort("deletions found between repo revs")
772 raise util.Abort("deletions found between repo revs")
773 for f in c:
773 for f in c:
774 getfile(f, mmap[f])
774 getfile(f, mmap[f])
775 for f in r:
775 for f in r:
776 getfile(f, mmap[f])
776 getfile(f, mmap[f])
777 util.set_exec(repo.wjoin(f), mf1[f])
777 util.set_exec(repo.wjoin(f), mf1[f])
778 repo.dirstate.update(c + r, 'n')
778 repo.dirstate.update(c + r, 'n')
779 for f in a:
779 for f in a:
780 try: os.unlink(repo.wjoin(f))
780 try: os.unlink(repo.wjoin(f))
781 except: raise
781 except: raise
782 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
782 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
783 except: pass
783 except: pass
784 if a:
784 if a:
785 repo.dirstate.forget(a)
785 repo.dirstate.forget(a)
786 repo.dirstate.setparents(qp, revlog.nullid)
786 repo.dirstate.setparents(qp, revlog.nullid)
787 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
787 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
788 del self.applied[start:end]
788 del self.applied[start:end]
789 if len(self.applied):
789 if len(self.applied):
790 self.ui.write("Now at: %s\n" % self.applied[-1].name)
790 self.ui.write("Now at: %s\n" % self.applied[-1].name)
791 else:
791 else:
792 self.ui.write("Patch queue now empty\n")
792 self.ui.write("Patch queue now empty\n")
793
793
794 def diff(self, repo, files):
794 def diff(self, repo, files):
795 top = self.check_toppatch(repo)
795 top = self.check_toppatch(repo)
796 if not top:
796 if not top:
797 self.ui.write("No patches applied\n")
797 self.ui.write("No patches applied\n")
798 return
798 return
799 qp = self.qparents(repo, top)
799 qp = self.qparents(repo, top)
800 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
800 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
801
801
802 def refresh(self, repo, msg=None, short=False):
802 def refresh(self, repo, msg=None, short=False):
803 if len(self.applied) == 0:
803 if len(self.applied) == 0:
804 self.ui.write("No patches applied\n")
804 self.ui.write("No patches applied\n")
805 return
805 return
806 wlock = repo.wlock()
806 wlock = repo.wlock()
807 self.check_toppatch(repo)
807 self.check_toppatch(repo)
808 qp = self.qparents(repo)
808 qp = self.qparents(repo)
809 (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
809 (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
810 top = revlog.bin(top)
810 top = revlog.bin(top)
811 cparents = repo.changelog.parents(top)
811 cparents = repo.changelog.parents(top)
812 patchparent = self.qparents(repo, top)
812 patchparent = self.qparents(repo, top)
813 message, comments, user, date, patchfound = self.readheaders(patch)
813 message, comments, user, date, patchfound = self.readheaders(patch)
814
814
815 patchf = self.opener(patch, "w")
815 patchf = self.opener(patch, "w")
816 msg = msg.rstrip()
816 msg = msg.rstrip()
817 if msg:
817 if msg:
818 if comments:
818 if comments:
819 # Remove existing message.
819 # Remove existing message.
820 ci = 0
820 ci = 0
821 for mi in range(len(message)):
821 for mi in range(len(message)):
822 while message[mi] != comments[ci]:
822 while message[mi] != comments[ci]:
823 ci += 1
823 ci += 1
824 del comments[ci]
824 del comments[ci]
825 comments.append(msg)
825 comments.append(msg)
826 if comments:
826 if comments:
827 comments = "\n".join(comments) + '\n\n'
827 comments = "\n".join(comments) + '\n\n'
828 patchf.write(comments)
828 patchf.write(comments)
829
829
830 tip = repo.changelog.tip()
830 tip = repo.changelog.tip()
831 if top == tip:
831 if top == tip:
832 # if the top of our patch queue is also the tip, there is an
832 # if the top of our patch queue is also the tip, there is an
833 # optimization here. We update the dirstate in place and strip
833 # optimization here. We update the dirstate in place and strip
834 # off the tip commit. Then just commit the current directory
834 # off the tip commit. Then just commit the current directory
835 # tree. We can also send repo.commit the list of files
835 # tree. We can also send repo.commit the list of files
836 # changed to speed up the diff
836 # changed to speed up the diff
837 #
837 #
838 # in short mode, we only diff the files included in the
838 # in short mode, we only diff the files included in the
839 # patch already
839 # patch already
840 #
840 #
841 # this should really read:
841 # this should really read:
842 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
842 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
843 # but we do it backwards to take advantage of manifest/chlog
843 # but we do it backwards to take advantage of manifest/chlog
844 # caching against the next repo.changes call
844 # caching against the next repo.changes call
845 #
845 #
846 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
846 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
847 if short:
847 if short:
848 filelist = cc + aa + dd
848 filelist = cc + aa + dd
849 else:
849 else:
850 filelist = None
850 filelist = None
851 (c, a, r, d, u) = repo.changes(None, None, filelist)
851 (c, a, r, d, u) = repo.changes(None, None, filelist)
852
852
853 # we might end up with files that were added between tip and
853 # we might end up with files that were added between tip and
854 # the dirstate parent, but then changed in the local dirstate.
854 # the dirstate parent, but then changed in the local dirstate.
855 # in this case, we want them to only show up in the added section
855 # in this case, we want them to only show up in the added section
856 for x in c:
856 for x in c:
857 if x not in aa:
857 if x not in aa:
858 cc.append(x)
858 cc.append(x)
859 # we might end up with files added by the local dirstate that
859 # we might end up with files added by the local dirstate that
860 # were deleted by the patch. In this case, they should only
860 # were deleted by the patch. In this case, they should only
861 # show up in the changed section.
861 # show up in the changed section.
862 for x in a:
862 for x in a:
863 if x in dd:
863 if x in dd:
864 del dd[dd.index(x)]
864 del dd[dd.index(x)]
865 cc.append(x)
865 cc.append(x)
866 else:
866 else:
867 aa.append(x)
867 aa.append(x)
868 # make sure any files deleted in the local dirstate
868 # make sure any files deleted in the local dirstate
869 # are not in the add or change column of the patch
869 # are not in the add or change column of the patch
870 forget = []
870 forget = []
871 for x in d + r:
871 for x in d + r:
872 if x in aa:
872 if x in aa:
873 del aa[aa.index(x)]
873 del aa[aa.index(x)]
874 forget.append(x)
874 forget.append(x)
875 continue
875 continue
876 elif x in cc:
876 elif x in cc:
877 del cc[cc.index(x)]
877 del cc[cc.index(x)]
878 dd.append(x)
878 dd.append(x)
879
879
880 c = list(util.unique(cc))
880 c = list(util.unique(cc))
881 r = list(util.unique(dd))
881 r = list(util.unique(dd))
882 a = list(util.unique(aa))
882 a = list(util.unique(aa))
883 filelist = list(util.unique(c + r + a ))
883 filelist = list(util.unique(c + r + a ))
884 commands.dodiff(patchf, self.ui, repo, patchparent, None,
884 commands.dodiff(patchf, self.ui, repo, patchparent, None,
885 filelist, changes=(c, a, r, [], u))
885 filelist, changes=(c, a, r, [], u))
886 patchf.close()
886 patchf.close()
887
887
888 changes = repo.changelog.read(tip)
888 changes = repo.changelog.read(tip)
889 repo.dirstate.setparents(*cparents)
889 repo.dirstate.setparents(*cparents)
890 repo.dirstate.update(a, 'a')
890 repo.dirstate.update(a, 'a')
891 repo.dirstate.update(r, 'r')
891 repo.dirstate.update(r, 'r')
892 repo.dirstate.update(c, 'n')
892 repo.dirstate.update(c, 'n')
893 repo.dirstate.forget(forget)
893 repo.dirstate.forget(forget)
894
894
895 if not msg:
895 if not msg:
896 if not message:
896 if not message:
897 message = "patch queue: %s\n" % patch
897 message = "patch queue: %s\n" % patch
898 else:
898 else:
899 message = "\n".join(message)
899 message = "\n".join(message)
900 else:
900 else:
901 message = msg
901 message = msg
902
902
903 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
903 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
904 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
904 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
905 self.applied[-1] = StatusEntry(revlog.hex(n), patch)
905 self.applied[-1] = StatusEntry(revlog.hex(n), patch)
906 self.applied_dirty = 1
906 self.applied_dirty = 1
907 else:
907 else:
908 commands.dodiff(patchf, self.ui, repo, patchparent, None)
908 commands.dodiff(patchf, self.ui, repo, patchparent, None)
909 patchf.close()
909 patchf.close()
910 self.pop(repo, force=True, wlock=wlock)
910 self.pop(repo, force=True, wlock=wlock)
911 self.push(repo, force=True, wlock=wlock)
911 self.push(repo, force=True, wlock=wlock)
912
912
913 def init(self, repo, create=False):
913 def init(self, repo, create=False):
914 if os.path.isdir(self.path):
914 if os.path.isdir(self.path):
915 raise util.Abort(_("patch queue directory already exists"))
915 raise util.Abort(_("patch queue directory already exists"))
916 os.mkdir(self.path)
916 os.mkdir(self.path)
917 if create:
917 if create:
918 return self.qrepo(create=True)
918 return self.qrepo(create=True)
919
919
920 def unapplied(self, repo, patch=None):
920 def unapplied(self, repo, patch=None):
921 if patch and patch not in self.series:
921 if patch and patch not in self.series:
922 raise util.Abort(_("patch %s is not in series file") % patch)
922 raise util.Abort(_("patch %s is not in series file") % patch)
923 if not patch:
923 if not patch:
924 start = self.series_end()
924 start = self.series_end()
925 else:
925 else:
926 start = self.series.index(patch) + 1
926 start = self.series.index(patch) + 1
927 return [(i, self.series[i]) for i in xrange(start, len(self.series))]
927 return [(i, self.series[i]) for i in xrange(start, len(self.series))]
928
928
929 def qseries(self, repo, missing=None, summary=False):
929 def qseries(self, repo, missing=None, summary=False):
930 start = self.series_end()
930 start = self.series_end()
931 if not missing:
931 if not missing:
932 for i in range(len(self.series)):
932 for i in range(len(self.series)):
933 patch = self.series[i]
933 patch = self.series[i]
934 if self.ui.verbose:
934 if self.ui.verbose:
935 if i < start:
935 if i < start:
936 status = 'A'
936 status = 'A'
937 else:
937 else:
938 status = 'U'
938 status = 'U'
939 self.ui.write('%d %s ' % (i, status))
939 self.ui.write('%d %s ' % (i, status))
940 if summary:
940 if summary:
941 msg = self.readheaders(patch)[0]
941 msg = self.readheaders(patch)[0]
942 msg = msg and ': ' + msg[0] or ': '
942 msg = msg and ': ' + msg[0] or ': '
943 else:
943 else:
944 msg = ''
944 msg = ''
945 self.ui.write('%s%s\n' % (patch, msg))
945 self.ui.write('%s%s\n' % (patch, msg))
946 else:
946 else:
947 list = []
947 msng_list = []
948 for root, dirs, files in os.walk(self.path):
948 for root, dirs, files in os.walk(self.path):
949 d = root[len(self.path) + 1:]
949 d = root[len(self.path) + 1:]
950 for f in files:
950 for f in files:
951 fl = os.path.join(d, f)
951 fl = os.path.join(d, f)
952 if (fl not in self.series and
952 if (fl not in self.series and
953 fl not in (self.status_path, self.series_path)
953 fl not in (self.status_path, self.series_path)
954 and not fl.startswith('.')):
954 and not fl.startswith('.')):
955 list.append(fl)
955 msng_list.append(fl)
956 list.sort()
956 msng_list.sort()
957 if list:
957 if msng_list:
958 for x in list:
958 for x in msng_list:
959 if self.ui.verbose:
959 if self.ui.verbose:
960 self.ui.write("D ")
960 self.ui.write("D ")
961 self.ui.write("%s\n" % x)
961 self.ui.write("%s\n" % x)
962
962
963 def issaveline(self, l):
963 def issaveline(self, l):
964 name = l.split(':')[1]
964 name = l.split(':')[1]
965 if name == '.hg.patches.save.line':
965 if name == '.hg.patches.save.line':
966 return True
966 return True
967
967
968 def qrepo(self, create=False):
968 def qrepo(self, create=False):
969 if create or os.path.isdir(os.path.join(self.path, ".hg")):
969 if create or os.path.isdir(os.path.join(self.path, ".hg")):
970 return hg.repository(self.ui, path=self.path, create=create)
970 return hg.repository(self.ui, path=self.path, create=create)
971
971
972 def restore(self, repo, rev, delete=None, qupdate=None):
972 def restore(self, repo, rev, delete=None, qupdate=None):
973 c = repo.changelog.read(rev)
973 c = repo.changelog.read(rev)
974 desc = c[4].strip()
974 desc = c[4].strip()
975 lines = desc.splitlines()
975 lines = desc.splitlines()
976 i = 0
976 i = 0
977 datastart = None
977 datastart = None
978 series = []
978 series = []
979 applied = []
979 applied = []
980 qpp = None
980 qpp = None
981 for i in xrange(0, len(lines)):
981 for i in xrange(0, len(lines)):
982 if lines[i] == 'Patch Data:':
982 if lines[i] == 'Patch Data:':
983 datastart = i + 1
983 datastart = i + 1
984 elif lines[i].startswith('Dirstate:'):
984 elif lines[i].startswith('Dirstate:'):
985 l = lines[i].rstrip()
985 l = lines[i].rstrip()
986 l = l[10:].split(' ')
986 l = l[10:].split(' ')
987 qpp = [ hg.bin(x) for x in l ]
987 qpp = [ hg.bin(x) for x in l ]
988 elif datastart != None:
988 elif datastart != None:
989 l = lines[i].rstrip()
989 l = lines[i].rstrip()
990 se = StatusEntry(l)
990 se = StatusEntry(l)
991 id = se.rev
991 file_ = se.name
992 file = se.name
992 if se.rev:
993 if id:
994 applied.append(se)
993 applied.append(se)
995 series.append(file)
994 series.append(file_)
996 if datastart == None:
995 if datastart == None:
997 self.ui.warn("No saved patch data found\n")
996 self.ui.warn("No saved patch data found\n")
998 return 1
997 return 1
999 self.ui.warn("restoring status: %s\n" % lines[0])
998 self.ui.warn("restoring status: %s\n" % lines[0])
1000 self.full_series = series
999 self.full_series = series
1001 self.applied = applied
1000 self.applied = applied
1002 self.parse_series()
1001 self.parse_series()
1003 self.series_dirty = 1
1002 self.series_dirty = 1
1004 self.applied_dirty = 1
1003 self.applied_dirty = 1
1005 heads = repo.changelog.heads()
1004 heads = repo.changelog.heads()
1006 if delete:
1005 if delete:
1007 if rev not in heads:
1006 if rev not in heads:
1008 self.ui.warn("save entry has children, leaving it alone\n")
1007 self.ui.warn("save entry has children, leaving it alone\n")
1009 else:
1008 else:
1010 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1009 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1011 pp = repo.dirstate.parents()
1010 pp = repo.dirstate.parents()
1012 if rev in pp:
1011 if rev in pp:
1013 update = True
1012 update = True
1014 else:
1013 else:
1015 update = False
1014 update = False
1016 self.strip(repo, rev, update=update, backup='strip')
1015 self.strip(repo, rev, update=update, backup='strip')
1017 if qpp:
1016 if qpp:
1018 self.ui.warn("saved queue repository parents: %s %s\n" %
1017 self.ui.warn("saved queue repository parents: %s %s\n" %
1019 (hg.short(qpp[0]), hg.short(qpp[1])))
1018 (hg.short(qpp[0]), hg.short(qpp[1])))
1020 if qupdate:
1019 if qupdate:
1021 print "queue directory updating"
1020 print "queue directory updating"
1022 r = self.qrepo()
1021 r = self.qrepo()
1023 if not r:
1022 if not r:
1024 self.ui.warn("Unable to load queue repository\n")
1023 self.ui.warn("Unable to load queue repository\n")
1025 return 1
1024 return 1
1026 r.update(qpp[0], allow=False, force=True)
1025 r.update(qpp[0], allow=False, force=True)
1027
1026
1028 def save(self, repo, msg=None):
1027 def save(self, repo, msg=None):
1029 if len(self.applied) == 0:
1028 if len(self.applied) == 0:
1030 self.ui.warn("save: no patches applied, exiting\n")
1029 self.ui.warn("save: no patches applied, exiting\n")
1031 return 1
1030 return 1
1032 if self.issaveline(self.applied[-1]):
1031 if self.issaveline(self.applied[-1]):
1033 self.ui.warn("status is already saved\n")
1032 self.ui.warn("status is already saved\n")
1034 return 1
1033 return 1
1035
1034
1036 ar = [ ':' + x for x in self.full_series ]
1035 ar = [ ':' + x for x in self.full_series ]
1037 if not msg:
1036 if not msg:
1038 msg = "hg patches saved state"
1037 msg = "hg patches saved state"
1039 else:
1038 else:
1040 msg = "hg patches: " + msg.rstrip('\r\n')
1039 msg = "hg patches: " + msg.rstrip('\r\n')
1041 r = self.qrepo()
1040 r = self.qrepo()
1042 if r:
1041 if r:
1043 pp = r.dirstate.parents()
1042 pp = r.dirstate.parents()
1044 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1043 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1045 msg += "\n\nPatch Data:\n"
1044 msg += "\n\nPatch Data:\n"
1046 text = msg + "\n".join(str(self.applied)) + '\n' + (ar and "\n".join(ar)
1045 text = msg + "\n".join(str(self.applied)) + '\n' + (ar and "\n".join(ar)
1047 + '\n' or "")
1046 + '\n' or "")
1048 n = repo.commit(None, text, user=None, force=1)
1047 n = repo.commit(None, text, user=None, force=1)
1049 if not n:
1048 if not n:
1050 self.ui.warn("repo commit failed\n")
1049 self.ui.warn("repo commit failed\n")
1051 return 1
1050 return 1
1052 self.applied.append(StatusEntry(revlog.hex(n),'.hg.patches.save.line'))
1051 self.applied.append(StatusEntry(revlog.hex(n),'.hg.patches.save.line'))
1053 self.applied_dirty = 1
1052 self.applied_dirty = 1
1054
1053
1055 def full_series_end(self):
1054 def full_series_end(self):
1056 if len(self.applied) > 0:
1055 if len(self.applied) > 0:
1057 p = self.applied[-1].name
1056 p = self.applied[-1].name
1058 end = self.find_series(p)
1057 end = self.find_series(p)
1059 if end == None:
1058 if end == None:
1060 return len(self.full_series)
1059 return len(self.full_series)
1061 return end + 1
1060 return end + 1
1062 return 0
1061 return 0
1063
1062
1064 def series_end(self):
1063 def series_end(self):
1065 end = 0
1064 end = 0
1066 if len(self.applied) > 0:
1065 if len(self.applied) > 0:
1067 p = self.applied[-1].name
1066 p = self.applied[-1].name
1068 try:
1067 try:
1069 end = self.series.index(p)
1068 end = self.series.index(p)
1070 except ValueError:
1069 except ValueError:
1071 return 0
1070 return 0
1072 return end + 1
1071 return end + 1
1073 return end
1072 return end
1074
1073
1075 def qapplied(self, repo, patch=None):
1074 def qapplied(self, repo, patch=None):
1076 if patch and patch not in self.series:
1075 if patch and patch not in self.series:
1077 raise util.Abort(_("patch %s is not in series file") % patch)
1076 raise util.Abort(_("patch %s is not in series file") % patch)
1078 if not patch:
1077 if not patch:
1079 end = len(self.applied)
1078 end = len(self.applied)
1080 else:
1079 else:
1081 end = self.series.index(patch) + 1
1080 end = self.series.index(patch) + 1
1082 for x in xrange(end):
1081 for x in xrange(end):
1083 p = self.appliedname(x)
1082 p = self.appliedname(x)
1084 self.ui.write("%s\n" % p)
1083 self.ui.write("%s\n" % p)
1085
1084
1086 def appliedname(self, index):
1085 def appliedname(self, index):
1087 pname = self.applied[index].name
1086 pname = self.applied[index].name
1088 if not self.ui.verbose:
1087 if not self.ui.verbose:
1089 p = pname
1088 p = pname
1090 else:
1089 else:
1091 p = str(self.series.index(pname)) + " " + p
1090 p = str(self.series.index(pname)) + " " + p
1092 return p
1091 return p
1093
1092
1094 def top(self, repo):
1093 def top(self, repo):
1095 if len(self.applied):
1094 if len(self.applied):
1096 p = self.appliedname(-1)
1095 p = self.appliedname(-1)
1097 self.ui.write(p + '\n')
1096 self.ui.write(p + '\n')
1098 else:
1097 else:
1099 self.ui.write("No patches applied\n")
1098 self.ui.write("No patches applied\n")
1100
1099
1101 def next(self, repo):
1100 def next(self, repo):
1102 end = self.series_end()
1101 end = self.series_end()
1103 if end == len(self.series):
1102 if end == len(self.series):
1104 self.ui.write("All patches applied\n")
1103 self.ui.write("All patches applied\n")
1105 else:
1104 else:
1106 p = self.series[end]
1105 p = self.series[end]
1107 if self.ui.verbose:
1106 if self.ui.verbose:
1108 self.ui.write("%d " % self.series.index(p))
1107 self.ui.write("%d " % self.series.index(p))
1109 self.ui.write(p + '\n')
1108 self.ui.write(p + '\n')
1110
1109
1111 def prev(self, repo):
1110 def prev(self, repo):
1112 if len(self.applied) > 1:
1111 if len(self.applied) > 1:
1113 p = self.appliedname(-2)
1112 p = self.appliedname(-2)
1114 self.ui.write(p + '\n')
1113 self.ui.write(p + '\n')
1115 elif len(self.applied) == 1:
1114 elif len(self.applied) == 1:
1116 self.ui.write("Only one patch applied\n")
1115 self.ui.write("Only one patch applied\n")
1117 else:
1116 else:
1118 self.ui.write("No patches applied\n")
1117 self.ui.write("No patches applied\n")
1119
1118
1120 def qimport(self, repo, files, patch=None, existing=None, force=None):
1119 def qimport(self, repo, files, patch=None, existing=None, force=None):
1121 if len(files) > 1 and patch:
1120 if len(files) > 1 and patch:
1122 raise util.Abort(_('option "-n" not valid when importing multiple '
1121 raise util.Abort(_('option "-n" not valid when importing multiple '
1123 'files'))
1122 'files'))
1124 i = 0
1123 i = 0
1125 added = []
1124 added = []
1126 for filename in files:
1125 for filename in files:
1127 if existing:
1126 if existing:
1128 if not patch:
1127 if not patch:
1129 patch = filename
1128 patch = filename
1130 if not os.path.isfile(os.path.join(self.path, patch)):
1129 if not os.path.isfile(os.path.join(self.path, patch)):
1131 raise util.Abort(_("patch %s does not exist") % patch)
1130 raise util.Abort(_("patch %s does not exist") % patch)
1132 else:
1131 else:
1133 try:
1132 try:
1134 text = file(filename).read()
1133 text = file(filename).read()
1135 except IOError:
1134 except IOError:
1136 raise util.Abort(_("unable to read %s") % patch)
1135 raise util.Abort(_("unable to read %s") % patch)
1137 if not patch:
1136 if not patch:
1138 patch = os.path.split(filename)[1]
1137 patch = os.path.split(filename)[1]
1139 if not force and os.path.exists(os.path.join(self.path, patch)):
1138 if not force and os.path.exists(os.path.join(self.path, patch)):
1140 raise util.Abort(_('patch "%s" already exists') % patch)
1139 raise util.Abort(_('patch "%s" already exists') % patch)
1141 patchf = self.opener(patch, "w")
1140 patchf = self.opener(patch, "w")
1142 patchf.write(text)
1141 patchf.write(text)
1143 if patch in self.series:
1142 if patch in self.series:
1144 raise util.Abort(_('patch %s is already in the series file')
1143 raise util.Abort(_('patch %s is already in the series file')
1145 % patch)
1144 % patch)
1146 index = self.full_series_end() + i
1145 index = self.full_series_end() + i
1147 self.full_series[index:index] = [patch]
1146 self.full_series[index:index] = [patch]
1148 self.parse_series()
1147 self.parse_series()
1149 self.ui.warn("adding %s to series file\n" % patch)
1148 self.ui.warn("adding %s to series file\n" % patch)
1150 i += 1
1149 i += 1
1151 added.append(patch)
1150 added.append(patch)
1152 patch = None
1151 patch = None
1153 self.series_dirty = 1
1152 self.series_dirty = 1
1154 qrepo = self.qrepo()
1153 qrepo = self.qrepo()
1155 if qrepo:
1154 if qrepo:
1156 qrepo.add(added)
1155 qrepo.add(added)
1157
1156
1158 def delete(ui, repo, patch, **opts):
1157 def delete(ui, repo, patch, **opts):
1159 """remove a patch from the series file
1158 """remove a patch from the series file
1160
1159
1161 The patch must not be applied.
1160 The patch must not be applied.
1162 With -f, deletes the patch file as well as the series entry."""
1161 With -f, deletes the patch file as well as the series entry."""
1163 q = repo.mq
1162 q = repo.mq
1164 q.delete(repo, patch, force=opts.get('force'))
1163 q.delete(repo, patch, force=opts.get('force'))
1165 q.save_dirty()
1164 q.save_dirty()
1166 return 0
1165 return 0
1167
1166
1168 def applied(ui, repo, patch=None, **opts):
1167 def applied(ui, repo, patch=None, **opts):
1169 """print the patches already applied"""
1168 """print the patches already applied"""
1170 repo.mq.qapplied(repo, patch)
1169 repo.mq.qapplied(repo, patch)
1171 return 0
1170 return 0
1172
1171
1173 def unapplied(ui, repo, patch=None, **opts):
1172 def unapplied(ui, repo, patch=None, **opts):
1174 """print the patches not yet applied"""
1173 """print the patches not yet applied"""
1175 for i, p in repo.mq.unapplied(repo, patch):
1174 for i, p in repo.mq.unapplied(repo, patch):
1176 if ui.verbose:
1175 if ui.verbose:
1177 ui.write("%d " % i)
1176 ui.write("%d " % i)
1178 ui.write("%s\n" % p)
1177 ui.write("%s\n" % p)
1179
1178
1180 def qimport(ui, repo, *filename, **opts):
1179 def qimport(ui, repo, *filename, **opts):
1181 """import a patch"""
1180 """import a patch"""
1182 q = repo.mq
1181 q = repo.mq
1183 q.qimport(repo, filename, patch=opts['name'],
1182 q.qimport(repo, filename, patch=opts['name'],
1184 existing=opts['existing'], force=opts['force'])
1183 existing=opts['existing'], force=opts['force'])
1185 q.save_dirty()
1184 q.save_dirty()
1186 return 0
1185 return 0
1187
1186
1188 def init(ui, repo, **opts):
1187 def init(ui, repo, **opts):
1189 """init a new queue repository
1188 """init a new queue repository
1190
1189
1191 The queue repository is unversioned by default. If -c is
1190 The queue repository is unversioned by default. If -c is
1192 specified, qinit will create a separate nested repository
1191 specified, qinit will create a separate nested repository
1193 for patches. Use qcommit to commit changes to this queue
1192 for patches. Use qcommit to commit changes to this queue
1194 repository."""
1193 repository."""
1195 q = repo.mq
1194 q = repo.mq
1196 r = q.init(repo, create=opts['create_repo'])
1195 r = q.init(repo, create=opts['create_repo'])
1197 q.save_dirty()
1196 q.save_dirty()
1198 if r:
1197 if r:
1199 fp = r.wopener('.hgignore', 'w')
1198 fp = r.wopener('.hgignore', 'w')
1200 print >> fp, 'syntax: glob'
1199 print >> fp, 'syntax: glob'
1201 print >> fp, 'status'
1200 print >> fp, 'status'
1202 fp.close()
1201 fp.close()
1203 r.wopener('series', 'w').close()
1202 r.wopener('series', 'w').close()
1204 r.add(['.hgignore', 'series'])
1203 r.add(['.hgignore', 'series'])
1205 return 0
1204 return 0
1206
1205
1207 def clone(ui, source, dest=None, **opts):
1206 def clone(ui, source, dest=None, **opts):
1208 '''clone main and patch repository at same time
1207 '''clone main and patch repository at same time
1209
1208
1210 If source is local, destination will have no patches applied. If
1209 If source is local, destination will have no patches applied. If
1211 source is remote, this command can not check if patches are
1210 source is remote, this command can not check if patches are
1212 applied in source, so cannot guarantee that patches are not
1211 applied in source, so cannot guarantee that patches are not
1213 applied in destination. If you clone remote repository, be sure
1212 applied in destination. If you clone remote repository, be sure
1214 before that it has no patches applied.
1213 before that it has no patches applied.
1215
1214
1216 Source patch repository is looked for in <src>/.hg/patches by
1215 Source patch repository is looked for in <src>/.hg/patches by
1217 default. Use -p <url> to change.
1216 default. Use -p <url> to change.
1218 '''
1217 '''
1219 commands.setremoteconfig(ui, opts)
1218 commands.setremoteconfig(ui, opts)
1220 if dest is None:
1219 if dest is None:
1221 dest = hg.defaultdest(source)
1220 dest = hg.defaultdest(source)
1222 sr = hg.repository(ui, ui.expandpath(source))
1221 sr = hg.repository(ui, ui.expandpath(source))
1223 qbase, destrev = None, None
1222 qbase, destrev = None, None
1224 if sr.local():
1223 if sr.local():
1225 reposetup(ui, sr)
1224 reposetup(ui, sr)
1226 if sr.mq.applied:
1225 if sr.mq.applied:
1227 qbase = revlog.bin(sr.mq.applied[0].rev)
1226 qbase = revlog.bin(sr.mq.applied[0].rev)
1228 if not hg.islocal(dest):
1227 if not hg.islocal(dest):
1229 destrev = sr.parents(qbase)[0]
1228 destrev = sr.parents(qbase)[0]
1230 ui.note(_('cloning main repo\n'))
1229 ui.note(_('cloning main repo\n'))
1231 sr, dr = hg.clone(ui, sr, dest,
1230 sr, dr = hg.clone(ui, sr, dest,
1232 pull=opts['pull'],
1231 pull=opts['pull'],
1233 rev=destrev,
1232 rev=destrev,
1234 update=False,
1233 update=False,
1235 stream=opts['uncompressed'])
1234 stream=opts['uncompressed'])
1236 ui.note(_('cloning patch repo\n'))
1235 ui.note(_('cloning patch repo\n'))
1237 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1236 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1238 dr.url() + '/.hg/patches',
1237 dr.url() + '/.hg/patches',
1239 pull=opts['pull'],
1238 pull=opts['pull'],
1240 update=not opts['noupdate'],
1239 update=not opts['noupdate'],
1241 stream=opts['uncompressed'])
1240 stream=opts['uncompressed'])
1242 if dr.local():
1241 if dr.local():
1243 if qbase:
1242 if qbase:
1244 ui.note(_('stripping applied patches from destination repo\n'))
1243 ui.note(_('stripping applied patches from destination repo\n'))
1245 reposetup(ui, dr)
1244 reposetup(ui, dr)
1246 dr.mq.strip(dr, qbase, update=False, backup=None)
1245 dr.mq.strip(dr, qbase, update=False, backup=None)
1247 if not opts['noupdate']:
1246 if not opts['noupdate']:
1248 ui.note(_('updating destination repo\n'))
1247 ui.note(_('updating destination repo\n'))
1249 dr.update(dr.changelog.tip())
1248 dr.update(dr.changelog.tip())
1250
1249
1251 def commit(ui, repo, *pats, **opts):
1250 def commit(ui, repo, *pats, **opts):
1252 """commit changes in the queue repository"""
1251 """commit changes in the queue repository"""
1253 q = repo.mq
1252 q = repo.mq
1254 r = q.qrepo()
1253 r = q.qrepo()
1255 if not r: raise util.Abort('no queue repository')
1254 if not r: raise util.Abort('no queue repository')
1256 commands.commit(r.ui, r, *pats, **opts)
1255 commands.commit(r.ui, r, *pats, **opts)
1257
1256
1258 def series(ui, repo, **opts):
1257 def series(ui, repo, **opts):
1259 """print the entire series file"""
1258 """print the entire series file"""
1260 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1259 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1261 return 0
1260 return 0
1262
1261
1263 def top(ui, repo, **opts):
1262 def top(ui, repo, **opts):
1264 """print the name of the current patch"""
1263 """print the name of the current patch"""
1265 repo.mq.top(repo)
1264 repo.mq.top(repo)
1266 return 0
1265 return 0
1267
1266
1268 def next(ui, repo, **opts):
1267 def next(ui, repo, **opts):
1269 """print the name of the next patch"""
1268 """print the name of the next patch"""
1270 repo.mq.next(repo)
1269 repo.mq.next(repo)
1271 return 0
1270 return 0
1272
1271
1273 def prev(ui, repo, **opts):
1272 def prev(ui, repo, **opts):
1274 """print the name of the previous patch"""
1273 """print the name of the previous patch"""
1275 repo.mq.prev(repo)
1274 repo.mq.prev(repo)
1276 return 0
1275 return 0
1277
1276
1278 def new(ui, repo, patch, **opts):
1277 def new(ui, repo, patch, **opts):
1279 """create a new patch
1278 """create a new patch
1280
1279
1281 qnew creates a new patch on top of the currently-applied patch
1280 qnew creates a new patch on top of the currently-applied patch
1282 (if any). It will refuse to run if there are any outstanding
1281 (if any). It will refuse to run if there are any outstanding
1283 changes unless -f is specified, in which case the patch will
1282 changes unless -f is specified, in which case the patch will
1284 be initialised with them.
1283 be initialised with them.
1285
1284
1286 -m or -l set the patch header as well as the commit message.
1285 -m or -l set the patch header as well as the commit message.
1287 If neither is specified, the patch header is empty and the
1286 If neither is specified, the patch header is empty and the
1288 commit message is 'New patch: PATCH'"""
1287 commit message is 'New patch: PATCH'"""
1289 q = repo.mq
1288 q = repo.mq
1290 message=commands.logmessage(**opts)
1289 message=commands.logmessage(**opts)
1291 q.new(repo, patch, msg=message, force=opts['force'])
1290 q.new(repo, patch, msg=message, force=opts['force'])
1292 q.save_dirty()
1291 q.save_dirty()
1293 return 0
1292 return 0
1294
1293
1295 def refresh(ui, repo, **opts):
1294 def refresh(ui, repo, **opts):
1296 """update the current patch"""
1295 """update the current patch"""
1297 q = repo.mq
1296 q = repo.mq
1298 message=commands.logmessage(**opts)
1297 message=commands.logmessage(**opts)
1299 if opts['edit']:
1298 if opts['edit']:
1300 if message:
1299 if message:
1301 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1300 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1302 patch = q.applied[-1].name
1301 patch = q.applied[-1].name
1303 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1302 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1304 message = ui.edit('\n'.join(message), user or ui.username())
1303 message = ui.edit('\n'.join(message), user or ui.username())
1305 q.refresh(repo, msg=message, short=opts['short'])
1304 q.refresh(repo, msg=message, short=opts['short'])
1306 q.save_dirty()
1305 q.save_dirty()
1307 return 0
1306 return 0
1308
1307
1309 def diff(ui, repo, *files, **opts):
1308 def diff(ui, repo, *files, **opts):
1310 """diff of the current patch"""
1309 """diff of the current patch"""
1311 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1310 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1312 repo.mq.diff(repo, list(files))
1311 repo.mq.diff(repo, list(files))
1313 return 0
1312 return 0
1314
1313
1315 def fold(ui, repo, *files, **opts):
1314 def fold(ui, repo, *files, **opts):
1316 """fold the named patches into the current patch
1315 """fold the named patches into the current patch
1317
1316
1318 Patches must not yet be applied. Each patch will be successively
1317 Patches must not yet be applied. Each patch will be successively
1319 applied to the current patch in the order given. If all the
1318 applied to the current patch in the order given. If all the
1320 patches apply successfully, the current patch will be refreshed
1319 patches apply successfully, the current patch will be refreshed
1321 with the new cumulative patch, and the folded patches will
1320 with the new cumulative patch, and the folded patches will
1322 be deleted. With -f/--force, the folded patch files will
1321 be deleted. With -f/--force, the folded patch files will
1323 be removed afterwards.
1322 be removed afterwards.
1324
1323
1325 The header for each folded patch will be concatenated with
1324 The header for each folded patch will be concatenated with
1326 the current patch header, separated by a line of '* * *'."""
1325 the current patch header, separated by a line of '* * *'."""
1327
1326
1328 q = repo.mq
1327 q = repo.mq
1329
1328
1330 if not files:
1329 if not files:
1331 raise util.Abort(_('qfold requires at least one patch name'))
1330 raise util.Abort(_('qfold requires at least one patch name'))
1332 if not q.check_toppatch(repo):
1331 if not q.check_toppatch(repo):
1333 raise util.Abort(_('No patches applied\n'))
1332 raise util.Abort(_('No patches applied\n'))
1334
1333
1335 message=commands.logmessage(**opts)
1334 message=commands.logmessage(**opts)
1336 if opts['edit']:
1335 if opts['edit']:
1337 if message:
1336 if message:
1338 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1337 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1339
1338
1340 parent = q.lookup('qtip')
1339 parent = q.lookup('qtip')
1341 patches = []
1340 patches = []
1342 messages = []
1341 messages = []
1343 for f in files:
1342 for f in files:
1344 patch = q.lookup(f)
1343 patch = q.lookup(f)
1345 if patch in patches or patch == parent:
1344 if patch in patches or patch == parent:
1346 self.ui.warn(_('Skipping already folded patch %s') % patch)
1345 self.ui.warn(_('Skipping already folded patch %s') % patch)
1347 if q.isapplied(patch):
1346 if q.isapplied(patch):
1348 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1347 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1349 patches.append(patch)
1348 patches.append(patch)
1350
1349
1351 for patch in patches:
1350 for patch in patches:
1352 if not message:
1351 if not message:
1353 messages.append(q.readheaders(patch)[0])
1352 messages.append(q.readheaders(patch)[0])
1354 pf = os.path.join(q.path, patch)
1353 pf = os.path.join(q.path, patch)
1355 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1354 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1356 if not patchsuccess:
1355 if not patchsuccess:
1357 raise util.Abort(_('Error folding patch %s') % patch)
1356 raise util.Abort(_('Error folding patch %s') % patch)
1358
1357
1359 if not message:
1358 if not message:
1360 message, comments, user = q.readheaders(parent)[0:3]
1359 message, comments, user = q.readheaders(parent)[0:3]
1361 for msg in messages:
1360 for msg in messages:
1362 message.append('* * *')
1361 message.append('* * *')
1363 message.extend(msg)
1362 message.extend(msg)
1364 message = '\n'.join(message)
1363 message = '\n'.join(message)
1365
1364
1366 if opts['edit']:
1365 if opts['edit']:
1367 message = ui.edit(message, user or ui.username())
1366 message = ui.edit(message, user or ui.username())
1368
1367
1369 q.refresh(repo, msg=message)
1368 q.refresh(repo, msg=message)
1370
1369
1371 for patch in patches:
1370 for patch in patches:
1372 q.delete(repo, patch, force=opts['force'])
1371 q.delete(repo, patch, force=opts['force'])
1373
1372
1374 q.save_dirty()
1373 q.save_dirty()
1375
1374
1376 def header(ui, repo, patch=None):
1375 def header(ui, repo, patch=None):
1377 """Print the header of the topmost or specified patch"""
1376 """Print the header of the topmost or specified patch"""
1378 q = repo.mq
1377 q = repo.mq
1379
1378
1380 if patch:
1379 if patch:
1381 patch = q.lookup(patch)
1380 patch = q.lookup(patch)
1382 else:
1381 else:
1383 if not q.applied:
1382 if not q.applied:
1384 ui.write('No patches applied\n')
1383 ui.write('No patches applied\n')
1385 return
1384 return
1386 patch = q.lookup('qtip')
1385 patch = q.lookup('qtip')
1387 message = repo.mq.readheaders(patch)[0]
1386 message = repo.mq.readheaders(patch)[0]
1388
1387
1389 ui.write('\n'.join(message) + '\n')
1388 ui.write('\n'.join(message) + '\n')
1390
1389
1391 def lastsavename(path):
1390 def lastsavename(path):
1392 (dir, base) = os.path.split(path)
1391 (directory, base) = os.path.split(path)
1393 names = os.listdir(dir)
1392 names = os.listdir(directory)
1394 namere = re.compile("%s.([0-9]+)" % base)
1393 namere = re.compile("%s.([0-9]+)" % base)
1395 max = None
1394 maxindex = None
1396 maxname = None
1395 maxname = None
1397 for f in names:
1396 for f in names:
1398 m = namere.match(f)
1397 m = namere.match(f)
1399 if m:
1398 if m:
1400 index = int(m.group(1))
1399 index = int(m.group(1))
1401 if max == None or index > max:
1400 if maxindex == None or index > maxindex:
1402 max = index
1401 maxindex = index
1403 maxname = f
1402 maxname = f
1404 if maxname:
1403 if maxname:
1405 return (os.path.join(dir, maxname), max)
1404 return (os.path.join(directory, maxname), maxindex)
1406 return (None, None)
1405 return (None, None)
1407
1406
1408 def savename(path):
1407 def savename(path):
1409 (last, index) = lastsavename(path)
1408 (last, index) = lastsavename(path)
1410 if last is None:
1409 if last is None:
1411 index = 0
1410 index = 0
1412 newpath = path + ".%d" % (index + 1)
1411 newpath = path + ".%d" % (index + 1)
1413 return newpath
1412 return newpath
1414
1413
1415 def push(ui, repo, patch=None, **opts):
1414 def push(ui, repo, patch=None, **opts):
1416 """push the next patch onto the stack"""
1415 """push the next patch onto the stack"""
1417 q = repo.mq
1416 q = repo.mq
1418 mergeq = None
1417 mergeq = None
1419
1418
1420 if opts['all']:
1419 if opts['all']:
1421 patch = q.series[-1]
1420 patch = q.series[-1]
1422 if opts['merge']:
1421 if opts['merge']:
1423 if opts['name']:
1422 if opts['name']:
1424 newpath = opts['name']
1423 newpath = opts['name']
1425 else:
1424 else:
1426 newpath, i = lastsavename(q.path)
1425 newpath, i = lastsavename(q.path)
1427 if not newpath:
1426 if not newpath:
1428 ui.warn("no saved queues found, please use -n\n")
1427 ui.warn("no saved queues found, please use -n\n")
1429 return 1
1428 return 1
1430 mergeq = queue(ui, repo.join(""), newpath)
1429 mergeq = queue(ui, repo.join(""), newpath)
1431 ui.warn("merging with queue at: %s\n" % mergeq.path)
1430 ui.warn("merging with queue at: %s\n" % mergeq.path)
1432 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1431 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1433 mergeq=mergeq)
1432 mergeq=mergeq)
1434 q.save_dirty()
1433 q.save_dirty()
1435 return ret
1434 return ret
1436
1435
1437 def pop(ui, repo, patch=None, **opts):
1436 def pop(ui, repo, patch=None, **opts):
1438 """pop the current patch off the stack"""
1437 """pop the current patch off the stack"""
1439 localupdate = True
1438 localupdate = True
1440 if opts['name']:
1439 if opts['name']:
1441 q = queue(ui, repo.join(""), repo.join(opts['name']))
1440 q = queue(ui, repo.join(""), repo.join(opts['name']))
1442 ui.warn('using patch queue: %s\n' % q.path)
1441 ui.warn('using patch queue: %s\n' % q.path)
1443 localupdate = False
1442 localupdate = False
1444 else:
1443 else:
1445 q = repo.mq
1444 q = repo.mq
1446 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1445 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1447 q.save_dirty()
1446 q.save_dirty()
1448 return 0
1447 return 0
1449
1448
1450 def rename(ui, repo, patch, name=None, **opts):
1449 def rename(ui, repo, patch, name=None, **opts):
1451 """rename a patch
1450 """rename a patch
1452
1451
1453 With one argument, renames the current patch to PATCH1.
1452 With one argument, renames the current patch to PATCH1.
1454 With two arguments, renames PATCH1 to PATCH2."""
1453 With two arguments, renames PATCH1 to PATCH2."""
1455
1454
1456 q = repo.mq
1455 q = repo.mq
1457
1456
1458 if not name:
1457 if not name:
1459 name = patch
1458 name = patch
1460 patch = None
1459 patch = None
1461
1460
1462 if name in q.series:
1461 if name in q.series:
1463 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1462 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1464
1463
1465 absdest = os.path.join(q.path, name)
1464 absdest = os.path.join(q.path, name)
1466 if os.path.exists(absdest):
1465 if os.path.exists(absdest):
1467 raise util.Abort(_('%s already exists') % absdest)
1466 raise util.Abort(_('%s already exists') % absdest)
1468
1467
1469 if patch:
1468 if patch:
1470 patch = q.lookup(patch)
1469 patch = q.lookup(patch)
1471 else:
1470 else:
1472 if not q.applied:
1471 if not q.applied:
1473 ui.write(_('No patches applied\n'))
1472 ui.write(_('No patches applied\n'))
1474 return
1473 return
1475 patch = q.lookup('qtip')
1474 patch = q.lookup('qtip')
1476
1475
1477 if ui.verbose:
1476 if ui.verbose:
1478 ui.write('Renaming %s to %s\n' % (patch, name))
1477 ui.write('Renaming %s to %s\n' % (patch, name))
1479 i = q.find_series(patch)
1478 i = q.find_series(patch)
1480 q.full_series[i] = name
1479 q.full_series[i] = name
1481 q.parse_series()
1480 q.parse_series()
1482 q.series_dirty = 1
1481 q.series_dirty = 1
1483
1482
1484 info = q.isapplied(patch)
1483 info = q.isapplied(patch)
1485 if info:
1484 if info:
1486 q.applied[info[0]] = StatusEntry(info[1], name)
1485 q.applied[info[0]] = StatusEntry(info[1], name)
1487 q.applied_dirty = 1
1486 q.applied_dirty = 1
1488
1487
1489 util.rename(os.path.join(q.path, patch), absdest)
1488 util.rename(os.path.join(q.path, patch), absdest)
1490 r = q.qrepo()
1489 r = q.qrepo()
1491 if r:
1490 if r:
1492 wlock = r.wlock()
1491 wlock = r.wlock()
1493 if r.dirstate.state(name) == 'r':
1492 if r.dirstate.state(name) == 'r':
1494 r.undelete([name], wlock)
1493 r.undelete([name], wlock)
1495 r.copy(patch, name, wlock)
1494 r.copy(patch, name, wlock)
1496 r.remove([patch], False, wlock)
1495 r.remove([patch], False, wlock)
1497
1496
1498 q.save_dirty()
1497 q.save_dirty()
1499
1498
1500 def restore(ui, repo, rev, **opts):
1499 def restore(ui, repo, rev, **opts):
1501 """restore the queue state saved by a rev"""
1500 """restore the queue state saved by a rev"""
1502 rev = repo.lookup(rev)
1501 rev = repo.lookup(rev)
1503 q = repo.mq
1502 q = repo.mq
1504 q.restore(repo, rev, delete=opts['delete'],
1503 q.restore(repo, rev, delete=opts['delete'],
1505 qupdate=opts['update'])
1504 qupdate=opts['update'])
1506 q.save_dirty()
1505 q.save_dirty()
1507 return 0
1506 return 0
1508
1507
1509 def save(ui, repo, **opts):
1508 def save(ui, repo, **opts):
1510 """save current queue state"""
1509 """save current queue state"""
1511 q = repo.mq
1510 q = repo.mq
1512 message=commands.logmessage(**opts)
1511 message=commands.logmessage(**opts)
1513 ret = q.save(repo, msg=message)
1512 ret = q.save(repo, msg=message)
1514 if ret:
1513 if ret:
1515 return ret
1514 return ret
1516 q.save_dirty()
1515 q.save_dirty()
1517 if opts['copy']:
1516 if opts['copy']:
1518 path = q.path
1517 path = q.path
1519 if opts['name']:
1518 if opts['name']:
1520 newpath = os.path.join(q.basepath, opts['name'])
1519 newpath = os.path.join(q.basepath, opts['name'])
1521 if os.path.exists(newpath):
1520 if os.path.exists(newpath):
1522 if not os.path.isdir(newpath):
1521 if not os.path.isdir(newpath):
1523 raise util.Abort(_('destination %s exists and is not '
1522 raise util.Abort(_('destination %s exists and is not '
1524 'a directory') % newpath)
1523 'a directory') % newpath)
1525 if not opts['force']:
1524 if not opts['force']:
1526 raise util.Abort(_('destination %s exists, '
1525 raise util.Abort(_('destination %s exists, '
1527 'use -f to force') % newpath)
1526 'use -f to force') % newpath)
1528 else:
1527 else:
1529 newpath = savename(path)
1528 newpath = savename(path)
1530 ui.warn("copy %s to %s\n" % (path, newpath))
1529 ui.warn("copy %s to %s\n" % (path, newpath))
1531 util.copyfiles(path, newpath)
1530 util.copyfiles(path, newpath)
1532 if opts['empty']:
1531 if opts['empty']:
1533 try:
1532 try:
1534 os.unlink(os.path.join(q.path, q.status_path))
1533 os.unlink(os.path.join(q.path, q.status_path))
1535 except:
1534 except:
1536 pass
1535 pass
1537 return 0
1536 return 0
1538
1537
1539 def strip(ui, repo, rev, **opts):
1538 def strip(ui, repo, rev, **opts):
1540 """strip a revision and all later revs on the same branch"""
1539 """strip a revision and all later revs on the same branch"""
1541 rev = repo.lookup(rev)
1540 rev = repo.lookup(rev)
1542 backup = 'all'
1541 backup = 'all'
1543 if opts['backup']:
1542 if opts['backup']:
1544 backup = 'strip'
1543 backup = 'strip'
1545 elif opts['nobackup']:
1544 elif opts['nobackup']:
1546 backup = 'none'
1545 backup = 'none'
1547 repo.mq.strip(repo, rev, backup=backup)
1546 repo.mq.strip(repo, rev, backup=backup)
1548 return 0
1547 return 0
1549
1548
1550 def version(ui, q=None):
1549 def version(ui, q=None):
1551 """print the version number of the mq extension"""
1550 """print the version number of the mq extension"""
1552 ui.write("mq version %s\n" % versionstr)
1551 ui.write("mq version %s\n" % versionstr)
1553 return 0
1552 return 0
1554
1553
1555 def reposetup(ui, repo):
1554 def reposetup(ui, repo):
1556 class MqRepo(repo.__class__):
1555 class MqRepo(repo.__class__):
1557 def tags(self):
1556 def tags(self):
1558 if self.tagscache:
1557 if self.tagscache:
1559 return self.tagscache
1558 return self.tagscache
1560
1559
1561 tagscache = super(MqRepo, self).tags()
1560 tagscache = super(MqRepo, self).tags()
1562
1561
1563 q = self.mq
1562 q = self.mq
1564 if not q.applied:
1563 if not q.applied:
1565 return tagscache
1564 return tagscache
1566
1565
1567 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1566 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1568 mqtags.append((mqtags[-1][0], 'qtip'))
1567 mqtags.append((mqtags[-1][0], 'qtip'))
1569 mqtags.append((mqtags[0][0], 'qbase'))
1568 mqtags.append((mqtags[0][0], 'qbase'))
1570 for patch in mqtags:
1569 for patch in mqtags:
1571 if patch[1] in tagscache:
1570 if patch[1] in tagscache:
1572 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1571 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1573 else:
1572 else:
1574 tagscache[patch[1]] = revlog.bin(patch[0])
1573 tagscache[patch[1]] = revlog.bin(patch[0])
1575
1574
1576 return tagscache
1575 return tagscache
1577
1576
1578 repo.__class__ = MqRepo
1577 repo.__class__ = MqRepo
1579 repo.mq = queue(ui, repo.join(""))
1578 repo.mq = queue(ui, repo.join(""))
1580
1579
1581 cmdtable = {
1580 cmdtable = {
1582 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1581 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1583 "qclone": (clone,
1582 "qclone": (clone,
1584 [('', 'pull', None, _('use pull protocol to copy metadata')),
1583 [('', 'pull', None, _('use pull protocol to copy metadata')),
1585 ('U', 'noupdate', None, _('do not update the new working directories')),
1584 ('U', 'noupdate', None, _('do not update the new working directories')),
1586 ('', 'uncompressed', None,
1585 ('', 'uncompressed', None,
1587 _('use uncompressed transfer (fast over LAN)')),
1586 _('use uncompressed transfer (fast over LAN)')),
1588 ('e', 'ssh', '', _('specify ssh command to use')),
1587 ('e', 'ssh', '', _('specify ssh command to use')),
1589 ('p', 'patches', '', _('location of source patch repo')),
1588 ('p', 'patches', '', _('location of source patch repo')),
1590 ('', 'remotecmd', '',
1589 ('', 'remotecmd', '',
1591 _('specify hg command to run on the remote side'))],
1590 _('specify hg command to run on the remote side'))],
1592 'hg qclone [OPTION]... SOURCE [DEST]'),
1591 'hg qclone [OPTION]... SOURCE [DEST]'),
1593 "qcommit|qci":
1592 "qcommit|qci":
1594 (commit,
1593 (commit,
1595 commands.table["^commit|ci"][1],
1594 commands.table["^commit|ci"][1],
1596 'hg qcommit [OPTION]... [FILE]...'),
1595 'hg qcommit [OPTION]... [FILE]...'),
1597 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1596 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1598 "qdelete":
1597 "qdelete":
1599 (delete,
1598 (delete,
1600 [('f', 'force', None, _('delete patch file'))],
1599 [('f', 'force', None, _('delete patch file'))],
1601 'hg qdelete [-f] PATCH'),
1600 'hg qdelete [-f] PATCH'),
1602 'qfold':
1601 'qfold':
1603 (fold,
1602 (fold,
1604 [('e', 'edit', None, _('edit patch header')),
1603 [('e', 'edit', None, _('edit patch header')),
1605 ('f', 'force', None, _('delete folded patch files')),
1604 ('f', 'force', None, _('delete folded patch files')),
1606 ('m', 'message', '', _('set patch header to <text>')),
1605 ('m', 'message', '', _('set patch header to <text>')),
1607 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1606 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1608 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1607 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1609 'qheader': (header, [],
1608 'qheader': (header, [],
1610 _('hg qheader [PATCH]')),
1609 _('hg qheader [PATCH]')),
1611 "^qimport":
1610 "^qimport":
1612 (qimport,
1611 (qimport,
1613 [('e', 'existing', None, 'import file in patch dir'),
1612 [('e', 'existing', None, 'import file in patch dir'),
1614 ('n', 'name', '', 'patch file name'),
1613 ('n', 'name', '', 'patch file name'),
1615 ('f', 'force', None, 'overwrite existing files')],
1614 ('f', 'force', None, 'overwrite existing files')],
1616 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1615 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1617 "^qinit":
1616 "^qinit":
1618 (init,
1617 (init,
1619 [('c', 'create-repo', None, 'create queue repository')],
1618 [('c', 'create-repo', None, 'create queue repository')],
1620 'hg qinit [-c]'),
1619 'hg qinit [-c]'),
1621 "qnew":
1620 "qnew":
1622 (new,
1621 (new,
1623 [('m', 'message', '', _('use <text> as commit message')),
1622 [('m', 'message', '', _('use <text> as commit message')),
1624 ('l', 'logfile', '', _('read the commit message from <file>')),
1623 ('l', 'logfile', '', _('read the commit message from <file>')),
1625 ('f', 'force', None, _('import uncommitted changes into patch'))],
1624 ('f', 'force', None, _('import uncommitted changes into patch'))],
1626 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1625 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1627 "qnext": (next, [], 'hg qnext'),
1626 "qnext": (next, [], 'hg qnext'),
1628 "qprev": (prev, [], 'hg qprev'),
1627 "qprev": (prev, [], 'hg qprev'),
1629 "^qpop":
1628 "^qpop":
1630 (pop,
1629 (pop,
1631 [('a', 'all', None, 'pop all patches'),
1630 [('a', 'all', None, 'pop all patches'),
1632 ('n', 'name', '', 'queue name to pop'),
1631 ('n', 'name', '', 'queue name to pop'),
1633 ('f', 'force', None, 'forget any local changes')],
1632 ('f', 'force', None, 'forget any local changes')],
1634 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1633 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1635 "^qpush":
1634 "^qpush":
1636 (push,
1635 (push,
1637 [('f', 'force', None, 'apply if the patch has rejects'),
1636 [('f', 'force', None, 'apply if the patch has rejects'),
1638 ('l', 'list', None, 'list patch name in commit text'),
1637 ('l', 'list', None, 'list patch name in commit text'),
1639 ('a', 'all', None, 'apply all patches'),
1638 ('a', 'all', None, 'apply all patches'),
1640 ('m', 'merge', None, 'merge from another queue'),
1639 ('m', 'merge', None, 'merge from another queue'),
1641 ('n', 'name', '', 'merge queue name')],
1640 ('n', 'name', '', 'merge queue name')],
1642 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1641 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1643 "^qrefresh":
1642 "^qrefresh":
1644 (refresh,
1643 (refresh,
1645 [('e', 'edit', None, _('edit commit message')),
1644 [('e', 'edit', None, _('edit commit message')),
1646 ('m', 'message', '', _('change commit message with <text>')),
1645 ('m', 'message', '', _('change commit message with <text>')),
1647 ('l', 'logfile', '', _('change commit message with <file> content')),
1646 ('l', 'logfile', '', _('change commit message with <file> content')),
1648 ('s', 'short', None, 'short refresh')],
1647 ('s', 'short', None, 'short refresh')],
1649 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1648 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1650 'qrename|qmv':
1649 'qrename|qmv':
1651 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1650 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1652 "qrestore":
1651 "qrestore":
1653 (restore,
1652 (restore,
1654 [('d', 'delete', None, 'delete save entry'),
1653 [('d', 'delete', None, 'delete save entry'),
1655 ('u', 'update', None, 'update queue working dir')],
1654 ('u', 'update', None, 'update queue working dir')],
1656 'hg qrestore [-d] [-u] REV'),
1655 'hg qrestore [-d] [-u] REV'),
1657 "qsave":
1656 "qsave":
1658 (save,
1657 (save,
1659 [('m', 'message', '', _('use <text> as commit message')),
1658 [('m', 'message', '', _('use <text> as commit message')),
1660 ('l', 'logfile', '', _('read the commit message from <file>')),
1659 ('l', 'logfile', '', _('read the commit message from <file>')),
1661 ('c', 'copy', None, 'copy patch directory'),
1660 ('c', 'copy', None, 'copy patch directory'),
1662 ('n', 'name', '', 'copy directory name'),
1661 ('n', 'name', '', 'copy directory name'),
1663 ('e', 'empty', None, 'clear queue status file'),
1662 ('e', 'empty', None, 'clear queue status file'),
1664 ('f', 'force', None, 'force copy')],
1663 ('f', 'force', None, 'force copy')],
1665 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1664 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1666 "qseries":
1665 "qseries":
1667 (series,
1666 (series,
1668 [('m', 'missing', None, 'print patches not in series'),
1667 [('m', 'missing', None, 'print patches not in series'),
1669 ('s', 'summary', None, _('print first line of patch header'))],
1668 ('s', 'summary', None, _('print first line of patch header'))],
1670 'hg qseries [-m]'),
1669 'hg qseries [-m]'),
1671 "^strip":
1670 "^strip":
1672 (strip,
1671 (strip,
1673 [('f', 'force', None, 'force multi-head removal'),
1672 [('f', 'force', None, 'force multi-head removal'),
1674 ('b', 'backup', None, 'bundle unrelated changesets'),
1673 ('b', 'backup', None, 'bundle unrelated changesets'),
1675 ('n', 'nobackup', None, 'no backups')],
1674 ('n', 'nobackup', None, 'no backups')],
1676 'hg strip [-f] [-b] [-n] REV'),
1675 'hg strip [-f] [-b] [-n] REV'),
1677 "qtop": (top, [], 'hg qtop'),
1676 "qtop": (top, [], 'hg qtop'),
1678 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1677 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1679 "qversion": (version, [], 'hg qversion')
1678 "qversion": (version, [], 'hg qversion')
1680 }
1679 }
1681
1680
General Comments 0
You need to be logged in to leave comments. Login now