##// END OF EJS Templates
patch: fuzz more aggressively to match patch(1) behaviour...
Patrick Mezard -
r16124:0e0060bf stable
parent child Browse files
Show More
@@ -1,1878 +1,1868 b''
1 1 # patch.py - patch file parsing routines
2 2 #
3 3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import cStringIO, email.Parser, os, errno, re
10 10 import tempfile, zlib, shutil
11 11
12 12 from i18n import _
13 13 from node import hex, nullid, short
14 14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
15 15 import context
16 16
17 17 gitre = re.compile('diff --git a/(.*) b/(.*)')
18 18
19 19 class PatchError(Exception):
20 20 pass
21 21
22 22
23 23 # public functions
24 24
25 25 def split(stream):
26 26 '''return an iterator of individual patches from a stream'''
27 27 def isheader(line, inheader):
28 28 if inheader and line[0] in (' ', '\t'):
29 29 # continuation
30 30 return True
31 31 if line[0] in (' ', '-', '+'):
32 32 # diff line - don't check for header pattern in there
33 33 return False
34 34 l = line.split(': ', 1)
35 35 return len(l) == 2 and ' ' not in l[0]
36 36
37 37 def chunk(lines):
38 38 return cStringIO.StringIO(''.join(lines))
39 39
40 40 def hgsplit(stream, cur):
41 41 inheader = True
42 42
43 43 for line in stream:
44 44 if not line.strip():
45 45 inheader = False
46 46 if not inheader and line.startswith('# HG changeset patch'):
47 47 yield chunk(cur)
48 48 cur = []
49 49 inheader = True
50 50
51 51 cur.append(line)
52 52
53 53 if cur:
54 54 yield chunk(cur)
55 55
56 56 def mboxsplit(stream, cur):
57 57 for line in stream:
58 58 if line.startswith('From '):
59 59 for c in split(chunk(cur[1:])):
60 60 yield c
61 61 cur = []
62 62
63 63 cur.append(line)
64 64
65 65 if cur:
66 66 for c in split(chunk(cur[1:])):
67 67 yield c
68 68
69 69 def mimesplit(stream, cur):
70 70 def msgfp(m):
71 71 fp = cStringIO.StringIO()
72 72 g = email.Generator.Generator(fp, mangle_from_=False)
73 73 g.flatten(m)
74 74 fp.seek(0)
75 75 return fp
76 76
77 77 for line in stream:
78 78 cur.append(line)
79 79 c = chunk(cur)
80 80
81 81 m = email.Parser.Parser().parse(c)
82 82 if not m.is_multipart():
83 83 yield msgfp(m)
84 84 else:
85 85 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
86 86 for part in m.walk():
87 87 ct = part.get_content_type()
88 88 if ct not in ok_types:
89 89 continue
90 90 yield msgfp(part)
91 91
92 92 def headersplit(stream, cur):
93 93 inheader = False
94 94
95 95 for line in stream:
96 96 if not inheader and isheader(line, inheader):
97 97 yield chunk(cur)
98 98 cur = []
99 99 inheader = True
100 100 if inheader and not isheader(line, inheader):
101 101 inheader = False
102 102
103 103 cur.append(line)
104 104
105 105 if cur:
106 106 yield chunk(cur)
107 107
108 108 def remainder(cur):
109 109 yield chunk(cur)
110 110
111 111 class fiter(object):
112 112 def __init__(self, fp):
113 113 self.fp = fp
114 114
115 115 def __iter__(self):
116 116 return self
117 117
118 118 def next(self):
119 119 l = self.fp.readline()
120 120 if not l:
121 121 raise StopIteration
122 122 return l
123 123
124 124 inheader = False
125 125 cur = []
126 126
127 127 mimeheaders = ['content-type']
128 128
129 129 if not util.safehasattr(stream, 'next'):
130 130 # http responses, for example, have readline but not next
131 131 stream = fiter(stream)
132 132
133 133 for line in stream:
134 134 cur.append(line)
135 135 if line.startswith('# HG changeset patch'):
136 136 return hgsplit(stream, cur)
137 137 elif line.startswith('From '):
138 138 return mboxsplit(stream, cur)
139 139 elif isheader(line, inheader):
140 140 inheader = True
141 141 if line.split(':', 1)[0].lower() in mimeheaders:
142 142 # let email parser handle this
143 143 return mimesplit(stream, cur)
144 144 elif line.startswith('--- ') and inheader:
145 145 # No evil headers seen by diff start, split by hand
146 146 return headersplit(stream, cur)
147 147 # Not enough info, keep reading
148 148
149 149 # if we are here, we have a very plain patch
150 150 return remainder(cur)
151 151
152 152 def extract(ui, fileobj):
153 153 '''extract patch from data read from fileobj.
154 154
155 155 patch can be a normal patch or contained in an email message.
156 156
157 157 return tuple (filename, message, user, date, branch, node, p1, p2).
158 158 Any item in the returned tuple can be None. If filename is None,
159 159 fileobj did not contain a patch. Caller must unlink filename when done.'''
160 160
161 161 # attempt to detect the start of a patch
162 162 # (this heuristic is borrowed from quilt)
163 163 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
164 164 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
165 165 r'---[ \t].*?^\+\+\+[ \t]|'
166 166 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
167 167
168 168 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
169 169 tmpfp = os.fdopen(fd, 'w')
170 170 try:
171 171 msg = email.Parser.Parser().parse(fileobj)
172 172
173 173 subject = msg['Subject']
174 174 user = msg['From']
175 175 if not subject and not user:
176 176 # Not an email, restore parsed headers if any
177 177 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
178 178
179 179 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
180 180 # should try to parse msg['Date']
181 181 date = None
182 182 nodeid = None
183 183 branch = None
184 184 parents = []
185 185
186 186 if subject:
187 187 if subject.startswith('[PATCH'):
188 188 pend = subject.find(']')
189 189 if pend >= 0:
190 190 subject = subject[pend + 1:].lstrip()
191 191 subject = re.sub(r'\n[ \t]+', ' ', subject)
192 192 ui.debug('Subject: %s\n' % subject)
193 193 if user:
194 194 ui.debug('From: %s\n' % user)
195 195 diffs_seen = 0
196 196 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
197 197 message = ''
198 198 for part in msg.walk():
199 199 content_type = part.get_content_type()
200 200 ui.debug('Content-Type: %s\n' % content_type)
201 201 if content_type not in ok_types:
202 202 continue
203 203 payload = part.get_payload(decode=True)
204 204 m = diffre.search(payload)
205 205 if m:
206 206 hgpatch = False
207 207 hgpatchheader = False
208 208 ignoretext = False
209 209
210 210 ui.debug('found patch at byte %d\n' % m.start(0))
211 211 diffs_seen += 1
212 212 cfp = cStringIO.StringIO()
213 213 for line in payload[:m.start(0)].splitlines():
214 214 if line.startswith('# HG changeset patch') and not hgpatch:
215 215 ui.debug('patch generated by hg export\n')
216 216 hgpatch = True
217 217 hgpatchheader = True
218 218 # drop earlier commit message content
219 219 cfp.seek(0)
220 220 cfp.truncate()
221 221 subject = None
222 222 elif hgpatchheader:
223 223 if line.startswith('# User '):
224 224 user = line[7:]
225 225 ui.debug('From: %s\n' % user)
226 226 elif line.startswith("# Date "):
227 227 date = line[7:]
228 228 elif line.startswith("# Branch "):
229 229 branch = line[9:]
230 230 elif line.startswith("# Node ID "):
231 231 nodeid = line[10:]
232 232 elif line.startswith("# Parent "):
233 233 parents.append(line[10:])
234 234 elif not line.startswith("# "):
235 235 hgpatchheader = False
236 236 elif line == '---' and gitsendmail:
237 237 ignoretext = True
238 238 if not hgpatchheader and not ignoretext:
239 239 cfp.write(line)
240 240 cfp.write('\n')
241 241 message = cfp.getvalue()
242 242 if tmpfp:
243 243 tmpfp.write(payload)
244 244 if not payload.endswith('\n'):
245 245 tmpfp.write('\n')
246 246 elif not diffs_seen and message and content_type == 'text/plain':
247 247 message += '\n' + payload
248 248 except:
249 249 tmpfp.close()
250 250 os.unlink(tmpname)
251 251 raise
252 252
253 253 if subject and not message.startswith(subject):
254 254 message = '%s\n%s' % (subject, message)
255 255 tmpfp.close()
256 256 if not diffs_seen:
257 257 os.unlink(tmpname)
258 258 return None, message, user, date, branch, None, None, None
259 259 p1 = parents and parents.pop(0) or None
260 260 p2 = parents and parents.pop(0) or None
261 261 return tmpname, message, user, date, branch, nodeid, p1, p2
262 262
263 263 class patchmeta(object):
264 264 """Patched file metadata
265 265
266 266 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
267 267 or COPY. 'path' is patched file path. 'oldpath' is set to the
268 268 origin file when 'op' is either COPY or RENAME, None otherwise. If
269 269 file mode is changed, 'mode' is a tuple (islink, isexec) where
270 270 'islink' is True if the file is a symlink and 'isexec' is True if
271 271 the file is executable. Otherwise, 'mode' is None.
272 272 """
273 273 def __init__(self, path):
274 274 self.path = path
275 275 self.oldpath = None
276 276 self.mode = None
277 277 self.op = 'MODIFY'
278 278 self.binary = False
279 279
280 280 def setmode(self, mode):
281 281 islink = mode & 020000
282 282 isexec = mode & 0100
283 283 self.mode = (islink, isexec)
284 284
285 285 def copy(self):
286 286 other = patchmeta(self.path)
287 287 other.oldpath = self.oldpath
288 288 other.mode = self.mode
289 289 other.op = self.op
290 290 other.binary = self.binary
291 291 return other
292 292
293 293 def __repr__(self):
294 294 return "<patchmeta %s %r>" % (self.op, self.path)
295 295
296 296 def readgitpatch(lr):
297 297 """extract git-style metadata about patches from <patchname>"""
298 298
299 299 # Filter patch for git information
300 300 gp = None
301 301 gitpatches = []
302 302 for line in lr:
303 303 line = line.rstrip(' \r\n')
304 304 if line.startswith('diff --git'):
305 305 m = gitre.match(line)
306 306 if m:
307 307 if gp:
308 308 gitpatches.append(gp)
309 309 dst = m.group(2)
310 310 gp = patchmeta(dst)
311 311 elif gp:
312 312 if line.startswith('--- '):
313 313 gitpatches.append(gp)
314 314 gp = None
315 315 continue
316 316 if line.startswith('rename from '):
317 317 gp.op = 'RENAME'
318 318 gp.oldpath = line[12:]
319 319 elif line.startswith('rename to '):
320 320 gp.path = line[10:]
321 321 elif line.startswith('copy from '):
322 322 gp.op = 'COPY'
323 323 gp.oldpath = line[10:]
324 324 elif line.startswith('copy to '):
325 325 gp.path = line[8:]
326 326 elif line.startswith('deleted file'):
327 327 gp.op = 'DELETE'
328 328 elif line.startswith('new file mode '):
329 329 gp.op = 'ADD'
330 330 gp.setmode(int(line[-6:], 8))
331 331 elif line.startswith('new mode '):
332 332 gp.setmode(int(line[-6:], 8))
333 333 elif line.startswith('GIT binary patch'):
334 334 gp.binary = True
335 335 if gp:
336 336 gitpatches.append(gp)
337 337
338 338 return gitpatches
339 339
340 340 class linereader(object):
341 341 # simple class to allow pushing lines back into the input stream
342 342 def __init__(self, fp):
343 343 self.fp = fp
344 344 self.buf = []
345 345
346 346 def push(self, line):
347 347 if line is not None:
348 348 self.buf.append(line)
349 349
350 350 def readline(self):
351 351 if self.buf:
352 352 l = self.buf[0]
353 353 del self.buf[0]
354 354 return l
355 355 return self.fp.readline()
356 356
357 357 def __iter__(self):
358 358 while True:
359 359 l = self.readline()
360 360 if not l:
361 361 break
362 362 yield l
363 363
364 364 class abstractbackend(object):
365 365 def __init__(self, ui):
366 366 self.ui = ui
367 367
368 368 def getfile(self, fname):
369 369 """Return target file data and flags as a (data, (islink,
370 370 isexec)) tuple.
371 371 """
372 372 raise NotImplementedError
373 373
374 374 def setfile(self, fname, data, mode, copysource):
375 375 """Write data to target file fname and set its mode. mode is a
376 376 (islink, isexec) tuple. If data is None, the file content should
377 377 be left unchanged. If the file is modified after being copied,
378 378 copysource is set to the original file name.
379 379 """
380 380 raise NotImplementedError
381 381
382 382 def unlink(self, fname):
383 383 """Unlink target file."""
384 384 raise NotImplementedError
385 385
386 386 def writerej(self, fname, failed, total, lines):
387 387 """Write rejected lines for fname. total is the number of hunks
388 388 which failed to apply and total the total number of hunks for this
389 389 files.
390 390 """
391 391 pass
392 392
393 393 def exists(self, fname):
394 394 raise NotImplementedError
395 395
396 396 class fsbackend(abstractbackend):
397 397 def __init__(self, ui, basedir):
398 398 super(fsbackend, self).__init__(ui)
399 399 self.opener = scmutil.opener(basedir)
400 400
401 401 def _join(self, f):
402 402 return os.path.join(self.opener.base, f)
403 403
404 404 def getfile(self, fname):
405 405 path = self._join(fname)
406 406 if os.path.islink(path):
407 407 return (os.readlink(path), (True, False))
408 408 isexec = False
409 409 try:
410 410 isexec = os.lstat(path).st_mode & 0100 != 0
411 411 except OSError, e:
412 412 if e.errno != errno.ENOENT:
413 413 raise
414 414 return (self.opener.read(fname), (False, isexec))
415 415
416 416 def setfile(self, fname, data, mode, copysource):
417 417 islink, isexec = mode
418 418 if data is None:
419 419 util.setflags(self._join(fname), islink, isexec)
420 420 return
421 421 if islink:
422 422 self.opener.symlink(data, fname)
423 423 else:
424 424 self.opener.write(fname, data)
425 425 if isexec:
426 426 util.setflags(self._join(fname), False, True)
427 427
428 428 def unlink(self, fname):
429 429 try:
430 430 util.unlinkpath(self._join(fname))
431 431 except OSError, inst:
432 432 if inst.errno != errno.ENOENT:
433 433 raise
434 434
435 435 def writerej(self, fname, failed, total, lines):
436 436 fname = fname + ".rej"
437 437 self.ui.warn(
438 438 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
439 439 (failed, total, fname))
440 440 fp = self.opener(fname, 'w')
441 441 fp.writelines(lines)
442 442 fp.close()
443 443
444 444 def exists(self, fname):
445 445 return os.path.lexists(self._join(fname))
446 446
447 447 class workingbackend(fsbackend):
448 448 def __init__(self, ui, repo, similarity):
449 449 super(workingbackend, self).__init__(ui, repo.root)
450 450 self.repo = repo
451 451 self.similarity = similarity
452 452 self.removed = set()
453 453 self.changed = set()
454 454 self.copied = []
455 455
456 456 def _checkknown(self, fname):
457 457 if self.repo.dirstate[fname] == '?' and self.exists(fname):
458 458 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
459 459
460 460 def setfile(self, fname, data, mode, copysource):
461 461 self._checkknown(fname)
462 462 super(workingbackend, self).setfile(fname, data, mode, copysource)
463 463 if copysource is not None:
464 464 self.copied.append((copysource, fname))
465 465 self.changed.add(fname)
466 466
467 467 def unlink(self, fname):
468 468 self._checkknown(fname)
469 469 super(workingbackend, self).unlink(fname)
470 470 self.removed.add(fname)
471 471 self.changed.add(fname)
472 472
473 473 def close(self):
474 474 wctx = self.repo[None]
475 475 addremoved = set(self.changed)
476 476 for src, dst in self.copied:
477 477 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
478 478 if self.removed:
479 479 wctx.forget(sorted(self.removed))
480 480 for f in self.removed:
481 481 if f not in self.repo.dirstate:
482 482 # File was deleted and no longer belongs to the
483 483 # dirstate, it was probably marked added then
484 484 # deleted, and should not be considered by
485 485 # addremove().
486 486 addremoved.discard(f)
487 487 if addremoved:
488 488 cwd = self.repo.getcwd()
489 489 if cwd:
490 490 addremoved = [util.pathto(self.repo.root, cwd, f)
491 491 for f in addremoved]
492 492 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
493 493 return sorted(self.changed)
494 494
495 495 class filestore(object):
496 496 def __init__(self, maxsize=None):
497 497 self.opener = None
498 498 self.files = {}
499 499 self.created = 0
500 500 self.maxsize = maxsize
501 501 if self.maxsize is None:
502 502 self.maxsize = 4*(2**20)
503 503 self.size = 0
504 504 self.data = {}
505 505
506 506 def setfile(self, fname, data, mode, copied=None):
507 507 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
508 508 self.data[fname] = (data, mode, copied)
509 509 self.size += len(data)
510 510 else:
511 511 if self.opener is None:
512 512 root = tempfile.mkdtemp(prefix='hg-patch-')
513 513 self.opener = scmutil.opener(root)
514 514 # Avoid filename issues with these simple names
515 515 fn = str(self.created)
516 516 self.opener.write(fn, data)
517 517 self.created += 1
518 518 self.files[fname] = (fn, mode, copied)
519 519
520 520 def getfile(self, fname):
521 521 if fname in self.data:
522 522 return self.data[fname]
523 523 if not self.opener or fname not in self.files:
524 524 raise IOError()
525 525 fn, mode, copied = self.files[fname]
526 526 return self.opener.read(fn), mode, copied
527 527
528 528 def close(self):
529 529 if self.opener:
530 530 shutil.rmtree(self.opener.base)
531 531
532 532 class repobackend(abstractbackend):
533 533 def __init__(self, ui, repo, ctx, store):
534 534 super(repobackend, self).__init__(ui)
535 535 self.repo = repo
536 536 self.ctx = ctx
537 537 self.store = store
538 538 self.changed = set()
539 539 self.removed = set()
540 540 self.copied = {}
541 541
542 542 def _checkknown(self, fname):
543 543 if fname not in self.ctx:
544 544 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
545 545
546 546 def getfile(self, fname):
547 547 try:
548 548 fctx = self.ctx[fname]
549 549 except error.LookupError:
550 550 raise IOError()
551 551 flags = fctx.flags()
552 552 return fctx.data(), ('l' in flags, 'x' in flags)
553 553
554 554 def setfile(self, fname, data, mode, copysource):
555 555 if copysource:
556 556 self._checkknown(copysource)
557 557 if data is None:
558 558 data = self.ctx[fname].data()
559 559 self.store.setfile(fname, data, mode, copysource)
560 560 self.changed.add(fname)
561 561 if copysource:
562 562 self.copied[fname] = copysource
563 563
564 564 def unlink(self, fname):
565 565 self._checkknown(fname)
566 566 self.removed.add(fname)
567 567
568 568 def exists(self, fname):
569 569 return fname in self.ctx
570 570
571 571 def close(self):
572 572 return self.changed | self.removed
573 573
574 574 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
575 575 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
576 576 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
577 577 eolmodes = ['strict', 'crlf', 'lf', 'auto']
578 578
579 579 class patchfile(object):
580 580 def __init__(self, ui, gp, backend, store, eolmode='strict'):
581 581 self.fname = gp.path
582 582 self.eolmode = eolmode
583 583 self.eol = None
584 584 self.backend = backend
585 585 self.ui = ui
586 586 self.lines = []
587 587 self.exists = False
588 588 self.missing = True
589 589 self.mode = gp.mode
590 590 self.copysource = gp.oldpath
591 591 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
592 592 self.remove = gp.op == 'DELETE'
593 593 try:
594 594 if self.copysource is None:
595 595 data, mode = backend.getfile(self.fname)
596 596 self.exists = True
597 597 else:
598 598 data, mode = store.getfile(self.copysource)[:2]
599 599 self.exists = backend.exists(self.fname)
600 600 self.missing = False
601 601 if data:
602 602 self.lines = mdiff.splitnewlines(data)
603 603 if self.mode is None:
604 604 self.mode = mode
605 605 if self.lines:
606 606 # Normalize line endings
607 607 if self.lines[0].endswith('\r\n'):
608 608 self.eol = '\r\n'
609 609 elif self.lines[0].endswith('\n'):
610 610 self.eol = '\n'
611 611 if eolmode != 'strict':
612 612 nlines = []
613 613 for l in self.lines:
614 614 if l.endswith('\r\n'):
615 615 l = l[:-2] + '\n'
616 616 nlines.append(l)
617 617 self.lines = nlines
618 618 except IOError:
619 619 if self.create:
620 620 self.missing = False
621 621 if self.mode is None:
622 622 self.mode = (False, False)
623 623 if self.missing:
624 624 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
625 625
626 626 self.hash = {}
627 627 self.dirty = 0
628 628 self.offset = 0
629 629 self.skew = 0
630 630 self.rej = []
631 631 self.fileprinted = False
632 632 self.printfile(False)
633 633 self.hunks = 0
634 634
635 635 def writelines(self, fname, lines, mode):
636 636 if self.eolmode == 'auto':
637 637 eol = self.eol
638 638 elif self.eolmode == 'crlf':
639 639 eol = '\r\n'
640 640 else:
641 641 eol = '\n'
642 642
643 643 if self.eolmode != 'strict' and eol and eol != '\n':
644 644 rawlines = []
645 645 for l in lines:
646 646 if l and l[-1] == '\n':
647 647 l = l[:-1] + eol
648 648 rawlines.append(l)
649 649 lines = rawlines
650 650
651 651 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
652 652
653 653 def printfile(self, warn):
654 654 if self.fileprinted:
655 655 return
656 656 if warn or self.ui.verbose:
657 657 self.fileprinted = True
658 658 s = _("patching file %s\n") % self.fname
659 659 if warn:
660 660 self.ui.warn(s)
661 661 else:
662 662 self.ui.note(s)
663 663
664 664
665 665 def findlines(self, l, linenum):
666 666 # looks through the hash and finds candidate lines. The
667 667 # result is a list of line numbers sorted based on distance
668 668 # from linenum
669 669
670 670 cand = self.hash.get(l, [])
671 671 if len(cand) > 1:
672 672 # resort our list of potentials forward then back.
673 673 cand.sort(key=lambda x: abs(x - linenum))
674 674 return cand
675 675
676 676 def write_rej(self):
677 677 # our rejects are a little different from patch(1). This always
678 678 # creates rejects in the same form as the original patch. A file
679 679 # header is inserted so that you can run the reject through patch again
680 680 # without having to type the filename.
681 681 if not self.rej:
682 682 return
683 683 base = os.path.basename(self.fname)
684 684 lines = ["--- %s\n+++ %s\n" % (base, base)]
685 685 for x in self.rej:
686 686 for l in x.hunk:
687 687 lines.append(l)
688 688 if l[-1] != '\n':
689 689 lines.append("\n\ No newline at end of file\n")
690 690 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
691 691
692 692 def apply(self, h):
693 693 if not h.complete():
694 694 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
695 695 (h.number, h.desc, len(h.a), h.lena, len(h.b),
696 696 h.lenb))
697 697
698 698 self.hunks += 1
699 699
700 700 if self.missing:
701 701 self.rej.append(h)
702 702 return -1
703 703
704 704 if self.exists and self.create:
705 705 if self.copysource:
706 706 self.ui.warn(_("cannot create %s: destination already "
707 707 "exists\n" % self.fname))
708 708 else:
709 709 self.ui.warn(_("file %s already exists\n") % self.fname)
710 710 self.rej.append(h)
711 711 return -1
712 712
713 713 if isinstance(h, binhunk):
714 714 if self.remove:
715 715 self.backend.unlink(self.fname)
716 716 else:
717 717 self.lines[:] = h.new()
718 718 self.offset += len(h.new())
719 719 self.dirty = True
720 720 return 0
721 721
722 722 horig = h
723 723 if (self.eolmode in ('crlf', 'lf')
724 724 or self.eolmode == 'auto' and self.eol):
725 725 # If new eols are going to be normalized, then normalize
726 726 # hunk data before patching. Otherwise, preserve input
727 727 # line-endings.
728 728 h = h.getnormalized()
729 729
730 730 # fast case first, no offsets, no fuzz
731 731 old, oldstart, new, newstart = h.fuzzit(0, False)
732 732 oldstart += self.offset
733 733 orig_start = oldstart
734 734 # if there's skew we want to emit the "(offset %d lines)" even
735 735 # when the hunk cleanly applies at start + skew, so skip the
736 736 # fast case code
737 737 if (self.skew == 0 and
738 738 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
739 739 if self.remove:
740 740 self.backend.unlink(self.fname)
741 741 else:
742 742 self.lines[oldstart:oldstart + len(old)] = new
743 743 self.offset += len(new) - len(old)
744 744 self.dirty = True
745 745 return 0
746 746
747 747 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
748 748 self.hash = {}
749 749 for x, s in enumerate(self.lines):
750 750 self.hash.setdefault(s, []).append(x)
751 751
752 752 for fuzzlen in xrange(3):
753 753 for toponly in [True, False]:
754 754 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
755 755 oldstart = oldstart + self.offset + self.skew
756 756 oldstart = min(oldstart, len(self.lines))
757 757 if old:
758 758 cand = self.findlines(old[0][1:], oldstart)
759 759 else:
760 760 # Only adding lines with no or fuzzed context, just
761 761 # take the skew in account
762 762 cand = [oldstart]
763 763
764 764 for l in cand:
765 765 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
766 766 self.lines[l : l + len(old)] = new
767 767 self.offset += len(new) - len(old)
768 768 self.skew = l - orig_start
769 769 self.dirty = True
770 770 offset = l - orig_start - fuzzlen
771 771 if fuzzlen:
772 772 msg = _("Hunk #%d succeeded at %d "
773 773 "with fuzz %d "
774 774 "(offset %d lines).\n")
775 775 self.printfile(True)
776 776 self.ui.warn(msg %
777 777 (h.number, l + 1, fuzzlen, offset))
778 778 else:
779 779 msg = _("Hunk #%d succeeded at %d "
780 780 "(offset %d lines).\n")
781 781 self.ui.note(msg % (h.number, l + 1, offset))
782 782 return fuzzlen
783 783 self.printfile(True)
784 784 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
785 785 self.rej.append(horig)
786 786 return -1
787 787
788 788 def close(self):
789 789 if self.dirty:
790 790 self.writelines(self.fname, self.lines, self.mode)
791 791 self.write_rej()
792 792 return len(self.rej)
793 793
794 794 class hunk(object):
795 795 def __init__(self, desc, num, lr, context):
796 796 self.number = num
797 797 self.desc = desc
798 798 self.hunk = [desc]
799 799 self.a = []
800 800 self.b = []
801 801 self.starta = self.lena = None
802 802 self.startb = self.lenb = None
803 803 if lr is not None:
804 804 if context:
805 805 self.read_context_hunk(lr)
806 806 else:
807 807 self.read_unified_hunk(lr)
808 808
809 809 def getnormalized(self):
810 810 """Return a copy with line endings normalized to LF."""
811 811
812 812 def normalize(lines):
813 813 nlines = []
814 814 for line in lines:
815 815 if line.endswith('\r\n'):
816 816 line = line[:-2] + '\n'
817 817 nlines.append(line)
818 818 return nlines
819 819
820 820 # Dummy object, it is rebuilt manually
821 821 nh = hunk(self.desc, self.number, None, None)
822 822 nh.number = self.number
823 823 nh.desc = self.desc
824 824 nh.hunk = self.hunk
825 825 nh.a = normalize(self.a)
826 826 nh.b = normalize(self.b)
827 827 nh.starta = self.starta
828 828 nh.startb = self.startb
829 829 nh.lena = self.lena
830 830 nh.lenb = self.lenb
831 831 return nh
832 832
833 833 def read_unified_hunk(self, lr):
834 834 m = unidesc.match(self.desc)
835 835 if not m:
836 836 raise PatchError(_("bad hunk #%d") % self.number)
837 837 self.starta, self.lena, self.startb, self.lenb = m.groups()
838 838 if self.lena is None:
839 839 self.lena = 1
840 840 else:
841 841 self.lena = int(self.lena)
842 842 if self.lenb is None:
843 843 self.lenb = 1
844 844 else:
845 845 self.lenb = int(self.lenb)
846 846 self.starta = int(self.starta)
847 847 self.startb = int(self.startb)
848 848 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
849 849 # if we hit eof before finishing out the hunk, the last line will
850 850 # be zero length. Lets try to fix it up.
851 851 while len(self.hunk[-1]) == 0:
852 852 del self.hunk[-1]
853 853 del self.a[-1]
854 854 del self.b[-1]
855 855 self.lena -= 1
856 856 self.lenb -= 1
857 857 self._fixnewline(lr)
858 858
859 859 def read_context_hunk(self, lr):
860 860 self.desc = lr.readline()
861 861 m = contextdesc.match(self.desc)
862 862 if not m:
863 863 raise PatchError(_("bad hunk #%d") % self.number)
864 864 self.starta, aend = m.groups()
865 865 self.starta = int(self.starta)
866 866 if aend is None:
867 867 aend = self.starta
868 868 self.lena = int(aend) - self.starta
869 869 if self.starta:
870 870 self.lena += 1
871 871 for x in xrange(self.lena):
872 872 l = lr.readline()
873 873 if l.startswith('---'):
874 874 # lines addition, old block is empty
875 875 lr.push(l)
876 876 break
877 877 s = l[2:]
878 878 if l.startswith('- ') or l.startswith('! '):
879 879 u = '-' + s
880 880 elif l.startswith(' '):
881 881 u = ' ' + s
882 882 else:
883 883 raise PatchError(_("bad hunk #%d old text line %d") %
884 884 (self.number, x))
885 885 self.a.append(u)
886 886 self.hunk.append(u)
887 887
888 888 l = lr.readline()
889 889 if l.startswith('\ '):
890 890 s = self.a[-1][:-1]
891 891 self.a[-1] = s
892 892 self.hunk[-1] = s
893 893 l = lr.readline()
894 894 m = contextdesc.match(l)
895 895 if not m:
896 896 raise PatchError(_("bad hunk #%d") % self.number)
897 897 self.startb, bend = m.groups()
898 898 self.startb = int(self.startb)
899 899 if bend is None:
900 900 bend = self.startb
901 901 self.lenb = int(bend) - self.startb
902 902 if self.startb:
903 903 self.lenb += 1
904 904 hunki = 1
905 905 for x in xrange(self.lenb):
906 906 l = lr.readline()
907 907 if l.startswith('\ '):
908 908 # XXX: the only way to hit this is with an invalid line range.
909 909 # The no-eol marker is not counted in the line range, but I
910 910 # guess there are diff(1) out there which behave differently.
911 911 s = self.b[-1][:-1]
912 912 self.b[-1] = s
913 913 self.hunk[hunki - 1] = s
914 914 continue
915 915 if not l:
916 916 # line deletions, new block is empty and we hit EOF
917 917 lr.push(l)
918 918 break
919 919 s = l[2:]
920 920 if l.startswith('+ ') or l.startswith('! '):
921 921 u = '+' + s
922 922 elif l.startswith(' '):
923 923 u = ' ' + s
924 924 elif len(self.b) == 0:
925 925 # line deletions, new block is empty
926 926 lr.push(l)
927 927 break
928 928 else:
929 929 raise PatchError(_("bad hunk #%d old text line %d") %
930 930 (self.number, x))
931 931 self.b.append(s)
932 932 while True:
933 933 if hunki >= len(self.hunk):
934 934 h = ""
935 935 else:
936 936 h = self.hunk[hunki]
937 937 hunki += 1
938 938 if h == u:
939 939 break
940 940 elif h.startswith('-'):
941 941 continue
942 942 else:
943 943 self.hunk.insert(hunki - 1, u)
944 944 break
945 945
946 946 if not self.a:
947 947 # this happens when lines were only added to the hunk
948 948 for x in self.hunk:
949 949 if x.startswith('-') or x.startswith(' '):
950 950 self.a.append(x)
951 951 if not self.b:
952 952 # this happens when lines were only deleted from the hunk
953 953 for x in self.hunk:
954 954 if x.startswith('+') or x.startswith(' '):
955 955 self.b.append(x[1:])
956 956 # @@ -start,len +start,len @@
957 957 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
958 958 self.startb, self.lenb)
959 959 self.hunk[0] = self.desc
960 960 self._fixnewline(lr)
961 961
962 962 def _fixnewline(self, lr):
963 963 l = lr.readline()
964 964 if l.startswith('\ '):
965 965 diffhelpers.fix_newline(self.hunk, self.a, self.b)
966 966 else:
967 967 lr.push(l)
968 968
969 969 def complete(self):
970 970 return len(self.a) == self.lena and len(self.b) == self.lenb
971 971
972 972 def _fuzzit(self, old, new, fuzz, toponly):
973 973 # this removes context lines from the top and bottom of list 'l'. It
974 974 # checks the hunk to make sure only context lines are removed, and then
975 975 # returns a new shortened list of lines.
976 fuzz = min(fuzz, len(old)-1)
976 fuzz = min(fuzz, len(old))
977 977 if fuzz:
978 978 top = 0
979 979 bot = 0
980 980 hlen = len(self.hunk)
981 981 for x in xrange(hlen - 1):
982 982 # the hunk starts with the @@ line, so use x+1
983 983 if self.hunk[x + 1][0] == ' ':
984 984 top += 1
985 985 else:
986 986 break
987 987 if not toponly:
988 988 for x in xrange(hlen - 1):
989 989 if self.hunk[hlen - bot - 1][0] == ' ':
990 990 bot += 1
991 991 else:
992 992 break
993 993
994 # top and bot now count context in the hunk
995 # adjust them if either one is short
996 context = max(top, bot, 3)
997 if bot < context:
998 bot = max(0, fuzz - (context - bot))
999 else:
1000 994 bot = min(fuzz, bot)
1001 if top < context:
1002 top = max(0, fuzz - (context - top))
1003 else:
1004 995 top = min(fuzz, top)
1005
1006 996 return old[top:len(old)-bot], new[top:len(new)-bot], top
1007 997 return old, new, 0
1008 998
1009 999 def fuzzit(self, fuzz, toponly):
1010 1000 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1011 1001 oldstart = self.starta + top
1012 1002 newstart = self.startb + top
1013 1003 # zero length hunk ranges already have their start decremented
1014 1004 if self.lena:
1015 1005 oldstart -= 1
1016 1006 if self.lenb:
1017 1007 newstart -= 1
1018 1008 return old, oldstart, new, newstart
1019 1009
1020 1010 class binhunk(object):
1021 1011 'A binary patch file. Only understands literals so far.'
1022 1012 def __init__(self, lr):
1023 1013 self.text = None
1024 1014 self.hunk = ['GIT binary patch\n']
1025 1015 self._read(lr)
1026 1016
1027 1017 def complete(self):
1028 1018 return self.text is not None
1029 1019
1030 1020 def new(self):
1031 1021 return [self.text]
1032 1022
1033 1023 def _read(self, lr):
1034 1024 line = lr.readline()
1035 1025 self.hunk.append(line)
1036 1026 while line and not line.startswith('literal '):
1037 1027 line = lr.readline()
1038 1028 self.hunk.append(line)
1039 1029 if not line:
1040 1030 raise PatchError(_('could not extract binary patch'))
1041 1031 size = int(line[8:].rstrip())
1042 1032 dec = []
1043 1033 line = lr.readline()
1044 1034 self.hunk.append(line)
1045 1035 while len(line) > 1:
1046 1036 l = line[0]
1047 1037 if l <= 'Z' and l >= 'A':
1048 1038 l = ord(l) - ord('A') + 1
1049 1039 else:
1050 1040 l = ord(l) - ord('a') + 27
1051 1041 dec.append(base85.b85decode(line[1:-1])[:l])
1052 1042 line = lr.readline()
1053 1043 self.hunk.append(line)
1054 1044 text = zlib.decompress(''.join(dec))
1055 1045 if len(text) != size:
1056 1046 raise PatchError(_('binary patch is %d bytes, not %d') %
1057 1047 len(text), size)
1058 1048 self.text = text
1059 1049
1060 1050 def parsefilename(str):
1061 1051 # --- filename \t|space stuff
1062 1052 s = str[4:].rstrip('\r\n')
1063 1053 i = s.find('\t')
1064 1054 if i < 0:
1065 1055 i = s.find(' ')
1066 1056 if i < 0:
1067 1057 return s
1068 1058 return s[:i]
1069 1059
1070 1060 def pathstrip(path, strip):
1071 1061 pathlen = len(path)
1072 1062 i = 0
1073 1063 if strip == 0:
1074 1064 return '', path.rstrip()
1075 1065 count = strip
1076 1066 while count > 0:
1077 1067 i = path.find('/', i)
1078 1068 if i == -1:
1079 1069 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1080 1070 (count, strip, path))
1081 1071 i += 1
1082 1072 # consume '//' in the path
1083 1073 while i < pathlen - 1 and path[i] == '/':
1084 1074 i += 1
1085 1075 count -= 1
1086 1076 return path[:i].lstrip(), path[i:].rstrip()
1087 1077
1088 1078 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1089 1079 nulla = afile_orig == "/dev/null"
1090 1080 nullb = bfile_orig == "/dev/null"
1091 1081 create = nulla and hunk.starta == 0 and hunk.lena == 0
1092 1082 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1093 1083 abase, afile = pathstrip(afile_orig, strip)
1094 1084 gooda = not nulla and backend.exists(afile)
1095 1085 bbase, bfile = pathstrip(bfile_orig, strip)
1096 1086 if afile == bfile:
1097 1087 goodb = gooda
1098 1088 else:
1099 1089 goodb = not nullb and backend.exists(bfile)
1100 1090 missing = not goodb and not gooda and not create
1101 1091
1102 1092 # some diff programs apparently produce patches where the afile is
1103 1093 # not /dev/null, but afile starts with bfile
1104 1094 abasedir = afile[:afile.rfind('/') + 1]
1105 1095 bbasedir = bfile[:bfile.rfind('/') + 1]
1106 1096 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1107 1097 and hunk.starta == 0 and hunk.lena == 0):
1108 1098 create = True
1109 1099 missing = False
1110 1100
1111 1101 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1112 1102 # diff is between a file and its backup. In this case, the original
1113 1103 # file should be patched (see original mpatch code).
1114 1104 isbackup = (abase == bbase and bfile.startswith(afile))
1115 1105 fname = None
1116 1106 if not missing:
1117 1107 if gooda and goodb:
1118 1108 fname = isbackup and afile or bfile
1119 1109 elif gooda:
1120 1110 fname = afile
1121 1111
1122 1112 if not fname:
1123 1113 if not nullb:
1124 1114 fname = isbackup and afile or bfile
1125 1115 elif not nulla:
1126 1116 fname = afile
1127 1117 else:
1128 1118 raise PatchError(_("undefined source and destination files"))
1129 1119
1130 1120 gp = patchmeta(fname)
1131 1121 if create:
1132 1122 gp.op = 'ADD'
1133 1123 elif remove:
1134 1124 gp.op = 'DELETE'
1135 1125 return gp
1136 1126
1137 1127 def scangitpatch(lr, firstline):
1138 1128 """
1139 1129 Git patches can emit:
1140 1130 - rename a to b
1141 1131 - change b
1142 1132 - copy a to c
1143 1133 - change c
1144 1134
1145 1135 We cannot apply this sequence as-is, the renamed 'a' could not be
1146 1136 found for it would have been renamed already. And we cannot copy
1147 1137 from 'b' instead because 'b' would have been changed already. So
1148 1138 we scan the git patch for copy and rename commands so we can
1149 1139 perform the copies ahead of time.
1150 1140 """
1151 1141 pos = 0
1152 1142 try:
1153 1143 pos = lr.fp.tell()
1154 1144 fp = lr.fp
1155 1145 except IOError:
1156 1146 fp = cStringIO.StringIO(lr.fp.read())
1157 1147 gitlr = linereader(fp)
1158 1148 gitlr.push(firstline)
1159 1149 gitpatches = readgitpatch(gitlr)
1160 1150 fp.seek(pos)
1161 1151 return gitpatches
1162 1152
1163 1153 def iterhunks(fp):
1164 1154 """Read a patch and yield the following events:
1165 1155 - ("file", afile, bfile, firsthunk): select a new target file.
1166 1156 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1167 1157 "file" event.
1168 1158 - ("git", gitchanges): current diff is in git format, gitchanges
1169 1159 maps filenames to gitpatch records. Unique event.
1170 1160 """
1171 1161 afile = ""
1172 1162 bfile = ""
1173 1163 state = None
1174 1164 hunknum = 0
1175 1165 emitfile = newfile = False
1176 1166 gitpatches = None
1177 1167
1178 1168 # our states
1179 1169 BFILE = 1
1180 1170 context = None
1181 1171 lr = linereader(fp)
1182 1172
1183 1173 while True:
1184 1174 x = lr.readline()
1185 1175 if not x:
1186 1176 break
1187 1177 if state == BFILE and (
1188 1178 (not context and x[0] == '@')
1189 1179 or (context is not False and x.startswith('***************'))
1190 1180 or x.startswith('GIT binary patch')):
1191 1181 gp = None
1192 1182 if (gitpatches and
1193 1183 (gitpatches[-1][0] == afile or gitpatches[-1][1] == bfile)):
1194 1184 gp = gitpatches.pop()[2]
1195 1185 if x.startswith('GIT binary patch'):
1196 1186 h = binhunk(lr)
1197 1187 else:
1198 1188 if context is None and x.startswith('***************'):
1199 1189 context = True
1200 1190 h = hunk(x, hunknum + 1, lr, context)
1201 1191 hunknum += 1
1202 1192 if emitfile:
1203 1193 emitfile = False
1204 1194 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1205 1195 yield 'hunk', h
1206 1196 elif x.startswith('diff --git'):
1207 1197 m = gitre.match(x)
1208 1198 if not m:
1209 1199 continue
1210 1200 if not gitpatches:
1211 1201 # scan whole input for git metadata
1212 1202 gitpatches = [('a/' + gp.path, 'b/' + gp.path, gp) for gp
1213 1203 in scangitpatch(lr, x)]
1214 1204 yield 'git', [g[2].copy() for g in gitpatches
1215 1205 if g[2].op in ('COPY', 'RENAME')]
1216 1206 gitpatches.reverse()
1217 1207 afile = 'a/' + m.group(1)
1218 1208 bfile = 'b/' + m.group(2)
1219 1209 while afile != gitpatches[-1][0] and bfile != gitpatches[-1][1]:
1220 1210 gp = gitpatches.pop()[2]
1221 1211 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1222 1212 gp = gitpatches[-1][2]
1223 1213 # copy/rename + modify should modify target, not source
1224 1214 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode:
1225 1215 afile = bfile
1226 1216 newfile = True
1227 1217 elif x.startswith('---'):
1228 1218 # check for a unified diff
1229 1219 l2 = lr.readline()
1230 1220 if not l2.startswith('+++'):
1231 1221 lr.push(l2)
1232 1222 continue
1233 1223 newfile = True
1234 1224 context = False
1235 1225 afile = parsefilename(x)
1236 1226 bfile = parsefilename(l2)
1237 1227 elif x.startswith('***'):
1238 1228 # check for a context diff
1239 1229 l2 = lr.readline()
1240 1230 if not l2.startswith('---'):
1241 1231 lr.push(l2)
1242 1232 continue
1243 1233 l3 = lr.readline()
1244 1234 lr.push(l3)
1245 1235 if not l3.startswith("***************"):
1246 1236 lr.push(l2)
1247 1237 continue
1248 1238 newfile = True
1249 1239 context = True
1250 1240 afile = parsefilename(x)
1251 1241 bfile = parsefilename(l2)
1252 1242
1253 1243 if newfile:
1254 1244 newfile = False
1255 1245 emitfile = True
1256 1246 state = BFILE
1257 1247 hunknum = 0
1258 1248
1259 1249 while gitpatches:
1260 1250 gp = gitpatches.pop()[2]
1261 1251 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1262 1252
1263 1253 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1264 1254 """Reads a patch from fp and tries to apply it.
1265 1255
1266 1256 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1267 1257 there was any fuzz.
1268 1258
1269 1259 If 'eolmode' is 'strict', the patch content and patched file are
1270 1260 read in binary mode. Otherwise, line endings are ignored when
1271 1261 patching then normalized according to 'eolmode'.
1272 1262 """
1273 1263 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1274 1264 eolmode=eolmode)
1275 1265
1276 1266 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1277 1267 eolmode='strict'):
1278 1268
1279 1269 def pstrip(p):
1280 1270 return pathstrip(p, strip - 1)[1]
1281 1271
1282 1272 rejects = 0
1283 1273 err = 0
1284 1274 current_file = None
1285 1275
1286 1276 for state, values in iterhunks(fp):
1287 1277 if state == 'hunk':
1288 1278 if not current_file:
1289 1279 continue
1290 1280 ret = current_file.apply(values)
1291 1281 if ret > 0:
1292 1282 err = 1
1293 1283 elif state == 'file':
1294 1284 if current_file:
1295 1285 rejects += current_file.close()
1296 1286 current_file = None
1297 1287 afile, bfile, first_hunk, gp = values
1298 1288 if gp:
1299 1289 path = pstrip(gp.path)
1300 1290 gp.path = pstrip(gp.path)
1301 1291 if gp.oldpath:
1302 1292 gp.oldpath = pstrip(gp.oldpath)
1303 1293 else:
1304 1294 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1305 1295 if gp.op == 'RENAME':
1306 1296 backend.unlink(gp.oldpath)
1307 1297 if not first_hunk:
1308 1298 if gp.op == 'DELETE':
1309 1299 backend.unlink(gp.path)
1310 1300 continue
1311 1301 data, mode = None, None
1312 1302 if gp.op in ('RENAME', 'COPY'):
1313 1303 data, mode = store.getfile(gp.oldpath)[:2]
1314 1304 if gp.mode:
1315 1305 mode = gp.mode
1316 1306 if gp.op == 'ADD':
1317 1307 # Added files without content have no hunk and
1318 1308 # must be created
1319 1309 data = ''
1320 1310 if data or mode:
1321 1311 if (gp.op in ('ADD', 'RENAME', 'COPY')
1322 1312 and backend.exists(gp.path)):
1323 1313 raise PatchError(_("cannot create %s: destination "
1324 1314 "already exists") % gp.path)
1325 1315 backend.setfile(gp.path, data, mode, gp.oldpath)
1326 1316 continue
1327 1317 try:
1328 1318 current_file = patcher(ui, gp, backend, store,
1329 1319 eolmode=eolmode)
1330 1320 except PatchError, inst:
1331 1321 ui.warn(str(inst) + '\n')
1332 1322 current_file = None
1333 1323 rejects += 1
1334 1324 continue
1335 1325 elif state == 'git':
1336 1326 for gp in values:
1337 1327 path = pstrip(gp.oldpath)
1338 1328 data, mode = backend.getfile(path)
1339 1329 store.setfile(path, data, mode)
1340 1330 else:
1341 1331 raise util.Abort(_('unsupported parser state: %s') % state)
1342 1332
1343 1333 if current_file:
1344 1334 rejects += current_file.close()
1345 1335
1346 1336 if rejects:
1347 1337 return -1
1348 1338 return err
1349 1339
1350 1340 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1351 1341 similarity):
1352 1342 """use <patcher> to apply <patchname> to the working directory.
1353 1343 returns whether patch was applied with fuzz factor."""
1354 1344
1355 1345 fuzz = False
1356 1346 args = []
1357 1347 cwd = repo.root
1358 1348 if cwd:
1359 1349 args.append('-d %s' % util.shellquote(cwd))
1360 1350 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1361 1351 util.shellquote(patchname)))
1362 1352 try:
1363 1353 for line in fp:
1364 1354 line = line.rstrip()
1365 1355 ui.note(line + '\n')
1366 1356 if line.startswith('patching file '):
1367 1357 pf = util.parsepatchoutput(line)
1368 1358 printed_file = False
1369 1359 files.add(pf)
1370 1360 elif line.find('with fuzz') >= 0:
1371 1361 fuzz = True
1372 1362 if not printed_file:
1373 1363 ui.warn(pf + '\n')
1374 1364 printed_file = True
1375 1365 ui.warn(line + '\n')
1376 1366 elif line.find('saving rejects to file') >= 0:
1377 1367 ui.warn(line + '\n')
1378 1368 elif line.find('FAILED') >= 0:
1379 1369 if not printed_file:
1380 1370 ui.warn(pf + '\n')
1381 1371 printed_file = True
1382 1372 ui.warn(line + '\n')
1383 1373 finally:
1384 1374 if files:
1385 1375 cfiles = list(files)
1386 1376 cwd = repo.getcwd()
1387 1377 if cwd:
1388 1378 cfiles = [util.pathto(repo.root, cwd, f)
1389 1379 for f in cfiles]
1390 1380 scmutil.addremove(repo, cfiles, similarity=similarity)
1391 1381 code = fp.close()
1392 1382 if code:
1393 1383 raise PatchError(_("patch command failed: %s") %
1394 1384 util.explainexit(code)[0])
1395 1385 return fuzz
1396 1386
1397 1387 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1398 1388 if files is None:
1399 1389 files = set()
1400 1390 if eolmode is None:
1401 1391 eolmode = ui.config('patch', 'eol', 'strict')
1402 1392 if eolmode.lower() not in eolmodes:
1403 1393 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1404 1394 eolmode = eolmode.lower()
1405 1395
1406 1396 store = filestore()
1407 1397 try:
1408 1398 fp = open(patchobj, 'rb')
1409 1399 except TypeError:
1410 1400 fp = patchobj
1411 1401 try:
1412 1402 ret = applydiff(ui, fp, backend, store, strip=strip,
1413 1403 eolmode=eolmode)
1414 1404 finally:
1415 1405 if fp != patchobj:
1416 1406 fp.close()
1417 1407 files.update(backend.close())
1418 1408 store.close()
1419 1409 if ret < 0:
1420 1410 raise PatchError(_('patch failed to apply'))
1421 1411 return ret > 0
1422 1412
1423 1413 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1424 1414 similarity=0):
1425 1415 """use builtin patch to apply <patchobj> to the working directory.
1426 1416 returns whether patch was applied with fuzz factor."""
1427 1417 backend = workingbackend(ui, repo, similarity)
1428 1418 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1429 1419
1430 1420 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1431 1421 eolmode='strict'):
1432 1422 backend = repobackend(ui, repo, ctx, store)
1433 1423 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1434 1424
1435 1425 def makememctx(repo, parents, text, user, date, branch, files, store,
1436 1426 editor=None):
1437 1427 def getfilectx(repo, memctx, path):
1438 1428 data, (islink, isexec), copied = store.getfile(path)
1439 1429 return context.memfilectx(path, data, islink=islink, isexec=isexec,
1440 1430 copied=copied)
1441 1431 extra = {}
1442 1432 if branch:
1443 1433 extra['branch'] = encoding.fromlocal(branch)
1444 1434 ctx = context.memctx(repo, parents, text, files, getfilectx, user,
1445 1435 date, extra)
1446 1436 if editor:
1447 1437 ctx._text = editor(repo, ctx, [])
1448 1438 return ctx
1449 1439
1450 1440 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1451 1441 similarity=0):
1452 1442 """Apply <patchname> to the working directory.
1453 1443
1454 1444 'eolmode' specifies how end of lines should be handled. It can be:
1455 1445 - 'strict': inputs are read in binary mode, EOLs are preserved
1456 1446 - 'crlf': EOLs are ignored when patching and reset to CRLF
1457 1447 - 'lf': EOLs are ignored when patching and reset to LF
1458 1448 - None: get it from user settings, default to 'strict'
1459 1449 'eolmode' is ignored when using an external patcher program.
1460 1450
1461 1451 Returns whether patch was applied with fuzz factor.
1462 1452 """
1463 1453 patcher = ui.config('ui', 'patch')
1464 1454 if files is None:
1465 1455 files = set()
1466 1456 try:
1467 1457 if patcher:
1468 1458 return _externalpatch(ui, repo, patcher, patchname, strip,
1469 1459 files, similarity)
1470 1460 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1471 1461 similarity)
1472 1462 except PatchError, err:
1473 1463 raise util.Abort(str(err))
1474 1464
1475 1465 def changedfiles(ui, repo, patchpath, strip=1):
1476 1466 backend = fsbackend(ui, repo.root)
1477 1467 fp = open(patchpath, 'rb')
1478 1468 try:
1479 1469 changed = set()
1480 1470 for state, values in iterhunks(fp):
1481 1471 if state == 'file':
1482 1472 afile, bfile, first_hunk, gp = values
1483 1473 if gp:
1484 1474 gp.path = pathstrip(gp.path, strip - 1)[1]
1485 1475 if gp.oldpath:
1486 1476 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1487 1477 else:
1488 1478 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1489 1479 changed.add(gp.path)
1490 1480 if gp.op == 'RENAME':
1491 1481 changed.add(gp.oldpath)
1492 1482 elif state not in ('hunk', 'git'):
1493 1483 raise util.Abort(_('unsupported parser state: %s') % state)
1494 1484 return changed
1495 1485 finally:
1496 1486 fp.close()
1497 1487
1498 1488 def b85diff(to, tn):
1499 1489 '''print base85-encoded binary diff'''
1500 1490 def gitindex(text):
1501 1491 if not text:
1502 1492 return hex(nullid)
1503 1493 l = len(text)
1504 1494 s = util.sha1('blob %d\0' % l)
1505 1495 s.update(text)
1506 1496 return s.hexdigest()
1507 1497
1508 1498 def fmtline(line):
1509 1499 l = len(line)
1510 1500 if l <= 26:
1511 1501 l = chr(ord('A') + l - 1)
1512 1502 else:
1513 1503 l = chr(l - 26 + ord('a') - 1)
1514 1504 return '%c%s\n' % (l, base85.b85encode(line, True))
1515 1505
1516 1506 def chunk(text, csize=52):
1517 1507 l = len(text)
1518 1508 i = 0
1519 1509 while i < l:
1520 1510 yield text[i:i + csize]
1521 1511 i += csize
1522 1512
1523 1513 tohash = gitindex(to)
1524 1514 tnhash = gitindex(tn)
1525 1515 if tohash == tnhash:
1526 1516 return ""
1527 1517
1528 1518 # TODO: deltas
1529 1519 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1530 1520 (tohash, tnhash, len(tn))]
1531 1521 for l in chunk(zlib.compress(tn)):
1532 1522 ret.append(fmtline(l))
1533 1523 ret.append('\n')
1534 1524 return ''.join(ret)
1535 1525
1536 1526 class GitDiffRequired(Exception):
1537 1527 pass
1538 1528
1539 1529 def diffopts(ui, opts=None, untrusted=False, section='diff'):
1540 1530 def get(key, name=None, getter=ui.configbool):
1541 1531 return ((opts and opts.get(key)) or
1542 1532 getter(section, name or key, None, untrusted=untrusted))
1543 1533 return mdiff.diffopts(
1544 1534 text=opts and opts.get('text'),
1545 1535 git=get('git'),
1546 1536 nodates=get('nodates'),
1547 1537 showfunc=get('show_function', 'showfunc'),
1548 1538 ignorews=get('ignore_all_space', 'ignorews'),
1549 1539 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1550 1540 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1551 1541 context=get('unified', getter=ui.config))
1552 1542
1553 1543 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1554 1544 losedatafn=None, prefix=''):
1555 1545 '''yields diff of changes to files between two nodes, or node and
1556 1546 working directory.
1557 1547
1558 1548 if node1 is None, use first dirstate parent instead.
1559 1549 if node2 is None, compare node1 with working directory.
1560 1550
1561 1551 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1562 1552 every time some change cannot be represented with the current
1563 1553 patch format. Return False to upgrade to git patch format, True to
1564 1554 accept the loss or raise an exception to abort the diff. It is
1565 1555 called with the name of current file being diffed as 'fn'. If set
1566 1556 to None, patches will always be upgraded to git format when
1567 1557 necessary.
1568 1558
1569 1559 prefix is a filename prefix that is prepended to all filenames on
1570 1560 display (used for subrepos).
1571 1561 '''
1572 1562
1573 1563 if opts is None:
1574 1564 opts = mdiff.defaultopts
1575 1565
1576 1566 if not node1 and not node2:
1577 1567 node1 = repo.dirstate.p1()
1578 1568
1579 1569 def lrugetfilectx():
1580 1570 cache = {}
1581 1571 order = []
1582 1572 def getfilectx(f, ctx):
1583 1573 fctx = ctx.filectx(f, filelog=cache.get(f))
1584 1574 if f not in cache:
1585 1575 if len(cache) > 20:
1586 1576 del cache[order.pop(0)]
1587 1577 cache[f] = fctx.filelog()
1588 1578 else:
1589 1579 order.remove(f)
1590 1580 order.append(f)
1591 1581 return fctx
1592 1582 return getfilectx
1593 1583 getfilectx = lrugetfilectx()
1594 1584
1595 1585 ctx1 = repo[node1]
1596 1586 ctx2 = repo[node2]
1597 1587
1598 1588 if not changes:
1599 1589 changes = repo.status(ctx1, ctx2, match=match)
1600 1590 modified, added, removed = changes[:3]
1601 1591
1602 1592 if not modified and not added and not removed:
1603 1593 return []
1604 1594
1605 1595 revs = None
1606 1596 if not repo.ui.quiet:
1607 1597 hexfunc = repo.ui.debugflag and hex or short
1608 1598 revs = [hexfunc(node) for node in [node1, node2] if node]
1609 1599
1610 1600 copy = {}
1611 1601 if opts.git or opts.upgrade:
1612 1602 copy = copies.pathcopies(ctx1, ctx2)
1613 1603
1614 1604 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1615 1605 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1616 1606 if opts.upgrade and not opts.git:
1617 1607 try:
1618 1608 def losedata(fn):
1619 1609 if not losedatafn or not losedatafn(fn=fn):
1620 1610 raise GitDiffRequired()
1621 1611 # Buffer the whole output until we are sure it can be generated
1622 1612 return list(difffn(opts.copy(git=False), losedata))
1623 1613 except GitDiffRequired:
1624 1614 return difffn(opts.copy(git=True), None)
1625 1615 else:
1626 1616 return difffn(opts, None)
1627 1617
1628 1618 def difflabel(func, *args, **kw):
1629 1619 '''yields 2-tuples of (output, label) based on the output of func()'''
1630 1620 headprefixes = [('diff', 'diff.diffline'),
1631 1621 ('copy', 'diff.extended'),
1632 1622 ('rename', 'diff.extended'),
1633 1623 ('old', 'diff.extended'),
1634 1624 ('new', 'diff.extended'),
1635 1625 ('deleted', 'diff.extended'),
1636 1626 ('---', 'diff.file_a'),
1637 1627 ('+++', 'diff.file_b')]
1638 1628 textprefixes = [('@', 'diff.hunk'),
1639 1629 ('-', 'diff.deleted'),
1640 1630 ('+', 'diff.inserted')]
1641 1631 head = False
1642 1632 for chunk in func(*args, **kw):
1643 1633 lines = chunk.split('\n')
1644 1634 for i, line in enumerate(lines):
1645 1635 if i != 0:
1646 1636 yield ('\n', '')
1647 1637 if head:
1648 1638 if line.startswith('@'):
1649 1639 head = False
1650 1640 else:
1651 1641 if line and not line[0] in ' +-@\\':
1652 1642 head = True
1653 1643 stripline = line
1654 1644 if not head and line and line[0] in '+-':
1655 1645 # highlight trailing whitespace, but only in changed lines
1656 1646 stripline = line.rstrip()
1657 1647 prefixes = textprefixes
1658 1648 if head:
1659 1649 prefixes = headprefixes
1660 1650 for prefix, label in prefixes:
1661 1651 if stripline.startswith(prefix):
1662 1652 yield (stripline, label)
1663 1653 break
1664 1654 else:
1665 1655 yield (line, '')
1666 1656 if line != stripline:
1667 1657 yield (line[len(stripline):], 'diff.trailingwhitespace')
1668 1658
1669 1659 def diffui(*args, **kw):
1670 1660 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1671 1661 return difflabel(diff, *args, **kw)
1672 1662
1673 1663
1674 1664 def _addmodehdr(header, omode, nmode):
1675 1665 if omode != nmode:
1676 1666 header.append('old mode %s\n' % omode)
1677 1667 header.append('new mode %s\n' % nmode)
1678 1668
1679 1669 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1680 1670 copy, getfilectx, opts, losedatafn, prefix):
1681 1671
1682 1672 def join(f):
1683 1673 return os.path.join(prefix, f)
1684 1674
1685 1675 date1 = util.datestr(ctx1.date())
1686 1676 man1 = ctx1.manifest()
1687 1677
1688 1678 gone = set()
1689 1679 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1690 1680
1691 1681 copyto = dict([(v, k) for k, v in copy.items()])
1692 1682
1693 1683 if opts.git:
1694 1684 revs = None
1695 1685
1696 1686 for f in sorted(modified + added + removed):
1697 1687 to = None
1698 1688 tn = None
1699 1689 dodiff = True
1700 1690 header = []
1701 1691 if f in man1:
1702 1692 to = getfilectx(f, ctx1).data()
1703 1693 if f not in removed:
1704 1694 tn = getfilectx(f, ctx2).data()
1705 1695 a, b = f, f
1706 1696 if opts.git or losedatafn:
1707 1697 if f in added:
1708 1698 mode = gitmode[ctx2.flags(f)]
1709 1699 if f in copy or f in copyto:
1710 1700 if opts.git:
1711 1701 if f in copy:
1712 1702 a = copy[f]
1713 1703 else:
1714 1704 a = copyto[f]
1715 1705 omode = gitmode[man1.flags(a)]
1716 1706 _addmodehdr(header, omode, mode)
1717 1707 if a in removed and a not in gone:
1718 1708 op = 'rename'
1719 1709 gone.add(a)
1720 1710 else:
1721 1711 op = 'copy'
1722 1712 header.append('%s from %s\n' % (op, join(a)))
1723 1713 header.append('%s to %s\n' % (op, join(f)))
1724 1714 to = getfilectx(a, ctx1).data()
1725 1715 else:
1726 1716 losedatafn(f)
1727 1717 else:
1728 1718 if opts.git:
1729 1719 header.append('new file mode %s\n' % mode)
1730 1720 elif ctx2.flags(f):
1731 1721 losedatafn(f)
1732 1722 # In theory, if tn was copied or renamed we should check
1733 1723 # if the source is binary too but the copy record already
1734 1724 # forces git mode.
1735 1725 if util.binary(tn):
1736 1726 if opts.git:
1737 1727 dodiff = 'binary'
1738 1728 else:
1739 1729 losedatafn(f)
1740 1730 if not opts.git and not tn:
1741 1731 # regular diffs cannot represent new empty file
1742 1732 losedatafn(f)
1743 1733 elif f in removed:
1744 1734 if opts.git:
1745 1735 # have we already reported a copy above?
1746 1736 if ((f in copy and copy[f] in added
1747 1737 and copyto[copy[f]] == f) or
1748 1738 (f in copyto and copyto[f] in added
1749 1739 and copy[copyto[f]] == f)):
1750 1740 dodiff = False
1751 1741 else:
1752 1742 header.append('deleted file mode %s\n' %
1753 1743 gitmode[man1.flags(f)])
1754 1744 elif not to or util.binary(to):
1755 1745 # regular diffs cannot represent empty file deletion
1756 1746 losedatafn(f)
1757 1747 else:
1758 1748 oflag = man1.flags(f)
1759 1749 nflag = ctx2.flags(f)
1760 1750 binary = util.binary(to) or util.binary(tn)
1761 1751 if opts.git:
1762 1752 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1763 1753 if binary:
1764 1754 dodiff = 'binary'
1765 1755 elif binary or nflag != oflag:
1766 1756 losedatafn(f)
1767 1757 if opts.git:
1768 1758 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1769 1759
1770 1760 if dodiff:
1771 1761 if dodiff == 'binary':
1772 1762 text = b85diff(to, tn)
1773 1763 else:
1774 1764 text = mdiff.unidiff(to, date1,
1775 1765 # ctx2 date may be dynamic
1776 1766 tn, util.datestr(ctx2.date()),
1777 1767 join(a), join(b), revs, opts=opts)
1778 1768 if header and (text or len(header) > 1):
1779 1769 yield ''.join(header)
1780 1770 if text:
1781 1771 yield text
1782 1772
1783 1773 def diffstatsum(stats):
1784 1774 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1785 1775 for f, a, r, b in stats:
1786 1776 maxfile = max(maxfile, encoding.colwidth(f))
1787 1777 maxtotal = max(maxtotal, a + r)
1788 1778 addtotal += a
1789 1779 removetotal += r
1790 1780 binary = binary or b
1791 1781
1792 1782 return maxfile, maxtotal, addtotal, removetotal, binary
1793 1783
1794 1784 def diffstatdata(lines):
1795 1785 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1796 1786
1797 1787 results = []
1798 1788 filename, adds, removes, isbinary = None, 0, 0, False
1799 1789
1800 1790 def addresult():
1801 1791 if filename:
1802 1792 results.append((filename, adds, removes, isbinary))
1803 1793
1804 1794 for line in lines:
1805 1795 if line.startswith('diff'):
1806 1796 addresult()
1807 1797 # set numbers to 0 anyway when starting new file
1808 1798 adds, removes, isbinary = 0, 0, False
1809 1799 if line.startswith('diff --git'):
1810 1800 filename = gitre.search(line).group(1)
1811 1801 elif line.startswith('diff -r'):
1812 1802 # format: "diff -r ... -r ... filename"
1813 1803 filename = diffre.search(line).group(1)
1814 1804 elif line.startswith('+') and not line.startswith('+++ '):
1815 1805 adds += 1
1816 1806 elif line.startswith('-') and not line.startswith('--- '):
1817 1807 removes += 1
1818 1808 elif (line.startswith('GIT binary patch') or
1819 1809 line.startswith('Binary file')):
1820 1810 isbinary = True
1821 1811 addresult()
1822 1812 return results
1823 1813
1824 1814 def diffstat(lines, width=80, git=False):
1825 1815 output = []
1826 1816 stats = diffstatdata(lines)
1827 1817 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1828 1818
1829 1819 countwidth = len(str(maxtotal))
1830 1820 if hasbinary and countwidth < 3:
1831 1821 countwidth = 3
1832 1822 graphwidth = width - countwidth - maxname - 6
1833 1823 if graphwidth < 10:
1834 1824 graphwidth = 10
1835 1825
1836 1826 def scale(i):
1837 1827 if maxtotal <= graphwidth:
1838 1828 return i
1839 1829 # If diffstat runs out of room it doesn't print anything,
1840 1830 # which isn't very useful, so always print at least one + or -
1841 1831 # if there were at least some changes.
1842 1832 return max(i * graphwidth // maxtotal, int(bool(i)))
1843 1833
1844 1834 for filename, adds, removes, isbinary in stats:
1845 1835 if isbinary:
1846 1836 count = 'Bin'
1847 1837 else:
1848 1838 count = adds + removes
1849 1839 pluses = '+' * scale(adds)
1850 1840 minuses = '-' * scale(removes)
1851 1841 output.append(' %s%s | %*s %s%s\n' %
1852 1842 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1853 1843 countwidth, count, pluses, minuses))
1854 1844
1855 1845 if stats:
1856 1846 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1857 1847 % (len(stats), totaladds, totalremoves))
1858 1848
1859 1849 return ''.join(output)
1860 1850
1861 1851 def diffstatui(*args, **kw):
1862 1852 '''like diffstat(), but yields 2-tuples of (output, label) for
1863 1853 ui.write()
1864 1854 '''
1865 1855
1866 1856 for line in diffstat(*args, **kw).splitlines():
1867 1857 if line and line[-1] in '+-':
1868 1858 name, graph = line.rsplit(' ', 1)
1869 1859 yield (name + ' ', '')
1870 1860 m = re.search(r'\++', graph)
1871 1861 if m:
1872 1862 yield (m.group(0), 'diffstat.inserted')
1873 1863 m = re.search(r'-+', graph)
1874 1864 if m:
1875 1865 yield (m.group(0), 'diffstat.deleted')
1876 1866 else:
1877 1867 yield (line, '')
1878 1868 yield ('\n', '')
@@ -1,259 +1,267 b''
1 1 $ "$TESTDIR/hghave" symlink execbit || exit 80
2 2
3 3 $ echo "[extensions]" >> $HGRCPATH
4 4 $ echo "purge=" >> $HGRCPATH
5 5 $ echo "graphlog=" >> $HGRCPATH
6 6
7 7 $ shortlog() {
8 8 > hg glog --template '{rev}:{node|short} {author} {date|hgdate} - {branch} - {desc|firstline}\n'
9 9 > }
10 10
11 11 Test --bypass with other options
12 12
13 13 $ hg init repo-options
14 14 $ cd repo-options
15 15 $ echo a > a
16 16 $ hg ci -Am adda
17 17 adding a
18 18 $ echo a >> a
19 19 $ hg branch foo
20 20 marked working directory as branch foo
21 21 (branches are permanent and global, did you want a bookmark?)
22 22 $ hg ci -Am changea
23 23 $ hg export . > ../test.diff
24 24 $ hg up null
25 25 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
26 26
27 27 Test importing an existing revision
28 28
29 29 $ hg import --bypass --exact ../test.diff
30 30 applying ../test.diff
31 31 $ shortlog
32 32 o 1:4e322f7ce8e3 test 0 0 - foo - changea
33 33 |
34 34 o 0:07f494440405 test 0 0 - default - adda
35 35
36 36
37 37 Test failure without --exact
38 38
39 39 $ hg import --bypass ../test.diff
40 40 applying ../test.diff
41 41 unable to find 'a' for patching
42 42 abort: patch failed to apply
43 43 [255]
44 44 $ hg st
45 45 $ shortlog
46 46 o 1:4e322f7ce8e3 test 0 0 - foo - changea
47 47 |
48 48 o 0:07f494440405 test 0 0 - default - adda
49 49
50 50
51 51 Test --user, --date and --message
52 52
53 53 $ hg up 0
54 54 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
55 55 $ hg import --bypass --u test2 -d '1 0' -m patch2 ../test.diff
56 56 applying ../test.diff
57 57 $ cat .hg/last-message.txt
58 58 patch2 (no-eol)
59 59 $ shortlog
60 60 o 2:2e127d1da504 test2 1 0 - default - patch2
61 61 |
62 62 | o 1:4e322f7ce8e3 test 0 0 - foo - changea
63 63 |/
64 64 @ 0:07f494440405 test 0 0 - default - adda
65 65
66 66 $ hg rollback
67 67 repository tip rolled back to revision 1 (undo import)
68 68
69 69 Test --import-branch
70 70
71 71 $ hg import --bypass --import-branch ../test.diff
72 72 applying ../test.diff
73 73 $ shortlog
74 74 o 1:4e322f7ce8e3 test 0 0 - foo - changea
75 75 |
76 76 @ 0:07f494440405 test 0 0 - default - adda
77 77
78 78 $ hg rollback
79 79 repository tip rolled back to revision 1 (undo import)
80 80
81 81 Test --strip
82 82
83 83 $ hg import --bypass --strip 0 - <<EOF
84 84 > # HG changeset patch
85 85 > # User test
86 86 > # Date 0 0
87 87 > # Branch foo
88 88 > # Node ID 4e322f7ce8e3e4203950eac9ece27bf7e45ffa6c
89 89 > # Parent 07f4944404050f47db2e5c5071e0e84e7a27bba9
90 90 > changea
91 91 >
92 92 > diff -r 07f494440405 -r 4e322f7ce8e3 a
93 93 > --- a Thu Jan 01 00:00:00 1970 +0000
94 94 > +++ a Thu Jan 01 00:00:00 1970 +0000
95 95 > @@ -1,1 +1,2 @@
96 96 > a
97 97 > +a
98 98 > EOF
99 99 applying patch from stdin
100 100 $ hg rollback
101 101 repository tip rolled back to revision 1 (undo import)
102 102
103 103 Test unsupported combinations
104 104
105 105 $ hg import --bypass --no-commit ../test.diff
106 106 abort: cannot use --no-commit with --bypass
107 107 [255]
108 108 $ hg import --bypass --similarity 50 ../test.diff
109 109 abort: cannot use --similarity with --bypass
110 110 [255]
111 111
112 112 Test commit editor
113 113
114 $ hg diff -c 1 > ../test.diff
114 $ cat > ../test.diff <<EOF
115 > diff -r 07f494440405 -r 4e322f7ce8e3 a
116 > --- a/a Thu Jan 01 00:00:00 1970 +0000
117 > +++ b/a Thu Jan 01 00:00:00 1970 +0000
118 > @@ -1,1 +1,2 @@
119 > -a
120 > +b
121 > +c
122 > EOF
115 123 $ HGEDITOR=cat hg import --bypass ../test.diff
116 124 applying ../test.diff
117 125
118 126
119 127 HG: Enter commit message. Lines beginning with 'HG:' are removed.
120 128 HG: Leave message empty to abort commit.
121 129 HG: --
122 130 HG: user: test
123 131 HG: branch 'default'
124 132 HG: changed a
125 133 abort: empty commit message
126 134 [255]
127 135
128 136 Test patch.eol is handled
129 137
130 138 $ python -c 'file("a", "wb").write("a\r\n")'
131 139 $ hg ci -m makeacrlf
132 140 $ hg import -m 'should fail because of eol' --bypass ../test.diff
133 141 applying ../test.diff
134 142 patching file a
135 143 Hunk #1 FAILED at 0
136 144 abort: patch failed to apply
137 145 [255]
138 146 $ hg --config patch.eol=auto import -d '0 0' -m 'test patch.eol' --bypass ../test.diff
139 147 applying ../test.diff
140 148 $ shortlog
141 o 3:d7805b4d2cb3 test 0 0 - default - test patch.eol
149 o 3:c606edafba99 test 0 0 - default - test patch.eol
142 150 |
143 151 @ 2:872023de769d test 0 0 - default - makeacrlf
144 152 |
145 153 | o 1:4e322f7ce8e3 test 0 0 - foo - changea
146 154 |/
147 155 o 0:07f494440405 test 0 0 - default - adda
148 156
149 157
150 158 Test applying multiple patches
151 159
152 160 $ hg up -qC 0
153 161 $ echo e > e
154 162 $ hg ci -Am adde
155 163 adding e
156 164 created new head
157 165 $ hg export . > ../patch1.diff
158 166 $ hg up -qC 1
159 167 $ echo f > f
160 168 $ hg ci -Am addf
161 169 adding f
162 170 $ hg export . > ../patch2.diff
163 171 $ cd ..
164 172 $ hg clone -r1 repo-options repo-multi1
165 173 adding changesets
166 174 adding manifests
167 175 adding file changes
168 176 added 2 changesets with 2 changes to 1 files
169 177 updating to branch foo
170 178 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
171 179 $ cd repo-multi1
172 180 $ hg up 0
173 181 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
174 182 $ hg import --bypass ../patch1.diff ../patch2.diff
175 183 applying ../patch1.diff
176 184 applying ../patch2.diff
177 185 $ shortlog
178 186 o 3:bc8ca3f8a7c4 test 0 0 - default - addf
179 187 |
180 188 o 2:16581080145e test 0 0 - default - adde
181 189 |
182 190 | o 1:4e322f7ce8e3 test 0 0 - foo - changea
183 191 |/
184 192 @ 0:07f494440405 test 0 0 - default - adda
185 193
186 194
187 195 Test applying multiple patches with --exact
188 196
189 197 $ cd ..
190 198 $ hg clone -r1 repo-options repo-multi2
191 199 adding changesets
192 200 adding manifests
193 201 adding file changes
194 202 added 2 changesets with 2 changes to 1 files
195 203 updating to branch foo
196 204 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
197 205 $ cd repo-multi2
198 206 $ hg import --bypass --exact ../patch1.diff ../patch2.diff
199 207 applying ../patch1.diff
200 208 applying ../patch2.diff
201 209 $ shortlog
202 210 o 3:d60cb8989666 test 0 0 - foo - addf
203 211 |
204 212 | o 2:16581080145e test 0 0 - default - adde
205 213 | |
206 214 @ | 1:4e322f7ce8e3 test 0 0 - foo - changea
207 215 |/
208 216 o 0:07f494440405 test 0 0 - default - adda
209 217
210 218
211 219 $ cd ..
212 220
213 221 Test complicated patch with --exact
214 222
215 223 $ hg init repo-exact
216 224 $ cd repo-exact
217 225 $ echo a > a
218 226 $ echo c > c
219 227 $ echo d > d
220 228 $ echo e > e
221 229 $ echo f > f
222 230 $ chmod +x f
223 231 $ ln -s c linkc
224 232 $ hg ci -Am t
225 233 adding a
226 234 adding c
227 235 adding d
228 236 adding e
229 237 adding f
230 238 adding linkc
231 239 $ hg cp a aa1
232 240 $ echo b >> a
233 241 $ echo b > b
234 242 $ hg add b
235 243 $ hg cp a aa2
236 244 $ echo aa >> aa2
237 245 $ chmod +x e
238 246 $ chmod -x f
239 247 $ ln -s a linka
240 248 $ hg rm d
241 249 $ hg rm linkc
242 250 $ hg mv c cc
243 251 $ hg ci -m patch
244 252 $ hg export --git . > ../test.diff
245 253 $ hg up -C null
246 254 0 files updated, 0 files merged, 7 files removed, 0 files unresolved
247 255 $ hg purge
248 256 $ hg st
249 257 $ hg import --bypass --exact ../test.diff
250 258 applying ../test.diff
251 259
252 260 The patch should have matched the exported revision and generated no additional
253 261 data. If not, diff both heads to debug it.
254 262
255 263 $ shortlog
256 264 o 1:2978fd5c8aa4 test 0 0 - default - patch
257 265 |
258 266 o 0:a0e19e636a43 test 0 0 - default - t
259 267
@@ -1,1074 +1,1097 b''
1 1 $ "$TESTDIR/hghave" unix-permissions || exit 80
2 2
3 3 $ hg init a
4 4 $ mkdir a/d1
5 5 $ mkdir a/d1/d2
6 6 $ echo line 1 > a/a
7 7 $ echo line 1 > a/d1/d2/a
8 8 $ hg --cwd a ci -Ama
9 9 adding a
10 10 adding d1/d2/a
11 11
12 12 $ echo line 2 >> a/a
13 13 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
14 14
15 15 import with no args:
16 16
17 17 $ hg --cwd a import
18 18 abort: need at least one patch to import
19 19 [255]
20 20
21 21 generate patches for the test
22 22
23 23 $ hg --cwd a export tip > exported-tip.patch
24 24 $ hg --cwd a diff -r0:1 > diffed-tip.patch
25 25
26 26
27 27 import exported patch
28 28
29 29 $ hg clone -r0 a b
30 30 adding changesets
31 31 adding manifests
32 32 adding file changes
33 33 added 1 changesets with 2 changes to 2 files
34 34 updating to branch default
35 35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 36 $ hg --cwd b import ../exported-tip.patch
37 37 applying ../exported-tip.patch
38 38
39 39 message and committer should be same
40 40
41 41 $ hg --cwd b tip
42 42 changeset: 1:1d4bd90af0e4
43 43 tag: tip
44 44 user: someone
45 45 date: Thu Jan 01 00:00:01 1970 +0000
46 46 summary: second change
47 47
48 48 $ rm -r b
49 49
50 50
51 51 import exported patch with external patcher
52 52
53 53 $ cat > dummypatch.py <<EOF
54 54 > print 'patching file a'
55 55 > file('a', 'wb').write('line2\n')
56 56 > EOF
57 57 $ chmod +x dummypatch.py
58 58 $ hg clone -r0 a b
59 59 adding changesets
60 60 adding manifests
61 61 adding file changes
62 62 added 1 changesets with 2 changes to 2 files
63 63 updating to branch default
64 64 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
65 65 $ hg --config ui.patch='python ../dummypatch.py' --cwd b import ../exported-tip.patch
66 66 applying ../exported-tip.patch
67 67 $ cat b/a
68 68 line2
69 69 $ rm -r b
70 70
71 71
72 72 import of plain diff should fail without message
73 73
74 74 $ hg clone -r0 a b
75 75 adding changesets
76 76 adding manifests
77 77 adding file changes
78 78 added 1 changesets with 2 changes to 2 files
79 79 updating to branch default
80 80 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
81 81 $ hg --cwd b import ../diffed-tip.patch
82 82 applying ../diffed-tip.patch
83 83 abort: empty commit message
84 84 [255]
85 85 $ rm -r b
86 86
87 87
88 88 import of plain diff should be ok with message
89 89
90 90 $ hg clone -r0 a b
91 91 adding changesets
92 92 adding manifests
93 93 adding file changes
94 94 added 1 changesets with 2 changes to 2 files
95 95 updating to branch default
96 96 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
97 97 $ hg --cwd b import -mpatch ../diffed-tip.patch
98 98 applying ../diffed-tip.patch
99 99 $ rm -r b
100 100
101 101
102 102 import of plain diff with specific date and user
103 103
104 104 $ hg clone -r0 a b
105 105 adding changesets
106 106 adding manifests
107 107 adding file changes
108 108 added 1 changesets with 2 changes to 2 files
109 109 updating to branch default
110 110 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 111 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
112 112 applying ../diffed-tip.patch
113 113 $ hg -R b tip -pv
114 114 changeset: 1:ca68f19f3a40
115 115 tag: tip
116 116 user: user@nowhere.net
117 117 date: Thu Jan 01 00:00:01 1970 +0000
118 118 files: a
119 119 description:
120 120 patch
121 121
122 122
123 123 diff -r 80971e65b431 -r ca68f19f3a40 a
124 124 --- a/a Thu Jan 01 00:00:00 1970 +0000
125 125 +++ b/a Thu Jan 01 00:00:01 1970 +0000
126 126 @@ -1,1 +1,2 @@
127 127 line 1
128 128 +line 2
129 129
130 130 $ rm -r b
131 131
132 132
133 133 import of plain diff should be ok with --no-commit
134 134
135 135 $ hg clone -r0 a b
136 136 adding changesets
137 137 adding manifests
138 138 adding file changes
139 139 added 1 changesets with 2 changes to 2 files
140 140 updating to branch default
141 141 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 142 $ hg --cwd b import --no-commit ../diffed-tip.patch
143 143 applying ../diffed-tip.patch
144 144 $ hg --cwd b diff --nodates
145 145 diff -r 80971e65b431 a
146 146 --- a/a
147 147 +++ b/a
148 148 @@ -1,1 +1,2 @@
149 149 line 1
150 150 +line 2
151 151 $ rm -r b
152 152
153 153
154 154 import of malformed plain diff should fail
155 155
156 156 $ hg clone -r0 a b
157 157 adding changesets
158 158 adding manifests
159 159 adding file changes
160 160 added 1 changesets with 2 changes to 2 files
161 161 updating to branch default
162 162 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
163 163 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
164 164 $ hg --cwd b import -mpatch ../broken.patch
165 165 applying ../broken.patch
166 166 abort: bad hunk #1
167 167 [255]
168 168 $ rm -r b
169 169
170 170
171 171 hg -R repo import
172 172 put the clone in a subdir - having a directory named "a"
173 173 used to hide a bug.
174 174
175 175 $ mkdir dir
176 176 $ hg clone -r0 a dir/b
177 177 adding changesets
178 178 adding manifests
179 179 adding file changes
180 180 added 1 changesets with 2 changes to 2 files
181 181 updating to branch default
182 182 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
183 183 $ cd dir
184 184 $ hg -R b import ../exported-tip.patch
185 185 applying ../exported-tip.patch
186 186 $ cd ..
187 187 $ rm -r dir
188 188
189 189
190 190 import from stdin
191 191
192 192 $ hg clone -r0 a b
193 193 adding changesets
194 194 adding manifests
195 195 adding file changes
196 196 added 1 changesets with 2 changes to 2 files
197 197 updating to branch default
198 198 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
199 199 $ hg --cwd b import - < exported-tip.patch
200 200 applying patch from stdin
201 201 $ rm -r b
202 202
203 203
204 204 import two patches in one stream
205 205
206 206 $ hg init b
207 207 $ hg --cwd a export 0:tip | hg --cwd b import -
208 208 applying patch from stdin
209 209 $ hg --cwd a id
210 210 1d4bd90af0e4 tip
211 211 $ hg --cwd b id
212 212 1d4bd90af0e4 tip
213 213 $ rm -r b
214 214
215 215
216 216 override commit message
217 217
218 218 $ hg clone -r0 a b
219 219 adding changesets
220 220 adding manifests
221 221 adding file changes
222 222 added 1 changesets with 2 changes to 2 files
223 223 updating to branch default
224 224 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
225 225 $ hg --cwd b import -m 'override' - < exported-tip.patch
226 226 applying patch from stdin
227 227 $ hg --cwd b tip | grep override
228 228 summary: override
229 229 $ rm -r b
230 230
231 231 $ cat > mkmsg.py <<EOF
232 232 > import email.Message, sys
233 233 > msg = email.Message.Message()
234 234 > patch = open(sys.argv[1], 'rb').read()
235 235 > msg.set_payload('email commit message\n' + patch)
236 236 > msg['Subject'] = 'email patch'
237 237 > msg['From'] = 'email patcher'
238 238 > file(sys.argv[2], 'wb').write(msg.as_string())
239 239 > EOF
240 240
241 241
242 242 plain diff in email, subject, message body
243 243
244 244 $ hg clone -r0 a b
245 245 adding changesets
246 246 adding manifests
247 247 adding file changes
248 248 added 1 changesets with 2 changes to 2 files
249 249 updating to branch default
250 250 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
251 251 $ python mkmsg.py diffed-tip.patch msg.patch
252 252 $ hg --cwd b import ../msg.patch
253 253 applying ../msg.patch
254 254 $ hg --cwd b tip | grep email
255 255 user: email patcher
256 256 summary: email patch
257 257 $ rm -r b
258 258
259 259
260 260 plain diff in email, no subject, message body
261 261
262 262 $ hg clone -r0 a b
263 263 adding changesets
264 264 adding manifests
265 265 adding file changes
266 266 added 1 changesets with 2 changes to 2 files
267 267 updating to branch default
268 268 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
269 269 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
270 270 applying patch from stdin
271 271 $ rm -r b
272 272
273 273
274 274 plain diff in email, subject, no message body
275 275
276 276 $ hg clone -r0 a b
277 277 adding changesets
278 278 adding manifests
279 279 adding file changes
280 280 added 1 changesets with 2 changes to 2 files
281 281 updating to branch default
282 282 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
283 283 $ grep -v '^email ' msg.patch | hg --cwd b import -
284 284 applying patch from stdin
285 285 $ rm -r b
286 286
287 287
288 288 plain diff in email, no subject, no message body, should fail
289 289
290 290 $ hg clone -r0 a b
291 291 adding changesets
292 292 adding manifests
293 293 adding file changes
294 294 added 1 changesets with 2 changes to 2 files
295 295 updating to branch default
296 296 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
297 297 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
298 298 applying patch from stdin
299 299 abort: empty commit message
300 300 [255]
301 301 $ rm -r b
302 302
303 303
304 304 hg export in email, should use patch header
305 305
306 306 $ hg clone -r0 a b
307 307 adding changesets
308 308 adding manifests
309 309 adding file changes
310 310 added 1 changesets with 2 changes to 2 files
311 311 updating to branch default
312 312 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
313 313 $ python mkmsg.py exported-tip.patch msg.patch
314 314 $ cat msg.patch | hg --cwd b import -
315 315 applying patch from stdin
316 316 $ hg --cwd b tip | grep second
317 317 summary: second change
318 318 $ rm -r b
319 319
320 320
321 321 subject: duplicate detection, removal of [PATCH]
322 322 The '---' tests the gitsendmail handling without proper mail headers
323 323
324 324 $ cat > mkmsg2.py <<EOF
325 325 > import email.Message, sys
326 326 > msg = email.Message.Message()
327 327 > patch = open(sys.argv[1], 'rb').read()
328 328 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
329 329 > msg['Subject'] = '[PATCH] email patch'
330 330 > msg['From'] = 'email patcher'
331 331 > file(sys.argv[2], 'wb').write(msg.as_string())
332 332 > EOF
333 333
334 334
335 335 plain diff in email, [PATCH] subject, message body with subject
336 336
337 337 $ hg clone -r0 a b
338 338 adding changesets
339 339 adding manifests
340 340 adding file changes
341 341 added 1 changesets with 2 changes to 2 files
342 342 updating to branch default
343 343 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
344 344 $ python mkmsg2.py diffed-tip.patch msg.patch
345 345 $ cat msg.patch | hg --cwd b import -
346 346 applying patch from stdin
347 347 $ hg --cwd b tip --template '{desc}\n'
348 348 email patch
349 349
350 350 next line
351 351 ---
352 352 $ rm -r b
353 353
354 354
355 355 Issue963: Parent of working dir incorrect after import of multiple
356 356 patches and rollback
357 357
358 358 We weren't backing up the correct dirstate file when importing many
359 359 patches: import patch1 patch2; rollback
360 360
361 361 $ echo line 3 >> a/a
362 362 $ hg --cwd a ci -m'third change'
363 363 $ hg --cwd a export -o '../patch%R' 1 2
364 364 $ hg clone -qr0 a b
365 365 $ hg --cwd b parents --template 'parent: {rev}\n'
366 366 parent: 0
367 367 $ hg --cwd b import -v ../patch1 ../patch2
368 368 applying ../patch1
369 369 patching file a
370 370 a
371 371 created 1d4bd90af0e4
372 372 applying ../patch2
373 373 patching file a
374 374 a
375 375 created 6d019af21222
376 376 $ hg --cwd b rollback
377 377 repository tip rolled back to revision 0 (undo import)
378 378 working directory now based on revision 0
379 379 $ hg --cwd b parents --template 'parent: {rev}\n'
380 380 parent: 0
381 381 $ rm -r b
382 382
383 383
384 384 importing a patch in a subdirectory failed at the commit stage
385 385
386 386 $ echo line 2 >> a/d1/d2/a
387 387 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
388 388
389 389 hg import in a subdirectory
390 390
391 391 $ hg clone -r0 a b
392 392 adding changesets
393 393 adding manifests
394 394 adding file changes
395 395 added 1 changesets with 2 changes to 2 files
396 396 updating to branch default
397 397 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
398 398 $ hg --cwd a export tip > tmp
399 399 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
400 400 $ dir=`pwd`
401 401 $ cd b/d1/d2 2>&1 > /dev/null
402 402 $ hg import ../../../subdir-tip.patch
403 403 applying ../../../subdir-tip.patch
404 404 $ cd "$dir"
405 405
406 406 message should be 'subdir change'
407 407 committer should be 'someoneelse'
408 408
409 409 $ hg --cwd b tip
410 410 changeset: 1:3577f5aea227
411 411 tag: tip
412 412 user: someoneelse
413 413 date: Thu Jan 01 00:00:01 1970 +0000
414 414 summary: subdir change
415 415
416 416
417 417 should be empty
418 418
419 419 $ hg --cwd b status
420 420
421 421
422 422 Test fuzziness (ambiguous patch location, fuzz=2)
423 423
424 424 $ hg init fuzzy
425 425 $ cd fuzzy
426 426 $ echo line1 > a
427 427 $ echo line0 >> a
428 428 $ echo line3 >> a
429 429 $ hg ci -Am adda
430 430 adding a
431 431 $ echo line1 > a
432 432 $ echo line2 >> a
433 433 $ echo line0 >> a
434 434 $ echo line3 >> a
435 435 $ hg ci -m change a
436 436 $ hg export tip > fuzzy-tip.patch
437 437 $ hg up -C 0
438 438 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
439 439 $ echo line1 > a
440 440 $ echo line0 >> a
441 441 $ echo line1 >> a
442 442 $ echo line0 >> a
443 443 $ hg ci -m brancha
444 444 created new head
445 445 $ hg import --no-commit -v fuzzy-tip.patch
446 446 applying fuzzy-tip.patch
447 447 patching file a
448 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
448 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
449 449 applied to working directory
450 450 $ hg revert -a
451 451 reverting a
452 452
453 453
454 454 import with --no-commit should have written .hg/last-message.txt
455 455
456 456 $ cat .hg/last-message.txt
457 457 change (no-eol)
458 458
459 459
460 460 test fuzziness with eol=auto
461 461
462 462 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
463 463 applying fuzzy-tip.patch
464 464 patching file a
465 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
465 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
466 466 applied to working directory
467 467 $ cd ..
468 468
469 469
470 470 Test hunk touching empty files (issue906)
471 471
472 472 $ hg init empty
473 473 $ cd empty
474 474 $ touch a
475 475 $ touch b1
476 476 $ touch c1
477 477 $ echo d > d
478 478 $ hg ci -Am init
479 479 adding a
480 480 adding b1
481 481 adding c1
482 482 adding d
483 483 $ echo a > a
484 484 $ echo b > b1
485 485 $ hg mv b1 b2
486 486 $ echo c > c1
487 487 $ hg copy c1 c2
488 488 $ rm d
489 489 $ touch d
490 490 $ hg diff --git
491 491 diff --git a/a b/a
492 492 --- a/a
493 493 +++ b/a
494 494 @@ -0,0 +1,1 @@
495 495 +a
496 496 diff --git a/b1 b/b2
497 497 rename from b1
498 498 rename to b2
499 499 --- a/b1
500 500 +++ b/b2
501 501 @@ -0,0 +1,1 @@
502 502 +b
503 503 diff --git a/c1 b/c1
504 504 --- a/c1
505 505 +++ b/c1
506 506 @@ -0,0 +1,1 @@
507 507 +c
508 508 diff --git a/c1 b/c2
509 509 copy from c1
510 510 copy to c2
511 511 --- a/c1
512 512 +++ b/c2
513 513 @@ -0,0 +1,1 @@
514 514 +c
515 515 diff --git a/d b/d
516 516 --- a/d
517 517 +++ b/d
518 518 @@ -1,1 +0,0 @@
519 519 -d
520 520 $ hg ci -m empty
521 521 $ hg export --git tip > empty.diff
522 522 $ hg up -C 0
523 523 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
524 524 $ hg import empty.diff
525 525 applying empty.diff
526 526 $ for name in a b1 b2 c1 c2 d; do
527 527 > echo % $name file
528 528 > test -f $name && cat $name
529 529 > done
530 530 % a file
531 531 a
532 532 % b1 file
533 533 % b2 file
534 534 b
535 535 % c1 file
536 536 c
537 537 % c2 file
538 538 c
539 539 % d file
540 540 $ cd ..
541 541
542 542
543 543 Test importing a patch ending with a binary file removal
544 544
545 545 $ hg init binaryremoval
546 546 $ cd binaryremoval
547 547 $ echo a > a
548 548 $ python -c "file('b', 'wb').write('a\x00b')"
549 549 $ hg ci -Am addall
550 550 adding a
551 551 adding b
552 552 $ hg rm a
553 553 $ hg rm b
554 554 $ hg st
555 555 R a
556 556 R b
557 557 $ hg ci -m remove
558 558 $ hg export --git . > remove.diff
559 559 $ cat remove.diff | grep git
560 560 diff --git a/a b/a
561 561 diff --git a/b b/b
562 562 $ hg up -C 0
563 563 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
564 564 $ hg import remove.diff
565 565 applying remove.diff
566 566 $ hg manifest
567 567 $ cd ..
568 568
569 569
570 570 Issue927: test update+rename with common name
571 571
572 572 $ hg init t
573 573 $ cd t
574 574 $ touch a
575 575 $ hg ci -Am t
576 576 adding a
577 577 $ echo a > a
578 578
579 579 Here, bfile.startswith(afile)
580 580
581 581 $ hg copy a a2
582 582 $ hg ci -m copya
583 583 $ hg export --git tip > copy.diff
584 584 $ hg up -C 0
585 585 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
586 586 $ hg import copy.diff
587 587 applying copy.diff
588 588
589 589 a should contain an 'a'
590 590
591 591 $ cat a
592 592 a
593 593
594 594 and a2 should have duplicated it
595 595
596 596 $ cat a2
597 597 a
598 598 $ cd ..
599 599
600 600
601 601 test -p0
602 602
603 603 $ hg init p0
604 604 $ cd p0
605 605 $ echo a > a
606 606 $ hg ci -Am t
607 607 adding a
608 608 $ hg import -p0 - << EOF
609 609 > foobar
610 610 > --- a Sat Apr 12 22:43:58 2008 -0400
611 611 > +++ a Sat Apr 12 22:44:05 2008 -0400
612 612 > @@ -1,1 +1,1 @@
613 613 > -a
614 614 > +bb
615 615 > EOF
616 616 applying patch from stdin
617 617 $ hg status
618 618 $ cat a
619 619 bb
620 620 $ cd ..
621 621
622 622
623 623 test paths outside repo root
624 624
625 625 $ mkdir outside
626 626 $ touch outside/foo
627 627 $ hg init inside
628 628 $ cd inside
629 629 $ hg import - <<EOF
630 630 > diff --git a/a b/b
631 631 > rename from ../outside/foo
632 632 > rename to bar
633 633 > EOF
634 634 applying patch from stdin
635 635 abort: path contains illegal component: ../outside/foo
636 636 [255]
637 637 $ cd ..
638 638
639 639
640 640 test import with similarity and git and strip (issue295 et al.)
641 641
642 642 $ hg init sim
643 643 $ cd sim
644 644 $ echo 'this is a test' > a
645 645 $ hg ci -Ama
646 646 adding a
647 647 $ cat > ../rename.diff <<EOF
648 648 > diff --git a/foo/a b/foo/a
649 649 > deleted file mode 100644
650 650 > --- a/foo/a
651 651 > +++ /dev/null
652 652 > @@ -1,1 +0,0 @@
653 653 > -this is a test
654 654 > diff --git a/foo/b b/foo/b
655 655 > new file mode 100644
656 656 > --- /dev/null
657 657 > +++ b/foo/b
658 658 > @@ -0,0 +1,2 @@
659 659 > +this is a test
660 660 > +foo
661 661 > EOF
662 662 $ hg import --no-commit -v -s 1 ../rename.diff -p2
663 663 applying ../rename.diff
664 664 patching file a
665 665 patching file b
666 666 adding b
667 667 recording removal of a as rename to b (88% similar)
668 668 applied to working directory
669 669 $ hg st -C
670 670 A b
671 671 a
672 672 R a
673 673 $ hg revert -a
674 674 undeleting a
675 675 forgetting b
676 676 $ rm b
677 677 $ hg import --no-commit -v -s 100 ../rename.diff -p2
678 678 applying ../rename.diff
679 679 patching file a
680 680 patching file b
681 681 adding b
682 682 applied to working directory
683 683 $ hg st -C
684 684 A b
685 685 R a
686 686 $ cd ..
687 687
688 688
689 689 Issue1495: add empty file from the end of patch
690 690
691 691 $ hg init addemptyend
692 692 $ cd addemptyend
693 693 $ touch a
694 694 $ hg addremove
695 695 adding a
696 696 $ hg ci -m "commit"
697 697 $ cat > a.patch <<EOF
698 698 > add a, b
699 699 > diff --git a/a b/a
700 700 > --- a/a
701 701 > +++ b/a
702 702 > @@ -0,0 +1,1 @@
703 703 > +a
704 704 > diff --git a/b b/b
705 705 > new file mode 100644
706 706 > EOF
707 707 $ hg import --no-commit a.patch
708 708 applying a.patch
709 709
710 710 apply a good patch followed by an empty patch (mainly to ensure
711 711 that dirstate is *not* updated when import crashes)
712 712 $ hg update -q -C .
713 713 $ rm b
714 714 $ touch empty.patch
715 715 $ hg import a.patch empty.patch
716 716 applying a.patch
717 717 applying empty.patch
718 718 transaction abort!
719 719 rollback completed
720 720 abort: empty.patch: no diffs found
721 721 [255]
722 722 $ hg tip --template '{rev} {desc|firstline}\n'
723 723 0 commit
724 724 $ hg -q status
725 725 M a
726 726 $ cd ..
727 727
728 728 create file when source is not /dev/null
729 729
730 730 $ cat > create.patch <<EOF
731 731 > diff -Naur proj-orig/foo proj-new/foo
732 732 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
733 733 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
734 734 > @@ -0,0 +1,1 @@
735 735 > +a
736 736 > EOF
737 737
738 738 some people have patches like the following too
739 739
740 740 $ cat > create2.patch <<EOF
741 741 > diff -Naur proj-orig/foo proj-new/foo
742 742 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
743 743 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
744 744 > @@ -0,0 +1,1 @@
745 745 > +a
746 746 > EOF
747 747 $ hg init oddcreate
748 748 $ cd oddcreate
749 749 $ hg import --no-commit ../create.patch
750 750 applying ../create.patch
751 751 $ cat foo
752 752 a
753 753 $ rm foo
754 754 $ hg revert foo
755 755 $ hg import --no-commit ../create2.patch
756 756 applying ../create2.patch
757 757 $ cat foo
758 758 a
759 759
760 760
761 761 Issue1859: first line mistaken for email headers
762 762
763 763 $ hg init emailconfusion
764 764 $ cd emailconfusion
765 765 $ cat > a.patch <<EOF
766 766 > module: summary
767 767 >
768 768 > description
769 769 >
770 770 >
771 771 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
772 772 > --- /dev/null
773 773 > +++ b/a
774 774 > @@ -0,0 +1,1 @@
775 775 > +a
776 776 > EOF
777 777 $ hg import -d '0 0' a.patch
778 778 applying a.patch
779 779 $ hg parents -v
780 780 changeset: 0:5a681217c0ad
781 781 tag: tip
782 782 user: test
783 783 date: Thu Jan 01 00:00:00 1970 +0000
784 784 files: a
785 785 description:
786 786 module: summary
787 787
788 788 description
789 789
790 790
791 791 $ cd ..
792 792
793 793
794 794 --- in commit message
795 795
796 796 $ hg init commitconfusion
797 797 $ cd commitconfusion
798 798 $ cat > a.patch <<EOF
799 799 > module: summary
800 800 >
801 801 > --- description
802 802 >
803 803 > diff --git a/a b/a
804 804 > new file mode 100644
805 805 > --- /dev/null
806 806 > +++ b/a
807 807 > @@ -0,0 +1,1 @@
808 808 > +a
809 809 > EOF
810 810 > hg import -d '0 0' a.patch
811 811 > hg parents -v
812 812 > cd ..
813 813 >
814 814 > echo '% tricky header splitting'
815 815 > cat > trickyheaders.patch <<EOF
816 816 > From: User A <user@a>
817 817 > Subject: [PATCH] from: tricky!
818 818 >
819 819 > # HG changeset patch
820 820 > # User User B
821 821 > # Date 1266264441 18000
822 822 > # Branch stable
823 823 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
824 824 > # Parent 0000000000000000000000000000000000000000
825 825 > from: tricky!
826 826 >
827 827 > That is not a header.
828 828 >
829 829 > diff -r 000000000000 -r f2be6a1170ac foo
830 830 > --- /dev/null
831 831 > +++ b/foo
832 832 > @@ -0,0 +1,1 @@
833 833 > +foo
834 834 > EOF
835 835 applying a.patch
836 836 changeset: 0:f34d9187897d
837 837 tag: tip
838 838 user: test
839 839 date: Thu Jan 01 00:00:00 1970 +0000
840 840 files: a
841 841 description:
842 842 module: summary
843 843
844 844
845 845 % tricky header splitting
846 846
847 847 $ hg init trickyheaders
848 848 $ cd trickyheaders
849 849 $ hg import -d '0 0' ../trickyheaders.patch
850 850 applying ../trickyheaders.patch
851 851 $ hg export --git tip
852 852 # HG changeset patch
853 853 # User User B
854 854 # Date 0 0
855 855 # Node ID eb56ab91903632294ac504838508cb370c0901d2
856 856 # Parent 0000000000000000000000000000000000000000
857 857 from: tricky!
858 858
859 859 That is not a header.
860 860
861 861 diff --git a/foo b/foo
862 862 new file mode 100644
863 863 --- /dev/null
864 864 +++ b/foo
865 865 @@ -0,0 +1,1 @@
866 866 +foo
867 867 $ cd ..
868 868
869 869
870 870 Issue2102: hg export and hg import speak different languages
871 871
872 872 $ hg init issue2102
873 873 $ cd issue2102
874 874 $ mkdir -p src/cmd/gc
875 875 $ touch src/cmd/gc/mksys.bash
876 876 $ hg ci -Am init
877 877 adding src/cmd/gc/mksys.bash
878 878 $ hg import - <<EOF
879 879 > # HG changeset patch
880 880 > # User Rob Pike
881 881 > # Date 1216685449 25200
882 882 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
883 883 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
884 884 > help management of empty pkg and lib directories in perforce
885 885 >
886 886 > R=gri
887 887 > DELTA=4 (4 added, 0 deleted, 0 changed)
888 888 > OCL=13328
889 889 > CL=13328
890 890 >
891 891 > diff --git a/lib/place-holder b/lib/place-holder
892 892 > new file mode 100644
893 893 > --- /dev/null
894 894 > +++ b/lib/place-holder
895 895 > @@ -0,0 +1,2 @@
896 896 > +perforce does not maintain empty directories.
897 897 > +this file helps.
898 898 > diff --git a/pkg/place-holder b/pkg/place-holder
899 899 > new file mode 100644
900 900 > --- /dev/null
901 901 > +++ b/pkg/place-holder
902 902 > @@ -0,0 +1,2 @@
903 903 > +perforce does not maintain empty directories.
904 904 > +this file helps.
905 905 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
906 906 > old mode 100644
907 907 > new mode 100755
908 908 > EOF
909 909 applying patch from stdin
910 910 $ hg sum
911 911 parent: 1:d59915696727 tip
912 912 help management of empty pkg and lib directories in perforce
913 913 branch: default
914 914 commit: (clean)
915 915 update: (current)
916 916 $ hg diff --git -c tip
917 917 diff --git a/lib/place-holder b/lib/place-holder
918 918 new file mode 100644
919 919 --- /dev/null
920 920 +++ b/lib/place-holder
921 921 @@ -0,0 +1,2 @@
922 922 +perforce does not maintain empty directories.
923 923 +this file helps.
924 924 diff --git a/pkg/place-holder b/pkg/place-holder
925 925 new file mode 100644
926 926 --- /dev/null
927 927 +++ b/pkg/place-holder
928 928 @@ -0,0 +1,2 @@
929 929 +perforce does not maintain empty directories.
930 930 +this file helps.
931 931 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
932 932 old mode 100644
933 933 new mode 100755
934 934 $ cd ..
935 935
936 936
937 937 diff lines looking like headers
938 938
939 939 $ hg init difflineslikeheaders
940 940 $ cd difflineslikeheaders
941 941 $ echo a >a
942 942 $ echo b >b
943 943 $ echo c >c
944 944 $ hg ci -Am1
945 945 adding a
946 946 adding b
947 947 adding c
948 948
949 949 $ echo "key: value" >>a
950 950 $ echo "key: value" >>b
951 951 $ echo "foo" >>c
952 952 $ hg ci -m2
953 953
954 954 $ hg up -C 0
955 955 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
956 956 $ hg diff --git -c1 >want
957 957 $ hg diff -c1 | hg import --no-commit -
958 958 applying patch from stdin
959 959 $ hg diff --git >have
960 960 $ diff want have
961 961 $ cd ..
962 962
963 963 import a unified diff with no lines of context (diff -U0)
964 964
965 965 $ hg init diffzero
966 966 $ cd diffzero
967 967 $ cat > f << EOF
968 968 > c2
969 969 > c4
970 970 > c5
971 971 > EOF
972 972 $ hg commit -Am0
973 973 adding f
974 974
975 975 $ hg import --no-commit - << EOF
976 976 > # HG changeset patch
977 977 > # User test
978 978 > # Date 0 0
979 979 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
980 980 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
981 981 > 1
982 982 > diff -r 8679a12a975b -r f4974ab632f3 f
983 983 > --- a/f Thu Jan 01 00:00:00 1970 +0000
984 984 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
985 985 > @@ -0,0 +1,1 @@
986 986 > +c1
987 987 > @@ -1,0 +3,1 @@
988 988 > +c3
989 989 > @@ -3,1 +4,0 @@
990 990 > -c5
991 991 > EOF
992 992 applying patch from stdin
993 993
994 994 $ cat f
995 995 c1
996 996 c2
997 997 c3
998 998 c4
999 999
1000 1000 Test corner case involving fuzz and skew
1001 1001
1002 1002 $ hg init morecornercases
1003 1003 $ cd morecornercases
1004 1004
1005 1005 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1006 1006 > diff --git a/a b/a
1007 1007 > --- a/a
1008 1008 > +++ b/a
1009 1009 > @@ -1,0 +1,1 @@
1010 1010 > +line
1011 1011 > EOF
1012 1012
1013 1013 $ cat > 02-no-context-middle-of-file.diff <<EOF
1014 1014 > diff --git a/a b/a
1015 1015 > --- a/a
1016 1016 > +++ b/a
1017 1017 > @@ -1,1 +1,1 @@
1018 1018 > -2
1019 1019 > +add some skew
1020 1020 > @@ -2,0 +2,1 @@
1021 1021 > +line
1022 1022 > EOF
1023 1023
1024 1024 $ cat > 03-no-context-end-of-file.diff <<EOF
1025 1025 > diff --git a/a b/a
1026 1026 > --- a/a
1027 1027 > +++ b/a
1028 1028 > @@ -10,0 +10,1 @@
1029 1029 > +line
1030 1030 > EOF
1031 1031
1032 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1033 > diff --git a/a b/a
1034 > --- a/a
1035 > +++ b/a
1036 > @@ -1,1 +1,1 @@
1037 > -2
1038 > +add some skew
1039 > @@ -2,2 +2,3 @@
1040 > not matching, should fuzz
1041 > ... a bit
1042 > +line
1043 > EOF
1044
1032 1045 $ cat > a <<EOF
1033 1046 > 1
1034 1047 > 2
1035 1048 > 3
1036 1049 > 4
1037 1050 > EOF
1038 1051 $ hg ci -Am adda a
1039 1052 $ for p in *.diff; do
1040 1053 > hg import -v --no-commit $p
1041 1054 > cat a
1042 1055 > hg revert -aqC a
1043 1056 > # patch -p1 < $p
1044 1057 > # cat a
1045 1058 > # hg revert -aC a
1046 1059 > done
1047 1060 applying 01-no-context-beginning-of-file.diff
1048 1061 patching file a
1049 1062 applied to working directory
1050 1063 1
1051 1064 line
1052 1065 2
1053 1066 3
1054 1067 4
1055 1068 applying 02-no-context-middle-of-file.diff
1056 1069 patching file a
1057 1070 Hunk #1 succeeded at 2 (offset 1 lines).
1058 1071 Hunk #2 succeeded at 4 (offset 1 lines).
1059 1072 applied to working directory
1060 1073 1
1061 1074 add some skew
1062 1075 3
1063 1076 line
1064 1077 4
1065 1078 applying 03-no-context-end-of-file.diff
1066 1079 patching file a
1067 1080 Hunk #1 succeeded at 5 (offset -6 lines).
1068 1081 applied to working directory
1069 1082 1
1070 1083 2
1071 1084 3
1072 1085 4
1073 1086 line
1087 applying 04-middle-of-file-completely-fuzzed.diff
1088 patching file a
1089 Hunk #1 succeeded at 2 (offset 1 lines).
1090 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1091 applied to working directory
1092 1
1093 add some skew
1094 3
1095 4
1096 line
1074 1097
@@ -1,175 +1,173 b''
1 1 Setup extension:
2 2
3 3 $ echo "[extensions]" >> $HGRCPATH
4 4 $ echo "mq =" >> $HGRCPATH
5 5 $ echo "[mq]" >> $HGRCPATH
6 6 $ echo "git = keep" >> $HGRCPATH
7 7
8 8 Test merge with mq changeset as the second parent:
9 9
10 10 $ hg init m
11 11 $ cd m
12 12 $ touch a b c
13 13 $ hg add a
14 14 $ hg commit -m a
15 15 $ hg add b
16 16 $ hg qnew -d "0 0" b
17 17 $ hg update 0
18 18 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
19 19 $ hg add c
20 20 $ hg commit -m c
21 21 created new head
22 22 $ hg merge
23 23 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 24 (branch merge, don't forget to commit)
25 25 $ hg commit -m merge
26 26 abort: cannot commit over an applied mq patch
27 27 [255]
28 28 $ cd ..
29 29
30 30 Issue529: mq aborts when merging patch deleting files
31 31
32 32 $ checkundo()
33 33 > {
34 34 > if [ -f .hg/store/undo ]; then
35 35 > echo ".hg/store/undo still exists"
36 36 > fi
37 37 > }
38 38
39 39 Commit two dummy files in "init" changeset:
40 40
41 41 $ hg init t
42 42 $ cd t
43 43 $ echo a > a
44 44 $ echo b > b
45 45 $ hg ci -Am init
46 46 adding a
47 47 adding b
48 48 $ hg tag -l init
49 49
50 50 Create a patch removing a:
51 51
52 52 $ hg qnew rm_a
53 53 $ hg rm a
54 54 $ hg qrefresh -m "rm a"
55 55
56 56 Save the patch queue so we can merge it later:
57 57
58 58 $ hg qsave -c -e
59 59 copy $TESTTMP/t/.hg/patches to $TESTTMP/t/.hg/patches.1 (glob)
60 60 $ checkundo
61 61
62 62 Update b and commit in an "update" changeset:
63 63
64 64 $ hg up -C init
65 65 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 66 $ echo b >> b
67 67 $ hg st
68 68 M b
69 69 $ hg ci -m update
70 70 created new head
71 71
72 72 # Here, qpush used to abort with :
73 73 # The system cannot find the file specified => a
74 74 $ hg manifest
75 75 a
76 76 b
77 77
78 78 $ hg qpush -a -m
79 79 merging with queue at: $TESTTMP/t/.hg/patches.1 (glob)
80 80 applying rm_a
81 81 now at: rm_a
82 82
83 83 $ checkundo
84 84 $ hg manifest
85 85 b
86 86
87 87 Ensure status is correct after merge:
88 88
89 89 $ hg qpop -a
90 90 popping rm_a
91 91 popping .hg.patches.merge.marker
92 92 patch queue now empty
93 93
94 94 $ cd ..
95 95
96 96 Classic MQ merge sequence *with an explicit named queue*:
97 97
98 98 $ hg init t2
99 99 $ cd t2
100 100 $ echo '[diff]' > .hg/hgrc
101 101 $ echo 'nodates = 1' >> .hg/hgrc
102 102 $ echo a > a
103 103 $ hg ci -Am init
104 104 adding a
105 105 $ echo b > a
106 106 $ hg ci -m changea
107 107 $ hg up -C 0
108 108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 109 $ hg cp a aa
110 110 $ echo c >> a
111 111 $ hg qnew --git -f -e patcha
112 112 $ echo d >> a
113 113 $ hg qnew -d '0 0' -f -e patcha2
114 114
115 115 Create the reference queue:
116 116
117 117 $ hg qsave -c -e -n refqueue
118 118 copy $TESTTMP/t2/.hg/patches to $TESTTMP/t2/.hg/refqueue (glob)
119 119 $ hg up -C 1
120 120 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
121 121
122 122 Merge:
123 123
124 124 $ HGMERGE=internal:other hg qpush -a -m -n refqueue
125 125 merging with queue at: $TESTTMP/t2/.hg/refqueue (glob)
126 126 applying patcha
127 127 patching file a
128 Hunk #1 FAILED at 0
129 1 out of 1 hunks FAILED -- saving rejects to file a.rej
130 patch failed, unable to continue (try -v)
131 patch failed, rejects left in working dir
128 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
129 fuzz found when applying patch, stopping
132 130 patch didn't work out, merging patcha
133 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
131 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
134 132 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
135 133 (branch merge, don't forget to commit)
136 134 applying patcha2
137 135 now at: patcha2
138 136
139 137 Check patcha is still a git patch:
140 138
141 139 $ cat .hg/patches/patcha
142 140 # HG changeset patch
143 141 # Parent d3873e73d99ef67873dac33fbcc66268d5d2b6f4
144 142
145 143 diff --git a/a b/a
146 144 --- a/a
147 145 +++ b/a
148 146 @@ -1,1 +1,2 @@
149 147 -b
150 148 +a
151 149 +c
152 150 diff --git a/aa b/aa
153 151 new file mode 100644
154 152 --- /dev/null
155 153 +++ b/aa
156 154 @@ -0,0 +1,1 @@
157 155 +a
158 156
159 157 Check patcha2 is still a regular patch:
160 158
161 159 $ cat .hg/patches/patcha2
162 160 # HG changeset patch
163 161 # Parent ???????????????????????????????????????? (glob)
164 162 # Date 0 0
165 163
166 164 diff -r ???????????? -r ???????????? a (glob)
167 165 --- a/a
168 166 +++ b/a
169 167 @@ -1,2 +1,3 @@
170 168 a
171 169 c
172 170 +d
173 171
174 172 $ cd ..
175 173
General Comments 0
You need to be logged in to leave comments. Login now