From: Ronnie Sahlberg <hidden> Date: 2016-06-15 23:00:45
This patch series changes most of the places where the ref functions for
locking and writing refs to instead use the new ref transaction API. There
are still three more places where write_ref_sha1() is called from outside
of refs.c but those all will require more complex work and review so those
changes belong in a different patch series.
Version 2:
- Add a patch to ref_transaction_commit to make it honor onerr even if the
error triggered in ref_Transaction_commit itself rather than in a call
to other functions (that already honor onerr).
- Add a patch to make the update_ref() helper function use transactions
internally.
- Change ref_transaction_update to die() instead of error() if we pass
if a NULL old_sha1 but have have_old == true.
- Change ref_transaction_create to die() instead of error() if new_sha1
is false but we pass it a null_sha1.
- Change ref_transaction_delete die() instead of error() if we pass
if a NULL old_sha1 but have have_old == true.
- Change several places to do if(!transaction || ref_transaction_update()
|| ref_Transaction_commit()) die(generic-message) instead of checking each
step separately and having a different message for each failure.
Most users are likely not interested in what step of the transaction
failed and only whether it failed or not.
- Change commit.c to only pass a pointer to ref_transaction_update
iff current_head is non-NULL.
The previous patch used to compute a garbage pointer for
current_head->object.sha1 and relied on the fact that ref_transaction_update
would not try to dereference this pointer if !!current_head was 0.
- Updated commit message for the walker_fetch change to try to justify why
the change in locking semantics should not be harmful.
Ronnie Sahlberg (13):
refs.c: constify the sha arguments for
ref_transaction_create|delete|update
refs.c: use a single exit path from transaction commit and handle
onerr
refs.c: change ref_transaction_update() to do error checking and
return status
refs.c: change ref_transaction_create to do error checking and return
status
refs.c: ref_transaction_delete to check for error and return status
tag.c: use ref transactions when doing updates
replace.c: use the ref transaction functions for updates
commit.c: use ref transactions for updates
sequencer.c: use ref transactions for all ref updates
fast-import.c: change update_branch to use ref transactions
branch.c: use ref transaction for all ref updates
walker.c: use ref transaction for ref updates
refs.c: change update_ref to use a transaction
branch.c | 16 ++++++----
builtin/commit.c | 23 +++++++-------
builtin/replace.c | 13 ++++----
builtin/tag.c | 11 +++----
builtin/update-ref.c | 19 +++++++-----
fast-import.c | 23 +++++++++-----
refs.c | 84 +++++++++++++++++++++++++++++++++++++++-------------
refs.h | 25 ++++++++--------
sequencer.c | 17 ++++++++---
walker.c | 45 +++++++++++++---------------
10 files changed, 172 insertions(+), 104 deletions(-)
--
1.9.1.515.g3b87021
From: Ronnie Sahlberg <hidden> Date: 2016-06-15 23:00:45
Do basic error checking in ref_transaction_create() and make it return
status. Update all callers to check the result of ref_transaction_create()
Signed-off-by: Ronnie Sahlberg <redacted>
---
builtin/update-ref.c | 4 +++-
refs.c | 17 +++++++++++------
refs.h | 8 ++++----
3 files changed, 18 insertions(+), 11 deletions(-)
From: Ronnie Sahlberg <hidden> Date: 2016-06-15 23:00:45
ref_transaction_create|delete|update has no need to modify the sha1
arguments passed to it so it should use const unsigned char* instead
of unsigned char*.
Some functions, such as fast_forward_to(), already have its old/new
sha1 arguments as consts. This function will at some point need to
use ref_transaction_update() in which case this change is required.
Signed-off-by: Ronnie Sahlberg <redacted>
---
refs.c | 7 ++++---
refs.h | 7 ++++---
2 files changed, 8 insertions(+), 6 deletions(-)
From: Ronnie Sahlberg <hidden> Date: 2016-06-15 23:00:45
Update ref_transaction_commit to have a single exit path and process onerr
if an error occured during hte commit. This does mean that in case of an error
occuring for UPDATE_REFS_MSG_ON_ERR during the calls to update_ref_lock or
update_ref_write we will log errors from both those functions as well as a
generic message from ref_transaction_commit.
I thought a while to make the MSG_ON_ERR message in ref_transaction_commit
conditional to only trigger if the error was not triggered by the two functions
we call that also take onerr, and which would already have logger an error
already for this case, but the code would just look too awful. I think it
is acceptable to log two error messages for those two cases than to badify
the commit code.
Signed-off-by: Ronnie Sahlberg <redacted>
---
refs.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
@@ -3414,12 +3414,12 @@ int ref_transaction_commit(struct ref_transaction *transaction,constchar*msg,enumaction_on_erronerr){intret=0,delnum=0,i;-constchar**delnames;+constchar**delnames=NULL;intn=transaction->nr;structref_update**updates=transaction->updates;if(!n)-return0;+gotocleanup;/* Allocate work space */delnames=xmalloc(sizeof(*delnames)*n);
@@ -3366,19 +3366,24 @@ int ref_transaction_create(struct ref_transaction *transaction,return0;}-voidref_transaction_delete(structref_transaction*transaction,-constchar*refname,-constunsignedchar*old_sha1,-intflags,inthave_old)+intref_transaction_delete(structref_transaction*transaction,+constchar*refname,+constunsignedchar*old_sha1,+intflags,inthave_old){-structref_update*update=add_update(transaction,refname);+structref_update*update;+if(have_old&&!old_sha1)+die("have_old is true but old_sha1 is NULL");++update=add_update(transaction,refname);update->flags=flags;update->have_old=have_old;if(have_old){assert(!is_null_sha1(old_sha1));hashcpy(update->old_sha1,old_sha1);}+return0;}intupdate_ref(constchar*action,constchar*refname,
@@ -129,7 +129,7 @@ static int replace_object(const char *object_ref, const char *replace_ref,unsignedcharobject[20],prev[20],repl[20];enumobject_typeobj_type,repl_type;charref[PATH_MAX];-structref_lock*lock;+structref_transaction*transaction;if(get_sha1(object_ref,object))die("Failed to resolve '%s' as a valid ref.",object_ref);
@@ -157,11 +157,12 @@ static int replace_object(const char *object_ref, const char *replace_ref,elseif(!force)die("replace ref '%s' already exists",ref);-lock=lock_any_ref_for_update(ref,prev,0,NULL);-if(!lock)-die("%s: cannot lock the ref",ref);-if(write_ref_sha1(lock,repl,NULL)<0)-die("%s: cannot update the ref",ref);+transaction=ref_transaction_begin();+if(!transaction||+ref_transaction_update(transaction,ref,repl,prev,+0,!is_null_sha1(prev))||+ref_transaction_commit(transaction,NULL,UPDATE_REFS_DIE_ON_ERR))+die(_("%s: failed to replace ref"),ref);return0;}
From: Ronnie Sahlberg <hidden> Date: 2016-06-15 23:00:46
Change commit.c to use ref transactions for all ref updates.
Make sure we pass a NULL pointer to ref_transaction_update if have_old
is false.
Signed-off-by: Ronnie Sahlberg <redacted>
---
builtin/commit.c | 23 +++++++++++++----------
1 file changed, 13 insertions(+), 10 deletions(-)
From: Ronnie Sahlberg <hidden> Date: 2016-06-15 23:00:46
Change to use ref transactions for all updates to refs.
Signed-off-by: Ronnie Sahlberg <redacted>
---
sequencer.c | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
@@ -272,17 +272,26 @@ static int error_dirty_index(struct replay_opts *opts)staticintfast_forward_to(constunsignedchar*to,constunsignedchar*from,intunborn,structreplay_opts*opts){-structref_lock*ref_lock;+structref_transaction*transaction;structstrbufsb=STRBUF_INIT;intret;read_cache();if(checkout_fast_forward(from,to,1))exit(1);/* the callee should have complained already */-ref_lock=lock_any_ref_for_update("HEAD",unborn?null_sha1:from,-0,NULL);++transaction=ref_transaction_begin();+if(!transaction)+return1;+if(ref_transaction_update(transaction,"HEAD",to,from,+0,!unborn)){+ref_transaction_rollback(transaction);+return1;+}+strbuf_addf(&sb,"%s: fast-forward",action_name(opts));-ret=write_ref_sha1(ref_lock,to,sb.buf);+ret=ref_transaction_commit(transaction,sb.buf,+UPDATE_REFS_MSG_ON_ERR);strbuf_release(&sb);returnret;}
@@ -1678,36 +1678,43 @@ found_entry:staticintupdate_branch(structbranch*b){staticconstchar*msg="fast-import";-structref_lock*lock;+structref_transaction*transaction;unsignedcharold_sha1[20];if(is_null_sha1(b->sha1))return0;if(read_ref(b->name,old_sha1))hashclr(old_sha1);-lock=lock_any_ref_for_update(b->name,old_sha1,0,NULL);-if(!lock)-returnerror("Unable to lock %s",b->name);+transaction=ref_transaction_begin();+if(!transaction)+returnerror("Unable to begin transaction for %s",b->name);if(!force_update&&!is_null_sha1(old_sha1)){structcommit*old_cmit,*new_cmit;old_cmit=lookup_commit_reference_gently(old_sha1,0);new_cmit=lookup_commit_reference_gently(b->sha1,0);if(!old_cmit||!new_cmit){-unlock_ref(lock);+ref_transaction_rollback(transaction);returnerror("Branch %s is missing commits.",b->name);}if(!in_merge_bases(old_cmit,new_cmit)){-unlock_ref(lock);+ref_transaction_rollback(transaction);warning("Not updating %s"" (new tip %s does not contain %s)",b->name,sha1_to_hex(b->sha1),sha1_to_hex(old_sha1));return-1;}}-if(write_ref_sha1(lock,b->sha1,msg)<0)-returnerror("Unable to update %s",b->name);+if(ref_transaction_update(transaction,b->name,b->sha1,old_sha1,+0,1)){+ref_transaction_rollback(transaction);+returnerror("Unable to update transaction for %s",b->name);+}+if(ref_transaction_commit(transaction,msg,+UPDATE_REFS_QUIET_ON_ERR))+returnerror("Unable to commit transaction for %s",b->name);+return0;}
@@ -286,9 +286,12 @@ void create_branch(const char *head,hashcpy(sha1,commit->object.sha1);if(!dont_change_ref){-lock=lock_any_ref_for_update(ref.buf,NULL,0,NULL);-if(!lock)-die_errno(_("Failed to lock ref for update"));+transaction=ref_transaction_begin();+if(!transaction)+die_errno(_("Failed to begin transaction"));+if(ref_transaction_update(transaction,ref.buf,sha1,NULL,+0,0))+die_errno(_("Failed to update transaction"));}if(reflog)
@@ -305,8 +308,9 @@ void create_branch(const char *head,setup_tracking(ref.buf+11,real_ref,track,quiet);if(!dont_change_ref)-if(write_ref_sha1(lock,sha1,msg)<0)-die_errno(_("Failed to write ref"));+if(ref_transaction_commit(transaction,msg,+UPDATE_REFS_DIE_ON_ERR))+die_errno(_("Failed to commit transaction"));strbuf_release(&ref);free(real_ref);
From: Ronnie Sahlberg <hidden> Date: 2016-06-15 23:00:46
Change the update_ref helper function to use a ref transaction internally.
Signed-off-by: Ronnie Sahlberg <redacted>
---
refs.c | 28 +++++++++++++++++++++++-----
1 file changed, 23 insertions(+), 5 deletions(-)
From: Ronnie Sahlberg <hidden> Date: 2016-06-15 23:00:46
Switch to using ref transactions in walker_fetch(). As part of the refactoring
to use ref transactions we also fix a potential memory leak where in the
original code if write_ref_sha1() would fail we would end up returning from
the function without free()ing the msg string.
This changes the locking slightly for walker_fetch. Previously the code would
lock all refs before writing them but now we do not lock the refs until the
commit stage. There is thus a very short window where changes could be done
locally during the fetch which would be overwritten when the fetch completes
and commits its transaction. But this window should be reasonably short.
Even if this race does trigger, since both the old code and the new code
just overwrites the refs to the new values without checking or comparing
them with the previous value, this is not too dissimilar to a similar scenario
where you first do a ref change locally and then later do a fetch that
overwrites the local change. With this in mind I do not see the change in
locking semantics to be critical.
Note that this function is only called when fetching from a remote HTTP
repository onto the local (most of the time single-user) repository which
likely means that the type of collissions that the previous locking would
protect against and cause the fetch to fail for to be even more rare.
Signed-off-by: Ronnie Sahlberg <redacted>
---
walker.c | 45 ++++++++++++++++++++-------------------------
1 file changed, 20 insertions(+), 25 deletions(-)
@@ -276,14 +268,14 @@ int walker_fetch(struct walker *walker, int targets, char **target,for(i=0;i<targets;i++){if(interpret_target(walker,target[i],&sha1[20*i])){error("Could not interpret response from server '%s' as something to pull",target[i]);-gotounlock_and_fail;+gotorollback_and_fail;}if(process(walker,lookup_unknown_object(&sha1[20*i])))-gotounlock_and_fail;+gotorollback_and_fail;}if(loop(walker))-gotounlock_and_fail;+gotorollback_and_fail;if(write_ref_log_details){msg=xmalloc(strlen(write_ref_log_details)+12);
@@ -294,19 +286,22 @@ int walker_fetch(struct walker *walker, int targets, char **target,for(i=0;i<targets;i++){if(!write_ref||!write_ref[i])continue;-ret=write_ref_sha1(lock[i],&sha1[20*i],msg?msg:"fetch (unknown)");-lock[i]=NULL;-if(ret)-gotounlock_and_fail;+if(ref_transaction_update(transaction,write_ref[i],+&sha1[20*i],NULL,+0,0))+gotorollback_and_fail;}-free(msg);+if(ref_transaction_commit(transaction,msg?msg:"fetch (unknown)",+UPDATE_REFS_QUIET_ON_ERR))+gotorollback_and_fail;++free(msg);return0;-unlock_and_fail:-for(i=0;i<targets;i++)-if(lock[i])-unlock_ref(lock[i]);+rollback_and_fail:+free(msg);+ref_transaction_rollback(transaction);return-1;}