threads / patch / 16566

patch, 2 partsImprove closing of files in error situations.

Subject: [JGIT PATCH 2/2] Improve closing of files in error situations.

## tl;dr

98 messages between Dec 2, 2008 and Feb 27, 2025. Diffs are folded; open one to read it.

replies: 97people: 31as markdown or json

Robin Rosenberg· Dec 2, 2008, 21:20 UTC · lore

[JGIT PATCH 1/2] Close a forgotten reference to the HEAD ref.

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../src/org/spearce/jgit/lib/Repository.java       |   12 ++++++++++--
 1 files changed, 10 insertions(+), 2 deletions(-)
Show changes to org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java +10 −2
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index c953531..b54afd5 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -989,9 +989,10 @@ public Ref peel(final Ref ref) {
 	 * @return true if HEAD points to a StGit patch.
 	 */
 	public boolean isStGitMode() {
+		File file = new File(getDirectory(), "HEAD");
+		BufferedReader reader = null;
 		try {
-			File file = new File(getDirectory(), "HEAD");
-			BufferedReader reader = new BufferedReader(new FileReader(file));
+			reader = new BufferedReader(new FileReader(file));
 			String string = reader.readLine();
 			if (!string.startsWith("ref: refs/heads/"))
 				return false;
@@ -1007,6 +1008,13 @@ public boolean isStGitMode() {
 		} catch (IOException e) {
 			e.printStackTrace();
 			return false;
+		} finally {
+			try {
+				if (reader != null)
+					reader.close();
+			} catch (IOException e1) {
+				// nothing to do here
+			}
 		}
 	}
 
-- 
1.6.0.3.640.g6331a
Robin Rosenberg· Dec 2, 2008, 21:20 UTC · re: Robin Rosenberg · lore
Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../exttst/org/spearce/jgit/lib/SpeedTestBase.java |   12 +++++--
 .../src/org/spearce/jgit/lib/Repository.java       |   31 +++++++++++++-------
 2 files changed, 28 insertions(+), 15 deletions(-)
Show changes to 2 files +28 −15

org.spearce.jgit.test/exttst/org/spearce/jgit/lib/SpeedTestBase.java, org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java

diff --git a/org.spearce.jgit.test/exttst/org/spearce/jgit/lib/SpeedTestBase.java b/org.spearce.jgit.test/exttst/org/spearce/jgit/lib/SpeedTestBase.java
index 11f7439..36a5e0e 100644
--- a/org.spearce.jgit.test/exttst/org/spearce/jgit/lib/SpeedTestBase.java
+++ b/org.spearce.jgit.test/exttst/org/spearce/jgit/lib/SpeedTestBase.java
@@ -72,10 +72,14 @@
 	protected void prepare(String[] refcmd) throws Exception {
 		try {
 			BufferedReader bufferedReader = new BufferedReader(new FileReader("kernel.ref"));
-			kernelrepo = bufferedReader.readLine();
-			bufferedReader.close();
-			timeNativeGit(kernelrepo, refcmd);
-			nativeTime = timeNativeGit(kernelrepo, refcmd);
+			try {
+				kernelrepo = bufferedReader.readLine();
+				bufferedReader.close();
+				timeNativeGit(kernelrepo, refcmd);
+				nativeTime = timeNativeGit(kernelrepo, refcmd);
+			} finally {
+				bufferedReader.close();
+			}
 		} catch (Exception e) {
 			System.out.println("Create a file named kernel.ref and put the path to the Linux kernels repository there");
 			throw e;
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index b54afd5..da1494f 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -144,10 +144,13 @@ public Repository(final File d) throws IOException {
 		final File altFile = FS.resolve(objectsDir, "info/alternates");
 		if (altFile.exists()) {
 			BufferedReader ar = new BufferedReader(new FileReader(altFile));
-			for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
-				readObjectsDirs(FS.resolve(objectsDir, alt), ret);
+			try {
+				for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
+					readObjectsDirs(FS.resolve(objectsDir, alt), ret);
+				}
+			} catch (Exception e) {
+				ar.close();
 			}
-			ar.close();
 		}
 		return ret;
 	}
@@ -1027,15 +1030,21 @@ public boolean isStGitMode() {
 		if (isStGitMode()) {
 			File patchDir = new File(new File(getDirectory(),"patches"),getBranch());
 			BufferedReader apr = new BufferedReader(new FileReader(new File(patchDir,"applied")));
-			for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
-				File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
-				BufferedReader tfr = new BufferedReader(new FileReader(topFile));
-				String objectId = tfr.readLine();
-				ObjectId id = ObjectId.fromString(objectId);
-				ret.put(id, new StGitPatch(patchName, id));
-				tfr.close();
+			try {
+				for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
+					File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
+					BufferedReader tfr = new BufferedReader(new FileReader(topFile));
+					try {
+						String objectId = tfr.readLine();
+						ObjectId id = ObjectId.fromString(objectId);
+						ret.put(id, new StGitPatch(patchName, id));
+					} finally {
+						tfr.close();
+					}
+				}
+			} finally {
+				apr.close();
 			}
-			apr.close();
 		}
 		return ret;
 	}
-- 
1.6.0.3.640.g6331a
Robin Rosenberg· Dec 2, 2008, 21:41 UTC · re: Robin Rosenberg · lore

[JGIT PATCH 2/2 v2] Improve closing of files in error situations.

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../exttst/org/spearce/jgit/lib/SpeedTestBase.java |   12 +++++--
 .../dircache/DirCacheCGitCompatabilityTest.java    |   24 ++++++++++-----
 .../org/spearce/jgit/lib/RepositoryTestCase.java   |   27 +++++++++++------
 .../src/org/spearce/jgit/lib/GitIndex.java         |    2 +-
 .../src/org/spearce/jgit/lib/Repository.java       |   31 +++++++++++++-------
 5 files changed, 63 insertions(+), 33 deletions(-)
I was a bit quick there. This is an extended version of patch 2/2.
-- robin
Show changes to 5 files +63 −33

org.spearce.jgit.test/exttst/org/spearce/jgit/lib/SpeedTestBase.java, org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheCGitCompatabilityTest.java, org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java, org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java, org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java

diff --git a/org.spearce.jgit.test/exttst/org/spearce/jgit/lib/SpeedTestBase.java b/org.spearce.jgit.test/exttst/org/spearce/jgit/lib/SpeedTestBase.java
index 11f7439..36a5e0e 100644
--- a/org.spearce.jgit.test/exttst/org/spearce/jgit/lib/SpeedTestBase.java
+++ b/org.spearce.jgit.test/exttst/org/spearce/jgit/lib/SpeedTestBase.java
@@ -72,10 +72,14 @@
 	protected void prepare(String[] refcmd) throws Exception {
 		try {
 			BufferedReader bufferedReader = new BufferedReader(new FileReader("kernel.ref"));
-			kernelrepo = bufferedReader.readLine();
-			bufferedReader.close();
-			timeNativeGit(kernelrepo, refcmd);
-			nativeTime = timeNativeGit(kernelrepo, refcmd);
+			try {
+				kernelrepo = bufferedReader.readLine();
+				bufferedReader.close();
+				timeNativeGit(kernelrepo, refcmd);
+				nativeTime = timeNativeGit(kernelrepo, refcmd);
+			} finally {
+				bufferedReader.close();
+			}
 		} catch (Exception e) {
 			System.out.println("Create a file named kernel.ref and put the path to the Linux kernels repository there");
 			throw e;
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheCGitCompatabilityTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheCGitCompatabilityTest.java
index b052686..42832fe 100644
--- a/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheCGitCompatabilityTest.java
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheCGitCompatabilityTest.java
@@ -146,10 +146,14 @@ private File pathOf(final String name) {
 		final LinkedHashMap<String, CGitIndexRecord> r = new LinkedHashMap<String, CGitIndexRecord>();
 		final BufferedReader br = new BufferedReader(new InputStreamReader(
 				new FileInputStream(pathOf("gitgit.lsfiles")), "UTF-8"));
-		String line;
-		while ((line = br.readLine()) != null) {
-			final CGitIndexRecord cr = new CGitIndexRecord(line);
-			r.put(cr.path, cr);
+		try {
+			String line;
+			while ((line = br.readLine()) != null) {
+				final CGitIndexRecord cr = new CGitIndexRecord(line);
+				r.put(cr.path, cr);
+			}
+		} finally {
+			br.close();
 		}
 		return r;
 	}
@@ -158,10 +162,14 @@ private File pathOf(final String name) {
 		final LinkedHashMap<String, CGitLsTreeRecord> r = new LinkedHashMap<String, CGitLsTreeRecord>();
 		final BufferedReader br = new BufferedReader(new InputStreamReader(
 				new FileInputStream(pathOf("gitgit.lstree")), "UTF-8"));
-		String line;
-		while ((line = br.readLine()) != null) {
-			final CGitLsTreeRecord cr = new CGitLsTreeRecord(line);
-			r.put(cr.path, cr);
+		try {
+			String line;
+			while ((line = br.readLine()) != null) {
+				final CGitLsTreeRecord cr = new CGitLsTreeRecord(line);
+				r.put(cr.path, cr);
+			}
+		} finally {
+			br.close();
 		}
 		return r;
 	}
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java
index 22bf395..8937145 100644
--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java
@@ -152,14 +152,20 @@ private static void reportDeleteFailure(final String name,
 	protected static void copyFile(final File src, final File dst)
 			throws IOException {
 		final FileInputStream fis = new FileInputStream(src);
-		final FileOutputStream fos = new FileOutputStream(dst);
-		final byte[] buf = new byte[4096];
-		int r;
-		while ((r = fis.read(buf)) > 0) {
-			fos.write(buf, 0, r);
+		try {
+			final FileOutputStream fos = new FileOutputStream(dst);
+			try {
+				final byte[] buf = new byte[4096];
+				int r;
+				while ((r = fis.read(buf)) > 0) {
+					fos.write(buf, 0, r);
+				}
+			} finally {
+				fos.close();
+			}
+		} finally {
+			fis.close();
 		}
-		fis.close();
-		fos.close();
 	}
 
 	protected File writeTrashFile(final String name, final String data)
@@ -170,8 +176,11 @@ protected File writeTrashFile(final String name, final String data)
 			throw new Error("Could not create directory " + tf.getParentFile());
 		final OutputStreamWriter fw = new OutputStreamWriter(
 				new FileOutputStream(tf), "UTF-8");
-		fw.write(data);
-		fw.close();
+		try {
+			fw.write(data);
+		} finally {
+			fw.close();
+		}
 		return tf;
 	}
 
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java
index bafddef..7ff6754 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java
@@ -547,8 +547,8 @@ public boolean isModified(File wd, boolean forceContentCheck) {
 
 				try {
 					InputStream is = new FileInputStream(file);
-					ObjectWriter objectWriter = new ObjectWriter(db);
 					try {
+						ObjectWriter objectWriter = new ObjectWriter(db);
 						ObjectId newId = objectWriter.computeBlobSha1(file
 								.length(), is);
 						boolean ret = !newId.equals(sha1);
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index b54afd5..da1494f 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -144,10 +144,13 @@ public Repository(final File d) throws IOException {
 		final File altFile = FS.resolve(objectsDir, "info/alternates");
 		if (altFile.exists()) {
 			BufferedReader ar = new BufferedReader(new FileReader(altFile));
-			for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
-				readObjectsDirs(FS.resolve(objectsDir, alt), ret);
+			try {
+				for (String alt=ar.readLine(); alt!=null; alt=ar.readLine()) {
+					readObjectsDirs(FS.resolve(objectsDir, alt), ret);
+				}
+			} catch (Exception e) {
+				ar.close();
 			}
-			ar.close();
 		}
 		return ret;
 	}
@@ -1027,15 +1030,21 @@ public boolean isStGitMode() {
 		if (isStGitMode()) {
 			File patchDir = new File(new File(getDirectory(),"patches"),getBranch());
 			BufferedReader apr = new BufferedReader(new FileReader(new File(patchDir,"applied")));
-			for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
-				File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
-				BufferedReader tfr = new BufferedReader(new FileReader(topFile));
-				String objectId = tfr.readLine();
-				ObjectId id = ObjectId.fromString(objectId);
-				ret.put(id, new StGitPatch(patchName, id));
-				tfr.close();
+			try {
+				for (String patchName=apr.readLine(); patchName!=null; patchName=apr.readLine()) {
+					File topFile = new File(new File(new File(patchDir,"patches"), patchName), "top");
+					BufferedReader tfr = new BufferedReader(new FileReader(topFile));
+					try {
+						String objectId = tfr.readLine();
+						ObjectId id = ObjectId.fromString(objectId);
+						ret.put(id, new StGitPatch(patchName, id));
+					} finally {
+						tfr.close();
+					}
+				}
+			} finally {
+				apr.close();
 			}
-			apr.close();
 		}
 		return ret;
 	}
-- 
1.6.0.3.640.g6331a
Robin Rosenberg· Jun 7, 2009, 20:19 UTC · lore

[EGIT PATCH 0/3] Ref log reader

Initially this is needed to the next revised set of rename tests. I have not optimized much for performance.

-- robin
Robin Rosenberg (3):
  Assert the name and origName properties of Ref objects
  Add methods to RawParseUtils for scanning backwards.
  Add a ref log reader class
 .../tst/org/spearce/jgit/lib/RefTest.java          |   11 ++
 .../tst/org/spearce/jgit/lib/ReflogReaderTest.java |  153 ++++++++++++++++
 .../src/org/spearce/jgit/lib/ReflogReader.java     |  186 ++++++++++++++++++++
 .../src/org/spearce/jgit/lib/Repository.java       |    9 +
 .../src/org/spearce/jgit/util/RawParseUtils.java   |   84 +++++++++-
 5 files changed, 442 insertions(+), 1 deletions(-)
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java
Robin Rosenberg· Jun 7, 2009, 20:19 UTC · re: Robin Rosenberg · lore

[EGIT PATCH 1/3] Assert the name and origName properties of Ref objects

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../tst/org/spearce/jgit/lib/RefTest.java          |   11 +++++++++++
 1 files changed, 11 insertions(+), 0 deletions(-)
Show changes to org.spearce.jgit.test/tst/org/spearce/jgit/lib/RefTest.java +11 −0
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RefTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RefTest.java
index 440686d..fabbe7e 100644
--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RefTest.java
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RefTest.java
@@ -144,4 +144,15 @@ public void testReadSimplePackedRefSameRepo() throws IOException {
 		assertEquals(Storage.LOOSE_PACKED, ref.getStorage());
 	}
 
+	public void testOrigResolvedNamesBranch() throws IOException {
+		Ref ref = db.getRef("a");
+		assertEquals("refs/heads/a", ref.getName());
+		assertEquals("refs/heads/a", ref.getOrigName());
+	}
+
+	public void testOrigResolvedNamesSymRef() throws IOException {
+		Ref ref = db.getRef("HEAD");
+		assertEquals("refs/heads/master", ref.getName());
+		assertEquals("HEAD", ref.getOrigName());
+	}
 }
-- 
1.6.3.2.199.g7340d
Robin Rosenberg· Jun 7, 2009, 20:19 UTC · re: Robin Rosenberg · lore

[EGIT PATCH 2/3] Add methods to RawParseUtils for scanning backwards.

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../src/org/spearce/jgit/util/RawParseUtils.java   |   84 +++++++++++++++++++-
 1 files changed, 83 insertions(+), 1 deletions(-)
Show changes to org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java +83 −1
diff --git a/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java b/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java
index 79ebe41..bdd6a11 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java
@@ -321,6 +321,67 @@ public static final int nextLF(final byte[] b, int ptr, final char chrA) {
 	}
 
 	/**
+	 * Locate the first position before a given character.
+	 * 
+	 * @param b
+	 *            buffer to scan.
+	 * @param ptr
+	 *            position within buffer to start looking for chrA at.
+	 * @param chrA
+	 *            character to find.
+	 * @return new position just before chrA, -1 for not found
+	 */
+	public static final int prev(final byte[] b, int ptr, final char chrA) {
+		if (ptr == b.length)
+			--ptr;
+		while (ptr >= 0) {
+			if (b[ptr--] == chrA)
+				return ptr;
+		}
+		return ptr;
+	}
+
+	/**
+	 * Locate the first position before the previous LF.
+	 * <p>
+	 * This method stops on the first '\n' it finds.
+	 * 
+	 * @param b
+	 *            buffer to scan.
+	 * @param ptr
+	 *            position within buffer to start looking for LF at.
+	 * @return new position just before the first LF found, -1 for not found
+	 */
+	public static final int prevLF(final byte[] b, int ptr) {
+		return prev(b, ptr, '\n');
+	}
+
+	/**
+	 * Locate the previous position before either the given character or LF.
+	 * <p>
+	 * This method stops on the first match it finds from either chrA or '\n'.
+	 * 
+	 * @param b
+	 *            buffer to scan.
+	 * @param ptr
+	 *            position within buffer to start looking for chrA or LF at.
+	 * @param chrA
+	 *            character to find.
+	 * @return new position just before the first chrA or LF to be found, -1 for
+	 *         not found
+	 */
+	public static final int prevLF(final byte[] b, int ptr, final char chrA) {
+		if (ptr == b.length)
+			--ptr;
+		while (ptr >= 0) {
+			final byte c = b[ptr--];
+			if (c == chrA || c == '\n')
+				return ptr;
+		}
+		return ptr;
+	}
+
+	/**
 	 * Index the region between <code>[ptr, end)</code> to find line starts.
 	 * <p>
 	 * The returned list is 1 indexed. Index 0 contains
@@ -519,7 +580,28 @@ public static PersonIdent parsePersonIdent(final byte[] raw, final int nameB) {
 	 *         after decoding the region through the specified character set.
 	 */
 	public static String decode(final byte[] buffer) {
-		return decode(Constants.CHARSET, buffer, 0, buffer.length);
+		return decode(buffer, 0, buffer.length);
+	}
+
+	/**
+	 * Decode a buffer under UTF-8, if possible.
+	 * 
+	 * If the byte stream cannot be decoded that way, the platform default is
+	 * tried and if that too fails, the fail-safe ISO-8859-1 encoding is tried.
+	 * 
+	 * @param buffer
+	 *            buffer to pull raw bytes from.
+	 * @param start
+	 *            start position in buffer
+	 * @param end
+	 *            one position past the last location within the buffer to take
+	 *            data from.
+	 * @return a string representation of the range <code>[start,end)</code>,
+	 *         after decoding the region through the specified character set.
+	 */
+	public static String decode(final byte[] buffer, final int start,
+			final int end) {
+		return decode(Constants.CHARSET, buffer, start, end);
 	}
 
 	/**
-- 
1.6.3.2.199.g7340d
Robin Rosenberg· Jun 7, 2009, 20:19 UTC · re: Robin Rosenberg · lore

[EGIT PATCH 3/3] Add a ref log reader class

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../tst/org/spearce/jgit/lib/ReflogReaderTest.java |  153 ++++++++++++++++
 .../src/org/spearce/jgit/lib/ReflogReader.java     |  186 ++++++++++++++++++++
 .../src/org/spearce/jgit/lib/Repository.java       |    9 +
 3 files changed, 348 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java
Show changes to 3 files +348 −0

org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java, org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java, org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java

diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java
new file mode 100644
index 0000000..1919477
--- /dev/null
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2009, Robin Rosenberg
+ *
+ * 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.lib;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+import org.spearce.jgit.lib.ReflogReader.Entry;
+
+public class ReflogReaderTest extends RepositoryTestCase {
+
+	static byte[] oneLine = "da85355dfc525c9f6f3927b876f379f46ccf826e 3e7549db262d1e836d9bf0af7e22355468f1717c Robin Rosenberg <robin.rosenberg@dewire.com> 1243028200 +0200\tcommit: Add a toString for debugging to RemoteRefUpdate\n"
+			.getBytes();
+
+	static byte[] twoLine = ("0000000000000000000000000000000000000000 c6734895958052a9dbc396cff4459dc1a25029ab A U Thor <thor@committer.au> 1243028201 -0100\tbranch: Created from rr/renamebranchv4\n"
+			+ "c6734895958052a9dbc396cff4459dc1a25029ab 54794942a18a237c57a80719afed44bb78172b10 Same A U Thor <same.author@example.com> 1243028202 +0100\trebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d\n")
+			.getBytes();
+
+	static byte[] aLine = "1111111111111111111111111111111111111111 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor <thor@committer.au> 1243028201 -0100\tbranch: change to a\n"
+			.getBytes();
+
+	static byte[] masterLine = "2222222222222222222222222222222222222222 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor <thor@committer.au> 1243028201 -0100\tbranch: change to master\n"
+			.getBytes();
+
+	static byte[] headLine = "3333333333333333333333333333333333333333 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor <thor@committer.au> 1243028201 -0100\tbranch: change to HEAD\n"
+			.getBytes();
+
+	public void testReadOneLine() throws Exception {
+		setupReflog("logs/refs/heads/master", oneLine);
+
+		ReflogReader reader = new ReflogReader(db, "refs/heads/master");
+		Entry e = reader.getLastEntry();
+		assertEquals(ObjectId
+				.fromString("da85355dfc525c9f6f3927b876f379f46ccf826e"), e
+				.getOldId());
+		assertEquals(ObjectId
+				.fromString("3e7549db262d1e836d9bf0af7e22355468f1717c"), e
+				.getNewId());
+		assertEquals("Robin Rosenberg", e.getWho().getName());
+		assertEquals("robin.rosenberg@dewire.com", e.getWho().getEmailAddress());
+		assertEquals(120, e.getWho().getTimeZoneOffset());
+		assertEquals("2009-05-22T23:36:40", iso(e.getWho().getWhen()));
+		assertEquals("commit", e.getAction());
+		assertEquals("Add a toString for debugging to RemoteRefUpdate", e
+				.getComment());
+	}
+
+	private String iso(Date d) {
+		return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").format(d);
+	}
+
+	public void testReadTwoLine() throws Exception {
+		setupReflog("logs/refs/heads/master", twoLine);
+
+		ReflogReader reader = new ReflogReader(db, "refs/heads/master");
+		List<Entry> reverseEntries = reader.getReverseEntries();
+		Entry e = reverseEntries.get(0);
+		assertEquals(ObjectId
+				.fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e
+				.getOldId());
+		assertEquals(ObjectId
+				.fromString("54794942a18a237c57a80719afed44bb78172b10"), e
+				.getNewId());
+		assertEquals("Same A U Thor", e.getWho().getName());
+		assertEquals("same.author@example.com", e.getWho().getEmailAddress());
+		assertEquals(60, e.getWho().getTimeZoneOffset());
+		assertEquals("2009-05-22T23:36:42", iso(e.getWho().getWhen()));
+		assertEquals("rebase finished", e.getAction());
+		assertEquals(
+				"refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d",
+				e.getComment());
+
+		e = reverseEntries.get(1);
+		assertEquals(ObjectId
+				.fromString("0000000000000000000000000000000000000000"), e
+				.getOldId());
+		assertEquals(ObjectId
+				.fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e
+				.getNewId());
+		assertEquals("A U Thor", e.getWho().getName());
+		assertEquals("thor@committer.au", e.getWho().getEmailAddress());
+		assertEquals(-60, e.getWho().getTimeZoneOffset());
+		assertEquals("2009-05-22T23:36:41", iso(e.getWho().getWhen()));
+		assertEquals("branch", e.getAction());
+		assertEquals("Created from rr/renamebranchv4", e.getComment());
+	}
+
+	public void testReadRightLog() throws Exception {
+		setupReflog("logs/refs/heads/a", aLine);
+		setupReflog("logs/refs/heads/master", masterLine);
+		setupReflog("logs/HEAD", headLine);
+		assertEquals("change to master", db.getReflogReader("master")
+				.getLastEntry().getComment());
+		assertEquals("change to a", db.getReflogReader("a").getLastEntry()
+				.getComment());
+		assertEquals("change to HEAD", db.getReflogReader("HEAD").getLastEntry()
+				.getComment());
+	}
+
+	public void testNoLog() throws Exception {
+		assertEquals(0, db.getReflogReader("master").getReverseEntries().size());
+		assertNull(db.getReflogReader("master").getLastEntry());
+	}
+
+	private void setupReflog(String logName, byte[] data)
+			throws FileNotFoundException, IOException {
+		File logfile = new File(db.getDirectory(), logName);
+		logfile.getParentFile().mkdirs();
+		FileOutputStream fileOutputStream = new FileOutputStream(logfile);
+		fileOutputStream.write(data);
+		fileOutputStream.close();
+
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java b/org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java
new file mode 100644
index 0000000..15591af
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2009, Robin Rosenberg
+ *
+ * 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.lib;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.spearce.jgit.util.NB;
+import org.spearce.jgit.util.RawParseUtils;
+
+/**
+ * Utility for reading reflog entries
+ */
+public class ReflogReader {
+	/**
+	 * Parsed reflog entry
+	 */
+	static public class Entry {
+		Entry(byte[] raw, int pos) {
+			oldId = ObjectId.fromString(raw, pos);
+			if (raw[pos + Constants.OBJECT_ID_LENGTH * 2] != ' ')
+				throw new IllegalArgumentException(
+						"Raw log message does not parse as log entry");
+			newId = ObjectId.fromString(raw, pos + Constants.OBJECT_ID_LENGTH
+					* 2 + 1);
+			if (raw[pos + Constants.OBJECT_ID_LENGTH * 2 * 2 + 1] != ' ')
+				throw new IllegalArgumentException(
+						"Raw log message does not parse as log entry");
+			who = RawParseUtils.parsePersonIdent(raw, pos
+					+ Constants.OBJECT_ID_LENGTH * 2 * 2 + 2);
+			int p0 = RawParseUtils.next(raw, pos + Constants.OBJECT_ID_LENGTH
+					* 2 * 2 + 2, '\t');
+			if (p0 == -1)
+				throw new IllegalArgumentException(
+						"Raw log message does not parse as log entry");
+			int p1 = RawParseUtils.next(raw, p0 + 1, ':');
+			if (p1 == -1)
+				throw new IllegalArgumentException(
+						"Raw log message does not parse as log entry");
+			action = RawParseUtils.decode(raw, p0, p1-1).trim();
+			int p2 = RawParseUtils.nextLF(raw, p1 + 1);
+			if (p2 == -1)
+				throw new IllegalArgumentException(
+						"Raw log message does not parse as log entry");
+			comment = RawParseUtils.decode(raw, p1, p2).trim();
+		}
+
+		/**
+		 * @return the commit id before the change
+		 */
+		public ObjectId getOldId() {
+			return oldId;
+		}
+
+		/**
+		 * @return the commit id after the change
+		 */
+		public ObjectId getNewId() {
+			return newId;
+		}
+
+		/**
+		 * @return user performin the change
+		 */
+		public PersonIdent getWho() {
+			return who;
+		}
+
+		/**
+		 * @return some kind of textual indication of the change made
+		 */
+		public String getAction() {
+			return action;
+		}
+
+		/**
+		 * @return textual description of the change
+		 */
+		public String getComment() {
+			return comment;
+		}
+
+		private ObjectId oldId;
+
+		private ObjectId newId;
+
+		private PersonIdent who;
+
+		private String action;
+
+		private String comment;
+	}
+
+	private File logName;
+
+	ReflogReader(Repository db, String refname) {
+		logName = new File(db.getDirectory(), "logs/" + refname);
+	}
+
+	/**
+	 * Get the last entry in the reflog
+	 * 
+	 * @return the latest reflog entry, or null if no log
+	 * @throws IOException
+	 */
+	public Entry getLastEntry() throws IOException {
+		List<Entry> entries = getReverseEntries(1);
+		return entries.size() > 0 ? entries.get(0) : null;
+	}
+
+	/**
+	 * @return all reflog entries in reverse order
+	 * @throws IOException
+	 */
+	public List<Entry> getReverseEntries() throws IOException {
+		return getReverseEntries(Integer.MAX_VALUE);
+	}
+
+	/**
+	 * @param max
+	 *            max numer of entries to read
+	 * @return all reglog entries in reverse order
+	 * @throws IOException
+	 */
+	public List<Entry> getReverseEntries(int max) throws IOException {
+		FileInputStream fileInputStream;
+		try {
+			fileInputStream = new FileInputStream(logName);
+		} catch (FileNotFoundException e) {
+			return Collections.emptyList();
+		}
+		if (logName.length() > Integer.MAX_VALUE)
+			// implementation limit, will suck with smaller files too
+			throw new IOException("Cannot handle reflog larger than "
+					+ Integer.MAX_VALUE + " bytes");
+		byte[] log = new byte[(int) logName.length()];
+		NB.readFully(fileInputStream, log, 0, log.length);
+		int rs = log.length - 2; // skip terminating \n
+		List<Entry> ret = new ArrayList<Entry>();
+		while (rs >= 0 && max-- > 0) {
+			rs = RawParseUtils.prevLF(log, rs);
+			Entry entry = new Entry(log, rs < 0 ? 0 : rs + 2);
+			ret.add(entry);
+		}
+		return ret;
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index 5def5d3..89dc044 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -1089,4 +1089,13 @@ public String shortenRefName(String refName) {
 			return refName.substring(Constants.R_REMOTES.length());
 		return refName;
 	}
+
+	/**
+	 * @param refName
+	 * @return a {@link ReflogReader} for the refname
+	 * @throws IOException 
+	 */
+	public ReflogReader getReflogReader(String refName) throws IOException {
+		return new ReflogReader(this, getRef(refName).getOrigName());
+	}
 }
-- 
1.6.3.2.199.g7340d
Shawn O. Pearce· Jun 7, 2009, 22:21 UTC · re: Robin Rosenberg · lore

Re: [EGIT PATCH 3/3] Add a ref log reader class

Robin Rosenberg <robin.rosenberg@dewire.com> wrote:
Show 6 quoted lines
> diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java b/org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java
> new file mode 100644
> index 0000000..15591af
> --- /dev/null
> +++ b/org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java
> @@ -0,0 +1,186 @@
...
Show 10 quoted lines
> +public class ReflogReader {
> +	/**
> +	 * Parsed reflog entry
> +	 */
> +	static public class Entry {
> +		Entry(byte[] raw, int pos) {
> +			oldId = ObjectId.fromString(raw, pos);
> +			if (raw[pos + Constants.OBJECT_ID_LENGTH * 2] != ' ')
> +				throw new IllegalArgumentException(
> +						"Raw log message does not parse as log entry");

Please, for the sake of everyone's sanity, increment pos after you read oldId. Then its just raw[pos++] != ' ' here, and the next line for newId is even shorter.

> +			int p1 = RawParseUtils.next(raw, p0 + 1, ':');
> +			if (p1 == -1)
> +				throw new IllegalArgumentException(
> +						"Raw log message does not parse as log entry");

Technically, missing a ':' is legal. Everything after the '\t' is the comment. It may be splittable into an action/comment, it might not be.

Show 6 quoted lines
> +			action = RawParseUtils.decode(raw, p0, p1-1).trim();
> +			int p2 = RawParseUtils.nextLF(raw, p1 + 1);
> +			if (p2 == -1)
> +				throw new IllegalArgumentException(
> +						"Raw log message does not parse as log entry");
> +			comment = RawParseUtils.decode(raw, p1, p2).trim();

trim() on these shouldn't be necessary. If you parse the line right, you can avoid handing the '\t' and the '\n' to the decode, and then whatever is left is what was given to the logger, and we should faithfully return that to the caller. Its unlikely to have unncessary whitespace, so the trim is just wasting CPU looking for it.

Show 9 quoted lines
> +		private ObjectId oldId;
> +
> +		private ObjectId newId;
> +
> +		private PersonIdent who;
> +
> +		private String action;
> +
> +		private String comment;
Style nit: I much prefer fields to appear before the constructor.
> +	}
...
Show 7 quoted lines
> +	public List<Entry> getReverseEntries(int max) throws IOException {
> +		FileInputStream fileInputStream;
> +		try {
> +			fileInputStream = new FileInputStream(logName);
> +		} catch (FileNotFoundException e) {
> +			return Collections.emptyList();
> +		}

Please ensure fileInputStream doesn't leak and is closed before this method block returns.

> +		if (logName.length() > Integer.MAX_VALUE)
> +			// implementation limit, will suck with smaller files too
> +			throw new IOException("Cannot handle reflog larger than "
> +					+ Integer.MAX_VALUE + " bytes");

Style nit: Please wrap this huge block in {}, its easier to read when there are more than one line dangling below.

> +		byte[] log = new byte[(int) logName.length()];

Please use fileInputStream.getChannel().size() as it does an fstat() rather than a stat() on the path.

But I'd rather just read the file backwards, with a RandomAccessFile and what amounts to a reverse version of BufferedInputStream.

> +		NB.readFully(fileInputStream, log, 0, log.length);
> +		int rs = log.length - 2; // skip terminating \n

If the file is currently being appended to, it might not end in '\n'. Something to keep in mind.

-- 
Shawn.
Robin Rosenberg· Jun 7, 2009, 22:45 UTC · re: Shawn O. Pearce · lore

Re: [EGIT PATCH 3/3] Add a ref log reader class

måndag 08 juni 2009 00:21:54 skrev "Shawn O. Pearce" <spearce@spearce.org>:
> Robin Rosenberg <robin.rosenberg@dewire.com> wrote:
...
Show 8 quoted lines
> > +			int p1 = RawParseUtils.next(raw, p0 + 1, ':');
> > +			if (p1 == -1)
> > +				throw new IllegalArgumentException(
> > +						"Raw log message does not parse as log entry");
> 
> Technically, missing a ':' is legal.  Everything after the '\t'
> is the comment.  It may be splittable into an action/comment,
> it might not be.

Do you think I should just skip parsing out action? I don't really need it. I can go with everything after tab as one string for my purposes, i.e. reading reflogs in JUnit tests.

As for optimized reading, I'd rather spend my time on something else. Reading reflogs won't likely be a real problem and I think the interface will be stable even if it needs to be optimized.

The other stuff I'll fix.
-- robin
Shawn O. Pearce· Jun 7, 2009, 22:47 UTC · re: Robin Rosenberg · lore

Re: [EGIT PATCH 3/3] Add a ref log reader class

Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:
Show 15 quoted lines
> m?ndag 08 juni 2009 00:21:54 skrev "Shawn O. Pearce" <spearce@spearce.org>:
> > Robin Rosenberg <robin.rosenberg@dewire.com> wrote:
> ...
> > > +			int p1 = RawParseUtils.next(raw, p0 + 1, ':');
> > > +			if (p1 == -1)
> > > +				throw new IllegalArgumentException(
> > > +						"Raw log message does not parse as log entry");
> > 
> > Technically, missing a ':' is legal.  Everything after the '\t'
> > is the comment.  It may be splittable into an action/comment,
> > it might not be.
> 
> Do you think I should just skip parsing out action? I don't really need it. I can
> go with everything after tab as one string for my purposes, i.e. reading reflogs
> in JUnit tests.
Yea, just skip it.
 
Show 5 quoted lines
> As for optimized reading, I'd rather spend my time on something else. Reading
> reflogs won't likely be a real problem and I think the interface will be stable
> even if it needs to be optimized.
> 
> The other stuff I'll fix.
OK, sounds fine to me.

Maybe we should cap the limit at say 20 MiB of log or something, and refuse to read anything more than that, rather than allowing 2 GiB.

Or, since we most likely only care about the tail, if its over 5 MiB, skip through to the end and then read the last 5 MiB, and if we have a partial entry left over at the start of the buffer, ignore it.

-- 
Shawn.
Robin Rosenberg· Jun 8, 2009, 17:28 UTC · re: Shawn O. Pearce · lore

[EGIT PATCH 1/2] Add methods to RawParseUtils for scanning backwards.

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../src/org/spearce/jgit/util/RawParseUtils.java   |   84 +++++++++++++++++++-
 1 files changed, 83 insertions(+), 1 deletions(-)
Show changes to org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java +83 −1
diff --git a/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java b/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java
index 79ebe41..bdd6a11 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java
@@ -321,6 +321,67 @@ public static final int nextLF(final byte[] b, int ptr, final char chrA) {
 	}
 
 	/**
+	 * Locate the first position before a given character.
+	 * 
+	 * @param b
+	 *            buffer to scan.
+	 * @param ptr
+	 *            position within buffer to start looking for chrA at.
+	 * @param chrA
+	 *            character to find.
+	 * @return new position just before chrA, -1 for not found
+	 */
+	public static final int prev(final byte[] b, int ptr, final char chrA) {
+		if (ptr == b.length)
+			--ptr;
+		while (ptr >= 0) {
+			if (b[ptr--] == chrA)
+				return ptr;
+		}
+		return ptr;
+	}
+
+	/**
+	 * Locate the first position before the previous LF.
+	 * <p>
+	 * This method stops on the first '\n' it finds.
+	 * 
+	 * @param b
+	 *            buffer to scan.
+	 * @param ptr
+	 *            position within buffer to start looking for LF at.
+	 * @return new position just before the first LF found, -1 for not found
+	 */
+	public static final int prevLF(final byte[] b, int ptr) {
+		return prev(b, ptr, '\n');
+	}
+
+	/**
+	 * Locate the previous position before either the given character or LF.
+	 * <p>
+	 * This method stops on the first match it finds from either chrA or '\n'.
+	 * 
+	 * @param b
+	 *            buffer to scan.
+	 * @param ptr
+	 *            position within buffer to start looking for chrA or LF at.
+	 * @param chrA
+	 *            character to find.
+	 * @return new position just before the first chrA or LF to be found, -1 for
+	 *         not found
+	 */
+	public static final int prevLF(final byte[] b, int ptr, final char chrA) {
+		if (ptr == b.length)
+			--ptr;
+		while (ptr >= 0) {
+			final byte c = b[ptr--];
+			if (c == chrA || c == '\n')
+				return ptr;
+		}
+		return ptr;
+	}
+
+	/**
 	 * Index the region between <code>[ptr, end)</code> to find line starts.
 	 * <p>
 	 * The returned list is 1 indexed. Index 0 contains
@@ -519,7 +580,28 @@ public static PersonIdent parsePersonIdent(final byte[] raw, final int nameB) {
 	 *         after decoding the region through the specified character set.
 	 */
 	public static String decode(final byte[] buffer) {
-		return decode(Constants.CHARSET, buffer, 0, buffer.length);
+		return decode(buffer, 0, buffer.length);
+	}
+
+	/**
+	 * Decode a buffer under UTF-8, if possible.
+	 * 
+	 * If the byte stream cannot be decoded that way, the platform default is
+	 * tried and if that too fails, the fail-safe ISO-8859-1 encoding is tried.
+	 * 
+	 * @param buffer
+	 *            buffer to pull raw bytes from.
+	 * @param start
+	 *            start position in buffer
+	 * @param end
+	 *            one position past the last location within the buffer to take
+	 *            data from.
+	 * @return a string representation of the range <code>[start,end)</code>,
+	 *         after decoding the region through the specified character set.
+	 */
+	public static String decode(final byte[] buffer, final int start,
+			final int end) {
+		return decode(Constants.CHARSET, buffer, start, end);
 	}
 
 	/**
-- 
1.6.3.2.199.g7340d
Robin Rosenberg· Jun 8, 2009, 17:28 UTC · re: Robin Rosenberg · lore

[EGIT PATCH 2/2] Add a ref log reader class

This reader allows the caller to the reflog entries for a given ref.
Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../tst/org/spearce/jgit/lib/ReflogReaderTest.java |  187 ++++++++++++++++++++
 .../src/org/spearce/jgit/lib/ReflogReader.java     |  181 +++++++++++++++++++
 .../src/org/spearce/jgit/lib/Repository.java       |    9 +
 3 files changed, 377 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java
Show changes to 3 files +377 −0

org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java, org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java, org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java

diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java
new file mode 100644
index 0000000..3e1946e
--- /dev/null
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2009, Robin Rosenberg
+ *
+ * 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.lib;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+import org.spearce.jgit.lib.ReflogReader.Entry;
+
+public class ReflogReaderTest extends RepositoryTestCase {
+
+	static byte[] oneLine = "da85355dfc525c9f6f3927b876f379f46ccf826e 3e7549db262d1e836d9bf0af7e22355468f1717c Robin Rosenberg <robin.rosenberg@dewire.com> 1243028200 +0200\tcommit: Add a toString for debugging to RemoteRefUpdate\n"
+			.getBytes();
+
+	static byte[] twoLine = ("0000000000000000000000000000000000000000 c6734895958052a9dbc396cff4459dc1a25029ab A U Thor <thor@committer.au> 1243028201 -0100\tbranch: Created from rr/renamebranchv4\n"
+			+ "c6734895958052a9dbc396cff4459dc1a25029ab 54794942a18a237c57a80719afed44bb78172b10 Same A U Thor <same.author@example.com> 1243028202 +0100\trebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d\n")
+			.getBytes();
+
+	static byte[] twoLineWithAppendInProgress = ("0000000000000000000000000000000000000000 c6734895958052a9dbc396cff4459dc1a25029ab A U Thor <thor@committer.au> 1243028201 -0100\tbranch: Created from rr/renamebranchv4\n"
+			+ "c6734895958052a9dbc396cff4459dc1a25029ab 54794942a18a237c57a80719afed44bb78172b10 Same A U Thor <same.author@example.com> 1243028202 +0100\trebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d\n"
+			+ "54794942a18a237c57a80719afed44bb78172b10 ")
+			.getBytes();
+
+	static byte[] aLine = "1111111111111111111111111111111111111111 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor <thor@committer.au> 1243028201 -0100\tbranch: change to a\n"
+			.getBytes();
+
+	static byte[] masterLine = "2222222222222222222222222222222222222222 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor <thor@committer.au> 1243028201 -0100\tbranch: change to master\n"
+			.getBytes();
+
+	static byte[] headLine = "3333333333333333333333333333333333333333 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor <thor@committer.au> 1243028201 -0100\tbranch: change to HEAD\n"
+			.getBytes();
+
+	public void testReadOneLine() throws Exception {
+		setupReflog("logs/refs/heads/master", oneLine);
+
+		ReflogReader reader = new ReflogReader(db, "refs/heads/master");
+		Entry e = reader.getLastEntry();
+		assertEquals(ObjectId
+				.fromString("da85355dfc525c9f6f3927b876f379f46ccf826e"), e
+				.getOldId());
+		assertEquals(ObjectId
+				.fromString("3e7549db262d1e836d9bf0af7e22355468f1717c"), e
+				.getNewId());
+		assertEquals("Robin Rosenberg", e.getWho().getName());
+		assertEquals("robin.rosenberg@dewire.com", e.getWho().getEmailAddress());
+		assertEquals(120, e.getWho().getTimeZoneOffset());
+		assertEquals("2009-05-22T23:36:40", iso(e.getWho().getWhen()));
+		assertEquals("commit: Add a toString for debugging to RemoteRefUpdate",
+				e.getComment());
+	}
+
+	private String iso(Date d) {
+		return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").format(d);
+	}
+
+	public void testReadTwoLine() throws Exception {
+		setupReflog("logs/refs/heads/master", twoLine);
+
+		ReflogReader reader = new ReflogReader(db, "refs/heads/master");
+		List<Entry> reverseEntries = reader.getReverseEntries();
+		assertEquals(2, reverseEntries.size());
+		Entry e = reverseEntries.get(0);
+		assertEquals(ObjectId
+				.fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e
+				.getOldId());
+		assertEquals(ObjectId
+				.fromString("54794942a18a237c57a80719afed44bb78172b10"), e
+				.getNewId());
+		assertEquals("Same A U Thor", e.getWho().getName());
+		assertEquals("same.author@example.com", e.getWho().getEmailAddress());
+		assertEquals(60, e.getWho().getTimeZoneOffset());
+		assertEquals("2009-05-22T23:36:42", iso(e.getWho().getWhen()));
+		assertEquals(
+				"rebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d",
+				e.getComment());
+
+		e = reverseEntries.get(1);
+		assertEquals(ObjectId
+				.fromString("0000000000000000000000000000000000000000"), e
+				.getOldId());
+		assertEquals(ObjectId
+				.fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e
+				.getNewId());
+		assertEquals("A U Thor", e.getWho().getName());
+		assertEquals("thor@committer.au", e.getWho().getEmailAddress());
+		assertEquals(-60, e.getWho().getTimeZoneOffset());
+		assertEquals("2009-05-22T23:36:41", iso(e.getWho().getWhen()));
+		assertEquals("branch: Created from rr/renamebranchv4", e.getComment());
+	}
+
+	public void testReadWhileAppendIsInProgress() throws Exception {
+		setupReflog("logs/refs/heads/master", twoLineWithAppendInProgress);
+		ReflogReader reader = new ReflogReader(db, "refs/heads/master");
+		List<Entry> reverseEntries = reader.getReverseEntries();
+		assertEquals(2, reverseEntries.size());
+		Entry e = reverseEntries.get(0);
+		assertEquals(ObjectId
+				.fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e
+				.getOldId());
+		assertEquals(ObjectId
+				.fromString("54794942a18a237c57a80719afed44bb78172b10"), e
+				.getNewId());
+		assertEquals("Same A U Thor", e.getWho().getName());
+		assertEquals("same.author@example.com", e.getWho().getEmailAddress());
+		assertEquals(60, e.getWho().getTimeZoneOffset());
+		assertEquals("2009-05-22T23:36:42", iso(e.getWho().getWhen()));
+		assertEquals(
+				"rebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d",
+				e.getComment());
+		// while similar to testReadTwoLine, we can assume that if we get the last entry
+		// right, everything else is too
+	}
+
+
+	public void testReadRightLog() throws Exception {
+		setupReflog("logs/refs/heads/a", aLine);
+		setupReflog("logs/refs/heads/master", masterLine);
+		setupReflog("logs/HEAD", headLine);
+		assertEquals("branch: change to master", db.getReflogReader("master")
+				.getLastEntry().getComment());
+		assertEquals("branch: change to a", db.getReflogReader("a")
+				.getLastEntry().getComment());
+		assertEquals("branch: change to HEAD", db.getReflogReader("HEAD")
+				.getLastEntry().getComment());
+	}
+
+	public void testNoLog() throws Exception {
+		assertEquals(0, db.getReflogReader("master").getReverseEntries().size());
+		assertNull(db.getReflogReader("master").getLastEntry());
+	}
+
+	private void setupReflog(String logName, byte[] data)
+			throws FileNotFoundException, IOException {
+		File logfile = new File(db.getDirectory(), logName);
+		if (!logfile.getParentFile().mkdirs()
+				&& !logfile.getParentFile().isDirectory()) {
+			throw new IOException(
+					"oops, cannot create the directory for the test reflog file"
+							+ logfile);
+		}
+		FileOutputStream fileOutputStream = new FileOutputStream(logfile);
+		try {
+			fileOutputStream.write(data);
+		} finally {
+			fileOutputStream.close();
+		}
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java b/org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java
new file mode 100644
index 0000000..73a5e0c
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2009, Robin Rosenberg
+ *
+ * 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.lib;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.spearce.jgit.util.NB;
+import org.spearce.jgit.util.RawParseUtils;
+
+/**
+ * Utility for reading reflog entries
+ */
+public class ReflogReader {
+	/**
+	 * Parsed reflog entry
+	 */
+	static public class Entry {
+		private ObjectId oldId;
+
+		private ObjectId newId;
+
+		private PersonIdent who;
+
+		private String comment;
+
+		Entry(byte[] raw, int pos) {
+			oldId = ObjectId.fromString(raw, pos);
+			pos += Constants.OBJECT_ID_LENGTH * 2;
+			if (raw[pos++] != ' ')
+				throw new IllegalArgumentException(
+						"Raw log message does not parse as log entry");
+			newId = ObjectId.fromString(raw, pos);
+			pos += Constants.OBJECT_ID_LENGTH * 2;
+			if (raw[pos++] != ' ') {
+				throw new IllegalArgumentException(
+						"Raw log message does not parse as log entry");
+			}
+			who = RawParseUtils.parsePersonIdent(raw, pos);
+			int p0 = RawParseUtils.next(raw, pos, '\t'); // personident has no
+															// \t
+			if (p0 == -1) {
+				throw new IllegalArgumentException(
+						"Raw log message does not parse as log entry");
+			}
+			int p1 = RawParseUtils.nextLF(raw, p0);
+			if (p1 == -1) {
+				throw new IllegalArgumentException(
+						"Raw log message does not parse as log entry");
+			}
+			comment = RawParseUtils.decode(raw, p0, p1 - 1);
+		}
+
+		/**
+		 * @return the commit id before the change
+		 */
+		public ObjectId getOldId() {
+			return oldId;
+		}
+
+		/**
+		 * @return the commit id after the change
+		 */
+		public ObjectId getNewId() {
+			return newId;
+		}
+
+		/**
+		 * @return user performin the change
+		 */
+		public PersonIdent getWho() {
+			return who;
+		}
+
+		/**
+		 * @return textual description of the change
+		 */
+		public String getComment() {
+			return comment;
+		}
+	}
+
+	private File logName;
+
+	ReflogReader(Repository db, String refname) {
+		logName = new File(db.getDirectory(), "logs/" + refname);
+	}
+
+	/**
+	 * Get the last entry in the reflog
+	 * 
+	 * @return the latest reflog entry, or null if no log
+	 * @throws IOException
+	 */
+	public Entry getLastEntry() throws IOException {
+		List<Entry> entries = getReverseEntries(1);
+		return entries.size() > 0 ? entries.get(0) : null;
+	}
+
+	/**
+	 * @return all reflog entries in reverse order
+	 * @throws IOException
+	 */
+	public List<Entry> getReverseEntries() throws IOException {
+		return getReverseEntries(Integer.MAX_VALUE);
+	}
+
+	/**
+	 * @param max
+	 *            max numer of entries to read
+	 * @return all reflog entries in reverse order
+	 * @throws IOException
+	 */
+	public List<Entry> getReverseEntries(int max) throws IOException {
+		FileInputStream fileInputStream;
+		try {
+			fileInputStream = new FileInputStream(logName);
+		} catch (FileNotFoundException e) {
+			return Collections.emptyList();
+		}
+		try {
+			long logSize = fileInputStream.getChannel().size();
+			if (logSize > Integer.MAX_VALUE) {
+				// implementation limit, will suck with smaller files too
+				throw new IOException("Cannot handle reflog larger than "
+						+ Integer.MAX_VALUE + " bytes");
+			}
+			byte[] log = new byte[(int) logSize];
+			NB.readFully(fileInputStream, log, 0, log.length);
+			int rs = RawParseUtils.prevLF(log, log.length);
+			List<Entry> ret = new ArrayList<Entry>();
+			while (rs >= 0 && max-- > 0) {
+				rs = RawParseUtils.prevLF(log, rs);
+				Entry entry = new Entry(log, rs < 0 ? 0 : rs + 2);
+				ret.add(entry);
+			}
+			return ret;
+		} finally {
+			fileInputStream.close();
+		}
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index 5def5d3..89dc044 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -1089,4 +1089,13 @@ public String shortenRefName(String refName) {
 			return refName.substring(Constants.R_REMOTES.length());
 		return refName;
 	}
+
+	/**
+	 * @param refName
+	 * @return a {@link ReflogReader} for the refname
+	 * @throws IOException 
+	 */
+	public ReflogReader getReflogReader(String refName) throws IOException {
+		return new ReflogReader(this, getRef(refName).getOrigName());
+	}
 }
-- 
1.6.3.2.199.g7340d
Shawn O. Pearce· Jun 12, 2009, 19:52 UTC · re: Robin Rosenberg · lore

Re: [EGIT PATCH 2/2] Add a ref log reader class

Robin Rosenberg <robin.rosenberg@dewire.com> wrote:
Show 10 quoted lines
> This reader allows the caller to the reflog entries for a given ref.
> 
> Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
> ---
>  .../tst/org/spearce/jgit/lib/ReflogReaderTest.java |  187 ++++++++++++++++++++
>  .../src/org/spearce/jgit/lib/ReflogReader.java     |  181 +++++++++++++++++++
>  .../src/org/spearce/jgit/lib/Repository.java       |    9 +
>  3 files changed, 377 insertions(+), 0 deletions(-)
>  create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java
>  create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java
Tests fail, I suspect time zone failure (I am in PST):
  ComparisonFailure: expected:<2009-05-22T[23]:36:40> but was:<2009-05-22T[14]:36:40>
  at org.spearce.jgit.lib.ReflogReaderTest.testReadOneLine(ReflogReaderTest.java:86)
Otherwise, it looks sane to me.
-- 
Shawn.
Robin Rosenberg· Jun 15, 2009, 21:25 UTC · re: Shawn O. Pearce · lore

[EGIT PATCH 1/2] Use a UTC relative time zone for PersonIdent

The timezone used in Git objects is not the user's preferred timezone, but rather a numeric form stating the offset relative to UTC in hours and minuutes. Any special rules like daylight savings is already accounted for and cannot be deduced from the time zone part of the git timestamp.

As an example, a committer in Sweden will have +0100 as his/her timezone in winter and +0200 in summer.

We hereby abandon the attempts to guess a real timezone from the UTC offset. When creating a person ident a real timezone can be used, but it's identifty will be lost when externalizing and only the UTC offset will be left.

Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 .../src/org/spearce/jgit/lib/PersonIdent.java      |   54 ++++++++++++-------
 1 files changed, 34 insertions(+), 20 deletions(-)
Show changes to org.spearce.jgit/src/org/spearce/jgit/lib/PersonIdent.java +34 −20
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/PersonIdent.java b/org.spearce.jgit/src/org/spearce/jgit/lib/PersonIdent.java
index 85435b5..4ed2f71 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/PersonIdent.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/PersonIdent.java
@@ -39,7 +39,9 @@
 
 package org.spearce.jgit.lib;
 
+import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.Locale;
 import java.util.TimeZone;
 
 /**
@@ -228,24 +230,24 @@ public String getEmailAddress() {
 	}
 
 	/**
-	 * @return timestamp
+	 * @return timestamp in the person's local time
 	 */
 	public Date getWhen() {
 		return new Date(when);
 	}
 
 	/**
-	 * @return this person's preferred time zone; null if time zone is unknown.
+	 * @return this person's declared time zone; null if time zone is unknown.
 	 */
 	public TimeZone getTimeZone() {
-		final String[] ids = TimeZone.getAvailableIDs(tzOffset * 60 * 1000);
-		if (ids.length == 0)
-			return null;
-		return TimeZone.getTimeZone(ids[0]);
+		StringBuffer tzId = new StringBuffer(8);
+		tzId.append("GMT");
+		appendTimezone(tzId);
+		return TimeZone.getTimeZone(tzId.toString());
 	}
 
 	/**
-	 * @return this person's preferred time zone as minutes east of UTC. If the
+	 * @return this person's declared time zone as minutes east of UTC. If the
 	 *         timezone is to the west of UTC it is negative.
 	 */
 	public int getTimeZoneOffset() {
@@ -273,6 +275,17 @@ public boolean equals(final Object o) {
 	 */
 	public String toExternalString() {
 		final StringBuffer r = new StringBuffer();
+		r.append(getName());
+		r.append(" <");
+		r.append(getEmailAddress());
+		r.append("> ");
+		r.append(when / 1000);
+		r.append(' ');
+		appendTimezone(r);
+		return r.toString();
+	}
+
+	private void appendTimezone(final StringBuffer r) {
 		int offset = tzOffset;
 		final char sign;
 		final int offsetHours;
@@ -288,12 +301,6 @@ public String toExternalString() {
 		offsetHours = offset / 60;
 		offsetMins = offset % 60;
 
-		r.append(getName());
-		r.append(" <");
-		r.append(getEmailAddress());
-		r.append("> ");
-		r.append(when / 1000);
-		r.append(' ');
 		r.append(sign);
 		if (offsetHours < 10) {
 			r.append('0');
@@ -303,25 +310,32 @@ public String toExternalString() {
 			r.append('0');
 		}
 		r.append(offsetMins);
-		return r.toString();
 	}
 
 	public String toString() {
 		final StringBuffer r = new StringBuffer();
-		int minutes;
-
-		minutes = tzOffset < 0 ? -tzOffset : tzOffset;
-		minutes = (minutes / 100) * 60 + (minutes % 100);
-		minutes = tzOffset < 0 ? -minutes : minutes;
 
 		r.append("PersonIdent[");
 		r.append(getName());
 		r.append(", ");
 		r.append(getEmailAddress());
 		r.append(", ");
-		r.append(new Date(when + minutes * 60));
+		r.append(formatTime(new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US)));
 		r.append("]");
 
 		return r.toString();
 	}
+
+	/**
+	 * Format the time represented by this object using the give
+	 * SimpleDateFormat.
+	 * 
+	 * @param simpleDateFormat
+	 * @return a formatted time stamp.
+	 */
+	@SuppressWarnings("boxing")
+	public String formatTime(SimpleDateFormat simpleDateFormat) {
+		simpleDateFormat.setTimeZone(getTimeZone());
+		return simpleDateFormat.format(when);
+	}
 }
-- 
1.6.3.2.199.g7340d
Robin Rosenberg· Jun 15, 2009, 21:25 UTC · re: Robin Rosenberg · lore

[EGIT PATCH 2/2] Add a ref log reader class

This reader allows the caller to the reflog entries for a given ref.
Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
 ...pearce.jgit--All-External-Tests (Java 6).launch |   27 ++--
 .../tst/org/spearce/jgit/lib/ReflogReaderTest.java |  165 +++++++++++++++++
 .../org/spearce/jgit/lib/RepositoryTestCase.java   |   18 ++
 .../src/org/spearce/jgit/lib/ReflogReader.java     |  187 ++++++++++++++++++++
 .../src/org/spearce/jgit/lib/Repository.java       |   14 ++
 .../src/org/spearce/jgit/util/RawParseUtils.java   |   46 +++++
 6 files changed, 445 insertions(+), 12 deletions(-)
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java
Show changes to 6 files +445 −12

org.spearce.jgit.test/org.spearce.jgit--All-External-Tests, org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java, org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java, org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java, org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java, org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java

diff --git a/org.spearce.jgit.test/org.spearce.jgit--All-External-Tests (Java 6).launch b/org.spearce.jgit.test/org.spearce.jgit--All-External-Tests (Java 6).launch
index 73a2f63..1e16df2 100644
--- a/org.spearce.jgit.test/org.spearce.jgit--All-External-Tests (Java 6).launch	
+++ b/org.spearce.jgit.test/org.spearce.jgit--All-External-Tests (Java 6).launch	
@@ -1,21 +1,24 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
-<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
-<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
-<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/org.spearce.jgit.test/exttst"/>
+</listAttribute>
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
-<listEntry value="4"/>
+<listEntry value="2"/>
 </listAttribute>
-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.spearce.jgit.test"/>
-<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+<mapAttribute key="org.eclipse.debug.core.environmentVariables">
+<mapEntry key="TZ" value="America/Los_Angeles"/>
+</mapAttribute>
 <listAttribute key="org.eclipse.debug.ui.favoriteGroups">
-<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
 <listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
-</listAttribute>
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/org.spearce.jgit.test"/>
+<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
 </listAttribute>
 <stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=org.spearce.jgit.test/exttst"/>
-<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
+<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
 <stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit3"/>
+<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.spearce.jgit.test"/>
 </launchConfiguration>
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java
new file mode 100644
index 0000000..93fcf9d
--- /dev/null
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ReflogReaderTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2009, Robin Rosenberg
+ *
+ * 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.lib;
+
+import java.text.SimpleDateFormat;
+import java.util.List;
+
+import org.spearce.jgit.lib.ReflogReader.Entry;
+
+public class ReflogReaderTest extends RepositoryTestCase {
+
+	static byte[] oneLine = "da85355dfc525c9f6f3927b876f379f46ccf826e 3e7549db262d1e836d9bf0af7e22355468f1717c A O Thor Too <authortoo@wri.tr> 1243028200 +0200\tcommit: Add a toString for debugging to RemoteRefUpdate\n"
+			.getBytes();
+
+	static byte[] twoLine = ("0000000000000000000000000000000000000000 c6734895958052a9dbc396cff4459dc1a25029ab A U Thor <thor@committer.au> 1243028201 -0100\tbranch: Created from rr/renamebranchv4\n"
+			+ "c6734895958052a9dbc396cff4459dc1a25029ab 54794942a18a237c57a80719afed44bb78172b10 Same A U Thor <same.author@example.com> 1243028202 +0100\trebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d\n")
+			.getBytes();
+
+	static byte[] twoLineWithAppendInProgress = ("0000000000000000000000000000000000000000 c6734895958052a9dbc396cff4459dc1a25029ab A U Thor <thor@committer.au> 1243028201 -0100\tbranch: Created from rr/renamebranchv4\n"
+			+ "c6734895958052a9dbc396cff4459dc1a25029ab 54794942a18a237c57a80719afed44bb78172b10 Same A U Thor <same.author@example.com> 1243028202 +0100\trebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d\n"
+			+ "54794942a18a237c57a80719afed44bb78172b10 ")
+			.getBytes();
+
+	static byte[] aLine = "1111111111111111111111111111111111111111 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor <thor@committer.au> 1243028201 -0100\tbranch: change to a\n"
+			.getBytes();
+
+	static byte[] masterLine = "2222222222222222222222222222222222222222 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor <thor@committer.au> 1243028201 -0100\tbranch: change to master\n"
+			.getBytes();
+
+	static byte[] headLine = "3333333333333333333333333333333333333333 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor <thor@committer.au> 1243028201 -0100\tbranch: change to HEAD\n"
+			.getBytes();
+
+	public void testReadOneLine() throws Exception {
+		setupReflog("logs/refs/heads/master", oneLine);
+
+		ReflogReader reader = new ReflogReader(db, "refs/heads/master");
+		Entry e = reader.getLastEntry();
+		assertEquals(ObjectId
+				.fromString("da85355dfc525c9f6f3927b876f379f46ccf826e"), e
+				.getOldId());
+		assertEquals(ObjectId
+				.fromString("3e7549db262d1e836d9bf0af7e22355468f1717c"), e
+				.getNewId());
+		assertEquals("A O Thor Too", e.getWho().getName());
+		assertEquals("authortoo@wri.tr", e.getWho().getEmailAddress());
+		assertEquals(120, e.getWho().getTimeZoneOffset());
+		assertEquals("2009-05-22T23:36:40", iso(e.getWho()));
+		assertEquals("commit: Add a toString for debugging to RemoteRefUpdate",
+				e.getComment());
+	}
+
+	private String iso(PersonIdent id) {
+		return id.formatTime(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"));
+	}
+
+	public void testReadTwoLine() throws Exception {
+		setupReflog("logs/refs/heads/master", twoLine);
+
+		ReflogReader reader = new ReflogReader(db, "refs/heads/master");
+		List<Entry> reverseEntries = reader.getReverseEntries();
+		assertEquals(2, reverseEntries.size());
+		Entry e = reverseEntries.get(0);
+		assertEquals(ObjectId
+				.fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e
+				.getOldId());
+		assertEquals(ObjectId
+				.fromString("54794942a18a237c57a80719afed44bb78172b10"), e
+				.getNewId());
+		assertEquals("Same A U Thor", e.getWho().getName());
+		assertEquals("same.author@example.com", e.getWho().getEmailAddress());
+		assertEquals(60, e.getWho().getTimeZoneOffset());
+		assertEquals("2009-05-22T22:36:42", iso(e.getWho()));
+		assertEquals(
+				"rebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d",
+				e.getComment());
+
+		e = reverseEntries.get(1);
+		assertEquals(ObjectId
+				.fromString("0000000000000000000000000000000000000000"), e
+				.getOldId());
+		assertEquals(ObjectId
+				.fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e
+				.getNewId());
+		assertEquals("A U Thor", e.getWho().getName());
+		assertEquals("thor@committer.au", e.getWho().getEmailAddress());
+		assertEquals(-60, e.getWho().getTimeZoneOffset());
+		assertEquals("2009-05-22T20:36:41", iso(e.getWho()));
+		assertEquals("branch: Created from rr/renamebranchv4", e.getComment());
+	}
+
+	public void testReadWhileAppendIsInProgress() throws Exception {
+		setupReflog("logs/refs/heads/master", twoLineWithAppendInProgress);
+		ReflogReader reader = new ReflogReader(db, "refs/heads/master");
+		List<Entry> reverseEntries = reader.getReverseEntries();
+		assertEquals(2, reverseEntries.size());
+		Entry e = reverseEntries.get(0);
+		assertEquals(ObjectId
+				.fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e
+				.getOldId());
+		assertEquals(ObjectId
+				.fromString("54794942a18a237c57a80719afed44bb78172b10"), e
+				.getNewId());
+		assertEquals("Same A U Thor", e.getWho().getName());
+		assertEquals("same.author@example.com", e.getWho().getEmailAddress());
+		assertEquals(60, e.getWho().getTimeZoneOffset());
+		assertEquals("2009-05-22T22:36:42", iso(e.getWho()));
+		assertEquals(
+				"rebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d",
+				e.getComment());
+		// while similar to testReadTwoLine, we can assume that if we get the last entry
+		// right, everything else is too
+	}
+
+
+	public void testReadRightLog() throws Exception {
+		setupReflog("logs/refs/heads/a", aLine);
+		setupReflog("logs/refs/heads/master", masterLine);
+		setupReflog("logs/HEAD", headLine);
+		assertEquals("branch: change to master", db.getReflogReader("master")
+				.getLastEntry().getComment());
+		assertEquals("branch: change to a", db.getReflogReader("a")
+				.getLastEntry().getComment());
+		assertEquals("branch: change to HEAD", db.getReflogReader("HEAD")
+				.getLastEntry().getComment());
+	}
+
+	public void testNoLog() throws Exception {
+		assertEquals(0, db.getReflogReader("master").getReverseEntries().size());
+		assertNull(db.getReflogReader("master").getLastEntry());
+	}
+}
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java
index 3b03ac1..3837ea9 100644
--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java
@@ -40,6 +40,7 @@
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -348,4 +349,21 @@ public void run() {
 		return newRepo;
 	}
 
+	protected void setupReflog(String logName, byte[] data)
+			throws FileNotFoundException, IOException {
+				File logfile = new File(db.getDirectory(), logName);
+				if (!logfile.getParentFile().mkdirs()
+						&& !logfile.getParentFile().isDirectory()) {
+					throw new IOException(
+							"oops, cannot create the directory for the test reflog file"
+									+ logfile);
+				}
+				FileOutputStream fileOutputStream = new FileOutputStream(logfile);
+				try {
+					fileOutputStream.write(data);
+				} finally {
+					fileOutputStream.close();
+				}
+			}
+
 }
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java b/org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java
new file mode 100644
index 0000000..081b52c
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/ReflogReader.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2009, Robin Rosenberg
+ *
+ * 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.lib;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.spearce.jgit.util.NB;
+import org.spearce.jgit.util.RawParseUtils;
+
+/**
+ * Utility for reading reflog entries
+ */
+public class ReflogReader {
+	/**
+	 * Parsed reflog entry
+	 */
+	static public class Entry {
+		private ObjectId oldId;
+
+		private ObjectId newId;
+
+		private PersonIdent who;
+
+		private String comment;
+
+		Entry(byte[] raw, int pos) {
+			oldId = ObjectId.fromString(raw, pos);
+			pos += Constants.OBJECT_ID_LENGTH * 2;
+			if (raw[pos++] != ' ')
+				throw new IllegalArgumentException(
+						"Raw log message does not parse as log entry");
+			newId = ObjectId.fromString(raw, pos);
+			pos += Constants.OBJECT_ID_LENGTH * 2;
+			if (raw[pos++] != ' ') {
+				throw new IllegalArgumentException(
+						"Raw log message does not parse as log entry");
+			}
+			who = RawParseUtils.parsePersonIdentOnly(raw, pos);
+			int p0 = RawParseUtils.next(raw, pos, '\t'); // personident has no
+															// \t
+			if (p0 == -1) {
+				throw new IllegalArgumentException(
+						"Raw log message does not parse as log entry");
+			}
+			int p1 = RawParseUtils.nextLF(raw, p0);
+			if (p1 == -1) {
+				throw new IllegalArgumentException(
+						"Raw log message does not parse as log entry");
+			}
+			comment = RawParseUtils.decode(raw, p0, p1 - 1);
+		}
+
+		/**
+		 * @return the commit id before the change
+		 */
+		public ObjectId getOldId() {
+			return oldId;
+		}
+
+		/**
+		 * @return the commit id after the change
+		 */
+		public ObjectId getNewId() {
+			return newId;
+		}
+
+		/**
+		 * @return user performin the change
+		 */
+		public PersonIdent getWho() {
+			return who;
+		}
+
+		/**
+		 * @return textual description of the change
+		 */
+		public String getComment() {
+			return comment;
+		}
+		
+		@Override
+		public String toString() {
+			return "Entry[" + oldId.name() + ", " + newId.name() + ", " + getWho() + ", "
+					+ getComment() + "]";
+		}
+	}
+
+	private File logName;
+
+	ReflogReader(Repository db, String refname) {
+		logName = new File(db.getDirectory(), "logs/" + refname);
+	}
+
+	/**
+	 * Get the last entry in the reflog
+	 * 
+	 * @return the latest reflog entry, or null if no log
+	 * @throws IOException
+	 */
+	public Entry getLastEntry() throws IOException {
+		List<Entry> entries = getReverseEntries(1);
+		return entries.size() > 0 ? entries.get(0) : null;
+	}
+
+	/**
+	 * @return all reflog entries in reverse order
+	 * @throws IOException
+	 */
+	public List<Entry> getReverseEntries() throws IOException {
+		return getReverseEntries(Integer.MAX_VALUE);
+	}
+
+	/**
+	 * @param max
+	 *            max numer of entries to read
+	 * @return all reflog entries in reverse order
+	 * @throws IOException
+	 */
+	public List<Entry> getReverseEntries(int max) throws IOException {
+		FileInputStream fileInputStream;
+		try {
+			fileInputStream = new FileInputStream(logName);
+		} catch (FileNotFoundException e) {
+			return Collections.emptyList();
+		}
+		try {
+			long logSize = fileInputStream.getChannel().size();
+			if (logSize > Integer.MAX_VALUE) {
+				// implementation limit, will suck with smaller files too
+				throw new IOException("Cannot handle reflog larger than "
+						+ Integer.MAX_VALUE + " bytes");
+			}
+			byte[] log = new byte[(int) logSize];
+			NB.readFully(fileInputStream, log, 0, log.length);
+			int rs = RawParseUtils.prevLF(log, log.length);
+			List<Entry> ret = new ArrayList<Entry>();
+			while (rs >= 0 && max-- > 0) {
+				rs = RawParseUtils.prevLF(log, rs);
+				Entry entry = new Entry(log, rs < 0 ? 0 : rs + 2);
+				ret.add(entry);
+			}
+			return ret;
+		} finally {
+			fileInputStream.close();
+		}
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
index 5def5d3..971215c 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java
@@ -1089,4 +1089,18 @@ public String shortenRefName(String refName) {
 			return refName.substring(Constants.R_REMOTES.length());
 		return refName;
 	}
+
+	/**
+	 * @param refName
+	 * @return a {@link ReflogReader} for the refname, or null if the names ref
+	 *         does not exist.
+	 * @throws IOException 
+	 */
+	public ReflogReader getReflogReader(String refName) throws IOException {
+		Ref ref = getRef(refName);
+		if (ref != null)
+			return new ReflogReader(this, ref.getOrigName());
+		else
+			return null;
+	}
 }
diff --git a/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java b/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java
index bdd6a11..df24190 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java
@@ -569,6 +569,52 @@ public static PersonIdent parsePersonIdent(final byte[] raw, final int nameB) {
 	}
 
 	/**
+	 * Parse a name data (e.g. as within a reflog) into a PersonIdent.
+	 * <p>
+	 * When passing in a value for <code>nameB</code> callers should use the
+	 * return value of {@link #author(byte[], int)} or
+	 * {@link #committer(byte[], int)}, as these methods provide the proper
+	 * position within the buffer.
+	 * 
+	 * @param raw
+	 *            the buffer to parse character data from.
+	 * @param nameB
+	 *            first position of the identity information. This should be the
+	 *            first position after the space which delimits the header field
+	 *            name (e.g. "author" or "committer") from the rest of the
+	 *            identity line.
+	 * @return the parsed identity. Never null.
+	 */
+	public static PersonIdent parsePersonIdentOnly(final byte[] raw, final int nameB) {
+		int stop = nextLF(raw, nameB);
+		int emailB = nextLF(raw, nameB, '<');
+		int emailE = nextLF(raw, emailB, '>');
+		final String name;
+		final String email;
+		if (emailE < stop) {
+			email = decode(raw, emailB, emailE - 1);
+		} else {
+			email = "invalid";
+		}
+		if (emailB < stop)
+			name = decode(raw, nameB, emailB - 2);
+		else
+			name = decode(raw, nameB, stop);
+
+		final MutableInteger ptrout = new MutableInteger();
+		long when;
+		int tz;
+		if (emailE < stop) {
+			when = parseLongBase10(raw, emailE + 1, ptrout);
+			tz = parseTimeZoneOffset(raw, ptrout.value);
+		} else {
+			when = 0;
+			tz = 0;
+		}
+		return new PersonIdent(name, email, when * 1000L, tz);
+	}
+
+	/**
 	 * Decode a buffer under UTF-8, if possible.
 	 *
 	 * If the byte stream cannot be decoded that way, the platform default is tried
-- 
1.6.3.2.199.g7340d
Robin Rosenberg· Jun 15, 2009, 22:21 UTC · re: Robin Rosenberg · lore

Re: [EGIT PATCH 2/2] Add a ref log reader class

måndag 15 juni 2009 23:25:36 skrev Robin Rosenberg <robin.rosenberg@dewire.com>:
> This reader allows the caller to the reflog entries for a given ref.
Oops a typo.

This reader allows the caller to read the reflog entries for a given ref.. I also noted two missing comments in UIText.java. If you want to I can resend the whole series(es).

-- robin
y@vger.kernel.org· Jun 22, 2009, 11:49 UTC · lore

[BUG] apply: test for trailing whitespace & no new line bug

From: SZEDER Gábor <szeder@ira.uka.de>

If a patch adds a new line to the end of a file and this line ends with trailing whitespace but has no newline, then 'git apply --whitespace=fix' does not remove the trailing whitespace from that line.

Signed-off-by: SZEDER Gábor <szeder@ira.uka.de>
---
Noticed last week, but couldn't fix it myself during the weekend.
 t/t4124-apply-ws-rule.sh |   18 ++++++++++++++++++
 1 files changed, 18 insertions(+), 0 deletions(-)
Show changes to t/t4124-apply-ws-rule.sh +18 −0
diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh
index f83322e..0db9b59 100755
--- a/t/t4124-apply-ws-rule.sh
+++ b/t/t4124-apply-ws-rule.sh
@@ -148,4 +148,22 @@ do
 	done
 done
 
+create_patch () {
+	sed -e "s/_/ /" <<-\EOF
+		diff --git a/target b/target
+		index e69de29..8bd6648 100644
+		--- a/target
+		+++ b/target
+		@@ -0,0 +1 @@
+		+A line with trailing whitespace and no newline_
+		\ No newline at end of file
+	EOF
+}
+
+test_expect_failure 'trailing whitespace & no newline at the end of file' '
+	>target &&
+	create_patch | git apply --whitespace=fix - &&
+	git diff --check -- target
+'
+
 test_done
-- 
1.6.3.2.370.g6bb4.dirty
y@vger.kernel.org· Jun 22, 2009, 11:49 UTC · lore

[BUG] apply: test for trailing whitespace & no new line bug

From: SZEDER Gábor <szeder@ira.uka.de>

If a patch adds a new line to the end of a file and this line ends with trailing whitespace but has no newline, then 'git apply --whitespace=fix' does not remove the trailing whitespace from that line.

Signed-off-by: SZEDER Gábor <szeder@ira.uka.de>
---
Noticed last week, but couldn't fix it myself during the weekend.
 t/t4124-apply-ws-rule.sh |   18 ++++++++++++++++++
 1 files changed, 18 insertions(+), 0 deletions(-)
Show changes to t/t4124-apply-ws-rule.sh +18 −0
diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh
index f83322e..0db9b59 100755
--- a/t/t4124-apply-ws-rule.sh
+++ b/t/t4124-apply-ws-rule.sh
@@ -148,4 +148,22 @@ do
 	done
 done
 
+create_patch () {
+	sed -e "s/_/ /" <<-\EOF
+		diff --git a/target b/target
+		index e69de29..8bd6648 100644
+		--- a/target
+		+++ b/target
+		@@ -0,0 +1 @@
+		+A line with trailing whitespace and no newline_
+		\ No newline at end of file
+	EOF
+}
+
+test_expect_failure 'trailing whitespace & no newline at the end of file' '
+	>target &&
+	create_patch | git apply --whitespace=fix - &&
+	git diff --check -- target
+'
+
 test_done
-- 
1.6.3.2.370.g6bb4.dirty
struggleyb.nku@gmail.com· Jul 8, 2010, 15:12 UTC · lore

[PATCH] diff.c: fix a graph output bug

From: Bo Yang <struggleyb.nku@gmail.com>
Ouput the line_prefix ahead of color sequence.
Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
---
 diff.c |    3 +--
 1 files changed, 1 insertions(+), 2 deletions(-)
Show changes to diff.c +1 −2
diff --git a/diff.c b/diff.c
index 3aa695d..17873f3 100644
--- a/diff.c
+++ b/diff.c
@@ -2627,8 +2627,7 @@ static void fill_metainfo(struct strbuf *msg,
 			    (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
 				abbrev = 40;
 		}
-		strbuf_addf(msg, "%s%sindex %s..", set,
-			    line_prefix,
+		strbuf_addf(msg, "%s%sindex %s..", line_prefix, set,
 			    find_unique_abbrev(one->sha1, abbrev));
 		strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev));
 		if (one->mode == two->mode)
-- 
1.7.0.2.273.gc2413.dirty
Junio C Hamano· Jul 9, 2010, 00:15 UTC · re: struggleyb.nku@gmail.com · lore

Re: [PATCH] diff.c: fix a graph output bug

struggleyb.nku@gmail.com writes:
> From: Bo Yang <struggleyb.nku@gmail.com>
>
> Ouput the line_prefix ahead of color sequence.

Justification for the patch, and/or a better bug description ("when run with this combination of options, X gets painted in that color that is not meant to be used for X") please?

Show 27 quoted lines
>
> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
> ---
>  diff.c |    3 +--
>  1 files changed, 1 insertions(+), 2 deletions(-)
>
> diff --git a/diff.c b/diff.c
> index 3aa695d..17873f3 100644
> --- a/diff.c
> +++ b/diff.c
> @@ -2627,8 +2627,7 @@ static void fill_metainfo(struct strbuf *msg,
>  			    (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
>  				abbrev = 40;
>  		}
> -		strbuf_addf(msg, "%s%sindex %s..", set,
> -			    line_prefix,
> +		strbuf_addf(msg, "%s%sindex %s..", line_prefix, set,
>  			    find_unique_abbrev(one->sha1, abbrev));
>  		strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev));
>  		if (one->mode == two->mode)
> -- 
> 1.7.0.2.273.gc2413.dirty
>
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
Junio C Hamano· Jul 9, 2010, 00:57 UTC · re: Junio C Hamano · lore

Re: [PATCH] diff.c: fix a graph output bug

Junio C Hamano <gitster@pobox.com> writes:
Show 11 quoted lines
> struggleyb.nku@gmail.com writes:
>
>> From: Bo Yang <struggleyb.nku@gmail.com>
>>
>> Ouput the line_prefix ahead of color sequence.
>
> Justification for the patch, and/or a better bug description ("when run
> with this combination of options, X gets painted in that color that is not
> meant to be used for X") please?
>>
>> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Perhaps something like this?
From: Bo Yang <struggleyb.nku@gmail.com>
Date: Thu, 8 Jul 2010 23:12:34 +0800
Subject: [PATCH] diff.c: fix a graph output bug

When --graph is in effect, the line-prefix typically has colored graph line segments and ends with reset. The color sequence "set" given to this function is for showing the metainfo part of the patch text and (1) it should not be applied to the graph lines, and (2) it will be reset at the end of line_prefix so it won't be effect anyway.

Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 diff.c |    3 +--
 1 files changed, 1 insertions(+), 2 deletions(-)
 ...
Nazri Ramliy· Jul 9, 2010, 01:04 UTC · re: Junio C Hamano · lore

Re: [PATCH] diff.c: fix a graph output bug

On Fri, Jul 9, 2010 at 8:57 AM, Junio C Hamano <gitster@pobox.com> wrote:
> reset at the end of line_prefix so it won't be effect anyway.
s/effect/affected/  ?
nazri
Junio C Hamano· Jul 9, 2010, 01:09 UTC · re: Nazri Ramliy · lore

Re: [PATCH] diff.c: fix a graph output bug

Nazri Ramliy <ayiehere@gmail.com> writes:
> On Fri, Jul 9, 2010 at 8:57 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> reset at the end of line_prefix so it won't be effect anyway.
>
> s/effect/affected/  ?
Thanks for spotting.  I meant to say "won't be in effect".
Bo Yang· Jul 11, 2010, 06:10 UTC · re: Junio C Hamano · lore

Re: [PATCH] diff.c: fix a graph output bug

Hi Junio,
On Fri, Jul 9, 2010 at 8:57 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 19 quoted lines
> Perhaps something like this?
>
> From: Bo Yang <struggleyb.nku@gmail.com>
> Date: Thu, 8 Jul 2010 23:12:34 +0800
> Subject: [PATCH] diff.c: fix a graph output bug
>
> When --graph is in effect, the line-prefix typically has colored graph
> line segments and ends with reset.  The color sequence "set" given to
> this function is for showing the metainfo part of the patch text and
> (1) it should not be applied to the graph lines, and (2) it will be
> reset at the end of line_prefix so it won't be effect anyway.
>
> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  diff.c |    3 +--
>  1 files changed, 1 insertions(+), 2 deletions(-)
>  ...
>

Hmm, your description is great, thanks a lot! And please apply this patch, thanks!

-- 
Regards!
Bo
----------------------------
My blog: http://blog.morebits.org
Why Git: http://www.whygitisbetterthanx.com/
pclouds@gmail.com· Oct 7, 2010, 04:46 UTC · lore

[PATCH 1/2] setup.c: add enter_work_tree()

From: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>

This function, apart from chdir() to worktree topdir, also invalidates inside_work_tree so is_inside_work_tree() should reflect the new situation correctly.

This function may be used when cwd is outside worktree and the running command supports this case.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 To be used by builtin/grep.c when cwd is outside worktree.
 More explanation in the next patch.
 cache.h |    1 +
 setup.c |    7 +++++++
 2 files changed, 8 insertions(+), 0 deletions(-)
Show changes to 2 files +8 −0

cache.h, setup.c

diff --git a/cache.h b/cache.h
index 2ef2fa3..e71db23 100644
--- a/cache.h
+++ b/cache.h
@@ -414,6 +414,7 @@ extern int set_git_dir(const char *path);
 extern const char *get_git_work_tree(void);
 extern const char *read_gitfile_gently(const char *path);
 extern void set_git_work_tree(const char *tree);
+extern void enter_work_tree();
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
diff --git a/setup.c b/setup.c
index a3b76de..3fbe928 100644
--- a/setup.c
+++ b/setup.c
@@ -208,6 +208,13 @@ int is_inside_work_tree(void)
 	return inside_work_tree;
 }
 
+void enter_work_tree(void)
+{
+	if (chdir(get_git_work_tree()))
+		die("Could not chdir to %s", get_git_work_tree());
+	inside_work_tree = -1;
+}
+
 /*
  * set_work_tree() is only ever called if you set GIT_DIR explicitly.
  * The old behaviour (which we retain here) is to set the work tree root
-- 
1.7.1.rc1.70.g788ca
pclouds@gmail.com· Oct 7, 2010, 04:46 UTC · lore

[PATCH 2/2] worktree: provide better prefix to go back to original cwd

From: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>

When both GIT_DIR and GIT_WORK_TREE are set, if cwd is outside worktree, prefix (the one passed to every builtin commands) will be set to NULL, which means "user stays at worktree topdir", but cwd is not moved to worktree topdir.

As a consequence, command line arguments will be interpreted differently. Assume there is a file named "bar" in foo/.git. A command can expect command line arguments to be "path in repository":

$ GIT_DIR=foo/.git GIT_WORK_TREE=foo git blah bar
or "path in file system":
$ GIT_DIR=foo/.git GIT_WORK_TREE=foo git blah foo/bar

Some commands may want "path in repository" and "path in file system" to be identical. Moreover, output from commands in such situations are relative to worktree topdir (because prefix is NULL), not what users expect. It's just confusing.

This patch does not address that. Instead it prepares some variables to ease access to original cwd in this case. Commands that work well outside worktree should:

 - detect this case, via is_inside_work_tree()
 - enter_work_tree() (not strictly needed, but I believe it would
   less work this way)
 - prepend startup_info->cwd_to_worktree to output so it becomes
   relative to original cwd
 - prepend startup_info->worktree_to_cwd to command line arguments
   if they are meant to access file system
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 I don't care Windows UNC path. And I did not test the case
 where cwd is on C:\ while worktree is on another drive.
 As stated elsewhere, I neglect other cases where cwd may be
 outside worktree. The goal of these patches is to help
 Chris' grep series. If it works out, I'll fix the rest.
 Sorry Chris, I think I have dragged you into worktree setup
 mess.
 builtin/rev-parse.c        |   10 ++++
 cache.h                    |    2 +
 setup.c                    |  119 ++++++++++++++++++++++++++++++++++++++++++--
 t/t1510-worktree-prefix.sh |   52 +++++++++++++++++++
 4 files changed, 179 insertions(+), 4 deletions(-)
 create mode 100755 t/t1510-worktree-prefix.sh
Show changes to 4 files +179 −4

builtin/rev-parse.c, cache.h, setup.c, t/t1510-worktree-prefix.sh

diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index a5a1c86..525610e 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -623,6 +623,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 					puts(prefix);
 				continue;
 			}
+			if (!strcmp(arg, "--cwd-to-worktree")) {
+				if (startup_info->cwd_to_worktree)
+					puts(startup_info->cwd_to_worktree);
+				continue;
+			}
+			if (!strcmp(arg, "--worktree-to-cwd")) {
+				if (startup_info->worktree_to_cwd)
+					puts(startup_info->worktree_to_cwd);
+				continue;
+			}
 			if (!strcmp(arg, "--show-cdup")) {
 				const char *pfx = prefix;
 				if (!is_inside_work_tree()) {
diff --git a/cache.h b/cache.h
index e71db23..be35b8f 100644
--- a/cache.h
+++ b/cache.h
@@ -1111,6 +1111,8 @@ const char *split_cmdline_strerror(int cmdline_errno);
 /* git.c */
 struct startup_info {
 	int have_repository;
+	char *cwd_to_worktree; /* path from original cwd to worktree */
+	char *worktree_to_cwd; /* path from worktree to original cwd */
 };
 extern struct startup_info *startup_info;
 
diff --git a/setup.c b/setup.c
index 3fbe928..1f5117a 100644
--- a/setup.c
+++ b/setup.c
@@ -320,10 +320,120 @@ const char *read_gitfile_gently(const char *path)
 	return path;
 }
 
+/*
+ * Given "foo/bar" and "hey/hello/world", return "../../hey/hello/world/"
+ * Either path1 or path2 can be NULL. All must be canonical.
+ */
+static char *make_path_to_path(const char *path1, const char *path2)
+{
+	int nr_back = 0;
+	int i, pathlen = path2 ? strlen(path2) : 0;
+	char *buf, *p;
+
+	if (path1 && *path1) {
+		nr_back = 1;
+		while (*path1) {
+			if (is_dir_sep(*path1))
+				nr_back++;
+			path1++;
+		}
+	}
+
+	if (!nr_back && !pathlen)
+		return NULL;
+
+	p = buf = xmalloc(3*nr_back + pathlen + 2); /* "../"^nr_back + path2 + '/' + NULL */
+	for (i = 0; i < nr_back; i++) {
+		memcpy(p, "../", 3);
+		p += 3;
+	}
+	if (pathlen) {
+		memcpy(p, path2, pathlen);
+		p += pathlen;
+		if (p[-1] != '/') /* path2 may be c:/, don't put another slash  */
+			*p++ = '/';
+	}
+	*p = '\0';
+	return buf;
+}
+
+/*
+ * Return a prefix if cwd inside worktree, or NULL otherwise.
+ * Also fill startup_info struct.
+ */
+static const char *setup_prefix(const char *cwd)
+{
+	const char *worktree = get_git_work_tree();
+	int len = 0, cwd_len = strlen(cwd), worktree_len = strlen(worktree);
+
+	while (worktree[len] && worktree[len] == cwd[len])
+		len++;
+
+	if (!worktree[len] && !cwd[len]) {
+		if (startup_info) {
+			startup_info->cwd_to_worktree = NULL;
+			startup_info->worktree_to_cwd = NULL;
+		}
+		return NULL;
+	}
+	/* get /foo/, not /foo/baa if /foo/baa1 and /foo/baa2 are given */
+	else if (worktree[len] && cwd[len]) {
+		while (len && !is_dir_sep(worktree[len]))
+			len--;
+		len++;
+
+		/* Worktree and cwd are on different drives? */
+		if (len == 3 && has_dos_drive_prefix(cwd)) {
+			if (startup_info) {
+				/* make_path_to_path will add the trailing slash */
+				startup_info->cwd_to_worktree = make_path_to_path(NULL, worktree);
+				startup_info->worktree_to_cwd = make_path_to_path(NULL, cwd);
+			}
+			return NULL;
+		}
+	}
+	else {
+		if (worktree[len]) {
+			if (!is_dir_sep(worktree[len])) {
+				while (len && !is_dir_sep(worktree[len]))
+					len--;
+			}
+		}
+		else {
+			if (!is_dir_sep(cwd[len])) {
+				while (len && !is_dir_sep(cwd[len]))
+					len--;
+			}
+		}
+		len++;		/* must be a slash here, skip it */
+	}
+
+	if (len < cwd_len && len < worktree_len) {
+		if (startup_info) {
+			startup_info->cwd_to_worktree = make_path_to_path(cwd+len, worktree+len);
+			startup_info->worktree_to_cwd = make_path_to_path(worktree+len, cwd+len);
+		}
+		return NULL;
+	}
+
+	if (startup_info) {
+		if (len < cwd_len) { /* cwd inside worktree */
+			startup_info->cwd_to_worktree = make_path_to_path(cwd+len, NULL);
+			startup_info->worktree_to_cwd = make_path_to_path(NULL, cwd+len);
+		}
+		else {
+			startup_info->cwd_to_worktree = make_path_to_path(NULL, worktree+len);
+			startup_info->worktree_to_cwd = make_path_to_path(worktree+len, NULL);
+		}
+	}
+
+	return len < cwd_len ? cwd+len : NULL;
+}
+
 static const char *setup_explicit_git_dir(const char *gitdirenv,
 				const char *work_tree_env, int *nongit_ok)
 {
-	static char buffer[1024 + 1];
+	static char buffer[PATH_MAX];
 	const char *retval;
 
 	if (PATH_MAX - 40 < strlen(gitdirenv))
@@ -344,9 +454,10 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
 	}
 	if (check_repository_format_gently(nongit_ok))
 		return NULL;
-	retval = get_relative_cwd(buffer, sizeof(buffer) - 1,
-			get_git_work_tree());
-	if (!retval || !*retval)
+	if (!getcwd(buffer, sizeof(buffer)))
+		die_errno("can't find the current directory");
+	retval = setup_prefix(buffer);
+	if (!retval)
 		return NULL;
 	set_git_dir(make_absolute_path(gitdirenv));
 	if (chdir(work_tree_env) < 0)
diff --git a/t/t1510-worktree-prefix.sh b/t/t1510-worktree-prefix.sh
new file mode 100755
index 0000000..3839493
--- /dev/null
+++ b/t/t1510-worktree-prefix.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='test rev-parse --cwd-to-worktree and --worktree-to-cwd'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	mkdir foo bar &&
+	mv .git foo &&
+	mkdir foo/bar &&
+	GIT_DIR=`pwd`/foo/.git &&
+	GIT_WORK_TREE=`pwd`/foo &&
+	export GIT_DIR GIT_WORK_TREE
+'
+
+test_expect_success 'at root' '
+	(
+	cd foo &&
+	git rev-parse --cwd-to-worktree --worktree-to-cwd >result &&
+	: >expected &&
+	test_cmp expected result
+	)
+'
+
+test_expect_success 'cwd inside worktree' '
+	(
+	cd foo/bar &&
+	git rev-parse --cwd-to-worktree --worktree-to-cwd >result &&
+	echo ../ >expected &&
+	echo bar/ >>expected &&
+	test_cmp expected result
+	)
+'
+
+test_expect_success 'cwd outside worktree' '
+	git rev-parse --cwd-to-worktree --worktree-to-cwd >result &&
+	echo foo/ >expected &&
+	echo ../ >>expected &&
+	test_cmp expected result
+'
+
+test_expect_success 'cwd outside worktree (2)' '
+	(
+	cd bar &&
+	git rev-parse --cwd-to-worktree --worktree-to-cwd >result &&
+	echo ../foo/ >expected &&
+	echo ../bar/ >>expected &&
+	test_cmp expected result
+	)
+'
+
+test_done
-- 
1.7.1.rc1.70.g788ca
Nguyen Thai Ngoc Duy· Oct 7, 2010, 05:53 UTC · re: pclouds@gmail.com · lore

Re: [PATCH 2/2] worktree: provide better prefix to go back to original cwd

On Thu, Oct 7, 2010 at 11:46 AM,  <pclouds@gmail.com> wrote:
Show 9 quoted lines
> +               /* Worktree and cwd are on different drives? */
> +               if (len == 3 && has_dos_drive_prefix(cwd)) {
> +                       if (startup_info) {
> +                               /* make_path_to_path will add the trailing slash */
> +                               startup_info->cwd_to_worktree = make_path_to_path(NULL, worktree);
> +                               startup_info->worktree_to_cwd = make_path_to_path(NULL, cwd);
> +                       }
> +                       return NULL;
> +               }
Went for lunch and realized this code is just wrong. Gaahh..
-- 
Duy
Junio C Hamano· Oct 7, 2010, 05:54 UTC · re: pclouds@gmail.com · lore

Re: [PATCH 2/2] worktree: provide better prefix to go back to original cwd

pclouds@gmail.com writes:
Show 6 quoted lines
> From: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>
> When both GIT_DIR and GIT_WORK_TREE are set, if cwd is outside
> worktree, prefix (the one passed to every builtin commands) will be
> set to NULL, which means "user stays at worktree topdir", but cwd is
> not moved to worktree topdir.

Well, NULL traditionally meant "_if_ the cwd is inside the working tree, it is at the top", not "stays at worktree topdir" if you started from elsewhere.

And when cwd is inside the working tree, we do want to feed paths given from the command line after adding the prefix, so that we will see the paths relative to the cwd after we do cd-to-topdir.

Obviously when you run git command that requires you to be in a working tree from outside the working tree, relative or full paths would not make any difference. The command should simply fail.

I suspect that you are introducing a new semantics on top of that traditional semantics; if so, you may want to state it more clearly.

For example:
    When you run git command that requires you to be in a working tree
    from outside the working tree, the command should simply fail.
    When GIT_WORK_TREE is in use, however, it could be argued that we do
    not necessarily have to be in anywhere in the working tree to perform
    a whole-tree operation.  Instead, we could just say the operation
    always runs as if the command was started at the root level of the
    working tree.
    To support this new mode of operation, however, prefix needs to be
    adjusted to allow the program, after running chdir(2) to the root of
    the working tree, to refer to non-absolute paths originally given from
    the command line as relative to the original cwd.  This patch adds a
    mechanism to support that.

I have a queasy feeling about the idea of the second paragraph above, though.

If the original cwd is inside GIT_WORK_TREE, limiting ourselves inside prefix naturally limits the operation to the subdirectory we started from (if the original cwd is at GIT_WORK_TREE, that would make it a whole-tree operation). A natural extension of this idea to limit the operation to the part of the subtree of the working tree we started from is to refuse to work in the case where the original cwd is outside GIT_WORK_TREE (the current implementation of GIT_WORK_TREE may or may not correctly implement it, though---I never use it myself).

Futzing with the prefix that is not a prefix to reach into the working tree from sideways may make the relative paths given from the command line mean something to the current implementation, but it doesn't change the fundamental fact that you are introducing a funny special case where your cwd does _not_ mean anything with respect to which part of the working tree should be affected.

> Some commands may want "path in repository" and "path in file system"
> to be identical. Moreover, output from commands in such situations are
> relative to worktree topdir (because prefix is NULL), not what users
> expect. It's just confusing.

My gut feeling is that this is probably made more confusing, not less, with the change. Perhaps we should instead make sure this fails?

    $ cd /srv/git/git.git
    $ export GIT_DIR=$(pwd)/.git GIT_WORK_TREE=$(pwd)
    $ cd /var/tmp ;# no git stuff there
    $ git status
Nguyen Thai Ngoc Duy· Oct 7, 2010, 07:20 UTC · re: Junio C Hamano · lore

Re: [PATCH 2/2] worktree: provide better prefix to go back to original cwd

On Thu, Oct 7, 2010 at 12:54 PM, Junio C Hamano <gitster@pobox.com> wrote:
Show 23 quoted lines
> pclouds@gmail.com writes:
>
>> From: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>>
>> When both GIT_DIR and GIT_WORK_TREE are set, if cwd is outside
>> worktree, prefix (the one passed to every builtin commands) will be
>> set to NULL, which means "user stays at worktree topdir", but cwd is
>> not moved to worktree topdir.
>
> Well, NULL traditionally meant "_if_ the cwd is inside the working tree,
> it is at the top", not "stays at worktree topdir" if you started from
> elsewhere.
>
> And when cwd is inside the working tree, we do want to feed paths given
> from the command line after adding the prefix, so that we will see the
> paths relative to the cwd after we do cd-to-topdir.
>
> Obviously when you run git command that requires you to be in a working
> tree from outside the working tree, relative or full paths would not make
> any difference.  The command should simply fail.
>
> I suspect that you are introducing a new semantics on top of that
> traditional semantics; if so, you may want to state it more clearly.
Yes. The better semantics _when_ cwd is outside worktree.
Show 28 quoted lines
> For example:
>
>    When you run git command that requires you to be in a working tree
>    from outside the working tree, the command should simply fail.
>
>    When GIT_WORK_TREE is in use, however, it could be argued that we do
>    not necessarily have to be in anywhere in the working tree to perform
>    a whole-tree operation.  Instead, we could just say the operation
>    always runs as if the command was started at the root level of the
>    working tree.
>
>    To support this new mode of operation, however, prefix needs to be
>    adjusted to allow the program, after running chdir(2) to the root of
>    the working tree, to refer to non-absolute paths originally given from
>    the command line as relative to the original cwd.  This patch adds a
>    mechanism to support that.
>
> I have a queasy feeling about the idea of the second paragraph above,
> though.
>
> If the original cwd is inside GIT_WORK_TREE, limiting ourselves inside
> prefix naturally limits the operation to the subdirectory we started from
> (if the original cwd is at GIT_WORK_TREE, that would make it a whole-tree
> operation).  A natural extension of this idea to limit the operation to
> the part of the subtree of the working tree we started from is to refuse
> to work in the case where the original cwd is outside GIT_WORK_TREE (the
> current implementation of GIT_WORK_TREE may or may not correctly implement
> it, though---I never use it myself).

I tend to think that as we go up to worktree's root, prefix is shorten and the operation area is widen. When cwd is at worktree's, we operate on full worktree. If it goes up one level higher, the operation area remains full worktree (but not everything under cwd because cwd now can have non-worktree directories).

Show 6 quoted lines
> Futzing with the prefix that is not a prefix to reach into the working
> tree from sideways may make the relative paths given from the command line
> mean something to the current implementation, but it doesn't change the
> fundamental fact that you are introducing a funny special case where your
> cwd does _not_ mean anything with respect to which part of the working
> tree should be affected.

It would mean full worktree is affected. But not full cwd. In other words, in set theory, it's a union of cwd and worktree.

Show 9 quoted lines
>> Some commands may want "path in repository" and "path in file system"
>> to be identical. Moreover, output from commands in such situations are
>> relative to worktree topdir (because prefix is NULL), not what users
>> expect. It's just confusing.
>
> My gut feeling is that this is probably made more confusing, not less,
> with the change.  Perhaps we should instead make sure this fails?
>
>    $ cd /srv/git/git.git
Err.. you did not mean bare repo, did you?
>    $ export GIT_DIR=$(pwd)/.git GIT_WORK_TREE=$(pwd)
>    $ cd /var/tmp ;# no git stuff there
>    $ git status

I think it used to fail some time ago. Though I see nothing wrong if that command works. And it'd be better if it outputs relative to /var/tmp rather than /srv/git/git.git. I don't use this a lot but sometimes I export GIT_DIR/GIT_WORK_TREE, cd around then do "git add /srv/git/git.git/blah". To me, it should work. I may stand outside worktree, but I give input inside worktree.

-- 
Duy
Junio C Hamano· Oct 7, 2010, 14:56 UTC · re: Nguyen Thai Ngoc Duy · lore

Re: [PATCH 2/2] worktree: provide better prefix to go back to original cwd

Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
Show 17 quoted lines
> On Thu, Oct 7, 2010 at 12:54 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> pclouds@gmail.com writes:
>> ...
>> If the original cwd is inside GIT_WORK_TREE, limiting ourselves inside
>> prefix naturally limits the operation to the subdirectory we started from
>> (if the original cwd is at GIT_WORK_TREE, that would make it a whole-tree
>> operation).  A natural extension of this idea to limit the operation to
>> the part of the subtree of the working tree we started from is to refuse
>> to work in the case where the original cwd is outside GIT_WORK_TREE (the
>> current implementation of GIT_WORK_TREE may or may not correctly implement
>> it, though---I never use it myself).
>
> I tend to think that as we go up to worktree's root, prefix is shorten
> and the operation area is widen. When cwd is at worktree's, we operate
> on full worktree. If it goes up one level higher, the operation area
> remains full worktree (but not everything under cwd because cwd now
> can have non-worktree directories).

I have a feeling that you did not understand my /srv/git/git.git (no, it is not a bare repository) vs /var/tmp example.

I think it makes the new semantics much less yucky if the special case is limited to "the working tree is a subdirectory somewhere under cwd". But does your patch check and notice that /var/tmp is not above the working tree and they are completely unrelated?

Nguyen Thai Ngoc Duy· Oct 7, 2010, 15:11 UTC · re: Junio C Hamano · lore

Re: [PATCH 2/2] worktree: provide better prefix to go back to original cwd

On Thu, Oct 7, 2010 at 9:56 PM, Junio C Hamano <gitster@pobox.com> wrote:
Show 28 quoted lines
> Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
>
>> On Thu, Oct 7, 2010 at 12:54 PM, Junio C Hamano <gitster@pobox.com> wrote:
>>> pclouds@gmail.com writes:
>>> ...
>>> If the original cwd is inside GIT_WORK_TREE, limiting ourselves inside
>>> prefix naturally limits the operation to the subdirectory we started from
>>> (if the original cwd is at GIT_WORK_TREE, that would make it a whole-tree
>>> operation).  A natural extension of this idea to limit the operation to
>>> the part of the subtree of the working tree we started from is to refuse
>>> to work in the case where the original cwd is outside GIT_WORK_TREE (the
>>> current implementation of GIT_WORK_TREE may or may not correctly implement
>>> it, though---I never use it myself).
>>
>> I tend to think that as we go up to worktree's root, prefix is shorten
>> and the operation area is widen. When cwd is at worktree's, we operate
>> on full worktree. If it goes up one level higher, the operation area
>> remains full worktree (but not everything under cwd because cwd now
>> can have non-worktree directories).
>
> I have a feeling that you did not understand my /srv/git/git.git (no, it
> is not a bare repository) vs /var/tmp example.
>
> I think it makes the new semantics much less yucky if the special case is
> limited to "the working tree is a subdirectory somewhere under cwd".  But
> does your patch check and notice that /var/tmp is not above the working
> tree and they are completely unrelated?
>

OK I see it now. I think my patch deals with completely unrelated worktree/cwd just fine (both cwd-to-worktree and worktree-to-cwd contain a few "../").

There is however a case where "git status" on separate cwd/worktree might make sense. Suppose a master repo a/.git has two submodules a/b/.git and a/c/.git. If user stands in a/c and want to do git-status on all repos (let's skip how it finds out a/.git is the master repo), it would make sense to display paths, including ones from a/b/.git, relative to a/c (which is cwd and completely unrelated to a/b/.git).

-- 
Duy
Michał Pomorski· Nov 23, 2010, 02:32 UTC · lore

[PATCH] Corrected cmitmode set in the right lower window when initiating a diff.

Under certain circumstances it caused an error: bad text index "a:_number_" in treeopendir: -Start gitk; -Select the tree radio button in the lower right window; -Choose a diff from the context menu in the main window (this->selected or vice versa) -Observe the lower right window: a file list is displayed, but the radio button 'tree' remains selected -Select one of the files listed. This should cause the error.

Because the variable cmitmode remains in the 'tree' setting, gitk is trying to calculate a tree view selection.
This patch corrects this by setting the cmitmode variable to 'patch' each time a diff is selected.
---
 gitk-git/gitk |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)
Show changes to gitk-git/gitk +2 −1
diff --git a/gitk-git/gitk b/gitk-git/gitk
index 1b0e09a..5d76de4 100644
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -7330,8 +7330,9 @@ proc mergediff {id} {
 }
 
 proc startdiff {ids} {
-    global treediffs diffids treepending diffmergeid nullid nullid2
+    global treediffs diffids treepending diffmergeid nullid nullid2 cmitmode
 
+    set cmitmode patch
     settabs 1
     set diffids $ids
     catch {unset diffmergeid}
-- 
1.7.3.2
Andrew Garber· Apr 7, 2011, 06:01 UTC · lore

[PATCH] git-p4: replace each tab with 8 spaces for consistency

Note that the majority of git-p4 uses spaces, not tabs, for indentation. Consistent indentation is critical to the functionality of Python scripts: mixing tabs and spaces in Python can lead to hard-to-find bugs.

Andrew Garber (1):
  git-p4: replace each tab with 8 spaces for consistency
 contrib/fast-import/git-p4 |   64 ++++++++++++++++++++++----------------------
 1 files changed, 32 insertions(+), 32 deletions(-)
-- 
1.7.3.4
Junio C Hamano· Apr 7, 2011, 07:36 UTC · re: Andrew Garber · lore

Re: [PATCH] git-p4: replace each tab with 8 spaces for consistency

Andrew Garber <andrew@andrewgarber.com> writes:
> Note that the majority of git-p4 uses spaces, not tabs, for indentation.
> Consistent indentation is critical to the functionality of Python
> scripts: mixing tabs and spaces in Python can lead to hard-to-find bugs.
Mixing tabs and spaces can lead to hard-to-find bugs, I agree.
I just threw this in to my .git/info/attributes:
    contrib/fast-import/git-p4 whitespace=!indent,tail,tab-in-indent
and then did this:
    $ >contrib/fast-import/git-p4
    $ git diff -R | git apply --whitespace=fix
    $ git diff
The changes I get out of the last step seem to exactly match your patch.
Thanks.
Phil Hord· Apr 7, 2011, 16:35 UTC · re: Junio C Hamano · lore

Re: [PATCH] git-p4: replace each tab with 8 spaces for consistency

On 04/07/2011 03:36 AM, Junio C Hamano wrote:
Show 13 quoted lines
> Mixing tabs and spaces can lead to hard-to-find bugs, I agree.
>
> I just threw this in to my .git/info/attributes:
>
>     contrib/fast-import/git-p4 whitespace=!indent,tail,tab-in-indent
>
> and then did this:
>
>     $ >contrib/fast-import/git-p4
>     $ git diff -R | git apply --whitespace=fix
>     $ git diff
>
> The changes I get out of the last step seem to exactly match your patch.
Junio!  Thanks for that recipe.

I suspected there must be some way to employ git-apply's whitespace=fix feature to ws-normalize my code, but I was clueless how to do that. I wound up cobbling together a python script that reimplements the same fixes (hopefully).

I wish this (the git-apply trick) was documented somewhere and/or made more prominent. Is it, and I have also overlooked it?

This is what I want:
   git fix-whitespace             # fix new ws errors in workdir
   git fix-whitespace --all       # fix all ws errors in workdir
   git fix-whitespace foo         # fix new ws errors since foo
   git fix-whitespace --cached    # fix new ws errors in the index
   etc.
Is this already implemented somewhere in the git porcelain?
Phil
Junio C Hamano· Apr 7, 2011, 18:45 UTC · re: Phil Hord · lore

Re: [PATCH] git-p4: replace each tab with 8 spaces for consistency

Phil Hord <hordp@cisco.com> writes:
Show 7 quoted lines
> This is what I want:
>
>    git fix-whitespace             # fix new ws errors in workdir
>    git fix-whitespace --all       # fix all ws errors in workdir
>    git fix-whitespace foo         # fix new ws errors since foo
>    git fix-whitespace --cached    # fix new ws errors in the index
>    etc.

If you understood the example I gave, you should be able to figure all of these out. In the most general case, you would come up with a way to generate a diff that represents the change you want to fix (e.g. "new errors" would be comparing the HEAD and the working tree), reverse apply the diff and then forward apply the same diff with --whitespace=fix, but often you can omit "reverse apply the diff" step by finding an appropriate combination between HEAD/index/working tree.

I long time wrote this alias
[alias]
        wsadd = "!sh -c 'git diff -- \"$@\" | git apply --cached --whitespace=fix;\
                git co -- ${1-.} \"$@\"' -"

that acts as if you are saying "git add $path" while fixing the whitespace errors I introduced in the working tree version.

Andrew Garber· Apr 7, 2011, 06:01 UTC · lore

[PATCH] git-p4: replace each tab with 8 spaces for consistency

Note that the majority of git-p4 uses spaces, not tabs, for indentation. Consistent indentation is critical to the functionality of Python scripts: mixing tabs and spaces in Python can lead to hard-to-find bugs.

Signed-off-by: Andrew Garber <andrew@andrewgarber.com>
---
 contrib/fast-import/git-p4 |   64 ++++++++++++++++++++++----------------------
 1 files changed, 32 insertions(+), 32 deletions(-)
Show changes to contrib/fast-import/git-p4 +32 −32
diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
index 3881515..78e5b3a 100755
--- a/contrib/fast-import/git-p4
+++ b/contrib/fast-import/git-p4
@@ -222,10 +222,10 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
     try:
         while True:
             entry = marshal.load(p4.stdout)
-	    if cb is not None:
-		cb(entry)
-	    else:
-		result.append(entry)
+            if cb is not None:
+                cb(entry)
+            else:
+                result.append(entry)
     except EOFError:
         pass
     exitCode = p4.wait()
@@ -449,8 +449,8 @@ def p4ChangesForPaths(depotPaths, changeRange):
 
     changes = {}
     for line in output:
-	changeNum = int(line.split(" ")[1])
-	changes[changeNum] = True
+        changeNum = int(line.split(" ")[1])
+        changes[changeNum] = True
 
     changelist = changes.keys()
     changelist.sort()
@@ -1033,10 +1033,10 @@ class P4Sync(Command):
     # - helper for streamP4Files
 
     def streamOneP4File(self, file, contents):
-	if file["type"] == "apple":
-	    print "\nfile %s is a strange apple file that forks. Ignoring" % \
-		file['depotFile']
-	    return
+        if file["type"] == "apple":
+            print "\nfile %s is a strange apple file that forks. Ignoring" % \
+                file['depotFile']
+            return
 
         relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
         relPath = self.wildcard_decode(relPath)
@@ -1085,22 +1085,22 @@ class P4Sync(Command):
     # handle another chunk of streaming data
     def streamP4FilesCb(self, marshalled):
 
-	if marshalled.has_key('depotFile') and self.stream_have_file_info:
-	    # start of a new file - output the old one first
-	    self.streamOneP4File(self.stream_file, self.stream_contents)
-	    self.stream_file = {}
-	    self.stream_contents = []
-	    self.stream_have_file_info = False
+        if marshalled.has_key('depotFile') and self.stream_have_file_info:
+            # start of a new file - output the old one first
+            self.streamOneP4File(self.stream_file, self.stream_contents)
+            self.stream_file = {}
+            self.stream_contents = []
+            self.stream_have_file_info = False
 
-	# pick up the new file information... for the
-	# 'data' field we need to append to our array
-	for k in marshalled.keys():
-	    if k == 'data':
-		self.stream_contents.append(marshalled['data'])
-	    else:
-		self.stream_file[k] = marshalled[k]
+        # pick up the new file information... for the
+        # 'data' field we need to append to our array
+        for k in marshalled.keys():
+            if k == 'data':
+                self.stream_contents.append(marshalled['data'])
+            else:
+                self.stream_file[k] = marshalled[k]
 
-	self.stream_have_file_info = True
+        self.stream_have_file_info = True
 
     # Stream directly from "p4 files" into "git fast-import"
     def streamP4Files(self, files):
@@ -1132,14 +1132,14 @@ class P4Sync(Command):
             self.stream_contents = []
             self.stream_have_file_info = False
 
-	    # curry self argument
-	    def streamP4FilesCbSelf(entry):
-		self.streamP4FilesCb(entry)
+            # curry self argument
+            def streamP4FilesCbSelf(entry):
+                self.streamP4FilesCb(entry)
 
-	    p4CmdList("-x - print",
-		'\n'.join(['%s#%s' % (f['path'], f['rev'])
+            p4CmdList("-x - print",
+                '\n'.join(['%s#%s' % (f['path'], f['rev'])
                                                   for f in filesToRead]),
-	        cb=streamP4FilesCbSelf)
+                cb=streamP4FilesCbSelf)
 
             # do the last chunk
             if self.stream_file.has_key('depotFile'):
@@ -1148,7 +1148,7 @@ class P4Sync(Command):
     def commit(self, details, files, branch, branchPrefixes, parent = ""):
         epoch = details["time"]
         author = details["user"]
-	self.branchPrefixes = branchPrefixes
+        self.branchPrefixes = branchPrefixes
 
         if self.verbose:
             print "commit into %s" % branch
@@ -1253,7 +1253,7 @@ class P4Sync(Command):
 
         s = ''
         for (key, val) in self.users.items():
-	    s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
+            s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
 
         open(self.getUserCacheFilename(), "wb").write(s)
         self.userMapFromPerforceServer = True
-- 
1.7.3.4
Pavel Volek· Jun 4, 2012, 19:15 UTC · lore

[PATCH/RFC] file import functionality for git-remote-mw

From: Volek Pavel <me@pavelvolek.cz>
The current version of the git-remote-mediawiki supports only import and export of the pages, doesn't support import and export of file attachements which are also exposed by MediaWiki API. This patch adds the functionality to import the last versions of the files and all versions of description pages for these files.
Signed-off-by: Pavel Volek <Pavel.Volek@ensimag.imag.fr>
Signed-off-by: NGUYEN Kim Thuat <Kim-Thuat.Nguyen@ensimag.imag.fr>
Signed-off-by: ROUCHER IGLESIAS Javier <roucherj@ensimag.imag.fr>
Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
 contrib/mw-to-git/git-remote-mediawiki | 128 +++++++++++++++++++++++++++++++--
 1 file changed, 123 insertions(+), 5 deletions(-)
Show changes to contrib/mw-to-git/git-remote-mediawiki +123 −5
diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki
index c18bfa1..4168218 100755
--- a/contrib/mw-to-git/git-remote-mediawiki
+++ b/contrib/mw-to-git/git-remote-mediawiki
@@ -267,6 +267,39 @@ sub get_mw_pages {
 	return values(%pages);
 }
 
+sub get_mw_media_pages {
+	mw_connect_maybe();
+
+	my %pages; # hash on page titles to avoid duplicates
+
+	# get all pages for mediafiles (they are in different namespace)
+	# only one namespace can be queried at the same moment
+	my $mw_pages = $mediawiki->list({
+		action => 'query',
+		list => 'allpages',
+		apnamespace => get_mw_namespace_id("File"),
+		aplimit => 500,
+	});
+	if (!defined($mw_pages)) {
+		print STDERR "fatal: could not get the list of media file pages.\n";
+		print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+		print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
+		exit 1;
+	}
+	foreach my $page (@{$mw_pages}) {
+		$pages{$page->{title}} = $page;
+	}
+	return values(%pages);
+}
+
+sub get_all_mw_pages() {
+	my @pages = get_mw_pages();
+	my @media_pages = get_mw_media_pages();
+	push(@pages,@media_pages);
+
+	return @pages;
+}
+
 sub run_git {
 	open(my $git, "-|:encoding(UTF-8)", "git " . $_[0]);
 	my $res = do { local $/; <$git> };
@@ -300,7 +333,7 @@ my %basetimestamps;
 sub get_last_remote_revision {
 	mw_connect_maybe();
 
-	my @pages = get_mw_pages();
+	my @pages = get_all_mw_pages();
 
 	my $max_rev_num = 0;
 
@@ -403,6 +436,25 @@ sub mw_option {
 	print STDOUT "unsupported\n";
 }
 
+# Returns MediaWiki id for a canonical namespace name. Ex.: "File", "Project".
+sub get_mw_namespace_id() {
+	mw_connect_maybe();
+	my $name = shift;
+	my $query = {
+		action => 'query',
+		meta => 'siteinfo',
+		siprop => 'namespaces',
+	};
+	my $result = $mediawiki->api($query);
+
+	while (my ($id, $ns) = each(%{$result->{query}->{namespaces}})) {
+		if (defined($ns->{canonical}) && ($ns->{canonical} eq $name)) {
+			return $ns->{id};
+		}
+	}
+	die "Namespace $name was not found on MediaWiki.";
+}
+
 sub fetch_mw_revisions_for_page {
 	my $page = shift;
 	my $id = shift;
@@ -461,11 +513,37 @@ sub fetch_mw_revisions {
 	return ($n, @revisions);
 }
 
+sub download_mw_mediafile {
+	my $filename = shift;
+
+	$mediawiki->{config}->{files_url} = $url;
+
+	my $file = $mediawiki->download( { title => $filename } )
+			|| die 'Fatal: Error ' .
+				$mediawiki->{error}->{code} .
+				' from mediwiki: ' . $mediawiki->{error}->{details};
+	if (!defined($file)){
+		print STDERR "\tFile \'$filename\' could not be downloaded.\n";
+		exit 1;
+	} elsif ($file eq "") {
+		print STDERR "\tFile \'$filename\' does not exist on the wiki.\n";
+		exit 1;
+	} else {
+		return $file;
+	}
+}
+
 sub import_file_revision {
 	my $commit = shift;
 	my %commit = %{$commit};
 	my $full_import = shift;
 	my $n = shift;
+	my $mediafile_import = shift;
+	my $mediafile;my %mediafile;
+	if ($mediafile_import) {
+		$mediafile = shift;
+		%mediafile = %{$mediafile};
+	}
 
 	my $title = $commit{title};
 	my $comment = $commit{comment};
@@ -485,6 +563,10 @@ sub import_file_revision {
 	if ($content ne DELETED_CONTENT) {
 		print STDOUT "M 644 inline $title.mw\n";
 		literal_data($content);
+		if ($mediafile_import) {
+			print STDOUT "M 644 inline $mediafile{title}\n";
+			literal_data($mediafile{content});
+		}
 		print STDOUT "\n\n";
 	} else {
 		print STDOUT "D $title.mw\n";
@@ -547,7 +629,7 @@ sub mw_import_ref {
 
 	mw_connect_maybe();
 
-	my @pages = get_mw_pages();
+	my @pages = get_all_mw_pages();
 
 	print STDERR "Searching revisions...\n";
 	my $last_local = get_last_local_revision();
@@ -580,6 +662,7 @@ sub mw_import_ref {
 
 		$n++;
 
+		my $page_title = $result->{query}->{pages}->{$pagerevid->{pageid}}->{title};
 		my %commit;
 		$commit{author} = $rev->{user} || 'Anonymous';
 		$commit{comment} = $rev->{comment} || '*Empty MediaWiki Message*';
@@ -596,9 +679,44 @@ sub mw_import_ref {
 		}
 		$commit{date} = DateTime::Format::ISO8601->parse_datetime($last_timestamp);
 
-		print STDERR "$n/", scalar(@revisions), ": Revision #$pagerevid->{revid} of $commit{title}\n";
-
-		import_file_revision(\%commit, ($fetch_from == 1), $n);
+		# differentiates classic pages and media pages
+		my @prefix = split (":",$page_title);
+		if ($prefix[0] eq "File" || $prefix[0] eq "Image") {
+			# check if there is a corresponding mediafile with the same timestamp => it is page
+			# for new verion of the file (not only for new version of the description of the file)
+			# => download corresponding file version
+			$query = {
+				action => 'query',
+				prop => 'imageinfo',
+				titles => $page_title,
+				iistart => $rev->{timestamp},
+				iiend => $rev->{timestamp},
+				iiprop => 'timestamp|archivename',
+				iilimit => 1,
+			};
+			$result = $mediawiki->api($query);
+
+			my ($imageid,$imageinfo) = each ( %{$result->{query}->{pages}} );
+			# page has a related version of the file
+			if (defined($imageinfo->{imageinfo})) {
+				foreach ( @{$imageinfo->{imageinfo}} ) {
+					my %mediafile;
+					if ($prefix[0] eq "File") { $mediafile{title} = substr $page_title, 5; }
+					else { $mediafile{title} = substr $page_title, 6; }
+
+					$mediafile{content} = download_mw_mediafile("File:".$mediafile{title});
+					print STDERR "$n/", scalar(@revisions), ": Revision #$pagerevid->{revid} of $commit{title}\n";
+					import_file_revision(\%commit, ($fetch_from == 1), $n, 1, \%mediafile);
+				}
+			# page has no related version of the file, do commit only for the page
+			} else {
+				print STDERR "$n/", scalar(@revisions), ": Revision #$pagerevid->{revid} of $commit{title}\n";
+				import_file_revision(\%commit, ($fetch_from == 1), $n, 0);
+			}
+		} else {
+			print STDERR "$n/", scalar(@revisions), ": Revision #$pagerevid->{revid} of $commit{title}\n";
+			import_file_revision(\%commit, ($fetch_from == 1), $n, 0);
+		}
 	}
 
 	if ($fetch_from == 1 && $n == 0) {
-- 
1.7.10.2.552.gaa3bb87
Matthieu Moy· Jun 4, 2012, 20:54 UTC · re: Pavel Volek · lore

Re: [PATCH/RFC] file import functionality for git-remote-mw

Pavel Volek <Pavel.Volek@ensimag.imag.fr> writes:
Show 13 quoted lines
> +sub get_mw_media_pages {
> +	mw_connect_maybe();
> +
> +	my %pages; # hash on page titles to avoid duplicates
> +
> +	# get all pages for mediafiles (they are in different namespace)
> +	# only one namespace can be queried at the same moment
> +	my $mw_pages = $mediawiki->list({
> +		action => 'query',
> +		list => 'allpages',
> +		apnamespace => get_mw_namespace_id("File"),
> +		aplimit => 500,
> +	});

This seems to be done unconditionally. Is this reasonable if the user has explicitely set remote.origin.pages or remote.origin.categories?

Actually, shouldn't this be added to get_mw_pages, next to the code dealing with these two variables? Perhaps the function should be split into multiple functions, along the lines of:

sub get_mw_pages {
	mw_connect_maybe();
        my %pages;
	if (@tracked_pages) {
		$user_defined = 1;
		get_mw_tracked_pages(\%pages);
	}
	if (@tracked_categories) {
		$user_defined = 1;
		get_mw_tracked_categories(\%pages);
	}
	if (!$user_defined) {
		get_mw_all_pages(\%pages);
	}
	return values(%pages);
}
And your code would need to take these 3 options into account.
> +sub get_all_mw_pages() {
> +	my @pages = get_mw_pages();
> +	my @media_pages = get_mw_media_pages();
> +	push(@pages,@media_pages);
Space after comma.
Show 10 quoted lines
> +# Returns MediaWiki id for a canonical namespace name. Ex.: "File", "Project".
> +sub get_mw_namespace_id() {
> +	mw_connect_maybe();
> +	my $name = shift;
> +	my $query = {
> +		action => 'query',
> +		meta => 'siteinfo',
> +		siprop => 'namespaces',
> +	};
> +	my $result = $mediawiki->api($query);

It may make sense to cache the result, to avoid querying the API multiple times if you call the function more than once. We can even cache this in a configuration variable as the namespace identifiers are unlikely to change for a given wiki.

> +	if (!defined($file)){
Space between ) and { please.
> +		my @prefix = split (":",$page_title);
Space after , please.
> +		if ($prefix[0] eq "File" || $prefix[0] eq "Image") {
> +			# check if there is a corresponding mediafile with the same timestamp => it is page
> +			# for new verion of the file (not only for new version of the description of the file)
> +			# => download corresponding file version

Don't make long lines like this. In general, we avoid lines longer than 80 characters (or even a bit less), these are >100 and the following are worse.

Long lines are usually an indication that you did not structure your code into functions, and this diagnosis seems to apply here.

> +			my ($imageid,$imageinfo) = each ( %{$result->{query}->{pages}} );
Space after ",".
-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
NGUYEN Kim Thuat· Jun 4, 2012, 20:49 UTC · lore

[PATCH/RFC] Export file attachements in git-remote-mediawiki

From: nguyenkimthuat <nguyenkimthuat@gmail.com>
The current version of the git-remote-mediawiki supports only import and export of the pages, doesn't support import and export of file attachements which are also exposed by MediaWiki API. This patch add the functionnality to export the file attachements from the local git's repository using the API of mediawiki.
 For the moment, i removed the line: "use enconding 'utf8'" because it's broken, especially when reding a binary file for exemple .pdf or .jpg.
Signed-off-by: Pavel Volek <Pavel.Volek@ensimag.imag.fr>
Signed-off-by: NGUYEN Kim Thuat <Kim-Thuat.Nguyen@ensimag.imag.fr>
Signed-off-by: ROUCHER IGLESIAS Javier <roucherj@ensimag.imag.fr>
Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
 contrib/mw-to-git/git-remote-mediawiki | 90 ++++++++++++++++++++++++++++++++--
 1 file changed, 87 insertions(+), 3 deletions(-)
Show changes to contrib/mw-to-git/git-remote-mediawiki +87 −3
diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki
index c18bfa1..830e2d0 100755
--- a/contrib/mw-to-git/git-remote-mediawiki
+++ b/contrib/mw-to-git/git-remote-mediawiki
@@ -36,7 +36,6 @@
 use strict;
 use MediaWiki::API;
 use DateTime::Format::ISO8601;
-use encoding 'utf8';
 
 # use encoding 'utf8' doesn't change STDERROR
 # but we're going to output UTF-8 filenames to STDERR
@@ -114,6 +113,9 @@ $wiki_name =~ s/[^\/]*:\/\///;
 # and '@' sign, to avoid author like MWUser@HTTPUser@host.com
 $wiki_name =~ s/^.*@//;
 
+# Get the list of file extensions supported by the current version of mediawiki 
+my @list_file_extensions = get_file_extensions();
+
 # Commands parser
 my $entry;
 my @cmd;
@@ -275,6 +277,13 @@ sub run_git {
 	return $res;
 }
 
+sub run_git_raw {
+        open(my $g,"-|","git " . $_[0]);   
+        my $r = do { local $/; <$g> };
+        close($g);
+
+        return $r;
+}
 
 sub get_last_local_revision {
 	# Get note regarding last mediawiki revision
@@ -642,8 +651,14 @@ sub mw_push_file {
 	my $old_sha1 = $diff_info_split[2];
 	my $page_created = ($old_sha1 eq NULL_SHA1);
 	my $page_deleted = ($new_sha1 eq NULL_SHA1);
+	my $file_deleted = ($new_sha1 eq NULL_SHA1);
 	$complete_file_name = mediawiki_clean_filename($complete_file_name);
 
+	my %hashFiles = map {$_ => 1}@list_file_extensions;
+	my $path = "File:".$complete_file_name;
+	my @extensions = split(/\./,$complete_file_name);
+	my $extension = pop(@extensions);
+
 	if (substr($complete_file_name,-3) eq ".mw") {
 		my $title = substr($complete_file_name,0,-3);
 
@@ -686,8 +701,34 @@ sub mw_push_file {
 		}
 		$newrevid = $result->{edit}->{newrevid};
 		print STDERR "Pushed file: $new_sha1 - $title\n";
-	} else {
-		print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n"
+	} elsif (exists($hashFiles{$extension})) {
+		# Deleting and uploading a file require the priviledge of the user
+		if ($file_deleted) {
+			mw_connect_maybe();
+			my $res = $mediawiki->edit( {
+			action => 'delete',
+			title => $path,
+			reason => $summary } )
+			|| die $mediawiki-> {error}->{code} . ':' . $mediawiki->{error}->{details};
+
+		} else {
+			my $content = run_git_raw("cat-file blob $new_sha1");
+			mw_connect_maybe();
+				$mediawiki->{config}->{upload_url} = "$url/index.php/Special:Upload";
+
+				$mediawiki->upload( {
+				title => $complete_file_name,
+				summary => $summary,
+				data => $content,
+				ignorewarnings=>1
+                                  }, {
+					skip_encoding => 1 # Helps with names with accentuated characters
+			} ) || die $mediawiki-> {error}->{code} . ':' . $mediawiki->{error}->{details};
+			$newrevid = get_reviId_filepage();
+			print STDERR "Pushed file: $new_sha1 - $complete_file_name\n";
+			 }
+	else {
+		print STDERR "$complete_file_name is not supported on this version of Mediawiki.\n"
 	}
 	return ($newrevid, "ok");
 }
@@ -825,3 +866,46 @@ sub mw_push_revision {
 	print STDOUT "ok $remote\n";
 	return 1;
 }
+
+sub get_reviId_filepage() {
+	mw_connect_maybe();
+
+	my $max_rev_num_file = 0;
+
+	my @list_file_pages = get_mw_media_pages();
+
+	foreach my $file_page (@list_file_pages) {
+	my $id = $file_page->{pageid};
+        
+	my $query = {
+		action => 'query',
+		prop => 'revisions',
+		rvprop => 'ids',
+		pageids => $id,
+		};
+
+		my $result = $mediawiki->api($query);
+
+		my $lastrev = pop(@{$result->{query}->{pages}->{$id}->{revisions}});
+	}
+}
+
+sub get_file_extensions {
+	mw_connect_maybe();
+
+	my $query = {
+	action => 'query',
+	meta => 'siteinfo',
+	siprop => 'fileextensions'
+	};
+
+	my $result = $mediawiki->api($query);
+
+	my @file_extensions = map $_->{ext},@{$result->{query}->{fileextensions}};
+
+	return @file_extensions;
+}
+
+
+
+
-- 
1.7.10.2.552.gaa3bb87
Matthieu Moy· Jun 4, 2012, 21:34 UTC · re: NGUYEN Kim Thuat · lore

Re: [PATCH/RFC] Export file attachements in git-remote-mediawiki

NGUYEN Kim Thuat <kim-thuat.nguyen@ensimag.imag.fr> writes:
> +# Get the list of file extensions supported by the current version of mediawiki 
> +my @list_file_extensions = get_file_extensions();
You should do it only on demand (like $mediawiki is created lazily).
> +        open(my $g,"-|","git " . $_[0]);   
Space after ",".
Show 5 quoted lines
> @@ -642,8 +651,14 @@ sub mw_push_file {
>  	my $old_sha1 = $diff_info_split[2];
>  	my $page_created = ($old_sha1 eq NULL_SHA1);
>  	my $page_deleted = ($new_sha1 eq NULL_SHA1);
> +	my $file_deleted = ($new_sha1 eq NULL_SHA1);

This line looks suspiciously similar to the previous one. Do you need another variable for the same value?

> +	my @extensions = split(/\./,$complete_file_name);
Space after "," (many more instances after).
Show 10 quoted lines
> -	} else {
> -		print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n"
> +	} elsif (exists($hashFiles{$extension})) {
> +		# Deleting and uploading a file require the priviledge of the user
> +		if ($file_deleted) {
> +			mw_connect_maybe();
> +			my $res = $mediawiki->edit( {
> +			action => 'delete',
> +			title => $path,
> +			reason => $summary } )
Indent the body of {} please.
> +		} else {
> +			my $content = run_git_raw("cat-file blob $new_sha1");
> +			mw_connect_maybe();
> +				$mediawiki->{config}->{upload_url} = "$url/index.php/Special:Upload";
Broken indentation.

Does this work on wiki configured in foreign languages, like french that has Spécial:Téléverser instead?

> +			} ) || die $mediawiki-> {error}->{code} . ':' . $mediawiki->{error}->{details};
> +			$newrevid = get_reviId_filepage();

This queries the wiki to get the last revision id. The existing code (to deal with page) could get this from the response of the API to the edit request, like this:

		$newrevid = $result->{edit}->{newrevid};

Your version is much more inefficient (many requests each time you upload a file), and has a race condition (what happens if someone else creates a revision at the same time?). Isn't there a better way?

> +			print STDERR "Pushed file: $new_sha1 - $complete_file_name\n";
> +			 }
Broken indentation.
> +	else {
> +		print STDERR "$complete_file_name is not supported on this version of Mediawiki.\n"
It's not a matter of version, it's a matter of configuration.
> +sub get_reviId_filepage() {

Strange name (two consecutive i?). If this function fetches the last wiki revision, why not call it get_last_mw_revid or something like this?

Show 8 quoted lines
> +	mw_connect_maybe();
> +
> +	my $max_rev_num_file = 0;
> +
> +	my @list_file_pages = get_mw_media_pages();
> +
> +	foreach my $file_page (@list_file_pages) {
> +	my $id = $file_page->{pageid};
Broken indentation.
Show 8 quoted lines
> +	my $query = {
> +		action => 'query',
> +		prop => 'revisions',
> +		rvprop => 'ids',
> +		pageids => $id,
> +		};
> +
> +		my $result = $mediawiki->api($query);
Broken indentation.
Show 8 quoted lines
> +sub get_file_extensions {
> +	mw_connect_maybe();
> +
> +	my $query = {
> +	action => 'query',
> +	meta => 'siteinfo',
> +	siprop => 'fileextensions'
> +	};
Broken indentation.
-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
nguyenki· Jun 5, 2012, 17:00 UTC · re: Matthieu Moy · lore

Re: [PATCH/RFC] Export file attachements in git-remote-mediawiki

On Mon, 04 Jun 2012 23:34:29 +0200, Matthieu Moy wrote:
Show 7 quoted lines
> NGUYEN Kim Thuat <kim-thuat.nguyen@ensimag.imag.fr> writes:
>
>> +# Get the list of file extensions supported by the current version 
>> of mediawiki
>> +my @list_file_extensions = get_file_extensions();
>
> You should do it only on demand (like $mediawiki is created lazily).
yeah, i changed the code in this part, it look like:
Show changes to diff +18 −15
@@ -113,9 +113,6 @@ $wiki_name =~ s/[^\/]*:\/\///;
  # and '@' sign, to avoid author like MWUser@HTTPUser@host.com
  $wiki_name =~ s/^.*@//;

-# Get the list of file extensions supported by the current version of 
mediawiki
-my @list_file_extensions = get_file_extensions();
-
  # Commands parser
  my $entry;
  my @cmd;
@@ -654,7 +651,7 @@ sub mw_push_file {
  	my $file_deleted = ($new_sha1 eq NULL_SHA1);
  	$complete_file_name = mediawiki_clean_filename($complete_file_name);

-	my %hashFiles = map {$_ => 1}@list_file_extensions;
+	my %hashFiles = get_file_extensions_maybe($complete_file_name);
  	my $path = "File:".$complete_file_name;
  	my @extensions = split(/\./,$complete_file_name);
  	my $extension = pop(@extensions);

-sub get_file_extensions {
-	mw_connect_maybe();
-
-	my $query = {
-	action => 'query',
-	meta => 'siteinfo',
-	siprop => 'fileextensions'
-	};
+sub get_file_extensions_maybe {
+	my $file_name = shift;
+	my $est_mw_page = substr($file_name,-3) eq ".mw";
+	if(!$est_mw_page) {
+		mw_connect_maybe();

-	my $result = $mediawiki->api($query);
+		my $query = {
+			action => 'query',
+			meta => 'siteinfo',
+			siprop => 'fileextensions'
+			};

-	my @file_extensions = map 
$_->{ext},@{$result->{query}->{fileextensions}};
+		my $result = $mediawiki->api($query);
+		my @file_extensions = map 
$_->{ext},@{$result->{query}->{fileextensions}};
+		my %hashFile = map {$_ => 1}@file_extensions;

-	return @file_extensions;
+		return %hashFile;
+	} else {
+		return ;
+	}
  }

  Now, the function will list the file extensions on demand.


>> @@ -642,8 +651,14 @@ sub mw_push_file {
>>  	my $old_sha1 = $diff_info_split[2];
>>  	my $page_created = ($old_sha1 eq NULL_SHA1);
>>  	my $page_deleted = ($new_sha1 eq NULL_SHA1);
>> +	my $file_deleted = ($new_sha1 eq NULL_SHA1);
>
> This line looks suspiciously similar to the previous one. Do you need
> another variable for the same value?
Yes, it's true. I just want the code to be more visible. Because, when 
we delete a file attachment, it's not a page wiki.

> Does this work on wiki configured in foreign languages, like french 
> that
> has Spécial:Téléverser instead?
>

>> +	else {
>> +		print STDERR "$complete_file_name is not supported on this 
>> version of Mediawiki.\n"
>
> It's not a matter of version, it's a matter of configuration.
What do you think if i change it like:
         else {
	print STDERR "$complete_file_name is not a permitted file type. Check 
your configuration for more information\n"
Matthieu Moy· Jun 5, 2012, 17:05 UTC · re: nguyenki · lore

Re: [PATCH/RFC] Export file attachements in git-remote-mediawiki

nguyenki <nguyenki@ensibm.imag.fr> writes:
Show 10 quoted lines
>>> @@ -642,8 +651,14 @@ sub mw_push_file {
>>>  	my $old_sha1 = $diff_info_split[2];
>>>  	my $page_created = ($old_sha1 eq NULL_SHA1);
>>>  	my $page_deleted = ($new_sha1 eq NULL_SHA1);
>>> +	my $file_deleted = ($new_sha1 eq NULL_SHA1);
>>
>> This line looks suspiciously similar to the previous one. Do you need
>> another variable for the same value?
> Yes, it's true. I just want the code to be more visible. Because, when
> we delete a file attachment, it's not a page wiki.

I still don't see a reason to have two variables for the same thing. If $page_deleted is not a good name, then rename the variable to something more general (e.g. $path_deleted ?)

Show 9 quoted lines
>>> +	else {
>>> +		print STDERR "$complete_file_name is not supported on
>>> this version of Mediawiki.\n"
>>
>> It's not a matter of version, it's a matter of configuration.
> What do you think if i change it like:
>         else {
> 	print STDERR "$complete_file_name is not a permitted file
> type. Check your configuration for more information\n"

That's better, but it doesn't tell the user which configuration to check (Git's one, or the wiki's one).

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
nguyenki· Jun 6, 2012, 22:25 UTC · re: Matthieu Moy · lore

Re: [PATCH/RFC] Export file attachements in git-remote-mediawiki

On Tue, 05 Jun 2012 19:05:44 +0200, Matthieu Moy wrote:
Show 20 quoted lines
> nguyenki <nguyenki@ensibm.imag.fr> writes:
>
>>>> @@ -642,8 +651,14 @@ sub mw_push_file {
>>>>  	my $old_sha1 = $diff_info_split[2];
>>>>  	my $page_created = ($old_sha1 eq NULL_SHA1);
>>>>  	my $page_deleted = ($new_sha1 eq NULL_SHA1);
>>>> +	my $file_deleted = ($new_sha1 eq NULL_SHA1);
>>>
>>> This line looks suspiciously similar to the previous one. Do you 
>>> need
>>> another variable for the same value?
>> Yes, it's true. I just want the code to be more visible. Because, 
>> when
>> we delete a file attachment, it's not a page wiki.
>
> I still don't see a reason to have two variables for the same thing. 
> If
> $page_deleted is not a good name, then rename the variable to
> something more general (e.g. $path_deleted ?)
>
Show 12 quoted lines
>>>> +	else {
>>>> +		print STDERR "$complete_file_name is not supported on
>>>> this version of Mediawiki.\n"
>>>
>>> It's not a matter of version, it's a matter of configuration.
>> What do you think if i change it like:
>>         else {
>> 	print STDERR "$complete_file_name is not a permitted file
>> type. Check your configuration for more information\n"
>
> That's better, but it doesn't tell the user which configuration to
> check (Git's one, or the wiki's one).

Thank for your advices, i changed the code from your suggestions, now it look like:

Show changes to diff +14 −35
@@ -36,6 +36,7 @@
  use strict;
  use MediaWiki::API;
  use DateTime::Format::ISO8601;
+use encoding 'utf8';

  # use encoding 'utf8' doesn't change STDERROR
  # but we're going to output UTF-8 filenames to STDERR
@@ -275,7 +276,8 @@ sub run_git {
  }

  sub run_git_raw {
-        open(my $g,"-|","git " . $_[0]);
+	no encoding 'utf8';
+        open(my $g, "-|:utf8", "git " . $_[0]);
          my $r = do { local $/; <$g> };
          close($g);

@@ -648,7 +650,6 @@ sub mw_push_file {
  	my $old_sha1 = $diff_info_split[2];
  	my $page_created = ($old_sha1 eq NULL_SHA1);
  	my $page_deleted = ($new_sha1 eq NULL_SHA1);
-	my $file_deleted = ($new_sha1 eq NULL_SHA1);
  	$complete_file_name = mediawiki_clean_filename($complete_file_name);

  	my %hashFiles = get_file_extensions_maybe($complete_file_name);
@@ -700,28 +701,29 @@ sub mw_push_file {
  		print STDERR "Pushed file: $new_sha1 - $title\n";
  	} elsif (exists($hashFiles{$extension})) {
  		# Deleting and uploading a file require the priviledge of the user
-		if ($file_deleted) {
+		if ($page_deleted) {
  			mw_connect_maybe();
  			my $res = $mediawiki->edit( {
-			action => 'delete',
-			title => $path,
-			reason => $summary } )
+				action => 'delete',
+				title => $path,
+				reason => $summary } )
  			|| die $mediawiki-> {error}->{code} . ':' . 
$mediawiki->{error}->{details};

  		} else {
  			my $content = run_git_raw("cat-file blob $new_sha1");
  			mw_connect_maybe();
-				$mediawiki->{config}->{upload_url} = 
"$url/index.php/Special:Upload";
+			$mediawiki->{config}->{upload_url} = 
"$url/index.php/Special:Upload";

-				$mediawiki->upload( {
+			$mediawiki->upload( {
  				title => $complete_file_name,
  				summary => $summary,
  				data => $content,
  				ignorewarnings=>1
-                                  }, {
-					skip_encoding => 1 # Helps with names with accentuated characters
-			} ) || die $mediawiki-> {error}->{code} . ':' . 
$mediawiki->{error}->{details};
-			$newrevid = get_reviId_filepage();
+					}, {
+						skip_encoding => 1 # Helps with names with accentuated 
characters
+					} ) || die $mediawiki-> {error}->{code} . ':' . 
$mediawiki->{error}->{details};
+			my $last_file_page = $mediawiki->get_page({title =>$path});
+			$newrevid = $last_file_page->{revid};
  			print STDERR "Pushed file: $new_sha1 - $complete_file_name\n";
  			 }
  	else {
@@ -864,29 +866,6 @@ sub mw_push_revision {
  	return 1;
  }

-sub get_reviId_filepage() {
-	mw_connect_maybe();
-
-	my $max_rev_num_file = 0;
-
-	my @list_file_pages = get_mw_media_pages();
-
-	foreach my $file_page (@list_file_pages) {
-	my $id = $file_page->{pageid};
-
-	my $query = {
-		action => 'query',
-		prop => 'revisions',
-		rvprop => 'ids',
-		pageids => $id,
-		};
-
-	my $result = $mediawiki->api($query);
-
-	my $lastrev = pop(@{$result->{query}->{pages}->{$id}->{revisions}});
-	}
-}
-
nguyenki· Jun 5, 2012, 17:11 UTC · re: Matthieu Moy · lore

Re: [PATCH/RFC] Export file attachements in git-remote-mediawiki

On Mon, 04 Jun 2012 23:34:29 +0200, Matthieu Moy wrote:
> NGUYEN Kim Thuat <kim-thuat.nguyen@ensimag.imag.fr> writes:
>
Show 12 quoted lines
>> +		} else {
>> +			my $content = run_git_raw("cat-file blob $new_sha1");
>> +			mw_connect_maybe();
>> +				$mediawiki->{config}->{upload_url} = 
>> "$url/index.php/Special:Upload";
>
> Broken indentation.
>
> Does this work on wiki configured in foreign languages, like french 
> that
> has Spécial:Téléverser instead?
>

[[Special:Upload]] will work on any wiki, since the English names (known internally as the "canonical" page) will work regardless of the wiki's language.

For example, if you go to http://fr.wikipedia.org/wiki/Special:Upload, you'll see that it redirects automatically.

Kim Thuat NGUYEN· Jun 8, 2012, 13:27 UTC · lore

[PATCHv1] Export file in git-remote-mediawiki

From: nguyenkimthuat <nguyenkimthuat@gmail.com>

This patch adds the functionnality to export the file attachements from the local git's repository using the API of mediawiki. It also provides the possibility for an user to delete a page in the local repository Git which means the page will be deleted in the wiki site after user do the 'push'.

Signed-off-by: VOLEK Pavel <Pavel.Volek@ensimag.imag.fr>
Signed-off-by: NGUYEN Kim Thuat <Kim-Thuat.Nguyen@ensimag.imag.fr>
Signed-off-by: ROUCHER IGLESIAS Javier <roucherj@ensimag.imag.fr>
Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
 contrib/mw-to-git/git-remote-mediawiki | 126 +++++++++++++++++++++++++--------
 1 file changed, 98 insertions(+), 28 deletions(-)
Show changes to contrib/mw-to-git/git-remote-mediawiki +98 −28
diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki
index c18bfa1..2fd0e5b 100755
--- a/contrib/mw-to-git/git-remote-mediawiki
+++ b/contrib/mw-to-git/git-remote-mediawiki
@@ -275,6 +275,14 @@ sub run_git {
 	return $res;
 }
 
+sub run_git_raw {
+	no encoding 'utf8';
+	open(my $g,"-|","git " . $_[0]); 
+	my $r = do { local $/; <$g> };
+	close($g);
+
+	return $r;
+}
 
 sub get_last_local_revision {
 	# Get note regarding last mediawiki revision
@@ -644,6 +652,11 @@ sub mw_push_file {
 	my $page_deleted = ($new_sha1 eq NULL_SHA1);
 	$complete_file_name = mediawiki_clean_filename($complete_file_name);
 
+	my %hashFiles = get_file_extensions_maybe($complete_file_name);
+	my $path = "File:".$complete_file_name;
+	my @extensions = split(/\./, $complete_file_name);
+	my $extension = pop(@extensions);
+
 	if (substr($complete_file_name,-3) eq ".mw") {
 		my $title = substr($complete_file_name,0,-3);
 
@@ -653,39 +666,74 @@ sub mw_push_file {
 			# special priviledges. A common
 			# convention is to replace the page
 			# with this content instead:
-			$file_content = DELETED_CONTENT;
+			mw_connect_maybe();
+			my $re = $mediawiki->edit( {
+				action => 'delete',
+				title => $title,
+				reason => $summary 
+				} )|| die $mediawiki-> {error}->{code} . ':' . $mediawiki->{error}->{details};
 		} else {
 			$file_content = run_git("cat-file blob $new_sha1");
-		}
-
-		mw_connect_maybe();
 
-		my $result = $mediawiki->edit( {
-			action => 'edit',
-			summary => $summary,
-			title => $title,
-			basetimestamp => $basetimestamps{$newrevid},
-			text => mediawiki_clean($file_content, $page_created),
-				  }, {
-					  skip_encoding => 1 # Helps with names with accentuated characters
-				  });
-		if (!$result) {
-			if ($mediawiki->{error}->{code} == 3) {
-				# edit conflicts, considered as non-fast-forward
-				print STDERR 'Warning: Error ' .
-				    $mediawiki->{error}->{code} .
-				    ' from mediwiki: ' . $mediawiki->{error}->{details} .
-				    ".\n";
-				return ($newrevid, "non-fast-forward");
-			} else {
-				# Other errors. Shouldn't happen => just die()
-				die 'Fatal: Error ' .
-				    $mediawiki->{error}->{code} .
-				    ' from mediwiki: ' . $mediawiki->{error}->{details};
+			mw_connect_maybe();
+	
+			my $result = $mediawiki->edit( {
+				action => 'edit',
+				summary => $summary,
+				title => $title,
+				basetimestamp => $basetimestamps{$newrevid},
+				text => mediawiki_clean($file_content, $page_created),
+					  }, {
+						  skip_encoding => 1 # Helps with names with accentuated characters
+					  });
+			if (!$result) {
+				if ($mediawiki->{error}->{code} == 3) {
+					# edit conflicts, considered as non-fast-forward
+					print STDERR 'Warning: Error ' .
+					    $mediawiki->{error}->{code} .
+				   	 ' from mediwiki: ' . $mediawiki->{error}->{details} .".\n";
+					return ($newrevid, "non-fast-forward");
+				} else {
+					# Other errors. Shouldn't happen => just die()
+					die 'Fatal: Error ' .
+					    $mediawiki->{error}->{code} . ' from mediwiki: ' . $mediawiki->{error}->{details};
+				}
 			}
+			$newrevid = $result->{edit}->{newrevid};
+			print STDERR "Pushed file: $new_sha1 - $title\n";
+		}
+	elsif (exists($hashFiles{$extension}))      
+	{		
+		# Deleting and uploading a file require the priviledge of the user
+		if ($page_deleted) {       
+			mw_connect_maybe();
+			my $res = $mediawiki->edit( {
+				action => 'delete',
+				title => $path,
+				reason => $summary
+				} )|| die $mediawiki-> {error}->{code} . ':' . $mediawiki->{error}->{details};	
+		} else {
+			my $content = run_git_raw("cat-file blob $new_sha1");	
+			if ($content ne "") { 
+				mw_connect_maybe();	
+				$mediawiki->{config}->{upload_url} = "$url/index.php/Special:Upload";
+			
+				my $res = $mediawiki->edit ( {
+					action => 'upload',
+					filename => $complete_file_name,
+					comment => $summary,
+					file => [undef, $complete_file_name, Content => $content ],
+					ignorewarnings=>1,
+					}, {
+						  skip_encoding => 1 # Helps with names with accentuated characters
+						} ) || die $mediawiki-> {error}->{code} . ':' . $mediawiki->{error}->{details};
+				my $last_file_page = $mediawiki->get_page({title =>$path});
+				$newrevid = $last_file_page->{revid};
+				print STDERR "Pushed file: $new_sha1 - $complete_file_name\n";
+			} else {
+				print STDERR "Empty file. Can not upload \n ";
+				}
 		}
-		$newrevid = $result->{edit}->{newrevid};
-		print STDERR "Pushed file: $new_sha1 - $title\n";
 	} else {
 		print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n"
 	}
@@ -825,3 +873,25 @@ sub mw_push_revision {
 	print STDOUT "ok $remote\n";
 	return 1;
 }
+
+sub get_file_extensions_maybe {
+	my $file_name = shift;
+	my $est_mw_page = substr($file_name,-3) eq ".mw";
+	if(!$est_mw_page) {
+		mw_connect_maybe();
+
+        	my $query = {
+			action => 'query',
+			meta => 'siteinfo',
+			siprop => 'fileextensions'
+			};
+
+		my $result = $mediawiki->api($query);
+		my @file_extensions= map $_->{ext},@{$result->{query}->{fileextensions}};
+		my %hashFile = map {$_ => 1}@file_extensions;
+
+		return %hashFile;
+	} else {
+		return ;
+	}
+}
-- 
1.7.10.2.552.gaa3bb87
Matthieu Moy· Jun 8, 2012, 14:07 UTC · re: Kim Thuat NGUYEN · lore

Re: [PATCHv1] Export file in git-remote-mediawiki

> Subject: Re: [PATCHv1] Export file in git-remote-mediawiki
We usually write commit subject lines as "subsystem: description", hence
git-remote-mediawiki: export "File:" attachments
Kim Thuat NGUYEN <kim-thuat.nguyen@ensimag.imag.fr> writes:
> From: nguyenkimthuat <nguyenkimthuat@gmail.com>
>
> This patch adds the functionnality to export the file attachements from the local git's repository using the API of mediawiki. It also provides the possibility for
> an user to delete a page in the local repository Git which means the page  will be deleted in the wiki site after user do the 'push'.
Please, avoid long lines (> 80 characters).
> +	open(my $g,"-|","git " . $_[0]); 
Space after , please.
> +	my %hashFiles = get_file_extensions_maybe($complete_file_name);

What does this function do? My first understanding was that it queried the wiki for allowed file extensions, but why does it need the file name? It does nothing if $complete_file_name ends with .mw, but then why do you run it before entering the following if() statement?

>  	if (substr($complete_file_name,-3) eq ".mw") {
>  		my $title = substr($complete_file_name,0,-3);
Show 11 quoted lines
> @@ -653,39 +666,74 @@ sub mw_push_file {
>  			# special priviledges. A common
>  			# convention is to replace the page
>  			# with this content instead:
> -			$file_content = DELETED_CONTENT;
> +			mw_connect_maybe();
> +			my $re = $mediawiki->edit( {
> +				action => 'delete',
> +				title => $title,
> +				reason => $summary 
> +				} )|| die $mediawiki-> {error}->{code} . ':' . $mediawiki->{error}->{details};
This is an unrelated topic, and should not appear in this patch.

If you want to propagate page deletions, then you also need to deal with the case where the user is not allowed to do so (very common on MediaWiki). Also, if you change the code corresponding to the comment right above, you should update the comment too.

> +	elsif (exists($hashFiles{$extension}))      
> +	{
Brace on the same line as else please.
> +			} else {
> +				print STDERR "Empty file. Can not upload \n ";
> +				}
Broken indentation.
>  	} else {
>  		print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n"
>  	}
Isn't the very point of this patch to remove this error message?
Show 8 quoted lines
> @@ -825,3 +873,25 @@ sub mw_push_revision {
>  	print STDOUT "ok $remote\n";
>  	return 1;
>  }
> +
> +sub get_file_extensions_maybe {
> +	my $file_name = shift;
> +	my $est_mw_page = substr($file_name,-3) eq ".mw";
English please. "est" is french ;-).
-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
nguyenki· Jun 8, 2012, 22:59 UTC · re: Matthieu Moy · lore

Re: [PATCHv1] Export file in git-remote-mediawiki

On Fri, 08 Jun 2012 16:07:23 +0200, Matthieu Moy wrote:
Show 7 quoted lines
>> Subject: Re: [PATCHv1] Export file in git-remote-mediawiki
>
> We usually write commit subject lines as "subsystem: description", 
> hence
>
> git-remote-mediawiki: export "File:" attachments
>
Yes, it will be corrected in the next version.
Show 28 quoted lines
> Kim Thuat NGUYEN <kim-thuat.nguyen@ensimag.imag.fr> writes:
>
>> From: nguyenkimthuat <nguyenkimthuat@gmail.com>
>>
>> This patch adds the functionnality to export the file attachements 
>> from the local git's repository using the API of mediawiki. It also 
>> provides the possibility for
>> an user to delete a page in the local repository Git which means the 
>> page  will be deleted in the wiki site after user do the 'push'.
>
> Please, avoid long lines (> 80 characters).
>
>> +	open(my $g,"-|","git " . $_[0]);
>
> Space after , please.
>
>> +	my %hashFiles = get_file_extensions_maybe($complete_file_name);
>
> What does this function do? My first understanding was that it 
> queried
> the wiki for allowed file extensions, but why does it need the file
> name? It does nothing if $complete_file_name ends with .mw, but then 
> why
> do you run it before entering the following if() statement?
>
>>  	if (substr($complete_file_name,-3) eq ".mw") {
>>  		my $title = substr($complete_file_name,0,-3);
>
  This function will get a list of allowed file extensions. It need the 
file name to verify if this name begins with .mw or not. If not, it does 
nothing like
you said. But if $complete_file_name is not a wiki page (end with .mw), 
the function will return the list of file extensions %hashFiles to 
verify if this file is allowed in this condition:
                              } elsif (exists($hashFiles{$extension})) {
Show 24 quoted lines
>> @@ -653,39 +666,74 @@ sub mw_push_file {
>>  			# special priviledges. A common
>>  			# convention is to replace the page
>>  			# with this content instead:
>> -			$file_content = DELETED_CONTENT;
>> +			mw_connect_maybe();
>> +			my $re = $mediawiki->edit( {
>> +				action => 'delete',
>> +				title => $title,
>> +				reason => $summary
>> +				} )|| die $mediawiki-> {error}->{code} . ':' . 
>> $mediawiki->{error}->{details};
>
> This is an unrelated topic, and should not appear in this patch.
>
> If you want to propagate page deletions, then you also need to deal 
> with
> the case where the user is not allowed to do so (very common on
> MediaWiki). Also, if you change the code corresponding to the comment
> right above, you should update the comment too.
>
>> +	elsif (exists($hashFiles{$extension}))
>> +	{
>

Yes, i'll correct it in the next patch. For the moment, i added these lines to deal with the case similar - the case when an user tries to upload a file or pages but he doesn't have sufficient rights or he failed to login.

+sub error_insufficient_right {
+       print STDERR "Can not delete or upload file and wiki pages\n";
+       print STDERR "You don't have right to do it\n";
+       print STDOUT "error $_[0]\"right insufficient\"\n";
+       return 0;
+}
Show changes to diff +23 −3
@@ -726,17 +1011,32 @@ sub mw_push_file {
                                         ignorewarnings=>1,
                                         }, {
                                                   skip_encoding => 1 # 
Helps with names with accentuated characters
-                                               } ) || die $mediawiki-> 
{error}->{code} . ':' . $mediawiki->{error}->{details};
+                                               } );
+                               if (!$res) {
+                                       if ($mediawiki->{error}->{code} 
== 3) {
+                                               # Failed to upload, 
user didn't login or he doesn't have sufficient rights
+                                               print STDERR 'Warning: 
Error ' .
+                                                   
$mediawiki->{error}->{code} .
+                                                   ' from mediwiki: ' 
. $mediawiki->{error}->{details} .
+                                                   ".\n";
+                                               return 
($newrevid,"insufficient-right");
+                                       } else {
+                                               # Other errors. 
Shouldn't happen => just die()
+                                               die 'Fatal: Error ' .
+                                                   
$mediawiki->{error}->{code} .
+                                                   ' from mediwiki: ' 
. $mediawiki->{error}->{details};
+                                       }
+                               }


@@ -860,6 +1161,9 @@ sub mw_push_revision {
                                 # accurate error message.
                                 return error_non_fast_forward($remote);
                         }
+                       if($status eq "insufficient-right") {
+                               return 
error_insufficient_right($remote);
+                       }
                         if ($status ne "ok") {
                                 die("Unknown error from 
mw_push_file()");
                         }



>
>>  	} else {
>>  		print STDERR "$complete_file_name not a mediawiki file (Not 
>> pushable on this version of git-remote-mediawiki).\n"
>>  	}
>
> Isn't the very point of this patch to remove this error message?
Now, the message is
+               print STDERR "$complete_file_name is not a permitted 
file. Check the configuration of file uploads in your mediawiki \n"
+       }


>
>> @@ -825,3 +873,25 @@ sub mw_push_revision {
>>  	print STDOUT "ok $remote\n";
>>  	return 1;
>>  }
>> +
>> +sub get_file_extensions_maybe {
>> +	my $file_name = shift;
>> +	my $est_mw_page = substr($file_name,-3) eq ".mw";
>
> English please. "est" is french ;-).
Corrected.
-       my $est_mw_page = substr($file_name,-3) eq ".mw";
-       if(!$est_mw_page) {
+       my $is_mw_page = substr($file_name,-3) eq ".mw";
+       if(!$is_mw_page) {

Thanks you so much for your advices.

Thuat.
Matthieu Moy· Jun 10, 2012, 13:01 UTC · re: nguyenki · lore

Re: [PATCHv1] Export file in git-remote-mediawiki

nguyenki <nguyenki@ensibm.imag.fr> writes:
Show 18 quoted lines
>>> +	my %hashFiles = get_file_extensions_maybe($complete_file_name);
>>
>> What does this function do? My first understanding was that it
>> queried
>> the wiki for allowed file extensions, but why does it need the file
>> name? It does nothing if $complete_file_name ends with .mw, but then
>> why
>> do you run it before entering the following if() statement?
>>
>>>  	if (substr($complete_file_name,-3) eq ".mw") {
>>>  		my $title = substr($complete_file_name,0,-3);
>>
>  This function will get a list of allowed file extensions. It need the
> file name to verify if this name begins with .mw or not. If not, it
> does nothing like
> you said. But if $complete_file_name is not a wiki page (end with
> .mw), the function will return the list of file extensions %hashFiles
> to verify if this file is allowed in this condition:

If $complete_file_name is not a wiki page, you basically don't need to call this function. As I said, move the call to the function within the next "if" statement, and you won't need this extra complexity.

Show 11 quoted lines
> Yes, i'll correct it in the next patch.
> For the moment, i added these lines to deal with the case similar -
> the case when an user tries to upload a file or pages but he doesn't
> have sufficient rights or he failed to login.
>
> +sub error_insufficient_right {
> +       print STDERR "Can not delete or upload file and wiki pages\n";
> +       print STDERR "You don't have right to do it\n";
> +       print STDOUT "error $_[0]\"right insufficient\"\n";
> +       return 0;
> +}

This is still a regression. The previous version was propagating page deletion as "replace content with [[Category:Deleted]]", which did work with insufficient priviledge.

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
Kim Thuat NGUYEN· Jun 12, 2012, 12:46 UTC · lore

[PATCHv2] git-remote-mediawiki: export File: attachments

From: nguyenkimthuat <nguyenkimthuat@gmail.com>

This patch adds the functionnality to export the file attachements from the local git's repository using the API of mediawiki.

Signed-off-by: VOLEK Pavel <Pavel.Volek@ensimag.imag.fr>
Signed-off-by: NGUYEN Kim Thuat <Kim-Thuat.Nguyen@ensimag.imag.fr>
Signed-off-by: ROUCHER IGLESIAS Javier <roucherj@ensimag.imag.fr>
Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
 contrib/mw-to-git/git-remote-mediawiki | 62 +++++++++++++++++++++++++++++++++-
 1 file changed, 61 insertions(+), 1 deletion(-)
Show changes to contrib/mw-to-git/git-remote-mediawiki +61 −1
diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki
index c18bfa1..8f711e2 100755
--- a/contrib/mw-to-git/git-remote-mediawiki
+++ b/contrib/mw-to-git/git-remote-mediawiki
@@ -275,6 +275,14 @@ sub run_git {
 	return $res;
 }
 
+sub run_git_raw {
+	no encoding 'utf8';
+	open(my $g, "-|", "git " . $_[0]);
+	my $r = do { local $/; <$g> };
+	close($g);
+
+	return $r;
+}
 
 sub get_last_local_revision {
 	# Get note regarding last mediawiki revision
@@ -644,6 +652,10 @@ sub mw_push_file {
 	my $page_deleted = ($new_sha1 eq NULL_SHA1);
 	$complete_file_name = mediawiki_clean_filename($complete_file_name);
 
+	my $path = "File:".$complete_file_name;
+	my @extensions = split(/\./, $complete_file_name);
+	my $extension = pop(@extensions);
+
 	if (substr($complete_file_name,-3) eq ".mw") {
 		my $title = substr($complete_file_name,0,-3);
 
@@ -687,7 +699,40 @@ sub mw_push_file {
 		$newrevid = $result->{edit}->{newrevid};
 		print STDERR "Pushed file: $new_sha1 - $title\n";
 	} else {
-		print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n"
+		my %hashFiles = get_file_extensions();
+		if (exists($hashFiles{$extension})) {
+			# Deleting and uploading a file require the priviledge of the user
+			if ($page_deleted) {
+				mw_connect_maybe();
+				my $res = $mediawiki->edit( {
+					action => 'delete',
+					title => $path,
+					reason => $summary
+					} )|| die $mediawiki-> {error}->{code} . ':' . $mediawiki->{error}->{details};
+			} else {
+				my $content = run_git_raw("cat-file blob $new_sha1");
+				if ($content ne "") {
+					mw_connect_maybe();
+					$mediawiki->{config}->{upload_url} = "$url/index.php/Special:Upload";
+					my $res = $mediawiki->edit ( {
+						action => 'upload',
+						filename => $complete_file_name,
+						comment => $summary,
+						file => [undef, $complete_file_name, Content => $content ],
+						ignorewarnings=>1,
+						}, {
+								skip_encoding => 1 # Helps with names with accentuated characters
+							} )  || die $mediawiki-> {error}->{code} . ':' . $mediawiki->{error}->{details};
+					my $last_file_page = $mediawiki->get_page({title =>$path});
+					$newrevid = $last_file_page->{revid};
+					print STDERR "Pushed file: $new_sha1 - $complete_file_name\n";
+				} else {
+					print STDERR "Empty file. Can not upload \n ";
+				}
+			}
+		} else {
+			print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n";
+		}
 	}
 	return ($newrevid, "ok");
 }
@@ -825,3 +870,18 @@ sub mw_push_revision {
 	print STDOUT "ok $remote\n";
 	return 1;
 }
+
+sub get_file_extensions {
+	mw_connect_maybe();
+
+	my $query = {
+		action => 'query',
+		meta => 'siteinfo',
+		siprop => 'fileextensions'
+		};
+	my $result = $mediawiki->api($query);
+	my @file_extensions= map $_->{ext},@{$result->{query}->{fileextensions}};
+	my %hashFile = map {$_ => 1}@file_extensions;
+
+	return %hashFile;
+}
-- 
1.7.10.GIT
Matthieu Moy· Jun 12, 2012, 12:52 UTC · re: Kim Thuat NGUYEN · lore

Re: [PATCHv2] git-remote-mediawiki: export File: attachments

Kim Thuat NGUYEN <kim-thuat.nguyen@ensimag.imag.fr> writes:
> From: nguyenkimthuat <nguyenkimthuat@gmail.com>

Please use your @ensimag email for Ensimag-related things. This adress is the one used in the commit itself, i.e. ~/.gitconfig. "git commit --reset-author" can help.

Show 10 quoted lines
> @@ -644,6 +652,10 @@ sub mw_push_file {
>  	my $page_deleted = ($new_sha1 eq NULL_SHA1);
>  	$complete_file_name = mediawiki_clean_filename($complete_file_name);
>  
> +	my $path = "File:".$complete_file_name;
> +	my @extensions = split(/\./, $complete_file_name);
> +	my $extension = pop(@extensions);
> +
>  	if (substr($complete_file_name,-3) eq ".mw") {
>  		my $title = substr($complete_file_name,0,-3);

If you extract the extension explicitely, then you don't need these "substr(...)" anymore.

Show 10 quoted lines
> +		my %hashFiles = get_file_extensions();
> +		if (exists($hashFiles{$extension})) {
> +			# Deleting and uploading a file require the priviledge of the user
> +			if ($page_deleted) {
> +				mw_connect_maybe();
> +				my $res = $mediawiki->edit( {
> +					action => 'delete',
> +					title => $path,
> +					reason => $summary
> +					} )|| die $mediawiki-> {error}->{code} . ':' . $mediawiki->{error}->{details};
Here and below: you still have too long lines.
> +						ignorewarnings=>1,
Spaces around =>.
> +						}, {
> +								skip_encoding => 1 # Helps with names with accentuated characters
> +							} )  || die $mediawiki-> {error}->{code} . ':' . $mediawiki->{error}->{details};
Weird indentation.
> +				} else {
> +					print STDERR "Empty file. Can not upload \n ";
> +				}
No space, but a "." before \n.
> +		} else {
> +			print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n";
> +		}
Why does this message keep reappearing?
Show 14 quoted lines
> +sub get_file_extensions {
> +	mw_connect_maybe();
> +
> +	my $query = {
> +		action => 'query',
> +		meta => 'siteinfo',
> +		siprop => 'fileextensions'
> +		};
> +	my $result = $mediawiki->api($query);
> +	my @file_extensions= map $_->{ext},@{$result->{query}->{fileextensions}};
> +	my %hashFile = map {$_ => 1}@file_extensions;
> +
> +	return %hashFile;
> +}
I like the new function much more than the previous one.
-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
nguyenki· Jun 12, 2012, 12:54 UTC · re: Kim Thuat NGUYEN · lore

Re: [PATCHv2] git-remote-mediawiki: export File: attachments

On Tue, 12 Jun 2012 14:46:16 +0200, Kim Thuat NGUYEN wrote:
Show 6 quoted lines
> +			print STDERR "$complete_file_name not a mediawiki file (Not
> pushable on this version of git-remote-mediawiki).\n";
> +		}
>  	}
>  	return ($newrevid, "ok");
>  }
I changed the comment like that, i'll be correct in the nex patch.

- print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n"; + print STDERR "$complete_file_name is not a permitted file. Check the configuration of file uploads in your mediawiki \n";

Max Horn· Jun 12, 2012, 13:51 UTC · re: Kim Thuat NGUYEN · lore

Re: [PATCHv2] git-remote-mediawiki: export File: attachments

Am 12.06.2012 um 14:46 schrieb Kim Thuat NGUYEN:
> From: nguyenkimthuat <nguyenkimthuat@gmail.com>
> 
> This patch adds the functionnality to export the 

Minor nitpick: functionnality => functionality

Cheers, Max

kim-thuat.nguyen@ensimag.imag.fr· Jun 13, 2012, 07:56 UTC · lore

[PATCHv3] git-remote-mediawiki: export File: attachments

From: nguyenkimthuat <Kim-Thuat.Nguyen@ensimag.imag.fr>

This patch adds the functionality to export the file attachements from the local git's repository using the API of mediawiki.

Signed-off-by: VOLEK Pavel <Pavel.Volek@ensimag.imag.fr>
Signed-off-by: NGUYEN Kim Thuat <Kim-Thuat.Nguyen@ensimag.imag.fr>
Signed-off-by: ROUCHER IGLESIAS Javier <roucherj@ensimag.imag.fr>
Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
 contrib/mw-to-git/git-remote-mediawiki | 67 +++++++++++++++++++++++++++++++++-
 1 file changed, 65 insertions(+), 2 deletions(-)
Show changes to contrib/mw-to-git/git-remote-mediawiki +65 −2
diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki
index c18bfa1..e6f8940 100755
--- a/contrib/mw-to-git/git-remote-mediawiki
+++ b/contrib/mw-to-git/git-remote-mediawiki
@@ -275,6 +275,14 @@ sub run_git {
 	return $res;
 }
 
+sub run_git_raw {
+	no encoding 'utf8';
+	open(my $g, "-|", "git " . $_[0]);
+	my $r = do { local $/; <$g> };
+	close($g);
+
+	return $r;
+}
 
 sub get_last_local_revision {
 	# Get note regarding last mediawiki revision
@@ -644,7 +652,11 @@ sub mw_push_file {
 	my $page_deleted = ($new_sha1 eq NULL_SHA1);
 	$complete_file_name = mediawiki_clean_filename($complete_file_name);
 
-	if (substr($complete_file_name,-3) eq ".mw") {
+	my $path = "File:".$complete_file_name;
+	my @extensions = split(/\./, $complete_file_name);
+	my $extension = pop(@extensions);
+
+	if ($extension eq "mw") {
 		my $title = substr($complete_file_name,0,-3);
 
 		my $file_content;
@@ -687,7 +699,43 @@ sub mw_push_file {
 		$newrevid = $result->{edit}->{newrevid};
 		print STDERR "Pushed file: $new_sha1 - $title\n";
 	} else {
-		print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n"
+		my %hashFiles = get_file_extensions();
+		if (exists($hashFiles{$extension})) {
+			# Deleting and uploading a file require the priviledge of the user
+			if ($page_deleted) {
+				mw_connect_maybe();
+				my $res = $mediawiki->edit( {
+					action => 'delete',
+					title => $path,
+					reason => $summary
+					} )|| die $mediawiki-> {error}->{code} .
+						':' . $mediawiki->{error}->{details};
+			} else {
+				my $content = run_git_raw("cat-file blob $new_sha1");
+				if ($content ne "") {
+					mw_connect_maybe();
+					$mediawiki->{config}->{upload_url} = "$url/index.php/Special:Upload";
+
+					my $res = $mediawiki->edit ( {
+						action => 'upload',
+						filename => $complete_file_name,
+						comment => $summary,
+						file => [undef, $complete_file_name, Content => $content ],
+						ignorewarnings => 1,
+						}, {
+							skip_encoding => 1 # Helps with names with accentuated characters
+						} )  || die $mediawiki-> {error}->{code} .
+							':' . $mediawiki->{error}->{details};
+					my $last_file_page = $mediawiki->get_page({title =>$path});
+					$newrevid = $last_file_page->{revid};
+					print STDERR "Pushed file: $new_sha1 - $complete_file_name.\n";
+				} else {
+					print STDERR "Empty file. Can not upload.\n ";
+				}
+			}
+		} else {
+			print STDERR "$complete_file_name is not a permitted file. Check the configuration of file uploads in your mediawiki.\n";
+		}
 	}
 	return ($newrevid, "ok");
 }
@@ -825,3 +873,18 @@ sub mw_push_revision {
 	print STDOUT "ok $remote\n";
 	return 1;
 }
+
+sub get_file_extensions {
+	mw_connect_maybe();
+
+	my $query = {
+		action => 'query',
+		meta => 'siteinfo',
+		siprop => 'fileextensions'
+		};
+	my $result = $mediawiki->api($query);
+	my @file_extensions= map $_->{ext},@{$result->{query}->{fileextensions}};
+	my %hashFile = map {$_ => 1}@file_extensions;
+
+	return %hashFile;
+}
-- 
1.7.11.rc2.10.g9fb1faf
Eric Blake· Jan 11, 2013, 16:39 UTC · lore

git send-email should not allow 'y' for in-reply-to

[raising this UI wart to the git list]
On 01/11/2013 01:42 AM, Peter Krempa wrote:
> On 01/11/13 07:31, Chunyan Liu wrote:
>> This patch series is to...
[snip]
Show 7 quoted lines
> 
> Please don't answer "y" when git send email shows the following prompt:
> 
> "Message-ID to be used as In-Reply-To for the first email?"
> 
> you should respond with a message ID there. Unfortunately we have a
> growing thread that contains submissions with this mistake.

Anyone willing to patch upstream 'git send-email' to reject a simple 'y' rather than blindly sending a bad messageID for the in-reply-to field, to help future users avoid this mistake? Obviously, it won't help until the patch eventually percolates into distros, so it would be a few more months before we see the benefits, but down the road it will prevent confusing threads.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
Jeff King· Jan 11, 2013, 16:47 UTC · re: Eric Blake · lore

Re: git send-email should not allow 'y' for in-reply-to

On Fri, Jan 11, 2013 at 09:39:06AM -0700, Eric Blake wrote:
Show 13 quoted lines
> > Please don't answer "y" when git send email shows the following prompt:
> > 
> > "Message-ID to be used as In-Reply-To for the first email?"
> > 
> > you should respond with a message ID there. Unfortunately we have a
> > growing thread that contains submissions with this mistake.
> 
> Anyone willing to patch upstream 'git send-email' to reject a simple 'y'
> rather than blindly sending a bad messageID for the in-reply-to field,
> to help future users avoid this mistake?  Obviously, it won't help until
> the patch eventually percolates into distros, so it would be a few more
> months before we see the benefits, but down the road it will prevent
> confusing threads.

What version of git? Commit 51bbccf is in v1.7.12.1 and higher, and says:

  $ git show 51bbccf
  commit 51bbccfd1b4a9e2807413022c56ab05c835164fb
  Author: Junio C Hamano <gitster@pobox.com>
  Date:   Tue Aug 14 15:15:53 2012 -0700
  send-email: validate & reconfirm interactive responses
  People answer 'y' to "Who should the emails appear to be from?"  and
  'n' to "Message-ID to be used as In-Reply-To for the first email?"
  for some unknown reason.  While it is possible that your local
  username really is "y" and you are sending the mail to your local
  colleagues, it is possible, and some might even say it is likely,
  that it is a user error.
  Fortunately, our interactive prompter already has input validation
  mechanism built-in.  Enhance it so that we can optionally reconfirm
  and allow the user to pass an input that does not validate, and
  "softly" require input to the sender, in-reply-to, and recipient to
  contain "@" and "." in this order, which would catch most cases of
  mistakes.
-Peff
Eric Blake· Jan 11, 2013, 17:51 UTC · re: Jeff King · lore

Re: git send-email should not allow 'y' for in-reply-to

On 01/11/2013 09:47 AM, Jeff King wrote:
> On Fri, Jan 11, 2013 at 09:39:06AM -0700, Eric Blake wrote:
> 
>>> Please don't answer "y" when git send email shows the following prompt:
>>>
>>
>> Anyone willing to patch upstream 'git send-email' to reject a simple 'y'
Show 16 quoted lines
> What version of git? Commit 51bbccf is in v1.7.12.1 and higher, and
> says:
> 
>   $ git show 51bbccf
>   commit 51bbccfd1b4a9e2807413022c56ab05c835164fb
>   Author: Junio C Hamano <gitster@pobox.com>
>   Date:   Tue Aug 14 15:15:53 2012 -0700
> 
>   send-email: validate & reconfirm interactive responses
> 
>   People answer 'y' to "Who should the emails appear to be from?"  and
>   'n' to "Message-ID to be used as In-Reply-To for the first email?"
>   for some unknown reason.  While it is possible that your local
>   username really is "y" and you are sending the mail to your local
>   colleagues, it is possible, and some might even say it is likely,
>   that it is a user error.

Awesome! Already implemented! In the case that sparked this particular email, the culprit was using 1.7.3.4; earlier this month, a separate culprit to the same libvirt mailing list was using 1.7.11.7.

I was right about it needing to take a few months to percolate to the actual users.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
Hilco Wijbenga· Jan 11, 2013, 18:43 UTC · re: Jeff King · lore

Re: git send-email should not allow 'y' for in-reply-to

On 11 January 2013 08:47, Jeff King <peff@peff.net> wrote:
Show 8 quoted lines
> On Fri, Jan 11, 2013 at 09:39:06AM -0700, Eric Blake wrote:
>
>> > Please don't answer "y" when git send email shows the following prompt:
>> >
>> > "Message-ID to be used as In-Reply-To for the first email?"
>> >
>> > you should respond with a message ID there. Unfortunately we have a
>> > growing thread that contains submissions with this mistake.
<snip/>
Show 6 quoted lines
>   People answer 'y' to "Who should the emails appear to be from?"  and
>   'n' to "Message-ID to be used as In-Reply-To for the first email?"
>   for some unknown reason.  While it is possible that your local
>   username really is "y" and you are sending the mail to your local
>   colleagues, it is possible, and some might even say it is likely,
>   that it is a user error.

I have never used Git's email support so this doesn't affect me one way or another but it seems that checking the results is fixing the symptoms, not the problem? I apologize if this was already discussed but I couldn't find such a discussion.

I was wondering if it might be a better idea to change the wording of the questions if they have proven so confusing? The first time (just now) that I read "Message-ID to be used as In-Reply-To for the first email?", it clearly seemed like a yes/no question to me. :-)

How about "What Message-ID to use as In-Reply-To for the first email?" or "Provide the Message-ID to use as In-Reply-To for the first email:". I'm a little surprised that "Who should the emails appear to be from?" would be interpreted as a yes/no question but we could rephrase that similarly as "Provide the name of the email sender:" (I don't really like this particular version but you get the idea).

Jeff King· Jan 11, 2013, 18:54 UTC · re: Hilco Wijbenga · lore

Re: git send-email should not allow 'y' for in-reply-to

On Fri, Jan 11, 2013 at 10:43:39AM -0800, Hilco Wijbenga wrote:
Show 11 quoted lines
> >   People answer 'y' to "Who should the emails appear to be from?"  and
> >   'n' to "Message-ID to be used as In-Reply-To for the first email?"
> >   for some unknown reason.  While it is possible that your local
> >   username really is "y" and you are sending the mail to your local
> >   colleagues, it is possible, and some might even say it is likely,
> >   that it is a user error.
> 
> I have never used Git's email support so this doesn't affect me one
> way or another but it seems that checking the results is fixing the
> symptoms, not the problem? I apologize if this was already discussed
> but I couldn't find such a discussion.

It depends on who you are. If you are the person running send-email, then the symptom is your confusion. If you are somebody else, the symptom is somebody else sending out a bogus email. That patch fixes only the latter. :)

More seriously, I agree that re-wording the question is a reasonable thing to do. I do not use send-email, either, so I don't have a strong opinion on it. The suggestions you made:

> How about "What Message-ID to use as In-Reply-To for the first email?"
> or "Provide the Message-ID to use as In-Reply-To for the first
> email:".

seem fine to me. Maybe somebody who has been confused by it can offer more. At any rate, patches welcome.

-Peff
Junio C Hamano· Feb 24, 2013, 09:03 UTC · re: Jeff King · lore

Re: git send-email should not allow 'y' for in-reply-to

Jeff King <peff@peff.net> writes:
Show 12 quoted lines
> On Fri, Jan 11, 2013 at 10:43:39AM -0800, Hilco Wijbenga wrote:
> ...
> More seriously, I agree that re-wording the question is a reasonable
> thing to do. I do not use send-email, either, so I don't have a strong
> opinion on it. The suggestions you made:
>
>> How about "What Message-ID to use as In-Reply-To for the first email?"
>> or "Provide the Message-ID to use as In-Reply-To for the first
>> email:".
>
> seem fine to me. Maybe somebody who has been confused by it can offer
> more. At any rate, patches welcome.

Has anything come out of this discussion? Is the current phrasing fine as-is?

y@ensimag.imag.fr· Jun 10, 2013, 15:13 UTC · lore

v3 [PATCH 1/2] status: introduce status.short to enable --short by default

From: Jorge Juan Garcia Garcia <Jorge-Juan.Garcia-Garcia@ensimag.imag.fr>

Some people always run 'git status -s'. The configuration variable status.short allows to set it by default.

Signed-off-by: Jorge Juan Garcia Garcia <Jorge-Juan.Garcia-Garcia@ensimag.imag.fr>
Signed-off-by: Mathieu Lienard--Mayor <Mathieu.Lienard--Mayor@ensimag.imag.fr>
Signed-off-by: Matthieu Moy <Matthieu.Moy@grenoble-inp.fr>
---
 Changes to be commented:
- Cleaning test
 Documentation/config.txt |    4 ++++
 builtin/commit.c         |    5 +++++
 t/t7508-status.sh        |   35 +++++++++++++++++++++++++++++++++++
 3 files changed, 44 insertions(+), 0 deletions(-)
Show changes to 3 files +44 −0

Documentation/config.txt, builtin/commit.c, t/t7508-status.sh

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 6e53fc5..1983bf7 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -2066,6 +2066,10 @@ status.relativePaths::
 	relative to the repository root (this was the default for Git
 	prior to v1.5.4).
 
+status.short::
+	Set to true to enable --short by default in linkgit:git-status[1].
+	The option --no-short takes precedence over this variable.
+
 status.showUntrackedFiles::
 	By default, linkgit:git-status[1] and linkgit:git-commit[1] show
 	files which are not currently tracked by Git. Directories which
diff --git a/builtin/commit.c b/builtin/commit.c
index 1621dfc..287f1cb 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1112,6 +1112,11 @@ static int git_status_config(const char *k, const char *v, void *cb)
 			s->submodule_summary = -1;
 		return 0;
 	}
+	if (!strcmp(k, "status.short")) {
+		if (git_config_bool(k, v))
+			status_format = STATUS_FORMAT_SHORT;
+		return 0;
+	}
 	if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
 		s->use_color = git_config_colorbool(k, v);
 		return 0;
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index e2ffdac..9a07f15 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -1335,4 +1335,39 @@ test_expect_failure '.git/config ignore=all suppresses submodule summary' '
 	git config -f .gitmodules  --remove-section submodule.subname
 '
 
+test_expect_success '"Setup of environment of test"' '
+	git config status.showUntrackedFiles no
+'
+
+test_expect_success '"status.short=true" same as "-s"' '
+	git -c status.short=true status >actual &&
+	git status -s >expected_short &&
+	test_cmp actual expected_short
+'
+
+test_expect_success '"status.short=true" different from "--no-short"' '
+	git status --no-short >expected_noshort &&
+	test_must_fail test_cmp actual expected_noshort
+'
+
+test_expect_success '"status.short=true" weaker than "--no-short"' '
+	git -c status.short=true status --no-short >actual &&
+	test_cmp actual expected_noshort
+'
+
+test_expect_success '"status.short=false" same as "--no-short"' '
+	git -c status.short=false status >actual &&
+	git status -s >expected_short &&
+	test_cmp actual expected_noshort
+'
+
+test_expect_success '"status.short=false" weaker than "-s"' '
+	git -c status.short=false status -s >actual &&
+	test_cmp actual expected_short
+'
+
+test_expect_success '"Back to environment of test by default"' '
+	git config status.showUntrackedFiles yes
+'
+
 test_done
-- 
1.7.8
y@ensimag.imag.fr· Jun 10, 2013, 15:13 UTC · lore

v3 [PATCH 1/2] status: introduce status.short to enable --short by default

From: Jorge Juan Garcia Garcia <Jorge-Juan.Garcia-Garcia@ensimag.imag.fr>

Some people always run 'git status -s'. The configuration variable status.short allows to set it by default.

Signed-off-by: Jorge Juan Garcia Garcia <Jorge-Juan.Garcia-Garcia@ensimag.imag.fr>
Signed-off-by: Mathieu Lienard--Mayor <Mathieu.Lienard--Mayor@ensimag.imag.fr>
Signed-off-by: Matthieu Moy <Matthieu.Moy@grenoble-inp.fr>
---
 Changes to be commented:
- Cleaning test
 Documentation/config.txt |    4 ++++
 builtin/commit.c         |    5 +++++
 t/t7508-status.sh        |   35 +++++++++++++++++++++++++++++++++++
 3 files changed, 44 insertions(+), 0 deletions(-)
Show changes to 3 files +44 −0

Documentation/config.txt, builtin/commit.c, t/t7508-status.sh

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 6e53fc5..1983bf7 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -2066,6 +2066,10 @@ status.relativePaths::
 	relative to the repository root (this was the default for Git
 	prior to v1.5.4).
 
+status.short::
+	Set to true to enable --short by default in linkgit:git-status[1].
+	The option --no-short takes precedence over this variable.
+
 status.showUntrackedFiles::
 	By default, linkgit:git-status[1] and linkgit:git-commit[1] show
 	files which are not currently tracked by Git. Directories which
diff --git a/builtin/commit.c b/builtin/commit.c
index 1621dfc..287f1cb 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1112,6 +1112,11 @@ static int git_status_config(const char *k, const char *v, void *cb)
 			s->submodule_summary = -1;
 		return 0;
 	}
+	if (!strcmp(k, "status.short")) {
+		if (git_config_bool(k, v))
+			status_format = STATUS_FORMAT_SHORT;
+		return 0;
+	}
 	if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
 		s->use_color = git_config_colorbool(k, v);
 		return 0;
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index e2ffdac..9a07f15 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -1335,4 +1335,39 @@ test_expect_failure '.git/config ignore=all suppresses submodule summary' '
 	git config -f .gitmodules  --remove-section submodule.subname
 '
 
+test_expect_success '"Setup of environment of test"' '
+	git config status.showUntrackedFiles no
+'
+
+test_expect_success '"status.short=true" same as "-s"' '
+	git -c status.short=true status >actual &&
+	git status -s >expected_short &&
+	test_cmp actual expected_short
+'
+
+test_expect_success '"status.short=true" different from "--no-short"' '
+	git status --no-short >expected_noshort &&
+	test_must_fail test_cmp actual expected_noshort
+'
+
+test_expect_success '"status.short=true" weaker than "--no-short"' '
+	git -c status.short=true status --no-short >actual &&
+	test_cmp actual expected_noshort
+'
+
+test_expect_success '"status.short=false" same as "--no-short"' '
+	git -c status.short=false status >actual &&
+	git status -s >expected_short &&
+	test_cmp actual expected_noshort
+'
+
+test_expect_success '"status.short=false" weaker than "-s"' '
+	git -c status.short=false status -s >actual &&
+	test_cmp actual expected_short
+'
+
+test_expect_success '"Back to environment of test by default"' '
+	git config status.showUntrackedFiles yes
+'
+
 test_done
-- 
1.7.8
y@ensimag.imag.fr· Jun 10, 2013, 15:13 UTC · re: y@ensimag.imag.fr · lore

v3 [PATCH 2/2] status:introduce status.branch to enable --branch by default

From: Jorge Juan Garcia Garcia <Jorge-Juan.Garcia-Garcia@ensimag.imag.fr>

Some people often run 'git status -b'. The config variable status.branch allows to set it by default.

Signed-off-by: Jorge Juan Garcia Garcia <Jorge-Juan.Garcia-Garcia@ensimag.imag.fr>
Signed-off-by: Mathieu Lienard--Mayor <Mathieu.Lienard--Mayor@ensimag.imag.fr>
Signed-off-by: Matthieu Moy <Matthieu.Moy@grenoble-inp.fr>
--- 
Changes to be commented:
- Cleaning test
 Documentation/config.txt |    4 ++++
 builtin/commit.c         |    4 ++++
 t/t7508-status.sh        |   27 +++++++++++++++++++++++++++
 3 files changed, 35 insertions(+), 0 deletions(-)
Show changes to 3 files +35 −0

Documentation/config.txt, builtin/commit.c, t/t7508-status.sh

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 1983bf7..ecdcd6d 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -2070,6 +2070,10 @@ status.short::
 	Set to true to enable --short by default in linkgit:git-status[1].
 	The option --no-short takes precedence over this variable.
 
+status.branch::
+	Set to true to enable --branch by default in linkgit:git-status[1].
+	The option --no-branch takes precedence over this variable.
+
 status.showUntrackedFiles::
 	By default, linkgit:git-status[1] and linkgit:git-commit[1] show
 	files which are not currently tracked by Git. Directories which
diff --git a/builtin/commit.c b/builtin/commit.c
index 287f1cb..f2b5d44 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1117,6 +1117,10 @@ static int git_status_config(const char *k, const char *v, void *cb)
 			status_format = STATUS_FORMAT_SHORT;
 		return 0;
 	}
+	if (!strcmp(k, "status.branch")) {
+		s->show_branch = git_config_bool(k, v);
+		return 0;
+	}
 	if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
 		s->use_color = git_config_colorbool(k, v);
 		return 0;
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index 9a07f15..958617a 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -1366,6 +1366,33 @@ test_expect_success '"status.short=false" weaker than "-s"' '
 	test_cmp actual expected_short
 '
 
+test_expect_success '"status.branch=true" same as "-b"' '
+	git -c status.branch=true status -s >actual &&
+	git status -sb >expected_branch &&
+	test_cmp actual expected_branch
+'
+
+test_expect_success '"status.branch=true" different from "--no-branch"' '
+	git -c status.branch=true status -s >actual &&
+	git status -s --no-branch  >expected_nobranch &&
+	test_must_fail test_cmp actual expected_nobranch
+'
+
+test_expect_success '"status.branch=true" weaker than "--no-branch"' '
+	git -c status.branch=true status -s --no-branch >actual &&
+	test_cmp actual expected_nobranch
+'
+
+test_expect_success '"status.branch=false" same as "--no-branch"' '
+	git -c status.branch=false status -s >actual &&
+	test_cmp actual expected_nobranch
+'
+
+test_expect_success '"status.branch=false" weaker than "-b"' '
+	git -c status.branch=false status -sb >actual &&
+	test_cmp actual expected_branch
+'
+
 test_expect_success '"Back to environment of test by default"' '
 	git config status.showUntrackedFiles yes
 '
-- 
1.7.8
y@ensimag.imag.fr· Jun 10, 2013, 15:13 UTC · re: y@ensimag.imag.fr · lore

v3 [PATCH 2/2] status:introduce status.branch to enable --branch by default

From: Jorge Juan Garcia Garcia <Jorge-Juan.Garcia-Garcia@ensimag.imag.fr>

Some people often run 'git status -b'. The config variable status.branch allows to set it by default.

Signed-off-by: Jorge Juan Garcia Garcia <Jorge-Juan.Garcia-Garcia@ensimag.imag.fr>
Signed-off-by: Mathieu Lienard--Mayor <Mathieu.Lienard--Mayor@ensimag.imag.fr>
Signed-off-by: Matthieu Moy <Matthieu.Moy@grenoble-inp.fr>
--- 
Changes to be commented:
- Cleaning test
 Documentation/config.txt |    4 ++++
 builtin/commit.c         |    4 ++++
 t/t7508-status.sh        |   27 +++++++++++++++++++++++++++
 3 files changed, 35 insertions(+), 0 deletions(-)
Show changes to 3 files +35 −0

Documentation/config.txt, builtin/commit.c, t/t7508-status.sh

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 1983bf7..ecdcd6d 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -2070,6 +2070,10 @@ status.short::
 	Set to true to enable --short by default in linkgit:git-status[1].
 	The option --no-short takes precedence over this variable.
 
+status.branch::
+	Set to true to enable --branch by default in linkgit:git-status[1].
+	The option --no-branch takes precedence over this variable.
+
 status.showUntrackedFiles::
 	By default, linkgit:git-status[1] and linkgit:git-commit[1] show
 	files which are not currently tracked by Git. Directories which
diff --git a/builtin/commit.c b/builtin/commit.c
index 287f1cb..f2b5d44 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1117,6 +1117,10 @@ static int git_status_config(const char *k, const char *v, void *cb)
 			status_format = STATUS_FORMAT_SHORT;
 		return 0;
 	}
+	if (!strcmp(k, "status.branch")) {
+		s->show_branch = git_config_bool(k, v);
+		return 0;
+	}
 	if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
 		s->use_color = git_config_colorbool(k, v);
 		return 0;
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index 9a07f15..958617a 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -1366,6 +1366,33 @@ test_expect_success '"status.short=false" weaker than "-s"' '
 	test_cmp actual expected_short
 '
 
+test_expect_success '"status.branch=true" same as "-b"' '
+	git -c status.branch=true status -s >actual &&
+	git status -sb >expected_branch &&
+	test_cmp actual expected_branch
+'
+
+test_expect_success '"status.branch=true" different from "--no-branch"' '
+	git -c status.branch=true status -s >actual &&
+	git status -s --no-branch  >expected_nobranch &&
+	test_must_fail test_cmp actual expected_nobranch
+'
+
+test_expect_success '"status.branch=true" weaker than "--no-branch"' '
+	git -c status.branch=true status -s --no-branch >actual &&
+	test_cmp actual expected_nobranch
+'
+
+test_expect_success '"status.branch=false" same as "--no-branch"' '
+	git -c status.branch=false status -s >actual &&
+	test_cmp actual expected_nobranch
+'
+
+test_expect_success '"status.branch=false" weaker than "-b"' '
+	git -c status.branch=false status -sb >actual &&
+	test_cmp actual expected_branch
+'
+
 test_expect_success '"Back to environment of test by default"' '
 	git config status.showUntrackedFiles yes
 '
-- 
1.7.8
Matthieu Moy· Jun 10, 2013, 15:20 UTC · re: y@ensimag.imag.fr · lore

Re: v3 [PATCH 1/2] status: introduce status.short to enable --short by default

y@ensimag.imag.fr writes:
> To: y@ensimag.imag.fr

Common mistake, but you're not supposed to answer "y" when you're prompted for an email ;-).

set sendemail.from to avoid this.
> +test_expect_success '"Setup of environment of test"' '
Same problem as v2.
> +test_expect_success '"Back to environment of test by default"' '
> +	git config status.showUntrackedFiles yes
Same.
Probably something went wrong during your rebase.
-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
Junio C Hamano· Jun 10, 2013, 18:02 UTC · re: Matthieu Moy · lore

Re: v3 [PATCH 1/2] status: introduce status.short to enable --short by default

Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:
Show 6 quoted lines
> y@ensimag.imag.fr writes:
>
>> To: y@ensimag.imag.fr
>
> Common mistake, but you're not supposed to answer "y" when you're
> prompted for an email ;-).

Didn't we introduce safety against this in v1.7.12.1 and later? Is the new release taking more than 9 months to percolate, or are there still other codepaths that allow this to happen that we need to add further safety?

Elia Pinto· Jan 29, 2014, 13:33 UTC · lore

[PATCH 3/3] builtin/blame.c: reduce scope of variables

Signed-off-by: Elia Pinto <gitter.spiros@gmail.com>
---
 builtin/blame.c |    6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Show changes to builtin/blame.c +3 −3
diff --git a/builtin/blame.c b/builtin/blame.c
index e44a6bb..967a7c6 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -1580,14 +1580,14 @@ static const char *format_time(unsigned long time, const char *tz_str,
 			       int show_raw_time)
 {
 	static char time_buf[128];
-	const char *time_str;
-	int time_len;
-	int tz;
 
 	if (show_raw_time) {
 		snprintf(time_buf, sizeof(time_buf), "%lu %s", time, tz_str);
 	}
 	else {
+		const char *time_str;
+		int time_len;
+		int tz;
 		tz = atoi(tz_str);
 		time_str = show_date(time, tz, blame_date_mode);
 		time_len = strlen(time_str);
-- 
1.7.10.4
Rohit Mani· Mar 5, 2014, 19:29 UTC · lore

[PATCH] use strchrnul() in place of strchr() and strlen()

Avoid scanning strings twice, once with strchr() and then with strlen(), by using strchrnul(). Update the conditional expressions involving the return value of strchrnul() with a check for '\0'.

Signed-off-by: Rohit Mani <rohit.mani@outlook.com>
---
I plan to apply for the GSoC.
 archive.c        |    4 ++--
 cache-tree.c     |   16 +++++++---------
 diff.c           |    9 +++------
 fast-import.c    |   35 ++++++++++++++---------------------
 match-trees.c    |   11 ++++-------
 parse-options.c  |    5 +----
 pretty.c         |    5 ++---
 remote-testsvn.c |    4 ++--
 ws.c             |    7 ++-----
 9 files changed, 37 insertions(+), 59 deletions(-)
Show changes to 9 files +37 −59

archive.c, cache-tree.c, diff.c, fast-import.c, match-trees.c, parse-options.c, pretty.c, remote-testsvn.c, ws.c

diff --git a/archive.c b/archive.c
index 346f3b2..d196215 100644
--- a/archive.c
+++ b/archive.c
@@ -259,8 +259,8 @@ static void parse_treeish_arg(const char **argv,
 	/* Remotes are only allowed to fetch actual refs */
 	if (remote) {
 		char *ref = NULL;
-		const char *colon = strchr(name, ':');
-		int refnamelen = colon ? colon - name : strlen(name);
+		const char *colon = strchrnul(name, ':');
+		int refnamelen = colon - name;
 
 		if (!dwim_ref(name, refnamelen, sha1, &ref))
 			die("no such ref: %.*s", refnamelen, name);
diff --git a/cache-tree.c b/cache-tree.c
index 0bbec43..21a13cf 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -121,11 +121,11 @@ void cache_tree_invalidate_path(struct cache_tree *it, const char *path)
 
 	if (!it)
 		return;
-	slash = strchr(path, '/');
+	slash = strchrnul(path, '/');
 	it->entry_count = -1;
-	if (!slash) {
+	if (*slash == '\0') {
 		int pos;
-		namelen = strlen(path);
+		namelen = slash - path;
 		pos = subtree_pos(it, path, namelen);
 		if (0 <= pos) {
 			cache_tree_free(&it->down[pos]->cache_tree);
@@ -554,9 +554,7 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat
 		const char *slash;
 		struct cache_tree_sub *sub;
 
-		slash = strchr(path, '/');
-		if (!slash)
-			slash = path + strlen(path);
+		slash = strchrnul(path, '/');
 		/* between path and slash is the name of the
 		 * subtree to look for.
 		 */
@@ -564,10 +562,10 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat
 		if (!sub)
 			return NULL;
 		it = sub->cache_tree;
-		if (slash)
-			while (*slash && *slash == '/')
+		if (*slash != '\0')
+			while (*slash != '\0' && *slash == '/')
 				slash++;
-		if (!slash || !*slash)
+		if (*slash == '\0' || !*slash)
 			return it; /* prefix ended with slashes */
 		path = slash;
 	}
diff --git a/diff.c b/diff.c
index e800666..83a039b 100644
--- a/diff.c
+++ b/diff.c
@@ -3365,14 +3365,11 @@ static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *va
 	if (c != '-')
 		return 0;
 	arg++;
-	eq = strchr(arg, '=');
-	if (eq)
-		len = eq - arg;
-	else
-		len = strlen(arg);
+	eq = strchrnul(arg, '=');
+	len = eq - arg;
 	if (!len || strncmp(arg, arg_long, len))
 		return 0;
-	if (eq) {
+	if (*eq != '\0') {
 		int n;
 		char *end;
 		if (!isdigit(*++eq))
diff --git a/fast-import.c b/fast-import.c
index 4fd18a3..449595d 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -1485,14 +1485,12 @@ static int tree_content_set(
 	unsigned int i, n;
 	struct tree_entry *e;
 
-	slash1 = strchr(p, '/');
-	if (slash1)
-		n = slash1 - p;
-	else
-		n = strlen(p);
+	slash1 = strchrnul(p, '/');
+	n = slash1 - p;
+
 	if (!n)
 		die("Empty path component found in input");
-	if (!slash1 && !S_ISDIR(mode) && subtree)
+	if (*slash1 == '\0' && !S_ISDIR(mode) && subtree)
 		die("Non-directories cannot have subtrees");
 
 	if (!root->tree)
@@ -1501,7 +1499,7 @@ static int tree_content_set(
 	for (i = 0; i < t->entry_count; i++) {
 		e = t->entries[i];
 		if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
-			if (!slash1) {
+			if (*slash1 == '\0') {
 				if (!S_ISDIR(mode)
 						&& e->versions[1].mode == mode
 						&& !hashcmp(e->versions[1].sha1, sha1))
@@ -1552,7 +1550,7 @@ static int tree_content_set(
 	e->versions[0].mode = 0;
 	hashclr(e->versions[0].sha1);
 	t->entries[t->entry_count++] = e;
-	if (slash1) {
+	if (*slash1 != '\0') {
 		e->tree = new_tree_content(8);
 		e->versions[1].mode = S_IFDIR;
 		tree_content_set(e, slash1 + 1, sha1, mode, subtree);
@@ -1576,11 +1574,8 @@ static int tree_content_remove(
 	unsigned int i, n;
 	struct tree_entry *e;
 
-	slash1 = strchr(p, '/');
-	if (slash1)
-		n = slash1 - p;
-	else
-		n = strlen(p);
+	slash1 = strchrnul(p, '/');
+	n = slash1 - p;
 
 	if (!root->tree)
 		load_tree(root);
@@ -1594,7 +1589,7 @@ static int tree_content_remove(
 	for (i = 0; i < t->entry_count; i++) {
 		e = t->entries[i];
 		if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
-			if (slash1 && !S_ISDIR(e->versions[1].mode))
+			if (*slash1 != '\0' && !S_ISDIR(e->versions[1].mode))
 				/*
 				 * If p names a file in some subdirectory, and a
 				 * file or symlink matching the name of the
@@ -1602,7 +1597,7 @@ static int tree_content_remove(
 				 * exist and need not be deleted.
 				 */
 				return 1;
-			if (!slash1 || !S_ISDIR(e->versions[1].mode))
+			if (*slash1 == '\0' || !S_ISDIR(e->versions[1].mode))
 				goto del_entry;
 			if (!e->tree)
 				load_tree(e);
@@ -1644,11 +1639,9 @@ static int tree_content_get(
 	unsigned int i, n;
 	struct tree_entry *e;
 
-	slash1 = strchr(p, '/');
-	if (slash1)
-		n = slash1 - p;
-	else
-		n = strlen(p);
+	slash1 = strchrnul(p, '/');
+	n = slash1 - p;
+
 	if (!n && !allow_root)
 		die("Empty path component found in input");
 
@@ -1664,7 +1657,7 @@ static int tree_content_get(
 	for (i = 0; i < t->entry_count; i++) {
 		e = t->entries[i];
 		if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
-			if (!slash1)
+			if (*slash1 == '\0')
 				goto found_entry;
 			if (!S_ISDIR(e->versions[1].mode))
 				return 0;
diff --git a/match-trees.c b/match-trees.c
index 7873cde..7cca0f6 100644
--- a/match-trees.c
+++ b/match-trees.c
@@ -182,13 +182,10 @@ static int splice_tree(const unsigned char *hash1,
 	enum object_type type;
 	int status;
 
-	subpath = strchr(prefix, '/');
-	if (!subpath)
-		toplen = strlen(prefix);
-	else {
-		toplen = subpath - prefix;
+	subpath = strchrnul(prefix, '/');
+	toplen = subpath - prefix;
+	if (*subpath != '\0')
 		subpath++;
-	}
 
 	buf = read_sha1_file(hash1, &type, &sz);
 	if (!buf)
@@ -215,7 +212,7 @@ static int splice_tree(const unsigned char *hash1,
 	if (!rewrite_here)
 		die("entry %.*s not found in tree %s",
 		    toplen, prefix, sha1_to_hex(hash1));
-	if (subpath) {
+	if (*subpath != '\0') {
 		status = splice_tree(rewrite_here, subpath, hash2, subtree);
 		if (status)
 			return status;
diff --git a/parse-options.c b/parse-options.c
index 7b8d3fa..a5fa0b8 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -223,13 +223,10 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
                           const struct option *options)
 {
 	const struct option *all_opts = options;
-	const char *arg_end = strchr(arg, '=');
+	const char *arg_end = strchrnul(arg, '=');
 	const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
 	int abbrev_flags = 0, ambiguous_flags = 0;
 
-	if (!arg_end)
-		arg_end = arg + strlen(arg);
-
 	for (; options->type != OPTION_END; options++) {
 		const char *rest, *long_name = options->long_name;
 		int flags = 0, opt_flags = 0;
diff --git a/pretty.c b/pretty.c
index 87db08b..b1f60f9 100644
--- a/pretty.c
+++ b/pretty.c
@@ -549,14 +549,13 @@ static char *get_header(const struct commit *commit, const char *msg,
 	const char *line = msg;
 
 	while (line) {
-		const char *eol = strchr(line, '\n'), *next;
+		const char *eol = strchrnul(line, '\n'), *next;
 
 		if (line == eol)
 			return NULL;
-		if (!eol) {
+		if (*eol == '\0') {
 			warning("malformed commit (header is missing newline): %s",
 				sha1_to_hex(commit->object.sha1));
-			eol = line + strlen(line);
 			next = NULL;
 		} else
 			next = eol + 1;
diff --git a/remote-testsvn.c b/remote-testsvn.c
index 078f1ff..6be55cb 100644
--- a/remote-testsvn.c
+++ b/remote-testsvn.c
@@ -78,8 +78,8 @@ static int parse_rev_note(const char *msg, struct rev_note *res)
 	size_t len;
 
 	while (*msg) {
-		end = strchr(msg, '\n');
-		len = end ? end - msg : strlen(msg);
+		end = strchrnul(msg, '\n');
+		len = end - msg;
 
 		key = "Revision-number: ";
 		if (starts_with(msg, key)) {
diff --git a/ws.c b/ws.c
index b498d75..ea4b2b1 100644
--- a/ws.c
+++ b/ws.c
@@ -33,11 +33,8 @@ unsigned parse_whitespace_rule(const char *string)
 		int negated = 0;
 
 		string = string + strspn(string, ", \t\n\r");
-		ep = strchr(string, ',');
-		if (!ep)
-			len = strlen(string);
-		else
-			len = ep - string;
+		ep = strchrnul(string, ',');
+		len = ep - string;
 
 		if (*string == '-') {
 			negated = 1;
-- 
1.7.9.5
Junio C Hamano· Mar 6, 2014, 20:54 UTC · re: Rohit Mani · lore

Re: [PATCH] use strchrnul() in place of strchr() and strlen()

Rohit Mani <rohit.mani@outlook.com> writes:
Show 6 quoted lines
> Avoid scanning strings twice, once with strchr() and then with
> strlen(), by using strchrnul(). Update the conditional expressions
> involving the return value of strchrnul() with a check for '\0'.
>
> Signed-off-by: Rohit Mani <rohit.mani@outlook.com>
> ---

Nicely done. I am not sure if you need to say the "update the conditional...", which is a logical consequence of such a conversion and goes without saying, though.

>  cache-tree.c     |   16 +++++++---------

This part may overlap with other topics in flight, but I expect the conflict resolution would be trivial.

Show 13 quoted lines
> diff --git a/cache-tree.c b/cache-tree.c
> index 0bbec43..21a13cf 100644
> --- a/cache-tree.c
> +++ b/cache-tree.c
> @@ -121,11 +121,11 @@ void cache_tree_invalidate_path(struct cache_tree *it, const char *path)
>  
>  	if (!it)
>  		return;
> -	slash = strchr(path, '/');
> +	slash = strchrnul(path, '/');
>  	it->entry_count = -1;
> -	if (!slash) {
> +	if (*slash == '\0') {
Let's just say
	if (!*slash)
instead; it is more idiomatic (I won't repeat this for other hunks).
>  		int pos;
> -		namelen = strlen(path);
> +		namelen = slash - path;

After this "if (!*slash)", we compute "namelen = slash-path". Perhaps we can lose this assignment and the other one by hoisting it up before "if (!*slash)"?

> @@ -564,10 +562,10 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat
> +		if (*slash == '\0' || !*slash)
Huh?  "The byte pointed at by 'slash' is NUL, or it is NUL"???
Other than that, looks good to me.
Thanks.
Rohit Mani· Mar 8, 2014, 06:48 UTC · lore

[GSoC][PATCH v2] use strchrnul() in place of strchr() and strlen()

Avoid scanning strings twice, once with strchr() and then with strlen(), by using strchrnul().

Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Rohit Mani <rohit.mani@outlook.com>
---
PATCH v2 Updated commit description
	Updated code according to suggestions [1]
[1]	http://article.gmane.org/gmane.comp.version-control.git/243550
	
 archive.c        |    4 ++--
 cache-tree.c     |   15 ++++++---------
 diff.c           |    9 +++------
 fast-import.c    |   35 +++++++++++++----------------------
 match-trees.c    |   11 ++++-------
 parse-options.c  |    5 +----
 pretty.c         |    5 ++---
 remote-testsvn.c |    4 ++--
 ws.c             |    7 ++-----
 9 files changed, 35 insertions(+), 60 deletions(-)
Show changes to 9 files +35 −60

archive.c, cache-tree.c, diff.c, fast-import.c, match-trees.c, parse-options.c, pretty.c, remote-testsvn.c, ws.c

diff --git a/archive.c b/archive.c
index 346f3b2..d196215 100644
--- a/archive.c
+++ b/archive.c
@@ -259,8 +259,8 @@ static void parse_treeish_arg(const char **argv,
 	/* Remotes are only allowed to fetch actual refs */
 	if (remote) {
 		char *ref = NULL;
-		const char *colon = strchr(name, ':');
-		int refnamelen = colon ? colon - name : strlen(name);
+		const char *colon = strchrnul(name, ':');
+		int refnamelen = colon - name;
 
 		if (!dwim_ref(name, refnamelen, sha1, &ref))
 			die("no such ref: %.*s", refnamelen, name);
diff --git a/cache-tree.c b/cache-tree.c
index 0bbec43..2130f32 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -121,11 +121,11 @@ void cache_tree_invalidate_path(struct cache_tree *it, const char *path)
 
 	if (!it)
 		return;
-	slash = strchr(path, '/');
+	slash = strchrnul(path, '/');
+	namelen = slash - path;
 	it->entry_count = -1;
-	if (!slash) {
+	if (!*slash) {
 		int pos;
-		namelen = strlen(path);
 		pos = subtree_pos(it, path, namelen);
 		if (0 <= pos) {
 			cache_tree_free(&it->down[pos]->cache_tree);
@@ -143,7 +143,6 @@ void cache_tree_invalidate_path(struct cache_tree *it, const char *path)
 		}
 		return;
 	}
-	namelen = slash - path;
 	down = find_subtree(it, path, namelen, 0);
 	if (down)
 		cache_tree_invalidate_path(down->cache_tree, slash + 1);
@@ -554,9 +553,7 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat
 		const char *slash;
 		struct cache_tree_sub *sub;
 
-		slash = strchr(path, '/');
-		if (!slash)
-			slash = path + strlen(path);
+		slash = strchrnul(path, '/');
 		/* between path and slash is the name of the
 		 * subtree to look for.
 		 */
@@ -564,10 +561,10 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat
 		if (!sub)
 			return NULL;
 		it = sub->cache_tree;
-		if (slash)
+		if (*slash)
 			while (*slash && *slash == '/')
 				slash++;
-		if (!slash || !*slash)
+		if (!*slash)
 			return it; /* prefix ended with slashes */
 		path = slash;
 	}
diff --git a/diff.c b/diff.c
index e800666..cb6b98b 100644
--- a/diff.c
+++ b/diff.c
@@ -3365,14 +3365,11 @@ static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *va
 	if (c != '-')
 		return 0;
 	arg++;
-	eq = strchr(arg, '=');
-	if (eq)
-		len = eq - arg;
-	else
-		len = strlen(arg);
+	eq = strchrnul(arg, '=');
+	len = eq - arg;
 	if (!len || strncmp(arg, arg_long, len))
 		return 0;
-	if (eq) {
+	if (*eq) {
 		int n;
 		char *end;
 		if (!isdigit(*++eq))
diff --git a/fast-import.c b/fast-import.c
index 4fd18a3..dbbf6b0 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -1485,14 +1485,11 @@ static int tree_content_set(
 	unsigned int i, n;
 	struct tree_entry *e;
 
-	slash1 = strchr(p, '/');
-	if (slash1)
-		n = slash1 - p;
-	else
-		n = strlen(p);
+	slash1 = strchrnul(p, '/');
+	n = slash1 - p;
 	if (!n)
 		die("Empty path component found in input");
-	if (!slash1 && !S_ISDIR(mode) && subtree)
+	if (!*slash1 && !S_ISDIR(mode) && subtree)
 		die("Non-directories cannot have subtrees");
 
 	if (!root->tree)
@@ -1501,7 +1498,7 @@ static int tree_content_set(
 	for (i = 0; i < t->entry_count; i++) {
 		e = t->entries[i];
 		if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
-			if (!slash1) {
+			if (!*slash1) {
 				if (!S_ISDIR(mode)
 						&& e->versions[1].mode == mode
 						&& !hashcmp(e->versions[1].sha1, sha1))
@@ -1552,7 +1549,7 @@ static int tree_content_set(
 	e->versions[0].mode = 0;
 	hashclr(e->versions[0].sha1);
 	t->entries[t->entry_count++] = e;
-	if (slash1) {
+	if (*slash1) {
 		e->tree = new_tree_content(8);
 		e->versions[1].mode = S_IFDIR;
 		tree_content_set(e, slash1 + 1, sha1, mode, subtree);
@@ -1576,12 +1573,9 @@ static int tree_content_remove(
 	unsigned int i, n;
 	struct tree_entry *e;
 
-	slash1 = strchr(p, '/');
-	if (slash1)
-		n = slash1 - p;
-	else
-		n = strlen(p);
-
+	slash1 = strchrnul(p, '/');
+	n = slash1 - p;
+	
 	if (!root->tree)
 		load_tree(root);
 
@@ -1594,7 +1588,7 @@ static int tree_content_remove(
 	for (i = 0; i < t->entry_count; i++) {
 		e = t->entries[i];
 		if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
-			if (slash1 && !S_ISDIR(e->versions[1].mode))
+			if (*slash1 && !S_ISDIR(e->versions[1].mode))
 				/*
 				 * If p names a file in some subdirectory, and a
 				 * file or symlink matching the name of the
@@ -1602,7 +1596,7 @@ static int tree_content_remove(
 				 * exist and need not be deleted.
 				 */
 				return 1;
-			if (!slash1 || !S_ISDIR(e->versions[1].mode))
+			if (!*slash1 || !S_ISDIR(e->versions[1].mode))
 				goto del_entry;
 			if (!e->tree)
 				load_tree(e);
@@ -1644,11 +1638,8 @@ static int tree_content_get(
 	unsigned int i, n;
 	struct tree_entry *e;
 
-	slash1 = strchr(p, '/');
-	if (slash1)
-		n = slash1 - p;
-	else
-		n = strlen(p);
+	slash1 = strchrnul(p, '/');
+	n = slash1 - p;
 	if (!n && !allow_root)
 		die("Empty path component found in input");
 
@@ -1664,7 +1655,7 @@ static int tree_content_get(
 	for (i = 0; i < t->entry_count; i++) {
 		e = t->entries[i];
 		if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
-			if (!slash1)
+			if (!*slash1)
 				goto found_entry;
 			if (!S_ISDIR(e->versions[1].mode))
 				return 0;
diff --git a/match-trees.c b/match-trees.c
index 7873cde..e80b4af 100644
--- a/match-trees.c
+++ b/match-trees.c
@@ -182,13 +182,10 @@ static int splice_tree(const unsigned char *hash1,
 	enum object_type type;
 	int status;
 
-	subpath = strchr(prefix, '/');
-	if (!subpath)
-		toplen = strlen(prefix);
-	else {
-		toplen = subpath - prefix;
+	subpath = strchrnul(prefix, '/');
+	toplen = subpath - prefix;
+	if (*subpath)
 		subpath++;
-	}
 
 	buf = read_sha1_file(hash1, &type, &sz);
 	if (!buf)
@@ -215,7 +212,7 @@ static int splice_tree(const unsigned char *hash1,
 	if (!rewrite_here)
 		die("entry %.*s not found in tree %s",
 		    toplen, prefix, sha1_to_hex(hash1));
-	if (subpath) {
+	if (*subpath) {
 		status = splice_tree(rewrite_here, subpath, hash2, subtree);
 		if (status)
 			return status;
diff --git a/parse-options.c b/parse-options.c
index 7b8d3fa..a5fa0b8 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -223,13 +223,10 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
                           const struct option *options)
 {
 	const struct option *all_opts = options;
-	const char *arg_end = strchr(arg, '=');
+	const char *arg_end = strchrnul(arg, '=');
 	const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
 	int abbrev_flags = 0, ambiguous_flags = 0;
 
-	if (!arg_end)
-		arg_end = arg + strlen(arg);
-
 	for (; options->type != OPTION_END; options++) {
 		const char *rest, *long_name = options->long_name;
 		int flags = 0, opt_flags = 0;
diff --git a/pretty.c b/pretty.c
index 87db08b..99ba8ae 100644
--- a/pretty.c
+++ b/pretty.c
@@ -549,14 +549,13 @@ static char *get_header(const struct commit *commit, const char *msg,
 	const char *line = msg;
 
 	while (line) {
-		const char *eol = strchr(line, '\n'), *next;
+		const char *eol = strchrnul(line, '\n'), *next;
 
 		if (line == eol)
 			return NULL;
-		if (!eol) {
+		if (!*eol) {
 			warning("malformed commit (header is missing newline): %s",
 				sha1_to_hex(commit->object.sha1));
-			eol = line + strlen(line);
 			next = NULL;
 		} else
 			next = eol + 1;
diff --git a/remote-testsvn.c b/remote-testsvn.c
index 078f1ff..6be55cb 100644
--- a/remote-testsvn.c
+++ b/remote-testsvn.c
@@ -78,8 +78,8 @@ static int parse_rev_note(const char *msg, struct rev_note *res)
 	size_t len;
 
 	while (*msg) {
-		end = strchr(msg, '\n');
-		len = end ? end - msg : strlen(msg);
+		end = strchrnul(msg, '\n');
+		len = end - msg;
 
 		key = "Revision-number: ";
 		if (starts_with(msg, key)) {
diff --git a/ws.c b/ws.c
index b498d75..ea4b2b1 100644
--- a/ws.c
+++ b/ws.c
@@ -33,11 +33,8 @@ unsigned parse_whitespace_rule(const char *string)
 		int negated = 0;
 
 		string = string + strspn(string, ", \t\n\r");
-		ep = strchr(string, ',');
-		if (!ep)
-			len = strlen(string);
-		else
-			len = ep - string;
+		ep = strchrnul(string, ',');
+		len = ep - string;
 
 		if (*string == '-') {
 			negated = 1;
-- 
1.7.9.5
Junio C Hamano· Mar 10, 2014, 15:36 UTC · re: Rohit Mani · lore

Re: [GSoC][PATCH v2] use strchrnul() in place of strchr() and strlen()

Rohit Mani <rohit.mani@outlook.com> writes:
> Avoid scanning strings twice, once with strchr() and then with
> strlen(), by using strchrnul().
Thanks. The patch looks good.
TamerTas· Mar 10, 2014, 19:04 UTC · lore

[PATCH][GSOC2014]

I inspected branch.c:install_branch_config() and since the conditionals cover all the possibilities, I decided that making the code table-driven would be much cleaner and shorter. I've ran the tests and they all passed. Reimplementation using "git am" also didn't have any problems. Please let me know if there is any problems that I missed. I would also love to learn if you think that the code was better before and why it was better.

TamerTas (1):
  changed logical chain in branch.c to lookup tables
 branch.c |   31 ++++++++-----------------------
 1 file changed, 8 insertions(+), 23 deletions(-)
-- 
1.7.9.5
TamerTas· Mar 10, 2014, 19:04 UTC · lore

[PATCH][GSOC2014] changed logical chain in branch.c to lookup tables

Signed-off-by: TamerTas <tamertas@outlook.com>
---
 branch.c |   31 ++++++++-----------------------
 1 file changed, 8 insertions(+), 23 deletions(-)
Show changes to branch.c +8 −23
diff --git a/branch.c b/branch.c
index 723a36b..397edd3 100644
--- a/branch.c
+++ b/branch.c
@@ -50,6 +50,9 @@ static int should_setup_rebase(const char *origin)
 void install_branch_config(int flag, const char *local, const char *origin, const char *remote)
 {
 	const char *shortname = remote + 11;
+	const char *location[] = {"local", "remote"};
+	const char *type[] = {"branch", "ref"};
+
 	int remote_is_branch = starts_with(remote, "refs/heads/");
 	struct strbuf key = STRBUF_INIT;
 	int rebasing = should_setup_rebase(origin);
@@ -77,29 +80,11 @@ void install_branch_config(int flag, const char *local, const char *origin, cons
 	strbuf_release(&key);
 
 	if (flag & BRANCH_CONFIG_VERBOSE) {
-		if (remote_is_branch && origin)
-			printf_ln(rebasing ?
-				  _("Branch %s set up to track remote branch %s from %s by rebasing.") :
-				  _("Branch %s set up to track remote branch %s from %s."),
-				  local, shortname, origin);
-		else if (remote_is_branch && !origin)
-			printf_ln(rebasing ?
-				  _("Branch %s set up to track local branch %s by rebasing.") :
-				  _("Branch %s set up to track local branch %s."),
-				  local, shortname);
-		else if (!remote_is_branch && origin)
-			printf_ln(rebasing ?
-				  _("Branch %s set up to track remote ref %s by rebasing.") :
-				  _("Branch %s set up to track remote ref %s."),
-				  local, remote);
-		else if (!remote_is_branch && !origin)
-			printf_ln(rebasing ?
-				  _("Branch %s set up to track local ref %s by rebasing.") :
-				  _("Branch %s set up to track local ref %s."),
-				  local, remote);
-		else
-			die("BUG: impossible combination of %d and %p",
-			    remote_is_branch, origin);
+        
+        printf_ln(rebasing ?
+              _("Branch %s set up to track %s %s %s by rebasing.") :
+              _("Branch %s set up to track %s %s %s."),
+              local, location[!origin], type[remote_is_branch], remote);
 	}
 }
 
-- 
1.7.9.5
Stefan Beller· Mar 10, 2014, 21:05 UTC · re: TamerTas · lore

Re: [PATCH][GSOC2014] changed logical chain in branch.c to lookup tables

On 10.03.2014 20:04, TamerTas wrote:
Show 56 quoted lines
> 
> Signed-off-by: TamerTas <tamertas@outlook.com>
> ---
>  branch.c |   31 ++++++++-----------------------
>  1 file changed, 8 insertions(+), 23 deletions(-)
> 
> diff --git a/branch.c b/branch.c
> index 723a36b..397edd3 100644
> --- a/branch.c
> +++ b/branch.c
> @@ -50,6 +50,9 @@ static int should_setup_rebase(const char *origin)
>  void install_branch_config(int flag, const char *local, const char *origin, const char *remote)
>  {
>  	const char *shortname = remote + 11;
> +	const char *location[] = {"local", "remote"};
> +	const char *type[] = {"branch", "ref"};
> +
>  	int remote_is_branch = starts_with(remote, "refs/heads/");
>  	struct strbuf key = STRBUF_INIT;
>  	int rebasing = should_setup_rebase(origin);
> @@ -77,29 +80,11 @@ void install_branch_config(int flag, const char *local, const char *origin, cons
>  	strbuf_release(&key);
>  
>  	if (flag & BRANCH_CONFIG_VERBOSE) {
> -		if (remote_is_branch && origin)
> -			printf_ln(rebasing ?
> -				  _("Branch %s set up to track remote branch %s from %s by rebasing.") :
> -				  _("Branch %s set up to track remote branch %s from %s."),
> -				  local, shortname, origin);
> -		else if (remote_is_branch && !origin)
> -			printf_ln(rebasing ?
> -				  _("Branch %s set up to track local branch %s by rebasing.") :
> -				  _("Branch %s set up to track local branch %s."),
> -				  local, shortname);
> -		else if (!remote_is_branch && origin)
> -			printf_ln(rebasing ?
> -				  _("Branch %s set up to track remote ref %s by rebasing.") :
> -				  _("Branch %s set up to track remote ref %s."),
> -				  local, remote);
> -		else if (!remote_is_branch && !origin)
> -			printf_ln(rebasing ?
> -				  _("Branch %s set up to track local ref %s by rebasing.") :
> -				  _("Branch %s set up to track local ref %s."),
> -				  local, remote);
> -		else
> -			die("BUG: impossible combination of %d and %p",
> -			    remote_is_branch, origin);
> +        
> +        printf_ln(rebasing ?
> +              _("Branch %s set up to track %s %s %s by rebasing.") :
> +              _("Branch %s set up to track %s %s %s."),
> +              local, location[!origin], type[remote_is_branch], remote);
>  	}
>  }
>  
> 

This one is neat, I'd favor this one as opposed to the one posted earlier today.

Acked-by: Stefan Beller <stefanbeller@googlemail.com>
Eric Sunshine· Mar 10, 2014, 21:25 UTC · re: TamerTas · lore

Re: [PATCH][GSOC2014] changed logical chain in branch.c to lookup tables

On Mon, Mar 10, 2014 at 3:04 PM, TamerTas <tamertas@outlook.com> wrote:
> Signed-off-by: TamerTas <tamertas@outlook.com>

Thanks for the submission. It appears to be well executed. Read below for a concern about the approach taken.

Show 50 quoted lines
> ---
>  branch.c |   31 ++++++++-----------------------
>  1 file changed, 8 insertions(+), 23 deletions(-)
>
> diff --git a/branch.c b/branch.c
> index 723a36b..397edd3 100644
> --- a/branch.c
> +++ b/branch.c
> @@ -50,6 +50,9 @@ static int should_setup_rebase(const char *origin)
>  void install_branch_config(int flag, const char *local, const char *origin, const char *remote)
>  {
>         const char *shortname = remote + 11;
> +       const char *location[] = {"local", "remote"};
> +       const char *type[] = {"branch", "ref"};
> +
>         int remote_is_branch = starts_with(remote, "refs/heads/");
>         struct strbuf key = STRBUF_INIT;
>         int rebasing = should_setup_rebase(origin);
> @@ -77,29 +80,11 @@ void install_branch_config(int flag, const char *local, const char *origin, cons
>         strbuf_release(&key);
>
>         if (flag & BRANCH_CONFIG_VERBOSE) {
> -               if (remote_is_branch && origin)
> -                       printf_ln(rebasing ?
> -                                 _("Branch %s set up to track remote branch %s from %s by rebasing.") :
> -                                 _("Branch %s set up to track remote branch %s from %s."),
> -                                 local, shortname, origin);
> -               else if (remote_is_branch && !origin)
> -                       printf_ln(rebasing ?
> -                                 _("Branch %s set up to track local branch %s by rebasing.") :
> -                                 _("Branch %s set up to track local branch %s."),
> -                                 local, shortname);
> -               else if (!remote_is_branch && origin)
> -                       printf_ln(rebasing ?
> -                                 _("Branch %s set up to track remote ref %s by rebasing.") :
> -                                 _("Branch %s set up to track remote ref %s."),
> -                                 local, remote);
> -               else if (!remote_is_branch && !origin)
> -                       printf_ln(rebasing ?
> -                                 _("Branch %s set up to track local ref %s by rebasing.") :
> -                                 _("Branch %s set up to track local ref %s."),
> -                                 local, remote);
> -               else
> -                       die("BUG: impossible combination of %d and %p",
> -                           remote_is_branch, origin);
> +
> +        printf_ln(rebasing ?
> +              _("Branch %s set up to track %s %s %s by rebasing.") :
> +              _("Branch %s set up to track %s %s %s."),
> +              local, location[!origin], type[remote_is_branch], remote);

The GSoC microproject talks about the _() function used for internationalization and the limitations it places on string composition. This change assumes, probably incorrectly, that strings "local", "remote", "branch" and "ref" do not need to be localized. Even allowing internationalization of them (via N_() in the location[] and type[] tables) might not be sufficient since grammatical rules differ from language to language.

Show 5 quoted lines
>         }
>  }
>
> --
> 1.7.9.5
Tamer TAS· Mar 10, 2014, 21:47 UTC · re: Eric Sunshine · lore

Re: [PATCH][GSOC2014] changed logical chain in branch.c to lookup tables

Eric Sunshine wrote
> Even allowing internationalization of them (via N_() in the location[]
> and type[] tables) might not be sufficient since grammatical rules
> differ from language to language.

I didn't fully understand what you meant by that. Since they were being internationalized before using _() in the if-else bodies, wouldn't it produce the same output if I were to use the same method for location[] and type[] tables?

-- View this message in context: http://git.661346.n2.nabble.com/PATCH-GSOC2014-changed-logical-chain-in-branch-c-to-lookup-tables-tp7605343p7605372.html Sent from the git mailing list archive at Nabble.com.

Eric Sunshine· Mar 10, 2014, 22:39 UTC · re: Tamer TAS · lore

Re: [PATCH][GSOC2014] changed logical chain in branch.c to lookup tables

On Mon, Mar 10, 2014 at 5:47 PM, Tamer TAS <tamertas@outlook.com> wrote:
Show 10 quoted lines
> Eric Sunshine wrote
>> Even allowing internationalization of them (via N_() in the location[]
>> and type[] tables) might not be sufficient since grammatical rules
>> differ from language to language.
>
> I didn't fully understand what you meant by that. Since they were being
> internationalized before using _() in the if-else bodies, wouldn't it
> produce
> the same output if I were to use the same method for location[] and type[]
> tables?
In the original code, the person translating the text has the full context:
    "Branch %s set up to track remote ref %s."

With your revision, the translator has to translate standalone words "local", "remote", "branch", "ref" without context about how they are used. He then has to translate:

    "Branch %s set up to track %s %s %s."

without context of the words being inserted, which, depending upon grammatical rules of the language can result in a different (and perhaps worse) translation than the original full-context translation.

Section 4.3 of the GNU gettext manual [1] explains the issues in more detail. I urge you to read it. The upshot is that translators fare best when handed full sentences.

Note also that your change effectively reverts d53a35032a67 [2], which did away with the sort of string composition used in your patch.

[1]: http://www.gnu.org/software/gettext/manual/gettext.html#Preparing-Strings [2]: https://github.com/git/git/commit/d53a35032a67fde5b59c8a6a66e0466837cbaf1e

Tamer TAS· Mar 11, 2014, 11:33 UTC · re: Eric Sunshine · lore

Re: [PATCH][GSOC2014] changed logical chain in branch.c to lookup tables

Eric Sunshine wrote
> On Mon, Mar 10, 2014 at 5:47 PM, Tamer TAS &lt;
> tamertas@
Show 8 quoted lines
> &gt; wrote:
> 
> Section 4.3 of the GNU gettext manual [1] explains the issues in more
> detail. I urge you to read it. The upshot is that translators fare
> best when handed full sentences.
> 
> Note also that your change effectively reverts d53a35032a67 [2], which
> did away with the sort of string composition used in your patch.

Eric thank you for your constructive feedbacks. I read the section 4.3 of GNU gettext manual and also checked the commit you mentioned. It seems like that my previous changes were not internationalization compatible. In order for a table-driven change to be compatible, the sentences has to be meaningful and not tokenized. I made the following change to the branch.c in order for the function to be both table-driven and internationalization compatible. Let me know if there are any oversights on my part.

Signed-off-by: TamerTas <tamertas@outlook.com>
---
 branch.c |   39 ++++++++++++++++-----------------------
 1 file changed, 16 insertions(+), 23 deletions(-)
Show changes to branch.c +16 −23
diff --git a/branch.c b/branch.c
index 723a36b..4c04638 100644
--- a/branch.c
+++ b/branch.c
@@ -50,10 +50,25 @@ static int should_setup_rebase(const char *origin)
 void install_branch_config(int flag, const char *local, const char *origin,
const char *remote)
 {
 	const char *shortname = remote + 11;
+    const char *setup_messages[] = {
+		_("Branch %s set up to track remote branch %s from %s."),
+		_("Branch %s set up to track local branch %s."),
+		_("Branch %s set up to track remote ref %s."),
+		_("Branch %s set up to track local ref %s."),
+		_("Branch %s set up to track remote branch %s from %s by rebasing."),
+		_("Branch %s set up to track local branch %s by rebasing."),
+		_("Branch %s set up to track remote ref %s by rebasing."),
+		_("Branch %s set up to track local ref %s by rebasing.")
+	}; 
+
 	int remote_is_branch = starts_with(remote, "refs/heads/");
 	struct strbuf key = STRBUF_INIT;
 	int rebasing = should_setup_rebase(origin);
 
+    int msg_index = (!!origin           >> 0) +
+					(!!remote_is_branch >> 1) +
+					(!!rebasing         >> 2);
+   
 	if (remote_is_branch
 	    && !strcmp(local, shortname)
 	    && !origin) {
@@ -77,29 +92,7 @@ void install_branch_config(int flag, const char *local,
const char *origin, cons
 	strbuf_release(&key);
 
 	if (flag & BRANCH_CONFIG_VERBOSE) {
-		if (remote_is_branch && origin)
-			printf_ln(rebasing ?
-				  _("Branch %s set up to track remote branch %s from %s by rebasing.")
:
-				  _("Branch %s set up to track remote branch %s from %s."),
-				  local, shortname, origin);
-		else if (remote_is_branch && !origin)
-			printf_ln(rebasing ?
-				  _("Branch %s set up to track local branch %s by rebasing.") :
-				  _("Branch %s set up to track local branch %s."),
-				  local, shortname);
-		else if (!remote_is_branch && origin)
-			printf_ln(rebasing ?
-				  _("Branch %s set up to track remote ref %s by rebasing.") :
-				  _("Branch %s set up to track remote ref %s."),
-				  local, remote);
-		else if (!remote_is_branch && !origin)
-			printf_ln(rebasing ?
-				  _("Branch %s set up to track local ref %s by rebasing.") :
-				  _("Branch %s set up to track local ref %s."),
-				  local, remote);
-		else
-			die("BUG: impossible combination of %d and %p",
-			    remote_is_branch, origin);
+		printf_ln(setup_messages[msg_index], local, remote);
 	}
 }
 
-- 
1.7.9.5



--
View this message in context: http://git.661346.n2.nabble.com/PATCH-GSOC2014-changed-logical-chain-in-branch-c-to-lookup-tables-tp7605343p7605407.html
Sent from the git mailing list archive at Nabble.com.
Eric Sunshine· Mar 11, 2014, 20:32 UTC · re: Tamer TAS · lore

Re: [PATCH][GSOC2014] changed logical chain in branch.c to lookup tables

Thanks for the resubmission. Comments below.
On Tue, Mar 11, 2014 at 7:33 AM, Tamer TAS <tamertas@outlook.com> wrote:
> Subject: changed logical chain in branch.c to lookup tables
Use imperative tone: "change" rather than "changed"

Prefix the message with the part of the project you are touching. So, for instance, you might write:

    Subject: install_branch_config: change logical chain to lookup table
Show 25 quoted lines
> Eric Sunshine wrote
>> On Mon, Mar 10, 2014 at 5:47 PM, Tamer TAS &lt;
>
>> tamertas@
>
>> &gt; wrote:
>>
>> Section 4.3 of the GNU gettext manual [1] explains the issues in more
>> detail. I urge you to read it. The upshot is that translators fare
>> best when handed full sentences.
>>
>> Note also that your change effectively reverts d53a35032a67 [2], which
>> did away with the sort of string composition used in your patch.
>
> Eric thank you for your constructive feedbacks.
> I read the section 4.3 of GNU gettext manual and also checked the commit you
> mentioned.
> It seems like that my previous changes were not internationalization
> compatible.
> In order for a table-driven change to be compatible, the sentences has to be
> meaningful and not tokenized.
> I made the following change to the branch.c in order for the function to be
> both table-driven and
> internationalization compatible. Let me know if there are any oversights on
> my part.

This commentary is relevant to the ongoing email thread but not suitable for the permanent commit message. Place it below the "---" line just under your sign-off.

> Signed-off-by: TamerTas <tamertas@outlook.com>
> ---

It's considerate to reviewers to provide a link to the previous attempt, like this [1]. This area below the "---" line is the appropriate place to do so. Likewise, explain how this version differs from the previous one.

[1]: http://thread.gmane.org/gmane.comp.version-control.git/243793
Show 10 quoted lines
>  branch.c |   39 ++++++++++++++++-----------------------
>  1 file changed, 16 insertions(+), 23 deletions(-)
>
> diff --git a/branch.c b/branch.c
> index 723a36b..4c04638 100644
> --- a/branch.c
> +++ b/branch.c
> @@ -50,10 +50,25 @@ static int should_setup_rebase(const char *origin)
>  void install_branch_config(int flag, const char *local, const char *origin,
> const char *remote)

Your patch is whitespace damaged, probably due to being pasted into your email. "git send-email" avoids such problems.

Indentation is also broken. Use tabs for indentation, and set tab width to 8 in your editor, as explained in Documentation/CodingGuidelines.

Show 12 quoted lines
>  {
>         const char *shortname = remote + 11;
> +    const char *setup_messages[] = {
> +               _("Branch %s set up to track remote branch %s from %s."),
> +               _("Branch %s set up to track local branch %s."),
> +               _("Branch %s set up to track remote ref %s."),
> +               _("Branch %s set up to track local ref %s."),
> +               _("Branch %s set up to track remote branch %s from %s by rebasing."),
> +               _("Branch %s set up to track local branch %s by rebasing."),
> +               _("Branch %s set up to track remote ref %s by rebasing."),
> +               _("Branch %s set up to track local ref %s by rebasing.")
> +       };

On this project, arrays are usually (though not consistently) named in singular form (for instance setup_message[]) so that a reference to a single item, such as setup_message[42], reads more grammatically correct.

It's not correct to use _() inside the initializer list. Instead use N_(). Read section 4.7 of the GNU gettext manual [2] for an explanation. You will still need to use _() at the point where you extract the message from the array.

[2]: http://www.gnu.org/software/gettext/manual/gettext.html#Special-cases
Show 7 quoted lines
>         int remote_is_branch = starts_with(remote, "refs/heads/");
>         struct strbuf key = STRBUF_INIT;
>         int rebasing = should_setup_rebase(origin);
>
> +    int msg_index = (!!origin           >> 0) +
> +                                       (!!remote_is_branch >> 1) +
> +                                       (!!rebasing         >> 2);

Are you sure this is correct? As I read it, msg_index will only ever have a value of 0 or 1, which is unlikely what you intended.

Show 33 quoted lines
>         if (remote_is_branch
>             && !strcmp(local, shortname)
>             && !origin) {
> @@ -77,29 +92,7 @@ void install_branch_config(int flag, const char *local,
> const char *origin, cons
>         strbuf_release(&key);
>
>         if (flag & BRANCH_CONFIG_VERBOSE) {
> -               if (remote_is_branch && origin)
> -                       printf_ln(rebasing ?
> -                                 _("Branch %s set up to track remote branch %s from %s by rebasing.")
> :
> -                                 _("Branch %s set up to track remote branch %s from %s."),
> -                                 local, shortname, origin);
> -               else if (remote_is_branch && !origin)
> -                       printf_ln(rebasing ?
> -                                 _("Branch %s set up to track local branch %s by rebasing.") :
> -                                 _("Branch %s set up to track local branch %s."),
> -                                 local, shortname);
> -               else if (!remote_is_branch && origin)
> -                       printf_ln(rebasing ?
> -                                 _("Branch %s set up to track remote ref %s by rebasing.") :
> -                                 _("Branch %s set up to track remote ref %s."),
> -                                 local, remote);
> -               else if (!remote_is_branch && !origin)
> -                       printf_ln(rebasing ?
> -                                 _("Branch %s set up to track local ref %s by rebasing.") :
> -                                 _("Branch %s set up to track local ref %s."),
> -                                 local, remote);
> -               else
> -                       die("BUG: impossible combination of %d and %p",
> -                           remote_is_branch, origin);
> +               printf_ln(setup_messages[msg_index], local, remote);
This can't be correct.

In the original code, all of the printf_ln() invocations received 'local' as the first argument, but only two of them received 'remote', yet you are passing 'remote' on every invocation.

Worse, the first and fifths items in your setup_messages[] table expect three arguments (they have three %s's), but you're passing only two, which will surely lead to a crash.

Also, wrap _() around setup_messages[msg_index], as noted above.
Show 5 quoted lines
>         }
>  }
>
> --
> 1.7.9.5
Movchan Pavel· Mar 18, 2014, 12:31 UTC · lore

[PATCH][GSOC2014] add: Rewrite run_add_interactive to use struct argv_array

Origin code are code with own realisation argv array editing. It was changed, and code modified for using unified argv-array realisation from argv-array.h. Commit for Google Summer of Code 2014

Signed-off-by: Movchan Pavel <movchan.pv@gmail.com>
---
 builtin/add.c |   21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)
Show changes to builtin/add.c +10 −11
diff --git a/builtin/add.c b/builtin/add.c
index 4b045ba..258b491 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -15,6 +15,7 @@
 #include "diffcore.h"
 #include "revision.h"
 #include "bulk-checkin.h"
+#include "argv-array.h"
 
 static const char * const builtin_add_usage[] = {
 	N_("git add [options] [--] <pathspec>..."),
@@ -141,23 +142,21 @@ static void refresh(int verbose, const struct pathspec *pathspec)
 int run_add_interactive(const char *revision, const char *patch_mode,
 			const struct pathspec *pathspec)
 {
-	int status, ac, i;
-	const char **args;
+	int status, i;
+	struct argv_array *argv = ARGV_ARRAY_INIT;
 
-	args = xcalloc(sizeof(const char *), (pathspec->nr + 6));
-	ac = 0;
-	args[ac++] = "add--interactive";
+	argv_array_push(argv, "add--interactive");
 	if (patch_mode)
-		args[ac++] = patch_mode;
+		argv_array_push(argv, patch_mode);
 	if (revision)
-		args[ac++] = revision;
-	args[ac++] = "--";
+		argv_array_push(argv, revision);
+	argv_array_push(argv, "--");
 	for (i = 0; i < pathspec->nr; i++)
 		/* pass original pathspec, to be re-parsed */
-		args[ac++] = pathspec->items[i].original;
+		argv_array_push(argv, pathspec->items[i].original);
 
-	status = run_command_v_opt(args, RUN_GIT_CMD);
-	free(args);
+	status = run_command_v_opt(argv->argv, RUN_GIT_CMD);
+	argv_array_clear(argv);
 	return status;
 }
 
-- 
1.7.10.4
Junio C Hamano· Mar 18, 2014, 19:47 UTC · re: Movchan Pavel · lore

Re: [PATCH][GSOC2014] add: Rewrite run_add_interactive to use struct argv_array

Movchan Pavel <movchan.pv@gmail.com> writes:
Show 7 quoted lines
> Origin code are code with own realisation argv array editing.
> It was changed, and code modified for using unified argv-array
> realisation from argv-array.h.
> Commit for Google Summer of Code 2014
>
> Signed-off-by: Movchan Pavel <movchan.pv@gmail.com>
> ---

Thanks. "Commit for ..." is not something we would want to see in "git log" output, though.

Show 23 quoted lines
>  builtin/add.c |   21 ++++++++++-----------
>  1 file changed, 10 insertions(+), 11 deletions(-)
>
> diff --git a/builtin/add.c b/builtin/add.c
> index 4b045ba..258b491 100644
> --- a/builtin/add.c
> +++ b/builtin/add.c
> @@ -15,6 +15,7 @@
>  #include "diffcore.h"
>  #include "revision.h"
>  #include "bulk-checkin.h"
> +#include "argv-array.h"
>  
>  static const char * const builtin_add_usage[] = {
>  	N_("git add [options] [--] <pathspec>..."),
> @@ -141,23 +142,21 @@ static void refresh(int verbose, const struct pathspec *pathspec)
>  int run_add_interactive(const char *revision, const char *patch_mode,
>  			const struct pathspec *pathspec)
>  {
> -	int status, ac, i;
> -	const char **args;
> +	int status, i;
> +	struct argv_array *argv = ARGV_ARRAY_INIT;

Where does that pointer point at and who allocated the piece of memory used by it?

See http://thread.gmane.org/gmane.comp.version-control.git/244151 for an example solution.

Show 24 quoted lines
>  
> -	args = xcalloc(sizeof(const char *), (pathspec->nr + 6));
> -	ac = 0;
> -	args[ac++] = "add--interactive";
> +	argv_array_push(argv, "add--interactive");
>  	if (patch_mode)
> -		args[ac++] = patch_mode;
> +		argv_array_push(argv, patch_mode);
>  	if (revision)
> -		args[ac++] = revision;
> -	args[ac++] = "--";
> +		argv_array_push(argv, revision);
> +	argv_array_push(argv, "--");
>  	for (i = 0; i < pathspec->nr; i++)
>  		/* pass original pathspec, to be re-parsed */
> -		args[ac++] = pathspec->items[i].original;
> +		argv_array_push(argv, pathspec->items[i].original);
>  
> -	status = run_command_v_opt(args, RUN_GIT_CMD);
> -	free(args);
> +	status = run_command_v_opt(argv->argv, RUN_GIT_CMD);
> +	argv_array_clear(argv);
>  	return status;
>  }
Lars Vogel· Jun 9, 2016, 17:46 UTC · lore

[PATCH] Use "working tree" instead of "working directory" for git status

Working directory can be easily confused with the current directory.
In one of my patches I already updated the usage of working directory
with working tree for the man page but I noticed that git status also
uses this incorrect term.
---
 po/bg.po    | 2 +-
 po/ca.po    | 2 +-
 po/de.po    | 2 +-
 po/fr.po    | 2 +-
 po/git.pot  | 2 +-
 po/ko.po    | 2 +-
 po/ru.po    | 2 +-
 po/sv.po    | 2 +-
 po/vi.po    | 2 +-
 po/zh_CN.po | 2 +-
 wt-status.c | 2 +-
 11 files changed, 11 insertions(+), 11 deletions(-)
Show changes to 11 files +11 −11

po/bg.po, po/ca.po, po/de.po, po/fr.po, po/git.pot, po/ko.po, po/ru.po, po/sv.po, po/vi.po, po/zh_CN.po, wt-status.c

diff --git a/po/bg.po b/po/bg.po
index ac6f103..9390c81 100644
--- a/po/bg.po
+++ b/po/bg.po
@@ -2295,7 +2295,7 @@ msgstr ""
 
 #: wt-status.c:1535
 #, c-format
-msgid "nothing to commit, working directory clean\n"
+msgid "nothing to commit, working tree clean\n"
 msgstr "липсват каквито и да е промени, работното дърво е чисто\n"
 
 #: wt-status.c:1642
diff --git a/po/ca.po b/po/ca.po
index 46000d7..f54f137 100644
--- a/po/ca.po
+++ b/po/ca.po
@@ -2318,7 +2318,7 @@ msgstr ""
 
 #: wt-status.c:1535
 #, c-format
-msgid "nothing to commit, working directory clean\n"
+msgid "nothing to commit, working tree clean\n"
 msgstr "no hi ha res a cometre, directori de treball net\n"
 
 #: wt-status.c:1642
diff --git a/po/de.po b/po/de.po
index 0eadf34..a14fe92 100644
--- a/po/de.po
+++ b/po/de.po
@@ -2356,7 +2356,7 @@ msgstr ""
 
 #: wt-status.c:1535
 #, c-format
-msgid "nothing to commit, working directory clean\n"
+msgid "nothing to commit, working tree clean\n"
 msgstr "nichts zu committen, Arbeitsverzeichnis unverändert\n"
 
 #: wt-status.c:1642
diff --git a/po/fr.po b/po/fr.po
index 55ca387..627315c 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -2394,7 +2394,7 @@ msgstr "rien à valider (utilisez -u pour afficher les fichiers non suivis)\n"
 
 #: wt-status.c:1535
 #, c-format
-msgid "nothing to commit, working directory clean\n"
+msgid "nothing to commit, working tree clean\n"
 msgstr "rien à valider, la copie de travail est propre\n"
 
 #: wt-status.c:1642
diff --git a/po/git.pot b/po/git.pot
index 72ef798..2957091 100644
--- a/po/git.pot
+++ b/po/git.pot
@@ -2192,7 +2192,7 @@ msgstr ""
 
 #: wt-status.c:1535
 #, c-format
-msgid "nothing to commit, working directory clean\n"
+msgid "nothing to commit, working tree clean\n"
 msgstr ""
 
 #: wt-status.c:1642
diff --git a/po/ko.po b/po/ko.po
index 3ff3b9b..9520e04 100644
--- a/po/ko.po
+++ b/po/ko.po
@@ -2313,7 +2313,7 @@ msgstr ""
 
 #: wt-status.c:1535
 #, c-format
-msgid "nothing to commit, working directory clean\n"
+msgid "nothing to commit, working tree clean\n"
 msgstr "커밋할 사항 없음, 작업 폴더 깨끗함\n"
 
 #: wt-status.c:1642
diff --git a/po/ru.po b/po/ru.po
index c0a838b..6fe8c09 100644
--- a/po/ru.po
+++ b/po/ru.po
@@ -2233,7 +2233,7 @@ msgstr "нечего коммитить (используйте опцию «-u
 
 #: wt-status.c:1535
 #, c-format
-msgid "nothing to commit, working directory clean\n"
+msgid "nothing to commit, working tree clean\n"
 msgstr "нечего коммитить, нет изменений в рабочем каталоге\n"
 
 #: wt-status.c:1642
diff --git a/po/sv.po b/po/sv.po
index 32bcaba..801a816 100644
--- a/po/sv.po
+++ b/po/sv.po
@@ -2289,7 +2289,7 @@ msgstr "inget att checka in (använd -u för att visa ospårade filer)\n"
 
 #: wt-status.c:1535
 #, c-format
-msgid "nothing to commit, working directory clean\n"
+msgid "nothing to commit, working tree clean\n"
 msgstr "inget att checka in, arbetskatalogen ren\n"
 
 #: wt-status.c:1642
diff --git a/po/vi.po b/po/vi.po
index edd8e29..18fbdef 100644
--- a/po/vi.po
+++ b/po/vi.po
@@ -2306,7 +2306,7 @@ msgstr ""
 
 #: wt-status.c:1535
 #, c-format
-msgid "nothing to commit, working directory clean\n"
+msgid "nothing to commit, working tree clean\n"
 msgstr "không có gì để chuyển giao, thư mục làm việc sạch sẽ\n"
 
 #: wt-status.c:1642
diff --git a/po/zh_CN.po b/po/zh_CN.po
index a6b06f9..b76f2c0 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -2421,7 +2421,7 @@ msgstr "无文件要提交(使用 -u 显示未跟踪的文件)\n"
 #  译者:中文字符串拼接,可删除前导空格
 #: wt-status.c:1535
 #, c-format
-msgid "nothing to commit, working directory clean\n"
+msgid "nothing to commit, working tree clean\n"
 msgstr "无文件要提交,干净的工作区\n"
 
 #  译者:注意保持句尾空格
diff --git a/wt-status.c b/wt-status.c
index 4f27bd6..4ce4e35 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1554,7 +1554,7 @@ void wt_status_print(struct wt_status *s)
 			else
 				printf(_("nothing to commit\n"));
 		} else
-			printf(_("nothing to commit, working directory clean\n"));
+			printf(_("nothing to commit, working tree clean\n"));
 	}
 }
 
-- 
2.8.1
Eric Sunshine· Jun 9, 2016, 17:55 UTC · re: Lars Vogel · lore

Re: [PATCH] Use "working tree" instead of "working directory" for git status

On Thu, Jun 9, 2016 at 1:46 PM, Lars Vogel <lars.vogel@gmail.com> wrote:
> Working directory can be easily confused with the current directory.
> In one of my patches I already updated the usage of working directory
> with working tree for the man page but I noticed that git status also
> uses this incorrect term.
Missing sign-off.
Show 12 quoted lines
> ---
>  po/bg.po    | 2 +-
>  po/ca.po    | 2 +-
>  po/de.po    | 2 +-
>  po/fr.po    | 2 +-
>  po/git.pot  | 2 +-
>  po/ko.po    | 2 +-
>  po/ru.po    | 2 +-
>  po/sv.po    | 2 +-
>  po/vi.po    | 2 +-
>  po/zh_CN.po | 2 +-
>  wt-status.c | 2 +-

Don't bother updating the .po files; that's the job of the translators. Your patch should touch only wt-status.c.

>  11 files changed, 11 insertions(+), 11 deletions(-)
Lars Vogel· Jun 9, 2016, 18:21 UTC · re: Eric Sunshine · lore

Re: [PATCH] Use "working tree" instead of "working directory" for git status

Thanks updated patch on its way.
On Thu, Jun 9, 2016 at 7:55 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 25 quoted lines
> On Thu, Jun 9, 2016 at 1:46 PM, Lars Vogel <lars.vogel@gmail.com> wrote:
>> Working directory can be easily confused with the current directory.
>> In one of my patches I already updated the usage of working directory
>> with working tree for the man page but I noticed that git status also
>> uses this incorrect term.
>
> Missing sign-off.
>
>> ---
>>  po/bg.po    | 2 +-
>>  po/ca.po    | 2 +-
>>  po/de.po    | 2 +-
>>  po/fr.po    | 2 +-
>>  po/git.pot  | 2 +-
>>  po/ko.po    | 2 +-
>>  po/ru.po    | 2 +-
>>  po/sv.po    | 2 +-
>>  po/vi.po    | 2 +-
>>  po/zh_CN.po | 2 +-
>>  wt-status.c | 2 +-
>
> Don't bother updating the .po files; that's the job of the
> translators. Your patch should touch only wt-status.c.
>
>>  11 files changed, 11 insertions(+), 11 deletions(-)
-- 
Eclipse Platform UI and e4 project co-lead
CEO vogella GmbH

Haindaalwisch 17a, 22395 Hamburg
Amtsgericht Hamburg: HRB 127058
Geschäftsführer: Lars Vogel, Jennifer Nerlich de Vogel
USt-IdNr.: DE284122352
Fax (040) 5247 6322, Email: lars.vogel@vogella.com, Web: http://www.vogella.com
Takahito Ogawa· Oct 11, 2017, 18:03 UTC · lore

[PATCH 1/1] git-stash.txt: correct "git stash" behavior with no arguments

"git stash" behavior without any arguments was changed in 1ada5020b ("stash: use stash_push for no verb form", 2017-02-28). This is equivalent to "git stash push" but document says "git stash save".

Correct it.
Signed-off-by: Takahito Ogawa <takahito.ogawa@datagrid.co.jp>
---
 Documentation/git-stash.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to Documentation/git-stash.txt +1 −1
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 00f95fee1..63642c145 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -33,7 +33,7 @@ and reverts the working directory to match the `HEAD` commit.
 The modifications stashed away by this command can be listed with
 `git stash list`, inspected with `git stash show`, and restored
 (potentially on top of a different commit) with `git stash apply`.
-Calling `git stash` without any arguments is equivalent to `git stash save`.
+Calling `git stash` without any arguments is equivalent to `git stash push`.
 A stash is by default listed as "WIP on 'branchname' ...", but
 you can give a more descriptive message on the command line when
 you create one.
-- 
2.13.1
Junio C Hamano· Oct 12, 2017, 00:53 UTC · re: Takahito Ogawa · lore

Re: [PATCH 1/1] git-stash.txt: correct "git stash" behavior with no arguments

Takahito Ogawa <aiueogawa217@gmail.com> writes:
Show 6 quoted lines
> @@ -33,7 +33,7 @@ and reverts the working directory to match the `HEAD` commit.
>  The modifications stashed away by this command can be listed with
>  `git stash list`, inspected with `git stash show`, and restored
>  (potentially on top of a different commit) with `git stash apply`.
> -Calling `git stash` without any arguments is equivalent to `git stash save`.
> +Calling `git stash` without any arguments is equivalent to `git stash push`.
Hmph.  Is there any difference between
	git stash save
	git stash push
without any other argument?  Aren't they equivalent to
	git stash
without any argument, which is what this sentence explains?
小川恭史· Oct 12, 2017, 03:52 UTC · lore

Re: [PATCH 1/1] git-stash.txt: correct "git stash" behavior with no arguments

As you point,
    git stash
without any argument is equivalent to both of
    git stash save
    git stash push
. The original sentence is correct.
2017-10-12 12:31 GMT+09:00 小川恭史 <aiueogawa217@gmail.com>:
Show 33 quoted lines
> As you point,
>
>     git stash
>
> without any argument is equivalent to both of
> git stash save and
>
>
> 2017-10-12 9:53 GMT+09:00 Junio C Hamano <gitster@pobox.com>:
>> Takahito Ogawa <aiueogawa217@gmail.com> writes:
>>
>>> @@ -33,7 +33,7 @@ and reverts the working directory to match the `HEAD`
>>> commit.
>>>  The modifications stashed away by this command can be listed with
>>>  `git stash list`, inspected with `git stash show`, and restored
>>>  (potentially on top of a different commit) with `git stash apply`.
>>> -Calling `git stash` without any arguments is equivalent to `git stash
>>> save`.
>>> +Calling `git stash` without any arguments is equivalent to `git stash
>>> push`.
>>
>> Hmph.  Is there any difference between
>>
>>         git stash save
>>         git stash push
>>
>> without any other argument?  Aren't they equivalent to
>>
>>         git stash
>>
>> without any argument, which is what this sentence explains?
>>
>
小川恭史· Oct 12, 2017, 03:54 UTC · re: 小川恭史 · lore

Re: [PATCH 1/1] git-stash.txt: correct "git stash" behavior with no arguments

As you point,
    git stash
without any argument is equivalent to both of
    git stash save
    git stash push
. The original sentence is correct.
Junio C Hamano· Oct 12, 2017, 09:56 UTC · re: 小川恭史 · lore

Re: [PATCH 1/1] git-stash.txt: correct "git stash" behavior with no arguments

小川恭史 <aiueogawa217@gmail.com> writes:
Show 10 quoted lines
> As you point,
>
>     git stash
>
> without any argument is equivalent to both of
>
>     git stash save
>     git stash push
>
> . The original sentence is correct.
OK.  

Note that I was merely reacting to "Correct it." in your justification for the change, which made it sound like 'save' was incorrect.

As a part of a move to deprecate 'save' and nudge users towards 'push', the change does make sense. I just wanted to make sure the change and its motivation are presented with honesty ;-).

Brandon Richardson· Jan 17, 2019, 19:29 UTC · lore

[PATCH] Allow usage of --gpg-sign flag in commit-tree builtin.

Signed-off-by: Brandon Richardson <brandon1024.br@gmail.com>
---
Hi,

This is my first contribution, so please bear with me. All feedback is appreciated.

Ran into this issue while writing a signed commit object manually. Here are the steps I followed to replicate the issue:

mkdir test && cd test git init echo 'test' > test.txt git hash-object -w test.txt git update-index --add --cacheinfo 100644 <blob hash> test.txt git write-tree git commit-tree --gpg-sign -m 'test commit msg' <tree obj hash>

Thanks, Brandon

 builtin/commit-tree.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
Show changes to builtin/commit-tree.c +3 −1
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index 9ec36a82b..9a06594f6 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -66,7 +66,9 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 			continue;
 		}
 
-		if (skip_prefix(arg, "-S", &sign_commit))
+		if (skip_prefix(arg, "-S", &sign_commit) ||
+			skip_prefix(arg, "--gpg-sign=", &sign_commit) ||
+			skip_prefix(arg, "--gpg-sign", &sign_commit))
 			continue;
 
 		if (!strcmp(arg, "--no-gpg-sign")) {
-- 
2.20.1
Martin Ågren· Jan 17, 2019, 20:24 UTC · re: Brandon Richardson · lore

Re: [PATCH] Allow usage of --gpg-sign flag in commit-tree builtin.

Hi Brandon,
Welcome to the list! :-)

On Thu, 17 Jan 2019 at 20:30, Brandon Richardson <brandon1024.br@gmail.com> wrote:

> Subject: [PATCH] Allow usage of --gpg-sign flag in commit-tree builtin.
Good find!

Maybe "commit-tree: handle --gpg-sign" which looks more like what we see in `git log --oneline builtin/commit-tree.c`.

> Signed-off-by: Brandon Richardson <brandon1024.br@gmail.com>

It could be worthwhile to note in the proposed commit message that this option is actually documented, since 55ca3f99ae ("commit-tree: add and document --no-gpg-sign", 2013-12-13), so it's clearly an omission that it's not implemented.

Show 9 quoted lines
> Here are the steps I followed to replicate the issue:
>
> mkdir test && cd test
> git init
> echo 'test' > test.txt
> git hash-object -w test.txt
> git update-index --add --cacheinfo 100644 <blob hash> test.txt
> git write-tree
> git commit-tree --gpg-sign -m 'test commit msg' <tree obj hash>

Have you looked at turning this into a test in the t/ directory? Grepping a little suggests that t7510-signed-commit.sh might be a good spot. A test would make the patch more obviously correct, and would make sure that this doesn't regress in the future.

Show 5 quoted lines
> -               if (skip_prefix(arg, "-S", &sign_commit))
> +               if (skip_prefix(arg, "-S", &sign_commit) ||
> +                       skip_prefix(arg, "--gpg-sign=", &sign_commit) ||
> +                       skip_prefix(arg, "--gpg-sign", &sign_commit))
>                         continue;

This would match "--gpg-signfoo", which seems like a bug. The first two `skip_prefix()` calls look ok. Then maybe

  if (!strcmp(arg, "--gpg-sign")) {
          sign_commit = "";
          continue;
  }
That's entirely untested, though.
Thanks for looking into this, rather than just side-stepping it. ;-)
Martin
Brandon Richardson· Jan 18, 2019, 01:09 UTC · lore

[PATCH] commit-tree: add missing --gpg-sign flag

Add --gpg-sign option in commit-tree, which was documented, but not implemented, in 55ca3f99ae.

Signed-off-by: Brandon Richardson <brandon1024.br@gmail.com>
---
Thanks Martin for the tips and suggestions!
 builtin/commit-tree.c    | 8 +++++++-
 t/t7510-signed-commit.sh | 4 +++-
 2 files changed, 10 insertions(+), 2 deletions(-)
Show changes to 2 files +10 −2

builtin/commit-tree.c, t/t7510-signed-commit.sh

diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index 9ec36a82b..a51b2c8d7 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -66,7 +66,13 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 			continue;
 		}
 
-		if (skip_prefix(arg, "-S", &sign_commit))
+		if(!strcmp(arg, "--gpg-sign")) {
+		    skip_prefix(arg, "--gpg-sign", &sign_commit);
+		    continue;
+		}
+
+		if (skip_prefix(arg, "-S", &sign_commit) ||
+			skip_prefix(arg, "--gpg-sign=", &sign_commit))
 			continue;
 
 		if (!strcmp(arg, "--no-gpg-sign")) {
diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh
index 86d3f93fa..efc136eaf 100755
--- a/t/t7510-signed-commit.sh
+++ b/t/t7510-signed-commit.sh
@@ -51,7 +51,9 @@ test_expect_success GPG 'create signed commits' '
 	# commit.gpgsign is still on but this must not be signed
 	git tag ninth-unsigned $(echo 9 | git commit-tree HEAD^{tree}) &&
 	# explicit -S of course must sign.
-	git tag tenth-signed $(echo 9 | git commit-tree -S HEAD^{tree})
+	git tag tenth-signed $(echo 10 | git commit-tree -S HEAD^{tree})
+	# --gpg-sign must sign.
+	git tag eleventh-signed $(echo 11 | git commit-tree --gpg-sign HEAD^{tree})
 '
 
 test_expect_success GPG 'verify and show signatures' '
-- 
2.20.1
Martin Ågren· Jan 18, 2019, 07:11 UTC · re: Brandon Richardson · lore

Re: [PATCH] commit-tree: add missing --gpg-sign flag

Hi Brandon,

On Fri, 18 Jan 2019 at 02:12, Brandon Richardson <brandon1024.br@gmail.com> wrote:

Show 6 quoted lines
>
> Add --gpg-sign option in commit-tree, which was documented, but not
> implemented, in 55ca3f99ae.
>
> Signed-off-by: Brandon Richardson <brandon1024.br@gmail.com>
> ---
Thank you for an updated version!
>  builtin/commit-tree.c    | 8 +++++++-
>  t/t7510-signed-commit.sh | 4 +++-
Ah, a test, nice. :-)
> -               if (skip_prefix(arg, "-S", &sign_commit))
> +               if(!strcmp(arg, "--gpg-sign")) {
> +                   skip_prefix(arg, "--gpg-sign", &sign_commit);

I personally find this a bit convoluted, compared to just assigning "". Maybe there are reasons for doing it this way that I don't see?

Show 6 quoted lines
> +                   continue;
> +               }
> +
> +               if (skip_prefix(arg, "-S", &sign_commit) ||
> +                       skip_prefix(arg, "--gpg-sign=", &sign_commit))
>                         continue;
Looks good.
Show 11 quoted lines
> --- a/t/t7510-signed-commit.sh
> +++ b/t/t7510-signed-commit.sh
> @@ -51,7 +51,9 @@ test_expect_success GPG 'create signed commits' '
>         # commit.gpgsign is still on but this must not be signed
>         git tag ninth-unsigned $(echo 9 | git commit-tree HEAD^{tree}) &&
>         # explicit -S of course must sign.
> -       git tag tenth-signed $(echo 9 | git commit-tree -S HEAD^{tree})
> +       git tag tenth-signed $(echo 10 | git commit-tree -S HEAD^{tree})
> +       # --gpg-sign must sign.
> +       git tag eleventh-signed $(echo 11 | git commit-tree --gpg-sign HEAD^{tree})
>  '

Thanks for providing a test, and thanks for fixing the "9"/"10" copy-paste error. (You might want to remark "Fix a 9/10 typo while we're here." in the commit message, especially since that line requires editing anyway, see next paragraph.)

All of the commands above are suffixed with "&&" which means that the shell only keeps executing as long as the commands succeed. If any of those 20-30 commands fails, the shell will jump out of the &&-chain and land ... at this line you're adding. If that one succeeds, the test will be reported as succeeding. So please add a "&&" to the "10" line.

All of that said ... if I add the missing "&&" and run your test on the *old* implementation, it still succeeds. The reason is that I grepped for the "best" place to put this, and directed you to a part of the test suite where it might be a bit too easy to copy existing code and end up with something non-ideal. Sorry about that. :-(

What happens is that git commit-tree reports "fatal: Not a valid object name --gpg-sign", then we go on to happily execute `git tag eleventh-signed` where we've just substituted the empty string produced by git commit-tree. The verifications will succeed, because there's already a signature there... (BTW, the verifications happen further down, so you'll want to add "eleventh-signed" to the list of tags there.)

One way of making the test more robust might be to add a brand new commit, similar to how it's done earlier in the test.

It's not your fault that you fell into this trap. If you want to look into making this more robust -- and try running the test before and after your fix -- great! If you feel it's out of your comfort zone or interest range, just let me know and I could try to take it from here.

>  test_expect_success GPG 'verify and show signatures' '
BTW, here's that test where the signatures are verified.
Martin
Brandon Richardson· Jan 18, 2019, 15:06 UTC · re: Martin Ågren · lore

Re: [PATCH] commit-tree: add missing --gpg-sign flag

Hi Martin,
Thanks again for your comments and patience, I appreciate it :-)
Show 6 quoted lines
> > -               if (skip_prefix(arg, "-S", &sign_commit))
> > +               if(!strcmp(arg, "--gpg-sign")) {
> > +                   skip_prefix(arg, "--gpg-sign", &sign_commit);
>
> I personally find this a bit convoluted, compared to just assigning "".
> Maybe there are reasons for doing it this way that I don't see?

You're right, it is a bit convoluted. The reason I opted to do it this way is because I wanted to reuse part of "arg" by simply advancing the pointer, but this is silly and wastes cycles, and after thinking about it a bit more I realized how silly it was.

Show 5 quoted lines
> All of the commands above are suffixed with "&&" which means that the
> shell only keeps executing as long as the commands succeed. If any of
> those 20-30 commands fails, the shell will jump out of the &&-chain and
> land ... at this line you're adding. If that one succeeds, the test will
> be reported as succeeding. So please add a "&&" to the "10" line.
Woops, I can't believe I missed that.
Show 13 quoted lines
> All of that said ... if I add the missing "&&" and run your test on the
> *old* implementation, it still succeeds. The reason is that I grepped
> for the "best" place to put this, and directed you to a part of the test
> suite where it might be a bit too easy to copy existing code and end
> up with something non-ideal. Sorry about that. :-(
>
> What happens is that git commit-tree reports "fatal: Not a valid object
> name --gpg-sign", then we go on to happily execute `git tag
> eleventh-signed` where we've just substituted the empty string produced
> by git commit-tree. The verifications will succeed, because there's
> already a signature there... (BTW, the verifications happen further
> down, so you'll want to add "eleventh-signed" to the list of tags
> there.)

No need to apologize, I jumped the gun. I'm going to look into putting together a more robust test for this change.

Brandon
Junio C Hamano· Jan 18, 2019, 21:02 UTC · re: Brandon Richardson · lore

Re: [PATCH] commit-tree: add missing --gpg-sign flag

Brandon Richardson <brandon1024.br@gmail.com> writes:
> -		if (skip_prefix(arg, "-S", &sign_commit))
> +		if(!strcmp(arg, "--gpg-sign")) {
Style.  "if (!strcmp(arg, "--gpg-sign")) {"
> +		    skip_prefix(arg, "--gpg-sign", &sign_commit);
> +		    continue;

Technically, skipping the prefix S of string S will make us point at an empty substring at the end. So from that point of view, skip_prefix(arg, "--gpg-sign", &sign_commit) is not incorrect per-se, but it is highly misleading. We have already determined that the user gave us "--gpg-sign" option without anything after it, so we want to summon the "use the default key" behaviour by giving an empty string to sign_commit.

An explicit assignment
	sign_commit = "";
would be a lot more readable and make the intent a lot more clear.
> +		}
> +
> +		if (skip_prefix(arg, "-S", &sign_commit) ||
> +			skip_prefix(arg, "--gpg-sign=", &sign_commit))

This side is OK. "-S" gives us an empty string, but "-Skeyid" gives us "keyid" in sign_commit.

Show 18 quoted lines
>  			continue;
>  
>  		if (!strcmp(arg, "--no-gpg-sign")) {
> diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh
> index 86d3f93fa..efc136eaf 100755
> --- a/t/t7510-signed-commit.sh
> +++ b/t/t7510-signed-commit.sh
> @@ -51,7 +51,9 @@ test_expect_success GPG 'create signed commits' '
>  	# commit.gpgsign is still on but this must not be signed
>  	git tag ninth-unsigned $(echo 9 | git commit-tree HEAD^{tree}) &&
>  	# explicit -S of course must sign.
> -	git tag tenth-signed $(echo 9 | git commit-tree -S HEAD^{tree})
> +	git tag tenth-signed $(echo 10 | git commit-tree -S HEAD^{tree})
> +	# --gpg-sign must sign.
> +	git tag eleventh-signed $(echo 11 | git commit-tree --gpg-sign HEAD^{tree})
>  '
>  
>  test_expect_success GPG 'verify and show signatures' '
Jiang Xin· May 14, 2019, 09:28 UTC · lore

[L10N] Kickoff for Git 2.22.0 round #1

Hi,

Git v2.22.0-rc0 has been released, and it's time to start new round of git l10n. This time there are 270 updated messages need to be translated since last update. Let's start new round of git l10n based on the following commit:

    l10n: git.pot: v2.22.0 round 1 (270 new, 56 removed)
    
    Generate po/git.pot from v2.22.0-rc0 for git v2.22.0 l10n round 1.
    
    Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
You can get it from the usual place:
    https://github.com/git-l10n/git-po/

As how to update your XX.po and help to translate Git, please see "Updating a XX.po file" and other sections in "po/README" file.

-- Jiang Xin

Jiang Xin· Feb 27, 2025, 01:57 UTC · lore

[L10N] Kickoff for Git 2.49.0

Hi,

Git 2.49.0-rc0 has been released, and it's time to start a new round of git l10n. This time there are 35 updated messages need to be translated since the last release. Please send your pull request to the l10n coordinator's repository below before this update window closes on Thu, 13 Mar 2025.

    https://github.com/git-l10n/git-po/

The following description of our l10n workflow is from the "po/README.md" file.

## The "po/git.pot" file is a generated file, no longer in the repository

The l10n coordinator does not need to generate the "po/git.pot" file every time to start a new l10n workflow, and there is no "po/git.pot" file at all.

Everyone can generate the "po/git.pot" file with the command below:
    make po/git.pot

But we can also forget about it. By updating our corresponding "po/XX.po" file, the "po/git.pot" file is automatically generated.

## Update the "po/XX.po" file, and start to translate

Before updating the "po/XX.po" file, l10n contributors should pull the latest commits from the master branch of "git.git". E.g.:

    git pull --rebase git@github.com:git/git.git master
Then update the cooresponding "po/XX.po" file using the following command:
    make po-update PO_FILE=po/XX.po
Translate the uptodate "po/XX.po" file, and create a new commit.
## Refine your commits, send pull requests
In the "po/XX.po" file, there are location lines in comments like below:
    #: add-interactive.c:535 add-interactive.c:836 reset.c:136 sequencer.c:3505
    #: sequencer.c:3970 sequencer.c:4127 builtin/rebase.c:1261
    #: builtin/rebase.c:1671

These comments with file locations are useful for l10n contributors to locate the context easily during translation. But these file locations introduce a lot of noise and will consume a lot of repository storage. Therefore, we should remove these file locations from the "po/XX.po" file.

To remove file locations in the "po/XX.po" file, you can use one of the following two ways, but don't switch back and forth.

 * Keep the filenames, only remove locations (need gettext 0.19 and above):
        msgcat --add-location=file po/XX.po >po/XX.po.new
        mv po/XX.po.new po/XX.po
 * Remove both filenames and locations:
        msgcat --no-location po/XX.po >po/XX.po.new
        mv po/XX.po.new po/XX.po

After squashing trivial commits and removing file locations in the "po/XX.po" file, send pull request to the l10n coordinator's repository below:

    https://github.com/git-l10n/git-po/
## Resolve errors found by the l10n CI pipeline for the pull request

A helper program hosted on "https://github.com/git-l10n/git-po-helper" can help git l10n coordinator and git l10n contributors to check the conventions of git l10n contributions, and it is also used in GitHub actions as l10n CI pipeline to validate each pull request in the "git-l10n/git-po" repository. Please fix the issues found by the helper program.

** Please note: The update window will close on Thu, 13 Mar 2025. **

-- Jiang Xin

← back to recent threads