Mercurial > hg > mercurial-crew-with-dirclash
comparison mercurial/patch.py @ 2864:71e78f2ca5ae
merge git patch code.
author | Vadim Gelfer <vadim.gelfer@gmail.com> |
---|---|
date | Sat, 12 Aug 2006 12:47:18 -0700 |
parents | f3c68e0ca37d |
children | 2893e51407a4 |
comparison
equal
deleted
inserted
replaced
2858:345bac2bc4ec | 2864:71e78f2ca5ae |
---|---|
1 # patch.py - patch file parsing routines | |
2 # | |
3 # Copyright 2006 Brendan Cully <brendan@kublai.com> | |
4 # | |
5 # This software may be used and distributed according to the terms | |
6 # of the GNU General Public License, incorporated herein by reference. | |
7 | |
8 from demandload import demandload | |
9 demandload(globals(), "util") | |
10 demandload(globals(), "os re shutil tempfile") | |
11 | |
12 def readgitpatch(patchname): | |
13 """extract git-style metadata about patches from <patchname>""" | |
14 class gitpatch: | |
15 "op is one of ADD, DELETE, RENAME, MODIFY or COPY" | |
16 def __init__(self, path): | |
17 self.path = path | |
18 self.oldpath = None | |
19 self.mode = None | |
20 self.op = 'MODIFY' | |
21 self.copymod = False | |
22 self.lineno = 0 | |
23 | |
24 # Filter patch for git information | |
25 gitre = re.compile('diff --git a/(.*) b/(.*)') | |
26 pf = file(patchname) | |
27 gp = None | |
28 gitpatches = [] | |
29 # Can have a git patch with only metadata, causing patch to complain | |
30 dopatch = False | |
31 | |
32 lineno = 0 | |
33 for line in pf: | |
34 lineno += 1 | |
35 if line.startswith('diff --git'): | |
36 m = gitre.match(line) | |
37 if m: | |
38 if gp: | |
39 gitpatches.append(gp) | |
40 src, dst = m.group(1,2) | |
41 gp = gitpatch(dst) | |
42 gp.lineno = lineno | |
43 elif gp: | |
44 if line.startswith('--- '): | |
45 if gp.op in ('COPY', 'RENAME'): | |
46 gp.copymod = True | |
47 dopatch = 'filter' | |
48 gitpatches.append(gp) | |
49 gp = None | |
50 if not dopatch: | |
51 dopatch = True | |
52 continue | |
53 if line.startswith('rename from '): | |
54 gp.op = 'RENAME' | |
55 gp.oldpath = line[12:].rstrip() | |
56 elif line.startswith('rename to '): | |
57 gp.path = line[10:].rstrip() | |
58 elif line.startswith('copy from '): | |
59 gp.op = 'COPY' | |
60 gp.oldpath = line[10:].rstrip() | |
61 elif line.startswith('copy to '): | |
62 gp.path = line[8:].rstrip() | |
63 elif line.startswith('deleted file'): | |
64 gp.op = 'DELETE' | |
65 elif line.startswith('new file mode '): | |
66 gp.op = 'ADD' | |
67 gp.mode = int(line.rstrip()[-3:], 8) | |
68 elif line.startswith('new mode '): | |
69 gp.mode = int(line.rstrip()[-3:], 8) | |
70 if gp: | |
71 gitpatches.append(gp) | |
72 | |
73 if not gitpatches: | |
74 dopatch = True | |
75 | |
76 return (dopatch, gitpatches) | |
77 | |
78 def dogitpatch(patchname, gitpatches): | |
79 """Preprocess git patch so that vanilla patch can handle it""" | |
80 pf = file(patchname) | |
81 pfline = 1 | |
82 | |
83 fd, patchname = tempfile.mkstemp(prefix='hg-patch-') | |
84 tmpfp = os.fdopen(fd, 'w') | |
85 | |
86 try: | |
87 for i in range(len(gitpatches)): | |
88 p = gitpatches[i] | |
89 if not p.copymod: | |
90 continue | |
91 | |
92 if os.path.exists(p.path): | |
93 raise util.Abort(_("cannot create %s: destination already exists") % | |
94 p.path) | |
95 | |
96 (src, dst) = [os.path.join(os.getcwd(), n) | |
97 for n in (p.oldpath, p.path)] | |
98 | |
99 targetdir = os.path.dirname(dst) | |
100 if not os.path.isdir(targetdir): | |
101 os.makedirs(targetdir) | |
102 try: | |
103 shutil.copyfile(src, dst) | |
104 shutil.copymode(src, dst) | |
105 except shutil.Error, inst: | |
106 raise util.Abort(str(inst)) | |
107 | |
108 # rewrite patch hunk | |
109 while pfline < p.lineno: | |
110 tmpfp.write(pf.readline()) | |
111 pfline += 1 | |
112 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path)) | |
113 line = pf.readline() | |
114 pfline += 1 | |
115 while not line.startswith('--- a/'): | |
116 tmpfp.write(line) | |
117 line = pf.readline() | |
118 pfline += 1 | |
119 tmpfp.write('--- a/%s\n' % p.path) | |
120 | |
121 line = pf.readline() | |
122 while line: | |
123 tmpfp.write(line) | |
124 line = pf.readline() | |
125 except: | |
126 tmpfp.close() | |
127 os.unlink(patchname) | |
128 raise | |
129 | |
130 tmpfp.close() | |
131 return patchname | |
132 | |
133 def patch(strip, patchname, ui, cwd=None): | |
134 """apply the patch <patchname> to the working directory. | |
135 a list of patched files is returned""" | |
136 | |
137 (dopatch, gitpatches) = readgitpatch(patchname) | |
138 | |
139 files = {} | |
140 if dopatch: | |
141 if dopatch == 'filter': | |
142 patchname = dogitpatch(patchname, gitpatches) | |
143 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch') | |
144 args = [] | |
145 if cwd: | |
146 args.append('-d %s' % util.shellquote(cwd)) | |
147 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip, | |
148 util.shellquote(patchname))) | |
149 | |
150 if dopatch == 'filter': | |
151 False and os.unlink(patchname) | |
152 | |
153 for line in fp: | |
154 line = line.rstrip() | |
155 ui.status("%s\n" % line) | |
156 if line.startswith('patching file '): | |
157 pf = util.parse_patch_output(line) | |
158 files.setdefault(pf, (None, None)) | |
159 code = fp.close() | |
160 if code: | |
161 raise util.Abort(_("patch command failed: %s") % explain_exit(code)[0]) | |
162 | |
163 for gp in gitpatches: | |
164 files[gp.path] = (gp.op, gp) | |
165 | |
166 return files |