| /* |
| * Copyright (C) 2008-2009, Google Inc. and others |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Distribution License v. 1.0 which is available at |
| * https://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| package org.eclipse.jgit.patch; |
| |
| import static org.eclipse.jgit.util.RawParseUtils.match; |
| import static org.eclipse.jgit.util.RawParseUtils.nextLF; |
| import static org.eclipse.jgit.util.RawParseUtils.parseBase10; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.text.MessageFormat; |
| |
| import org.eclipse.jgit.diff.Edit; |
| import org.eclipse.jgit.diff.EditList; |
| import org.eclipse.jgit.internal.JGitText; |
| import org.eclipse.jgit.lib.AbbreviatedObjectId; |
| import org.eclipse.jgit.util.MutableInteger; |
| |
| /** |
| * Hunk header describing the layout of a single block of lines |
| */ |
| public class HunkHeader { |
| /** Details about an old image of the file. */ |
| public abstract static class OldImage { |
| /** First line number the hunk starts on in this file. */ |
| int startLine; |
| |
| /** Total number of lines this hunk covers in this file. */ |
| int lineCount; |
| |
| /** Number of lines deleted by the post-image from this file. */ |
| int nDeleted; |
| |
| /** Number of lines added by the post-image not in this file. */ |
| int nAdded; |
| |
| /** @return first line number the hunk starts on in this file. */ |
| public int getStartLine() { |
| return startLine; |
| } |
| |
| /** @return total number of lines this hunk covers in this file. */ |
| public int getLineCount() { |
| return lineCount; |
| } |
| |
| /** @return number of lines deleted by the post-image from this file. */ |
| public int getLinesDeleted() { |
| return nDeleted; |
| } |
| |
| /** @return number of lines added by the post-image not in this file. */ |
| public int getLinesAdded() { |
| return nAdded; |
| } |
| |
| /** @return object id of the pre-image file. */ |
| public abstract AbbreviatedObjectId getId(); |
| } |
| |
| final FileHeader file; |
| |
| /** Offset within {@link #file}.buf to the "@@ -" line. */ |
| final int startOffset; |
| |
| /** Position 1 past the end of this hunk within {@link #file}'s buf. */ |
| int endOffset; |
| |
| private final OldImage old; |
| |
| /** First line number in the post-image file where the hunk starts */ |
| int newStartLine; |
| |
| /** Total number of post-image lines this hunk covers (context + inserted) */ |
| int newLineCount; |
| |
| /** Total number of lines of context appearing in this hunk */ |
| int nContext; |
| |
| private EditList editList; |
| |
| HunkHeader(FileHeader fh, int offset) { |
| this(fh, offset, new OldImage() { |
| @Override |
| public AbbreviatedObjectId getId() { |
| return fh.getOldId(); |
| } |
| }); |
| } |
| |
| HunkHeader(FileHeader fh, int offset, OldImage oi) { |
| file = fh; |
| startOffset = offset; |
| old = oi; |
| } |
| |
| HunkHeader(FileHeader fh, EditList editList) { |
| this(fh, fh.buf.length); |
| this.editList = editList; |
| endOffset = startOffset; |
| nContext = 0; |
| if (editList.isEmpty()) { |
| newStartLine = 0; |
| newLineCount = 0; |
| } else { |
| newStartLine = editList.get(0).getBeginB(); |
| Edit last = editList.get(editList.size() - 1); |
| newLineCount = last.getEndB() - newStartLine; |
| } |
| } |
| |
| /** |
| * Get header for the file this hunk applies to. |
| * |
| * @return header for the file this hunk applies to. |
| */ |
| public FileHeader getFileHeader() { |
| return file; |
| } |
| |
| /** |
| * Get the byte array holding this hunk's patch script. |
| * |
| * @return the byte array holding this hunk's patch script. |
| */ |
| public byte[] getBuffer() { |
| return file.buf; |
| } |
| |
| /** |
| * Get offset of the start of this hunk in {@link #getBuffer()}. |
| * |
| * @return offset of the start of this hunk in {@link #getBuffer()}. |
| */ |
| public int getStartOffset() { |
| return startOffset; |
| } |
| |
| /** |
| * Get offset one past the end of the hunk in {@link #getBuffer()}. |
| * |
| * @return offset one past the end of the hunk in {@link #getBuffer()}. |
| */ |
| public int getEndOffset() { |
| return endOffset; |
| } |
| |
| /** |
| * Get information about the old image mentioned in this hunk. |
| * |
| * @return information about the old image mentioned in this hunk. |
| */ |
| public OldImage getOldImage() { |
| return old; |
| } |
| |
| /** |
| * Get first line number in the post-image file where the hunk starts. |
| * |
| * @return first line number in the post-image file where the hunk starts. |
| */ |
| public int getNewStartLine() { |
| return newStartLine; |
| } |
| |
| /** |
| * Get total number of post-image lines this hunk covers. |
| * |
| * @return total number of post-image lines this hunk covers. |
| */ |
| public int getNewLineCount() { |
| return newLineCount; |
| } |
| |
| /** |
| * Get total number of lines of context appearing in this hunk. |
| * |
| * @return total number of lines of context appearing in this hunk. |
| */ |
| public int getLinesContext() { |
| return nContext; |
| } |
| |
| /** |
| * Convert to a list describing the content edits performed within the hunk. |
| * |
| * @return a list describing the content edits performed within the hunk. |
| */ |
| public EditList toEditList() { |
| if (editList == null) { |
| editList = new EditList(); |
| final byte[] buf = file.buf; |
| int c = nextLF(buf, startOffset); |
| int oLine = old.startLine; |
| int nLine = newStartLine; |
| Edit in = null; |
| |
| SCAN: for (; c < endOffset; c = nextLF(buf, c)) { |
| switch (buf[c]) { |
| case ' ': |
| case '\n': |
| in = null; |
| oLine++; |
| nLine++; |
| continue; |
| |
| case '-': |
| if (in == null) { |
| in = new Edit(oLine - 1, nLine - 1); |
| editList.add(in); |
| } |
| oLine++; |
| in.extendA(); |
| continue; |
| |
| case '+': |
| if (in == null) { |
| in = new Edit(oLine - 1, nLine - 1); |
| editList.add(in); |
| } |
| nLine++; |
| in.extendB(); |
| continue; |
| |
| case '\\': // Matches "\ No newline at end of file" |
| continue; |
| |
| default: |
| break SCAN; |
| } |
| } |
| } |
| return editList; |
| } |
| |
| void parseHeader() { |
| // Parse "@@ -236,9 +236,9 @@ protected boolean" |
| // |
| final byte[] buf = file.buf; |
| final MutableInteger ptr = new MutableInteger(); |
| ptr.value = nextLF(buf, startOffset, ' '); |
| old.startLine = -parseBase10(buf, ptr.value, ptr); |
| if (buf[ptr.value] == ',') |
| old.lineCount = parseBase10(buf, ptr.value + 1, ptr); |
| else |
| old.lineCount = 1; |
| |
| newStartLine = parseBase10(buf, ptr.value + 1, ptr); |
| if (buf[ptr.value] == ',') |
| newLineCount = parseBase10(buf, ptr.value + 1, ptr); |
| else |
| newLineCount = 1; |
| } |
| |
| int parseBody(Patch script, int end) { |
| final byte[] buf = file.buf; |
| int c = nextLF(buf, startOffset), last = c; |
| |
| old.nDeleted = 0; |
| old.nAdded = 0; |
| |
| SCAN: for (; c < end; last = c, c = nextLF(buf, c)) { |
| switch (buf[c]) { |
| case ' ': |
| case '\n': |
| nContext++; |
| continue; |
| |
| case '-': |
| old.nDeleted++; |
| continue; |
| |
| case '+': |
| old.nAdded++; |
| continue; |
| |
| case '\\': // Matches "\ No newline at end of file" |
| continue; |
| |
| default: |
| break SCAN; |
| } |
| } |
| |
| if (last < end && nContext + old.nDeleted - 1 == old.lineCount |
| && nContext + old.nAdded == newLineCount |
| && match(buf, last, Patch.SIG_FOOTER) >= 0) { |
| // This is an extremely common occurrence of "corruption". |
| // Users add footers with their signatures after this mark, |
| // and git diff adds the git executable version number. |
| // Let it slide; the hunk otherwise looked sound. |
| // |
| old.nDeleted--; |
| return last; |
| } |
| |
| if (nContext + old.nDeleted < old.lineCount) { |
| final int missingCount = old.lineCount - (nContext + old.nDeleted); |
| script.error(buf, startOffset, MessageFormat.format( |
| JGitText.get().truncatedHunkOldLinesMissing, |
| Integer.valueOf(missingCount))); |
| |
| } else if (nContext + old.nAdded < newLineCount) { |
| final int missingCount = newLineCount - (nContext + old.nAdded); |
| script.error(buf, startOffset, MessageFormat.format( |
| JGitText.get().truncatedHunkNewLinesMissing, |
| Integer.valueOf(missingCount))); |
| |
| } else if (nContext + old.nDeleted > old.lineCount |
| || nContext + old.nAdded > newLineCount) { |
| final String oldcnt = old.lineCount + ":" + newLineCount; //$NON-NLS-1$ |
| final String newcnt = (nContext + old.nDeleted) + ":" //$NON-NLS-1$ |
| + (nContext + old.nAdded); |
| script.warn(buf, startOffset, MessageFormat.format( |
| JGitText.get().hunkHeaderDoesNotMatchBodyLineCountOf, oldcnt, newcnt)); |
| } |
| |
| return c; |
| } |
| |
| void extractFileLines(OutputStream[] out) throws IOException { |
| final byte[] buf = file.buf; |
| int ptr = startOffset; |
| int eol = nextLF(buf, ptr); |
| if (endOffset <= eol) |
| return; |
| |
| // Treat the hunk header as though it were from the ancestor, |
| // as it may have a function header appearing after it which |
| // was copied out of the ancestor file. |
| // |
| out[0].write(buf, ptr, eol - ptr); |
| |
| SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) { |
| eol = nextLF(buf, ptr); |
| switch (buf[ptr]) { |
| case ' ': |
| case '\n': |
| case '\\': |
| out[0].write(buf, ptr, eol - ptr); |
| out[1].write(buf, ptr, eol - ptr); |
| break; |
| case '-': |
| out[0].write(buf, ptr, eol - ptr); |
| break; |
| case '+': |
| out[1].write(buf, ptr, eol - ptr); |
| break; |
| default: |
| break SCAN; |
| } |
| } |
| } |
| |
| void extractFileLines(final StringBuilder sb, final String[] text, |
| final int[] offsets) { |
| final byte[] buf = file.buf; |
| int ptr = startOffset; |
| int eol = nextLF(buf, ptr); |
| if (endOffset <= eol) |
| return; |
| copyLine(sb, text, offsets, 0); |
| SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) { |
| eol = nextLF(buf, ptr); |
| switch (buf[ptr]) { |
| case ' ': |
| case '\n': |
| case '\\': |
| copyLine(sb, text, offsets, 0); |
| skipLine(text, offsets, 1); |
| break; |
| case '-': |
| copyLine(sb, text, offsets, 0); |
| break; |
| case '+': |
| copyLine(sb, text, offsets, 1); |
| break; |
| default: |
| break SCAN; |
| } |
| } |
| } |
| |
| void copyLine(final StringBuilder sb, final String[] text, |
| final int[] offsets, final int fileIdx) { |
| final String s = text[fileIdx]; |
| final int start = offsets[fileIdx]; |
| int end = s.indexOf('\n', start); |
| if (end < 0) |
| end = s.length(); |
| else |
| end++; |
| sb.append(s, start, end); |
| offsets[fileIdx] = end; |
| } |
| |
| void skipLine(final String[] text, final int[] offsets, |
| final int fileIdx) { |
| final String s = text[fileIdx]; |
| final int end = s.indexOf('\n', offsets[fileIdx]); |
| offsets[fileIdx] = end < 0 ? s.length() : end + 1; |
| } |
| |
| /** {@inheritDoc} */ |
| @SuppressWarnings("nls") |
| @Override |
| public String toString() { |
| StringBuilder buf = new StringBuilder(); |
| buf.append("HunkHeader["); |
| buf.append(getOldImage().getStartLine()); |
| buf.append(','); |
| buf.append(getOldImage().getLineCount()); |
| buf.append("->"); |
| buf.append(getNewStartLine()).append(',').append(getNewLineCount()); |
| buf.append(']'); |
| return buf.toString(); |
| } |
| } |