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