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