* [bug] pull --prune could not delete references due to lock file already exists error
@ 2025-06-25 12:32 ryenus
2025-06-25 14:18 ` Re " K Jayatheerth
0 siblings, 1 reply; 14+ messages in thread
From: ryenus @ 2025-06-25 12:32 UTC (permalink / raw)
To: Git mailing list
today I tried to prune the remote refs in a local repo, but `git pull --prune`
failed with an error saying it could not delete references. I retried several
times and it failed consistently, without a single ref pruned.
I found this interesting comment while looking into the code here:
https://github.com/git/git/blob/bd99d6e8db5e2c56dd24395e9711ee7ee564bf4f/refs.c#L2863-L2895
> /*
> * Since we don't check the references' old_oids, the
> * individual updates can't fail, so we can pack all of the
> * updates into a single transaction.
> */
The problem is the assume is wrong, coz things could fail, one such case
is to have two refs like below:
1. origin/TOM/b1
2. origin/TOM/b2
3. origin/tom/b2
Notice there's `TOM` vs `tom` in the path. Another factor is that my local file
system is case insensitive. As a result the 3rd ref would fail and cause the
transaction to be always rolled back.
^ permalink raw reply [flat|nested] 14+ messages in thread* Re [bug] pull --prune could not delete references due to lock file already exists error 2025-06-25 12:32 [bug] pull --prune could not delete references due to lock file already exists error ryenus @ 2025-06-25 14:18 ` K Jayatheerth 2025-06-27 18:59 ` brian m. carlson 2025-06-30 13:46 ` Karthik Nayak 0 siblings, 2 replies; 14+ messages in thread From: K Jayatheerth @ 2025-06-25 14:18 UTC (permalink / raw) To: ryenus; +Cc: git First off thanks for reporting the bug :) So I cannot test this bug as my files system is case sensitive but to just read the code and give a thought in a direction (Assuming that the bug is recreatable) int refs_delete_refs(struct ref_store *refs, const char *logmsg, struct string_list *refnames, unsigned int flags) { ... struct ref_transaction *transaction; ... for_each_string_list_item(item, refnames) { transaction = ref_store_transaction_begin(refs, 0, &err); if (!transaction) { warning(_("could not begin transaction to delete %s: %s"), item->string, err.buf); strbuf_reset(&err); failures = 1; continue; } ret = ref_transaction_delete(transaction, item->string, NULL, NULL, flags, msg, &err); if (ret) { warning(_("could not delete reference %s: %s"), item->string, err.buf); strbuf_reset(&err); ref_transaction_free(transaction); failures = 1; continue; } ret = ref_transaction_commit(transaction, &err); if (ret) { warning(_("could not commit deletion of %s: %s"), item->string, err.buf); strbuf_reset(&err); failures = 1; } ref_transaction_free(transaction); } if (failures) ret = -1; strbuf_release(&err); free(msg); return ret; } The original implementation: Starts a single transaction using ref_store_transaction_begin(). Adds all deletions to that transaction. Commits the transaction. If any deletion fails, the entire transaction is aborted. On case-insensitive file systems, two refs like: may conflict at the file system level (e.g. both mapped to the same file or directory). If Git tries to delete both in one go, the transaction fails due to a lock file or unlink error. (Above are my assumptions till now). What has changed is: Deletes each reference in its own transaction struct ref_transaction *transaction = ref_store_transaction_begin(...); ref_transaction_delete(transaction, ...); ref_transaction_commit(transaction, ...); ref_transaction_free(transaction); If one deletion fails due to a case conflict, the others still proceed. It avoids rolling back the entire prune operation just because of a single failure. Keeps failure count and returns appropriately Signals that something went wrong, but Git can now give partial success feedback. The question I have is If this approach seems viable or perhaps any solution, would it be possible to write a test case for this scenario? Thank you - Jayatheerth ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Re [bug] pull --prune could not delete references due to lock file already exists error 2025-06-25 14:18 ` Re " K Jayatheerth @ 2025-06-27 18:59 ` brian m. carlson 2025-06-30 7:26 ` JAYATHEERTH K 2025-06-30 13:46 ` Karthik Nayak 1 sibling, 1 reply; 14+ messages in thread From: brian m. carlson @ 2025-06-27 18:59 UTC (permalink / raw) To: K Jayatheerth; +Cc: ryenus, git [-- Attachment #1: Type: text/plain, Size: 764 bytes --] On 2025-06-25 at 14:18:49, K Jayatheerth wrote: > First off thanks for reporting the bug :) > So I cannot test this bug as my files system is case sensitive > but to just read the code and give a thought in a direction (Assuming that the bug is recreatable) Just so you know, on Linux, you can create a case-insensitive JFS partition on a loopback device and on macOS, you can create a case-insensitive APFS or HFS partition in a disk image file that can then be mounted (I think using `hdiutil` or the directions at [0]). I have used the former in the rare occasion that I need to test a case-insensitive file system. [0] https://support.apple.com/en-ca/guide/disk-utility/dskutl11888/mac -- brian m. carlson (they/them) Toronto, Ontario, CA [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 262 bytes --] ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Re [bug] pull --prune could not delete references due to lock file already exists error 2025-06-27 18:59 ` brian m. carlson @ 2025-06-30 7:26 ` JAYATHEERTH K 0 siblings, 0 replies; 14+ messages in thread From: JAYATHEERTH K @ 2025-06-30 7:26 UTC (permalink / raw) To: brian m. carlson, K Jayatheerth, ryenus, git On Sat, Jun 28, 2025 at 12:29 AM brian m. carlson <sandals@crustytoothpaste.net> wrote: > > On 2025-06-25 at 14:18:49, K Jayatheerth wrote: > > First off thanks for reporting the bug :) > > So I cannot test this bug as my files system is case sensitive > > but to just read the code and give a thought in a direction (Assuming that the bug is recreatable) > > Just so you know, on Linux, you can create a case-insensitive JFS > partition on a loopback device and on macOS, you can create a > case-insensitive APFS or HFS partition in a disk image file that can > then be mounted (I think using `hdiutil` or the directions at [0]). > > I have used the former in the rare occasion that I need to test a > case-insensitive file system. > Thanks Brian, that’s really helpful! I’ll try setting up a loopback filesystem and use it for testing similar bugs in the future. Appreciate the tip! - Jayatheerth ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Re [bug] pull --prune could not delete references due to lock file already exists error 2025-06-25 14:18 ` Re " K Jayatheerth 2025-06-27 18:59 ` brian m. carlson @ 2025-06-30 13:46 ` Karthik Nayak 2025-06-30 14:20 ` brian m. carlson 2025-07-01 0:52 ` JAYATHEERTH K 1 sibling, 2 replies; 14+ messages in thread From: Karthik Nayak @ 2025-06-30 13:46 UTC (permalink / raw) To: K Jayatheerth, ryenus; +Cc: git [-- Attachment #1: Type: text/plain, Size: 3177 bytes --] K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes: [snip] > The original implementation: > Starts a single transaction using ref_store_transaction_begin(). > Adds all deletions to that transaction. > Commits the transaction. > If any deletion fails, the entire transaction is aborted. > On case-insensitive file systems, two refs like: > may conflict at the file system level (e.g. both mapped to the same file or directory). > If Git tries to delete both in one go, the transaction fails due to a lock file or unlink error. > (Above are my assumptions till now). > > What has changed is: > Deletes each reference in its own transaction > struct ref_transaction *transaction = ref_store_transaction_begin(...); > ref_transaction_delete(transaction, ...); > ref_transaction_commit(transaction, ...); > ref_transaction_free(transaction); > If one deletion fails due to a case conflict, the others still proceed. > It avoids rolling back the entire prune operation just because of a single failure. > Keeps failure count and returns appropriately > Signals that something went wrong, but Git can now give partial success feedback. > > > The question I have is > If this approach seems viable or perhaps any solution, > would it be possible to write a test case for this scenario? > You analysis is right. With 'kn/fetch-push-bulk-ref-update' in the works (possibly be merged to next soon), we will start using batched updates in git-fetch(1) too. Batched updates allow individual updates to fail, while allowing the transaction as a whole to succeed. Unfortunately, because our transaction mechanism doesn't handle conflicts, we separate out pruning as a pre-step. So this bug would still be present there. The issue with the fix you're suggesting is a huge performance drop, since creating individual transaction for each deletion has a lot of overhead and the reftable backend would perform a lot worse in such situations. I can see few solutions overall (including the one you suggested). One solution is to drop duplicates in case insensitive systems, this is the shortest and easiest fix for now. Perhaps something like (untested back of the hand code): diff --git a/builtin/fetch.c b/builtin/fetch.c index cc0a3deb61..bc79d74b82 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1352,10 +1352,16 @@ static int prune_refs(struct display_state *display_state, goto cleanup; } } else { + const char *prev; struct string_list refnames = STRING_LIST_INIT_NODUP; - for (ref = stale_refs; ref; ref = ref->next) + for (ref = stale_refs; ref; ref = ref->next) { + if (ignore_case && prev && !strcasecmp(ref->next, prev)) + continue; + string_list_append(&refnames, ref->name); + prev = ref->name; + } result = refs_delete_refs(get_main_ref_store(the_repository), "fetch: prune", &refnames, A bigger and eventual goal is to simply introduce conflict resolution in reference transactions. This would allow us to use batched transaction together for pruning and updating of refs, and using batched transactions would ensure that single reference changes can fail without failing the entire batch. - Karthik [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 690 bytes --] ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: Re [bug] pull --prune could not delete references due to lock file already exists error 2025-06-30 13:46 ` Karthik Nayak @ 2025-06-30 14:20 ` brian m. carlson 2025-06-30 21:10 ` Junio C Hamano ` (2 more replies) 2025-07-01 0:52 ` JAYATHEERTH K 1 sibling, 3 replies; 14+ messages in thread From: brian m. carlson @ 2025-06-30 14:20 UTC (permalink / raw) To: Karthik Nayak; +Cc: K Jayatheerth, ryenus, git [-- Attachment #1: Type: text/plain, Size: 1523 bytes --] On 2025-06-30 at 13:46:35, Karthik Nayak wrote: > I can see few solutions overall (including the one you suggested). > > One solution is to drop duplicates in case insensitive systems, this is > the shortest and easiest fix for now. > > Perhaps something like (untested back of the hand code): > > diff --git a/builtin/fetch.c b/builtin/fetch.c > index cc0a3deb61..bc79d74b82 100644 > --- a/builtin/fetch.c > +++ b/builtin/fetch.c > @@ -1352,10 +1352,16 @@ static int prune_refs(struct display_state > *display_state, > goto cleanup; > } > } else { > + const char *prev; > struct string_list refnames = STRING_LIST_INIT_NODUP; > > - for (ref = stale_refs; ref; ref = ref->next) > + for (ref = stale_refs; ref; ref = ref->next) { > + if (ignore_case && prev && !strcasecmp(ref->next, prev)) > + continue; > + > string_list_append(&refnames, ref->name); > + prev = ref->name; > + } This won't work in the general case, since the two refs that match case insensitively aren't guaranteed to be adjacent. For instance: refs/heads/AAAA refs/heads/AAAB refs/heads/aaaa refs/heads/aaab They'll be in the above order for a bytewise comparison, but the matching entries won't be adjacent in the list. Another option is for users on case-insensitive systems to use reftable, which won't have the same problems as the file-based backend and will preserve case properly. -- brian m. carlson (they/them) Toronto, Ontario, CA [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 262 bytes --] ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Re [bug] pull --prune could not delete references due to lock file already exists error 2025-06-30 14:20 ` brian m. carlson @ 2025-06-30 21:10 ` Junio C Hamano 2025-07-01 10:31 ` Patrick Steinhardt 2025-07-01 8:20 ` Karthik Nayak 2025-07-02 4:50 ` Chris Torek 2 siblings, 1 reply; 14+ messages in thread From: Junio C Hamano @ 2025-06-30 21:10 UTC (permalink / raw) To: brian m. carlson; +Cc: Karthik Nayak, K Jayatheerth, ryenus, git "brian m. carlson" <sandals@crustytoothpaste.net> writes: > Another option is for users on case-insensitive systems to use reftable, > which won't have the same problems as the file-based backend and will > preserve case properly. The more guinea-pigs^Wadopters we have, the merrier we are ;-). ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Re [bug] pull --prune could not delete references due to lock file already exists error 2025-06-30 21:10 ` Junio C Hamano @ 2025-07-01 10:31 ` Patrick Steinhardt 2025-07-01 16:14 ` Junio C Hamano 0 siblings, 1 reply; 14+ messages in thread From: Patrick Steinhardt @ 2025-07-01 10:31 UTC (permalink / raw) To: Junio C Hamano Cc: brian m. carlson, Karthik Nayak, K Jayatheerth, ryenus, git On Mon, Jun 30, 2025 at 02:10:36PM -0700, Junio C Hamano wrote: > "brian m. carlson" <sandals@crustytoothpaste.net> writes: > > > Another option is for users on case-insensitive systems to use reftable, > > which won't have the same problems as the file-based backend and will > > preserve case properly. > > The more guinea-pigs^Wadopters we have, the merrier we are ;-). I'd really like to start thinking about reftables as the default backend. They fix filesystem-specific issues, compress better, are more efficient in most (not all) use cases, sometimes significantly so, have better properties when it comes to repository maintenance. We could for example make "features.experimental" enable the reftable backend by default and add a note to the Git 3.0 breaking changes in that spirit. I'm quite certain that reftables are stable enough to not cause any problems as used via Git. We have it rolled out to a couple hundred thousand of repositories on our staging systems at GitLab, and will soon roll them out to production systems. I myself have been running with reftables for 1.5 years now and haven't seen a bug after the first couple months. But the bigger issue unfortunately is the ecosystem. JGit supports reftables, but other implementations like libgit2 and Gitoxide don't. I partially got myself to blame here, because I haven't gotten around to updating the libgit2 pull request and am lacking the time to finish that one. So it's probably a non-starter if all tools that use those libraries cannot access such reftable-enabled Git repositories at all anymore. I bet there's also tons of scripts out there that just reach into the filesystem to do stuff, but that's something that we cannot really help with. Too bad :/ Patrick ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Re [bug] pull --prune could not delete references due to lock file already exists error 2025-07-01 10:31 ` Patrick Steinhardt @ 2025-07-01 16:14 ` Junio C Hamano 2025-07-02 8:50 ` Patrick Steinhardt 0 siblings, 1 reply; 14+ messages in thread From: Junio C Hamano @ 2025-07-01 16:14 UTC (permalink / raw) To: Patrick Steinhardt Cc: brian m. carlson, Karthik Nayak, K Jayatheerth, ryenus, git Patrick Steinhardt <ps@pks.im> writes: > I'd really like to start thinking about reftables as the default > backend. They fix filesystem-specific issues, compress better, are more > efficient in most (not all) use cases, sometimes significantly so, have > better properties when it comes to repository maintenance. We could for > example make "features.experimental" enable the reftable backend by > default and add a note to the Git 3.0 breaking changes in that spirit. I am a bit surprised that there is no reference to reftable in the breaking changes document ;-) I am all for allowing users to opt into it dynamically, and feature.experimental is the ideal mechanism to do so. Currently it sets four configuration variables, and making it five would not make it too scary for adventurous users. > I bet there's also tons of scripts out there that just reach into the > filesystem to do stuff, but that's something that we cannot really help > with. I thought you've done enough to make sure that common things people would want to do by direct access to the .git/refs/ hierarchy can be easily done with plumbing commands instead, so it would probably be a matter of writing and publicizing the "how to migrate to the world where you cannot write into files under .git/refs/ directory" document? Thanks. ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Re [bug] pull --prune could not delete references due to lock file already exists error 2025-07-01 16:14 ` Junio C Hamano @ 2025-07-02 8:50 ` Patrick Steinhardt 0 siblings, 0 replies; 14+ messages in thread From: Patrick Steinhardt @ 2025-07-02 8:50 UTC (permalink / raw) To: Junio C Hamano Cc: brian m. carlson, Karthik Nayak, K Jayatheerth, ryenus, git On Tue, Jul 01, 2025 at 09:14:19AM -0700, Junio C Hamano wrote: > Patrick Steinhardt <ps@pks.im> writes: > > I bet there's also tons of scripts out there that just reach into the > > filesystem to do stuff, but that's something that we cannot really help > > with. > > I thought you've done enough to make sure that common things people > would want to do by direct access to the .git/refs/ hierarchy can be > easily done with plumbing commands instead, so it would probably be > a matter of writing and publicizing the "how to migrate to the world > where you cannot write into files under .git/refs/ directory" > document? Yeah, all the tools are there. I'm not even sure whether such a guide would be required in the first place -- tools are just what you are used to already anyway. So I don't know whether I really need to explain how to use git-update-ref(1) and friends. I am of course biased here, so if disagree and think that this would be required I might just do it. Anyway, I'll hack something up and send it to the mailing list soonish to get the discussion going. Patrick ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Re [bug] pull --prune could not delete references due to lock file already exists error 2025-06-30 14:20 ` brian m. carlson 2025-06-30 21:10 ` Junio C Hamano @ 2025-07-01 8:20 ` Karthik Nayak 2025-07-02 4:50 ` Chris Torek 2 siblings, 0 replies; 14+ messages in thread From: Karthik Nayak @ 2025-07-01 8:20 UTC (permalink / raw) To: brian m. carlson; +Cc: K Jayatheerth, ryenus, git [-- Attachment #1: Type: text/plain, Size: 1869 bytes --] "brian m. carlson" <sandals@crustytoothpaste.net> writes: > On 2025-06-30 at 13:46:35, Karthik Nayak wrote: >> I can see few solutions overall (including the one you suggested). >> >> One solution is to drop duplicates in case insensitive systems, this is >> the shortest and easiest fix for now. >> >> Perhaps something like (untested back of the hand code): >> >> diff --git a/builtin/fetch.c b/builtin/fetch.c >> index cc0a3deb61..bc79d74b82 100644 >> --- a/builtin/fetch.c >> +++ b/builtin/fetch.c >> @@ -1352,10 +1352,16 @@ static int prune_refs(struct display_state >> *display_state, >> goto cleanup; >> } >> } else { >> + const char *prev; >> struct string_list refnames = STRING_LIST_INIT_NODUP; >> >> - for (ref = stale_refs; ref; ref = ref->next) >> + for (ref = stale_refs; ref; ref = ref->next) { >> + if (ignore_case && prev && !strcasecmp(ref->next, prev)) >> + continue; >> + >> string_list_append(&refnames, ref->name); >> + prev = ref->name; >> + } > > This won't work in the general case, since the two refs that match case > insensitively aren't guaranteed to be adjacent. For instance: > > refs/heads/AAAA > refs/heads/AAAB > refs/heads/aaaa > refs/heads/aaab > > They'll be in the above order for a bytewise comparison, but the > matching entries won't be adjacent in the list. > Indeed. Good catch, this wouldn't work! I guess you'd have to use a hashmap in that case, I think that is what Jayatheerth also mentioned in his reply. > Another option is for users on case-insensitive systems to use reftable, > which won't have the same problems as the file-based backend and will > preserve case properly. Yes, that's always recommended! But until we start marking the files backend as deprecated, we do have to plug this bug. > -- > brian m. carlson (they/them) > Toronto, Ontario, CA [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 690 bytes --] ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Re [bug] pull --prune could not delete references due to lock file already exists error 2025-06-30 14:20 ` brian m. carlson 2025-06-30 21:10 ` Junio C Hamano 2025-07-01 8:20 ` Karthik Nayak @ 2025-07-02 4:50 ` Chris Torek 2025-07-02 15:37 ` Junio C Hamano 2 siblings, 1 reply; 14+ messages in thread From: Chris Torek @ 2025-07-02 4:50 UTC (permalink / raw) To: brian m. carlson, Karthik Nayak, K Jayatheerth, ryenus, git On Mon, Jun 30, 2025 at 7:21 AM brian m. carlson <sandals@crustytoothpaste.net> wrote: [regarding] > > + if (ignore_case && prev && !strcasecmp(ref->next, prev)) > This won't work in the general case, since the two refs that match case > insensitively aren't guaranteed to be adjacent. Also worth mention: it's not just case-folding that matters. On OS X (Macs), path names get "normalized" so that the names s c h combining-umlaut o n and s c h umlaut-o n refer to the *same* file or directory. On a typical Linux/Unix FS, they differ. (I don't know what Windows does!) So, if you have a "folder-full" of "pretty" German refnames, some spelled one way and some another, well... (It's not clear to me what, if anything, Git should attempt to do here.) Chris ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Re [bug] pull --prune could not delete references due to lock file already exists error 2025-07-02 4:50 ` Chris Torek @ 2025-07-02 15:37 ` Junio C Hamano 0 siblings, 0 replies; 14+ messages in thread From: Junio C Hamano @ 2025-07-02 15:37 UTC (permalink / raw) To: Chris Torek; +Cc: brian m. carlson, Karthik Nayak, K Jayatheerth, ryenus, git Chris Torek <chris.torek@gmail.com> writes: > On Mon, Jun 30, 2025 at 7:21 AM brian m. carlson > <sandals@crustytoothpaste.net> wrote: > [regarding] >> > + if (ignore_case && prev && !strcasecmp(ref->next, prev)) >> This won't work in the general case, since the two refs that match case >> insensitively aren't guaranteed to be adjacent. > > Also worth mention: it's not just case-folding that matters. > > On OS X (Macs), path names get "normalized" so that the names > > s c h combining-umlaut o n > > and > > s c h umlaut-o n > > refer to the *same* file or directory. On a typical Linux/Unix FS, they differ. > > (I don't know what Windows does!) > > So, if you have a "folder-full" of "pretty" German refnames, some > spelled one way and some another, well... > > (It's not clear to me what, if anything, Git should attempt to do here.) The system supplied argv[] is fed to the precompose_argv_prefix() helper in compat/precompose_utf8.c; opendir/readdir/closedir are also wrapped with similar NFD/NFC normalization (really, UTF-8-MAC vs UTF-8) helpers defined in the same file. So the path you read (via the opendir/readdir like dir.c does) from the system, or the path you are fed from the command line (via argv[]), are normalized before code in Git above the compat layer even sees them. The path recoreded in various mechanisms in Git like the index and the tree objects are all normalized. ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Re [bug] pull --prune could not delete references due to lock file already exists error 2025-06-30 13:46 ` Karthik Nayak 2025-06-30 14:20 ` brian m. carlson @ 2025-07-01 0:52 ` JAYATHEERTH K 1 sibling, 0 replies; 14+ messages in thread From: JAYATHEERTH K @ 2025-07-01 0:52 UTC (permalink / raw) To: Karthik Nayak; +Cc: ryenus, git On Mon, Jun 30, 2025 at 7:16 PM Karthik Nayak <karthik.188@gmail.com> wrote: > > K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes: > > [snip] > > > The original implementation: > > Starts a single transaction using ref_store_transaction_begin(). > > Adds all deletions to that transaction. > > Commits the transaction. > > If any deletion fails, the entire transaction is aborted. > > On case-insensitive file systems, two refs like: > > may conflict at the file system level (e.g. both mapped to the same file or directory). > > If Git tries to delete both in one go, the transaction fails due to a lock file or unlink error. > > (Above are my assumptions till now). > > > > What has changed is: > > Deletes each reference in its own transaction > > struct ref_transaction *transaction = ref_store_transaction_begin(...); > > ref_transaction_delete(transaction, ...); > > ref_transaction_commit(transaction, ...); > > ref_transaction_free(transaction); > > If one deletion fails due to a case conflict, the others still proceed. > > It avoids rolling back the entire prune operation just because of a single failure. > > Keeps failure count and returns appropriately > > Signals that something went wrong, but Git can now give partial success feedback. > > > > > > The question I have is > > If this approach seems viable or perhaps any solution, > > would it be possible to write a test case for this scenario? > > > > You analysis is right. With 'kn/fetch-push-bulk-ref-update' in the works > (possibly be merged to next soon), we will start using batched updates > in git-fetch(1) too. Batched updates allow individual updates to fail, > while allowing the transaction as a whole to succeed. > > Unfortunately, because our transaction mechanism doesn't handle > conflicts, we separate out pruning as a pre-step. So this bug would > still be present there. > > The issue with the fix you're suggesting is a huge performance drop, > since creating individual transaction for each deletion has a lot of > overhead and the reftable backend would perform a lot worse in such > situations. > > I can see few solutions overall (including the one you suggested). > > One solution is to drop duplicates in case insensitive systems, this is > the shortest and easiest fix for now. > > Perhaps something like (untested back of the hand code): > > diff --git a/builtin/fetch.c b/builtin/fetch.c > index cc0a3deb61..bc79d74b82 100644 > --- a/builtin/fetch.c > +++ b/builtin/fetch.c > @@ -1352,10 +1352,16 @@ static int prune_refs(struct display_state > *display_state, > goto cleanup; > } > } else { > + const char *prev; > struct string_list refnames = STRING_LIST_INIT_NODUP; > > - for (ref = stale_refs; ref; ref = ref->next) > + for (ref = stale_refs; ref; ref = ref->next) { > + if (ignore_case && prev && !strcasecmp(ref->next, prev)) > + continue; > + > string_list_append(&refnames, ref->name); > + prev = ref->name; > + } > > result = refs_delete_refs(get_main_ref_store(the_repository), > "fetch: prune", &refnames, > > > A bigger and eventual goal is to simply introduce conflict resolution in > reference transactions. This would allow us to use batched transaction > together for pruning and updating of refs, and using batched > transactions would ensure that single reference changes can fail without > failing the entire batch. > > - Karthik Agreed that was a lazy fix. I had thought it(multiple transactions) was not a viable solution. Something I find interesting is Loop through the refs marked for deletion (stale_refs) If the filesystem is case-insensitive Convert the ref name to lowercase Check if this lowercase version was already seen If it's not a duplicate, store the lowercase name Add the actual (original-cased) ref to the refnames list Free up the memory used in seen_refs map --- struct strmap seen_refs = STRMAP_INIT; ... for (ref = stale_refs; ref; ref = ref->next) { if (ignore_case) { char *lower = xstrdup(ref->name); str_tolower(lower); if (strmap_contains(&seen_refs, lower)) { /* Skip this ref it would collide on case-insensitive FS */ warning(_("Skipping deletion of '%s' due to case-insensitive conflict"), ref->name); free(lower); continue; } /* Keep the lowercased key in the map to catch future conflicts */ strmap_put(&seen_refs, lower, (void *)1); } string_list_append(&refnames, ref->name); } strmap_clear(&seen_refs, 1); --- warning(_("Skipping deletion of '%s' due to case-insensitive conflict"), ref->name); When we find duplicates it should work fine I guess. Let me know if this direction makes sense or if you'd prefer a different handling strategy, happy to start sending patches. ^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2025-07-02 15:38 UTC | newest] Thread overview: 14+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-06-25 12:32 [bug] pull --prune could not delete references due to lock file already exists error ryenus 2025-06-25 14:18 ` Re " K Jayatheerth 2025-06-27 18:59 ` brian m. carlson 2025-06-30 7:26 ` JAYATHEERTH K 2025-06-30 13:46 ` Karthik Nayak 2025-06-30 14:20 ` brian m. carlson 2025-06-30 21:10 ` Junio C Hamano 2025-07-01 10:31 ` Patrick Steinhardt 2025-07-01 16:14 ` Junio C Hamano 2025-07-02 8:50 ` Patrick Steinhardt 2025-07-01 8:20 ` Karthik Nayak 2025-07-02 4:50 ` Chris Torek 2025-07-02 15:37 ` Junio C Hamano 2025-07-01 0:52 ` JAYATHEERTH K
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).