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