* [JGIT PATCH 3/5] Define FileHeader to parse the header block of a git diff
From: Shawn O. Pearce @ 2008-12-11 4:58 UTC (permalink / raw)
To: Robin Rosenberg; +Cc: git
In-Reply-To: <1228971522-28764-3-git-send-email-spearce@spearce.org>
This class parses the top header lines of a git style diff, such as:
diff --git a/SUBMITTING_PATCHES b/Q
similarity index 100%
copy from SUBMITTING_PATCHES
copy to Q
or:
diff --git a/Q b/Q
new file mode 100644
index 0000000..e4a135e
--- /dev/null
+++ b/Q
and makes the information available in an object form. Unit tests
cover the different styles of headers that are commonly created by
C git, including both rename formats.
The hunk header information is not handled by this class, and it
does not have a public API. It is my intention to wrap this into
a larger container class that handles multiple FileHeaders at once,
with the base case of course being a single FileHeader describing
a patch that impacts only one file.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
| 391 ++++++++++++++++++
| 430 ++++++++++++++++++++
2 files changed, 821 insertions(+), 0 deletions(-)
create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java
create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java
--git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java
new file mode 100644
index 0000000..1d87bc0
--- /dev/null
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.spearce.jgit.patch;
+
+import junit.framework.TestCase;
+
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.FileMode;
+import org.spearce.jgit.lib.ObjectId;
+
+public class FileHeaderTest extends TestCase {
+ public void testParseGitFileName_Empty() {
+ assertEquals(-1, data("").parseGitFileName(0));
+ }
+
+ public void testParseGitFileName_NoLF() {
+ assertEquals(-1, data("a/ b/").parseGitFileName(0));
+ }
+
+ public void testParseGitFileName_NoSecondLine() {
+ assertEquals(-1, data("\n").parseGitFileName(0));
+ }
+
+ public void testParseGitFileName_EmptyHeader() {
+ assertEquals(1, data("\n\n").parseGitFileName(0));
+ }
+
+ public void testParseGitFileName_Foo() {
+ final String name = "foo";
+ final FileHeader fh = header(name);
+ assertEquals(gitLine(name).length(), fh.parseGitFileName(0));
+ assertEquals(name, fh.getOldName());
+ assertSame(fh.getOldName(), fh.getNewName());
+ }
+
+ public void testParseGitFileName_FailFooBar() {
+ final FileHeader fh = data("a/foo b/bar\n-");
+ assertTrue(fh.parseGitFileName(0) > 0);
+ assertNull(fh.getOldName());
+ assertNull(fh.getNewName());
+ }
+
+ public void testParseGitFileName_FooSpBar() {
+ final String name = "foo bar";
+ final FileHeader fh = header(name);
+ assertEquals(gitLine(name).length(), fh.parseGitFileName(0));
+ assertEquals(name, fh.getOldName());
+ assertSame(fh.getOldName(), fh.getNewName());
+ }
+
+ public void testParseGitFileName_DqFooTabBar() {
+ final String name = "foo\tbar";
+ final String dqName = "foo\\tbar";
+ final FileHeader fh = dqHeader(dqName);
+ assertEquals(dqGitLine(dqName).length(), fh.parseGitFileName(0));
+ assertEquals(name, fh.getOldName());
+ assertSame(fh.getOldName(), fh.getNewName());
+ }
+
+ public void testParseGitFileName_DqFooSpLfNulBar() {
+ final String name = "foo \n\0bar";
+ final String dqName = "foo \\n\\0bar";
+ final FileHeader fh = dqHeader(dqName);
+ assertEquals(dqGitLine(dqName).length(), fh.parseGitFileName(0));
+ assertEquals(name, fh.getOldName());
+ assertSame(fh.getOldName(), fh.getNewName());
+ }
+
+ public void testParseGitFileName_SrcFooC() {
+ final String name = "src/foo/bar/argh/code.c";
+ final FileHeader fh = header(name);
+ assertEquals(gitLine(name).length(), fh.parseGitFileName(0));
+ assertEquals(name, fh.getOldName());
+ assertSame(fh.getOldName(), fh.getNewName());
+ }
+
+ public void testParseGitFileName_SrcFooCNonStandardPrefix() {
+ final String name = "src/foo/bar/argh/code.c";
+ final String header = "project-v-1.0/" + name + " mydev/" + name + "\n";
+ final FileHeader fh = data(header + "-");
+ assertEquals(header.length(), fh.parseGitFileName(0));
+ assertEquals(name, fh.getOldName());
+ assertSame(fh.getOldName(), fh.getNewName());
+ }
+
+ public void testParseUnicodeName_NewFile() {
+ final FileHeader fh = data("diff --git \"a/\\303\\205ngstr\\303\\266m\" \"b/\\303\\205ngstr\\303\\266m\"\n"
+ + "new file mode 100644\n"
+ + "index 0000000..7898192\n"
+ + "--- /dev/null\n"
+ + "+++ \"b/\\303\\205ngstr\\303\\266m\"\n"
+ + "@@ -0,0 +1 @@\n" + "+a\n");
+ assertParse(fh);
+
+ assertEquals("/dev/null", fh.getOldName());
+ assertSame(FileHeader.DEV_NULL, fh.getOldName());
+ assertEquals("\u00c5ngstr\u00f6m", fh.getNewName());
+
+ assertSame(FileHeader.ChangeType.ADD, fh.getChangeType());
+
+ assertNull(fh.getOldMode());
+ assertSame(FileMode.REGULAR_FILE, fh.getNewMode());
+
+ assertEquals("0000000", fh.getOldId().name());
+ assertEquals("7898192", fh.getNewId().name());
+ assertEquals(0, fh.getScore());
+ }
+
+ public void testParseUnicodeName_DeleteFile() {
+ final FileHeader fh = data("diff --git \"a/\\303\\205ngstr\\303\\266m\" \"b/\\303\\205ngstr\\303\\266m\"\n"
+ + "deleted file mode 100644\n"
+ + "index 7898192..0000000\n"
+ + "--- \"a/\\303\\205ngstr\\303\\266m\"\n"
+ + "+++ /dev/null\n"
+ + "@@ -1 +0,0 @@\n" + "-a\n");
+ assertParse(fh);
+
+ assertEquals("\u00c5ngstr\u00f6m", fh.getOldName());
+ assertEquals("/dev/null", fh.getNewName());
+ assertSame(FileHeader.DEV_NULL, fh.getNewName());
+
+ assertSame(FileHeader.ChangeType.DELETE, fh.getChangeType());
+
+ assertSame(FileMode.REGULAR_FILE, fh.getOldMode());
+ assertNull(fh.getNewMode());
+
+ assertEquals("7898192", fh.getOldId().name());
+ assertEquals("0000000", fh.getNewId().name());
+ assertEquals(0, fh.getScore());
+ }
+
+ public void testParseModeChange() {
+ final FileHeader fh = data("diff --git a/a b b/a b\n"
+ + "old mode 100644\n" + "new mode 100755\n");
+ assertParse(fh);
+ assertEquals("a b", fh.getOldName());
+ assertEquals("a b", fh.getNewName());
+
+ assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType());
+
+ assertNull(fh.getOldId());
+ assertNull(fh.getNewId());
+
+ assertSame(FileMode.REGULAR_FILE, fh.getOldMode());
+ assertSame(FileMode.EXECUTABLE_FILE, fh.getNewMode());
+ assertEquals(0, fh.getScore());
+ }
+
+ public void testParseRename100_NewStyle() {
+ final FileHeader fh = data("diff --git a/a b/ c/\\303\\205ngstr\\303\\266m\n"
+ + "similarity index 100%\n"
+ + "rename from a\n"
+ + "rename to \" c/\\303\\205ngstr\\303\\266m\"\n");
+ int ptr = fh.parseGitFileName(0);
+ assertTrue(ptr > 0);
+ assertNull(fh.getOldName()); // can't parse names on a rename
+ assertNull(fh.getNewName());
+
+ ptr = fh.parseGitHeaders(ptr);
+ assertTrue(ptr > 0);
+
+ assertEquals("a", fh.getOldName());
+ assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewName());
+
+ assertSame(FileHeader.ChangeType.RENAME, fh.getChangeType());
+
+ assertNull(fh.getOldId());
+ assertNull(fh.getNewId());
+
+ assertNull(fh.getOldMode());
+ assertNull(fh.getNewMode());
+
+ assertEquals(100, fh.getScore());
+ }
+
+ public void testParseRename100_OldStyle() {
+ final FileHeader fh = data("diff --git a/a b/ c/\\303\\205ngstr\\303\\266m\n"
+ + "similarity index 100%\n"
+ + "rename old a\n"
+ + "rename new \" c/\\303\\205ngstr\\303\\266m\"\n");
+ int ptr = fh.parseGitFileName(0);
+ assertTrue(ptr > 0);
+ assertNull(fh.getOldName()); // can't parse names on a rename
+ assertNull(fh.getNewName());
+
+ ptr = fh.parseGitHeaders(ptr);
+ assertTrue(ptr > 0);
+
+ assertEquals("a", fh.getOldName());
+ assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewName());
+
+ assertSame(FileHeader.ChangeType.RENAME, fh.getChangeType());
+
+ assertNull(fh.getOldId());
+ assertNull(fh.getNewId());
+
+ assertNull(fh.getOldMode());
+ assertNull(fh.getNewMode());
+
+ assertEquals(100, fh.getScore());
+ }
+ public void testParseCopy100() {
+ final FileHeader fh = data("diff --git a/a b/ c/\\303\\205ngstr\\303\\266m\n"
+ + "similarity index 100%\n"
+ + "copy from a\n"
+ + "copy to \" c/\\303\\205ngstr\\303\\266m\"\n");
+ int ptr = fh.parseGitFileName(0);
+ assertTrue(ptr > 0);
+ assertNull(fh.getOldName()); // can't parse names on a copy
+ assertNull(fh.getNewName());
+
+ ptr = fh.parseGitHeaders(ptr);
+ assertTrue(ptr > 0);
+
+ assertEquals("a", fh.getOldName());
+ assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewName());
+
+ assertSame(FileHeader.ChangeType.COPY, fh.getChangeType());
+
+ assertNull(fh.getOldId());
+ assertNull(fh.getNewId());
+
+ assertNull(fh.getOldMode());
+ assertNull(fh.getNewMode());
+
+ assertEquals(100, fh.getScore());
+ }
+
+ public void testParseFullIndexLine_WithMode() {
+ final String oid = "78981922613b2afb6025042ff6bd878ac1994e85";
+ final String nid = "61780798228d17af2d34fce4cfbdf35556832472";
+ final FileHeader fh = data("diff --git a/a b/a\n" + "index " + oid
+ + ".." + nid + " 100644\n" + "--- a/a\n" + "+++ b/a\n");
+ assertParse(fh);
+
+ assertEquals("a", fh.getOldName());
+ assertEquals("a", fh.getNewName());
+
+ assertSame(FileMode.REGULAR_FILE, fh.getOldMode());
+ assertSame(FileMode.REGULAR_FILE, fh.getNewMode());
+
+ assertNotNull(fh.getOldId());
+ assertNotNull(fh.getNewId());
+
+ assertTrue(fh.getOldId().isComplete());
+ assertTrue(fh.getNewId().isComplete());
+
+ assertEquals(ObjectId.fromString(oid), fh.getOldId().toObjectId());
+ assertEquals(ObjectId.fromString(nid), fh.getNewId().toObjectId());
+ }
+
+ public void testParseFullIndexLine_NoMode() {
+ final String oid = "78981922613b2afb6025042ff6bd878ac1994e85";
+ final String nid = "61780798228d17af2d34fce4cfbdf35556832472";
+ final FileHeader fh = data("diff --git a/a b/a\n" + "index " + oid
+ + ".." + nid + "\n" + "--- a/a\n" + "+++ b/a\n");
+ assertParse(fh);
+
+ assertEquals("a", fh.getOldName());
+ assertEquals("a", fh.getNewName());
+
+ assertNull(fh.getOldMode());
+ assertNull(fh.getNewMode());
+
+ assertNotNull(fh.getOldId());
+ assertNotNull(fh.getNewId());
+
+ assertTrue(fh.getOldId().isComplete());
+ assertTrue(fh.getNewId().isComplete());
+
+ assertEquals(ObjectId.fromString(oid), fh.getOldId().toObjectId());
+ assertEquals(ObjectId.fromString(nid), fh.getNewId().toObjectId());
+ }
+
+ public void testParseAbbrIndexLine_WithMode() {
+ final int a = 7;
+ final String oid = "78981922613b2afb6025042ff6bd878ac1994e85";
+ final String nid = "61780798228d17af2d34fce4cfbdf35556832472";
+ final FileHeader fh = data("diff --git a/a b/a\n" + "index "
+ + oid.substring(0, a - 1) + ".." + nid.substring(0, a - 1)
+ + " 100644\n" + "--- a/a\n" + "+++ b/a\n");
+ assertParse(fh);
+
+ assertEquals("a", fh.getOldName());
+ assertEquals("a", fh.getNewName());
+
+ assertSame(FileMode.REGULAR_FILE, fh.getOldMode());
+ assertSame(FileMode.REGULAR_FILE, fh.getNewMode());
+
+ assertNotNull(fh.getOldId());
+ assertNotNull(fh.getNewId());
+
+ assertFalse(fh.getOldId().isComplete());
+ assertFalse(fh.getNewId().isComplete());
+
+ assertEquals(oid.substring(0, a - 1), fh.getOldId().name());
+ assertEquals(nid.substring(0, a - 1), fh.getNewId().name());
+
+ assertTrue(ObjectId.fromString(oid).startsWith(fh.getOldId()));
+ assertTrue(ObjectId.fromString(nid).startsWith(fh.getNewId()));
+ }
+
+ public void testParseAbbrIndexLine_NoMode() {
+ final int a = 7;
+ final String oid = "78981922613b2afb6025042ff6bd878ac1994e85";
+ final String nid = "61780798228d17af2d34fce4cfbdf35556832472";
+ final FileHeader fh = data("diff --git a/a b/a\n" + "index "
+ + oid.substring(0, a - 1) + ".." + nid.substring(0, a - 1)
+ + "\n" + "--- a/a\n" + "+++ b/a\n");
+ assertParse(fh);
+
+ assertEquals("a", fh.getOldName());
+ assertEquals("a", fh.getNewName());
+
+ assertNull(fh.getOldMode());
+ assertNull(fh.getNewMode());
+
+ assertNotNull(fh.getOldId());
+ assertNotNull(fh.getNewId());
+
+ assertFalse(fh.getOldId().isComplete());
+ assertFalse(fh.getNewId().isComplete());
+
+ assertEquals(oid.substring(0, a - 1), fh.getOldId().name());
+ assertEquals(nid.substring(0, a - 1), fh.getNewId().name());
+
+ assertTrue(ObjectId.fromString(oid).startsWith(fh.getOldId()));
+ assertTrue(ObjectId.fromString(nid).startsWith(fh.getNewId()));
+ }
+
+ private static void assertParse(final FileHeader fh) {
+ int ptr = fh.parseGitFileName(0);
+ assertTrue(ptr > 0);
+ ptr = fh.parseGitHeaders(ptr);
+ assertTrue(ptr > 0);
+ }
+
+ private static FileHeader data(final String in) {
+ return new FileHeader(Constants.encodeASCII(in), 0);
+ }
+
+ private static FileHeader header(final String path) {
+ return data(gitLine(path) + "--- " + path + "\n");
+ }
+
+ private static String gitLine(final String path) {
+ return "a/" + path + " b/" + path + "\n";
+ }
+
+ private static FileHeader dqHeader(final String path) {
+ return data(dqGitLine(path) + "--- " + path + "\n");
+ }
+
+ private static String dqGitLine(final String path) {
+ return "\"a/" + path + "\" \"b/" + path + "\"\n";
+ }
+}
--git a/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java
new file mode 100644
index 0000000..5d1454b
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.patch;
+
+import static org.spearce.jgit.lib.Constants.encodeASCII;
+import static org.spearce.jgit.util.RawParseUtils.decode;
+import static org.spearce.jgit.util.RawParseUtils.match;
+import static org.spearce.jgit.util.RawParseUtils.nextLF;
+import static org.spearce.jgit.util.RawParseUtils.parseBase10;
+
+import org.spearce.jgit.lib.AbbreviatedObjectId;
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.FileMode;
+import org.spearce.jgit.util.QuotedString;
+
+/** Patch header describing an action for a single file path. */
+public class FileHeader {
+ /** Magical file name used for file adds or deletes. */
+ public static final String DEV_NULL = "/dev/null";
+
+ private static final byte[] OLD_MODE = encodeASCII("old mode ");
+
+ private static final byte[] NEW_MODE = encodeASCII("new mode ");
+
+ private static final byte[] DELETED_FILE_MODE = encodeASCII("deleted file mode ");
+
+ private static final byte[] NEW_FILE_MODE = encodeASCII("new file mode ");
+
+ private static final byte[] COPY_FROM = encodeASCII("copy from ");
+
+ private static final byte[] COPY_TO = encodeASCII("copy to ");
+
+ private static final byte[] RENAME_OLD = encodeASCII("rename old ");
+
+ private static final byte[] RENAME_NEW = encodeASCII("rename new ");
+
+ private static final byte[] RENAME_FROM = encodeASCII("rename from ");
+
+ private static final byte[] RENAME_TO = encodeASCII("rename to ");
+
+ private static final byte[] SIMILARITY_INDEX = encodeASCII("similarity index ");
+
+ private static final byte[] DISSIMILARITY_INDEX = encodeASCII("dissimilarity index ");
+
+ private static final byte[] INDEX = encodeASCII("index ");
+
+ static final byte[] OLD_NAME = encodeASCII("--- ");
+
+ static final byte[] NEW_NAME = encodeASCII("+++ ");
+
+ static final byte[] HUNK_HDR = encodeASCII("@@ -");
+
+ /** General type of change a single file-level patch describes. */
+ public static enum ChangeType {
+ /** Add a new file to the project */
+ ADD,
+
+ /** Modify an existing file in the project (content and/or mode) */
+ MODIFY,
+
+ /** Delete an existing file from the project */
+ DELETE,
+
+ /** Rename an existing file to a new location */
+ RENAME,
+
+ /** Copy an existing file to a new location, keeping the original */
+ COPY;
+ }
+
+ /** Buffer holding the patch data for this file. */
+ final byte[] buf;
+
+ /** Offset within {@link #buf} to the "diff ..." line. */
+ final int startOffset;
+
+ /** Position 1 past the end of this file within {@link #buf}. */
+ int endOffset;
+
+ /** File name of the old (pre-image). */
+ private String oldName;
+
+ /** File name of the new (post-image). */
+ private String newName;
+
+ /** Old mode of the file, if described by the patch, else null. */
+ private FileMode oldMode;
+
+ /** New mode of the file, if described by the patch, else null. */
+ private FileMode newMode;
+
+ /** General type of change indicated by the patch. */
+ private ChangeType changeType;
+
+ /** Similarity score if {@link #changeType} is a copy or rename. */
+ private int score;
+
+ /** ObjectId listed on the index line for the old (pre-image) */
+ private AbbreviatedObjectId oldId;
+
+ /** ObjectId listed on the index line for the new (post-image) */
+ private AbbreviatedObjectId newId;
+
+ FileHeader(final byte[] b, final int offset) {
+ buf = b;
+ startOffset = offset;
+ changeType = ChangeType.MODIFY; // unless otherwise designated
+ }
+
+ /**
+ * Get the old name associated with this file.
+ * <p>
+ * The meaning of the old name can differ depending on the semantic meaning
+ * of this patch:
+ * <ul>
+ * <li><i>file add</i>: always <code>/dev/null</code></li>
+ * <li><i>file modify</i>: always {@link #getNewName()}</li>
+ * <li><i>file delete</i>: always the file being deleted</li>
+ * <li><i>file copy</i>: source file the copy originates from</li>
+ * <li><i>file rename</i>: source file the rename originates from</li>
+ * </ul>
+ *
+ * @return old name for this file.
+ */
+ public String getOldName() {
+ return oldName;
+ }
+
+ /**
+ * Get the new name associated with this file.
+ * <p>
+ * The meaning of the new name can differ depending on the semantic meaning
+ * of this patch:
+ * <ul>
+ * <li><i>file add</i>: always the file being created</li>
+ * <li><i>file modify</i>: always {@link #getOldName()}</li>
+ * <li><i>file delete</i>: always <code>/dev/null</code></li>
+ * <li><i>file copy</i>: destination file the copy ends up at</li>
+ * <li><i>file rename</i>: destination file the rename ends up at/li>
+ * </ul>
+ *
+ * @return new name for this file.
+ */
+ public String getNewName() {
+ return newName;
+ }
+
+ /** @return the old file mode, if described in the patch */
+ public FileMode getOldMode() {
+ return oldMode;
+ }
+
+ /** @return the new file mode, if described in the patch */
+ public FileMode getNewMode() {
+ return newMode;
+ }
+
+ /** @return the type of change this patch makes on {@link #getNewName()} */
+ public ChangeType getChangeType() {
+ return changeType;
+ }
+
+ /**
+ * @return similarity score between {@link #getOldName()} and
+ * {@link #getNewName()} if {@link #getChangeType()} is
+ * {@link ChangeType#COPY} or {@link ChangeType#RENAME}.
+ */
+ public int getScore() {
+ return score;
+ }
+
+ /**
+ * Get the old object id from the <code>index</code>.
+ *
+ * @return the object id; null if there is no index line
+ */
+ public AbbreviatedObjectId getOldId() {
+ return oldId;
+ }
+
+ /**
+ * Get the new object id from the <code>index</code>.
+ *
+ * @return the object id; null if there is no index line
+ */
+ public AbbreviatedObjectId getNewId() {
+ return newId;
+ }
+
+ /**
+ * Parse a "diff --git" or "diff --cc" line.
+ *
+ * @param ptr
+ * first character after the "diff --git " or "diff --cc " part.
+ * @return first character after the LF at the end of the line; -1 on error.
+ */
+ int parseGitFileName(int ptr) {
+ final int eol = nextLF(buf, ptr);
+ final int bol = ptr;
+ if (eol >= buf.length) {
+ return -1;
+ }
+
+ // buffer[ptr..eol] looks like "a/foo b/foo\n". After the first
+ // A regex to match this is "^[^/]+/(.*?) [^/+]+/\1\n$". There
+ // is only one way to split the line such that text to the left
+ // of the space matches the text to the right, excluding the part
+ // before the first slash.
+ //
+
+ final int aStart = nextLF(buf, ptr, '/');
+ if (aStart >= eol)
+ return eol;
+
+ while (ptr < eol) {
+ final int sp = nextLF(buf, ptr, ' ');
+ if (sp >= eol) {
+ // We can't split the header, it isn't valid.
+ // This may be OK if this is a rename patch.
+ //
+ return eol;
+ }
+ final int bStart = nextLF(buf, sp, '/');
+ if (bStart >= eol)
+ return eol;
+
+ // If buffer[aStart..sp - 1] = buffer[bStart..eol - 1]
+ // we have a valid split.
+ //
+ if (eq(aStart, sp - 1, bStart, eol - 1)) {
+ if (buf[bol] == '"') {
+ // We're a double quoted name. The region better end
+ // in a double quote too, and we need to decode the
+ // characters before reading the name.
+ //
+ if (buf[sp - 2] != '"') {
+ return eol;
+ }
+ oldName = QuotedString.GIT_PATH.dequote(buf, bol, sp - 1);
+ oldName = p1(oldName);
+ } else {
+ oldName = decode(Constants.CHARSET, buf, aStart, sp - 1);
+ }
+ newName = oldName;
+ return eol;
+ }
+
+ // This split wasn't correct. Move past the space and try
+ // another split as the space must be part of the file name.
+ //
+ ptr = sp;
+ }
+
+ return eol;
+ }
+
+ int parseGitHeaders(int ptr) {
+ final int sz = buf.length;
+ while (ptr < sz) {
+ final int eol = nextLF(buf, ptr);
+ if (match(buf, ptr, HUNK_HDR) >= 0) {
+ // First hunk header; break out and parse them later.
+ break;
+
+ } else if (match(buf, ptr, OLD_NAME) >= 0) {
+ oldName = p1(parseName(oldName, ptr + OLD_NAME.length, eol));
+ if (oldName == DEV_NULL)
+ changeType = ChangeType.ADD;
+
+ } else if (match(buf, ptr, NEW_NAME) >= 0) {
+ newName = p1(parseName(newName, ptr + NEW_NAME.length, eol));
+ if (newName == DEV_NULL)
+ changeType = ChangeType.DELETE;
+
+ } else if (match(buf, ptr, OLD_MODE) >= 0) {
+ oldMode = parseFileMode(ptr + OLD_MODE.length, eol);
+
+ } else if (match(buf, ptr, NEW_MODE) >= 0) {
+ newMode = parseFileMode(ptr + NEW_MODE.length, eol);
+
+ } else if (match(buf, ptr, DELETED_FILE_MODE) >= 0) {
+ oldMode = parseFileMode(ptr + DELETED_FILE_MODE.length, eol);
+ changeType = ChangeType.DELETE;
+
+ } else if (match(buf, ptr, NEW_FILE_MODE) >= 0) {
+ newMode = parseFileMode(ptr + NEW_FILE_MODE.length, eol);
+ changeType = ChangeType.ADD;
+
+ } else if (match(buf, ptr, COPY_FROM) >= 0) {
+ oldName = parseName(oldName, ptr + COPY_FROM.length, eol);
+ changeType = ChangeType.COPY;
+
+ } else if (match(buf, ptr, COPY_TO) >= 0) {
+ newName = parseName(newName, ptr + COPY_TO.length, eol);
+ changeType = ChangeType.COPY;
+
+ } else if (match(buf, ptr, RENAME_OLD) >= 0) {
+ oldName = parseName(oldName, ptr + RENAME_OLD.length, eol);
+ changeType = ChangeType.RENAME;
+
+ } else if (match(buf, ptr, RENAME_NEW) >= 0) {
+ newName = parseName(newName, ptr + RENAME_NEW.length, eol);
+ changeType = ChangeType.RENAME;
+
+ } else if (match(buf, ptr, RENAME_FROM) >= 0) {
+ oldName = parseName(oldName, ptr + RENAME_FROM.length, eol);
+ changeType = ChangeType.RENAME;
+
+ } else if (match(buf, ptr, RENAME_TO) >= 0) {
+ newName = parseName(newName, ptr + RENAME_TO.length, eol);
+ changeType = ChangeType.RENAME;
+
+ } else if (match(buf, ptr, SIMILARITY_INDEX) >= 0) {
+ score = parseBase10(buf, ptr + SIMILARITY_INDEX.length, null);
+
+ } else if (match(buf, ptr, DISSIMILARITY_INDEX) >= 0) {
+ score = parseBase10(buf, ptr + DISSIMILARITY_INDEX.length, null);
+
+ } else if (match(buf, ptr, INDEX) >= 0) {
+ parseIndexLine(ptr + INDEX.length, eol);
+
+ } else {
+ // Probably an empty patch (stat dirty).
+ break;
+ }
+
+ ptr = eol;
+ }
+ return ptr;
+ }
+
+ private String parseName(final String expect, int ptr, final int end) {
+ if (ptr == end)
+ return expect;
+
+ String r;
+ if (buf[ptr] == '"') {
+ // New style GNU diff format
+ //
+ r = QuotedString.GIT_PATH.dequote(buf, ptr, end - 1);
+ } else {
+ // Older style GNU diff format, an optional tab ends the name.
+ //
+ int tab = end;
+ while (ptr < tab && buf[tab - 1] != '\t')
+ tab--;
+ if (ptr == tab)
+ tab = end;
+ r = decode(Constants.CHARSET, buf, ptr, tab - 1);
+ }
+
+ if (r.equals(DEV_NULL))
+ r = DEV_NULL;
+ return r;
+ }
+
+ private static String p1(final String r) {
+ final int s = r.indexOf('/');
+ return s > 0 ? r.substring(s + 1) : r;
+ }
+
+ private FileMode parseFileMode(int ptr, final int end) {
+ int tmp = 0;
+ while (ptr < end - 1) {
+ tmp <<= 3;
+ tmp += buf[ptr++] - '0';
+ }
+ return FileMode.fromBits(tmp);
+ }
+
+ private void parseIndexLine(int ptr, final int end) {
+ // "index $asha1..$bsha1[ $mode]" where $asha1 and $bsha1
+ // can be unique abbreviations
+ //
+ final int dot2 = nextLF(buf, ptr, '.');
+ final int mode = nextLF(buf, dot2, ' ');
+
+ oldId = AbbreviatedObjectId.fromString(buf, ptr, dot2 - 1);
+ newId = AbbreviatedObjectId.fromString(buf, dot2 + 1, mode - 1);
+
+ if (mode < end)
+ newMode = oldMode = parseFileMode(mode, end);
+ }
+
+ private boolean eq(int aPtr, int aEnd, int bPtr, int bEnd) {
+ if (aEnd - aPtr != bEnd - bPtr) {
+ return false;
+ }
+ while (aPtr < aEnd) {
+ if (buf[aPtr++] != buf[bPtr++])
+ return false;
+ }
+ return true;
+ }
+}
--
1.6.1.rc2.299.gead4c
^ permalink raw reply related
* [JGIT PATCH 4/5] Define Patch to parse a sequence of patch FileHeaders
From: Shawn O. Pearce @ 2008-12-11 4:58 UTC (permalink / raw)
To: Robin Rosenberg; +Cc: git
In-Reply-To: <1228971522-28764-4-git-send-email-spearce@spearce.org>
Most patch scripts impact more than one file at a time, so we need
to support parsing multiple FileHeaders from the same input stream
and collect them into a larger entity representing the entire script.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
.../tst/org/spearce/jgit/patch/PatchTest.java | 97 +++++++++
.../patch/testParse_ConfigCaseInsensitive.patch | 67 ++++++
| 28 +++
.../src/org/spearce/jgit/patch/Patch.java | 217 ++++++++++++++++++++
4 files changed, 409 insertions(+), 0 deletions(-)
create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java
create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_ConfigCaseInsensitive.patch
create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java
new file mode 100644
index 0000000..f389d89
--- /dev/null
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.patch;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import junit.framework.TestCase;
+
+import org.spearce.jgit.lib.FileMode;
+
+public class PatchTest extends TestCase {
+ public void testEmpty() {
+ final Patch p = new Patch();
+ assertTrue(p.getFiles().isEmpty());
+ }
+
+ public void testParse_ConfigCaseInsensitive() throws IOException {
+ final Patch p = parseTestPatchFile();
+ assertEquals(2, p.getFiles().size());
+
+ final FileHeader fRepositoryConfigTest = p.getFiles().get(0);
+ final FileHeader fRepositoryConfig = p.getFiles().get(1);
+
+ assertEquals(
+ "org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java",
+ fRepositoryConfigTest.getNewName());
+
+ assertEquals(
+ "org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java",
+ fRepositoryConfig.getNewName());
+
+ assertEquals(572, fRepositoryConfigTest.startOffset);
+ assertEquals(1490, fRepositoryConfig.startOffset);
+
+ assertEquals("da7e704", fRepositoryConfigTest.getOldId().name());
+ assertEquals("34ce04a", fRepositoryConfigTest.getNewId().name());
+ assertSame(FileMode.REGULAR_FILE, fRepositoryConfigTest.getOldMode());
+ assertSame(FileMode.REGULAR_FILE, fRepositoryConfigTest.getNewMode());
+
+ assertEquals("45c2f8a", fRepositoryConfig.getOldId().name());
+ assertEquals("3291bba", fRepositoryConfig.getNewId().name());
+ assertSame(FileMode.REGULAR_FILE, fRepositoryConfig.getOldMode());
+ assertSame(FileMode.REGULAR_FILE, fRepositoryConfig.getNewMode());
+ }
+
+ private Patch parseTestPatchFile() throws IOException {
+ final String patchFile = getName() + ".patch";
+ final InputStream in = getClass().getResourceAsStream(patchFile);
+ if (in == null) {
+ fail("No " + patchFile + " test vector");
+ return null; // Never happens
+ }
+ try {
+ final Patch p = new Patch();
+ p.parse(in);
+ return p;
+ } finally {
+ in.close();
+ }
+ }
+}
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_ConfigCaseInsensitive.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_ConfigCaseInsensitive.patch
new file mode 100644
index 0000000..b30418e
--- /dev/null
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_ConfigCaseInsensitive.patch
@@ -0,0 +1,67 @@
+From ce9b593ddf2530613f6da9d7f7e4a5ff93da8b36 Mon Sep 17 00:00:00 2001
+From: Robin Rosenberg <robin.rosenberg@dewire.com>
+Date: Mon, 13 Oct 2008 00:50:59 +0200
+Subject: [PATCH] git config file is case insensitive
+
+Keys are now always compared with ignore case rules.
+
+Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
+Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
+---
+ .../org/spearce/jgit/lib/RepositoryConfigTest.java | 7 +++++++
+ .../src/org/spearce/jgit/lib/RepositoryConfig.java | 8 ++++----
+ 2 files changed, 11 insertions(+), 4 deletions(-)
+
+diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java
+index da7e704..34ce04a 100644
+--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java
++++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java
+@@ -109,4 +109,11 @@ assertTrue(Arrays.equals(values.toArray(), repositoryConfig
+ .getStringList("my", null, "somename")));
+ checkFile(cfgFile, "[my]\n\tsomename = value1\n\tsomename = value2\n");
+ }
++
++ public void test006_readCaseInsensitive() throws IOException {
++ final File path = writeTrashFile("config_001", "[Foo]\nBar\n");
++ RepositoryConfig repositoryConfig = new RepositoryConfig(null, path);
++ assertEquals(true, repositoryConfig.getBoolean("foo", null, "bar", false));
++ assertEquals("", repositoryConfig.getString("foo", null, "bar"));
++ }
+ }
+diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java
+index 45c2f8a..3291bba 100644
+--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java
++++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java
+@@ -236,9 +236,9 @@ protected boolean getBoolean(final String section, String subsection,
+ return defaultValue;
+
+ n = n.toLowerCase();
+- if (MAGIC_EMPTY_VALUE.equals(n) || "yes".equals(n) || "true".equals(n) || "1".equals(n)) {
++ if (MAGIC_EMPTY_VALUE.equals(n) || "yes".equalsIgnoreCase(n) || "true".equalsIgnoreCase(n) || "1".equals(n)) {
+ return true;
+- } else if ("no".equals(n) || "false".equals(n) || "0".equals(n)) {
++ } else if ("no".equalsIgnoreCase(n) || "false".equalsIgnoreCase(n) || "0".equalsIgnoreCase(n)) {
+ return false;
+ } else {
+ throw new IllegalArgumentException("Invalid boolean value: "
+@@ -300,7 +300,7 @@ public String getString(final String section, String subsection, final String na
+ final Set<String> result = new HashSet<String>();
+
+ for (final Entry e : entries) {
+- if (section.equals(e.base) && e.extendedBase != null)
++ if (section.equalsIgnoreCase(e.base) && e.extendedBase != null)
+ result.add(e.extendedBase);
+ }
+ if (baseConfig != null)
+@@ -954,7 +954,7 @@ private static boolean eq(final String a, final String b) {
+ return true;
+ if (a == null || b == null)
+ return false;
+- return a.equals(b);
++ return a.equalsIgnoreCase(b);
+ }
+ }
+ }
+--
+1.6.1.rc2.299.gead4c
+
--git a/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java
index 5d1454b..f57a0ff 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java
@@ -364,6 +364,34 @@ int parseGitHeaders(int ptr) {
return ptr;
}
+ int parseTraditionalHeaders(int ptr) {
+ final int sz = buf.length;
+ while (ptr < sz) {
+ final int eol = nextLF(buf, ptr);
+ if (match(buf, ptr, HUNK_HDR) >= 0) {
+ // First hunk header; break out and parse them later.
+ break;
+
+ } else if (match(buf, ptr, OLD_NAME) >= 0) {
+ oldName = p1(parseName(oldName, ptr + OLD_NAME.length, eol));
+ if (oldName == DEV_NULL)
+ changeType = ChangeType.ADD;
+
+ } else if (match(buf, ptr, NEW_NAME) >= 0) {
+ newName = p1(parseName(newName, ptr + NEW_NAME.length, eol));
+ if (newName == DEV_NULL)
+ changeType = ChangeType.DELETE;
+
+ } else {
+ // Possibly an empty patch.
+ break;
+ }
+
+ ptr = eol;
+ }
+ return ptr;
+ }
+
private String parseName(final String expect, int ptr, final int end) {
if (ptr == end)
return expect;
diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java
new file mode 100644
index 0000000..30d12a5
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.patch;
+
+import static org.spearce.jgit.lib.Constants.encodeASCII;
+import static org.spearce.jgit.patch.FileHeader.HUNK_HDR;
+import static org.spearce.jgit.patch.FileHeader.NEW_NAME;
+import static org.spearce.jgit.patch.FileHeader.OLD_NAME;
+import static org.spearce.jgit.util.RawParseUtils.match;
+import static org.spearce.jgit.util.RawParseUtils.nextLF;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.spearce.jgit.util.TemporaryBuffer;
+
+/** A parsed collection of {@link FileHeader}s from a unified diff patch file */
+public class Patch {
+ private static final byte[] DIFF_GIT = encodeASCII("diff --git ");
+
+ private static final byte[] DIFF_CC = encodeASCII("diff --cc ");
+
+ /** The files, in the order they were parsed out of the input. */
+ private final List<FileHeader> files;
+
+ /** Create an empty patch. */
+ public Patch() {
+ files = new ArrayList<FileHeader>();
+ }
+
+ /**
+ * Add a single file to this patch.
+ * <p>
+ * Typically files should be added by parsing the text through one of this
+ * class's parse methods.
+ *
+ * @param fh
+ * the header of the file.
+ */
+ public void addFile(final FileHeader fh) {
+ files.add(fh);
+ }
+
+ /** @return list of files described in the patch, in occurrence order. */
+ public List<FileHeader> getFiles() {
+ return files;
+ }
+
+ /**
+ * Parse a patch received from an InputStream.
+ * <p>
+ * Multiple parse calls on the same instance will concatenate the patch
+ * data, but each parse input must start with a valid file header (don't
+ * split a single file across parse calls).
+ *
+ * @param is
+ * the stream to read the patch data from. The stream is read
+ * until EOF is reached.
+ * @throws IOException
+ * there was an error reading from the input stream.
+ */
+ public void parse(final InputStream is) throws IOException {
+ final byte[] buf = readFully(is);
+ parse(buf, 0, buf.length);
+ }
+
+ private static byte[] readFully(final InputStream is) throws IOException {
+ final TemporaryBuffer b = new TemporaryBuffer();
+ b.copy(is);
+ final byte[] buf = b.toByteArray();
+ return buf;
+ }
+
+ /**
+ * Parse a patch stored in a byte[].
+ * <p>
+ * Multiple parse calls on the same instance will concatenate the patch
+ * data, but each parse input must start with a valid file header (don't
+ * split a single file across parse calls).
+ *
+ * @param buf
+ * the buffer to parse.
+ * @param ptr
+ * starting position to parse from.
+ * @param end
+ * 1 past the last position to end parsing. The total length to
+ * be parsed is <code>end - ptr</code>.
+ */
+ public void parse(final byte[] buf, int ptr, final int end) {
+ while (ptr < end)
+ ptr = parseFile(buf, ptr);
+ }
+
+ private int parseFile(final byte[] buf, int c) {
+ final int sz = buf.length;
+ while (c < sz) {
+ // Valid git style patch?
+ //
+ if (match(buf, c, DIFF_GIT) >= 0)
+ return parseDiffGit(buf, c);
+ if (match(buf, c, DIFF_CC) >= 0)
+ return parseDiffCC(buf, c);
+
+ // Junk between files? Leading junk? Traditional
+ // (non-git generated) patch?
+ //
+ final int n = nextLF(buf, c);
+ if (n >= sz) {
+ // Patches cannot be only one line long. This must be
+ // trailing junk that we should ignore.
+ //
+ return sz;
+ }
+
+ if (n - c < 6) {
+ // A valid header must be at least 6 bytes on the
+ // first line, e.g. "--- a/b\n".
+ //
+ c = n;
+ continue;
+ }
+
+ if (match(buf, c, OLD_NAME) >= 0 && match(buf, n, NEW_NAME) >= 0) {
+ // Probably a traditional patch. Ensure we have at least
+ // a "@@ -0,0" smelling line next. We only check the "@@ -".
+ //
+ final int f = nextLF(buf, n);
+ if (f >= sz)
+ return sz;
+ if (match(buf, f, HUNK_HDR) >= 0)
+ return parseTraditionalPatch(buf, c);
+ }
+
+ c = n;
+ }
+ return c;
+ }
+
+ private int parseDiffGit(final byte[] buf, final int startOffset) {
+ final FileHeader fh = new FileHeader(buf, startOffset);
+ int ptr = fh.parseGitFileName(startOffset + DIFF_GIT.length);
+ if (ptr < 0)
+ return skipFile(buf, startOffset);
+
+ ptr = fh.parseGitHeaders(ptr);
+ // TODO parse hunks
+ fh.endOffset = ptr;
+ addFile(fh);
+ return ptr;
+ }
+
+ private int parseDiffCC(final byte[] buf, final int startOffset) {
+ final FileHeader fh = new FileHeader(buf, startOffset);
+ int ptr = fh.parseGitFileName(startOffset + DIFF_CC.length);
+ if (ptr < 0)
+ return skipFile(buf, startOffset);
+
+ // TODO Support parsing diff --cc headers
+ // TODO parse diff --cc hunks
+ fh.endOffset = ptr;
+ addFile(fh);
+ return ptr;
+ }
+
+ private int parseTraditionalPatch(final byte[] buf, final int startOffset) {
+ final FileHeader fh = new FileHeader(buf, startOffset);
+ int ptr = fh.parseTraditionalHeaders(startOffset);
+ // TODO parse hunks
+ fh.endOffset = ptr;
+ addFile(fh);
+ return ptr;
+ }
+
+ private static int skipFile(final byte[] buf, int ptr) {
+ ptr = nextLF(buf, ptr);
+ if (match(buf, ptr, OLD_NAME) >= 0)
+ ptr = nextLF(buf, ptr);
+ return ptr;
+ }
+}
--
1.6.1.rc2.299.gead4c
^ permalink raw reply related
* [JGIT PATCH 5/5] Add HunkHeader to represent a single hunk of a file within a patch
From: Shawn O. Pearce @ 2008-12-11 4:58 UTC (permalink / raw)
To: Robin Rosenberg; +Cc: git
In-Reply-To: <1228971522-28764-5-git-send-email-spearce@spearce.org>
The hunk header parses the lines of the hunk, counting up the body
to ensure it matches with the header line. We store the hunks on
on the FileHeader, providing public access to them after a patch
has been parsed.
If the line counts in the body differ from the header we don't make
that an immediate error. This permits a future implementation of
something like "git apply --recount", where the hunk headers can be
recomputed based on the actual hunk contents after a human has done
manual editing. We may also need to parse a corrupt patch and show
it in a visual tool for a human to repair, so aborting with any sort
of exception isn't useful.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
| 6 +-
.../tst/org/spearce/jgit/patch/PatchTest.java | 60 ++++++-
| 22 +++
| 185 ++++++++++++++++++++
.../src/org/spearce/jgit/patch/Patch.java | 54 ++++++-
5 files changed, 323 insertions(+), 4 deletions(-)
create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java
--git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java
index 1d87bc0..4094a5c 100644
--- a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java
@@ -44,7 +44,10 @@
public class FileHeaderTest extends TestCase {
public void testParseGitFileName_Empty() {
- assertEquals(-1, data("").parseGitFileName(0));
+ final FileHeader fh = data("");
+ assertEquals(-1, fh.parseGitFileName(0));
+ assertNotNull(fh.getHunks());
+ assertTrue(fh.getHunks().isEmpty());
}
public void testParseGitFileName_NoLF() {
@@ -233,6 +236,7 @@ public void testParseRename100_OldStyle() {
assertEquals(100, fh.getScore());
}
+
public void testParseCopy100() {
final FileHeader fh = data("diff --git a/a b/ c/\\303\\205ngstr\\303\\266m\n"
+ "similarity index 100%\n"
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java
index f389d89..8ddaadc 100644
--- a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java
@@ -72,11 +72,69 @@ assertEquals(
assertEquals("34ce04a", fRepositoryConfigTest.getNewId().name());
assertSame(FileMode.REGULAR_FILE, fRepositoryConfigTest.getOldMode());
assertSame(FileMode.REGULAR_FILE, fRepositoryConfigTest.getNewMode());
+ assertEquals(1, fRepositoryConfigTest.getHunks().size());
+ {
+ final HunkHeader h = fRepositoryConfigTest.getHunks().get(0);
+ assertEquals(921, h.startOffset);
+ assertEquals(109, h.getOldStartLine());
+ assertEquals(4, h.getOldLineCount());
+ assertEquals(109, h.getNewStartLine());
+ assertEquals(11, h.getNewLineCount());
+
+ assertEquals(4, h.getLinesContext());
+ assertEquals(7, h.getLinesAdded());
+ assertEquals(0, h.getLinesDeleted());
+
+ assertEquals(1490, h.endOffset);
+ }
assertEquals("45c2f8a", fRepositoryConfig.getOldId().name());
assertEquals("3291bba", fRepositoryConfig.getNewId().name());
assertSame(FileMode.REGULAR_FILE, fRepositoryConfig.getOldMode());
assertSame(FileMode.REGULAR_FILE, fRepositoryConfig.getNewMode());
+ assertEquals(3, fRepositoryConfig.getHunks().size());
+ {
+ final HunkHeader h = fRepositoryConfig.getHunks().get(0);
+ assertEquals(1803, h.startOffset);
+ assertEquals(236, h.getOldStartLine());
+ assertEquals(9, h.getOldLineCount());
+ assertEquals(236, h.getNewStartLine());
+ assertEquals(9, h.getNewLineCount());
+
+ assertEquals(7, h.getLinesContext());
+ assertEquals(2, h.getLinesAdded());
+ assertEquals(2, h.getLinesDeleted());
+
+ assertEquals(2434, h.endOffset);
+ }
+ {
+ final HunkHeader h = fRepositoryConfig.getHunks().get(1);
+ assertEquals(2434, h.startOffset);
+ assertEquals(300, h.getOldStartLine());
+ assertEquals(7, h.getOldLineCount());
+ assertEquals(300, h.getNewStartLine());
+ assertEquals(7, h.getNewLineCount());
+
+ assertEquals(6, h.getLinesContext());
+ assertEquals(1, h.getLinesAdded());
+ assertEquals(1, h.getLinesDeleted());
+
+ assertEquals(2816, h.endOffset);
+ }
+ {
+ final HunkHeader h = fRepositoryConfig.getHunks().get(2);
+ assertEquals(2816, h.startOffset);
+ assertEquals(954, h.getOldStartLine());
+ assertEquals(7, h.getOldLineCount());
+ assertEquals(954, h.getNewStartLine());
+ assertEquals(7, h.getNewLineCount());
+
+ assertEquals(6, h.getLinesContext());
+ assertEquals(1, h.getLinesAdded());
+ assertEquals(1, h.getLinesDeleted());
+
+ assertEquals(3035, h.endOffset);
+ }
}
private Patch parseTestPatchFile() throws IOException {
@@ -84,7 +142,7 @@ private Patch parseTestPatchFile() throws IOException {
final InputStream in = getClass().getResourceAsStream(patchFile);
if (in == null) {
fail("No " + patchFile + " test vector");
- return null; // Never happens
+ return null; // Never happens
}
try {
final Patch p = new Patch();
--git a/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java
index f57a0ff..b49cb7b 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java
@@ -43,6 +43,10 @@
import static org.spearce.jgit.util.RawParseUtils.nextLF;
import static org.spearce.jgit.util.RawParseUtils.parseBase10;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
import org.spearce.jgit.lib.AbbreviatedObjectId;
import org.spearce.jgit.lib.Constants;
import org.spearce.jgit.lib.FileMode;
@@ -136,6 +140,9 @@
/** ObjectId listed on the index line for the new (post-image) */
private AbbreviatedObjectId newId;
+ /** The hunks of this file */
+ private List<HunkHeader> hunks;
+
FileHeader(final byte[] b, final int offset) {
buf = b;
startOffset = offset;
@@ -222,6 +229,21 @@ public AbbreviatedObjectId getNewId() {
return newId;
}
+ /** @return hunks altering this file; in order of appearance in patch */
+ public List<HunkHeader> getHunks() {
+ if (hunks == null)
+ return Collections.emptyList();
+ return hunks;
+ }
+
+ void addHunk(final HunkHeader h) {
+ if (h.getFileHeader() != this)
+ throw new IllegalArgumentException("Hunk belongs to another file");
+ if (hunks == null)
+ hunks = new ArrayList<HunkHeader>();
+ hunks.add(h);
+ }
+
/**
* Parse a "diff --git" or "diff --cc" line.
*
--git a/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java
new file mode 100644
index 0000000..fc606c3
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spearce.jgit.patch;
+
+import static org.spearce.jgit.util.RawParseUtils.match;
+import static org.spearce.jgit.util.RawParseUtils.nextLF;
+import static org.spearce.jgit.util.RawParseUtils.parseBase10;
+
+import org.spearce.jgit.util.MutableInteger;
+
+/** Hunk header describing the layout of a single block of lines */
+public class HunkHeader {
+ private 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;
+
+ /** First line number in the pre-image file where the hunk starts */
+ int oldStartLine;
+
+ /** Total number of pre-image lines this hunk covers (context + deleted) */
+ int oldLineCount;
+
+ /** 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;
+
+ /** Number of lines removed by this hunk */
+ int nDeleted;
+
+ /** Number of lines added by this hunk */
+ int nAdded;
+
+ HunkHeader(final FileHeader fh, final int offset) {
+ file = fh;
+ startOffset = offset;
+ }
+
+ /** @return header for the file this hunk applies to */
+ public FileHeader getFileHeader() {
+ return file;
+ }
+
+ /** @return first line number in the pre-image file where the hunk starts */
+ public int getOldStartLine() {
+ return oldStartLine;
+ }
+
+ /** @return total number of pre-image lines this hunk covers */
+ public int getOldLineCount() {
+ return oldLineCount;
+ }
+
+ /** @return first line number in the post-image file where the hunk starts */
+ public int getNewStartLine() {
+ return newStartLine;
+ }
+
+ /** @return Total number of post-image lines this hunk covers */
+ public int getNewLineCount() {
+ return newLineCount;
+ }
+
+ /** @return total number of lines of context appearing in this hunk */
+ public int getLinesContext() {
+ return nContext;
+ }
+
+ /** @return number of lines removed by this hunk */
+ public int getLinesDeleted() {
+ return nDeleted;
+ }
+
+ /** @return number of lines added by this hunk */
+ public int getLinesAdded() {
+ return nAdded;
+ }
+
+ void parseHeader() {
+ // Parse "@@ -236,9 +236,9 @@ protected boolean"
+ //
+ final byte[] buf = file.buf;
+ final MutableInteger ptr = new MutableInteger();
+ ptr.value = nextLF(buf, startOffset, ' ');
+ oldStartLine = -parseBase10(buf, ptr.value, ptr);
+ oldLineCount = parseBase10(buf, ptr.value + 1, ptr);
+
+ newStartLine = parseBase10(buf, ptr.value + 1, ptr);
+ newLineCount = parseBase10(buf, ptr.value + 1, ptr);
+ }
+
+ int parseBody() {
+ final byte[] buf = file.buf;
+ final int sz = buf.length;
+ int c = nextLF(buf, startOffset), last = c;
+
+ nDeleted = 0;
+ nAdded = 0;
+
+ SCAN: for (; c < sz; last = c, c = nextLF(buf, c)) {
+ switch (buf[c]) {
+ case ' ':
+ case '\n':
+ nContext++;
+ continue;
+
+ case '-':
+ nDeleted++;
+ continue;
+
+ case '+':
+ nAdded++;
+ continue;
+
+ case '\\': // Matches "\ No newline at end of file"
+ continue;
+
+ default:
+ break SCAN;
+ }
+ }
+
+ if (last < sz && nContext + nDeleted - 1 == oldLineCount
+ && nContext + 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.
+ //
+ nDeleted--;
+ return last;
+ }
+
+ if (nContext + nDeleted != oldLineCount
+ || nContext + nAdded != newLineCount) {
+ // TODO report on truncated hunk
+ }
+
+ return c;
+ }
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java
index 30d12a5..165058d 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java
@@ -57,6 +57,8 @@
private static final byte[] DIFF_CC = encodeASCII("diff --cc ");
+ static final byte[] SIG_FOOTER = encodeASCII("-- \n");
+
/** The files, in the order they were parsed out of the input. */
private final List<FileHeader> files;
@@ -131,6 +133,17 @@ public void parse(final byte[] buf, int ptr, final int end) {
private int parseFile(final byte[] buf, int c) {
final int sz = buf.length;
while (c < sz) {
+ if (match(buf, c, HUNK_HDR) >= 0) {
+ // If we find a disconnected hunk header we might
+ // have missed a file header previously. The hunk
+ // isn't valid without knowing where it comes from.
+ //
+
+ // TODO handle a disconnected hunk fragment
+ c = nextLF(buf, c);
+ continue;
+ }
+
// Valid git style patch?
//
if (match(buf, c, DIFF_GIT) >= 0)
@@ -180,7 +193,7 @@ private int parseDiffGit(final byte[] buf, final int startOffset) {
return skipFile(buf, startOffset);
ptr = fh.parseGitHeaders(ptr);
- // TODO parse hunks
+ ptr = parseHunks(fh, buf, ptr);
fh.endOffset = ptr;
addFile(fh);
return ptr;
@@ -202,7 +215,7 @@ private int parseDiffCC(final byte[] buf, final int startOffset) {
private int parseTraditionalPatch(final byte[] buf, final int startOffset) {
final FileHeader fh = new FileHeader(buf, startOffset);
int ptr = fh.parseTraditionalHeaders(startOffset);
- // TODO parse hunks
+ ptr = parseHunks(fh, buf, ptr);
fh.endOffset = ptr;
addFile(fh);
return ptr;
@@ -214,4 +227,41 @@ private static int skipFile(final byte[] buf, int ptr) {
ptr = nextLF(buf, ptr);
return ptr;
}
+
+ private int parseHunks(final FileHeader fh, final byte[] buf, int c) {
+ final int sz = buf.length;
+ while (c < sz) {
+ // If we see a file header at this point, we have all of the
+ // hunks for our current file. We should stop and report back
+ // with this position so it can be parsed again later.
+ //
+ if (match(buf, c, DIFF_GIT) >= 0)
+ return c;
+ if (match(buf, c, DIFF_CC) >= 0)
+ return c;
+ if (match(buf, c, OLD_NAME) >= 0)
+ return c;
+ if (match(buf, c, NEW_NAME) >= 0)
+ return c;
+
+ if (match(buf, c, HUNK_HDR) >= 0) {
+ final HunkHeader h = new HunkHeader(fh, c);
+ h.parseHeader();
+ c = h.parseBody();
+ h.endOffset = c;
+ fh.addHunk(h);
+ if (c < sz && buf[c] != '@' && buf[c] != 'd'
+ && match(buf, c, SIG_FOOTER) < 0) {
+ // TODO report on noise between hunks, might be an error
+ }
+ continue;
+ }
+
+ // Skip this line and move to the next. Its probably garbage
+ // after the last hunk of a file.
+ //
+ c = nextLF(buf, c);
+ }
+ return c;
+ }
}
--
1.6.1.rc2.299.gead4c
^ permalink raw reply related
* Re: git fsck segmentation fault
From: Martin Koegler @ 2008-12-11 6:27 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Nicolas Pitre, Simon Hausmann, Git Mailing List
In-Reply-To: <7vljunwidr.fsf@gitster.siamese.dyndns.org>
On Wed, Dec 10, 2008 at 06:33:20PM -0800, Junio C Hamano wrote:
> mkoegler@auto.tuwien.ac.at (Martin Koegler) writes:
> A similar change would be needed for other callers of fsck_walk(), no?
> There seem to be one in builtin-unpack-objects.c (check_object calls
> fsck_walk as itself as the callback).
buitin-unpack-objects.c is different. First, its intended for the
small case [default unpack_limit is 100; it keeps the unpacked content
of trees/commits in memory], which will not overflow the
stack. Second, it may only write an object after all of its connected
objects have been written out. So it would need a totally different
logic.
> Another caller is in index-pack.c (sha1_object() calls fsck_walk with
> mark_link as the callback), but I do not think it would recurse for the
> depth of the history, so we are safe there.
mark_link only sets a flag on the direct connected objects, so yes, it
needs no change.
> I initially expected that the fix would be to introduce this "userspace
> work queue" (i.e. your objectstack) to be maintained on the
> fsck.c:fsck_walk() side (perhaps as an extra parameter to an actual queue
> for reentrancy), not by making the callee not to recurse, though.
fsck_walk has been designed to call a function on all directly
connected objected. There are callers, which expected this behaviour
(eg. index-pack, mark_used in fsck).
mfg Martin Kögler
^ permalink raw reply
* Re: git fsck segmentation fault
From: Junio C Hamano @ 2008-12-11 6:42 UTC (permalink / raw)
To: Martin Koegler; +Cc: Nicolas Pitre, Simon Hausmann, Git Mailing List
In-Reply-To: <20081211062753.GA17683@auto.tuwien.ac.at>
mkoegler@auto.tuwien.ac.at (Martin Koegler) writes:
> fsck_walk has been designed to call a function on all directly
> connected objected. There are callers, which expected this behaviour
> (eg. index-pack, mark_used in fsck).
Yes, that is where my "initially expected" comes from. I was not
complaining or suggesting the behaviour to change.
I was fooled by the word *walk* in the name, which implies an
implementation of walking connectivity fully, with or without an ability
for the callback to tell the machinery when to or not to dig deeper. It
wouldn't have been confusing if it were named "fsck_step()", which is what
the function is about: performing a single step of digging deeper.
^ permalink raw reply
* How track evolution of *file* across many branches?
From: chris @ 2008-12-11 7:13 UTC (permalink / raw)
To: git
How track a file across multiple branches.
Specifically...
1. How find all branches a file appears in?
2. How find changes in each branch for that one file?
Chris
^ permalink raw reply
* Re: epic fsck SIGSEGV!
From: Junio C Hamano @ 2008-12-11 7:33 UTC (permalink / raw)
To: Linus Torvalds
Cc: Nicolas Pitre, R. Tyler Ballance, Johannes Sixt, Git Mailing List
In-Reply-To: <alpine.LFD.2.00.0812101930590.3340@localhost.localdomain>
Linus Torvalds <torvalds@linux-foundation.org> writes:
> I dunno. I like this patch better. It's a bit larger. I think it's a bit
> more clearly separated (ie a "mark_object_reachable()" _literally_ just
> puts the object on a list, and the whole traversal is a whole separate
> phase), but I guess it's a matter of taste.
... which happens to match mine in this case ;-)
I'll consider this signed-off and do the usual forging (for people new on
the list, Cf. http://article.gmane.org/gmane.comp.version-control.git/19031).
^ permalink raw reply
* Re: How track evolution of *file* across many branches?
From: Alex Riesen @ 2008-12-11 7:34 UTC (permalink / raw)
To: chris; +Cc: git
In-Reply-To: <20081211071354.GA22730@seberino.org>
2008/12/11 <chris@seberino.org>:
> How track a file across multiple branches.
>
> Specifically...
>
> 1. How find all branches a file appears in?
>
> 2. How find changes in each branch for that one file?
>
gitk --all -- file1
^ permalink raw reply
* Re: epic fsck SIGSEGV!
From: Junio C Hamano @ 2008-12-11 7:53 UTC (permalink / raw)
To: Linus Torvalds
Cc: Nicolas Pitre, R. Tyler Ballance, Johannes Sixt, Git Mailing List
In-Reply-To: <alpine.LFD.2.00.0812101930590.3340@localhost.localdomain>
Linus Torvalds <torvalds@linux-foundation.org> writes:
> It has gotten no real testing. Caveat emptor. And I didn't even bother to
> check that it can run with less stack or that it makes any other
> difference.
A quick "git fsck --full" in a copy of git.git (eh, "not-so-quick" on a
not-so-quick machine, obviously) shows that the patch does reduce minor
faults significantly.
(without patch)
83.03user 0.60system 1:23.62elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+158275minor)pagefaults 0swaps
(with object_array patch)
82.88user 0.40system 1:23.28elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+95397minor)pagefaults 0swaps
^ permalink raw reply
* Re: [PATCH] modify/delete conflict resolution overwrites untracked file
From: Clemens Buchacher @ 2008-12-11 8:07 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Johannes Schindelin, Alex Riesen
In-Reply-To: <7voczjy55g.fsf@gitster.siamese.dyndns.org>
On Wed, Dec 10, 2008 at 03:36:11PM -0800, Junio C Hamano wrote:
> Clemens Buchacher <drizzd@aon.at> writes:
> >
> > "merge -s resolve" fails with
> >
> > Trying really trivial in-index merge...
> > error: Merge requires file-level merging
> > Nope.
> > Trying simple merge.
> > Simple merge failed, trying Automatic merge.
> > ERROR: c1.c: Not handling case ae9304576a6ec3419b231b2b9c8e33a06f97f9fb ->
> > -> 8173b675dc61bb578b411c769c9fb654625a7c4e
> > fatal: merge program failed
> > Automatic merge failed; fix conflicts and then commit the result.
> >
> > and therefore passes the test.
>
> Are you saying that:
>
> (1) the step should result in conflict and the merge should fail, but it
> should not clobber c1.c nevertheless; and
>
> (2) resolve fails to merge (as expected), and it does not clobber c1.c
> (as expected); therefore it passes the test.
The latter.
> If so, then you now established that it is a bug in merge-recursive,
> right [implementors of recursive-in-C CC'ed]?
Correct.
> Or are you saying that the step should not fail to begin with?
No. IMO, merge should fail and abort. That is, it should not modify the
working tree at all and tell the user that an untracked file is in the way.
The tests check that merge returns an error code and c1.c is not modified.
Test number 5 fails, unless the merge strategy resolve is used. While this
indicates a bug in the recursive strategy, I am not satisfied with the error
output of the resolve strategy either. It should output
"error: Untracked working tree file '...' would be overwritten by merge."
just like test number 2 does.
^ permalink raw reply
* Re: help needed: Splitting a git repository after subversion migration
From: Björn Steinbrink @ 2008-12-11 8:10 UTC (permalink / raw)
To: Thomas Jarosch; +Cc: Michael J Gruber, git
In-Reply-To: <200812101733.36221.thomas.jarosch@intra2net.com>
On 2008.12.10 17:33:28 +0100, Thomas Jarosch wrote:
> On Monday, 8. December 2008 18:34:20 Thomas Jarosch wrote:
> > 1. When I run "git rev-list --all --objects", I can see file names that
> > look like "SVN-branchname/directory/filename". Is it normal that "git svn"
> > creates a directory with the name of the branch and puts files below it?
>
> Ok, this seems to be a PEBKAC: In the history of the subversion repository,
> f.e. I once copied the "branches" root folder to tags/xyz. One revision later
> I noticed this and retagged the correct branch. git-svn imports all branches
> from the first tag, which is the correct thing to do :o)
>
> Now I'll manually check the history of the tags/ and branches/ folder
> for more funny tags and write down the revision. If I understood
> the git-svn man page correctly, I should be able to specifiy
> revision ranges it's going to import. I'll try to skip the broken tags.
As long as the breakage only involves branches/tags that are completely
useless, it's probably a lot easier to just delete them afterwards.
And if you accidently added changes to a tag, after it was created, it's
also easier to manually tag to right version in git, and just forgetting
about the additional commit.
And for a bunch of other cases, rebase -i/filter-branch are probably
also better options ;-)
Skipping revisions in a git-svn import sounds rather annoying and
error-prone.
Björn
^ permalink raw reply
* Re: [PATCH] modify/delete conflict resolution overwrites untracked file
From: Junio C Hamano @ 2008-12-11 8:13 UTC (permalink / raw)
To: Clemens Buchacher; +Cc: git, Johannes Schindelin, Alex Riesen
In-Reply-To: <20081211080752.GA26709@localhost>
Clemens Buchacher <drizzd@aon.at> writes:
> On Wed, Dec 10, 2008 at 03:36:11PM -0800, Junio C Hamano wrote:
>> Clemens Buchacher <drizzd@aon.at> writes:
>> >
>> > "merge -s resolve" fails with
>> >
>> > Trying really trivial in-index merge...
>> > error: Merge requires file-level merging
>> > Nope.
>> > Trying simple merge.
>> > Simple merge failed, trying Automatic merge.
>> > ERROR: c1.c: Not handling case ae9304576a6ec3419b231b2b9c8e33a06f97f9fb ->
>> > -> 8173b675dc61bb578b411c769c9fb654625a7c4e
>> > fatal: merge program failed
>> > Automatic merge failed; fix conflicts and then commit the result.
>> >
>> > and therefore passes the test.
>>
>> Are you saying that:
>>
>> (1) the step should result in conflict and the merge should fail, but it
>> should not clobber c1.c nevertheless; and
>>
>> (2) resolve fails to merge (as expected), and it does not clobber c1.c
>> (as expected); therefore it passes the test.
>
> The latter.
I said "and" at the end of (1) so I do not understand your answer, but I
take it to mean that both (1) and (2) are true, judging from the rest of
your message.
>> If so, then you now established that it is a bug in merge-recursive,
>> right [implementors of recursive-in-C CC'ed]?
>
> Correct.
>
>> Or are you saying that the step should not fail to begin with?
>
> No...
Ok, thanks. Unfortunately my git day for this week is over, so I'll try
to find time to take a look at what recursive strategy does wrong over the
weekend, if nobody gets to it before me.
^ permalink raw reply
* Re: malloc fails when dealing with huge files
From: Johannes Schindelin @ 2008-12-11 9:11 UTC (permalink / raw)
To: Linus Torvalds; +Cc: Jonathan Blanton, git
In-Reply-To: <alpine.LFD.2.00.0812101121401.3340@localhost.localdomain>
Hi,
On Wed, 10 Dec 2008, Linus Torvalds wrote:
> However, git performance with big files would never be wonderful, and
> things like "git diff" would still end up reading not just the whole
> file, but _both_versions_ at the same time. Marking the big files as
> being no-diff might help, though.
Makes me wonder if we should not have a default cut-off, say, 10MB, at
which files are automatically tagged with the no-diff attribute (unless
overridden explicitely in .gitattributes)?
Ciao,
Dscho
^ permalink raw reply
* Clarifying "invalid tag signature file" error message from git filter-branch (and others)
From: James Youngman @ 2008-12-11 10:14 UTC (permalink / raw)
To: git
[-- Attachment #1: Type: text/plain, Size: 852 bytes --]
What do the errors "error: char88: malformed tagger field" and "fatal:
invalid tag signature file" and "Could not create new tag object for
FINDUTILS-4_1-10" signify in the session below?
Are any of those errors correctable (I can re-run the tree rewrite
script as many times as needed, I'm just using it on a test repository
for now).
The script I'm invoking calls git filter-branch; I've attached the script.
$ bash ~/source/GNU/findutils/logrewrite.sh
fatal: Not a git repository
Rewrite 9afb13614381c4bfa22f515c18cbfe6f54798330 (1587/1587)
Ref 'refs/heads/master' was rewritten
FINDUTILS-4_1-10 -> FINDUTILS-4_1-10
(ce25eb352de8dc53a9a7805ba9efc1c9215d28c2 ->
79221244237bc408ced0c37f3c3476d53e665801)
error: char88: malformed tagger field
fatal: invalid tag signature file
Could not create new tag object for FINDUTILS-4_1-10
$
Thanks,
James.
[-- Attachment #2: logrewrite.sh --]
[-- Type: application/x-sh, Size: 1401 bytes --]
^ permalink raw reply
* fatal: $HOME not set
From: JD Guzman @ 2008-12-11 10:36 UTC (permalink / raw)
To: git
This is probably a really stupid question but I'm trying to run git outside
of the shell provided by msysgit.
Everything works however when I try to set config options I get the error in
the subject line. I would suspect this is because the windows command
prompt isn't providing any information about the user's home directory. My
question is would this be something that I can set manually or does git rely
on the OS to provide this information?
Regards,
JD Guzman
^ permalink raw reply
* Re: fatal: $HOME not set
From: Johannes Sixt @ 2008-12-11 11:09 UTC (permalink / raw)
To: JD Guzman; +Cc: git
In-Reply-To: <003a01c95b7c$65b93a40$312baec0$@com>
JD Guzman schrieb:
> This is probably a really stupid question but I'm trying to run git outside
> of the shell provided by msysgit.
> Everything works however when I try to set config options I get the error in
> the subject line. I would suspect this is because the windows command
> prompt isn't providing any information about the user's home directory. My
> question is would this be something that I can set manually or does git rely
> on the OS to provide this information?
You can set it yourself to where you want git to store the .gitconfig
file. Do this in Settings->System->Advanced->Environment Variables,
section User variables. You can set it to %HOMEDRIVE%%HOMEPATH% in order
point it somewhere below C:\Documents and Settings.
-- Hannes
^ permalink raw reply
* Specifying default checkout branch in url
From: Resul Cetin @ 2008-12-11 11:21 UTC (permalink / raw)
To: git
Hi,
I have the problem that I want to checkout a specific branch in a git-clone
run. Someone at the debian-mentors mailing list[1] has suggested to use a url
like git://git.myserver.org/project.git#branchname to checkout a branch with
the name branchname by default. But this doesn't seem to work. Is there
already another way to encode this in the url?
More information why I cannot change HEAD of the repository can be found in a
bug report against debcheckout[2].
Regards,
Resul
[1] http://article.gmane.org/gmane.linux.debian.devel.mentors/34421
[2] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=508433
^ permalink raw reply
* Re: Specifying default checkout branch in url
From: Mikael Magnusson @ 2008-12-11 11:45 UTC (permalink / raw)
To: Resul Cetin; +Cc: git
In-Reply-To: <200812111221.36561.Resul-Cetin@gmx.net>
2008/12/11 Resul Cetin <Resul-Cetin@gmx.net>:
> Hi,
> I have the problem that I want to checkout a specific branch in a git-clone
> run. Someone at the debian-mentors mailing list[1] has suggested to use a url
> like git://git.myserver.org/project.git#branchname to checkout a branch with
> the name branchname by default. But this doesn't seem to work. Is there
> already another way to encode this in the url?
> More information why I cannot change HEAD of the repository can be found in a
> bug report against debcheckout[2].
>
> Regards,
> Resul
>
> [1] http://article.gmane.org/gmane.linux.debian.devel.mentors/34421
> [2] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=508433
Use a git init, remote add, fetch branchname:branchname sequence.
--
Mikael Magnusson
^ permalink raw reply
* Re: What's cooking in git.git (Nov 2008, #06; Wed, 26)
From: Nguyen Thai Ngoc Duy @ 2008-12-11 13:04 UTC (permalink / raw)
To: Daniel Barkalow; +Cc: Junio C Hamano, Shawn O. Pearce, Johannes Schindelin, git
In-Reply-To: <alpine.LNX.1.00.0812081223140.19665@iabervon.org>
On 12/9/08, Daniel Barkalow <barkalow@iabervon.org> wrote:
> > - for "git grep", we ignore path with CE_NO_CHECKOUT (while using
> > cache version for CE_VALID)
>
>
> Is this sufficient? I'd expect "git grep" to ignore paths that are outside
> the checked-out region, even when searching an arbitrary tree, and even
> when those files aren't in the index at all (i.e., the current commit
> doesn't have them). That is, I'd expect core.defaultsparse or the
> equivalent to limit the paths, normally giving this effect.
That's the point. CE_VALID does not define checkout area while
CE_NO_CHECKOUT does. If an entry is CE_VALID, it is still in checkout
area. But if it is CE_NO_CHECKOUT, "git grep" should ignore that path.
core.defaultsparse has nothing to do here.
> > > The question, then, is what happens when the index and core.defaultsparse
> > > disagree, either because the porcelain supports causing it or because the
> > > user has simply editting the config file or used plumbing to modify the
> > > index. That is, (1) we have index entries that say that the worktree is
> > > ignored, and the rules don't say they're outside the sparse checkout; do
> > > we care whether we expect the worktree to be empty or match the index?
> > > And, (2) we have index entries that say we do care about them, but the
> > > rules say they're outside the sparse checkout; what happens with these?
> >
> > The rule is CE_NO_CHECKOUT is king. core.defaultsparse only helps
> > setting CE_NO_CHECKOUT on new entries when they enter the index.
>
>
> This seems like a really bad idea to me. If you ask for a file that's
> outside your default area to be checked out, and then you switch branches
> and switch back, the file may or may not disappear (depending on whether
> the branch you switched to temporarily had it or not). Likewise, if you
> remove files, and then switch branches and back, the files may or may not
> reappear.
Well, if you set core.defaultsparse properly, those files should
appear/disappear as you wish (and as of now if you define your
checkout area with "git checkout --{include-,exclude-,}sparse" then
core.defaultsparse should be updated accordingly). I don't say
core.defaultsparse is perfect.
Anyway how do you suppose the tool to do in your case (checkout,
switch away then switch back)?
--
Duy
^ permalink raw reply
* [PATCH v2] submodule: Allow tracking of the newest revision of a branch in a submodule
From: Fabian Franz @ 2008-12-11 13:16 UTC (permalink / raw)
To: FabianFranz, git; +Cc: hjemli, Fabian Franz
Submodules currently only allow tracking a specific revision
and each update in a submodule leads to a new commit in the
master repository. However some users may want to always track
the newest revision of a specific (named) tag or branch or HEAD.
For example the user might want to track a staging branch in all
submodules.
To allow this the "--track|-t <branch>" parameter was added to
git-submodule.sh, which is added to .gitmodules config file as
well as "track" parameter. This creates a new local branch on
checkout, which is tracking the remote branch in case the local
branch does not yet exist.
Technically the gitlink code was changed to always compare
successful (so no changes) in case the sha1 is null. In that
case no new commit is created when there are changes in the
submodule.
The submodule code is adding the file with 0000* on
"add".
Signed-off-by: Fabian Franz <git@fabian-franz.de>
---
On Tue, Dec 9, 2008 at 01:57, Fabian Franz <git@xxxxxxxxxxxxxxx> wrote:
>> Technically the gitlink code was changed to read .git/HEAD.gitlink
>> if it exists instead of the normal HEAD. If you add 0000* as sha1
>> sum to .git/HEAD.gitlink the submodule code will always fetch HEAD.
>This feels like the porcelain "fooling" the plumbing. How about
>something like this instead:
>This should make the plumbing happy no matter which commit is actually
>checked out in the submodule (not actually tested...).
Yeah, that works, thank you very much.
>Then, cmd_update() can check if the requested sha1 is all '0' and
>fetch+checkout latest HEAD (or some branch) without playing games with
>.git/HEAD.gitlink.
Okay. I do agree that this solution is much nicer.
>Finally, cmd_add() needs to update the index in the
>containing repository with the magic '0*' sha1 if '--track' is
>specifed. This can be achieved by replacing 'git add $path' with 'echo
>$mode $sha1\t$path | git update-index --index-info'.
>What do you think?
I added that solution and it does work. However I see problems on remove (thought there is no submodule remove so far):
Neither a new git add nor a git rm nor a git-update-index --remove do work afterwards.
This can just be done by doing the above command with some non-null sha1.
>> @@ -327,10 +335,14 @@ cmd_update()
>> say "Maybe you want to use 'update --init'?"
>> continue
>> fi
>> + track=$(git config -f .gitmodules submodule."$name".track)
>I'm pretty certain that we don't want to use info from .gitmodules in
>cmd_update(). Instead, cmd_init() probably should move the info from
>.gitmodules into .git/config and cmd_update() should check the latter.
Okay, I did this, however now I need a flag for init to be able to specify -f or a initalways command shortcut, so I can do in script:
git checkout staging # changes track = staging in .gitmodules
git submodule init -f
git submodule update # changes all branches to staging
Should I add this in a different patch?
Talking about script: Is there a possibility for a checkout hook?
Does something like that exist? Or do I need to add this myself, too?
So my workflow really is:
git checkout master # done long before
[...]
git checkout staging
# => in submodules/client/
# Checked out submodules/client/ staging.
# => in submodules/client/component1/
# Checked out submodules/client/component/1 staging.
So I would like to have this recursively and I think a post checkout hook would be nice for that to achieve.
>Btw: cmd_status() probably also needs some modifications to handle
>this special case.
I added the special case.
Here is the new patch.
What do you think?
Or do we need a track and untrack command for the rm of a submodule to work properly?
Best Wishes,
Fabian
Documentation/git-submodule.txt | 10 +++++++++-
git-submodule.sh | 38 ++++++++++++++++++++++++++++++++++++--
read-cache.c | 5 +++++
3 files changed, 50 insertions(+), 3 deletions(-)
diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt
index babaa9b..9c29678 100644
--- a/Documentation/git-submodule.txt
+++ b/Documentation/git-submodule.txt
@@ -9,7 +9,7 @@ git-submodule - Initialize, update or inspect submodules
SYNOPSIS
--------
[verse]
-'git submodule' [--quiet] add [-b branch] [--] <repository> <path>
+'git submodule' [--quiet] add [-b branch] [-t|--track <branch>] [--] <repository> <path>
'git submodule' [--quiet] status [--cached] [--] [<path>...]
'git submodule' [--quiet] init [--] [<path>...]
'git submodule' [--quiet] update [--init] [--] [<path>...]
@@ -118,6 +118,10 @@ update::
If the submodule is not yet initialized, and you just want to use the
setting as stored in .gitmodules, you can automatically initialize the
submodule with the --init option.
++
+If you used --track or set the "track" option in .gitmodules this will
+automatically pull the newest updates from remote instead of tracking a
+specific revision.
summary::
Show commit summary between the given commit (defaults to HEAD) and
@@ -159,6 +163,10 @@ OPTIONS
--branch::
Branch of repository to add as submodule.
+-t::
+--track::
+ Branch/Tag/HEAD of repository to track in a submodule.
+
--cached::
This option is only valid for status and summary commands. These
commands typically use the commit found in the submodule HEAD, but
diff --git a/git-submodule.sh b/git-submodule.sh
index 2f47e06..f25e744 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -5,7 +5,7 @@
# Copyright (c) 2007 Lars Hjemli
USAGE="[--quiet] [--cached] \
-[add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
+[add <repo> [-b branch] [--track|-t <branch>] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
[--] [<path>...]|[foreach <command>]|[sync [--] [<path>...]]"
OPTIONS_SPEC=
. git-sh-setup
@@ -16,6 +16,7 @@ command=
branch=
quiet=
cached=
+track=
#
# print stuff on stdout unless -q was specified
@@ -130,6 +131,11 @@ cmd_add()
-q|--quiet)
quiet=1
;;
+ -t|--track)
+ case "$2" in '') usage ;; esac
+ track=$2
+ shift
+ ;;
--)
shift
break
@@ -197,12 +203,14 @@ cmd_add()
(unset GIT_DIR; cd "$path" && git checkout -f -q ${branch:+-b "$branch" "origin/$branch"}) ||
die "Unable to checkout submodule '$path'"
fi
+ [ -n "$track" ] && echo "160000 0000000000000000000000000000000000000000\t$path" | git update-index --index-info
git add "$path" ||
die "Failed to add submodule '$path'"
git config -f .gitmodules submodule."$path".path "$path" &&
git config -f .gitmodules submodule."$path".url "$repo" &&
+ git config -f .gitmodules submodule."$path".track "$track" &&
git add .gitmodules ||
die "Failed to register submodule '$path'"
}
@@ -277,6 +285,10 @@ cmd_init()
git config submodule."$name".url "$url" ||
die "Failed to register url for submodule path '$path'"
+ track=$(git config -f .gitmodules submodule."$name".track)
+ git config submodule."$name".track "$track" ||
+ die "Failed to register track for submodule path '$path'"
+
say "Submodule '$name' ($url) registered for path '$path'"
done
}
@@ -327,10 +339,12 @@ cmd_update()
say "Maybe you want to use 'update --init'?"
continue
fi
+ track=$(git config submodule."$name".track)
if ! test -d "$path"/.git -o -f "$path"/.git
then
module_clone "$path" "$url" || exit
+
subsha1=
else
subsha1=$(unset GIT_DIR; cd "$path" &&
@@ -345,11 +359,28 @@ cmd_update()
then
force="-f"
fi
+ pull=
+ if [ "$sha1" = "0000000000000000000000000000000000000000" ]
+ then
+ [ -z "$track" ] && track="HEAD"
+ # if the local branch does not yet exist, create it
+ ( unset GIT_DIR; cd "$path"; git-show-ref --heads --tags -q "$track" || git branch --track "$track" "origin/$track" )
+ sha1="$track"
+ pull=1
+ fi
+
(unset GIT_DIR; cd "$path" && git-fetch &&
git-checkout $force -q "$sha1") ||
die "Unable to checkout '$sha1' in submodule path '$path'"
say "Submodule path '$path': checked out '$sha1'"
+
+ if [ "$pull" = "1" ]
+ then
+ # Now pull new updates from origin
+ ( unset GIT_DIR; cd "$path"; git-pull )
+ fi
+
fi
done
}
@@ -596,7 +627,10 @@ cmd_status()
set_name_rev "$path" "$sha1"
if git diff-files --quiet -- "$path"
then
- say " $sha1 $path$revname"
+ track=$(git config submodule."$name".track)
+ tracking=
+ [ -n "$track" ] && tracking=" (tracking $track)"
+ say " $sha1 $path$revname$tracking"
else
if test -z "$cached"
then
diff --git a/read-cache.c b/read-cache.c
index 8579663..0c14b68 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -137,6 +137,11 @@ static int ce_compare_gitlink(struct cache_entry *ce)
*/
if (resolve_gitlink_ref(ce->name, "HEAD", sha1) < 0)
return 0;
+
+ // To be able to track newest revision
+ if (is_null_sha1(ce->sha1))
+ return 0;
+
return hashcmp(sha1, ce->sha1);
}
--
1.6.1.rc2.1.g4859.dirty
^ permalink raw reply related
* Re: [PATCH v2] submodule: Allow tracking of the newest revision of a branch in a submodule
From: Johannes Sixt @ 2008-12-11 14:24 UTC (permalink / raw)
To: Fabian Franz; +Cc: FabianFranz, git, hjemli
In-Reply-To: <1229001361-9301-1-git-send-email-git@fabian-franz.de>
Fabian Franz schrieb:
> Submodules currently only allow tracking a specific revision
> and each update in a submodule leads to a new commit in the
> master repository. However some users may want to always track
> the newest revision of a specific (named) tag or branch or HEAD.
> For example the user might want to track a staging branch in all
> submodules.
Personally, I don't particularly like this feature (but then, nobody
forces me to use it ;) In which situation do you need this?
By tieing a project commit to a particular submodule commit the committer
gives the guarantee: "I've tested this with this module version, and it
works; all is ok." With this new feature, this guarantee vanishes, because
the committer has no control over which version of the module will
ultimately be used; it could be newer or it could be older.
I've reviewed the patch just from a shell code writer's point of view.
> + [ -n "$track" ] && echo "160000 0000000000000000000000000000000000000000\t$path" | git update-index --index-info
We tend to use "test" instead of "[ ]".
You cannot rely on that echo or the shell translates "\t"; use printf.
test "$track" && printf '160000
0000000000000000000000000000000000000000\t%s\n' "$path" | git update-index
--index-info
(The line-wrapping is from my MUA; sorry.)
> @@ -327,10 +339,12 @@ cmd_update()
> say "Maybe you want to use 'update --init'?"
> continue
> fi
> + track=$(git config submodule."$name".track)
You don't need $track *here*, do you?
> if ! test -d "$path"/.git -o -f "$path"/.git
> then
> module_clone "$path" "$url" || exit
> +
> subsha1=
And this extra blank line is an accident, isn't it?
> + [ -z "$track" ] && track="HEAD"
Instead of this you can use a shell trick (but I don't know if it's portable):
: "${track:=HEAD}"
And I think you can even spare the quotes.
> + # if the local branch does not yet exist, create it
> + ( unset GIT_DIR; cd "$path"; git-show-ref --heads --tags -q "$track" || git branch --track "$track" "origin/$track" )
Ugh! A *branch* named "HEAD"?? I think you should reconsider this decision.
> + if [ "$pull" = "1" ]
if test "$pull"
> + then
> + # Now pull new updates from origin
> + ( unset GIT_DIR; cd "$path"; git-pull )
Wow! Creating new commits on the fly while doing a "git submodule update"!
Error check is missing here.
> @@ -596,7 +627,10 @@ cmd_status()
> set_name_rev "$path" "$sha1"
> if git diff-files --quiet -- "$path"
> then
> - say " $sha1 $path$revname"
> + track=$(git config submodule."$name".track)
> + tracking=
> + [ -n "$track" ] && tracking=" (tracking $track)"
> + say " $sha1 $path$revname$tracking"
The last three lines can be shortened to this:
say " $sha1 $path$revname${track:+ (tracking "$track")}"
-- Hannes
^ permalink raw reply
* Re: [JGIT PATCH 2/5] Add copy(InputStream) to TemporaryBuffer
From: Robin Rosenberg @ 2008-12-11 15:40 UTC (permalink / raw)
To: Shawn O. Pearce; +Cc: git
In-Reply-To: <1228971522-28764-3-git-send-email-spearce@spearce.org>
torsdag 11 december 2008 05:58:39 skrev Shawn O. Pearce:
> In some places we may find it ourselves with an InputStream we
> need to copy into a TemporaryBuffer, so we can flatten out the
> entire stream to a single byte[]. Putting the copy loop here
> is more useful then duplicating it in application level code.
> + public void copy(final InputStream in) throws IOException {
> + final byte[] b = new byte[2048];
Why not 8192 here too?
-- robin
^ permalink raw reply
* [PATCH v3] submodule: Allow tracking of the newest revision of a branch in a submodule
From: Fabian Franz @ 2008-12-11 15:39 UTC (permalink / raw)
To: git; +Cc: hjemli, j.sixt, Fabian Franz
Submodules currently only allow tracking a specific revision
and each update in a submodule leads to a new commit in the
master repository. However some users may want to always track
the newest revision of a specific (named) tag or branch or HEAD.
For example the user might want to track a staging branch in all
submodules.
To allow this the "--track|-t <branch>" parameter was added to
git-submodule.sh, which is added to .gitmodules config file as
well as "track" parameter. This creates a new local branch on
checkout, which is tracking the remote branch in case the local
branch does not yet exist.
Technically the gitlink code was changed to always compare
successful (so no changes) in case the sha1 is null. In that
case no new commit is created when there are changes in the
submodule.
The submodule code is adding the file with 0000* on
"add".
Signed-off-by: Fabian Franz <git@fabian-franz.de>
---
>
>Fabian Franz schrieb:
>> Submodules currently only allow tracking a specific revision
>> and each update in a submodule leads to a new commit in the
>> master repository. However some users may want to always track
>> the newest revision of a specific (named) tag or branch or HEAD.
>> For example the user might want to track a staging branch in all
>> submodules.
>
>Personally, I don't particularly like this feature (but then, nobody
>forces me to use it ;) In which situation do you need this?
I have a development workflow in my company where we are independently
working on many components. And for some things I just always want to
have the newest revision without having to commit twice always.
>
>By tieing a project commit to a particular submodule commit the committer
>gives the guarantee: "I've tested this with this module version, and it
>works; all is ok." With this new feature, this guarantee vanishes, because
>the committer has no control over which version of the module will
>ultimately be used; it could be newer or it could be older.
I like this and because of that the --branch is optional. I also like that so much, that we have decided against Google Repo.
However I have both cases: Stable development, where I need one special version and "wild" development, where I always want the newest published one.
>
>I've reviewed the patch just from a shell code writer's point of view.
Okay, I added your suggestions.
Thanks for your feedback.
Best Wishes,
Fabian
Documentation/git-submodule.txt | 10 +++++++++-
git-submodule.sh | 35 +++++++++++++++++++++++++++++++++--
read-cache.c | 5 +++++
3 files changed, 47 insertions(+), 3 deletions(-)
diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt
index babaa9b..9c29678 100644
--- a/Documentation/git-submodule.txt
+++ b/Documentation/git-submodule.txt
@@ -9,7 +9,7 @@ git-submodule - Initialize, update or inspect submodules
SYNOPSIS
--------
[verse]
-'git submodule' [--quiet] add [-b branch] [--] <repository> <path>
+'git submodule' [--quiet] add [-b branch] [-t|--track <branch>] [--] <repository> <path>
'git submodule' [--quiet] status [--cached] [--] [<path>...]
'git submodule' [--quiet] init [--] [<path>...]
'git submodule' [--quiet] update [--init] [--] [<path>...]
@@ -118,6 +118,10 @@ update::
If the submodule is not yet initialized, and you just want to use the
setting as stored in .gitmodules, you can automatically initialize the
submodule with the --init option.
++
+If you used --track or set the "track" option in .gitmodules this will
+automatically pull the newest updates from remote instead of tracking a
+specific revision.
summary::
Show commit summary between the given commit (defaults to HEAD) and
@@ -159,6 +163,10 @@ OPTIONS
--branch::
Branch of repository to add as submodule.
+-t::
+--track::
+ Branch/Tag/HEAD of repository to track in a submodule.
+
--cached::
This option is only valid for status and summary commands. These
commands typically use the commit found in the submodule HEAD, but
diff --git a/git-submodule.sh b/git-submodule.sh
index 2f47e06..16df528 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -5,7 +5,7 @@
# Copyright (c) 2007 Lars Hjemli
USAGE="[--quiet] [--cached] \
-[add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
+[add <repo> [-b branch] [--track|-t <branch>] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
[--] [<path>...]|[foreach <command>]|[sync [--] [<path>...]]"
OPTIONS_SPEC=
. git-sh-setup
@@ -16,6 +16,7 @@ command=
branch=
quiet=
cached=
+track=
#
# print stuff on stdout unless -q was specified
@@ -130,6 +131,11 @@ cmd_add()
-q|--quiet)
quiet=1
;;
+ -t|--track)
+ case "$2" in '') usage ;; esac
+ track=$2
+ shift
+ ;;
--)
shift
break
@@ -197,12 +203,14 @@ cmd_add()
(unset GIT_DIR; cd "$path" && git checkout -f -q ${branch:+-b "$branch" "origin/$branch"}) ||
die "Unable to checkout submodule '$path'"
fi
+ test -n "$track" && printf '160000 0000000000000000000000000000000000000000\t%s\n' "$path" | git update-index --index-info
git add "$path" ||
die "Failed to add submodule '$path'"
git config -f .gitmodules submodule."$path".path "$path" &&
git config -f .gitmodules submodule."$path".url "$repo" &&
+ git config -f .gitmodules submodule."$path".track "$track" &&
git add .gitmodules ||
die "Failed to register submodule '$path'"
}
@@ -277,6 +285,10 @@ cmd_init()
git config submodule."$name".url "$url" ||
die "Failed to register url for submodule path '$path'"
+ track=$(git config -f .gitmodules submodule."$name".track)
+ git config submodule."$name".track "$track" ||
+ die "Failed to register track for submodule path '$path'"
+
say "Submodule '$name' ($url) registered for path '$path'"
done
}
@@ -345,11 +357,29 @@ cmd_update()
then
force="-f"
fi
+ pull=
+ if [ "$sha1" = "0000000000000000000000000000000000000000" ]
+ then
+ track=$(git config submodule."$name".track)
+ : ${track:="master"}
+ # if the local branch does not yet exist, create it
+ ( unset GIT_DIR; cd "$path"; git-show-ref --heads --tags -q "$track" || git branch --track "$track" "origin/$track" )
+ sha1="$track"
+ pull=1
+ fi
+
(unset GIT_DIR; cd "$path" && git-fetch &&
git-checkout $force -q "$sha1") ||
die "Unable to checkout '$sha1' in submodule path '$path'"
say "Submodule path '$path': checked out '$sha1'"
+
+ if [ "$pull" = "1" ]
+ then
+ # Now pull new updates from origin
+ ( unset GIT_DIR; cd "$path"; git-pull ) || die "Unable to pull in submodule path '$path'"
+ fi
+
fi
done
}
@@ -596,7 +626,8 @@ cmd_status()
set_name_rev "$path" "$sha1"
if git diff-files --quiet -- "$path"
then
- say " $sha1 $path$revname"
+ track=$(git config submodule."$name".track)
+ say " $sha1 $path$revname${track:+ (tracking "$track")}"
else
if test -z "$cached"
then
diff --git a/read-cache.c b/read-cache.c
index 8579663..0c14b68 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -137,6 +137,11 @@ static int ce_compare_gitlink(struct cache_entry *ce)
*/
if (resolve_gitlink_ref(ce->name, "HEAD", sha1) < 0)
return 0;
+
+ // To be able to track newest revision
+ if (is_null_sha1(ce->sha1))
+ return 0;
+
return hashcmp(sha1, ce->sha1);
}
--
1.5.3.6
^ permalink raw reply related
* Re: [JGIT PATCH 2/5] Add copy(InputStream) to TemporaryBuffer
From: Shawn O. Pearce @ 2008-12-11 15:52 UTC (permalink / raw)
To: Robin Rosenberg; +Cc: git
In-Reply-To: <200812111640.34435.robin.rosenberg@dewire.com>
Robin Rosenberg <robin.rosenberg@dewire.com> wrote:
> torsdag 11 december 2008 05:58:39 skrev Shawn O. Pearce:
> > In some places we may find it ourselves with an InputStream we
> > need to copy into a TemporaryBuffer, so we can flatten out the
> > entire stream to a single byte[]. Putting the copy loop here
> > is more useful then duplicating it in application level code.
> > + public void copy(final InputStream in) throws IOException {
> > + final byte[] b = new byte[2048];
>
> Why not 8192 here too?
Blargh, you're right. Actually what I should do is look to see
if blocks != null, in which case I should alloc a block and read
directly into it. That would avoid one copy of the data.
--
Shawn.
^ permalink raw reply
* [PATCH] Show a failure of rebase -p if the merge had a conflict
From: Johannes Sixt @ 2008-12-11 16:21 UTC (permalink / raw)
To: Johannes Schindelin
Cc: Andreas Ericsson, Stephen Haberman, git, Junio C Hamano,
Johannes Sixt
This extends t3409-rebase-preserve-merges by a case where the merge that
is rebased has a conflict. Therefore, the rebase stops and expects that
the user resolves the conflict. However, currently rebase --continue
fails because .git/rebase-merge/author-script is missing.
The test script had allocated two identical clones, but only one of them
(clone2) was used. Now we use both as indicated in the comment. Also, an
instance of && was missing in the setup part.
Signed-off-by: Johannes Sixt <j6t@kdbg.org>
---
BTW, I'm not 100% sure whether the additional tests of what to expect
from the test if it did not fail are correct.
I am unable to fix the failure.
-- Hannes
[Sorry, Junio, for the resend. git send-email & PEBCAK. :-/ ]
t/t3409-rebase-preserve-merges.sh | 43 ++++++++++++++++++++++++++++++++----
1 files changed, 38 insertions(+), 5 deletions(-)
diff --git a/t/t3409-rebase-preserve-merges.sh b/t/t3409-rebase-preserve-merges.sh
index 8cde40f..02a6401 100755
--- a/t/t3409-rebase-preserve-merges.sh
+++ b/t/t3409-rebase-preserve-merges.sh
@@ -11,7 +11,7 @@ Run "git rebase -p" and check that merges are properly carried along
GIT_AUTHOR_EMAIL=bogus_email_address
export GIT_AUTHOR_EMAIL
-#echo 'Setting up:
+#Clone 1 (trivial merge):
#
#A1--A2 <-- origin/master
# \ \
@@ -19,7 +19,15 @@ export GIT_AUTHOR_EMAIL
# \
# B2 <-- origin/topic
#
-#'
+#Clone 2 (conflicting merge):
+#
+#A1--A2--B3 <-- origin/master
+# \ \
+# B1------M <-- topic
+# \
+# B2 <-- origin/topic
+#
+# In both cases, 'topic' is rebased onto 'origin/topic'.
test_expect_success 'setup for merge-preserving rebase' \
'echo First > A &&
@@ -37,12 +45,19 @@ test_expect_success 'setup for merge-preserving rebase' \
cd clone1 &&
git checkout -b topic origin/topic &&
git merge origin/master &&
- cd ..
+ cd .. &&
+
+ echo Fifth > B &&
+ git add B &&
+ git commit -m "Add different B" &&
git clone ./. clone2
cd clone2 &&
git checkout -b topic origin/topic &&
- git merge origin/master &&
+ test_must_fail git merge origin/master &&
+ echo Resolved > B &&
+ git add B &&
+ git commit -m "Merge origin/master into topic" &&
cd .. &&
git checkout topic &&
@@ -51,11 +66,29 @@ test_expect_success 'setup for merge-preserving rebase' \
'
test_expect_success 'rebase -p fakes interactive rebase' '
- cd clone2 &&
+ (
+ cd clone1 &&
git fetch &&
git rebase -p origin/topic &&
test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
test 1 = $(git rev-list --all --pretty=oneline | grep "Merge commit" | wc -l)
+ )
+'
+
+test_expect_failure '--continue works after a conflict' '
+ (
+ cd clone2 &&
+ git fetch &&
+ test_must_fail git rebase -p origin/topic &&
+ test 2 = $(git ls-files B | wc -l) &&
+ echo Resolved again > B &&
+ test_must_fail git rebase --continue &&
+ git add B &&
+ git rebase --continue &&
+ test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
+ test 1 = $(git rev-list --all --pretty=oneline | grep "Add different" | wc -l) &&
+ test 1 = $(git rev-list --all --pretty=oneline | grep "Merge origin" | wc -l)
+ )
'
test_done
--
1.6.1.rc2.22.gf3bf84
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox