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