comparison mercurial/util.py @ 2860:0f08f2c042ec

Move patch-related code into its own module.
author Brendan Cully <brendan@kublai.com>
date Fri, 11 Aug 2006 15:50:16 -0700
parents b3d1145ed06c
children 71e78f2ca5ae
comparison
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