mercurial/patch.py
changeset 3367 7f486971d263
parent 3327 319358e6bd96
child 3372 fd43ff3b4442
equal deleted inserted replaced
3366:dca067d751a9 3367:7f486971d263
     6 # of the GNU General Public License, incorporated herein by reference.
     6 # of the GNU General Public License, incorporated herein by reference.
     7 
     7 
     8 from demandload import demandload
     8 from demandload import demandload
     9 from i18n import gettext as _
     9 from i18n import gettext as _
    10 from node import *
    10 from node import *
    11 demandload(globals(), "cmdutil mdiff util")
    11 demandload(globals(), "base85 cmdutil mdiff util")
    12 demandload(globals(), '''cStringIO email.Parser errno os re shutil sys tempfile
    12 demandload(globals(), "cStringIO email.Parser errno os re shutil sha sys")
    13                          popen2''')
    13 demandload(globals(), "tempfile zlib")
    14 
    14 
    15 # helper functions
    15 # helper functions
    16 
    16 
    17 def copyfile(src, dst, basedir=None):
    17 def copyfile(src, dst, basedir=None):
    18     if not basedir:
    18     if not basedir:
   126             self.oldpath = None
   126             self.oldpath = None
   127             self.mode = None
   127             self.mode = None
   128             self.op = 'MODIFY'
   128             self.op = 'MODIFY'
   129             self.copymod = False
   129             self.copymod = False
   130             self.lineno = 0
   130             self.lineno = 0
       
   131             self.binary = False
   131 
   132 
   132     # Filter patch for git information
   133     # Filter patch for git information
   133     gitre = re.compile('diff --git a/(.*) b/(.*)')
   134     gitre = re.compile('diff --git a/(.*) b/(.*)')
   134     pf = file(patchname)
   135     pf = file(patchname)
   135     gp = None
   136     gp = None
   173             elif line.startswith('new file mode '):
   174             elif line.startswith('new file mode '):
   174                 gp.op = 'ADD'
   175                 gp.op = 'ADD'
   175                 gp.mode = int(line.rstrip()[-3:], 8)
   176                 gp.mode = int(line.rstrip()[-3:], 8)
   176             elif line.startswith('new mode '):
   177             elif line.startswith('new mode '):
   177                 gp.mode = int(line.rstrip()[-3:], 8)
   178                 gp.mode = int(line.rstrip()[-3:], 8)
       
   179             elif line.startswith('GIT binary patch'):
       
   180                 if not dopatch:
       
   181                     dopatch = 'binary'
       
   182                 gp.binary = True
   178     if gp:
   183     if gp:
   179         gitpatches.append(gp)
   184         gitpatches.append(gp)
   180 
   185 
   181     if not gitpatches:
   186     if not gitpatches:
   182         dopatch = True
   187         dopatch = True
   183 
   188 
   184     return (dopatch, gitpatches)
   189     return (dopatch, gitpatches)
   185 
   190 
   186 def dogitpatch(patchname, gitpatches, cwd=None):
   191 def dogitpatch(patchname, gitpatches, cwd=None):
   187     """Preprocess git patch so that vanilla patch can handle it"""
   192     """Preprocess git patch so that vanilla patch can handle it"""
       
   193     def extractbin(fp):
       
   194         line = fp.readline()
       
   195         while line and not line.startswith('literal '):
       
   196             line = fp.readline()
       
   197         if not line:
       
   198             return
       
   199         size = int(line[8:].rstrip())
       
   200         dec = []
       
   201         line = fp.readline()
       
   202         while line:
       
   203             line = line[1:-1]
       
   204             dec.append(base85.b85decode(line))
       
   205             line = fp.readline()
       
   206         text = zlib.decompress(''.join(dec))
       
   207         if len(text) != size:
       
   208             raise util.Abort(_('binary patch is %d bytes, not %d') %
       
   209                              (len(text), size))
       
   210         return text
       
   211 
   188     pf = file(patchname)
   212     pf = file(patchname)
   189     pfline = 1
   213     pfline = 1
   190 
   214 
   191     fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
   215     fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
   192     tmpfp = os.fdopen(fd, 'w')
   216     tmpfp = os.fdopen(fd, 'w')
   193 
   217 
   194     try:
   218     try:
   195         for i in range(len(gitpatches)):
   219         for i in range(len(gitpatches)):
   196             p = gitpatches[i]
   220             p = gitpatches[i]
   197             if not p.copymod:
   221             if not p.copymod and not p.binary:
   198                 continue
   222                 continue
   199 
       
   200             copyfile(p.oldpath, p.path, basedir=cwd)
       
   201 
   223 
   202             # rewrite patch hunk
   224             # rewrite patch hunk
   203             while pfline < p.lineno:
   225             while pfline < p.lineno:
   204                 tmpfp.write(pf.readline())
   226                 tmpfp.write(pf.readline())
   205                 pfline += 1
   227                 pfline += 1
   206             tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
   228 
   207             line = pf.readline()
   229             if p.binary:
   208             pfline += 1
   230                 text = extractbin(pf)
   209             while not line.startswith('--- a/'):
   231                 if not text:
   210                 tmpfp.write(line)
   232                     raise util.Abort(_('binary patch extraction failed'))
       
   233                 if not cwd:
       
   234                     cwd = os.getcwd()
       
   235                 absdst = os.path.join(cwd, p.path)
       
   236                 basedir = os.path.dirname(absdst)
       
   237                 if not os.path.isdir(basedir):
       
   238                     os.makedirs(basedir)
       
   239                 out = file(absdst, 'wb')
       
   240                 out.write(text)
       
   241                 out.close()
       
   242             elif p.copymod:
       
   243                 copyfile(p.oldpath, p.path, basedir=cwd)
       
   244                 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
   211                 line = pf.readline()
   245                 line = pf.readline()
   212                 pfline += 1
   246                 pfline += 1
   213             tmpfp.write('--- a/%s\n' % p.path)
   247                 while not line.startswith('--- a/'):
       
   248                     tmpfp.write(line)
       
   249                     line = pf.readline()
       
   250                     pfline += 1
       
   251                 tmpfp.write('--- a/%s\n' % p.path)
   214 
   252 
   215         line = pf.readline()
   253         line = pf.readline()
   216         while line:
   254         while line:
   217             tmpfp.write(line)
   255             tmpfp.write(line)
   218             line = pf.readline()
   256             line = pf.readline()
   268                              util.explain_exit(code)[0])
   306                              util.explain_exit(code)[0])
   269         return files, fuzz
   307         return files, fuzz
   270 
   308 
   271     (dopatch, gitpatches) = readgitpatch(patchname)
   309     (dopatch, gitpatches) = readgitpatch(patchname)
   272 
   310 
       
   311     files, fuzz = {}, False
   273     if dopatch:
   312     if dopatch:
   274         if dopatch == 'filter':
   313         if dopatch in ('filter', 'binary'):
   275             patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
   314             patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
   276         try:
   315         try:
   277             files, fuzz = __patch(patchname)
   316             if dopatch != 'binary':
       
   317                 files, fuzz = __patch(patchname)
   278         finally:
   318         finally:
   279             if dopatch == 'filter':
   319             if dopatch == 'filter':
   280                 os.unlink(patchname)
   320                 os.unlink(patchname)
   281     else:
       
   282         files, fuzz = {}, False
       
   283 
   321 
   284     for gp in gitpatches:
   322     for gp in gitpatches:
   285         files[gp.path] = (gp.op, gp)
   323         files[gp.path] = (gp.op, gp)
   286 
   324 
   287     return (files, fuzz)
   325     return (files, fuzz)
   338     files.extend([r for r in removes if r not in files])
   376     files.extend([r for r in removes if r not in files])
   339     files.sort()
   377     files.sort()
   340 
   378 
   341     return files
   379     return files
   342 
   380 
       
   381 def b85diff(fp, to, tn):
       
   382     '''print base85-encoded binary diff'''
       
   383     def gitindex(text):
       
   384         if not text:
       
   385             return '0' * 40
       
   386         l = len(text)
       
   387         s = sha.new('blob %d\0' % l)
       
   388         s.update(text)
       
   389         return s.hexdigest()
       
   390 
       
   391     def fmtline(line):
       
   392         l = len(line)
       
   393         if l <= 26:
       
   394             l = chr(ord('A') + l - 1)
       
   395         else:
       
   396             l = chr(l - 26 + ord('a') - 1)
       
   397         return '%c%s\n' % (l, base85.b85encode(line, True))
       
   398 
       
   399     def chunk(text, csize=52):
       
   400         l = len(text)
       
   401         i = 0
       
   402         while i < l:
       
   403             yield text[i:i+csize]
       
   404             i += csize
       
   405 
       
   406     # TODO: deltas
       
   407     l = len(tn)
       
   408     fp.write('index %s..%s\nGIT binary patch\nliteral %s\n' %
       
   409              (gitindex(to), gitindex(tn), len(tn)))
       
   410 
       
   411     tn = ''.join([fmtline(l) for l in chunk(zlib.compress(tn))])
       
   412     fp.write(tn)
       
   413     fp.write('\n')
       
   414 
   343 def diff(repo, node1=None, node2=None, files=None, match=util.always,
   415 def diff(repo, node1=None, node2=None, files=None, match=util.always,
   344          fp=None, changes=None, opts=None):
   416          fp=None, changes=None, opts=None):
   345     '''print diff of changes to files between two nodes, or node and
   417     '''print diff of changes to files between two nodes, or node and
   346     working directory.
   418     working directory.
   347 
   419 
   494                     header.append('%s from %s\n' % (op, a))
   566                     header.append('%s from %s\n' % (op, a))
   495                     header.append('%s to %s\n' % (op, f))
   567                     header.append('%s to %s\n' % (op, f))
   496                     to = getfile(a).read(arev)
   568                     to = getfile(a).read(arev)
   497                 else:
   569                 else:
   498                     header.append('new file mode %s\n' % mode)
   570                     header.append('new file mode %s\n' % mode)
       
   571                     if util.binary(tn):
       
   572                         dodiff = 'binary'
   499             elif f in removed:
   573             elif f in removed:
   500                 if f in srcs:
   574                 if f in srcs:
   501                     dodiff = False
   575                     dodiff = False
   502                 else:
   576                 else:
   503                     mode = gitmode(mmap.execf(f))
   577                     mode = gitmode(mmap.execf(f))
   507                 if node2:
   581                 if node2:
   508                     nmode = gitmode(mmap2.execf(f))
   582                     nmode = gitmode(mmap2.execf(f))
   509                 else:
   583                 else:
   510                     nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
   584                     nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
   511                 addmodehdr(header, omode, nmode)
   585                 addmodehdr(header, omode, nmode)
       
   586                 if util.binary(to) or util.binary(tn):
       
   587                     dodiff = 'binary'
   512             r = None
   588             r = None
   513             header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
   589             header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
   514         if dodiff:
   590         if dodiff == 'binary':
       
   591             fp.write(''.join(header))
       
   592             b85diff(fp, to, tn)
       
   593         elif dodiff:
   515             text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
   594             text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
   516             if text or len(header) > 1:
   595             if text or len(header) > 1:
   517                 fp.write(''.join(header))
   596                 fp.write(''.join(header))
   518             fp.write(text)
   597             fp.write(text)
   519 
   598