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