View Javadoc
1   /*
2    * Copyright (C) 2008-2009, Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.patch;
12  
13  import static org.eclipse.jgit.util.RawParseUtils.match;
14  import static org.eclipse.jgit.util.RawParseUtils.nextLF;
15  import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
16  
17  import java.io.IOException;
18  import java.io.OutputStream;
19  import java.text.MessageFormat;
20  
21  import org.eclipse.jgit.diff.Edit;
22  import org.eclipse.jgit.diff.EditList;
23  import org.eclipse.jgit.internal.JGitText;
24  import org.eclipse.jgit.lib.AbbreviatedObjectId;
25  import org.eclipse.jgit.util.MutableInteger;
26  
27  /**
28   * Hunk header describing the layout of a single block of lines
29   */
30  public class HunkHeader {
31  	/** Details about an old image of the file. */
32  	public abstract static class OldImage {
33  		/** First line number the hunk starts on in this file. */
34  		int startLine;
35  
36  		/** Total number of lines this hunk covers in this file. */
37  		int lineCount;
38  
39  		/** Number of lines deleted by the post-image from this file. */
40  		int nDeleted;
41  
42  		/** Number of lines added by the post-image not in this file. */
43  		int nAdded;
44  
45  		/** @return first line number the hunk starts on in this file. */
46  		public int getStartLine() {
47  			return startLine;
48  		}
49  
50  		/** @return total number of lines this hunk covers in this file. */
51  		public int getLineCount() {
52  			return lineCount;
53  		}
54  
55  		/** @return number of lines deleted by the post-image from this file. */
56  		public int getLinesDeleted() {
57  			return nDeleted;
58  		}
59  
60  		/** @return number of lines added by the post-image not in this file. */
61  		public int getLinesAdded() {
62  			return nAdded;
63  		}
64  
65  		/** @return object id of the pre-image file. */
66  		public abstract AbbreviatedObjectId getId();
67  	}
68  
69  	final FileHeader file;
70  
71  	/** Offset within {@link #file}.buf to the "@@ -" line. */
72  	final int startOffset;
73  
74  	/** Position 1 past the end of this hunk within {@link #file}'s buf. */
75  	int endOffset;
76  
77  	private final OldImage old;
78  
79  	/** First line number in the post-image file where the hunk starts */
80  	int newStartLine;
81  
82  	/** Total number of post-image lines this hunk covers (context + inserted) */
83  	int newLineCount;
84  
85  	/** Total number of lines of context appearing in this hunk */
86  	int nContext;
87  
88  	private EditList editList;
89  
90  	HunkHeader(FileHeader fh, int offset) {
91  		this(fh, offset, new OldImage() {
92  			@Override
93  			public AbbreviatedObjectId getId() {
94  				return fh.getOldId();
95  			}
96  		});
97  	}
98  
99  	HunkHeader(FileHeader fh, int offset, OldImage oi) {
100 		file = fh;
101 		startOffset = offset;
102 		old = oi;
103 	}
104 
105 	HunkHeader(FileHeader fh, EditList editList) {
106 		this(fh, fh.buf.length);
107 		this.editList = editList;
108 		endOffset = startOffset;
109 		nContext = 0;
110 		if (editList.isEmpty()) {
111 			newStartLine = 0;
112 			newLineCount = 0;
113 		} else {
114 			newStartLine = editList.get(0).getBeginB();
115 			Edit last = editList.get(editList.size() - 1);
116 			newLineCount = last.getEndB() - newStartLine;
117 		}
118 	}
119 
120 	/**
121 	 * Get header for the file this hunk applies to.
122 	 *
123 	 * @return header for the file this hunk applies to.
124 	 */
125 	public FileHeader getFileHeader() {
126 		return file;
127 	}
128 
129 	/**
130 	 * Get the byte array holding this hunk's patch script.
131 	 *
132 	 * @return the byte array holding this hunk's patch script.
133 	 */
134 	public byte[] getBuffer() {
135 		return file.buf;
136 	}
137 
138 	/**
139 	 * Get offset of the start of this hunk in {@link #getBuffer()}.
140 	 *
141 	 * @return offset of the start of this hunk in {@link #getBuffer()}.
142 	 */
143 	public int getStartOffset() {
144 		return startOffset;
145 	}
146 
147 	/**
148 	 * Get offset one past the end of the hunk in {@link #getBuffer()}.
149 	 *
150 	 * @return offset one past the end of the hunk in {@link #getBuffer()}.
151 	 */
152 	public int getEndOffset() {
153 		return endOffset;
154 	}
155 
156 	/**
157 	 * Get information about the old image mentioned in this hunk.
158 	 *
159 	 * @return information about the old image mentioned in this hunk.
160 	 */
161 	public OldImage getOldImage() {
162 		return old;
163 	}
164 
165 	/**
166 	 * Get first line number in the post-image file where the hunk starts.
167 	 *
168 	 * @return first line number in the post-image file where the hunk starts.
169 	 */
170 	public int getNewStartLine() {
171 		return newStartLine;
172 	}
173 
174 	/**
175 	 * Get total number of post-image lines this hunk covers.
176 	 *
177 	 * @return total number of post-image lines this hunk covers.
178 	 */
179 	public int getNewLineCount() {
180 		return newLineCount;
181 	}
182 
183 	/**
184 	 * Get total number of lines of context appearing in this hunk.
185 	 *
186 	 * @return total number of lines of context appearing in this hunk.
187 	 */
188 	public int getLinesContext() {
189 		return nContext;
190 	}
191 
192 	/**
193 	 * Convert to a list describing the content edits performed within the hunk.
194 	 *
195 	 * @return a list describing the content edits performed within the hunk.
196 	 */
197 	public EditList toEditList() {
198 		if (editList == null) {
199 			editList = new EditList();
200 			final byte[] buf = file.buf;
201 			int c = nextLF(buf, startOffset);
202 			int oLine = old.startLine;
203 			int nLine = newStartLine;
204 			Edit in = null;
205 
206 			SCAN: for (; c < endOffset; c = nextLF(buf, c)) {
207 				switch (buf[c]) {
208 				case ' ':
209 				case '\n':
210 					in = null;
211 					oLine++;
212 					nLine++;
213 					continue;
214 
215 				case '-':
216 					if (in == null) {
217 						in = new Edit(oLine - 1, nLine - 1);
218 						editList.add(in);
219 					}
220 					oLine++;
221 					in.extendA();
222 					continue;
223 
224 				case '+':
225 					if (in == null) {
226 						in = new Edit(oLine - 1, nLine - 1);
227 						editList.add(in);
228 					}
229 					nLine++;
230 					in.extendB();
231 					continue;
232 
233 				case '\\': // Matches "\ No newline at end of file"
234 					continue;
235 
236 				default:
237 					break SCAN;
238 				}
239 			}
240 		}
241 		return editList;
242 	}
243 
244 	void parseHeader() {
245 		// Parse "@@ -236,9 +236,9 @@ protected boolean"
246 		//
247 		final byte[] buf = file.buf;
248 		final MutableIntegerger.html#MutableInteger">MutableInteger ptr = new MutableInteger();
249 		ptr.value = nextLF(buf, startOffset, ' ');
250 		old.startLine = -parseBase10(buf, ptr.value, ptr);
251 		if (buf[ptr.value] == ',')
252 			old.lineCount = parseBase10(buf, ptr.value + 1, ptr);
253 		else
254 			old.lineCount = 1;
255 
256 		newStartLine = parseBase10(buf, ptr.value + 1, ptr);
257 		if (buf[ptr.value] == ',')
258 			newLineCount = parseBase10(buf, ptr.value + 1, ptr);
259 		else
260 			newLineCount = 1;
261 	}
262 
263 	int parseBody(Patch script, int end) {
264 		final byte[] buf = file.buf;
265 		int c = nextLF(buf, startOffset), last = c;
266 
267 		old.nDeleted = 0;
268 		old.nAdded = 0;
269 
270 		SCAN: for (; c < end; last = c, c = nextLF(buf, c)) {
271 			switch (buf[c]) {
272 			case ' ':
273 			case '\n':
274 				nContext++;
275 				continue;
276 
277 			case '-':
278 				old.nDeleted++;
279 				continue;
280 
281 			case '+':
282 				old.nAdded++;
283 				continue;
284 
285 			case '\\': // Matches "\ No newline at end of file"
286 				continue;
287 
288 			default:
289 				break SCAN;
290 			}
291 		}
292 
293 		if (last < end && nContext + old.nDeleted - 1 == old.lineCount
294 				&& nContext + old.nAdded == newLineCount
295 				&& match(buf, last, Patch.SIG_FOOTER) >= 0) {
296 			// This is an extremely common occurrence of "corruption".
297 			// Users add footers with their signatures after this mark,
298 			// and git diff adds the git executable version number.
299 			// Let it slide; the hunk otherwise looked sound.
300 			//
301 			old.nDeleted--;
302 			return last;
303 		}
304 
305 		if (nContext + old.nDeleted < old.lineCount) {
306 			final int missingCount = old.lineCount - (nContext + old.nDeleted);
307 			script.error(buf, startOffset, MessageFormat.format(
308 					JGitText.get().truncatedHunkOldLinesMissing,
309 					Integer.valueOf(missingCount)));
310 
311 		} else if (nContext + old.nAdded < newLineCount) {
312 			final int missingCount = newLineCount - (nContext + old.nAdded);
313 			script.error(buf, startOffset, MessageFormat.format(
314 					JGitText.get().truncatedHunkNewLinesMissing,
315 					Integer.valueOf(missingCount)));
316 
317 		} else if (nContext + old.nDeleted > old.lineCount
318 				|| nContext + old.nAdded > newLineCount) {
319 			final String oldcnt = old.lineCount + ":" + newLineCount; //$NON-NLS-1$
320 			final String newcnt = (nContext + old.nDeleted) + ":" //$NON-NLS-1$
321 					+ (nContext + old.nAdded);
322 			script.warn(buf, startOffset, MessageFormat.format(
323 					JGitText.get().hunkHeaderDoesNotMatchBodyLineCountOf, oldcnt, newcnt));
324 		}
325 
326 		return c;
327 	}
328 
329 	void extractFileLines(OutputStream[] out) throws IOException {
330 		final byte[] buf = file.buf;
331 		int ptr = startOffset;
332 		int eol = nextLF(buf, ptr);
333 		if (endOffset <= eol)
334 			return;
335 
336 		// Treat the hunk header as though it were from the ancestor,
337 		// as it may have a function header appearing after it which
338 		// was copied out of the ancestor file.
339 		//
340 		out[0].write(buf, ptr, eol - ptr);
341 
342 		SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) {
343 			eol = nextLF(buf, ptr);
344 			switch (buf[ptr]) {
345 			case ' ':
346 			case '\n':
347 			case '\\':
348 				out[0].write(buf, ptr, eol - ptr);
349 				out[1].write(buf, ptr, eol - ptr);
350 				break;
351 			case '-':
352 				out[0].write(buf, ptr, eol - ptr);
353 				break;
354 			case '+':
355 				out[1].write(buf, ptr, eol - ptr);
356 				break;
357 			default:
358 				break SCAN;
359 			}
360 		}
361 	}
362 
363 	void extractFileLines(final StringBuilder sb, final String[] text,
364 			final int[] offsets) {
365 		final byte[] buf = file.buf;
366 		int ptr = startOffset;
367 		int eol = nextLF(buf, ptr);
368 		if (endOffset <= eol)
369 			return;
370 		copyLine(sb, text, offsets, 0);
371 		SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) {
372 			eol = nextLF(buf, ptr);
373 			switch (buf[ptr]) {
374 			case ' ':
375 			case '\n':
376 			case '\\':
377 				copyLine(sb, text, offsets, 0);
378 				skipLine(text, offsets, 1);
379 				break;
380 			case '-':
381 				copyLine(sb, text, offsets, 0);
382 				break;
383 			case '+':
384 				copyLine(sb, text, offsets, 1);
385 				break;
386 			default:
387 				break SCAN;
388 			}
389 		}
390 	}
391 
392 	void copyLine(final StringBuilder sb, final String[] text,
393 			final int[] offsets, final int fileIdx) {
394 		final String s = text[fileIdx];
395 		final int start = offsets[fileIdx];
396 		int end = s.indexOf('\n', start);
397 		if (end < 0)
398 			end = s.length();
399 		else
400 			end++;
401 		sb.append(s, start, end);
402 		offsets[fileIdx] = end;
403 	}
404 
405 	void skipLine(final String[] text, final int[] offsets,
406 			final int fileIdx) {
407 		final String s = text[fileIdx];
408 		final int end = s.indexOf('\n', offsets[fileIdx]);
409 		offsets[fileIdx] = end < 0 ? s.length() : end + 1;
410 	}
411 
412 	/** {@inheritDoc} */
413 	@SuppressWarnings("nls")
414 	@Override
415 	public String toString() {
416 		StringBuilder buf = new StringBuilder();
417 		buf.append("HunkHeader[");
418 		buf.append(getOldImage().getStartLine());
419 		buf.append(',');
420 		buf.append(getOldImage().getLineCount());
421 		buf.append("->");
422 		buf.append(getNewStartLine()).append(',').append(getNewLineCount());
423 		buf.append(']');
424 		return buf.toString();
425 	}
426 }