mercurial/util.py
changeset 2860 0f08f2c042ec
parent 2859 b3d1145ed06c
child 2864 71e78f2ca5ae
equal deleted inserted replaced
2859:b3d1145ed06c 2860:0f08f2c042ec
    90     for p in path:
    90     for p in path:
    91         p_name = os.path.join(p, name)
    91         p_name = os.path.join(p, name)
    92         if os.path.exists(p_name):
    92         if os.path.exists(p_name):
    93             return p_name
    93             return p_name
    94     return default
    94     return default
    95 
       
    96 def readgitpatch(patchname):
       
    97     """extract git-style metadata about patches from <patchname>"""
       
    98     class gitpatch:
       
    99         "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
       
   100         def __init__(self, path):
       
   101             self.path = path
       
   102             self.oldpath = None
       
   103             self.mode = None
       
   104             self.op = 'MODIFY'
       
   105             self.copymod = False
       
   106             self.lineno = 0
       
   107     
       
   108     # Filter patch for git information
       
   109     gitre = re.compile('diff --git a/(.*) b/(.*)')
       
   110     pf = file(patchname)
       
   111     gp = None
       
   112     gitpatches = []
       
   113     # Can have a git patch with only metadata, causing patch to complain
       
   114     dopatch = False
       
   115 
       
   116     lineno = 0
       
   117     for line in pf:
       
   118         lineno += 1
       
   119         if line.startswith('diff --git'):
       
   120             m = gitre.match(line)
       
   121             if m:
       
   122                 if gp:
       
   123                     gitpatches.append(gp)
       
   124                 src, dst = m.group(1,2)
       
   125                 gp = gitpatch(dst)
       
   126                 gp.lineno = lineno
       
   127         elif gp:
       
   128             if line.startswith('--- '):
       
   129                 if gp.op in ('COPY', 'RENAME'):
       
   130                     gp.copymod = True
       
   131                     dopatch = 'filter'
       
   132                 gitpatches.append(gp)
       
   133                 gp = None
       
   134                 if not dopatch:
       
   135                     dopatch = True
       
   136                 continue
       
   137             if line.startswith('rename from '):
       
   138                 gp.op = 'RENAME'
       
   139                 gp.oldpath = line[12:].rstrip()
       
   140             elif line.startswith('rename to '):
       
   141                 gp.path = line[10:].rstrip()
       
   142             elif line.startswith('copy from '):
       
   143                 gp.op = 'COPY'
       
   144                 gp.oldpath = line[10:].rstrip()
       
   145             elif line.startswith('copy to '):
       
   146                 gp.path = line[8:].rstrip()
       
   147             elif line.startswith('deleted file'):
       
   148                 gp.op = 'DELETE'
       
   149             elif line.startswith('new file mode '):
       
   150                 gp.op = 'ADD'
       
   151                 gp.mode = int(line.rstrip()[-3:], 8)
       
   152             elif line.startswith('new mode '):
       
   153                 gp.mode = int(line.rstrip()[-3:], 8)
       
   154     if gp:
       
   155         gitpatches.append(gp)
       
   156 
       
   157     if not gitpatches:
       
   158         dopatch = True
       
   159 
       
   160     return (dopatch, gitpatches)
       
   161 
       
   162 def dogitpatch(patchname, gitpatches):
       
   163     """Preprocess git patch so that vanilla patch can handle it"""
       
   164     pf = file(patchname)
       
   165     pfline = 1
       
   166 
       
   167     fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
       
   168     tmpfp = os.fdopen(fd, 'w')
       
   169 
       
   170     try:
       
   171         for i in range(len(gitpatches)):
       
   172             p = gitpatches[i]
       
   173             if not p.copymod:
       
   174                 continue
       
   175 
       
   176             if os.path.exists(p.path):
       
   177                 raise Abort(_("cannot create %s: destination already exists") %
       
   178                             p.path)
       
   179 
       
   180             (src, dst) = [os.path.join(os.getcwd(), n)
       
   181                           for n in (p.oldpath, p.path)]
       
   182 
       
   183             print "copying %s to %s" % (src, dst)
       
   184             targetdir = os.path.dirname(dst)
       
   185             if not os.path.isdir(targetdir):
       
   186                 os.makedirs(targetdir)
       
   187             try:
       
   188                 shutil.copyfile(src, dst)
       
   189                 shutil.copymode(src, dst)
       
   190             except shutil.Error, inst:
       
   191                 raise Abort(str(inst))
       
   192 
       
   193             # rewrite patch hunk
       
   194             while pfline < p.lineno:
       
   195                 tmpfp.write(pf.readline())
       
   196                 pfline += 1
       
   197             tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
       
   198             line = pf.readline()
       
   199             pfline += 1
       
   200             while not line.startswith('--- a/'):
       
   201                 tmpfp.write(line)
       
   202                 line = pf.readline()
       
   203                 pfline += 1
       
   204             tmpfp.write('--- a/%s\n' % p.path)
       
   205 
       
   206         line = pf.readline()
       
   207         while line:
       
   208             tmpfp.write(line)
       
   209             line = pf.readline()
       
   210     except:
       
   211         tmpfp.close()
       
   212         os.unlink(patchname)
       
   213         raise
       
   214 
       
   215     tmpfp.close()
       
   216     return patchname
       
   217 
       
   218 def patch(strip, patchname, ui, cwd=None):
       
   219     """apply the patch <patchname> to the working directory.
       
   220     a list of patched files is returned"""
       
   221 
       
   222     (dopatch, gitpatches) = readgitpatch(patchname)
       
   223 
       
   224     files = {}
       
   225     if dopatch:
       
   226         if dopatch == 'filter':
       
   227             patchname = dogitpatch(patchname, gitpatches)
       
   228         patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
       
   229         args = []
       
   230         if cwd:
       
   231             args.append('-d %s' % shellquote(cwd))
       
   232         fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
       
   233                                            shellquote(patchname)))
       
   234 
       
   235         if dopatch == 'filter':
       
   236             False and os.unlink(patchname)
       
   237 
       
   238         for line in fp:
       
   239             line = line.rstrip()
       
   240             ui.status("%s\n" % line)
       
   241             if line.startswith('patching file '):
       
   242                 pf = parse_patch_output(line)
       
   243                 files.setdefault(pf, (None, None))
       
   244         code = fp.close()
       
   245         if code:
       
   246             raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
       
   247 
       
   248     for gp in gitpatches:
       
   249         files[gp.path] = (gp.op, gp)
       
   250 
       
   251     return files
       
   252 
    95 
   253 def binary(s):
    96 def binary(s):
   254     """return true if a string is binary data using diff's heuristic"""
    97     """return true if a string is binary data using diff's heuristic"""
   255     if s and '\0' in s[:4096]:
    98     if s and '\0' in s[:4096]:
   256         return True
    99         return True