mercurial/patch.py
changeset 5650 5d3e2f918d65
parent 5649 a583117b536a
child 5651 e11940d84606
equal deleted inserted replaced
5649:a583117b536a 5650:5d3e2f918d65
   836             l = self.buf[0]
   836             l = self.buf[0]
   837             del self.buf[0]
   837             del self.buf[0]
   838             return l
   838             return l
   839         return self.fp.readline()
   839         return self.fp.readline()
   840 
   840 
   841 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
   841 def iterhunks(ui, fp, sourcefile=None):
   842               rejmerge=None, updatedir=None):
   842     """Read a patch and yield the following events:
   843     """reads a patch from fp and tries to apply it.  The dict 'changed' is
   843     - ("file", afile, bfile, firsthunk): select a new target file.
   844        filled in with all of the filenames changed by the patch.  Returns 0
   844     - ("hunk", hunk): a new hunk is ready to be applied, follows a
   845        for a clean patch, -1 if any rejects were found and 1 if there was
   845     "file" event.
   846        any fuzz."""
   846     - ("git", gitchanges): current diff is in git format, gitchanges
   847 
   847     maps filenames to gitpatch records. Unique event.
   848     def scangitpatch(fp, firstline, cwd=None):
   848     """
       
   849 
       
   850     def scangitpatch(fp, firstline):
   849         '''git patches can modify a file, then copy that file to
   851         '''git patches can modify a file, then copy that file to
   850         a new file, but expect the source to be the unmodified form.
   852         a new file, but expect the source to be the unmodified form.
   851         So we scan the patch looking for that case so we can do
   853         So we scan the patch looking for that case so we can do
   852         the copies ahead of time.'''
   854         the copies ahead of time.'''
   853 
   855 
   856             pos = fp.tell()
   858             pos = fp.tell()
   857         except IOError:
   859         except IOError:
   858             fp = cStringIO.StringIO(fp.read())
   860             fp = cStringIO.StringIO(fp.read())
   859 
   861 
   860         (dopatch, gitpatches) = readgitpatch(fp, firstline)
   862         (dopatch, gitpatches) = readgitpatch(fp, firstline)
   861         for gp in gitpatches:
       
   862             if gp.op in ('COPY', 'RENAME'):
       
   863                 copyfile(gp.oldpath, gp.path, basedir=cwd)
       
   864 
       
   865         fp.seek(pos)
   863         fp.seek(pos)
   866 
   864 
   867         return fp, dopatch, gitpatches
   865         return fp, dopatch, gitpatches
   868 
   866 
       
   867     changed = {}
   869     current_hunk = None
   868     current_hunk = None
   870     current_file = None
       
   871     afile = ""
   869     afile = ""
   872     bfile = ""
   870     bfile = ""
   873     state = None
   871     state = None
   874     hunknum = 0
   872     hunknum = 0
   875     rejects = 0
   873     emitfile = False
   876 
   874 
   877     git = False
   875     git = False
   878     gitre = re.compile('diff --git (a/.*) (b/.*)')
   876     gitre = re.compile('diff --git (a/.*) (b/.*)')
   879 
   877 
   880     # our states
   878     # our states
   881     BFILE = 1
   879     BFILE = 1
   882     err = 0
       
   883     context = None
   880     context = None
   884     lr = linereader(fp)
   881     lr = linereader(fp)
   885     dopatch = True
   882     dopatch = True
   886     gitworkdone = False
   883     gitworkdone = False
   887 
       
   888     def getpatchfile(afile, bfile, hunk):
       
   889          try:
       
   890              if sourcefile:
       
   891                  targetfile = patchfile(ui, sourcefile)
       
   892              else:
       
   893                  targetfile = selectfile(afile, bfile, hunk,
       
   894                                          strip, reverse)
       
   895                  targetfile = patchfile(ui, targetfile)
       
   896              return targetfile
       
   897          except PatchError, err:
       
   898              ui.warn(str(err) + '\n')
       
   899              return None
       
   900 
   884 
   901     while True:
   885     while True:
   902         newfile = False
   886         newfile = False
   903         x = lr.readline()
   887         x = lr.readline()
   904         if not x:
   888         if not x:
   905             break
   889             break
   906         if current_hunk:
   890         if current_hunk:
   907             if x.startswith('\ '):
   891             if x.startswith('\ '):
   908                 current_hunk.fix_newline()
   892                 current_hunk.fix_newline()
   909             ret = current_file.apply(current_hunk, reverse)
   893             yield 'hunk', current_hunk
   910             if ret >= 0:
       
   911                 changed.setdefault(current_file.fname, (None, None))
       
   912                 if ret > 0:
       
   913                     err = 1
       
   914             current_hunk = None
   894             current_hunk = None
   915             gitworkdone = False
   895             gitworkdone = False
   916         if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
   896         if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
   917             ((context or context == None) and x.startswith('***************')))):
   897             ((context or context == None) and x.startswith('***************')))):
   918             try:
   898             try:
   922             except PatchError, err:
   902             except PatchError, err:
   923                 ui.debug(err)
   903                 ui.debug(err)
   924                 current_hunk = None
   904                 current_hunk = None
   925                 continue
   905                 continue
   926             hunknum += 1
   906             hunknum += 1
   927             if not current_file:
   907             if emitfile:
   928                 current_file = getpatchfile(afile, bfile, current_hunk)
   908                 emitfile = False
   929                 if not current_file:
   909                 yield 'file', (afile, bfile, current_hunk)
   930                     current_file, current_hunk = None, None
       
   931                     rejects += 1
       
   932                     continue
       
   933         elif state == BFILE and x.startswith('GIT binary patch'):
   910         elif state == BFILE and x.startswith('GIT binary patch'):
   934             current_hunk = binhunk(changed[bfile[2:]][1])
   911             current_hunk = binhunk(changed[bfile[2:]][1])
   935             hunknum += 1
   912             hunknum += 1
   936             if not current_file:
   913             if emitfile:
   937                 current_file = getpatchfile(afile, bfile, current_hunk)
   914                 emitfile = False
   938                 if not current_file:
   915                 yield 'file', (afile, bfile, current_hunk)
   939                     current_file, current_hunk = None, None
       
   940                     rejects += 1
       
   941                     continue
       
   942             current_hunk.extract(fp)
   916             current_hunk.extract(fp)
   943         elif x.startswith('diff --git'):
   917         elif x.startswith('diff --git'):
   944             # check for git diff, scanning the whole patch file if needed
   918             # check for git diff, scanning the whole patch file if needed
   945             m = gitre.match(x)
   919             m = gitre.match(x)
   946             if m:
   920             if m:
   947                 afile, bfile = m.group(1, 2)
   921                 afile, bfile = m.group(1, 2)
   948                 if not git:
   922                 if not git:
   949                     git = True
   923                     git = True
   950                     fp, dopatch, gitpatches = scangitpatch(fp, x)
   924                     fp, dopatch, gitpatches = scangitpatch(fp, x)
       
   925                     yield 'git', gitpatches
   951                     for gp in gitpatches:
   926                     for gp in gitpatches:
   952                         changed[gp.path] = (gp.op, gp)
   927                         changed[gp.path] = (gp.op, gp)
   953                 # else error?
   928                 # else error?
   954                 # copy/rename + modify should modify target, not source
   929                 # copy/rename + modify should modify target, not source
   955                 if changed.get(bfile[2:], (None, None))[0] in ('COPY',
   930                 if changed.get(bfile[2:], (None, None))[0] in ('COPY',
   982             context = True
   957             context = True
   983             afile = parsefilename(x)
   958             afile = parsefilename(x)
   984             bfile = parsefilename(l2)
   959             bfile = parsefilename(l2)
   985 
   960 
   986         if newfile:
   961         if newfile:
   987             if current_file:
   962             emitfile = True
   988                 current_file.close()
       
   989                 if rejmerge:
       
   990                     rejmerge(current_file)
       
   991                 rejects += len(current_file.rej)
       
   992             state = BFILE
   963             state = BFILE
   993             current_file = None
       
   994             hunknum = 0
   964             hunknum = 0
   995     if current_hunk:
   965     if current_hunk:
   996         if current_hunk.complete():
   966         if current_hunk.complete():
       
   967             yield 'hunk', current_hunk
       
   968         else:
       
   969             raise PatchError(_("malformed patch %s %s") % (afile,
       
   970                              current_hunk.desc))
       
   971 
       
   972     if hunknum == 0 and dopatch and not gitworkdone:
       
   973         raise NoHunks
       
   974 
       
   975 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
       
   976               rejmerge=None, updatedir=None):
       
   977     """reads a patch from fp and tries to apply it.  The dict 'changed' is
       
   978        filled in with all of the filenames changed by the patch.  Returns 0
       
   979        for a clean patch, -1 if any rejects were found and 1 if there was
       
   980        any fuzz."""
       
   981 
       
   982     rejects = 0
       
   983     err = 0
       
   984     current_file = None
       
   985     gitpatches = None
       
   986 
       
   987     def closefile():
       
   988         if not current_file:
       
   989             return 0
       
   990         current_file.close()
       
   991         if rejmerge:
       
   992             rejmerge(current_file)
       
   993         return len(current_file.rej)
       
   994 
       
   995     for state, values in iterhunks(ui, fp, sourcefile):
       
   996         if state == 'hunk':
       
   997             if not current_file:
       
   998                 continue
       
   999             current_hunk = values
   997             ret = current_file.apply(current_hunk, reverse)
  1000             ret = current_file.apply(current_hunk, reverse)
   998             if ret >= 0:
  1001             if ret >= 0:
   999                 changed.setdefault(current_file.fname, (None, None))
  1002                 changed.setdefault(current_file.fname, (None, None))
  1000                 if ret > 0:
  1003                 if ret > 0:
  1001                     err = 1
  1004                     err = 1
       
  1005         elif state == 'file':
       
  1006             rejects += closefile()
       
  1007             afile, bfile, first_hunk = values
       
  1008             try:
       
  1009                 if sourcefile:
       
  1010                     current_file = patchfile(ui, sourcefile)
       
  1011                 else:
       
  1012                     current_file = selectfile(afile, bfile, first_hunk,
       
  1013                                             strip, reverse)
       
  1014                     current_file = patchfile(ui, current_file)
       
  1015             except PatchError, err:
       
  1016                 ui.warn(str(err) + '\n')
       
  1017                 current_file, current_hunk = None, None
       
  1018                 rejects += 1
       
  1019                 continue
       
  1020         elif state == 'git':
       
  1021             gitpatches = values
       
  1022             for gp in gitpatches:
       
  1023                 if gp.op in ('COPY', 'RENAME'):
       
  1024                     copyfile(gp.oldpath, gp.path)
       
  1025                 changed[gp.path] = (gp.op, gp)                
  1002         else:
  1026         else:
  1003             fname = current_file and current_file.fname or None
  1027             raise util.Abort(_('unsupported parser state: %s') % state)
  1004             raise PatchError(_("malformed patch %s %s") % (fname,
  1028 
  1005                              current_hunk.desc))
  1029     rejects += closefile()
  1006     if current_file:
  1030 
  1007         current_file.close()
  1031     if updatedir and gitpatches:
  1008         if rejmerge:
       
  1009             rejmerge(current_file)
       
  1010         rejects += len(current_file.rej)
       
  1011 
       
  1012     if not rejects and hunknum == 0 and dopatch and not gitworkdone:
       
  1013         raise NoHunks
       
  1014     if updatedir and git:
       
  1015         updatedir(gitpatches)
  1032         updatedir(gitpatches)
  1016     if rejects:
  1033     if rejects:
  1017         return -1
  1034         return -1
  1018     return err
  1035     return err
  1019 
  1036