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