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

Re: `git push --porcelain` has no effect when deleting a ref which does not exist

From
Xavier Morel <xmo@odoo.com>
Date
Aug 7, 2026, 06:51 UTC
Message-ID
<6ea78e82-0b35-4e73-99ff-ad6b653bc103@odoo.com>
In-Reply-To
<xmqq33wssf6x.fsf@gitster.g>
On 05/08/2026 18:30, Junio C Hamano wrote:
Show 29 quoted lines
> Xavier Morel <xmo@odoo.com> writes:
> 
>> Using `push --delete --porcelain` with refs which are extant correctly
>> outputs the relevant information in the documented format:
>>
>> -	:refs/heads/<branch1>	[deleted]
>> -	:refs/heads/<branch2>	[deleted]
>>
>> However doing the same with refs which don't exist on the remote (e.g.
>> because of a concurrent deletion) has the error written out in
>> human-targeted text:
>>
>> error: unable to delete '<branch1>': remote ref does not exist
>> error: unable to delete '<branch2>': remote ref does not exist
>>
>> I would have expected something along the lines of:
>>
>> !	:refs/heads/<branch>	[remote failure]
>>
>> which would be machine-readable as documented for the `--porcelain`
>> flag. Was that intended or is it just something that fell through the
>> cracks of code convolution?
> 
> If I have to guess, I would say it is because nobody thought of
> covering this usage pattern, which allows you to randomly throw a
> deletion request to probe what does and what does not exist on the
> other side.
> 
> Patches welcome.

Looking at the current code, the abort when requesting the deletion of a ref which is not on the remote is pretty early in the process, during ref matching, which then causes `push` to bail.

Reading some of the followup the following call `set_ref_status_for_push` can already set statuses on remote refs before the push, in which case such refs with statuses set will be ignored for the actual network operation (and the entire thing would be skipped if atomic), and then we get to the reporting and teardown.

So it looks like
- `match_explicit` could create a dummy dest ref and set its status to 
some sort of failure value (either an existing one or a new one) instead 
of aborting
- then `set_ref_status_for_push` should skip over refs which already 
have a status set (so it doesn't overwrite a previous error)
- push_refs_with_push all refs with a rejection status already set
- and then the formatting needs to get adapted for the new mode / case

And then trying to delete a non-existent ref would appear in the report normally, and valid pushes would be performed instead of ignored, unless `atomic` was set in which case they'd all be aborted, similar to other abortions from "pre-push" checks by set_ref_status_for_push.

Does that seem to make sense or did I miss something critical? Do you foresee significant issues?

Would you rather a new status code for this case or extending an existing case? e.g. I could see REF_STATUS_REJECT_NODELETE on an otherwise zeroed deletion ref for missing on remote, and a non-zeroed deletion ref would be the existing "remote rejecting the deletion" case so less code (and notably not push_refs_with_push) would have to be adapted, but it would make the new case a bit more implicit.

Previous: Junio C Hamano
Message 3 of 3 in “`git push --porcelain` has no effect when deleting a ref which does not exist”
  1. Xavier MorelAug 5, 2026
  2. Junio C HamanoAug 5, 2026
  3. Xavier MorelAug 7, 2026

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.