Skip to content

Fix crash when staging lines with the index locked#2771

Merged
lucasderraugh merged 1 commit intogit-up:masterfrom
Cykelero:fix-array-capacity-crash
Apr 23, 2026
Merged

Fix crash when staging lines with the index locked#2771
lucasderraugh merged 1 commit intogit-up:masterfrom
Cykelero:fix-array-capacity-crash

Conversation

@Cykelero
Copy link
Copy Markdown
Contributor

Fixes #2768.

Why it crashes

See the linked issue for a LLM-generated attempt at diagnosing the issue; it seems rather correct. (the proposed solutions are pretty terrible though)

In a nutshell: in two spots*, GUK calls git_diff_index_to_workdir(), and explicitly ignores its returned error if it’s GIT_ELOCKED. The reasoning is that we don’t care about failing to write the index: that’s just an optional, beneficial side-effect to our actual need, which is to get a diff.

However, when failing in that way, git_diff_index_to_workdir() does not return a diff**, it returns null! And so that null value is returned, silently makes its way through GUK, until this causes an array to be allocated with an incorrect size.

*diffWorkingDirectoryWithCommit and diffWorkingDirectoryWithIndex, on GCRepository.
** Presumably it did at some point, explaining why the code is written that way.

Reproducing

To reproduce, I had to patch git_diff_index_to_workdir() to always write the index:

-	if ((diff->opts.flags & GIT_DIFF_UPDATE_INDEX) && ((git_diff_generated *)diff)->index_updated)
+	if ((diff->opts.flags & GIT_DIFF_UPDATE_INDEX))

Then, you can trigger the crash by staging an individual line. (that’s what I did in Retcon, anyway, but it should work in GitUp as well)

The fix

I removed the override altogether. When I tested, if git_diff_index_to_workdir() returns GIT_ELOCKED, it always returns an empty diff. Maybe that’s not always the case, but it doesn’t feel like we should trust its return value in these cases anyway.

These error overrides might have worked at some point? But in my quick tests, now, if the function errors, it'll return a null diff.

As a result of the override, the null diff unexpectedly goes in the rest of GUK. IIRC, a GCDiff gets created with it, GCDiff._cacheDeltasIfNeeded tries to allocate an array of capacity -1 (which maps to unsigned int 18446744073709551615) because that's the output of git_diff_num_deltas on a null value, and that crashes.
@lucasderraugh
Copy link
Copy Markdown
Collaborator

@Cykelero What happens when this path is hit and GIT_ELOCKED is returned? Do we throw some kind of error UI?

@Cykelero
Copy link
Copy Markdown
Contributor Author

Yeah, the usual dialog:
image

@lucasderraugh lucasderraugh merged commit af24642 into git-up:master Apr 23, 2026
2 of 3 checks passed
@lucasderraugh
Copy link
Copy Markdown
Collaborator

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Crash in GCDiff _cacheDeltasIfNeeded when git index is locked (GIT_ELOCKED)

2 participants