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

Re: [PATCH v3 12/19] initial_ref_transaction_commit(): check for duplicate refs

From
Michael Haggerty <mhagger@alum.mit.edu>
Date
Jun 23, 2015, 07:11 UTC
Message-ID
<558906A4.8060106@alum.mit.edu>
In-Reply-To
<xmqqtwtzfo79.fsf@gitster.dls.corp.google.com>
On 06/22/2015 11:06 PM, Junio C Hamano wrote:
Show 33 quoted lines
> Michael Haggerty <mhagger@alum.mit.edu> writes:
> 
>> Error out if the ref_transaction includes more than one update for any
>> refname.
>>
>> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
>> ---
>>  refs.c | 11 +++++++++++
>>  1 file changed, 11 insertions(+)
> 
> This somehow feels like "ehh, I now know better and this function
> should have been like this from the beginning" to me.
> 
> But that is OK.
> 
> Is the initial creation logic too fragile to deserve its own
> function to force callers to think about it, by the way?
> 
> What I am wondering is if we could turn the safety logic that appear
> here (i.e. no existing refs must be assumed by the set of updates,
> etc.)  into an optimization cue and implement this as a special case
> helper to ref_transaction_commit(), i.e.
> 
> 	ref_transaction_commit(...)
>         {
> 		if (updates are all initial creation &&
>                     no existing refs in repository)
> 			return initial_ref_transaction_commit(...);
> 		/* otherwise we do the usual thing */
> 		...
> 	}
> 
> and have "clone" call ref_transaction_commit() as usual.

The safety logic in this function is (approximately) necessary, but not sufficient, to guarantee safety. One of the shortcuts that it takes is not locking the references while they are being created. Therefore, it would be unsafe for one process to call ref_transaction_commit() while another is calling initial_ref_transaction_commit(). So the caller has to "know" somehow that no other processes are working in the repository for this optimization to be safe. It conveys that knowledge by calling initial_ref_transaction_commit() rather than ref_transaction_commit().

Of course the next question is, "How does `git clone` know that no other process is working in the new repository?" Actually, it doesn't. For example, I just verified that I can run

    git clone $URL mygit &
    sleep 0.1
    cd mygit
    git commit --allow-empty -m "New root commit"

and thereby "overwrite" the upstream `master` without the usual non-fast-forward protection. I guess we are just relying on the user's common sense not to run Git commands in a new repository before its creation is complete.

I suppose we *could* special-case `git clone` to not finish the initialization of the repository (for example, not write the `config` file) until *after* the packed-refs file is written. This would prevent other git processes from recognizing the directory as a Git repository and so prevent them from running before the clone is finished.

But I think if anything it would make more sense to go the other direction:
* Teach ref_transaction_commit() an option that asks it to write
  references updates to packed-refs instead of loose refs (but
  locking the references as usual).
* Change clone to use ref_transaction_commit() like everybody
  else, passing it the new REFS_WRITE_TO_PACKED_REFS option.

Then clone would participate in the normal locking protocol, and it wouldn't *matter* if another process runs before the clone is finished. There would also be some consistency benefits. For example, if core.logallrefupdates is set globally or on the command line, the initial reference creations would be reflogged. And other operations that write references in bulk could use the new REFS_WRITE_TO_PACKED_REFS option to prevent loose reference proliferation.

But I don't think any of this is a problem in practice, and I think we can live with using the optimized-but-not-100%-safe initial_ref_transaction_commit() for cloning.

Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
Previous: Junio C HamanoNext: Junio C Hamano
Message 15 of 24 in “Improve "refs" encapsulation and speed up deletes”
  1. 00/19 Improve "refs" encapsulation and speed up deletesMichael Haggerty, Jun 22, 2015
  2. 01/19 delete_ref(): move declaration to refs.hMichael Haggerty, Jun 22, 2015
  3. 02/19 remove_branches(): remove temporaryMichael Haggerty, Jun 22, 2015
  4. 03/19 delete_ref(): handle special case more explicitlyMichael Haggerty, Jun 22, 2015
  5. 04/19 delete_refs(): new function for the refs APIMichael Haggerty, Jun 22, 2015
  6. 05/19 delete_refs(): make error message more genericMichael Haggerty, Jun 22, 2015
  7. 06/19 delete_refs(): bail early if the packed-refs file cannot be rewrittenMichael Haggerty, Jun 22, 2015
  8. 07/19 prune_remote(): use delete_refs()Michael Haggerty, Jun 22, 2015
  9. 08/19 prune_refs(): use delete_refs()Michael Haggerty, Jun 22, 2015
  10. 09/19 repack_without_refs(): make function privateMichael Haggerty, Jun 22, 2015
  11. 10/19 initial_ref_transaction_commit(): function for initial ref creationMichael Haggerty, Jun 22, 2015
  12. 11/19 refs: remove some functions from the module's public interfaceMichael Haggerty, Jun 22, 2015
  13. 12/19 initial_ref_transaction_commit(): check for duplicate refsMichael Haggerty, Jun 22, 2015
  14. Junio C HamanoJun 22, 2015
  15. Michael HaggertyJun 23, 2015
  16. Junio C HamanoJun 23, 2015
  17. 13/19 initial_ref_transaction_commit(): check for ref D/F conflictsMichael Haggerty, Jun 22, 2015
  18. 14/19 refs: move the remaining ref module declarations to refs.hMichael Haggerty, Jun 22, 2015
  19. 15/19 refs.h: add some parameter names to function declarationsMichael Haggerty, Jun 22, 2015
  20. 16/19 check_branch_commit(): make first parameter constMichael Haggerty, Jun 22, 2015
  21. 17/19 update_ref(): don't read old reference value before deleteMichael Haggerty, Jun 22, 2015
  22. 18/19 cmd_update_ref(): make logic more straightforwardMichael Haggerty, Jun 22, 2015
  23. 19/19 delete_ref(): use the usual convention for old_sha1Michael Haggerty, Jun 22, 2015
  24. Junio C HamanoJun 22, 2015

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.