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