mercurial/patch.py
changeset 6042 2da5b19a6460
parent 6040 1d0bfa4c75c0
parent 5878 d39af2eabb8c
child 6179 36ab165abbe2
equal deleted inserted replaced
6041:dd714452c26e 6042:2da5b19a6460
     7 # of the GNU General Public License, incorporated herein by reference.
     7 # of the GNU General Public License, incorporated herein by reference.
     8 
     8 
     9 from i18n import _
     9 from i18n import _
    10 from node import *
    10 from node import *
    11 import base85, cmdutil, mdiff, util, context, revlog, diffhelpers
    11 import base85, cmdutil, mdiff, util, context, revlog, diffhelpers
    12 import cStringIO, email.Parser, os, popen2, re, sha
    12 import cStringIO, email.Parser, os, popen2, re, sha, errno
    13 import sys, tempfile, zlib
    13 import sys, tempfile, zlib
    14 
    14 
    15 class PatchError(Exception):
    15 class PatchError(Exception):
    16     pass
    16     pass
    17 
    17 
    57     try:
    57     try:
    58         msg = email.Parser.Parser().parse(fileobj)
    58         msg = email.Parser.Parser().parse(fileobj)
    59 
    59 
    60         subject = msg['Subject']
    60         subject = msg['Subject']
    61         user = msg['From']
    61         user = msg['From']
       
    62         gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
    62         # should try to parse msg['Date']
    63         # should try to parse msg['Date']
    63         date = None
    64         date = None
    64         nodeid = None
    65         nodeid = None
    65         branch = None
    66         branch = None
    66         parents = []
    67         parents = []
   109                             branch = line[9:]
   110                             branch = line[9:]
   110                         elif line.startswith("# Node ID "):
   111                         elif line.startswith("# Node ID "):
   111                             nodeid = line[10:]
   112                             nodeid = line[10:]
   112                         elif line.startswith("# Parent "):
   113                         elif line.startswith("# Parent "):
   113                             parents.append(line[10:])
   114                             parents.append(line[10:])
   114                     elif line == '---' and 'git-send-email' in msg['X-Mailer']:
   115                     elif line == '---' and gitsendmail:
   115                         ignoretext = True
   116                         ignoretext = True
   116                     if not line.startswith('# ') and not ignoretext:
   117                     if not line.startswith('# ') and not ignoretext:
   117                         cfp.write(line)
   118                         cfp.write(line)
   118                         cfp.write('\n')
   119                         cfp.write('\n')
   119                 message = cfp.getvalue()
   120                 message = cfp.getvalue()
   140 
   141 
   141 GP_PATCH  = 1 << 0  # we have to run patch
   142 GP_PATCH  = 1 << 0  # we have to run patch
   142 GP_FILTER = 1 << 1  # there's some copy/rename operation
   143 GP_FILTER = 1 << 1  # there's some copy/rename operation
   143 GP_BINARY = 1 << 2  # there's a binary patch
   144 GP_BINARY = 1 << 2  # there's a binary patch
   144 
   145 
   145 def readgitpatch(fp, firstline):
   146 def readgitpatch(fp, firstline=None):
   146     """extract git-style metadata about patches from <patchname>"""
   147     """extract git-style metadata about patches from <patchname>"""
   147     class gitpatch:
   148     class gitpatch:
   148         "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
   149         "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
   149         def __init__(self, path):
   150         def __init__(self, path):
   150             self.path = path
   151             self.path = path
   151             self.oldpath = None
   152             self.oldpath = None
   152             self.mode = None
   153             self.mode = None
   153             self.op = 'MODIFY'
   154             self.op = 'MODIFY'
   154             self.copymod = False
       
   155             self.lineno = 0
   155             self.lineno = 0
   156             self.binary = False
   156             self.binary = False
   157 
   157 
   158     def reader(fp, firstline):
   158     def reader(fp, firstline):
   159         yield firstline
   159         if firstline is not None:
       
   160             yield firstline
   160         for line in fp:
   161         for line in fp:
   161             yield line
   162             yield line
   162 
   163 
   163     # Filter patch for git information
   164     # Filter patch for git information
   164     gitre = re.compile('diff --git a/(.*) b/(.*)')
   165     gitre = re.compile('diff --git a/(.*) b/(.*)')
   179                 gp = gitpatch(dst)
   180                 gp = gitpatch(dst)
   180                 gp.lineno = lineno
   181                 gp.lineno = lineno
   181         elif gp:
   182         elif gp:
   182             if line.startswith('--- '):
   183             if line.startswith('--- '):
   183                 if gp.op in ('COPY', 'RENAME'):
   184                 if gp.op in ('COPY', 'RENAME'):
   184                     gp.copymod = True
       
   185                     dopatch |= GP_FILTER
   185                     dopatch |= GP_FILTER
   186                 gitpatches.append(gp)
   186                 gitpatches.append(gp)
   187                 gp = None
   187                 gp = None
   188                 dopatch |= GP_PATCH
   188                 dopatch |= GP_PATCH
   189                 continue
   189                 continue
   199                 gp.path = line[8:].rstrip()
   199                 gp.path = line[8:].rstrip()
   200             elif line.startswith('deleted file'):
   200             elif line.startswith('deleted file'):
   201                 gp.op = 'DELETE'
   201                 gp.op = 'DELETE'
   202             elif line.startswith('new file mode '):
   202             elif line.startswith('new file mode '):
   203                 gp.op = 'ADD'
   203                 gp.op = 'ADD'
   204                 gp.mode = int(line.rstrip()[-3:], 8)
   204                 gp.mode = int(line.rstrip()[-6:], 8)
   205             elif line.startswith('new mode '):
   205             elif line.startswith('new mode '):
   206                 gp.mode = int(line.rstrip()[-3:], 8)
   206                 gp.mode = int(line.rstrip()[-6:], 8)
   207             elif line.startswith('GIT binary patch'):
   207             elif line.startswith('GIT binary patch'):
   208                 dopatch |= GP_BINARY
   208                 dopatch |= GP_BINARY
   209                 gp.binary = True
   209                 gp.binary = True
   210     if gp:
   210     if gp:
   211         gitpatches.append(gp)
   211         gitpatches.append(gp)
   247     returns whether patch was applied with fuzz factor."""
   247     returns whether patch was applied with fuzz factor."""
   248 
   248 
   249     fuzz = False
   249     fuzz = False
   250     if cwd:
   250     if cwd:
   251         args.append('-d %s' % util.shellquote(cwd))
   251         args.append('-d %s' % util.shellquote(cwd))
   252     fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
   252     fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
   253                                        util.shellquote(patchname)))
   253                                        util.shellquote(patchname)))
   254 
   254 
   255     for line in fp:
   255     for line in fp:
   256         line = line.rstrip()
   256         line = line.rstrip()
   257         ui.note(line + '\n')
   257         ui.note(line + '\n')
   276     if code:
   276     if code:
   277         raise PatchError(_("patch command failed: %s") %
   277         raise PatchError(_("patch command failed: %s") %
   278                          util.explain_exit(code)[0])
   278                          util.explain_exit(code)[0])
   279     return fuzz
   279     return fuzz
   280 
   280 
   281 def internalpatch(patchname, ui, strip, cwd, files):
   281 def internalpatch(patchobj, ui, strip, cwd, files={}):
   282     """use builtin patch to apply <patchname> to the working directory.
   282     """use builtin patch to apply <patchobj> to the working directory.
   283     returns whether patch was applied with fuzz factor."""
   283     returns whether patch was applied with fuzz factor."""
   284     fp = file(patchname, 'rb')
   284     try:
       
   285         fp = file(patchobj, 'rb')
       
   286     except TypeError:
       
   287         fp = patchobj
   285     if cwd:
   288     if cwd:
   286         curdir = os.getcwd()
   289         curdir = os.getcwd()
   287         os.chdir(cwd)
   290         os.chdir(cwd)
   288     try:
   291     try:
   289         ret = applydiff(ui, fp, files, strip=strip)
   292         ret = applydiff(ui, fp, files, strip=strip)
   297 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
   300 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
   298 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
   301 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
   299 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
   302 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
   300 
   303 
   301 class patchfile:
   304 class patchfile:
   302     def __init__(self, ui, fname):
   305     def __init__(self, ui, fname, missing=False):
   303         self.fname = fname
   306         self.fname = fname
   304         self.ui = ui
   307         self.ui = ui
   305         try:
   308         self.lines = []
   306             fp = file(fname, 'rb')
   309         self.exists = False
   307             self.lines = fp.readlines()
   310         self.missing = missing
   308             self.exists = True
   311         if not missing:
   309         except IOError:
   312             try:
       
   313                 fp = file(fname, 'rb')
       
   314                 self.lines = fp.readlines()
       
   315                 self.exists = True
       
   316             except IOError:
       
   317                 pass
       
   318         else:
       
   319             self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
       
   320 
       
   321         if not self.exists:
   310             dirname = os.path.dirname(fname)
   322             dirname = os.path.dirname(fname)
   311             if dirname and not os.path.isdir(dirname):
   323             if dirname and not os.path.isdir(dirname):
   312                 dirs = dirname.split(os.path.sep)
   324                 os.makedirs(dirname)
   313                 d = ""
   325 
   314                 for x in dirs:
       
   315                     d = os.path.join(d, x)
       
   316                     if not os.path.isdir(d):
       
   317                         os.mkdir(d)
       
   318             self.lines = []
       
   319             self.exists = False
       
   320             
       
   321         self.hash = {}
   326         self.hash = {}
   322         self.dirty = 0
   327         self.dirty = 0
   323         self.offset = 0
   328         self.offset = 0
   324         self.rej = []
   329         self.rej = []
   325         self.fileprinted = False
   330         self.fileprinted = False
   344         # from linenum
   349         # from linenum
   345         def sorter(a, b):
   350         def sorter(a, b):
   346             vala = abs(a - linenum)
   351             vala = abs(a - linenum)
   347             valb = abs(b - linenum)
   352             valb = abs(b - linenum)
   348             return cmp(vala, valb)
   353             return cmp(vala, valb)
   349             
   354 
   350         try:
   355         try:
   351             cand = self.hash[l]
   356             cand = self.hash[l]
   352         except:
   357         except:
   353             return []
   358             return []
   354 
   359 
   355         if len(cand) > 1:
   360         if len(cand) > 1:
   356             # resort our list of potentials forward then back.
   361             # resort our list of potentials forward then back.
   357             cand.sort(cmp=sorter)
   362             cand.sort(sorter)
   358         return cand
   363         return cand
   359 
   364 
   360     def hashlines(self):
   365     def hashlines(self):
   361         self.hash = {}
   366         self.hash = {}
   362         for x in xrange(len(self.lines)):
   367         for x in xrange(len(self.lines)):
   397             if not dest:
   402             if not dest:
   398                 dest = self.fname
   403                 dest = self.fname
   399             st = None
   404             st = None
   400             try:
   405             try:
   401                 st = os.lstat(dest)
   406                 st = os.lstat(dest)
   402                 if st.st_nlink > 1:
   407             except OSError, inst:
   403                     os.unlink(dest)
   408                 if inst.errno != errno.ENOENT:
   404             except: pass
   409                     raise
       
   410             if st and st.st_nlink > 1:
       
   411                 os.unlink(dest)
   405             fp = file(dest, 'wb')
   412             fp = file(dest, 'wb')
   406             if st:
   413             if st and st.st_nlink > 1:
   407                 os.chmod(dest, st.st_mode)
   414                 os.chmod(dest, st.st_mode)
   408             fp.writelines(self.lines)
   415             fp.writelines(self.lines)
   409             fp.close()
   416             fp.close()
   410 
   417 
   411     def close(self):
   418     def close(self):
   419                             h.lenb))
   426                             h.lenb))
   420 
   427 
   421         self.hunks += 1
   428         self.hunks += 1
   422         if reverse:
   429         if reverse:
   423             h.reverse()
   430             h.reverse()
       
   431 
       
   432         if self.missing:
       
   433             self.rej.append(h)
       
   434             return -1
   424 
   435 
   425         if self.exists and h.createfile():
   436         if self.exists and h.createfile():
   426             self.ui.warn(_("file %s already exists\n") % self.fname)
   437             self.ui.warn(_("file %s already exists\n") % self.fname)
   427             self.rej.append(h)
   438             self.rej.append(h)
   428             return -1
   439             return -1
   492         self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
   503         self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
   493         self.rej.append(h)
   504         self.rej.append(h)
   494         return -1
   505         return -1
   495 
   506 
   496 class hunk:
   507 class hunk:
   497     def __init__(self, desc, num, lr, context):
   508     def __init__(self, desc, num, lr, context, gitpatch=None):
   498         self.number = num
   509         self.number = num
   499         self.desc = desc
   510         self.desc = desc
   500         self.hunk = [ desc ]
   511         self.hunk = [ desc ]
   501         self.a = []
   512         self.a = []
   502         self.b = []
   513         self.b = []
   503         if context:
   514         if context:
   504             self.read_context_hunk(lr)
   515             self.read_context_hunk(lr)
   505         else:
   516         else:
   506             self.read_unified_hunk(lr)
   517             self.read_unified_hunk(lr)
       
   518         self.gitpatch = gitpatch
   507 
   519 
   508     def read_unified_hunk(self, lr):
   520     def read_unified_hunk(self, lr):
   509         m = unidesc.match(self.desc)
   521         m = unidesc.match(self.desc)
   510         if not m:
   522         if not m:
   511             raise PatchError(_("bad hunk #%d") % self.number)
   523             raise PatchError(_("bad hunk #%d") % self.number)
   656 
   668 
   657     def complete(self):
   669     def complete(self):
   658         return len(self.a) == self.lena and len(self.b) == self.lenb
   670         return len(self.a) == self.lena and len(self.b) == self.lenb
   659 
   671 
   660     def createfile(self):
   672     def createfile(self):
   661         return self.starta == 0 and self.lena == 0
   673         create = self.gitpatch is None or self.gitpatch.op == 'ADD'
       
   674         return self.starta == 0 and self.lena == 0 and create
   662 
   675 
   663     def rmfile(self):
   676     def rmfile(self):
   664         return self.startb == 0 and self.lenb == 0
   677         remove = self.gitpatch is None or self.gitpatch.op == 'DELETE'
       
   678         return self.startb == 0 and self.lenb == 0 and remove
   665 
   679 
   666     def fuzzit(self, l, fuzz, toponly):
   680     def fuzzit(self, l, fuzz, toponly):
   667         # this removes context lines from the top and bottom of list 'l'.  It
   681         # this removes context lines from the top and bottom of list 'l'.  It
   668         # checks the hunk to make sure only context lines are removed, and then
   682         # checks the hunk to make sure only context lines are removed, and then
   669         # returns a new shortened list of lines.
   683         # returns a new shortened list of lines.
   700             return l[top:len(l)-bot]
   714             return l[top:len(l)-bot]
   701         return l
   715         return l
   702 
   716 
   703     def old(self, fuzz=0, toponly=False):
   717     def old(self, fuzz=0, toponly=False):
   704         return self.fuzzit(self.a, fuzz, toponly)
   718         return self.fuzzit(self.a, fuzz, toponly)
   705         
   719 
   706     def newctrl(self):
   720     def newctrl(self):
   707         res = []
   721         res = []
   708         for x in self.hunk:
   722         for x in self.hunk:
   709             c = x[0]
   723             c = x[0]
   710             if c == ' ' or c == '+':
   724             if c == ' ' or c == '+':
   760                              len(text), size)
   774                              len(text), size)
   761         self.text = text
   775         self.text = text
   762 
   776 
   763 def parsefilename(str):
   777 def parsefilename(str):
   764     # --- filename \t|space stuff
   778     # --- filename \t|space stuff
   765     s = str[4:]
   779     s = str[4:].rstrip('\r\n')
   766     i = s.find('\t')
   780     i = s.find('\t')
   767     if i < 0:
   781     if i < 0:
   768         i = s.find(' ')
   782         i = s.find(' ')
   769         if i < 0:
   783         if i < 0:
   770             return s
   784             return s
   789         return path[i:].rstrip()
   803         return path[i:].rstrip()
   790 
   804 
   791     nulla = afile_orig == "/dev/null"
   805     nulla = afile_orig == "/dev/null"
   792     nullb = bfile_orig == "/dev/null"
   806     nullb = bfile_orig == "/dev/null"
   793     afile = pathstrip(afile_orig, strip)
   807     afile = pathstrip(afile_orig, strip)
   794     gooda = os.path.exists(afile) and not nulla
   808     gooda = not nulla and os.path.exists(afile)
   795     bfile = pathstrip(bfile_orig, strip)
   809     bfile = pathstrip(bfile_orig, strip)
   796     if afile == bfile:
   810     if afile == bfile:
   797         goodb = gooda
   811         goodb = gooda
   798     else:
   812     else:
   799         goodb = os.path.exists(bfile) and not nullb
   813         goodb = not nullb and os.path.exists(bfile)
   800     createfunc = hunk.createfile
   814     createfunc = hunk.createfile
   801     if reverse:
   815     if reverse:
   802         createfunc = hunk.rmfile
   816         createfunc = hunk.rmfile
   803     if not goodb and not gooda and not createfunc():
   817     missing = not goodb and not gooda and not createfunc()
   804         raise PatchError(_("unable to find %s or %s for patching") %
   818     fname = None
   805                          (afile, bfile))
   819     if not missing:
   806     if gooda and goodb:
   820         if gooda and goodb:
   807         fname = bfile
   821             fname = (afile in bfile) and afile or bfile
   808         if afile in bfile:
   822         elif gooda:
   809             fname = afile
   823             fname = afile
   810     elif gooda:
   824 
   811         fname = afile
   825     if not fname:
   812     elif not nullb:
   826         if not nullb:
   813         fname = bfile
   827             fname = (afile in bfile) and afile or bfile
   814         if afile in bfile:
   828         elif not nulla:
   815             fname = afile
   829             fname = afile
   816     elif not nulla:
   830         else:
   817         fname = afile
   831             raise PatchError(_("undefined source and destination files"))
   818     return fname
   832 
       
   833     return fname, missing
   819 
   834 
   820 class linereader:
   835 class linereader:
   821     # simple class to allow pushing lines back into the input stream
   836     # simple class to allow pushing lines back into the input stream
   822     def __init__(self, fp):
   837     def __init__(self, fp):
   823         self.fp = fp
   838         self.fp = fp
   831             l = self.buf[0]
   846             l = self.buf[0]
   832             del self.buf[0]
   847             del self.buf[0]
   833             return l
   848             return l
   834         return self.fp.readline()
   849         return self.fp.readline()
   835 
   850 
   836 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
   851 def iterhunks(ui, fp, sourcefile=None):
   837               rejmerge=None, updatedir=None):
   852     """Read a patch and yield the following events:
   838     """reads a patch from fp and tries to apply it.  The dict 'changed' is
   853     - ("file", afile, bfile, firsthunk): select a new target file.
   839        filled in with all of the filenames changed by the patch.  Returns 0
   854     - ("hunk", hunk): a new hunk is ready to be applied, follows a
   840        for a clean patch, -1 if any rejects were found and 1 if there was
   855     "file" event.
   841        any fuzz.""" 
   856     - ("git", gitchanges): current diff is in git format, gitchanges
   842 
   857     maps filenames to gitpatch records. Unique event.
   843     def scangitpatch(fp, firstline, cwd=None):
   858     """
       
   859 
       
   860     def scangitpatch(fp, firstline):
   844         '''git patches can modify a file, then copy that file to
   861         '''git patches can modify a file, then copy that file to
   845         a new file, but expect the source to be the unmodified form.
   862         a new file, but expect the source to be the unmodified form.
   846         So we scan the patch looking for that case so we can do
   863         So we scan the patch looking for that case so we can do
   847         the copies ahead of time.'''
   864         the copies ahead of time.'''
   848 
   865 
   851             pos = fp.tell()
   868             pos = fp.tell()
   852         except IOError:
   869         except IOError:
   853             fp = cStringIO.StringIO(fp.read())
   870             fp = cStringIO.StringIO(fp.read())
   854 
   871 
   855         (dopatch, gitpatches) = readgitpatch(fp, firstline)
   872         (dopatch, gitpatches) = readgitpatch(fp, firstline)
   856         for gp in gitpatches:
       
   857             if gp.copymod:
       
   858                 copyfile(gp.oldpath, gp.path, basedir=cwd)
       
   859 
       
   860         fp.seek(pos)
   873         fp.seek(pos)
   861 
   874 
   862         return fp, dopatch, gitpatches
   875         return fp, dopatch, gitpatches
   863 
   876 
       
   877     changed = {}
   864     current_hunk = None
   878     current_hunk = None
   865     current_file = None
       
   866     afile = ""
   879     afile = ""
   867     bfile = ""
   880     bfile = ""
   868     state = None
   881     state = None
   869     hunknum = 0
   882     hunknum = 0
   870     rejects = 0
   883     emitfile = False
   871 
   884 
   872     git = False
   885     git = False
   873     gitre = re.compile('diff --git (a/.*) (b/.*)')
   886     gitre = re.compile('diff --git (a/.*) (b/.*)')
   874 
   887 
   875     # our states
   888     # our states
   876     BFILE = 1
   889     BFILE = 1
   877     err = 0
       
   878     context = None
   890     context = None
   879     lr = linereader(fp)
   891     lr = linereader(fp)
   880     dopatch = True
   892     dopatch = True
   881     gitworkdone = False
   893     gitworkdone = False
   882 
   894 
   886         if not x:
   898         if not x:
   887             break
   899             break
   888         if current_hunk:
   900         if current_hunk:
   889             if x.startswith('\ '):
   901             if x.startswith('\ '):
   890                 current_hunk.fix_newline()
   902                 current_hunk.fix_newline()
   891             ret = current_file.apply(current_hunk, reverse)
   903             yield 'hunk', current_hunk
   892             if ret >= 0:
       
   893                 changed.setdefault(current_file.fname, (None, None))
       
   894                 if ret > 0:
       
   895                     err = 1
       
   896             current_hunk = None
   904             current_hunk = None
   897             gitworkdone = False
   905             gitworkdone = False
   898         if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
   906         if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
   899             ((context or context == None) and x.startswith('***************')))):
   907             ((context or context == None) and x.startswith('***************')))):
   900             try:
   908             try:
   901                 if context == None and x.startswith('***************'):
   909                 if context == None and x.startswith('***************'):
   902                     context = True
   910                     context = True
   903                 current_hunk = hunk(x, hunknum + 1, lr, context)
   911                 gpatch = changed.get(bfile[2:], (None, None))[1]
       
   912                 current_hunk = hunk(x, hunknum + 1, lr, context, gpatch)
   904             except PatchError, err:
   913             except PatchError, err:
   905                 ui.debug(err)
   914                 ui.debug(err)
   906                 current_hunk = None
   915                 current_hunk = None
   907                 continue
   916                 continue
   908             hunknum += 1
   917             hunknum += 1
   909             if not current_file:
   918             if emitfile:
   910                 if sourcefile:
   919                 emitfile = False
   911                     current_file = patchfile(ui, sourcefile)
   920                 yield 'file', (afile, bfile, current_hunk)
   912                 else:
       
   913                     current_file = selectfile(afile, bfile, current_hunk,
       
   914                                               strip, reverse)
       
   915                     current_file = patchfile(ui, current_file)
       
   916         elif state == BFILE and x.startswith('GIT binary patch'):
   921         elif state == BFILE and x.startswith('GIT binary patch'):
   917             current_hunk = binhunk(changed[bfile[2:]][1])
   922             current_hunk = binhunk(changed[bfile[2:]][1])
   918             if not current_file:
       
   919                 if sourcefile:
       
   920                     current_file = patchfile(ui, sourcefile)
       
   921                 else:
       
   922                     current_file = selectfile(afile, bfile, current_hunk,
       
   923                                               strip, reverse)
       
   924                     current_file = patchfile(ui, current_file)
       
   925             hunknum += 1
   923             hunknum += 1
       
   924             if emitfile:
       
   925                 emitfile = False
       
   926                 yield 'file', (afile, bfile, current_hunk)
   926             current_hunk.extract(fp)
   927             current_hunk.extract(fp)
   927         elif x.startswith('diff --git'):
   928         elif x.startswith('diff --git'):
   928             # check for git diff, scanning the whole patch file if needed
   929             # check for git diff, scanning the whole patch file if needed
   929             m = gitre.match(x)
   930             m = gitre.match(x)
   930             if m:
   931             if m:
   931                 afile, bfile = m.group(1, 2)
   932                 afile, bfile = m.group(1, 2)
   932                 if not git:
   933                 if not git:
   933                     git = True
   934                     git = True
   934                     fp, dopatch, gitpatches = scangitpatch(fp, x)
   935                     fp, dopatch, gitpatches = scangitpatch(fp, x)
       
   936                     yield 'git', gitpatches
   935                     for gp in gitpatches:
   937                     for gp in gitpatches:
   936                         changed[gp.path] = (gp.op, gp)
   938                         changed[gp.path] = (gp.op, gp)
   937                 # else error?
   939                 # else error?
   938                 # copy/rename + modify should modify target, not source
   940                 # copy/rename + modify should modify target, not source
   939                 if changed.get(bfile[2:], (None, None))[0] in ('COPY',
   941                 if changed.get(bfile[2:], (None, None))[0] in ('COPY',
   966             context = True
   968             context = True
   967             afile = parsefilename(x)
   969             afile = parsefilename(x)
   968             bfile = parsefilename(l2)
   970             bfile = parsefilename(l2)
   969 
   971 
   970         if newfile:
   972         if newfile:
   971             if current_file:
   973             emitfile = True
   972                 current_file.close()
       
   973                 if rejmerge:
       
   974                     rejmerge(current_file)
       
   975                 rejects += len(current_file.rej)
       
   976             state = BFILE
   974             state = BFILE
   977             current_file = None
       
   978             hunknum = 0
   975             hunknum = 0
   979     if current_hunk:
   976     if current_hunk:
   980         if current_hunk.complete():
   977         if current_hunk.complete():
       
   978             yield 'hunk', current_hunk
       
   979         else:
       
   980             raise PatchError(_("malformed patch %s %s") % (afile,
       
   981                              current_hunk.desc))
       
   982 
       
   983     if hunknum == 0 and dopatch and not gitworkdone:
       
   984         raise NoHunks
       
   985 
       
   986 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
       
   987               rejmerge=None, updatedir=None):
       
   988     """reads a patch from fp and tries to apply it.  The dict 'changed' is
       
   989        filled in with all of the filenames changed by the patch.  Returns 0
       
   990        for a clean patch, -1 if any rejects were found and 1 if there was
       
   991        any fuzz."""
       
   992 
       
   993     rejects = 0
       
   994     err = 0
       
   995     current_file = None
       
   996     gitpatches = None
       
   997 
       
   998     def closefile():
       
   999         if not current_file:
       
  1000             return 0
       
  1001         current_file.close()
       
  1002         if rejmerge:
       
  1003             rejmerge(current_file)
       
  1004         return len(current_file.rej)
       
  1005 
       
  1006     for state, values in iterhunks(ui, fp, sourcefile):
       
  1007         if state == 'hunk':
       
  1008             if not current_file:
       
  1009                 continue
       
  1010             current_hunk = values
   981             ret = current_file.apply(current_hunk, reverse)
  1011             ret = current_file.apply(current_hunk, reverse)
   982             if ret >= 0:
  1012             if ret >= 0:
   983                 changed.setdefault(current_file.fname, (None, None))
  1013                 changed.setdefault(current_file.fname, (None, None))
   984                 if ret > 0:
  1014                 if ret > 0:
   985                     err = 1
  1015                     err = 1
       
  1016         elif state == 'file':
       
  1017             rejects += closefile()
       
  1018             afile, bfile, first_hunk = values
       
  1019             try:
       
  1020                 if sourcefile:
       
  1021                     current_file = patchfile(ui, sourcefile)
       
  1022                 else:
       
  1023                     current_file, missing = selectfile(afile, bfile, first_hunk,
       
  1024                                             strip, reverse)
       
  1025                     current_file = patchfile(ui, current_file, missing)
       
  1026             except PatchError, err:
       
  1027                 ui.warn(str(err) + '\n')
       
  1028                 current_file, current_hunk = None, None
       
  1029                 rejects += 1
       
  1030                 continue
       
  1031         elif state == 'git':
       
  1032             gitpatches = values
       
  1033             for gp in gitpatches:
       
  1034                 if gp.op in ('COPY', 'RENAME'):
       
  1035                     copyfile(gp.oldpath, gp.path)
       
  1036                 changed[gp.path] = (gp.op, gp)
   986         else:
  1037         else:
   987             fname = current_file and current_file.fname or None
  1038             raise util.Abort(_('unsupported parser state: %s') % state)
   988             raise PatchError(_("malformed patch %s %s") % (fname,
  1039 
   989                              current_hunk.desc))
  1040     rejects += closefile()
   990     if current_file:
  1041 
   991         current_file.close()
  1042     if updatedir and gitpatches:
   992         if rejmerge:
       
   993             rejmerge(current_file)
       
   994         rejects += len(current_file.rej)
       
   995     if updatedir and git:
       
   996         updatedir(gitpatches)
  1043         updatedir(gitpatches)
   997     if rejects:
  1044     if rejects:
   998         return -1
  1045         return -1
   999     if hunknum == 0 and dopatch and not gitworkdone:
       
  1000         raise NoHunks
       
  1001     return err
  1046     return err
  1002 
  1047 
  1003 def diffopts(ui, opts={}, untrusted=False):
  1048 def diffopts(ui, opts={}, untrusted=False):
  1004     def get(key, name=None):
  1049     def get(key, name=None):
  1005         return (opts.get(key) or
  1050         return (opts.get(key) or
  1025     if cwd:
  1070     if cwd:
  1026         cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
  1071         cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
  1027     for f in patches:
  1072     for f in patches:
  1028         ctype, gp = patches[f]
  1073         ctype, gp = patches[f]
  1029         if ctype == 'RENAME':
  1074         if ctype == 'RENAME':
  1030             copies.append((gp.oldpath, gp.path, gp.copymod))
  1075             copies.append((gp.oldpath, gp.path))
  1031             removes[gp.oldpath] = 1
  1076             removes[gp.oldpath] = 1
  1032         elif ctype == 'COPY':
  1077         elif ctype == 'COPY':
  1033             copies.append((gp.oldpath, gp.path, gp.copymod))
  1078             copies.append((gp.oldpath, gp.path))
  1034         elif ctype == 'DELETE':
  1079         elif ctype == 'DELETE':
  1035             removes[gp.path] = 1
  1080             removes[gp.path] = 1
  1036     for src, dst, after in copies:
  1081     for src, dst in copies:
  1037         if not after:
       
  1038             copyfile(src, dst, repo.root)
       
  1039         repo.copy(src, dst)
  1082         repo.copy(src, dst)
  1040     removes = removes.keys()
  1083     removes = removes.keys()
  1041     if removes:
  1084     if removes:
  1042         removes.sort()
  1085         removes.sort()
  1043         repo.remove(removes, True)
  1086         repo.remove(removes, True)
  1044     for f in patches:
  1087     for f in patches:
  1045         ctype, gp = patches[f]
  1088         ctype, gp = patches[f]
  1046         if gp and gp.mode:
  1089         if gp and gp.mode:
  1047             x = gp.mode & 0100 != 0
  1090             flags = ''
       
  1091             if gp.mode & 0100:
       
  1092                 flags = 'x'
       
  1093             elif gp.mode & 020000:
       
  1094                 flags = 'l'
  1048             dst = os.path.join(repo.root, gp.path)
  1095             dst = os.path.join(repo.root, gp.path)
  1049             # patch won't create empty files
  1096             # patch won't create empty files
  1050             if ctype == 'ADD' and not os.path.exists(dst):
  1097             if ctype == 'ADD' and not os.path.exists(dst):
  1051                 repo.wwrite(gp.path, '', x and 'x' or '')
  1098                 repo.wwrite(gp.path, '', flags)
  1052             else:
  1099             else:
  1053                 util.set_exec(dst, x)
  1100                 util.set_flags(dst, flags)
  1054     cmdutil.addremove(repo, cfiles)
  1101     cmdutil.addremove(repo, cfiles)
  1055     files = patches.keys()
  1102     files = patches.keys()
  1056     files.extend([r for r in removes if r not in files])
  1103     files.extend([r for r in removes if r not in files])
  1057     files.sort()
  1104     files.sort()
  1058 
  1105 
  1059     return files
  1106     return files
  1060 
  1107 
  1061 def b85diff(fp, to, tn):
  1108 def b85diff(to, tn):
  1062     '''print base85-encoded binary diff'''
  1109     '''print base85-encoded binary diff'''
  1063     def gitindex(text):
  1110     def gitindex(text):
  1064         if not text:
  1111         if not text:
  1065             return '0' * 40
  1112             return '0' * 40
  1066         l = len(text)
  1113         l = len(text)
  1140         return
  1187         return
  1141 
  1188 
  1142     if node2:
  1189     if node2:
  1143         ctx2 = context.changectx(repo, node2)
  1190         ctx2 = context.changectx(repo, node2)
  1144         execf2 = ctx2.manifest().execf
  1191         execf2 = ctx2.manifest().execf
       
  1192         linkf2 = ctx2.manifest().linkf
  1145     else:
  1193     else:
  1146         ctx2 = context.workingctx(repo)
  1194         ctx2 = context.workingctx(repo)
  1147         execf2 = util.execfunc(repo.root, None)
  1195         execf2 = util.execfunc(repo.root, None)
       
  1196         linkf2 = util.linkfunc(repo.root, None)
  1148         if execf2 is None:
  1197         if execf2 is None:
  1149             execf2 = ctx2.parents()[0].manifest().copy().execf
  1198             mc = ctx2.parents()[0].manifest().copy()
       
  1199             execf2 = mc.execf
       
  1200             linkf2 = mc.linkf
  1150 
  1201 
  1151     # returns False if there was no rename between ctx1 and ctx2
  1202     # returns False if there was no rename between ctx1 and ctx2
  1152     # returns None if the file was created between ctx1 and ctx2
  1203     # returns None if the file was created between ctx1 and ctx2
  1153     # returns the (file, node) present in ctx1 that was renamed to f in ctx2
  1204     # returns the (file, node) present in ctx1 that was renamed to f in ctx2
  1154     def renamed(f):
  1205     # This will only really work if c1 is the Nth 1st parent of c2.
  1155         startrev = ctx1.rev()
  1206     def renamed(c1, c2, man, f):
  1156         c = ctx2
  1207         startrev = c1.rev()
       
  1208         c = c2
  1157         crev = c.rev()
  1209         crev = c.rev()
  1158         if crev is None:
  1210         if crev is None:
  1159             crev = repo.changelog.count()
  1211             crev = repo.changelog.count()
  1160         orig = f
  1212         orig = f
       
  1213         files = (f,)
  1161         while crev > startrev:
  1214         while crev > startrev:
  1162             if f in c.files():
  1215             if f in files:
  1163                 try:
  1216                 try:
  1164                     src = getfilectx(f, c).renamed()
  1217                     src = getfilectx(f, c).renamed()
  1165                 except revlog.LookupError:
  1218                 except revlog.LookupError:
  1166                     return None
  1219                     return None
  1167                 if src:
  1220                 if src:
  1168                     f = src[0]
  1221                     f = src[0]
  1169             crev = c.parents()[0].rev()
  1222             crev = c.parents()[0].rev()
  1170             # try to reuse
  1223             # try to reuse
  1171             c = getctx(crev)
  1224             c = getctx(crev)
  1172         if f not in man1:
  1225             files = c.files()
       
  1226         if f not in man:
  1173             return None
  1227             return None
  1174         if f == orig:
  1228         if f == orig:
  1175             return False
  1229             return False
  1176         return f
  1230         return f
  1177 
  1231 
  1181         hexfunc = repo.ui.debugflag and hex or short
  1235         hexfunc = repo.ui.debugflag and hex or short
  1182         r = [hexfunc(node) for node in [node1, node2] if node]
  1236         r = [hexfunc(node) for node in [node1, node2] if node]
  1183 
  1237 
  1184     if opts.git:
  1238     if opts.git:
  1185         copied = {}
  1239         copied = {}
  1186         for f in added:
  1240         c1, c2 = ctx1, ctx2
  1187             src = renamed(f)
  1241         files = added
       
  1242         man = man1
       
  1243         if node2 and ctx1.rev() >= ctx2.rev():
       
  1244             # renamed() starts at c2 and walks back in history until c1.
       
  1245             # Since ctx1.rev() >= ctx2.rev(), invert ctx2 and ctx1 to
       
  1246             # detect (inverted) copies.
       
  1247             c1, c2 = ctx2, ctx1
       
  1248             files = removed
       
  1249             man = ctx2.manifest()
       
  1250         for f in files:
       
  1251             src = renamed(c1, c2, man, f)
  1188             if src:
  1252             if src:
  1189                 copied[f] = src
  1253                 copied[f] = src
  1190         srcs = [x[1] for x in copied.items()]
  1254         if ctx1 == c2:
       
  1255             # invert the copied dict
       
  1256             copied = dict([(v, k) for (k, v) in copied.iteritems()])
       
  1257         # If we've renamed file foo to bar (copied['bar'] = 'foo'),
       
  1258         # avoid showing a diff for foo if we're going to show
       
  1259         # the rename to bar.
       
  1260         srcs = [x[1] for x in copied.iteritems() if x[0] in added]
  1191 
  1261 
  1192     all = modified + added + removed
  1262     all = modified + added + removed
  1193     all.sort()
  1263     all.sort()
  1194     gone = {}
  1264     gone = {}
  1195 
  1265 
  1200         header = []
  1270         header = []
  1201         if f in man1:
  1271         if f in man1:
  1202             to = getfilectx(f, ctx1).data()
  1272             to = getfilectx(f, ctx1).data()
  1203         if f not in removed:
  1273         if f not in removed:
  1204             tn = getfilectx(f, ctx2).data()
  1274             tn = getfilectx(f, ctx2).data()
       
  1275         a, b = f, f
  1205         if opts.git:
  1276         if opts.git:
  1206             def gitmode(x):
  1277             def gitmode(x, l):
  1207                 return x and '100755' or '100644'
  1278                 return l and '120000' or (x and '100755' or '100644')
  1208             def addmodehdr(header, omode, nmode):
  1279             def addmodehdr(header, omode, nmode):
  1209                 if omode != nmode:
  1280                 if omode != nmode:
  1210                     header.append('old mode %s\n' % omode)
  1281                     header.append('old mode %s\n' % omode)
  1211                     header.append('new mode %s\n' % nmode)
  1282                     header.append('new mode %s\n' % nmode)
  1212 
  1283 
  1213             a, b = f, f
       
  1214             if f in added:
  1284             if f in added:
  1215                 mode = gitmode(execf2(f))
  1285                 mode = gitmode(execf2(f), linkf2(f))
  1216                 if f in copied:
  1286                 if f in copied:
  1217                     a = copied[f]
  1287                     a = copied[f]
  1218                     omode = gitmode(man1.execf(a))
  1288                     omode = gitmode(man1.execf(a), man1.linkf(a))
  1219                     addmodehdr(header, omode, mode)
  1289                     addmodehdr(header, omode, mode)
  1220                     if a in removed and a not in gone:
  1290                     if a in removed and a not in gone:
  1221                         op = 'rename'
  1291                         op = 'rename'
  1222                         gone[a] = 1
  1292                         gone[a] = 1
  1223                     else:
  1293                     else:
  1231                     dodiff = 'binary'
  1301                     dodiff = 'binary'
  1232             elif f in removed:
  1302             elif f in removed:
  1233                 if f in srcs:
  1303                 if f in srcs:
  1234                     dodiff = False
  1304                     dodiff = False
  1235                 else:
  1305                 else:
  1236                     mode = gitmode(man1.execf(f))
  1306                     mode = gitmode(man1.execf(f), man1.linkf(f))
  1237                     header.append('deleted file mode %s\n' % mode)
  1307                     header.append('deleted file mode %s\n' % mode)
  1238             else:
  1308             else:
  1239                 omode = gitmode(man1.execf(f))
  1309                 omode = gitmode(man1.execf(f), man1.linkf(f))
  1240                 nmode = gitmode(execf2(f))
  1310                 nmode = gitmode(execf2(f), linkf2(f))
  1241                 addmodehdr(header, omode, nmode)
  1311                 addmodehdr(header, omode, nmode)
  1242                 if util.binary(to) or util.binary(tn):
  1312                 if util.binary(to) or util.binary(tn):
  1243                     dodiff = 'binary'
  1313                     dodiff = 'binary'
  1244             r = None
  1314             r = None
  1245             header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
  1315             header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
  1246         if dodiff:
  1316         if dodiff:
  1247             if dodiff == 'binary':
  1317             if dodiff == 'binary':
  1248                 text = b85diff(fp, to, tn)
  1318                 text = b85diff(to, tn)
  1249             else:
  1319             else:
  1250                 text = mdiff.unidiff(to, date1,
  1320                 text = mdiff.unidiff(to, date1,
  1251                                     # ctx2 date may be dynamic
  1321                                     # ctx2 date may be dynamic
  1252                                     tn, util.datestr(ctx2.date()),
  1322                                     tn, util.datestr(ctx2.date()),
  1253                                     f, r, opts=opts)
  1323                                     a, b, r, opts=opts)
  1254             if text or len(header) > 1:
  1324             if text or len(header) > 1:
  1255                 fp.write(''.join(header))
  1325                 fp.write(''.join(header))
  1256             fp.write(text)
  1326             fp.write(text)
  1257 
  1327 
  1258 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
  1328 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
  1301         return
  1371         return
  1302     fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
  1372     fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
  1303     try:
  1373     try:
  1304         p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
  1374         p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
  1305         try:
  1375         try:
  1306             for line in patchlines: print >> p.tochild, line
  1376             for line in patchlines:
       
  1377                 p.tochild.write(line + "\n")
  1307             p.tochild.close()
  1378             p.tochild.close()
  1308             if p.wait(): return
  1379             if p.wait(): return
  1309             fp = os.fdopen(fd, 'r')
  1380             fp = os.fdopen(fd, 'r')
  1310             stat = []
  1381             stat = []
  1311             for line in fp: stat.append(line.lstrip())
  1382             for line in fp: stat.append(line.lstrip())
  1312             last = stat.pop()
  1383             last = stat.pop()
  1313             stat.insert(0, last)
  1384             stat.insert(0, last)
  1314             stat = ''.join(stat)
  1385             stat = ''.join(stat)
  1315             if stat.startswith('0 files'): raise ValueError
       
  1316             return stat
  1386             return stat
  1317         except: raise
  1387         except: raise
  1318     finally:
  1388     finally:
  1319         try: os.unlink(name)
  1389         try: os.unlink(name)
  1320         except: pass
  1390         except: pass