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