##// END OF EJS Templates
mq: fix qnew and qimport to deal with series file comments...
Chris Mason -
r2698:c1123e83 default
parent child Browse files
Show More
@@ -1,1452 +1,1461 b''
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.full_series_end()
422 if msg:
422 if msg:
423 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
423 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
424 wlock=wlock)
424 wlock=wlock)
425 else:
425 else:
426 n = repo.commit(commitfiles,
426 n = repo.commit(commitfiles,
427 "New patch: %s" % patch, force=True, wlock=wlock)
427 "New patch: %s" % patch, force=True, wlock=wlock)
428 if n == None:
428 if n == None:
429 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, all=False,
711 def pop(self, repo, patch=None, force=False, update=True, all=False,
712 wlock=None):
712 wlock=None):
713 def getfile(f, rev):
713 def getfile(f, rev):
714 t = repo.file(f).read(rev)
714 t = repo.file(f).read(rev)
715 try:
715 try:
716 repo.wfile(f, "w").write(t)
716 repo.wfile(f, "w").write(t)
717 except IOError:
717 except IOError:
718 try:
718 try:
719 os.makedirs(os.path.dirname(repo.wjoin(f)))
719 os.makedirs(os.path.dirname(repo.wjoin(f)))
720 except OSError, err:
720 except OSError, err:
721 if err.errno != errno.EEXIST: raise
721 if err.errno != errno.EEXIST: raise
722 repo.wfile(f, "w").write(t)
722 repo.wfile(f, "w").write(t)
723
723
724 if not wlock:
724 if not wlock:
725 wlock = repo.wlock()
725 wlock = repo.wlock()
726 if patch:
726 if patch:
727 # index, rev, patch
727 # index, rev, patch
728 info = self.isapplied(patch)
728 info = self.isapplied(patch)
729 if not info:
729 if not info:
730 patch = self.lookup(patch)
730 patch = self.lookup(patch)
731 info = self.isapplied(patch)
731 info = self.isapplied(patch)
732 if not info:
732 if not info:
733 self.ui.warn("patch %s is not applied\n" % patch)
733 self.ui.warn("patch %s is not applied\n" % patch)
734 sys.exit(1)
734 sys.exit(1)
735 if len(self.applied) == 0:
735 if len(self.applied) == 0:
736 self.ui.warn("No patches applied\n")
736 self.ui.warn("No patches applied\n")
737 sys.exit(1)
737 sys.exit(1)
738
738
739 if not update:
739 if not update:
740 parents = repo.dirstate.parents()
740 parents = repo.dirstate.parents()
741 rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
741 rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
742 for p in parents:
742 for p in parents:
743 if p in rr:
743 if p in rr:
744 self.ui.warn("qpop: forcing dirstate update\n")
744 self.ui.warn("qpop: forcing dirstate update\n")
745 update = True
745 update = True
746
746
747 if not force and update:
747 if not force and update:
748 self.check_localchanges(repo)
748 self.check_localchanges(repo)
749
749
750 self.applied_dirty = 1;
750 self.applied_dirty = 1;
751 end = len(self.applied)
751 end = len(self.applied)
752 if not patch:
752 if not patch:
753 if all:
753 if all:
754 popi = 0
754 popi = 0
755 else:
755 else:
756 popi = len(self.applied) - 1
756 popi = len(self.applied) - 1
757 else:
757 else:
758 popi = info[0] + 1
758 popi = info[0] + 1
759 if popi >= end:
759 if popi >= end:
760 self.ui.warn("qpop: %s is already at the top\n" % patch)
760 self.ui.warn("qpop: %s is already at the top\n" % patch)
761 return
761 return
762 info = [ popi ] + self.applied[popi].split(':')
762 info = [ popi ] + self.applied[popi].split(':')
763
763
764 start = info[0]
764 start = info[0]
765 rev = revlog.bin(info[1])
765 rev = revlog.bin(info[1])
766
766
767 # we know there are no local changes, so we can make a simplified
767 # we know there are no local changes, so we can make a simplified
768 # form of hg.update.
768 # form of hg.update.
769 if update:
769 if update:
770 top = self.check_toppatch(repo)
770 top = self.check_toppatch(repo)
771 qp = self.qparents(repo, rev)
771 qp = self.qparents(repo, rev)
772 changes = repo.changelog.read(qp)
772 changes = repo.changelog.read(qp)
773 mf1 = repo.manifest.readflags(changes[0])
773 mf1 = repo.manifest.readflags(changes[0])
774 mmap = repo.manifest.read(changes[0])
774 mmap = repo.manifest.read(changes[0])
775 (c, a, r, d, u) = repo.changes(qp, top)
775 (c, a, r, d, u) = repo.changes(qp, top)
776 if d:
776 if d:
777 raise util.Abort("deletions found between repo revs")
777 raise util.Abort("deletions found between repo revs")
778 for f in c:
778 for f in c:
779 getfile(f, mmap[f])
779 getfile(f, mmap[f])
780 for f in r:
780 for f in r:
781 getfile(f, mmap[f])
781 getfile(f, mmap[f])
782 util.set_exec(repo.wjoin(f), mf1[f])
782 util.set_exec(repo.wjoin(f), mf1[f])
783 repo.dirstate.update(c + r, 'n')
783 repo.dirstate.update(c + r, 'n')
784 for f in a:
784 for f in a:
785 try: os.unlink(repo.wjoin(f))
785 try: os.unlink(repo.wjoin(f))
786 except: raise
786 except: raise
787 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
787 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
788 except: pass
788 except: pass
789 if a:
789 if a:
790 repo.dirstate.forget(a)
790 repo.dirstate.forget(a)
791 repo.dirstate.setparents(qp, revlog.nullid)
791 repo.dirstate.setparents(qp, revlog.nullid)
792 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
792 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
793 del self.applied[start:end]
793 del self.applied[start:end]
794 if len(self.applied):
794 if len(self.applied):
795 self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
795 self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
796 else:
796 else:
797 self.ui.write("Patch queue now empty\n")
797 self.ui.write("Patch queue now empty\n")
798
798
799 def diff(self, repo, files):
799 def diff(self, repo, files):
800 top = self.check_toppatch(repo)
800 top = self.check_toppatch(repo)
801 if not top:
801 if not top:
802 self.ui.write("No patches applied\n")
802 self.ui.write("No patches applied\n")
803 return
803 return
804 qp = self.qparents(repo, top)
804 qp = self.qparents(repo, top)
805 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
805 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
806
806
807 def refresh(self, repo, msg=None, short=False):
807 def refresh(self, repo, msg=None, short=False):
808 if len(self.applied) == 0:
808 if len(self.applied) == 0:
809 self.ui.write("No patches applied\n")
809 self.ui.write("No patches applied\n")
810 return
810 return
811 wlock = repo.wlock()
811 wlock = repo.wlock()
812 self.check_toppatch(repo)
812 self.check_toppatch(repo)
813 qp = self.qparents(repo)
813 qp = self.qparents(repo)
814 (top, patch) = self.applied[-1].split(':')
814 (top, patch) = self.applied[-1].split(':')
815 top = revlog.bin(top)
815 top = revlog.bin(top)
816 cparents = repo.changelog.parents(top)
816 cparents = repo.changelog.parents(top)
817 patchparent = self.qparents(repo, top)
817 patchparent = self.qparents(repo, top)
818 message, comments, user, date, patchfound = self.readheaders(patch)
818 message, comments, user, date, patchfound = self.readheaders(patch)
819
819
820 patchf = self.opener(patch, "w")
820 patchf = self.opener(patch, "w")
821 if comments:
821 if comments:
822 comments = "\n".join(comments) + '\n\n'
822 comments = "\n".join(comments) + '\n\n'
823 patchf.write(comments)
823 patchf.write(comments)
824
824
825 tip = repo.changelog.tip()
825 tip = repo.changelog.tip()
826 if top == tip:
826 if top == tip:
827 # 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
828 # optimization here. We update the dirstate in place and strip
828 # optimization here. We update the dirstate in place and strip
829 # off the tip commit. Then just commit the current directory
829 # off the tip commit. Then just commit the current directory
830 # tree. We can also send repo.commit the list of files
830 # tree. We can also send repo.commit the list of files
831 # changed to speed up the diff
831 # changed to speed up the diff
832 #
832 #
833 # in short mode, we only diff the files included in the
833 # in short mode, we only diff the files included in the
834 # patch already
834 # patch already
835 #
835 #
836 # this should really read:
836 # this should really read:
837 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
837 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
838 # but we do it backwards to take advantage of manifest/chlog
838 # but we do it backwards to take advantage of manifest/chlog
839 # caching against the next repo.changes call
839 # caching against the next repo.changes call
840 #
840 #
841 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
841 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
842 if short:
842 if short:
843 filelist = cc + aa + dd
843 filelist = cc + aa + dd
844 else:
844 else:
845 filelist = None
845 filelist = None
846 (c, a, r, d, u) = repo.changes(None, None, filelist)
846 (c, a, r, d, u) = repo.changes(None, None, filelist)
847
847
848 # 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
849 # the dirstate parent, but then changed in the local dirstate.
849 # the dirstate parent, but then changed in the local dirstate.
850 # 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
851 for x in c:
851 for x in c:
852 if x not in aa:
852 if x not in aa:
853 cc.append(x)
853 cc.append(x)
854 # 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
855 # were deleted by the patch. In this case, they should only
855 # were deleted by the patch. In this case, they should only
856 # show up in the changed section.
856 # show up in the changed section.
857 for x in a:
857 for x in a:
858 if x in dd:
858 if x in dd:
859 del dd[dd.index(x)]
859 del dd[dd.index(x)]
860 cc.append(x)
860 cc.append(x)
861 else:
861 else:
862 aa.append(x)
862 aa.append(x)
863 # make sure any files deleted in the local dirstate
863 # make sure any files deleted in the local dirstate
864 # are not in the add or change column of the patch
864 # are not in the add or change column of the patch
865 forget = []
865 forget = []
866 for x in d + r:
866 for x in d + r:
867 if x in aa:
867 if x in aa:
868 del aa[aa.index(x)]
868 del aa[aa.index(x)]
869 forget.append(x)
869 forget.append(x)
870 continue
870 continue
871 elif x in cc:
871 elif x in cc:
872 del cc[cc.index(x)]
872 del cc[cc.index(x)]
873 dd.append(x)
873 dd.append(x)
874
874
875 c = list(util.unique(cc))
875 c = list(util.unique(cc))
876 r = list(util.unique(dd))
876 r = list(util.unique(dd))
877 a = list(util.unique(aa))
877 a = list(util.unique(aa))
878 filelist = list(util.unique(c + r + a ))
878 filelist = list(util.unique(c + r + a ))
879 commands.dodiff(patchf, self.ui, repo, patchparent, None,
879 commands.dodiff(patchf, self.ui, repo, patchparent, None,
880 filelist, changes=(c, a, r, [], u))
880 filelist, changes=(c, a, r, [], u))
881 patchf.close()
881 patchf.close()
882
882
883 changes = repo.changelog.read(tip)
883 changes = repo.changelog.read(tip)
884 repo.dirstate.setparents(*cparents)
884 repo.dirstate.setparents(*cparents)
885 repo.dirstate.update(a, 'a')
885 repo.dirstate.update(a, 'a')
886 repo.dirstate.update(r, 'r')
886 repo.dirstate.update(r, 'r')
887 repo.dirstate.update(c, 'n')
887 repo.dirstate.update(c, 'n')
888 repo.dirstate.forget(forget)
888 repo.dirstate.forget(forget)
889
889
890 if not msg:
890 if not msg:
891 if not message:
891 if not message:
892 message = "patch queue: %s\n" % patch
892 message = "patch queue: %s\n" % patch
893 else:
893 else:
894 message = "\n".join(message)
894 message = "\n".join(message)
895 else:
895 else:
896 message = msg
896 message = msg
897
897
898 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
898 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
899 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
899 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
900 self.applied[-1] = revlog.hex(n) + ':' + patch
900 self.applied[-1] = revlog.hex(n) + ':' + patch
901 self.applied_dirty = 1
901 self.applied_dirty = 1
902 else:
902 else:
903 commands.dodiff(patchf, self.ui, repo, patchparent, None)
903 commands.dodiff(patchf, self.ui, repo, patchparent, None)
904 patchf.close()
904 patchf.close()
905 self.pop(repo, force=True, wlock=wlock)
905 self.pop(repo, force=True, wlock=wlock)
906 self.push(repo, force=True, wlock=wlock)
906 self.push(repo, force=True, wlock=wlock)
907
907
908 def init(self, repo, create=False):
908 def init(self, repo, create=False):
909 if os.path.isdir(self.path):
909 if os.path.isdir(self.path):
910 raise util.Abort("patch queue directory already exists")
910 raise util.Abort("patch queue directory already exists")
911 os.mkdir(self.path)
911 os.mkdir(self.path)
912 if create:
912 if create:
913 return self.qrepo(create=True)
913 return self.qrepo(create=True)
914
914
915 def unapplied(self, repo, patch=None):
915 def unapplied(self, repo, patch=None):
916 if patch and patch not in self.series:
916 if patch and patch not in self.series:
917 self.ui.warn("%s not in the series file\n" % patch)
917 self.ui.warn("%s not in the series file\n" % patch)
918 sys.exit(1)
918 sys.exit(1)
919 if not patch:
919 if not patch:
920 start = self.series_end()
920 start = self.series_end()
921 else:
921 else:
922 start = self.series.index(patch) + 1
922 start = self.series.index(patch) + 1
923 for p in self.series[start:]:
923 for p in self.series[start:]:
924 if self.ui.verbose:
924 if self.ui.verbose:
925 self.ui.write("%d " % self.series.index(p))
925 self.ui.write("%d " % self.series.index(p))
926 self.ui.write("%s\n" % p)
926 self.ui.write("%s\n" % p)
927
927
928 def qseries(self, repo, missing=None):
928 def qseries(self, repo, missing=None):
929 start = self.series_end()
929 start = self.series_end()
930 if not missing:
930 if not missing:
931 for p in self.series[:start]:
931 for p in self.series[:start]:
932 if self.ui.verbose:
932 if self.ui.verbose:
933 self.ui.write("%d A " % self.series.index(p))
933 self.ui.write("%d A " % self.series.index(p))
934 self.ui.write("%s\n" % p)
934 self.ui.write("%s\n" % p)
935 for p in self.series[start:]:
935 for p in self.series[start:]:
936 if self.ui.verbose:
936 if self.ui.verbose:
937 self.ui.write("%d U " % self.series.index(p))
937 self.ui.write("%d U " % self.series.index(p))
938 self.ui.write("%s\n" % p)
938 self.ui.write("%s\n" % p)
939 else:
939 else:
940 list = []
940 list = []
941 for root, dirs, files in os.walk(self.path):
941 for root, dirs, files in os.walk(self.path):
942 d = root[len(self.path) + 1:]
942 d = root[len(self.path) + 1:]
943 for f in files:
943 for f in files:
944 fl = os.path.join(d, f)
944 fl = os.path.join(d, f)
945 if (fl not in self.series and
945 if (fl not in self.series and
946 fl not in (self.status_path, self.series_path)
946 fl not in (self.status_path, self.series_path)
947 and not fl.startswith('.')):
947 and not fl.startswith('.')):
948 list.append(fl)
948 list.append(fl)
949 list.sort()
949 list.sort()
950 if list:
950 if list:
951 for x in list:
951 for x in list:
952 if self.ui.verbose:
952 if self.ui.verbose:
953 self.ui.write("D ")
953 self.ui.write("D ")
954 self.ui.write("%s\n" % x)
954 self.ui.write("%s\n" % x)
955
955
956 def issaveline(self, l):
956 def issaveline(self, l):
957 name = l.split(':')[1]
957 name = l.split(':')[1]
958 if name == '.hg.patches.save.line':
958 if name == '.hg.patches.save.line':
959 return True
959 return True
960
960
961 def qrepo(self, create=False):
961 def qrepo(self, create=False):
962 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")):
963 return hg.repository(self.ui, path=self.path, create=create)
963 return hg.repository(self.ui, path=self.path, create=create)
964
964
965 def restore(self, repo, rev, delete=None, qupdate=None):
965 def restore(self, repo, rev, delete=None, qupdate=None):
966 c = repo.changelog.read(rev)
966 c = repo.changelog.read(rev)
967 desc = c[4].strip()
967 desc = c[4].strip()
968 lines = desc.splitlines()
968 lines = desc.splitlines()
969 i = 0
969 i = 0
970 datastart = None
970 datastart = None
971 series = []
971 series = []
972 applied = []
972 applied = []
973 qpp = None
973 qpp = None
974 for i in xrange(0, len(lines)):
974 for i in xrange(0, len(lines)):
975 if lines[i] == 'Patch Data:':
975 if lines[i] == 'Patch Data:':
976 datastart = i + 1
976 datastart = i + 1
977 elif lines[i].startswith('Dirstate:'):
977 elif lines[i].startswith('Dirstate:'):
978 l = lines[i].rstrip()
978 l = lines[i].rstrip()
979 l = l[10:].split(' ')
979 l = l[10:].split(' ')
980 qpp = [ hg.bin(x) for x in l ]
980 qpp = [ hg.bin(x) for x in l ]
981 elif datastart != None:
981 elif datastart != None:
982 l = lines[i].rstrip()
982 l = lines[i].rstrip()
983 index = l.index(':')
983 index = l.index(':')
984 id = l[:index]
984 id = l[:index]
985 file = l[index + 1:]
985 file = l[index + 1:]
986 if id:
986 if id:
987 applied.append(l)
987 applied.append(l)
988 series.append(file)
988 series.append(file)
989 if datastart == None:
989 if datastart == None:
990 self.ui.warn("No saved patch data found\n")
990 self.ui.warn("No saved patch data found\n")
991 return 1
991 return 1
992 self.ui.warn("restoring status: %s\n" % lines[0])
992 self.ui.warn("restoring status: %s\n" % lines[0])
993 self.full_series = series
993 self.full_series = series
994 self.applied = applied
994 self.applied = applied
995 self.read_series(self.full_series)
995 self.read_series(self.full_series)
996 self.series_dirty = 1
996 self.series_dirty = 1
997 self.applied_dirty = 1
997 self.applied_dirty = 1
998 heads = repo.changelog.heads()
998 heads = repo.changelog.heads()
999 if delete:
999 if delete:
1000 if rev not in heads:
1000 if rev not in heads:
1001 self.ui.warn("save entry has children, leaving it alone\n")
1001 self.ui.warn("save entry has children, leaving it alone\n")
1002 else:
1002 else:
1003 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1003 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1004 pp = repo.dirstate.parents()
1004 pp = repo.dirstate.parents()
1005 if rev in pp:
1005 if rev in pp:
1006 update = True
1006 update = True
1007 else:
1007 else:
1008 update = False
1008 update = False
1009 self.strip(repo, rev, update=update, backup='strip')
1009 self.strip(repo, rev, update=update, backup='strip')
1010 if qpp:
1010 if qpp:
1011 self.ui.warn("saved queue repository parents: %s %s\n" %
1011 self.ui.warn("saved queue repository parents: %s %s\n" %
1012 (hg.short(qpp[0]), hg.short(qpp[1])))
1012 (hg.short(qpp[0]), hg.short(qpp[1])))
1013 if qupdate:
1013 if qupdate:
1014 print "queue directory updating"
1014 print "queue directory updating"
1015 r = self.qrepo()
1015 r = self.qrepo()
1016 if not r:
1016 if not r:
1017 self.ui.warn("Unable to load queue repository\n")
1017 self.ui.warn("Unable to load queue repository\n")
1018 return 1
1018 return 1
1019 r.update(qpp[0], allow=False, force=True)
1019 r.update(qpp[0], allow=False, force=True)
1020
1020
1021 def save(self, repo, msg=None):
1021 def save(self, repo, msg=None):
1022 if len(self.applied) == 0:
1022 if len(self.applied) == 0:
1023 self.ui.warn("save: no patches applied, exiting\n")
1023 self.ui.warn("save: no patches applied, exiting\n")
1024 return 1
1024 return 1
1025 if self.issaveline(self.applied[-1]):
1025 if self.issaveline(self.applied[-1]):
1026 self.ui.warn("status is already saved\n")
1026 self.ui.warn("status is already saved\n")
1027 return 1
1027 return 1
1028
1028
1029 ar = [ ':' + x for x in self.full_series ]
1029 ar = [ ':' + x for x in self.full_series ]
1030 if not msg:
1030 if not msg:
1031 msg = "hg patches saved state"
1031 msg = "hg patches saved state"
1032 else:
1032 else:
1033 msg = "hg patches: " + msg.rstrip('\r\n')
1033 msg = "hg patches: " + msg.rstrip('\r\n')
1034 r = self.qrepo()
1034 r = self.qrepo()
1035 if r:
1035 if r:
1036 pp = r.dirstate.parents()
1036 pp = r.dirstate.parents()
1037 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]))
1038 msg += "\n\nPatch Data:\n"
1038 msg += "\n\nPatch Data:\n"
1039 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)
1040 + '\n' or "")
1040 + '\n' or "")
1041 n = repo.commit(None, text, user=None, force=1)
1041 n = repo.commit(None, text, user=None, force=1)
1042 if not n:
1042 if not n:
1043 self.ui.warn("repo commit failed\n")
1043 self.ui.warn("repo commit failed\n")
1044 return 1
1044 return 1
1045 self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
1045 self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
1046 self.applied_dirty = 1
1046 self.applied_dirty = 1
1047
1047
1048 def full_series_end(self):
1049 if len(self.applied) > 0:
1050 (top, p) = self.applied[-1].split(':')
1051 end = self.find_series(p)
1052 if end == None:
1053 return len(self.full_series)
1054 return end + 1
1055 return 0
1056
1048 def series_end(self):
1057 def series_end(self):
1049 end = 0
1058 end = 0
1050 if len(self.applied) > 0:
1059 if len(self.applied) > 0:
1051 (top, p) = self.applied[-1].split(':')
1060 (top, p) = self.applied[-1].split(':')
1052 try:
1061 try:
1053 end = self.series.index(p)
1062 end = self.series.index(p)
1054 except ValueError:
1063 except ValueError:
1055 return 0
1064 return 0
1056 return end + 1
1065 return end + 1
1057 return end
1066 return end
1058
1067
1059 def qapplied(self, repo, patch=None):
1068 def qapplied(self, repo, patch=None):
1060 if patch and patch not in self.series:
1069 if patch and patch not in self.series:
1061 self.ui.warn("%s not in the series file\n" % patch)
1070 self.ui.warn("%s not in the series file\n" % patch)
1062 sys.exit(1)
1071 sys.exit(1)
1063 if not patch:
1072 if not patch:
1064 end = len(self.applied)
1073 end = len(self.applied)
1065 else:
1074 else:
1066 end = self.series.index(patch) + 1
1075 end = self.series.index(patch) + 1
1067 for x in xrange(end):
1076 for x in xrange(end):
1068 p = self.appliedname(x)
1077 p = self.appliedname(x)
1069 self.ui.write("%s\n" % p)
1078 self.ui.write("%s\n" % p)
1070
1079
1071 def appliedname(self, index):
1080 def appliedname(self, index):
1072 p = self.applied[index]
1081 p = self.applied[index]
1073 pname = p.split(':')[1]
1082 pname = p.split(':')[1]
1074 if not self.ui.verbose:
1083 if not self.ui.verbose:
1075 p = pname
1084 p = pname
1076 else:
1085 else:
1077 p = str(self.series.index(pname)) + " " + p
1086 p = str(self.series.index(pname)) + " " + p
1078 return p
1087 return p
1079
1088
1080 def top(self, repo):
1089 def top(self, repo):
1081 if len(self.applied):
1090 if len(self.applied):
1082 p = self.appliedname(-1)
1091 p = self.appliedname(-1)
1083 self.ui.write(p + '\n')
1092 self.ui.write(p + '\n')
1084 else:
1093 else:
1085 self.ui.write("No patches applied\n")
1094 self.ui.write("No patches applied\n")
1086
1095
1087 def next(self, repo):
1096 def next(self, repo):
1088 end = self.series_end()
1097 end = self.series_end()
1089 if end == len(self.series):
1098 if end == len(self.series):
1090 self.ui.write("All patches applied\n")
1099 self.ui.write("All patches applied\n")
1091 else:
1100 else:
1092 p = self.series[end]
1101 p = self.series[end]
1093 if self.ui.verbose:
1102 if self.ui.verbose:
1094 self.ui.write("%d " % self.series.index(p))
1103 self.ui.write("%d " % self.series.index(p))
1095 self.ui.write(p + '\n')
1104 self.ui.write(p + '\n')
1096
1105
1097 def prev(self, repo):
1106 def prev(self, repo):
1098 if len(self.applied) > 1:
1107 if len(self.applied) > 1:
1099 p = self.appliedname(-2)
1108 p = self.appliedname(-2)
1100 self.ui.write(p + '\n')
1109 self.ui.write(p + '\n')
1101 elif len(self.applied) == 1:
1110 elif len(self.applied) == 1:
1102 self.ui.write("Only one patch applied\n")
1111 self.ui.write("Only one patch applied\n")
1103 else:
1112 else:
1104 self.ui.write("No patches applied\n")
1113 self.ui.write("No patches applied\n")
1105
1114
1106 def qimport(self, repo, files, patch=None, existing=None, force=None):
1115 def qimport(self, repo, files, patch=None, existing=None, force=None):
1107 if len(files) > 1 and patch:
1116 if len(files) > 1 and patch:
1108 self.ui.warn("-n option not valid when importing multiple files\n")
1117 self.ui.warn("-n option not valid when importing multiple files\n")
1109 sys.exit(1)
1118 sys.exit(1)
1110 i = 0
1119 i = 0
1111 added = []
1120 added = []
1112 for filename in files:
1121 for filename in files:
1113 if existing:
1122 if existing:
1114 if not patch:
1123 if not patch:
1115 patch = filename
1124 patch = filename
1116 if not os.path.isfile(os.path.join(self.path, patch)):
1125 if not os.path.isfile(os.path.join(self.path, patch)):
1117 self.ui.warn("patch %s does not exist\n" % patch)
1126 self.ui.warn("patch %s does not exist\n" % patch)
1118 sys.exit(1)
1127 sys.exit(1)
1119 else:
1128 else:
1120 try:
1129 try:
1121 text = file(filename).read()
1130 text = file(filename).read()
1122 except IOError:
1131 except IOError:
1123 self.ui.warn("Unable to read %s\n" % patch)
1132 self.ui.warn("Unable to read %s\n" % patch)
1124 sys.exit(1)
1133 sys.exit(1)
1125 if not patch:
1134 if not patch:
1126 patch = os.path.split(filename)[1]
1135 patch = os.path.split(filename)[1]
1127 if not force and os.path.isfile(os.path.join(self.path, patch)):
1136 if not force and os.path.isfile(os.path.join(self.path, patch)):
1128 self.ui.warn("patch %s already exists\n" % patch)
1137 self.ui.warn("patch %s already exists\n" % patch)
1129 sys.exit(1)
1138 sys.exit(1)
1130 patchf = self.opener(patch, "w")
1139 patchf = self.opener(patch, "w")
1131 patchf.write(text)
1140 patchf.write(text)
1132 if patch in self.series:
1141 if patch in self.series:
1133 self.ui.warn("patch %s is already in the series file\n" % patch)
1142 self.ui.warn("patch %s is already in the series file\n" % patch)
1134 sys.exit(1)
1143 sys.exit(1)
1135 index = self.series_end() + i
1144 index = self.full_series_end() + i
1136 self.full_series[index:index] = [patch]
1145 self.full_series[index:index] = [patch]
1137 self.read_series(self.full_series)
1146 self.read_series(self.full_series)
1138 self.ui.warn("adding %s to series file\n" % patch)
1147 self.ui.warn("adding %s to series file\n" % patch)
1139 i += 1
1148 i += 1
1140 added.append(patch)
1149 added.append(patch)
1141 patch = None
1150 patch = None
1142 self.series_dirty = 1
1151 self.series_dirty = 1
1143 qrepo = self.qrepo()
1152 qrepo = self.qrepo()
1144 if qrepo:
1153 if qrepo:
1145 qrepo.add(added)
1154 qrepo.add(added)
1146
1155
1147 def delete(ui, repo, patch, **opts):
1156 def delete(ui, repo, patch, **opts):
1148 """remove a patch from the series file"""
1157 """remove a patch from the series file"""
1149 q = repomap[repo]
1158 q = repomap[repo]
1150 q.delete(repo, patch)
1159 q.delete(repo, patch)
1151 q.save_dirty()
1160 q.save_dirty()
1152 return 0
1161 return 0
1153
1162
1154 def applied(ui, repo, patch=None, **opts):
1163 def applied(ui, repo, patch=None, **opts):
1155 """print the patches already applied"""
1164 """print the patches already applied"""
1156 repomap[repo].qapplied(repo, patch)
1165 repomap[repo].qapplied(repo, patch)
1157 return 0
1166 return 0
1158
1167
1159 def unapplied(ui, repo, patch=None, **opts):
1168 def unapplied(ui, repo, patch=None, **opts):
1160 """print the patches not yet applied"""
1169 """print the patches not yet applied"""
1161 repomap[repo].unapplied(repo, patch)
1170 repomap[repo].unapplied(repo, patch)
1162 return 0
1171 return 0
1163
1172
1164 def qimport(ui, repo, *filename, **opts):
1173 def qimport(ui, repo, *filename, **opts):
1165 """import a patch"""
1174 """import a patch"""
1166 q = repomap[repo]
1175 q = repomap[repo]
1167 q.qimport(repo, filename, patch=opts['name'],
1176 q.qimport(repo, filename, patch=opts['name'],
1168 existing=opts['existing'], force=opts['force'])
1177 existing=opts['existing'], force=opts['force'])
1169 q.save_dirty()
1178 q.save_dirty()
1170 return 0
1179 return 0
1171
1180
1172 def init(ui, repo, **opts):
1181 def init(ui, repo, **opts):
1173 """init a new queue repository"""
1182 """init a new queue repository"""
1174 q = repomap[repo]
1183 q = repomap[repo]
1175 r = q.init(repo, create=opts['create_repo'])
1184 r = q.init(repo, create=opts['create_repo'])
1176 q.save_dirty()
1185 q.save_dirty()
1177 if r:
1186 if r:
1178 fp = r.wopener('.hgignore', 'w')
1187 fp = r.wopener('.hgignore', 'w')
1179 print >> fp, 'syntax: glob'
1188 print >> fp, 'syntax: glob'
1180 print >> fp, 'status'
1189 print >> fp, 'status'
1181 fp.close()
1190 fp.close()
1182 r.wopener('series', 'w').close()
1191 r.wopener('series', 'w').close()
1183 r.add(['.hgignore', 'series'])
1192 r.add(['.hgignore', 'series'])
1184 return 0
1193 return 0
1185
1194
1186 def commit(ui, repo, *pats, **opts):
1195 def commit(ui, repo, *pats, **opts):
1187 """commit changes in the queue repository"""
1196 """commit changes in the queue repository"""
1188 q = repomap[repo]
1197 q = repomap[repo]
1189 r = q.qrepo()
1198 r = q.qrepo()
1190 if not r: raise util.Abort('no queue repository')
1199 if not r: raise util.Abort('no queue repository')
1191 commands.commit(r.ui, r, *pats, **opts)
1200 commands.commit(r.ui, r, *pats, **opts)
1192
1201
1193 def series(ui, repo, **opts):
1202 def series(ui, repo, **opts):
1194 """print the entire series file"""
1203 """print the entire series file"""
1195 repomap[repo].qseries(repo, missing=opts['missing'])
1204 repomap[repo].qseries(repo, missing=opts['missing'])
1196 return 0
1205 return 0
1197
1206
1198 def top(ui, repo, **opts):
1207 def top(ui, repo, **opts):
1199 """print the name of the current patch"""
1208 """print the name of the current patch"""
1200 repomap[repo].top(repo)
1209 repomap[repo].top(repo)
1201 return 0
1210 return 0
1202
1211
1203 def next(ui, repo, **opts):
1212 def next(ui, repo, **opts):
1204 """print the name of the next patch"""
1213 """print the name of the next patch"""
1205 repomap[repo].next(repo)
1214 repomap[repo].next(repo)
1206 return 0
1215 return 0
1207
1216
1208 def prev(ui, repo, **opts):
1217 def prev(ui, repo, **opts):
1209 """print the name of the previous patch"""
1218 """print the name of the previous patch"""
1210 repomap[repo].prev(repo)
1219 repomap[repo].prev(repo)
1211 return 0
1220 return 0
1212
1221
1213 def new(ui, repo, patch, **opts):
1222 def new(ui, repo, patch, **opts):
1214 """create a new patch"""
1223 """create a new patch"""
1215 q = repomap[repo]
1224 q = repomap[repo]
1216 message=commands.logmessage(**opts)
1225 message=commands.logmessage(**opts)
1217 q.new(repo, patch, msg=message, force=opts['force'])
1226 q.new(repo, patch, msg=message, force=opts['force'])
1218 q.save_dirty()
1227 q.save_dirty()
1219 return 0
1228 return 0
1220
1229
1221 def refresh(ui, repo, **opts):
1230 def refresh(ui, repo, **opts):
1222 """update the current patch"""
1231 """update the current patch"""
1223 q = repomap[repo]
1232 q = repomap[repo]
1224 message=commands.logmessage(**opts)
1233 message=commands.logmessage(**opts)
1225 q.refresh(repo, msg=message, short=opts['short'])
1234 q.refresh(repo, msg=message, short=opts['short'])
1226 q.save_dirty()
1235 q.save_dirty()
1227 return 0
1236 return 0
1228
1237
1229 def diff(ui, repo, *files, **opts):
1238 def diff(ui, repo, *files, **opts):
1230 """diff of the current patch"""
1239 """diff of the current patch"""
1231 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1240 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1232 repomap[repo].diff(repo, list(files))
1241 repomap[repo].diff(repo, list(files))
1233 return 0
1242 return 0
1234
1243
1235 def lastsavename(path):
1244 def lastsavename(path):
1236 (dir, base) = os.path.split(path)
1245 (dir, base) = os.path.split(path)
1237 names = os.listdir(dir)
1246 names = os.listdir(dir)
1238 namere = re.compile("%s.([0-9]+)" % base)
1247 namere = re.compile("%s.([0-9]+)" % base)
1239 max = None
1248 max = None
1240 maxname = None
1249 maxname = None
1241 for f in names:
1250 for f in names:
1242 m = namere.match(f)
1251 m = namere.match(f)
1243 if m:
1252 if m:
1244 index = int(m.group(1))
1253 index = int(m.group(1))
1245 if max == None or index > max:
1254 if max == None or index > max:
1246 max = index
1255 max = index
1247 maxname = f
1256 maxname = f
1248 if maxname:
1257 if maxname:
1249 return (os.path.join(dir, maxname), max)
1258 return (os.path.join(dir, maxname), max)
1250 return (None, None)
1259 return (None, None)
1251
1260
1252 def savename(path):
1261 def savename(path):
1253 (last, index) = lastsavename(path)
1262 (last, index) = lastsavename(path)
1254 if last is None:
1263 if last is None:
1255 index = 0
1264 index = 0
1256 newpath = path + ".%d" % (index + 1)
1265 newpath = path + ".%d" % (index + 1)
1257 return newpath
1266 return newpath
1258
1267
1259 def push(ui, repo, patch=None, **opts):
1268 def push(ui, repo, patch=None, **opts):
1260 """push the next patch onto the stack"""
1269 """push the next patch onto the stack"""
1261 q = repomap[repo]
1270 q = repomap[repo]
1262 mergeq = None
1271 mergeq = None
1263
1272
1264 if opts['all']:
1273 if opts['all']:
1265 patch = q.series[-1]
1274 patch = q.series[-1]
1266 if opts['merge']:
1275 if opts['merge']:
1267 if opts['name']:
1276 if opts['name']:
1268 newpath = opts['name']
1277 newpath = opts['name']
1269 else:
1278 else:
1270 newpath, i = lastsavename(q.path)
1279 newpath, i = lastsavename(q.path)
1271 if not newpath:
1280 if not newpath:
1272 ui.warn("no saved queues found, please use -n\n")
1281 ui.warn("no saved queues found, please use -n\n")
1273 return 1
1282 return 1
1274 mergeq = queue(ui, repo.join(""), newpath)
1283 mergeq = queue(ui, repo.join(""), newpath)
1275 ui.warn("merging with queue at: %s\n" % mergeq.path)
1284 ui.warn("merging with queue at: %s\n" % mergeq.path)
1276 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1285 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1277 mergeq=mergeq)
1286 mergeq=mergeq)
1278 q.save_dirty()
1287 q.save_dirty()
1279 return ret
1288 return ret
1280
1289
1281 def pop(ui, repo, patch=None, **opts):
1290 def pop(ui, repo, patch=None, **opts):
1282 """pop the current patch off the stack"""
1291 """pop the current patch off the stack"""
1283 localupdate = True
1292 localupdate = True
1284 if opts['name']:
1293 if opts['name']:
1285 q = queue(ui, repo.join(""), repo.join(opts['name']))
1294 q = queue(ui, repo.join(""), repo.join(opts['name']))
1286 ui.warn('using patch queue: %s\n' % q.path)
1295 ui.warn('using patch queue: %s\n' % q.path)
1287 localupdate = False
1296 localupdate = False
1288 else:
1297 else:
1289 q = repomap[repo]
1298 q = repomap[repo]
1290 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1299 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1291 q.save_dirty()
1300 q.save_dirty()
1292 return 0
1301 return 0
1293
1302
1294 def restore(ui, repo, rev, **opts):
1303 def restore(ui, repo, rev, **opts):
1295 """restore the queue state saved by a rev"""
1304 """restore the queue state saved by a rev"""
1296 rev = repo.lookup(rev)
1305 rev = repo.lookup(rev)
1297 q = repomap[repo]
1306 q = repomap[repo]
1298 q.restore(repo, rev, delete=opts['delete'],
1307 q.restore(repo, rev, delete=opts['delete'],
1299 qupdate=opts['update'])
1308 qupdate=opts['update'])
1300 q.save_dirty()
1309 q.save_dirty()
1301 return 0
1310 return 0
1302
1311
1303 def save(ui, repo, **opts):
1312 def save(ui, repo, **opts):
1304 """save current queue state"""
1313 """save current queue state"""
1305 q = repomap[repo]
1314 q = repomap[repo]
1306 message=commands.logmessage(**opts)
1315 message=commands.logmessage(**opts)
1307 ret = q.save(repo, msg=message)
1316 ret = q.save(repo, msg=message)
1308 if ret:
1317 if ret:
1309 return ret
1318 return ret
1310 q.save_dirty()
1319 q.save_dirty()
1311 if opts['copy']:
1320 if opts['copy']:
1312 path = q.path
1321 path = q.path
1313 if opts['name']:
1322 if opts['name']:
1314 newpath = os.path.join(q.basepath, opts['name'])
1323 newpath = os.path.join(q.basepath, opts['name'])
1315 if os.path.exists(newpath):
1324 if os.path.exists(newpath):
1316 if not os.path.isdir(newpath):
1325 if not os.path.isdir(newpath):
1317 ui.warn("destination %s exists and is not a directory\n" %
1326 ui.warn("destination %s exists and is not a directory\n" %
1318 newpath)
1327 newpath)
1319 sys.exit(1)
1328 sys.exit(1)
1320 if not opts['force']:
1329 if not opts['force']:
1321 ui.warn("destination %s exists, use -f to force\n" %
1330 ui.warn("destination %s exists, use -f to force\n" %
1322 newpath)
1331 newpath)
1323 sys.exit(1)
1332 sys.exit(1)
1324 else:
1333 else:
1325 newpath = savename(path)
1334 newpath = savename(path)
1326 ui.warn("copy %s to %s\n" % (path, newpath))
1335 ui.warn("copy %s to %s\n" % (path, newpath))
1327 util.copyfiles(path, newpath)
1336 util.copyfiles(path, newpath)
1328 if opts['empty']:
1337 if opts['empty']:
1329 try:
1338 try:
1330 os.unlink(os.path.join(q.path, q.status_path))
1339 os.unlink(os.path.join(q.path, q.status_path))
1331 except:
1340 except:
1332 pass
1341 pass
1333 return 0
1342 return 0
1334
1343
1335 def strip(ui, repo, rev, **opts):
1344 def strip(ui, repo, rev, **opts):
1336 """strip a revision and all later revs on the same branch"""
1345 """strip a revision and all later revs on the same branch"""
1337 rev = repo.lookup(rev)
1346 rev = repo.lookup(rev)
1338 backup = 'all'
1347 backup = 'all'
1339 if opts['backup']:
1348 if opts['backup']:
1340 backup = 'strip'
1349 backup = 'strip'
1341 elif opts['nobackup']:
1350 elif opts['nobackup']:
1342 backup = 'none'
1351 backup = 'none'
1343 repomap[repo].strip(repo, rev, backup=backup)
1352 repomap[repo].strip(repo, rev, backup=backup)
1344 return 0
1353 return 0
1345
1354
1346 def version(ui, q=None):
1355 def version(ui, q=None):
1347 """print the version number"""
1356 """print the version number"""
1348 ui.write("mq version %s\n" % versionstr)
1357 ui.write("mq version %s\n" % versionstr)
1349 return 0
1358 return 0
1350
1359
1351 def reposetup(ui, repo):
1360 def reposetup(ui, repo):
1352 repomap[repo] = queue(ui, repo.join(""))
1361 repomap[repo] = queue(ui, repo.join(""))
1353 oldtags = repo.tags
1362 oldtags = repo.tags
1354
1363
1355 def qtags():
1364 def qtags():
1356 if repo.tagscache:
1365 if repo.tagscache:
1357 return repo.tagscache
1366 return repo.tagscache
1358
1367
1359 tagscache = oldtags()
1368 tagscache = oldtags()
1360
1369
1361 q = repomap[repo]
1370 q = repomap[repo]
1362 if len(q.applied) == 0:
1371 if len(q.applied) == 0:
1363 return tagscache
1372 return tagscache
1364
1373
1365 mqtags = [patch.split(':') for patch in q.applied]
1374 mqtags = [patch.split(':') for patch in q.applied]
1366 mqtags.append((mqtags[-1][0], 'qtip'))
1375 mqtags.append((mqtags[-1][0], 'qtip'))
1367 mqtags.append((mqtags[0][0], 'qbase'))
1376 mqtags.append((mqtags[0][0], 'qbase'))
1368 for patch in mqtags:
1377 for patch in mqtags:
1369 if patch[1] in tagscache:
1378 if patch[1] in tagscache:
1370 repo.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1379 repo.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1371 else:
1380 else:
1372 tagscache[patch[1]] = revlog.bin(patch[0])
1381 tagscache[patch[1]] = revlog.bin(patch[0])
1373
1382
1374 return tagscache
1383 return tagscache
1375
1384
1376 repo.tags = qtags
1385 repo.tags = qtags
1377
1386
1378 cmdtable = {
1387 cmdtable = {
1379 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1388 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1380 "qcommit|qci":
1389 "qcommit|qci":
1381 (commit,
1390 (commit,
1382 commands.table["^commit|ci"][1],
1391 commands.table["^commit|ci"][1],
1383 'hg qcommit [OPTION]... [FILE]...'),
1392 'hg qcommit [OPTION]... [FILE]...'),
1384 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1393 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1385 "qdelete": (delete, [], 'hg qdelete PATCH'),
1394 "qdelete": (delete, [], 'hg qdelete PATCH'),
1386 "^qimport":
1395 "^qimport":
1387 (qimport,
1396 (qimport,
1388 [('e', 'existing', None, 'import file in patch dir'),
1397 [('e', 'existing', None, 'import file in patch dir'),
1389 ('n', 'name', '', 'patch file name'),
1398 ('n', 'name', '', 'patch file name'),
1390 ('f', 'force', None, 'overwrite existing files')],
1399 ('f', 'force', None, 'overwrite existing files')],
1391 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1400 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1392 "^qinit":
1401 "^qinit":
1393 (init,
1402 (init,
1394 [('c', 'create-repo', None, 'create queue repository')],
1403 [('c', 'create-repo', None, 'create queue repository')],
1395 'hg qinit [-c]'),
1404 'hg qinit [-c]'),
1396 "qnew":
1405 "qnew":
1397 (new,
1406 (new,
1398 [('m', 'message', '', _('use <text> as commit message')),
1407 [('m', 'message', '', _('use <text> as commit message')),
1399 ('l', 'logfile', '', _('read the commit message from <file>')),
1408 ('l', 'logfile', '', _('read the commit message from <file>')),
1400 ('f', 'force', None, 'force')],
1409 ('f', 'force', None, 'force')],
1401 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1410 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1402 "qnext": (next, [], 'hg qnext'),
1411 "qnext": (next, [], 'hg qnext'),
1403 "qprev": (prev, [], 'hg qprev'),
1412 "qprev": (prev, [], 'hg qprev'),
1404 "^qpop":
1413 "^qpop":
1405 (pop,
1414 (pop,
1406 [('a', 'all', None, 'pop all patches'),
1415 [('a', 'all', None, 'pop all patches'),
1407 ('n', 'name', '', 'queue name to pop'),
1416 ('n', 'name', '', 'queue name to pop'),
1408 ('f', 'force', None, 'forget any local changes')],
1417 ('f', 'force', None, 'forget any local changes')],
1409 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1418 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1410 "^qpush":
1419 "^qpush":
1411 (push,
1420 (push,
1412 [('f', 'force', None, 'apply if the patch has rejects'),
1421 [('f', 'force', None, 'apply if the patch has rejects'),
1413 ('l', 'list', None, 'list patch name in commit text'),
1422 ('l', 'list', None, 'list patch name in commit text'),
1414 ('a', 'all', None, 'apply all patches'),
1423 ('a', 'all', None, 'apply all patches'),
1415 ('m', 'merge', None, 'merge from another queue'),
1424 ('m', 'merge', None, 'merge from another queue'),
1416 ('n', 'name', '', 'merge queue name')],
1425 ('n', 'name', '', 'merge queue name')],
1417 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1426 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1418 "^qrefresh":
1427 "^qrefresh":
1419 (refresh,
1428 (refresh,
1420 [('m', 'message', '', _('change commit message with <text>')),
1429 [('m', 'message', '', _('change commit message with <text>')),
1421 ('l', 'logfile', '', _('change commit message with <file> content')),
1430 ('l', 'logfile', '', _('change commit message with <file> content')),
1422 ('s', 'short', None, 'short refresh')],
1431 ('s', 'short', None, 'short refresh')],
1423 'hg qrefresh [-m TEXT] [-l FILE] [-s]'),
1432 'hg qrefresh [-m TEXT] [-l FILE] [-s]'),
1424 "qrestore":
1433 "qrestore":
1425 (restore,
1434 (restore,
1426 [('d', 'delete', None, 'delete save entry'),
1435 [('d', 'delete', None, 'delete save entry'),
1427 ('u', 'update', None, 'update queue working dir')],
1436 ('u', 'update', None, 'update queue working dir')],
1428 'hg qrestore [-d] [-u] REV'),
1437 'hg qrestore [-d] [-u] REV'),
1429 "qsave":
1438 "qsave":
1430 (save,
1439 (save,
1431 [('m', 'message', '', _('use <text> as commit message')),
1440 [('m', 'message', '', _('use <text> as commit message')),
1432 ('l', 'logfile', '', _('read the commit message from <file>')),
1441 ('l', 'logfile', '', _('read the commit message from <file>')),
1433 ('c', 'copy', None, 'copy patch directory'),
1442 ('c', 'copy', None, 'copy patch directory'),
1434 ('n', 'name', '', 'copy directory name'),
1443 ('n', 'name', '', 'copy directory name'),
1435 ('e', 'empty', None, 'clear queue status file'),
1444 ('e', 'empty', None, 'clear queue status file'),
1436 ('f', 'force', None, 'force copy')],
1445 ('f', 'force', None, 'force copy')],
1437 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1446 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1438 "qseries":
1447 "qseries":
1439 (series,
1448 (series,
1440 [('m', 'missing', None, 'print patches not in series')],
1449 [('m', 'missing', None, 'print patches not in series')],
1441 'hg qseries [-m]'),
1450 'hg qseries [-m]'),
1442 "^strip":
1451 "^strip":
1443 (strip,
1452 (strip,
1444 [('f', 'force', None, 'force multi-head removal'),
1453 [('f', 'force', None, 'force multi-head removal'),
1445 ('b', 'backup', None, 'bundle unrelated changesets'),
1454 ('b', 'backup', None, 'bundle unrelated changesets'),
1446 ('n', 'nobackup', None, 'no backups')],
1455 ('n', 'nobackup', None, 'no backups')],
1447 'hg strip [-f] [-b] [-n] REV'),
1456 'hg strip [-f] [-b] [-n] REV'),
1448 "qtop": (top, [], 'hg qtop'),
1457 "qtop": (top, [], 'hg qtop'),
1449 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1458 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1450 "qversion": (version, [], 'hg qversion')
1459 "qversion": (version, [], 'hg qversion')
1451 }
1460 }
1452
1461
General Comments 0
You need to be logged in to leave comments. Login now