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