Mercurial > hg > mercurial-crew-with-dirclash
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 |