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