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