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