HunkHeader.java

  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. package org.eclipse.jgit.patch;

  11. import static org.eclipse.jgit.util.RawParseUtils.match;
  12. import static org.eclipse.jgit.util.RawParseUtils.nextLF;
  13. import static org.eclipse.jgit.util.RawParseUtils.parseBase10;

  14. import java.io.IOException;
  15. import java.io.OutputStream;
  16. import java.text.MessageFormat;

  17. import org.eclipse.jgit.diff.Edit;
  18. import org.eclipse.jgit.diff.EditList;
  19. import org.eclipse.jgit.internal.JGitText;
  20. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  21. import org.eclipse.jgit.util.MutableInteger;

  22. /**
  23.  * Hunk header describing the layout of a single block of lines
  24.  */
  25. public class HunkHeader {
  26.     /** Details about an old image of the file. */
  27.     public abstract static class OldImage {
  28.         /** First line number the hunk starts on in this file. */
  29.         int startLine;

  30.         /** Total number of lines this hunk covers in this file. */
  31.         int lineCount;

  32.         /** Number of lines deleted by the post-image from this file. */
  33.         int nDeleted;

  34.         /** Number of lines added by the post-image not in this file. */
  35.         int nAdded;

  36.         /** @return first line number the hunk starts on in this file. */
  37.         public int getStartLine() {
  38.             return startLine;
  39.         }

  40.         /** @return total number of lines this hunk covers in this file. */
  41.         public int getLineCount() {
  42.             return lineCount;
  43.         }

  44.         /** @return number of lines deleted by the post-image from this file. */
  45.         public int getLinesDeleted() {
  46.             return nDeleted;
  47.         }

  48.         /** @return number of lines added by the post-image not in this file. */
  49.         public int getLinesAdded() {
  50.             return nAdded;
  51.         }

  52.         /** @return object id of the pre-image file. */
  53.         public abstract AbbreviatedObjectId getId();
  54.     }

  55.     final FileHeader file;

  56.     /** Offset within {@link #file}.buf to the "@@ -" line. */
  57.     final int startOffset;

  58.     /** Position 1 past the end of this hunk within {@link #file}'s buf. */
  59.     int endOffset;

  60.     private final OldImage old;

  61.     /** First line number in the post-image file where the hunk starts */
  62.     int newStartLine;

  63.     /** Total number of post-image lines this hunk covers (context + inserted) */
  64.     int newLineCount;

  65.     /** Total number of lines of context appearing in this hunk */
  66.     int nContext;

  67.     private EditList editList;

  68.     HunkHeader(FileHeader fh, int offset) {
  69.         this(fh, offset, new OldImage() {
  70.             @Override
  71.             public AbbreviatedObjectId getId() {
  72.                 return fh.getOldId();
  73.             }
  74.         });
  75.     }

  76.     HunkHeader(FileHeader fh, int offset, OldImage oi) {
  77.         file = fh;
  78.         startOffset = offset;
  79.         old = oi;
  80.     }

  81.     HunkHeader(FileHeader fh, EditList editList) {
  82.         this(fh, fh.buf.length);
  83.         this.editList = editList;
  84.         endOffset = startOffset;
  85.         nContext = 0;
  86.         if (editList.isEmpty()) {
  87.             newStartLine = 0;
  88.             newLineCount = 0;
  89.         } else {
  90.             newStartLine = editList.get(0).getBeginB();
  91.             Edit last = editList.get(editList.size() - 1);
  92.             newLineCount = last.getEndB() - newStartLine;
  93.         }
  94.     }

  95.     /**
  96.      * Get header for the file this hunk applies to.
  97.      *
  98.      * @return header for the file this hunk applies to.
  99.      */
  100.     public FileHeader getFileHeader() {
  101.         return file;
  102.     }

  103.     /**
  104.      * Get the byte array holding this hunk's patch script.
  105.      *
  106.      * @return the byte array holding this hunk's patch script.
  107.      */
  108.     public byte[] getBuffer() {
  109.         return file.buf;
  110.     }

  111.     /**
  112.      * Get offset of the start of this hunk in {@link #getBuffer()}.
  113.      *
  114.      * @return offset of the start of this hunk in {@link #getBuffer()}.
  115.      */
  116.     public int getStartOffset() {
  117.         return startOffset;
  118.     }

  119.     /**
  120.      * Get offset one past the end of the hunk in {@link #getBuffer()}.
  121.      *
  122.      * @return offset one past the end of the hunk in {@link #getBuffer()}.
  123.      */
  124.     public int getEndOffset() {
  125.         return endOffset;
  126.     }

  127.     /**
  128.      * Get information about the old image mentioned in this hunk.
  129.      *
  130.      * @return information about the old image mentioned in this hunk.
  131.      */
  132.     public OldImage getOldImage() {
  133.         return old;
  134.     }

  135.     /**
  136.      * Get first line number in the post-image file where the hunk starts.
  137.      *
  138.      * @return first line number in the post-image file where the hunk starts.
  139.      */
  140.     public int getNewStartLine() {
  141.         return newStartLine;
  142.     }

  143.     /**
  144.      * Get total number of post-image lines this hunk covers.
  145.      *
  146.      * @return total number of post-image lines this hunk covers.
  147.      */
  148.     public int getNewLineCount() {
  149.         return newLineCount;
  150.     }

  151.     /**
  152.      * Get total number of lines of context appearing in this hunk.
  153.      *
  154.      * @return total number of lines of context appearing in this hunk.
  155.      */
  156.     public int getLinesContext() {
  157.         return nContext;
  158.     }

  159.     /**
  160.      * Convert to a list describing the content edits performed within the hunk.
  161.      *
  162.      * @return a list describing the content edits performed within the hunk.
  163.      */
  164.     public EditList toEditList() {
  165.         if (editList == null) {
  166.             editList = new EditList();
  167.             final byte[] buf = file.buf;
  168.             int c = nextLF(buf, startOffset);
  169.             int oLine = old.startLine;
  170.             int nLine = newStartLine;
  171.             Edit in = null;

  172.             SCAN: for (; c < endOffset; c = nextLF(buf, c)) {
  173.                 switch (buf[c]) {
  174.                 case ' ':
  175.                 case '\n':
  176.                     in = null;
  177.                     oLine++;
  178.                     nLine++;
  179.                     continue;

  180.                 case '-':
  181.                     if (in == null) {
  182.                         in = new Edit(oLine - 1, nLine - 1);
  183.                         editList.add(in);
  184.                     }
  185.                     oLine++;
  186.                     in.extendA();
  187.                     continue;

  188.                 case '+':
  189.                     if (in == null) {
  190.                         in = new Edit(oLine - 1, nLine - 1);
  191.                         editList.add(in);
  192.                     }
  193.                     nLine++;
  194.                     in.extendB();
  195.                     continue;

  196.                 case '\\': // Matches "\ No newline at end of file"
  197.                     continue;

  198.                 default:
  199.                     break SCAN;
  200.                 }
  201.             }
  202.         }
  203.         return editList;
  204.     }

  205.     void parseHeader() {
  206.         // Parse "@@ -236,9 +236,9 @@ protected boolean"
  207.         //
  208.         final byte[] buf = file.buf;
  209.         final MutableInteger ptr = new MutableInteger();
  210.         ptr.value = nextLF(buf, startOffset, ' ');
  211.         old.startLine = -parseBase10(buf, ptr.value, ptr);
  212.         if (buf[ptr.value] == ',')
  213.             old.lineCount = parseBase10(buf, ptr.value + 1, ptr);
  214.         else
  215.             old.lineCount = 1;

  216.         newStartLine = parseBase10(buf, ptr.value + 1, ptr);
  217.         if (buf[ptr.value] == ',')
  218.             newLineCount = parseBase10(buf, ptr.value + 1, ptr);
  219.         else
  220.             newLineCount = 1;
  221.     }

  222.     int parseBody(Patch script, int end) {
  223.         final byte[] buf = file.buf;
  224.         int c = nextLF(buf, startOffset), last = c;

  225.         old.nDeleted = 0;
  226.         old.nAdded = 0;

  227.         SCAN: for (; c < end; last = c, c = nextLF(buf, c)) {
  228.             switch (buf[c]) {
  229.             case ' ':
  230.             case '\n':
  231.                 nContext++;
  232.                 continue;

  233.             case '-':
  234.                 old.nDeleted++;
  235.                 continue;

  236.             case '+':
  237.                 old.nAdded++;
  238.                 continue;

  239.             case '\\': // Matches "\ No newline at end of file"
  240.                 continue;

  241.             default:
  242.                 break SCAN;
  243.             }
  244.         }

  245.         if (last < end && nContext + old.nDeleted - 1 == old.lineCount
  246.                 && nContext + old.nAdded == newLineCount
  247.                 && match(buf, last, Patch.SIG_FOOTER) >= 0) {
  248.             // This is an extremely common occurrence of "corruption".
  249.             // Users add footers with their signatures after this mark,
  250.             // and git diff adds the git executable version number.
  251.             // Let it slide; the hunk otherwise looked sound.
  252.             //
  253.             old.nDeleted--;
  254.             return last;
  255.         }

  256.         if (nContext + old.nDeleted < old.lineCount) {
  257.             final int missingCount = old.lineCount - (nContext + old.nDeleted);
  258.             script.error(buf, startOffset, MessageFormat.format(
  259.                     JGitText.get().truncatedHunkOldLinesMissing,
  260.                     Integer.valueOf(missingCount)));

  261.         } else if (nContext + old.nAdded < newLineCount) {
  262.             final int missingCount = newLineCount - (nContext + old.nAdded);
  263.             script.error(buf, startOffset, MessageFormat.format(
  264.                     JGitText.get().truncatedHunkNewLinesMissing,
  265.                     Integer.valueOf(missingCount)));

  266.         } else if (nContext + old.nDeleted > old.lineCount
  267.                 || nContext + old.nAdded > newLineCount) {
  268.             final String oldcnt = old.lineCount + ":" + newLineCount; //$NON-NLS-1$
  269.             final String newcnt = (nContext + old.nDeleted) + ":" //$NON-NLS-1$
  270.                     + (nContext + old.nAdded);
  271.             script.warn(buf, startOffset, MessageFormat.format(
  272.                     JGitText.get().hunkHeaderDoesNotMatchBodyLineCountOf, oldcnt, newcnt));
  273.         }

  274.         return c;
  275.     }

  276.     void extractFileLines(OutputStream[] out) throws IOException {
  277.         final byte[] buf = file.buf;
  278.         int ptr = startOffset;
  279.         int eol = nextLF(buf, ptr);
  280.         if (endOffset <= eol)
  281.             return;

  282.         // Treat the hunk header as though it were from the ancestor,
  283.         // as it may have a function header appearing after it which
  284.         // was copied out of the ancestor file.
  285.         //
  286.         out[0].write(buf, ptr, eol - ptr);

  287.         SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) {
  288.             eol = nextLF(buf, ptr);
  289.             switch (buf[ptr]) {
  290.             case ' ':
  291.             case '\n':
  292.             case '\\':
  293.                 out[0].write(buf, ptr, eol - ptr);
  294.                 out[1].write(buf, ptr, eol - ptr);
  295.                 break;
  296.             case '-':
  297.                 out[0].write(buf, ptr, eol - ptr);
  298.                 break;
  299.             case '+':
  300.                 out[1].write(buf, ptr, eol - ptr);
  301.                 break;
  302.             default:
  303.                 break SCAN;
  304.             }
  305.         }
  306.     }

  307.     void extractFileLines(final StringBuilder sb, final String[] text,
  308.             final int[] offsets) {
  309.         final byte[] buf = file.buf;
  310.         int ptr = startOffset;
  311.         int eol = nextLF(buf, ptr);
  312.         if (endOffset <= eol)
  313.             return;
  314.         copyLine(sb, text, offsets, 0);
  315.         SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) {
  316.             eol = nextLF(buf, ptr);
  317.             switch (buf[ptr]) {
  318.             case ' ':
  319.             case '\n':
  320.             case '\\':
  321.                 copyLine(sb, text, offsets, 0);
  322.                 skipLine(text, offsets, 1);
  323.                 break;
  324.             case '-':
  325.                 copyLine(sb, text, offsets, 0);
  326.                 break;
  327.             case '+':
  328.                 copyLine(sb, text, offsets, 1);
  329.                 break;
  330.             default:
  331.                 break SCAN;
  332.             }
  333.         }
  334.     }

  335.     void copyLine(final StringBuilder sb, final String[] text,
  336.             final int[] offsets, final int fileIdx) {
  337.         final String s = text[fileIdx];
  338.         final int start = offsets[fileIdx];
  339.         int end = s.indexOf('\n', start);
  340.         if (end < 0)
  341.             end = s.length();
  342.         else
  343.             end++;
  344.         sb.append(s, start, end);
  345.         offsets[fileIdx] = end;
  346.     }

  347.     void skipLine(final String[] text, final int[] offsets,
  348.             final int fileIdx) {
  349.         final String s = text[fileIdx];
  350.         final int end = s.indexOf('\n', offsets[fileIdx]);
  351.         offsets[fileIdx] = end < 0 ? s.length() : end + 1;
  352.     }

  353.     /** {@inheritDoc} */
  354.     @SuppressWarnings("nls")
  355.     @Override
  356.     public String toString() {
  357.         StringBuilder buf = new StringBuilder();
  358.         buf.append("HunkHeader[");
  359.         buf.append(getOldImage().getStartLine());
  360.         buf.append(',');
  361.         buf.append(getOldImage().getLineCount());
  362.         buf.append("->");
  363.         buf.append(getNewStartLine()).append(',').append(getNewLineCount());
  364.         buf.append(']');
  365.         return buf.toString();
  366.     }
  367. }