git/list[1] front-page[2] threads[3] people[4] search[5] about
 

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

From
Robin Rosenberg <robin.rosenberg@dewire.com>
Date
Jun 7, 2009, 20:19 UTC
Message-ID
<1244405951-21808-4-git-send-email-robin.rosenberg@dewire.com>
In-Reply-To
<1244405951-21808-3-git-send-email-robin.rosenberg@dewire.com>
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
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
Previous: Robin RosenbergNext: Shawn O. Pearce
Message 7 of 98 in “Close a forgotten reference to the HEAD ref.”
  1. 1/2 Close a forgotten reference to the HEAD ref.Robin Rosenberg, Dec 2, 2008
  2. 2/2 Improve closing of files in error situations.Robin Rosenberg, Dec 2, 2008
  3. 2/2 Improve closing of files in error situations.Robin Rosenberg, Dec 2, 2008
  4. 0/3 Ref log readerRobin Rosenberg, Jun 7, 2009
  5. 1/3 Assert the name and origName properties of Ref objectsRobin Rosenberg, Jun 7, 2009
  6. 2/3 Add methods to RawParseUtils for scanning backwards.Robin Rosenberg, Jun 7, 2009
  7. 3/3 Add a ref log reader classRobin Rosenberg, Jun 7, 2009
  8. Shawn O. PearceJun 7, 2009
  9. Robin RosenbergJun 7, 2009
  10. Shawn O. PearceJun 7, 2009
  11. 1/2 Add methods to RawParseUtils for scanning backwards.Robin Rosenberg, Jun 8, 2009
  12. 2/2 Add a ref log reader classRobin Rosenberg, Jun 8, 2009
  13. Shawn O. PearceJun 12, 2009
  14. 1/2 Use a UTC relative time zone for PersonIdentRobin Rosenberg, Jun 15, 2009
  15. 2/2 Add a ref log reader classRobin Rosenberg, Jun 15, 2009
  16. Robin RosenbergJun 15, 2009
  17. [BUG] apply: test for trailing whitespace & no new line bugy@vger.kernel.org, Jun 22, 2009
  18. [BUG] apply: test for trailing whitespace & no new line bugy@vger.kernel.org, Jun 22, 2009
  19. diff.c: fix a graph output bugstruggleyb.nku@gmail.com, Jul 8, 2010
  20. Junio C HamanoJul 9, 2010
  21. Junio C HamanoJul 9, 2010
  22. Nazri RamliyJul 9, 2010
  23. Junio C HamanoJul 9, 2010
  24. Bo YangJul 11, 2010
  25. 1/2 setup.c: add enter_work_tree()pclouds@gmail.com, Oct 7, 2010
  26. 2/2 worktree: provide better prefix to go back to original cwdpclouds@gmail.com, Oct 7, 2010
  27. Nguyen Thai Ngoc DuyOct 7, 2010
  28. Junio C HamanoOct 7, 2010
  29. Nguyen Thai Ngoc DuyOct 7, 2010
  30. Junio C HamanoOct 7, 2010
  31. Nguyen Thai Ngoc DuyOct 7, 2010
  32. Corrected cmitmode set in the right lower window when initiating a diff.Michał Pomorski, Nov 23, 2010
  33. git-p4: replace each tab with 8 spaces for consistencyAndrew Garber, Apr 7, 2011
  34. Junio C HamanoApr 7, 2011
  35. Phil HordApr 7, 2011
  36. Junio C HamanoApr 7, 2011
  37. git-p4: replace each tab with 8 spaces for consistencyAndrew Garber, Apr 7, 2011
  38. file import functionality for git-remote-mwPavel Volek, Jun 4, 2012
  39. Matthieu MoyJun 4, 2012
  40. Export file attachements in git-remote-mediawikiNGUYEN Kim Thuat, Jun 4, 2012
  41. Matthieu MoyJun 4, 2012
  42. nguyenkiJun 5, 2012
  43. Matthieu MoyJun 5, 2012
  44. nguyenkiJun 6, 2012
  45. nguyenkiJun 5, 2012
  46. [PATCHv1] Export file in git-remote-mediawikiKim Thuat NGUYEN, Jun 8, 2012
  47. Matthieu MoyJun 8, 2012
  48. nguyenkiJun 8, 2012
  49. Matthieu MoyJun 10, 2012
  50. [PATCHv2] git-remote-mediawiki: export File: attachmentsKim Thuat NGUYEN, Jun 12, 2012
  51. Matthieu MoyJun 12, 2012
  52. nguyenkiJun 12, 2012
  53. Max HornJun 12, 2012
  54. [PATCHv3] git-remote-mediawiki: export File: attachmentskim-thuat.nguyen@ensimag.imag.fr, Jun 13, 2012
  55. Matthieu MoyJun 13, 2012
  56. git send-email should not allow 'y' for in-reply-toEric Blake, Jan 11, 2013
  57. Jeff KingJan 11, 2013
  58. Eric BlakeJan 11, 2013
  59. Hilco WijbengaJan 11, 2013
  60. Jeff KingJan 11, 2013
  61. Junio C HamanoFeb 24, 2013
  62. v3 [PATCH 1/2] status: introduce status.short to enable --short by defaulty@ensimag.imag.fr, Jun 10, 2013
  63. v3 [PATCH 1/2] status: introduce status.short to enable --short by defaulty@ensimag.imag.fr, Jun 10, 2013
  64. v3 [PATCH 2/2] status:introduce status.branch to enable --branch by defaulty@ensimag.imag.fr, Jun 10, 2013
  65. v3 [PATCH 2/2] status:introduce status.branch to enable --branch by defaulty@ensimag.imag.fr, Jun 10, 2013
  66. Matthieu MoyJun 10, 2013
  67. Junio C HamanoJun 10, 2013
  68. 3/3 builtin/blame.c: reduce scope of variablesElia Pinto, Jan 29, 2014
  69. use strchrnul() in place of strchr() and strlen()Rohit Mani, Mar 5, 2014
  70. Junio C HamanoMar 6, 2014
  71. [GSoC][PATCH v2] use strchrnul() in place of strchr() and strlen()Rohit Mani, Mar 8, 2014
  72. Junio C HamanoMar 10, 2014
  73. [GSOC2014]TamerTas, Mar 10, 2014
  74. [GSOC2014] changed logical chain in branch.c to lookup tablesTamerTas, Mar 10, 2014
  75. Stefan BellerMar 10, 2014
  76. Eric SunshineMar 10, 2014
  77. Tamer TASMar 10, 2014
  78. Eric SunshineMar 10, 2014
  79. Tamer TASMar 11, 2014
  80. Eric SunshineMar 11, 2014
  81. [GSOC2014] add: Rewrite run_add_interactive to use struct argv_arrayMovchan Pavel, Mar 18, 2014
  82. Junio C HamanoMar 18, 2014
  83. Use "working tree" instead of "working directory" for git statusLars Vogel, Jun 9, 2016
  84. Eric SunshineJun 9, 2016
  85. Lars VogelJun 9, 2016
  86. 1/1 git-stash.txt: correct "git stash" behavior with no argumentsTakahito Ogawa, Oct 11, 2017
  87. Junio C HamanoOct 12, 2017
  88. 小川恭史Oct 12, 2017
  89. 小川恭史Oct 12, 2017
  90. Junio C HamanoOct 12, 2017
  91. Allow usage of --gpg-sign flag in commit-tree builtin.Brandon Richardson, Jan 17, 2019
  92. Martin ÅgrenJan 17, 2019
  93. commit-tree: add missing --gpg-sign flagBrandon Richardson, Jan 18, 2019
  94. Martin ÅgrenJan 18, 2019
  95. Brandon RichardsonJan 18, 2019
  96. Junio C HamanoJan 18, 2019
  97. [L10N] Kickoff for Git 2.22.0 round #1Jiang Xin, May 14, 2019
  98. [L10N] Kickoff for Git 2.49.0Jiang Xin, Feb 27, 2025

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.