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