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