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