From a286a52ce1f6298d1c7613821ac4cf64032e87ca Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 21:25:03 +0000 Subject: [PATCH] Report a fetch's null object id as null, as Push() does [patch] git fetch --porcelain writes the all-zero id for the side of an update that does not exist: the old side of a created ref and the new side of a pruned one. GitFetchParser converted both fields unconditionally, so Fetch() returned a GitCommitSha of zeroes where Push(), Worktrees() and Submodules() return null. Map any null id (SHA-1 or SHA-256) to null. Fixes ktsu-dev/GitIntegration#273 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QixWHg8N55ALkBwbNXMhUp --- .../Parsing/GitFetchParserTests.cs | 32 +++++++++++++++++++ GitIntegration/Parsing/GitFetchParser.cs | 21 ++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/GitIntegration.Test/Parsing/GitFetchParserTests.cs b/GitIntegration.Test/Parsing/GitFetchParserTests.cs index cb16943..baca83c 100644 --- a/GitIntegration.Test/Parsing/GitFetchParserTests.cs +++ b/GitIntegration.Test/Parsing/GitFetchParserTests.cs @@ -90,6 +90,38 @@ public void ToleratesCarriageReturnLineEndings() Assert.AreEqual(NewSha.As(), result.Updates[0].NewSha); } + [TestMethod] + [DataRow("0000000000000000000000000000000000000000", "6702dd1957dedc4e0f54245bda65f3ddc5f37e64")] + [DataRow( + "0000000000000000000000000000000000000000000000000000000000000000", + "6702dd1957dedc4e0f54245bda65f3ddc5f37e646702dd1957dedc4e0f54245b")] + public void ReportsANullOldIdAsNullForACreatedRef(string nullId, string newSha) + { + // git writes its null id for the side of the update that does not exist. Push() reports + // null there, so fetch must too, under SHA-1 and SHA-256 alike. + GitRefUpdate update = GitFetchParser.Parse( + Record("*", nullId, newSha, "refs/remotes/origin/created")).Updates[0]; + + Assert.AreEqual(GitRefUpdateKind.Created, update.Kind); + Assert.IsNull(update.OldSha); + Assert.AreEqual(newSha.As(), update.NewSha); + } + + [TestMethod] + [DataRow("7c85857fc85452150652c289fc94f1f912b96c40", "0000000000000000000000000000000000000000")] + [DataRow( + "7c85857fc85452150652c289fc94f1f912b96c407c85857fc85452150652c289", + "0000000000000000000000000000000000000000000000000000000000000000")] + public void ReportsANullNewIdAsNullForAPrunedRef(string oldSha, string nullId) + { + GitRefUpdate update = GitFetchParser.Parse( + Record("-", oldSha, nullId, "refs/remotes/origin/pruned")).Updates[0]; + + Assert.AreEqual(GitRefUpdateKind.Removed, update.Kind); + Assert.AreEqual(oldSha.As(), update.OldSha); + Assert.IsNull(update.NewSha); + } + [TestMethod] public void ReportsAnUnknownFlagWithoutThrowing() { diff --git a/GitIntegration/Parsing/GitFetchParser.cs b/GitIntegration/Parsing/GitFetchParser.cs index aa0e5df..1e68052 100644 --- a/GitIntegration/Parsing/GitFetchParser.cs +++ b/GitIntegration/Parsing/GitFetchParser.cs @@ -67,11 +67,28 @@ private static GitRefUpdate ReadUpdate(string record) { Kind = ToKind(flag), Reference = GitParseValues.ToSemantic(fields[2], "fetched reference"), - OldSha = GitParseValues.ToSemantic(fields[0], "fetched old object id"), - NewSha = GitParseValues.ToSemantic(fields[1], "fetched new object id"), + OldSha = ReadObjectId(fields[0], "fetched old object id"), + NewSha = ReadObjectId(fields[1], "fetched new object id"), }; } + /// + /// Converts one side of a fetch record to a commit id, or for git's null id. + /// + /// + /// git writes the null id for whichever side of the update does not exist: the old side of a + /// created ref and the new side of a pruned one. Reporting it as matches + /// what Push() returns for the same kinds of update, and what the worktree and submodule + /// parsers do with the null id. + /// + /// The object id git printed. + /// What the field holds, for the parse error. + /// The commit id, or when git printed the null id. + private static GitCommitSha? ReadObjectId(string objectId, string description) => + GitParseValues.IsNullObjectId(objectId) + ? null + : GitParseValues.ToSemantic(objectId, description); + private static GitRefUpdateKind ToKind(char flag) => flag switch { ' ' => GitRefUpdateKind.FastForward,