comparison mercurial/patch.py @ 4900:e56c7e05c7e6

patch.py: re-add the ability to use an external patch program This is now invoked by default only if ui.patch is set. Otherwise, we use our built-in patch. If that fails because it can't find any valid hunks, we'll fall back to trying the external patch command.
author Bryan O'Sullivan <bos@serpentine.com>
date Tue, 17 Jul 2007 09:39:30 -0700
parents 1b7bbc4349e7
children 126f527b3ba3 020ee9c781cf
comparison
equal deleted inserted replaced
4899:1b7bbc4349e7 4900:e56c7e05c7e6
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
13 import sys, tempfile, zlib 13 import sys, tempfile, zlib
14 14
15 class PatchError(Exception): 15 class PatchError(Exception):
16 pass
17
18 class NoHunks(PatchError):
16 pass 19 pass
17 20
18 # helper functions 21 # helper functions
19 22
20 def copyfile(src, dst, basedir=None): 23 def copyfile(src, dst, basedir=None):
71 ui.debug('Subject: %s\n' % subject) 74 ui.debug('Subject: %s\n' % subject)
72 if user: 75 if user:
73 ui.debug('From: %s\n' % user) 76 ui.debug('From: %s\n' % user)
74 diffs_seen = 0 77 diffs_seen = 0
75 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch') 78 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
76 79 message = ''
77 for part in msg.walk(): 80 for part in msg.walk():
78 content_type = part.get_content_type() 81 content_type = part.get_content_type()
79 ui.debug('Content-Type: %s\n' % content_type) 82 ui.debug('Content-Type: %s\n' % content_type)
80 if content_type not in ok_types: 83 if content_type not in ok_types:
81 continue 84 continue
211 dopatch = GP_PATCH 214 dopatch = GP_PATCH
212 215
213 return (dopatch, gitpatches) 216 return (dopatch, gitpatches)
214 217
215 def patch(patchname, ui, strip=1, cwd=None, files={}): 218 def patch(patchname, ui, strip=1, cwd=None, files={}):
216 """apply the patch <patchname> to the working directory. 219 """apply <patchname> to the working directory.
217 a list of patched files is returned""" 220 returns whether patch was applied with fuzz factor."""
221 patcher = ui.config('ui', 'patch')
222 args = []
223 try:
224 if patcher:
225 return externalpatch(patcher, args, patchname, ui, strip, cwd,
226 files)
227 else:
228 try:
229 return internalpatch(patchname, ui, strip, cwd, files)
230 except NoHunks:
231 patcher = util.find_exe('gpatch') or util.find_exe('patch')
232 ui.debug('no valid hunks found; trying with %r instead\n' %
233 patcher)
234 if util.needbinarypatch():
235 args.append('--binary')
236 return externalpatch(patcher, args, patchname, ui, strip, cwd,
237 files)
238 except PatchError, err:
239 s = str(err)
240 if s:
241 raise util.Abort(s)
242 else:
243 raise util.Abort(_('patch failed to apply'))
244
245 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
246 """use <patcher> to apply <patchname> to the working directory.
247 returns whether patch was applied with fuzz factor."""
248
249 fuzz = False
250 if cwd:
251 args.append('-d %s' % util.shellquote(cwd))
252 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
253 util.shellquote(patchname)))
254
255 for line in fp:
256 line = line.rstrip()
257 ui.note(line + '\n')
258 if line.startswith('patching file '):
259 pf = util.parse_patch_output(line)
260 printed_file = False
261 files.setdefault(pf, (None, None))
262 elif line.find('with fuzz') >= 0:
263 fuzz = True
264 if not printed_file:
265 ui.warn(pf + '\n')
266 printed_file = True
267 ui.warn(line + '\n')
268 elif line.find('saving rejects to file') >= 0:
269 ui.warn(line + '\n')
270 elif line.find('FAILED') >= 0:
271 if not printed_file:
272 ui.warn(pf + '\n')
273 printed_file = True
274 ui.warn(line + '\n')
275 code = fp.close()
276 if code:
277 raise PatchError(_("patch command failed: %s") %
278 util.explain_exit(code)[0])
279 return fuzz
280
281 def internalpatch(patchname, ui, strip, cwd, files):
282 """use builtin patch to apply <patchname> to the working directory.
283 returns whether patch was applied with fuzz factor."""
218 fp = file(patchname) 284 fp = file(patchname)
219 fuzz = False
220 if cwd: 285 if cwd:
221 curdir = os.getcwd() 286 curdir = os.getcwd()
222 os.chdir(cwd) 287 os.chdir(cwd)
223 try: 288 try:
224 ret = applydiff(ui, fp, files, strip=strip) 289 ret = applydiff(ui, fp, files, strip=strip)
225 except PatchError, err: 290 finally:
226 ui.debug(err) 291 if cwd:
227 raise util.Abort(_("patch failed to apply")) 292 os.chdir(curdir)
228 if cwd:
229 os.chdir(curdir)
230 if ret < 0: 293 if ret < 0:
231 raise util.Abort(_("patch failed to apply")) 294 raise PatchError
232 if ret > 0: 295 return ret > 0
233 fuzz = True
234 return fuzz
235 296
236 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1 297 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
237 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@') 298 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
238 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)') 299 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
239 300
934 if updatedir and git: 995 if updatedir and git:
935 updatedir(gitpatches) 996 updatedir(gitpatches)
936 if rejects: 997 if rejects:
937 return -1 998 return -1
938 if hunknum == 0 and dopatch and not gitworkdone: 999 if hunknum == 0 and dopatch and not gitworkdone:
939 raise PatchError(_("no valid hunks found")) 1000 raise NoHunks
940 return err 1001 return err
941 1002
942 def diffopts(ui, opts={}, untrusted=False): 1003 def diffopts(ui, opts={}, untrusted=False):
943 def get(key, name=None): 1004 def get(key, name=None):
944 return (opts.get(key) or 1005 return (opts.get(key) or