1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 package org.eclipse.jgit.api;
44
45 import java.io.File;
46 import java.io.FileOutputStream;
47 import java.io.FileWriter;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.nio.file.StandardCopyOption;
51 import java.text.MessageFormat;
52 import java.util.ArrayList;
53 import java.util.List;
54
55 import org.eclipse.jgit.api.errors.GitAPIException;
56 import org.eclipse.jgit.api.errors.PatchApplyException;
57 import org.eclipse.jgit.api.errors.PatchFormatException;
58 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
59 import org.eclipse.jgit.diff.RawText;
60 import org.eclipse.jgit.internal.JGitText;
61 import org.eclipse.jgit.lib.FileMode;
62 import org.eclipse.jgit.lib.Repository;
63 import org.eclipse.jgit.patch.FileHeader;
64 import org.eclipse.jgit.patch.HunkHeader;
65 import org.eclipse.jgit.patch.Patch;
66 import org.eclipse.jgit.util.FileUtils;
67 import org.eclipse.jgit.util.IO;
68
69
70
71
72
73
74
75
76 public class ApplyCommand extends GitCommand<ApplyResult> {
77
78 private InputStream in;
79
80
81
82
83
84
85 ApplyCommand(Repository repo) {
86 super(repo);
87 }
88
89
90
91
92
93
94
95
96 public ApplyCommand setPatch(InputStream in) {
97 checkCallable();
98 this.in = in;
99 return this;
100 }
101
102
103
104
105
106
107
108
109
110
111 @Override
112 public ApplyResult call() throws GitAPIException, PatchFormatException,
113 PatchApplyException {
114 checkCallable();
115 ApplyResult r = new ApplyResult();
116 try {
117 final Patch p = new Patch();
118 try {
119 p.parse(in);
120 } finally {
121 in.close();
122 }
123 if (!p.getErrors().isEmpty())
124 throw new PatchFormatException(p.getErrors());
125 for (FileHeader fh : p.getFiles()) {
126 ChangeType type = fh.getChangeType();
127 File f = null;
128 switch (type) {
129 case ADD:
130 f = getFile(fh.getNewPath(), true);
131 apply(f, fh);
132 break;
133 case MODIFY:
134 f = getFile(fh.getOldPath(), false);
135 apply(f, fh);
136 break;
137 case DELETE:
138 f = getFile(fh.getOldPath(), false);
139 if (!f.delete())
140 throw new PatchApplyException(MessageFormat.format(
141 JGitText.get().cannotDeleteFile, f));
142 break;
143 case RENAME:
144 f = getFile(fh.getOldPath(), false);
145 File dest = getFile(fh.getNewPath(), false);
146 try {
147 FileUtils.rename(f, dest,
148 StandardCopyOption.ATOMIC_MOVE);
149 } catch (IOException e) {
150 throw new PatchApplyException(MessageFormat.format(
151 JGitText.get().renameFileFailed, f, dest), e);
152 }
153 break;
154 case COPY:
155 f = getFile(fh.getOldPath(), false);
156 byte[] bs = IO.readFully(f);
157 FileOutputStream fos = new FileOutputStream(getFile(
158 fh.getNewPath(),
159 true));
160 try {
161 fos.write(bs);
162 } finally {
163 fos.close();
164 }
165 }
166 r.addUpdatedFile(f);
167 }
168 } catch (IOException e) {
169 throw new PatchApplyException(MessageFormat.format(
170 JGitText.get().patchApplyException, e.getMessage()), e);
171 }
172 setCallable(false);
173 return r;
174 }
175
176 private File getFile(String path, boolean create)
177 throws PatchApplyException {
178 File f = new File(getRepository().getWorkTree(), path);
179 if (create)
180 try {
181 File parent = f.getParentFile();
182 FileUtils.mkdirs(parent, true);
183 FileUtils.createNewFile(f);
184 } catch (IOException e) {
185 throw new PatchApplyException(MessageFormat.format(
186 JGitText.get().createNewFileFailed, f), e);
187 }
188 return f;
189 }
190
191
192
193
194
195
196
197 private void apply(File f, FileHeader fh)
198 throws IOException, PatchApplyException {
199 RawText rt = new RawText(f);
200 List<String> oldLines = new ArrayList<>(rt.size());
201 for (int i = 0; i < rt.size(); i++)
202 oldLines.add(rt.getString(i));
203 List<String> newLines = new ArrayList<>(oldLines);
204 for (HunkHeader hh : fh.getHunks()) {
205
206 byte[] b = new byte[hh.getEndOffset() - hh.getStartOffset()];
207 System.arraycopy(hh.getBuffer(), hh.getStartOffset(), b, 0,
208 b.length);
209 RawText hrt = new RawText(b);
210
211 List<String> hunkLines = new ArrayList<>(hrt.size());
212 for (int i = 0; i < hrt.size(); i++)
213 hunkLines.add(hrt.getString(i));
214 int pos = 0;
215 for (int j = 1; j < hunkLines.size(); j++) {
216 String hunkLine = hunkLines.get(j);
217 switch (hunkLine.charAt(0)) {
218 case ' ':
219 if (!newLines.get(hh.getNewStartLine() - 1 + pos).equals(
220 hunkLine.substring(1))) {
221 throw new PatchApplyException(MessageFormat.format(
222 JGitText.get().patchApplyException, hh));
223 }
224 pos++;
225 break;
226 case '-':
227 if (hh.getNewStartLine() == 0) {
228 newLines.clear();
229 } else {
230 if (!newLines.get(hh.getNewStartLine() - 1 + pos)
231 .equals(hunkLine.substring(1))) {
232 throw new PatchApplyException(MessageFormat.format(
233 JGitText.get().patchApplyException, hh));
234 }
235 newLines.remove(hh.getNewStartLine() - 1 + pos);
236 }
237 break;
238 case '+':
239 newLines.add(hh.getNewStartLine() - 1 + pos,
240 hunkLine.substring(1));
241 pos++;
242 break;
243 }
244 }
245 }
246 if (!isNoNewlineAtEndOfFile(fh))
247 newLines.add("");
248 if (!rt.isMissingNewlineAtEnd())
249 oldLines.add("");
250 if (!isChanged(oldLines, newLines))
251 return;
252 StringBuilder sb = new StringBuilder();
253 for (String l : newLines) {
254
255
256 sb.append(l).append('\n');
257 }
258 if (sb.length() > 0) {
259 sb.deleteCharAt(sb.length() - 1);
260 }
261 try (FileWriter fw = new FileWriter(f)) {
262 fw.write(sb.toString());
263 }
264
265 getRepository().getFS().setExecute(f, fh.getNewMode() == FileMode.EXECUTABLE_FILE);
266 }
267
268 private static boolean isChanged(List<String> ol, List<String> nl) {
269 if (ol.size() != nl.size())
270 return true;
271 for (int i = 0; i < ol.size(); i++)
272 if (!ol.get(i).equals(nl.get(i)))
273 return true;
274 return false;
275 }
276
277 private boolean isNoNewlineAtEndOfFile(FileHeader fh) {
278 HunkHeader lastHunk = fh.getHunks().get(fh.getHunks().size() - 1);
279 RawText lhrt = new RawText(lastHunk.getBuffer());
280 return lhrt.getString(lhrt.size() - 1).equals(
281 "\\ No newline at end of file");
282 }
283 }