##// END OF EJS Templates
mq: codingstyle
Benoit Boissinot -
r2796:4c395680 default
parent child Browse files
Show More
@@ -1,1679 +1,1679 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 seen_list = seen.keys()
508 seen_list = seen.keys()
509 seen_list.sort()
509 seen_list.sort()
510 for f in seen_list:
510 for f in seen_list:
511 ff = repo.file(f)
511 ff = repo.file(f)
512 filerev = seen[f]
512 filerev = seen[f]
513 if filerev != 0:
513 if filerev != 0:
514 if filerev in ff.nodemap:
514 if filerev in ff.nodemap:
515 filerev = ff.rev(filerev)
515 filerev = ff.rev(filerev)
516 else:
516 else:
517 filerev = 0
517 filerev = 0
518 ff.strip(filerev, revnum)
518 ff.strip(filerev, revnum)
519
519
520 if not wlock:
520 if not wlock:
521 wlock = repo.wlock()
521 wlock = repo.wlock()
522 lock = repo.lock()
522 lock = repo.lock()
523 chlog = repo.changelog
523 chlog = repo.changelog
524 # TODO delete the undo files, and handle undo of merge sets
524 # TODO delete the undo files, and handle undo of merge sets
525 pp = chlog.parents(rev)
525 pp = chlog.parents(rev)
526 revnum = chlog.rev(rev)
526 revnum = chlog.rev(rev)
527
527
528 if update:
528 if update:
529 (c, a, r, d, u) = repo.changes(None, None)
529 (c, a, r, d, u) = repo.changes(None, None)
530 if c or a or d or r:
530 if c or a or d or r:
531 raise util.Abort(_("local changes found"))
531 raise util.Abort(_("local changes found"))
532 urev = self.qparents(repo, rev)
532 urev = self.qparents(repo, rev)
533 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 msng_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 msng_list.append(fl)
955 msng_list.append(fl)
956 msng_list.sort()
956 msng_list.sort()
957 for x in msng_list:
957 for x in msng_list:
958 if self.ui.verbose:
958 if self.ui.verbose:
959 self.ui.write("D ")
959 self.ui.write("D ")
960 self.ui.write("%s\n" % x)
960 self.ui.write("%s\n" % x)
961
961
962 def issaveline(self, l):
962 def issaveline(self, l):
963 name = l.split(':')[1]
963 name = l.split(':')[1]
964 if name == '.hg.patches.save.line':
964 if name == '.hg.patches.save.line':
965 return True
965 return True
966
966
967 def qrepo(self, create=False):
967 def qrepo(self, create=False):
968 if create or os.path.isdir(os.path.join(self.path, ".hg")):
968 if create or os.path.isdir(os.path.join(self.path, ".hg")):
969 return hg.repository(self.ui, path=self.path, create=create)
969 return hg.repository(self.ui, path=self.path, create=create)
970
970
971 def restore(self, repo, rev, delete=None, qupdate=None):
971 def restore(self, repo, rev, delete=None, qupdate=None):
972 c = repo.changelog.read(rev)
972 c = repo.changelog.read(rev)
973 desc = c[4].strip()
973 desc = c[4].strip()
974 lines = desc.splitlines()
974 lines = desc.splitlines()
975 i = 0
975 i = 0
976 datastart = None
976 datastart = None
977 series = []
977 series = []
978 applied = []
978 applied = []
979 qpp = None
979 qpp = None
980 for i in xrange(0, len(lines)):
980 for i in xrange(0, len(lines)):
981 if lines[i] == 'Patch Data:':
981 if lines[i] == 'Patch Data:':
982 datastart = i + 1
982 datastart = i + 1
983 elif lines[i].startswith('Dirstate:'):
983 elif lines[i].startswith('Dirstate:'):
984 l = lines[i].rstrip()
984 l = lines[i].rstrip()
985 l = l[10:].split(' ')
985 l = l[10:].split(' ')
986 qpp = [ hg.bin(x) for x in l ]
986 qpp = [ hg.bin(x) for x in l ]
987 elif datastart != None:
987 elif datastart != None:
988 l = lines[i].rstrip()
988 l = lines[i].rstrip()
989 se = StatusEntry(l)
989 se = StatusEntry(l)
990 file_ = se.name
990 file_ = se.name
991 if se.rev:
991 if se.rev:
992 applied.append(se)
992 applied.append(se)
993 series.append(file_)
993 series.append(file_)
994 if datastart == None:
994 if datastart == None:
995 self.ui.warn("No saved patch data found\n")
995 self.ui.warn("No saved patch data found\n")
996 return 1
996 return 1
997 self.ui.warn("restoring status: %s\n" % lines[0])
997 self.ui.warn("restoring status: %s\n" % lines[0])
998 self.full_series = series
998 self.full_series = series
999 self.applied = applied
999 self.applied = applied
1000 self.parse_series()
1000 self.parse_series()
1001 self.series_dirty = 1
1001 self.series_dirty = 1
1002 self.applied_dirty = 1
1002 self.applied_dirty = 1
1003 heads = repo.changelog.heads()
1003 heads = repo.changelog.heads()
1004 if delete:
1004 if delete:
1005 if rev not in heads:
1005 if rev not in heads:
1006 self.ui.warn("save entry has children, leaving it alone\n")
1006 self.ui.warn("save entry has children, leaving it alone\n")
1007 else:
1007 else:
1008 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1008 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1009 pp = repo.dirstate.parents()
1009 pp = repo.dirstate.parents()
1010 if rev in pp:
1010 if rev in pp:
1011 update = True
1011 update = True
1012 else:
1012 else:
1013 update = False
1013 update = False
1014 self.strip(repo, rev, update=update, backup='strip')
1014 self.strip(repo, rev, update=update, backup='strip')
1015 if qpp:
1015 if qpp:
1016 self.ui.warn("saved queue repository parents: %s %s\n" %
1016 self.ui.warn("saved queue repository parents: %s %s\n" %
1017 (hg.short(qpp[0]), hg.short(qpp[1])))
1017 (hg.short(qpp[0]), hg.short(qpp[1])))
1018 if qupdate:
1018 if qupdate:
1019 print "queue directory updating"
1019 print "queue directory updating"
1020 r = self.qrepo()
1020 r = self.qrepo()
1021 if not r:
1021 if not r:
1022 self.ui.warn("Unable to load queue repository\n")
1022 self.ui.warn("Unable to load queue repository\n")
1023 return 1
1023 return 1
1024 r.update(qpp[0], allow=False, force=True)
1024 r.update(qpp[0], allow=False, force=True)
1025
1025
1026 def save(self, repo, msg=None):
1026 def save(self, repo, msg=None):
1027 if len(self.applied) == 0:
1027 if len(self.applied) == 0:
1028 self.ui.warn("save: no patches applied, exiting\n")
1028 self.ui.warn("save: no patches applied, exiting\n")
1029 return 1
1029 return 1
1030 if self.issaveline(self.applied[-1]):
1030 if self.issaveline(self.applied[-1]):
1031 self.ui.warn("status is already saved\n")
1031 self.ui.warn("status is already saved\n")
1032 return 1
1032 return 1
1033
1033
1034 ar = [ ':' + x for x in self.full_series ]
1034 ar = [ ':' + x for x in self.full_series ]
1035 if not msg:
1035 if not msg:
1036 msg = "hg patches saved state"
1036 msg = "hg patches saved state"
1037 else:
1037 else:
1038 msg = "hg patches: " + msg.rstrip('\r\n')
1038 msg = "hg patches: " + msg.rstrip('\r\n')
1039 r = self.qrepo()
1039 r = self.qrepo()
1040 if r:
1040 if r:
1041 pp = r.dirstate.parents()
1041 pp = r.dirstate.parents()
1042 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1042 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1043 msg += "\n\nPatch Data:\n"
1043 msg += "\n\nPatch Data:\n"
1044 text = msg + "\n".join(str(self.applied)) + '\n' + (ar and "\n".join(ar)
1044 text = msg + "\n".join(str(self.applied)) + '\n' + (ar and "\n".join(ar)
1045 + '\n' or "")
1045 + '\n' or "")
1046 n = repo.commit(None, text, user=None, force=1)
1046 n = repo.commit(None, text, user=None, force=1)
1047 if not n:
1047 if not n:
1048 self.ui.warn("repo commit failed\n")
1048 self.ui.warn("repo commit failed\n")
1049 return 1
1049 return 1
1050 self.applied.append(StatusEntry(revlog.hex(n),'.hg.patches.save.line'))
1050 self.applied.append(StatusEntry(revlog.hex(n),'.hg.patches.save.line'))
1051 self.applied_dirty = 1
1051 self.applied_dirty = 1
1052
1052
1053 def full_series_end(self):
1053 def full_series_end(self):
1054 if len(self.applied) > 0:
1054 if len(self.applied) > 0:
1055 p = self.applied[-1].name
1055 p = self.applied[-1].name
1056 end = self.find_series(p)
1056 end = self.find_series(p)
1057 if end == None:
1057 if end == None:
1058 return len(self.full_series)
1058 return len(self.full_series)
1059 return end + 1
1059 return end + 1
1060 return 0
1060 return 0
1061
1061
1062 def series_end(self):
1062 def series_end(self):
1063 end = 0
1063 end = 0
1064 if len(self.applied) > 0:
1064 if len(self.applied) > 0:
1065 p = self.applied[-1].name
1065 p = self.applied[-1].name
1066 try:
1066 try:
1067 end = self.series.index(p)
1067 end = self.series.index(p)
1068 except ValueError:
1068 except ValueError:
1069 return 0
1069 return 0
1070 return end + 1
1070 return end + 1
1071 return end
1071 return end
1072
1072
1073 def qapplied(self, repo, patch=None):
1073 def qapplied(self, repo, patch=None):
1074 if patch and patch not in self.series:
1074 if patch and patch not in self.series:
1075 raise util.Abort(_("patch %s is not in series file") % patch)
1075 raise util.Abort(_("patch %s is not in series file") % patch)
1076 if not patch:
1076 if not patch:
1077 end = len(self.applied)
1077 end = len(self.applied)
1078 else:
1078 else:
1079 end = self.series.index(patch) + 1
1079 end = self.series.index(patch) + 1
1080 for x in xrange(end):
1080 for x in xrange(end):
1081 p = self.appliedname(x)
1081 p = self.appliedname(x)
1082 self.ui.write("%s\n" % p)
1082 self.ui.write("%s\n" % p)
1083
1083
1084 def appliedname(self, index):
1084 def appliedname(self, index):
1085 pname = self.applied[index].name
1085 pname = self.applied[index].name
1086 if not self.ui.verbose:
1086 if not self.ui.verbose:
1087 p = pname
1087 p = pname
1088 else:
1088 else:
1089 p = str(self.series.index(pname)) + " " + p
1089 p = str(self.series.index(pname)) + " " + p
1090 return p
1090 return p
1091
1091
1092 def top(self, repo):
1092 def top(self, repo):
1093 if len(self.applied):
1093 if len(self.applied):
1094 p = self.appliedname(-1)
1094 p = self.appliedname(-1)
1095 self.ui.write(p + '\n')
1095 self.ui.write(p + '\n')
1096 else:
1096 else:
1097 self.ui.write("No patches applied\n")
1097 self.ui.write("No patches applied\n")
1098
1098
1099 def next(self, repo):
1099 def next(self, repo):
1100 end = self.series_end()
1100 end = self.series_end()
1101 if end == len(self.series):
1101 if end == len(self.series):
1102 self.ui.write("All patches applied\n")
1102 self.ui.write("All patches applied\n")
1103 else:
1103 else:
1104 p = self.series[end]
1104 p = self.series[end]
1105 if self.ui.verbose:
1105 if self.ui.verbose:
1106 self.ui.write("%d " % self.series.index(p))
1106 self.ui.write("%d " % self.series.index(p))
1107 self.ui.write(p + '\n')
1107 self.ui.write(p + '\n')
1108
1108
1109 def prev(self, repo):
1109 def prev(self, repo):
1110 if len(self.applied) > 1:
1110 if len(self.applied) > 1:
1111 p = self.appliedname(-2)
1111 p = self.appliedname(-2)
1112 self.ui.write(p + '\n')
1112 self.ui.write(p + '\n')
1113 elif len(self.applied) == 1:
1113 elif len(self.applied) == 1:
1114 self.ui.write("Only one patch applied\n")
1114 self.ui.write("Only one patch applied\n")
1115 else:
1115 else:
1116 self.ui.write("No patches applied\n")
1116 self.ui.write("No patches applied\n")
1117
1117
1118 def qimport(self, repo, files, patch=None, existing=None, force=None):
1118 def qimport(self, repo, files, patch=None, existing=None, force=None):
1119 if len(files) > 1 and patch:
1119 if len(files) > 1 and patch:
1120 raise util.Abort(_('option "-n" not valid when importing multiple '
1120 raise util.Abort(_('option "-n" not valid when importing multiple '
1121 'files'))
1121 'files'))
1122 i = 0
1122 i = 0
1123 added = []
1123 added = []
1124 for filename in files:
1124 for filename in files:
1125 if existing:
1125 if existing:
1126 if not patch:
1126 if not patch:
1127 patch = filename
1127 patch = filename
1128 if not os.path.isfile(os.path.join(self.path, patch)):
1128 if not os.path.isfile(os.path.join(self.path, patch)):
1129 raise util.Abort(_("patch %s does not exist") % patch)
1129 raise util.Abort(_("patch %s does not exist") % patch)
1130 else:
1130 else:
1131 try:
1131 try:
1132 text = file(filename).read()
1132 text = file(filename).read()
1133 except IOError:
1133 except IOError:
1134 raise util.Abort(_("unable to read %s") % patch)
1134 raise util.Abort(_("unable to read %s") % patch)
1135 if not patch:
1135 if not patch:
1136 patch = os.path.split(filename)[1]
1136 patch = os.path.split(filename)[1]
1137 if not force and os.path.exists(os.path.join(self.path, patch)):
1137 if not force and os.path.exists(os.path.join(self.path, patch)):
1138 raise util.Abort(_('patch "%s" already exists') % patch)
1138 raise util.Abort(_('patch "%s" already exists') % patch)
1139 patchf = self.opener(patch, "w")
1139 patchf = self.opener(patch, "w")
1140 patchf.write(text)
1140 patchf.write(text)
1141 if patch in self.series:
1141 if patch in self.series:
1142 raise util.Abort(_('patch %s is already in the series file')
1142 raise util.Abort(_('patch %s is already in the series file')
1143 % patch)
1143 % patch)
1144 index = self.full_series_end() + i
1144 index = self.full_series_end() + i
1145 self.full_series[index:index] = [patch]
1145 self.full_series[index:index] = [patch]
1146 self.parse_series()
1146 self.parse_series()
1147 self.ui.warn("adding %s to series file\n" % patch)
1147 self.ui.warn("adding %s to series file\n" % patch)
1148 i += 1
1148 i += 1
1149 added.append(patch)
1149 added.append(patch)
1150 patch = None
1150 patch = None
1151 self.series_dirty = 1
1151 self.series_dirty = 1
1152 qrepo = self.qrepo()
1152 qrepo = self.qrepo()
1153 if qrepo:
1153 if qrepo:
1154 qrepo.add(added)
1154 qrepo.add(added)
1155
1155
1156 def delete(ui, repo, patch, **opts):
1156 def delete(ui, repo, patch, **opts):
1157 """remove a patch from the series file
1157 """remove a patch from the series file
1158
1158
1159 The patch must not be applied.
1159 The patch must not be applied.
1160 With -f, deletes the patch file as well as the series entry."""
1160 With -f, deletes the patch file as well as the series entry."""
1161 q = repo.mq
1161 q = repo.mq
1162 q.delete(repo, patch, force=opts.get('force'))
1162 q.delete(repo, patch, force=opts.get('force'))
1163 q.save_dirty()
1163 q.save_dirty()
1164 return 0
1164 return 0
1165
1165
1166 def applied(ui, repo, patch=None, **opts):
1166 def applied(ui, repo, patch=None, **opts):
1167 """print the patches already applied"""
1167 """print the patches already applied"""
1168 repo.mq.qapplied(repo, patch)
1168 repo.mq.qapplied(repo, patch)
1169 return 0
1169 return 0
1170
1170
1171 def unapplied(ui, repo, patch=None, **opts):
1171 def unapplied(ui, repo, patch=None, **opts):
1172 """print the patches not yet applied"""
1172 """print the patches not yet applied"""
1173 for i, p in repo.mq.unapplied(repo, patch):
1173 for i, p in repo.mq.unapplied(repo, patch):
1174 if ui.verbose:
1174 if ui.verbose:
1175 ui.write("%d " % i)
1175 ui.write("%d " % i)
1176 ui.write("%s\n" % p)
1176 ui.write("%s\n" % p)
1177
1177
1178 def qimport(ui, repo, *filename, **opts):
1178 def qimport(ui, repo, *filename, **opts):
1179 """import a patch"""
1179 """import a patch"""
1180 q = repo.mq
1180 q = repo.mq
1181 q.qimport(repo, filename, patch=opts['name'],
1181 q.qimport(repo, filename, patch=opts['name'],
1182 existing=opts['existing'], force=opts['force'])
1182 existing=opts['existing'], force=opts['force'])
1183 q.save_dirty()
1183 q.save_dirty()
1184 return 0
1184 return 0
1185
1185
1186 def init(ui, repo, **opts):
1186 def init(ui, repo, **opts):
1187 """init a new queue repository
1187 """init a new queue repository
1188
1188
1189 The queue repository is unversioned by default. If -c is
1189 The queue repository is unversioned by default. If -c is
1190 specified, qinit will create a separate nested repository
1190 specified, qinit will create a separate nested repository
1191 for patches. Use qcommit to commit changes to this queue
1191 for patches. Use qcommit to commit changes to this queue
1192 repository."""
1192 repository."""
1193 q = repo.mq
1193 q = repo.mq
1194 r = q.init(repo, create=opts['create_repo'])
1194 r = q.init(repo, create=opts['create_repo'])
1195 q.save_dirty()
1195 q.save_dirty()
1196 if r:
1196 if r:
1197 fp = r.wopener('.hgignore', 'w')
1197 fp = r.wopener('.hgignore', 'w')
1198 print >> fp, 'syntax: glob'
1198 print >> fp, 'syntax: glob'
1199 print >> fp, 'status'
1199 print >> fp, 'status'
1200 fp.close()
1200 fp.close()
1201 r.wopener('series', 'w').close()
1201 r.wopener('series', 'w').close()
1202 r.add(['.hgignore', 'series'])
1202 r.add(['.hgignore', 'series'])
1203 return 0
1203 return 0
1204
1204
1205 def clone(ui, source, dest=None, **opts):
1205 def clone(ui, source, dest=None, **opts):
1206 '''clone main and patch repository at same time
1206 '''clone main and patch repository at same time
1207
1207
1208 If source is local, destination will have no patches applied. If
1208 If source is local, destination will have no patches applied. If
1209 source is remote, this command can not check if patches are
1209 source is remote, this command can not check if patches are
1210 applied in source, so cannot guarantee that patches are not
1210 applied in source, so cannot guarantee that patches are not
1211 applied in destination. If you clone remote repository, be sure
1211 applied in destination. If you clone remote repository, be sure
1212 before that it has no patches applied.
1212 before that it has no patches applied.
1213
1213
1214 Source patch repository is looked for in <src>/.hg/patches by
1214 Source patch repository is looked for in <src>/.hg/patches by
1215 default. Use -p <url> to change.
1215 default. Use -p <url> to change.
1216 '''
1216 '''
1217 commands.setremoteconfig(ui, opts)
1217 commands.setremoteconfig(ui, opts)
1218 if dest is None:
1218 if dest is None:
1219 dest = hg.defaultdest(source)
1219 dest = hg.defaultdest(source)
1220 sr = hg.repository(ui, ui.expandpath(source))
1220 sr = hg.repository(ui, ui.expandpath(source))
1221 qbase, destrev = None, None
1221 qbase, destrev = None, None
1222 if sr.local():
1222 if sr.local():
1223 reposetup(ui, sr)
1223 reposetup(ui, sr)
1224 if sr.mq.applied:
1224 if sr.mq.applied:
1225 qbase = revlog.bin(sr.mq.applied[0].rev)
1225 qbase = revlog.bin(sr.mq.applied[0].rev)
1226 if not hg.islocal(dest):
1226 if not hg.islocal(dest):
1227 destrev = sr.parents(qbase)[0]
1227 destrev = sr.parents(qbase)[0]
1228 ui.note(_('cloning main repo\n'))
1228 ui.note(_('cloning main repo\n'))
1229 sr, dr = hg.clone(ui, sr, dest,
1229 sr, dr = hg.clone(ui, sr, dest,
1230 pull=opts['pull'],
1230 pull=opts['pull'],
1231 rev=destrev,
1231 rev=destrev,
1232 update=False,
1232 update=False,
1233 stream=opts['uncompressed'])
1233 stream=opts['uncompressed'])
1234 ui.note(_('cloning patch repo\n'))
1234 ui.note(_('cloning patch repo\n'))
1235 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1235 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1236 dr.url() + '/.hg/patches',
1236 dr.url() + '/.hg/patches',
1237 pull=opts['pull'],
1237 pull=opts['pull'],
1238 update=not opts['noupdate'],
1238 update=not opts['noupdate'],
1239 stream=opts['uncompressed'])
1239 stream=opts['uncompressed'])
1240 if dr.local():
1240 if dr.local():
1241 if qbase:
1241 if qbase:
1242 ui.note(_('stripping applied patches from destination repo\n'))
1242 ui.note(_('stripping applied patches from destination repo\n'))
1243 reposetup(ui, dr)
1243 reposetup(ui, dr)
1244 dr.mq.strip(dr, qbase, update=False, backup=None)
1244 dr.mq.strip(dr, qbase, update=False, backup=None)
1245 if not opts['noupdate']:
1245 if not opts['noupdate']:
1246 ui.note(_('updating destination repo\n'))
1246 ui.note(_('updating destination repo\n'))
1247 dr.update(dr.changelog.tip())
1247 dr.update(dr.changelog.tip())
1248
1248
1249 def commit(ui, repo, *pats, **opts):
1249 def commit(ui, repo, *pats, **opts):
1250 """commit changes in the queue repository"""
1250 """commit changes in the queue repository"""
1251 q = repo.mq
1251 q = repo.mq
1252 r = q.qrepo()
1252 r = q.qrepo()
1253 if not r: raise util.Abort('no queue repository')
1253 if not r: raise util.Abort('no queue repository')
1254 commands.commit(r.ui, r, *pats, **opts)
1254 commands.commit(r.ui, r, *pats, **opts)
1255
1255
1256 def series(ui, repo, **opts):
1256 def series(ui, repo, **opts):
1257 """print the entire series file"""
1257 """print the entire series file"""
1258 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1258 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1259 return 0
1259 return 0
1260
1260
1261 def top(ui, repo, **opts):
1261 def top(ui, repo, **opts):
1262 """print the name of the current patch"""
1262 """print the name of the current patch"""
1263 repo.mq.top(repo)
1263 repo.mq.top(repo)
1264 return 0
1264 return 0
1265
1265
1266 def next(ui, repo, **opts):
1266 def next(ui, repo, **opts):
1267 """print the name of the next patch"""
1267 """print the name of the next patch"""
1268 repo.mq.next(repo)
1268 repo.mq.next(repo)
1269 return 0
1269 return 0
1270
1270
1271 def prev(ui, repo, **opts):
1271 def prev(ui, repo, **opts):
1272 """print the name of the previous patch"""
1272 """print the name of the previous patch"""
1273 repo.mq.prev(repo)
1273 repo.mq.prev(repo)
1274 return 0
1274 return 0
1275
1275
1276 def new(ui, repo, patch, **opts):
1276 def new(ui, repo, patch, **opts):
1277 """create a new patch
1277 """create a new patch
1278
1278
1279 qnew creates a new patch on top of the currently-applied patch
1279 qnew creates a new patch on top of the currently-applied patch
1280 (if any). It will refuse to run if there are any outstanding
1280 (if any). It will refuse to run if there are any outstanding
1281 changes unless -f is specified, in which case the patch will
1281 changes unless -f is specified, in which case the patch will
1282 be initialised with them.
1282 be initialised with them.
1283
1283
1284 -m or -l set the patch header as well as the commit message.
1284 -m or -l set the patch header as well as the commit message.
1285 If neither is specified, the patch header is empty and the
1285 If neither is specified, the patch header is empty and the
1286 commit message is 'New patch: PATCH'"""
1286 commit message is 'New patch: PATCH'"""
1287 q = repo.mq
1287 q = repo.mq
1288 message=commands.logmessage(**opts)
1288 message = commands.logmessage(**opts)
1289 q.new(repo, patch, msg=message, force=opts['force'])
1289 q.new(repo, patch, msg=message, force=opts['force'])
1290 q.save_dirty()
1290 q.save_dirty()
1291 return 0
1291 return 0
1292
1292
1293 def refresh(ui, repo, **opts):
1293 def refresh(ui, repo, **opts):
1294 """update the current patch"""
1294 """update the current patch"""
1295 q = repo.mq
1295 q = repo.mq
1296 message=commands.logmessage(**opts)
1296 message = commands.logmessage(**opts)
1297 if opts['edit']:
1297 if opts['edit']:
1298 if message:
1298 if message:
1299 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1299 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1300 patch = q.applied[-1].name
1300 patch = q.applied[-1].name
1301 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1301 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1302 message = ui.edit('\n'.join(message), user or ui.username())
1302 message = ui.edit('\n'.join(message), user or ui.username())
1303 q.refresh(repo, msg=message, short=opts['short'])
1303 q.refresh(repo, msg=message, short=opts['short'])
1304 q.save_dirty()
1304 q.save_dirty()
1305 return 0
1305 return 0
1306
1306
1307 def diff(ui, repo, *files, **opts):
1307 def diff(ui, repo, *files, **opts):
1308 """diff of the current patch"""
1308 """diff of the current patch"""
1309 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1309 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1310 repo.mq.diff(repo, list(files))
1310 repo.mq.diff(repo, list(files))
1311 return 0
1311 return 0
1312
1312
1313 def fold(ui, repo, *files, **opts):
1313 def fold(ui, repo, *files, **opts):
1314 """fold the named patches into the current patch
1314 """fold the named patches into the current patch
1315
1315
1316 Patches must not yet be applied. Each patch will be successively
1316 Patches must not yet be applied. Each patch will be successively
1317 applied to the current patch in the order given. If all the
1317 applied to the current patch in the order given. If all the
1318 patches apply successfully, the current patch will be refreshed
1318 patches apply successfully, the current patch will be refreshed
1319 with the new cumulative patch, and the folded patches will
1319 with the new cumulative patch, and the folded patches will
1320 be deleted. With -f/--force, the folded patch files will
1320 be deleted. With -f/--force, the folded patch files will
1321 be removed afterwards.
1321 be removed afterwards.
1322
1322
1323 The header for each folded patch will be concatenated with
1323 The header for each folded patch will be concatenated with
1324 the current patch header, separated by a line of '* * *'."""
1324 the current patch header, separated by a line of '* * *'."""
1325
1325
1326 q = repo.mq
1326 q = repo.mq
1327
1327
1328 if not files:
1328 if not files:
1329 raise util.Abort(_('qfold requires at least one patch name'))
1329 raise util.Abort(_('qfold requires at least one patch name'))
1330 if not q.check_toppatch(repo):
1330 if not q.check_toppatch(repo):
1331 raise util.Abort(_('No patches applied\n'))
1331 raise util.Abort(_('No patches applied\n'))
1332
1332
1333 message=commands.logmessage(**opts)
1333 message = commands.logmessage(**opts)
1334 if opts['edit']:
1334 if opts['edit']:
1335 if message:
1335 if message:
1336 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1336 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1337
1337
1338 parent = q.lookup('qtip')
1338 parent = q.lookup('qtip')
1339 patches = []
1339 patches = []
1340 messages = []
1340 messages = []
1341 for f in files:
1341 for f in files:
1342 patch = q.lookup(f)
1342 patch = q.lookup(f)
1343 if patch in patches or patch == parent:
1343 if patch in patches or patch == parent:
1344 self.ui.warn(_('Skipping already folded patch %s') % patch)
1344 self.ui.warn(_('Skipping already folded patch %s') % patch)
1345 if q.isapplied(patch):
1345 if q.isapplied(patch):
1346 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1346 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1347 patches.append(patch)
1347 patches.append(patch)
1348
1348
1349 for patch in patches:
1349 for patch in patches:
1350 if not message:
1350 if not message:
1351 messages.append(q.readheaders(patch)[0])
1351 messages.append(q.readheaders(patch)[0])
1352 pf = os.path.join(q.path, patch)
1352 pf = os.path.join(q.path, patch)
1353 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1353 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1354 if not patchsuccess:
1354 if not patchsuccess:
1355 raise util.Abort(_('Error folding patch %s') % patch)
1355 raise util.Abort(_('Error folding patch %s') % patch)
1356
1356
1357 if not message:
1357 if not message:
1358 message, comments, user = q.readheaders(parent)[0:3]
1358 message, comments, user = q.readheaders(parent)[0:3]
1359 for msg in messages:
1359 for msg in messages:
1360 message.append('* * *')
1360 message.append('* * *')
1361 message.extend(msg)
1361 message.extend(msg)
1362 message = '\n'.join(message)
1362 message = '\n'.join(message)
1363
1363
1364 if opts['edit']:
1364 if opts['edit']:
1365 message = ui.edit(message, user or ui.username())
1365 message = ui.edit(message, user or ui.username())
1366
1366
1367 q.refresh(repo, msg=message)
1367 q.refresh(repo, msg=message)
1368
1368
1369 for patch in patches:
1369 for patch in patches:
1370 q.delete(repo, patch, force=opts['force'])
1370 q.delete(repo, patch, force=opts['force'])
1371
1371
1372 q.save_dirty()
1372 q.save_dirty()
1373
1373
1374 def header(ui, repo, patch=None):
1374 def header(ui, repo, patch=None):
1375 """Print the header of the topmost or specified patch"""
1375 """Print the header of the topmost or specified patch"""
1376 q = repo.mq
1376 q = repo.mq
1377
1377
1378 if patch:
1378 if patch:
1379 patch = q.lookup(patch)
1379 patch = q.lookup(patch)
1380 else:
1380 else:
1381 if not q.applied:
1381 if not q.applied:
1382 ui.write('No patches applied\n')
1382 ui.write('No patches applied\n')
1383 return
1383 return
1384 patch = q.lookup('qtip')
1384 patch = q.lookup('qtip')
1385 message = repo.mq.readheaders(patch)[0]
1385 message = repo.mq.readheaders(patch)[0]
1386
1386
1387 ui.write('\n'.join(message) + '\n')
1387 ui.write('\n'.join(message) + '\n')
1388
1388
1389 def lastsavename(path):
1389 def lastsavename(path):
1390 (directory, base) = os.path.split(path)
1390 (directory, base) = os.path.split(path)
1391 names = os.listdir(directory)
1391 names = os.listdir(directory)
1392 namere = re.compile("%s.([0-9]+)" % base)
1392 namere = re.compile("%s.([0-9]+)" % base)
1393 maxindex = None
1393 maxindex = None
1394 maxname = None
1394 maxname = None
1395 for f in names:
1395 for f in names:
1396 m = namere.match(f)
1396 m = namere.match(f)
1397 if m:
1397 if m:
1398 index = int(m.group(1))
1398 index = int(m.group(1))
1399 if maxindex == None or index > maxindex:
1399 if maxindex == None or index > maxindex:
1400 maxindex = index
1400 maxindex = index
1401 maxname = f
1401 maxname = f
1402 if maxname:
1402 if maxname:
1403 return (os.path.join(directory, maxname), maxindex)
1403 return (os.path.join(directory, maxname), maxindex)
1404 return (None, None)
1404 return (None, None)
1405
1405
1406 def savename(path):
1406 def savename(path):
1407 (last, index) = lastsavename(path)
1407 (last, index) = lastsavename(path)
1408 if last is None:
1408 if last is None:
1409 index = 0
1409 index = 0
1410 newpath = path + ".%d" % (index + 1)
1410 newpath = path + ".%d" % (index + 1)
1411 return newpath
1411 return newpath
1412
1412
1413 def push(ui, repo, patch=None, **opts):
1413 def push(ui, repo, patch=None, **opts):
1414 """push the next patch onto the stack"""
1414 """push the next patch onto the stack"""
1415 q = repo.mq
1415 q = repo.mq
1416 mergeq = None
1416 mergeq = None
1417
1417
1418 if opts['all']:
1418 if opts['all']:
1419 patch = q.series[-1]
1419 patch = q.series[-1]
1420 if opts['merge']:
1420 if opts['merge']:
1421 if opts['name']:
1421 if opts['name']:
1422 newpath = opts['name']
1422 newpath = opts['name']
1423 else:
1423 else:
1424 newpath, i = lastsavename(q.path)
1424 newpath, i = lastsavename(q.path)
1425 if not newpath:
1425 if not newpath:
1426 ui.warn("no saved queues found, please use -n\n")
1426 ui.warn("no saved queues found, please use -n\n")
1427 return 1
1427 return 1
1428 mergeq = queue(ui, repo.join(""), newpath)
1428 mergeq = queue(ui, repo.join(""), newpath)
1429 ui.warn("merging with queue at: %s\n" % mergeq.path)
1429 ui.warn("merging with queue at: %s\n" % mergeq.path)
1430 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1430 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1431 mergeq=mergeq)
1431 mergeq=mergeq)
1432 q.save_dirty()
1432 q.save_dirty()
1433 return ret
1433 return ret
1434
1434
1435 def pop(ui, repo, patch=None, **opts):
1435 def pop(ui, repo, patch=None, **opts):
1436 """pop the current patch off the stack"""
1436 """pop the current patch off the stack"""
1437 localupdate = True
1437 localupdate = True
1438 if opts['name']:
1438 if opts['name']:
1439 q = queue(ui, repo.join(""), repo.join(opts['name']))
1439 q = queue(ui, repo.join(""), repo.join(opts['name']))
1440 ui.warn('using patch queue: %s\n' % q.path)
1440 ui.warn('using patch queue: %s\n' % q.path)
1441 localupdate = False
1441 localupdate = False
1442 else:
1442 else:
1443 q = repo.mq
1443 q = repo.mq
1444 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1444 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1445 q.save_dirty()
1445 q.save_dirty()
1446 return 0
1446 return 0
1447
1447
1448 def rename(ui, repo, patch, name=None, **opts):
1448 def rename(ui, repo, patch, name=None, **opts):
1449 """rename a patch
1449 """rename a patch
1450
1450
1451 With one argument, renames the current patch to PATCH1.
1451 With one argument, renames the current patch to PATCH1.
1452 With two arguments, renames PATCH1 to PATCH2."""
1452 With two arguments, renames PATCH1 to PATCH2."""
1453
1453
1454 q = repo.mq
1454 q = repo.mq
1455
1455
1456 if not name:
1456 if not name:
1457 name = patch
1457 name = patch
1458 patch = None
1458 patch = None
1459
1459
1460 if name in q.series:
1460 if name in q.series:
1461 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1461 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1462
1462
1463 absdest = os.path.join(q.path, name)
1463 absdest = os.path.join(q.path, name)
1464 if os.path.exists(absdest):
1464 if os.path.exists(absdest):
1465 raise util.Abort(_('%s already exists') % absdest)
1465 raise util.Abort(_('%s already exists') % absdest)
1466
1466
1467 if patch:
1467 if patch:
1468 patch = q.lookup(patch)
1468 patch = q.lookup(patch)
1469 else:
1469 else:
1470 if not q.applied:
1470 if not q.applied:
1471 ui.write(_('No patches applied\n'))
1471 ui.write(_('No patches applied\n'))
1472 return
1472 return
1473 patch = q.lookup('qtip')
1473 patch = q.lookup('qtip')
1474
1474
1475 if ui.verbose:
1475 if ui.verbose:
1476 ui.write('Renaming %s to %s\n' % (patch, name))
1476 ui.write('Renaming %s to %s\n' % (patch, name))
1477 i = q.find_series(patch)
1477 i = q.find_series(patch)
1478 q.full_series[i] = name
1478 q.full_series[i] = name
1479 q.parse_series()
1479 q.parse_series()
1480 q.series_dirty = 1
1480 q.series_dirty = 1
1481
1481
1482 info = q.isapplied(patch)
1482 info = q.isapplied(patch)
1483 if info:
1483 if info:
1484 q.applied[info[0]] = StatusEntry(info[1], name)
1484 q.applied[info[0]] = StatusEntry(info[1], name)
1485 q.applied_dirty = 1
1485 q.applied_dirty = 1
1486
1486
1487 util.rename(os.path.join(q.path, patch), absdest)
1487 util.rename(os.path.join(q.path, patch), absdest)
1488 r = q.qrepo()
1488 r = q.qrepo()
1489 if r:
1489 if r:
1490 wlock = r.wlock()
1490 wlock = r.wlock()
1491 if r.dirstate.state(name) == 'r':
1491 if r.dirstate.state(name) == 'r':
1492 r.undelete([name], wlock)
1492 r.undelete([name], wlock)
1493 r.copy(patch, name, wlock)
1493 r.copy(patch, name, wlock)
1494 r.remove([patch], False, wlock)
1494 r.remove([patch], False, wlock)
1495
1495
1496 q.save_dirty()
1496 q.save_dirty()
1497
1497
1498 def restore(ui, repo, rev, **opts):
1498 def restore(ui, repo, rev, **opts):
1499 """restore the queue state saved by a rev"""
1499 """restore the queue state saved by a rev"""
1500 rev = repo.lookup(rev)
1500 rev = repo.lookup(rev)
1501 q = repo.mq
1501 q = repo.mq
1502 q.restore(repo, rev, delete=opts['delete'],
1502 q.restore(repo, rev, delete=opts['delete'],
1503 qupdate=opts['update'])
1503 qupdate=opts['update'])
1504 q.save_dirty()
1504 q.save_dirty()
1505 return 0
1505 return 0
1506
1506
1507 def save(ui, repo, **opts):
1507 def save(ui, repo, **opts):
1508 """save current queue state"""
1508 """save current queue state"""
1509 q = repo.mq
1509 q = repo.mq
1510 message=commands.logmessage(**opts)
1510 message = commands.logmessage(**opts)
1511 ret = q.save(repo, msg=message)
1511 ret = q.save(repo, msg=message)
1512 if ret:
1512 if ret:
1513 return ret
1513 return ret
1514 q.save_dirty()
1514 q.save_dirty()
1515 if opts['copy']:
1515 if opts['copy']:
1516 path = q.path
1516 path = q.path
1517 if opts['name']:
1517 if opts['name']:
1518 newpath = os.path.join(q.basepath, opts['name'])
1518 newpath = os.path.join(q.basepath, opts['name'])
1519 if os.path.exists(newpath):
1519 if os.path.exists(newpath):
1520 if not os.path.isdir(newpath):
1520 if not os.path.isdir(newpath):
1521 raise util.Abort(_('destination %s exists and is not '
1521 raise util.Abort(_('destination %s exists and is not '
1522 'a directory') % newpath)
1522 'a directory') % newpath)
1523 if not opts['force']:
1523 if not opts['force']:
1524 raise util.Abort(_('destination %s exists, '
1524 raise util.Abort(_('destination %s exists, '
1525 'use -f to force') % newpath)
1525 'use -f to force') % newpath)
1526 else:
1526 else:
1527 newpath = savename(path)
1527 newpath = savename(path)
1528 ui.warn("copy %s to %s\n" % (path, newpath))
1528 ui.warn("copy %s to %s\n" % (path, newpath))
1529 util.copyfiles(path, newpath)
1529 util.copyfiles(path, newpath)
1530 if opts['empty']:
1530 if opts['empty']:
1531 try:
1531 try:
1532 os.unlink(os.path.join(q.path, q.status_path))
1532 os.unlink(os.path.join(q.path, q.status_path))
1533 except:
1533 except:
1534 pass
1534 pass
1535 return 0
1535 return 0
1536
1536
1537 def strip(ui, repo, rev, **opts):
1537 def strip(ui, repo, rev, **opts):
1538 """strip a revision and all later revs on the same branch"""
1538 """strip a revision and all later revs on the same branch"""
1539 rev = repo.lookup(rev)
1539 rev = repo.lookup(rev)
1540 backup = 'all'
1540 backup = 'all'
1541 if opts['backup']:
1541 if opts['backup']:
1542 backup = 'strip'
1542 backup = 'strip'
1543 elif opts['nobackup']:
1543 elif opts['nobackup']:
1544 backup = 'none'
1544 backup = 'none'
1545 repo.mq.strip(repo, rev, backup=backup)
1545 repo.mq.strip(repo, rev, backup=backup)
1546 return 0
1546 return 0
1547
1547
1548 def version(ui, q=None):
1548 def version(ui, q=None):
1549 """print the version number of the mq extension"""
1549 """print the version number of the mq extension"""
1550 ui.write("mq version %s\n" % versionstr)
1550 ui.write("mq version %s\n" % versionstr)
1551 return 0
1551 return 0
1552
1552
1553 def reposetup(ui, repo):
1553 def reposetup(ui, repo):
1554 class MqRepo(repo.__class__):
1554 class MqRepo(repo.__class__):
1555 def tags(self):
1555 def tags(self):
1556 if self.tagscache:
1556 if self.tagscache:
1557 return self.tagscache
1557 return self.tagscache
1558
1558
1559 tagscache = super(MqRepo, self).tags()
1559 tagscache = super(MqRepo, self).tags()
1560
1560
1561 q = self.mq
1561 q = self.mq
1562 if not q.applied:
1562 if not q.applied:
1563 return tagscache
1563 return tagscache
1564
1564
1565 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1565 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1566 mqtags.append((mqtags[-1][0], 'qtip'))
1566 mqtags.append((mqtags[-1][0], 'qtip'))
1567 mqtags.append((mqtags[0][0], 'qbase'))
1567 mqtags.append((mqtags[0][0], 'qbase'))
1568 for patch in mqtags:
1568 for patch in mqtags:
1569 if patch[1] in tagscache:
1569 if patch[1] in tagscache:
1570 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1570 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1571 else:
1571 else:
1572 tagscache[patch[1]] = revlog.bin(patch[0])
1572 tagscache[patch[1]] = revlog.bin(patch[0])
1573
1573
1574 return tagscache
1574 return tagscache
1575
1575
1576 repo.__class__ = MqRepo
1576 repo.__class__ = MqRepo
1577 repo.mq = queue(ui, repo.join(""))
1577 repo.mq = queue(ui, repo.join(""))
1578
1578
1579 cmdtable = {
1579 cmdtable = {
1580 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1580 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1581 "qclone": (clone,
1581 "qclone": (clone,
1582 [('', 'pull', None, _('use pull protocol to copy metadata')),
1582 [('', 'pull', None, _('use pull protocol to copy metadata')),
1583 ('U', 'noupdate', None, _('do not update the new working directories')),
1583 ('U', 'noupdate', None, _('do not update the new working directories')),
1584 ('', 'uncompressed', None,
1584 ('', 'uncompressed', None,
1585 _('use uncompressed transfer (fast over LAN)')),
1585 _('use uncompressed transfer (fast over LAN)')),
1586 ('e', 'ssh', '', _('specify ssh command to use')),
1586 ('e', 'ssh', '', _('specify ssh command to use')),
1587 ('p', 'patches', '', _('location of source patch repo')),
1587 ('p', 'patches', '', _('location of source patch repo')),
1588 ('', 'remotecmd', '',
1588 ('', 'remotecmd', '',
1589 _('specify hg command to run on the remote side'))],
1589 _('specify hg command to run on the remote side'))],
1590 'hg qclone [OPTION]... SOURCE [DEST]'),
1590 'hg qclone [OPTION]... SOURCE [DEST]'),
1591 "qcommit|qci":
1591 "qcommit|qci":
1592 (commit,
1592 (commit,
1593 commands.table["^commit|ci"][1],
1593 commands.table["^commit|ci"][1],
1594 'hg qcommit [OPTION]... [FILE]...'),
1594 'hg qcommit [OPTION]... [FILE]...'),
1595 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1595 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1596 "qdelete":
1596 "qdelete":
1597 (delete,
1597 (delete,
1598 [('f', 'force', None, _('delete patch file'))],
1598 [('f', 'force', None, _('delete patch file'))],
1599 'hg qdelete [-f] PATCH'),
1599 'hg qdelete [-f] PATCH'),
1600 'qfold':
1600 'qfold':
1601 (fold,
1601 (fold,
1602 [('e', 'edit', None, _('edit patch header')),
1602 [('e', 'edit', None, _('edit patch header')),
1603 ('f', 'force', None, _('delete folded patch files')),
1603 ('f', 'force', None, _('delete folded patch files')),
1604 ('m', 'message', '', _('set patch header to <text>')),
1604 ('m', 'message', '', _('set patch header to <text>')),
1605 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1605 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1606 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1606 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1607 'qheader': (header, [],
1607 'qheader': (header, [],
1608 _('hg qheader [PATCH]')),
1608 _('hg qheader [PATCH]')),
1609 "^qimport":
1609 "^qimport":
1610 (qimport,
1610 (qimport,
1611 [('e', 'existing', None, 'import file in patch dir'),
1611 [('e', 'existing', None, 'import file in patch dir'),
1612 ('n', 'name', '', 'patch file name'),
1612 ('n', 'name', '', 'patch file name'),
1613 ('f', 'force', None, 'overwrite existing files')],
1613 ('f', 'force', None, 'overwrite existing files')],
1614 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1614 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1615 "^qinit":
1615 "^qinit":
1616 (init,
1616 (init,
1617 [('c', 'create-repo', None, 'create queue repository')],
1617 [('c', 'create-repo', None, 'create queue repository')],
1618 'hg qinit [-c]'),
1618 'hg qinit [-c]'),
1619 "qnew":
1619 "qnew":
1620 (new,
1620 (new,
1621 [('m', 'message', '', _('use <text> as commit message')),
1621 [('m', 'message', '', _('use <text> as commit message')),
1622 ('l', 'logfile', '', _('read the commit message from <file>')),
1622 ('l', 'logfile', '', _('read the commit message from <file>')),
1623 ('f', 'force', None, _('import uncommitted changes into patch'))],
1623 ('f', 'force', None, _('import uncommitted changes into patch'))],
1624 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1624 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1625 "qnext": (next, [], 'hg qnext'),
1625 "qnext": (next, [], 'hg qnext'),
1626 "qprev": (prev, [], 'hg qprev'),
1626 "qprev": (prev, [], 'hg qprev'),
1627 "^qpop":
1627 "^qpop":
1628 (pop,
1628 (pop,
1629 [('a', 'all', None, 'pop all patches'),
1629 [('a', 'all', None, 'pop all patches'),
1630 ('n', 'name', '', 'queue name to pop'),
1630 ('n', 'name', '', 'queue name to pop'),
1631 ('f', 'force', None, 'forget any local changes')],
1631 ('f', 'force', None, 'forget any local changes')],
1632 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1632 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1633 "^qpush":
1633 "^qpush":
1634 (push,
1634 (push,
1635 [('f', 'force', None, 'apply if the patch has rejects'),
1635 [('f', 'force', None, 'apply if the patch has rejects'),
1636 ('l', 'list', None, 'list patch name in commit text'),
1636 ('l', 'list', None, 'list patch name in commit text'),
1637 ('a', 'all', None, 'apply all patches'),
1637 ('a', 'all', None, 'apply all patches'),
1638 ('m', 'merge', None, 'merge from another queue'),
1638 ('m', 'merge', None, 'merge from another queue'),
1639 ('n', 'name', '', 'merge queue name')],
1639 ('n', 'name', '', 'merge queue name')],
1640 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1640 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1641 "^qrefresh":
1641 "^qrefresh":
1642 (refresh,
1642 (refresh,
1643 [('e', 'edit', None, _('edit commit message')),
1643 [('e', 'edit', None, _('edit commit message')),
1644 ('m', 'message', '', _('change commit message with <text>')),
1644 ('m', 'message', '', _('change commit message with <text>')),
1645 ('l', 'logfile', '', _('change commit message with <file> content')),
1645 ('l', 'logfile', '', _('change commit message with <file> content')),
1646 ('s', 'short', None, 'short refresh')],
1646 ('s', 'short', None, 'short refresh')],
1647 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1647 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1648 'qrename|qmv':
1648 'qrename|qmv':
1649 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1649 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1650 "qrestore":
1650 "qrestore":
1651 (restore,
1651 (restore,
1652 [('d', 'delete', None, 'delete save entry'),
1652 [('d', 'delete', None, 'delete save entry'),
1653 ('u', 'update', None, 'update queue working dir')],
1653 ('u', 'update', None, 'update queue working dir')],
1654 'hg qrestore [-d] [-u] REV'),
1654 'hg qrestore [-d] [-u] REV'),
1655 "qsave":
1655 "qsave":
1656 (save,
1656 (save,
1657 [('m', 'message', '', _('use <text> as commit message')),
1657 [('m', 'message', '', _('use <text> as commit message')),
1658 ('l', 'logfile', '', _('read the commit message from <file>')),
1658 ('l', 'logfile', '', _('read the commit message from <file>')),
1659 ('c', 'copy', None, 'copy patch directory'),
1659 ('c', 'copy', None, 'copy patch directory'),
1660 ('n', 'name', '', 'copy directory name'),
1660 ('n', 'name', '', 'copy directory name'),
1661 ('e', 'empty', None, 'clear queue status file'),
1661 ('e', 'empty', None, 'clear queue status file'),
1662 ('f', 'force', None, 'force copy')],
1662 ('f', 'force', None, 'force copy')],
1663 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1663 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1664 "qseries":
1664 "qseries":
1665 (series,
1665 (series,
1666 [('m', 'missing', None, 'print patches not in series'),
1666 [('m', 'missing', None, 'print patches not in series'),
1667 ('s', 'summary', None, _('print first line of patch header'))],
1667 ('s', 'summary', None, _('print first line of patch header'))],
1668 'hg qseries [-m]'),
1668 'hg qseries [-m]'),
1669 "^strip":
1669 "^strip":
1670 (strip,
1670 (strip,
1671 [('f', 'force', None, 'force multi-head removal'),
1671 [('f', 'force', None, 'force multi-head removal'),
1672 ('b', 'backup', None, 'bundle unrelated changesets'),
1672 ('b', 'backup', None, 'bundle unrelated changesets'),
1673 ('n', 'nobackup', None, 'no backups')],
1673 ('n', 'nobackup', None, 'no backups')],
1674 'hg strip [-f] [-b] [-n] REV'),
1674 'hg strip [-f] [-b] [-n] REV'),
1675 "qtop": (top, [], 'hg qtop'),
1675 "qtop": (top, [], 'hg qtop'),
1676 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1676 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1677 "qversion": (version, [], 'hg qversion')
1677 "qversion": (version, [], 'hg qversion')
1678 }
1678 }
1679
1679
General Comments 0
You need to be logged in to leave comments. Login now