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