{"thread":{"id":"37248","subject":"Transaction patch series overview","startedAt":"2014-07-30T17:10:37Z","lastAt":"2014-10-07T19:26:14Z","messageCount":129,"participants":["Ronnie Sahlberg","Junio C Hamano","Jonathan Nieder","Michael Haggerty","Shawn Pearce"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"246976","messageId":"CAL=YDWmtitT7kHsZqXmojbv8eKYwKwVn7c+gC180FPQN1uxBvQ@mail.gmail.com","threadId":"37248","inReplyTo":null,"subject":"Transaction patch series overview","fromName":"Ronnie Sahlberg","fromEmail":"sahlberg@google.com","sentAt":"2014-07-30T17:10:37Z","receivedAt":"2014-07-30T17:10:37Z","isPatch":false,"sender":{"key":"sahlberg@google.com","avatar":"https://avatars.githubusercontent.com/u/7320636?v=4"},"body":"List, please see here an overview and ordering of the ref transaction\npatch series.\nThese series build on each other and needs to be applied in the order\nlisted below.\n\n\nrs/ref-transaction-0\n---------------------------\n    Early part of the \"ref transaction\" topic.\n\n    * rs/ref-transaction-0:\n      refs.c: change ref_transaction_update() to do error checking and\nreturn status\n      refs.c: remove the onerr argument to ref_transaction_commit\n      update-ref: use err argument to get error from ref_transaction_commit\n      refs.c: make update_ref_write update a strbuf on failure\n      refs.c: make ref_update_reject_duplicates take a strbuf argument\nfor errors\n      refs.c: log_ref_write should try to return meaningful errno\n      refs.c: make resolve_ref_unsafe set errno to something meaningful on error\n      refs.c: commit_packed_refs to return a meaningful errno on failure\n      refs.c: make remove_empty_directories always set errno to something sane\n      refs.c: verify_lock should set errno to something meaningful\n      refs.c: make sure log_ref_setup returns a meaningful errno\n      refs.c: add an err argument to repack_without_refs\n      lockfile.c: make lock_file return a meaningful errno on failurei\n      lockfile.c: add a new public function unable_to_lock_message\n      refs.c: add a strbuf argument to ref_transaction_commit for error logging\n      refs.c: allow passing NULL to ref_transaction_free\n      refs.c: constify the sha arguments for\nref_transaction_create|delete|update\n      refs.c: ref_transaction_commit should not free the transaction\n      refs.c: remove ref_transaction_rollback\n\nHas been merged into next.\n\n\n\nref-transaction-1 (2014-07-16) 20 commits\n-------------------------------------------------------------\nSecond batch of ref transactions\n\n - refs.c: make delete_ref use a transaction\n - refs.c: make prune_ref use a transaction to delete the ref\n - refs.c: remove lock_ref_sha1\n - refs.c: remove the update_ref_write function\n - refs.c: remove the update_ref_lock function\n - refs.c: make lock_ref_sha1 static\n - walker.c: use ref transaction for ref updates\n - fast-import.c: use a ref transaction when dumping tags\n - receive-pack.c: use a reference transaction for updating the refs\n - refs.c: change update_ref to use a transaction\n - branch.c: use ref transaction for all ref updates\n - fast-import.c: change update_branch to use ref transactions\n - sequencer.c: use ref transactions for all ref updates\n - commit.c: use ref transactions for updates\n - replace.c: use the ref transaction functions for updates\n - tag.c: use ref transactions when doing updates\n - refs.c: add transaction.status and track OPEN/CLOSED/ERROR\n - refs.c: make ref_transaction_begin take an err argument\n - refs.c: update ref_transaction_delete to check for error and return status\n - refs.c: change ref_transaction_create to do error checking and return status\n (this branch is used by rs/ref-transaction, rs/ref-transaction-multi,\nrs/ref-transaction-reflog and rs/ref-transaction-rename.)\n\n The second batch of the transactional ref update series.\n\nHas been merged into pu\n\n\n\nrs/ref-transaction (2014-07-17) 12 commits\n-----------------------------------------------------------------\n - refs.c: fix handling of badly named refs\n - refs.c: make write_ref_sha1 static\n - fetch.c: change s_update_ref to use a ref transaction\n - refs.c: propagate any errno==ENOTDIR from _commit back to the callers\n - refs.c: pass a skip list to name_conflict_fn\n - refs.c: call lock_ref_sha1_basic directly from commit\n - refs.c: move the check for valid refname to lock_ref_sha1_basic\n - refs.c: pass NULL as *flags to read_ref_full\n - refs.c: pass the ref log message to _create/delete/update instead of _commit\n - refs.c: add an err argument to delete_ref_loose\n - wrapper.c: add a new function unlink_or_msg\n - wrapper.c: simplify warn_if_unremovable\n (this branch is used by rs/ref-transaction-multi,\nrs/ref-transaction-reflog and rs/ref-transaction-rename; uses\nrs/ref-transaction-1.)\n\nThe third and final part of the basic ref-transaction work.\n\nHas been merged into pu.\n\n\n\n\nrs/ref-transaction-reflog (2014-07-23) 15 commits\n-----------------------------------------------------------------------\n - refs.c: allow deleting refs with a broken sha1\n - refs.c: remove lock_any_ref_for_update\n - refs.c: make unlock_ref/close_ref/commit_ref static\n - refs.c: rename log_ref_setup to create_reflog\n - reflog.c: use a reflog transaction when writing during expire\n - refs.c: allow multiple reflog updates during a single transaction\n - refs.c: only write reflog update if msg is non-NULL\n - refs.c: add a flag to allow reflog updates to truncate the log\n - refs.c: add a transaction function to append a reflog entry\n - lockfile.c: make hold_lock_file_for_append preserve meaningful errno\n - refs.c: add a function to append a reflog entry to a fd\n - refs.c: add a new update_type field to ref_update\n - refs.c: rename the transaction functions\n - refs.c: make ref_transaction_delete a wrapper for ref_transaction_update\n - refs.c: make ref_transaction_create a wrapper to ref_transaction_update\n (this branch is used by rs/ref-transaction-multi and\nrs/ref-transaction-rename; uses rs/ref-transaction and\nrs/ref-transaction-1.)\n\nThis patch series adds support for reflog updates to the transaction subsystem.\nOnce it has refactored the builtin/reflog.c to use a transaction\ninstead of accessing the refs and\nreflogs directly it allows us to remove\nunlock_ref/close_ref/commit_ref/lock_any_ref_for_update\nfrom the public API.\n\nAs part of the reflog work I also refactor how reflog creation works\nrenaming log_ref_setup to create_reflog. These changes allow us then\nto reduce the number of places where\nwe expose the global log_all_ref_updates to the callers.\nThis leads to a much cleaner api for reflog creation and avoids completely the\n\"manipulate the global log_all_ref_updates before calling\nlog_ref_setup\" thing that was done\nin checkout.c. Now checkout.c can just call create_reflog() and the\nright thing will happen.\n\nAt the end of the patch series is an unrelated patch for repairing the\n\"allow deletion of a broken ref\" functionality that was lost a while\nback. This patch is independent of the reflog work but is placed here\njust to avoid creating additional conflicts with this series and\nseries that follow.\n\nHas been merged to pu.\n\n\n\n\n\nFollowing are the patch series that has not yet made its way into pu.\n\n\n\n\nref-transactions-rename\n-----------------------------------\nrefs.c: allow passing raw git_committer_info as email to _update_reflog\nrefs.c: return error instead of dying when locking fails during transaction\nrefs.c: use packed refs when deleting refs during a transaction\nrefs.c: update rename_ref to use a transaction\nrefs.c: rollback the lockfile before we die() in repack_without_refs\n\nThis series focuses on reworking how rename_ref works and to make it\nuse an atomic transaction for the rename.\nThe first three patches lead up to a point where we can now perform\nthe \"delete the old ref and add the new ref\" as a single atomic\noperation to the packed refs file followed by the fourth patch where\nwe rework rename_ref() completely to perform the rename and the reflog\nupdates as a single atomic transaction.\n\nAs part of this the rename_ref logic is greatly simplified and we\nreach several nice goals:\n* no need to disallow rename_ref() if the reflog is a symlink\n* no need to rename the reflog via a third filename\n* make the rename become completely atomic both for the caller but\nalso for any external observers.\n\nThe fifth patch is independent to the rename work itself but was\ndiscovered during the work for this patch. It could be applied\nseparately but I did not see any urgency to push it separetely and\nleft it as a patch in this series.\n\n\n\n\n\nref-transactions-req-packed-refs\n----------------------------------------------\nrefs.c: move reflog updates into its own function\nrefs.c: write updates to packed refs when a transaction has more than one ref\nremote.c: use a transaction for deleting refs\nrefs.c: make repack_without_refs static\nrefs.c: make the *_packed_refs functions static\n\nThese patches expands on the \"perform ref operations as a packed refs\nfile commit\" and changes the transaction subsystem to always perform\nmulti-ref updates as a packed ref commit instead of discreete\noperations on loose refs.\n\nThe new logic is basically:\nIF the transaction only touches a single ref, then just do the normal\nloose ref operation as before.\n   This is to make sure that the very common \"git commit\" command will\nstill always be fast.\n\nIF the transaction touches multiple refs, then\n * first copy/move all affected refs to the packed refs file and\ncommit, then re-lock the packed\n   refs file until the transaction has completed.\n * then delete any affected loose refs files (they are already in the\npacked refs file)\n * perform the operation by adding/removing/updating packed refs entries\n * finally, commit the packed refs file\n\nThis leads to several new nice properties.\nFirst, the transaction subsystem will now do the \"right thing\" and\nautomatically convert any\nmulti-ref updates to use the packed refs file. This then means that we\nno longer need to have\nspecial casing for clone.c to use a special packed_refs api. Clone.c\ncan now just use a transaction for its \"add all refs\" and the right\nthing will happen.\nSimilar optimizations can be done for remote.c which now also can just\nuse a transaction\nand know that the right thing will happen.\nAs we now no longer need to expose the special purpose packed-refs api\nto external callers we can now make all the packed refs functions\nprivate to refs.c and shrink the size of the public api.\n\nNote: we still do have packed refs and they still work the same way as\nbefore. It is just that the packed refs are now an internal\noptimization for the refs code and no longer part of the public api.\n\nSecond, all transaction that affects multiple refs will now also\nbecome atomic for any external observer regardless of whether they\nused the packed refs api or not.\n\n\nThe following functions are now static to refs.c and no longer part of\nthe public api:\n add_packed_ref/lock_packed_refs/commit_packed_refs/rollback_packed_refs/repack_without_refs\n\n\n\n\nref-transactions-req-strbuf-err\n-------------------------------------------\nThis series just contains various changes internally to refs.c to\nexpand on the use of a strbuf *err as argument for passing error\nmessages back to the caller.\nOnly thing by note here is tgat by doing this to update_ref() we can\nfinally get rid of the action_on_err enum:\n\n-enum action_on_err {\n-       UPDATE_REFS_MSG_ON_ERR,\n-       UPDATE_REFS_DIE_ON_ERR,\n-       UPDATE_REFS_QUIET_ON_ERR\n-};\n\nwooohooo\n\nAs part of this patch series I noticed that there were two places in\ntransport*.c where we were calling update_ref() and passing '0' as\nargument instead of UPDATE_REFS_MSG_ON_ERR.\nNot a bug and not a change in functionality but it would be nice if\nthe compiler could warn for this.\n\n\n\nref-transactions-send-pack\n---------------------------------------\nVarious changes to allow a new argument  --atomic-push, and protocol\nchanges, to allow a client to request/negotiate that a multi-ref push\nshould be atomic. I.e. apply all changes or none.\n\nNot sent to the list yet.\n\n\nref-transactions-send-pack-post\n----------------------------------------------\nVarious other changes to add a strbuf *err argument to error handling.\n\nNot sent to the list yet.\n\n\n\nAnd that concludes the basic ref-transaction stuff.\n\n\nOnce this is in I have a very large patch series that is mainly\nrearranging code and to add support for pluggable ref backends.\nBut that is for when the ref transaction patches are in.\n\n\n\nregards\nronnie sahlberg\n"},{"id":"247089","messageId":"CAL=YDWnd=GNycrPO-5yq+a_g569fZDOmzpat+AWrXd+5+bXDQA@mail.gmail.com","threadId":"37248","inReplyTo":"CAL=YDWmtitT7kHsZqXmojbv8eKYwKwVn7c+gC180FPQN1uxBvQ@mail.gmail.com","subject":"Re: Transaction patch series overview","fromName":"Ronnie Sahlberg","fromEmail":"sahlberg@google.com","sentAt":"2014-07-31T21:41:24Z","receivedAt":"2014-07-31T21:41:24Z","isPatch":false,"sender":{"key":"sahlberg@google.com","avatar":"https://avatars.githubusercontent.com/u/7320636?v=4"},"body":"List, please see here an overview and ordering of the ref transaction\npatch series.\nThese series build on each other and needs to be applied in the order\nlisted below.\n\nThis is an update.\n\n\n\nrs/ref-transaction-0\n---------------------------\n    Early part of the \"ref transaction\" topic.\n\n    * rs/ref-transaction-0:\n      refs.c: change ref_transaction_update() to do error checking and\nreturn status\n      refs.c: remove the onerr argument to ref_transaction_commit\n      update-ref: use err argument to get error from ref_transaction_commit\n      refs.c: make update_ref_write update a strbuf on failure\n      refs.c: make ref_update_reject_duplicates take a strbuf argument\nfor errors\n      refs.c: log_ref_write should try to return meaningful errno\n      refs.c: make resolve_ref_unsafe set errno to something meaningful on error\n      refs.c: commit_packed_refs to return a meaningful errno on failure\n      refs.c: make remove_empty_directories always set errno to something sane\n      refs.c: verify_lock should set errno to something meaningful\n      refs.c: make sure log_ref_setup returns a meaningful errno\n      refs.c: add an err argument to repack_without_refs\n      lockfile.c: make lock_file return a meaningful errno on failurei\n      lockfile.c: add a new public function unable_to_lock_message\n      refs.c: add a strbuf argument to ref_transaction_commit for error logging\n      refs.c: allow passing NULL to ref_transaction_free\n      refs.c: constify the sha arguments for\nref_transaction_create|delete|update\n      refs.c: ref_transaction_commit should not free the transaction\n      refs.c: remove ref_transaction_rollback\n\nHas been merged into next.\n\n\n\nref-transaction-1 (2014-07-16) 20 commits\n-------------------------------------------------------------\nSecond batch of ref transactions\n\n - refs.c: make delete_ref use a transaction\n - refs.c: make prune_ref use a transaction to delete the ref\n - refs.c: remove lock_ref_sha1\n - refs.c: remove the update_ref_write function\n - refs.c: remove the update_ref_lock function\n - refs.c: make lock_ref_sha1 static\n - walker.c: use ref transaction for ref updates\n - fast-import.c: use a ref transaction when dumping tags\n - receive-pack.c: use a reference transaction for updating the refs\n - refs.c: change update_ref to use a transaction\n - branch.c: use ref transaction for all ref updates\n - fast-import.c: change update_branch to use ref transactions\n - sequencer.c: use ref transactions for all ref updates\n - commit.c: use ref transactions for updates\n - replace.c: use the ref transaction functions for updates\n - tag.c: use ref transactions when doing updates\n - refs.c: add transaction.status and track OPEN/CLOSED/ERROR\n - refs.c: make ref_transaction_begin take an err argument\n - refs.c: update ref_transaction_delete to check for error and return status\n - refs.c: change ref_transaction_create to do error checking and return status\n (this branch is used by rs/ref-transaction, rs/ref-transaction-multi,\nrs/ref-transaction-reflog and rs/ref-transaction-rename.)\n\n The second batch of the transactional ref update series.\n\nHas been merged into pu\n\n\n\nrs/ref-transaction (2014-07-17) 12 commits\n-----------------------------------------------------------------\n - refs.c: fix handling of badly named refs\n - refs.c: make write_ref_sha1 static\n - fetch.c: change s_update_ref to use a ref transaction\n - refs.c: propagate any errno==ENOTDIR from _commit back to the callers\n - refs.c: pass a skip list to name_conflict_fn\n - refs.c: call lock_ref_sha1_basic directly from commit\n - refs.c: move the check for valid refname to lock_ref_sha1_basic\n - refs.c: pass NULL as *flags to read_ref_full\n - refs.c: pass the ref log message to _create/delete/update instead of _commit\n - refs.c: add an err argument to delete_ref_loose\n - wrapper.c: add a new function unlink_or_msg\n - wrapper.c: simplify warn_if_unremovable\n (this branch is used by rs/ref-transaction-multi,\nrs/ref-transaction-reflog and rs/ref-transaction-rename; uses\nrs/ref-transaction-1.)\n\nThe third and final part of the basic ref-transaction work.\n\nHas been merged into pu.\n\n\n\n\nrs/ref-transaction-reflog (2014-07-23) 15 commits\n-----------------------------------------------------------------------\n - refs.c: allow deleting refs with a broken sha1\n - refs.c: remove lock_any_ref_for_update\n - refs.c: make unlock_ref/close_ref/commit_ref static\n - refs.c: rename log_ref_setup to create_reflog\n - reflog.c: use a reflog transaction when writing during expire\n - refs.c: allow multiple reflog updates during a single transaction\n - refs.c: only write reflog update if msg is non-NULL\n - refs.c: add a flag to allow reflog updates to truncate the log\n - refs.c: add a transaction function to append a reflog entry\n - lockfile.c: make hold_lock_file_for_append preserve meaningful errno\n - refs.c: add a function to append a reflog entry to a fd\n - refs.c: add a new update_type field to ref_update\n - refs.c: rename the transaction functions\n - refs.c: make ref_transaction_delete a wrapper for ref_transaction_update\n - refs.c: make ref_transaction_create a wrapper to ref_transaction_update\n (this branch is used by rs/ref-transaction-multi and\nrs/ref-transaction-rename; uses rs/ref-transaction and\nrs/ref-transaction-1.)\n\nThis patch series adds support for reflog updates to the transaction subsystem.\nOnce it has refactored the builtin/reflog.c to use a transaction\ninstead of accessing the refs and\nreflogs directly it allows us to remove\nunlock_ref/close_ref/commit_ref/lock_any_ref_for_update\nfrom the public API.\n\nAs part of the reflog work I also refactor how reflog creation works\nrenaming log_ref_setup to create_reflog. These changes allow us then\nto reduce the number of places where\nwe expose the global log_all_ref_updates to the callers.\nThis leads to a much cleaner api for reflog creation and avoids completely the\n\"manipulate the global log_all_ref_updates before calling\nlog_ref_setup\" thing that was done\nin checkout.c. Now checkout.c can just call create_reflog() and the\nright thing will happen.\n\nAt the end of the patch series is an unrelated patch for repairing the\n\"allow deletion of a broken ref\" functionality that was lost a while\nback. This patch is independent of the reflog work but is placed here\njust to avoid creating additional conflicts with this series and\nseries that follow.\n\nHas been merged to pu.\n\n\nref-transactions-rename\n-----------------------------------\nrefs.c: allow passing raw git_committer_info as email to _update_reflog\nrefs.c: return error instead of dying when locking fails during transaction\nrefs.c: use packed refs when deleting refs during a transaction\nrefs.c: update rename_ref to use a transaction\nrefs.c: rollback the lockfile before we die() in repack_without_refs\n\nThis series focuses on reworking how rename_ref works and to make it\nuse an atomic transaction for the rename.\nThe first three patches lead up to a point where we can now perform\nthe \"delete the old ref and add the new ref\" as a single atomic\noperation to the packed refs file followed by the fourth patch where\nwe rework rename_ref() completely to perform the rename and the reflog\nupdates as a single atomic transaction.\n\nAs part of this the rename_ref logic is greatly simplified and we\nreach several nice goals:\n* no need to disallow rename_ref() if the reflog is a symlink\n* no need to rename the reflog via a third filename\n* make the rename become completely atomic both for the caller but\nalso for any external observers.\n\nThe fifth patch is independent to the rename work itself but was\ndiscovered during the work for this patch. It could be applied\nseparately but I did not see any urgency to push it separetely and\nleft it as a patch in this series.\n\nHas been merged into pu.\n\n\n\nref-transactions-req-packed-refs\n----------------------------------------------\nrefs.c: move reflog updates into its own function\nrefs.c: write updates to packed refs when a transaction has more than one ref\nremote.c: use a transaction for deleting refs\nrefs.c: make repack_without_refs static\nrefs.c: make the *_packed_refs functions static\n\nThese patches expands on the \"perform ref operations as a packed refs\nfile commit\" and changes the transaction subsystem to always perform\nmulti-ref updates as a packed ref commit instead of discreete\noperations on loose refs.\n\nThe new logic is basically:\nIF the transaction only touches a single ref, then just do the normal\nloose ref operation as before.\n   This is to make sure that the very common \"git commit\" command will\nstill always be fast.\n\nIF the transaction touches multiple refs, then\n * first copy/move all affected refs to the packed refs file and\ncommit, then re-lock the packed\n   refs file until the transaction has completed.\n * then delete any affected loose refs files (they are already in the\npacked refs file)\n * perform the operation by adding/removing/updating packed refs entries\n * finally, commit the packed refs file\n\nThis leads to several new nice properties.\nFirst, the transaction subsystem will now do the \"right thing\" and\nautomatically convert any\nmulti-ref updates to use the packed refs file. This then means that we\nno longer need to have\nspecial casing for clone.c to use a special packed_refs api. Clone.c\ncan now just use a transaction for its \"add all refs\" and the right\nthing will happen.\nSimilar optimizations can be done for remote.c which now also can just\nuse a transaction\nand know that the right thing will happen.\nAs we now no longer need to expose the special purpose packed-refs api\nto external callers we can now make all the packed refs functions\nprivate to refs.c and shrink the size of the public api.\n\nNote: we still do have packed refs and they still work the same way as\nbefore. It is just that the packed refs are now an internal\noptimization for the refs code and no longer part of the public api.\n\nSecond, all transaction that affects multiple refs will now also\nbecome atomic for any external observer regardless of whether they\nused the packed refs api or not.\n\n\nThe following functions are now static to refs.c and no longer part of\nthe public api:\n add_packed_ref/lock_packed_refs/commit_packed_refs/rollback_packed_refs/repack_without_refs\n\nHas been merged into pu.\n\n\n\nFollowing are the patch series that has not yet made its way into pu.\n\n\nref-transactions-req-strbuf-err\n-------------------------------------------\nThis series just contains various changes internally to refs.c to\nexpand on the use of a strbuf *err as argument for passing error\nmessages back to the caller.\nOnly thing by note here is tgat by doing this to update_ref() we can\nfinally get rid of the action_on_err enum:\n\n-enum action_on_err {\n-       UPDATE_REFS_MSG_ON_ERR,\n-       UPDATE_REFS_DIE_ON_ERR,\n-       UPDATE_REFS_QUIET_ON_ERR\n-};\n\nwooohooo\n\nAs part of this patch series I noticed that there were two places in\ntransport*.c where we were calling update_ref() and passing '0' as\nargument instead of UPDATE_REFS_MSG_ON_ERR.\nNot a bug and not a change in functionality but it would be nice if\nthe compiler could warn for this.\n\n\n\nref-transactions-send-pack\n---------------------------------------\nVarious changes to allow a new argument  --atomic-push, and protocol\nchanges, to allow a client to request/negotiate that a multi-ref push\nshould be atomic. I.e. apply all changes or none.\n\n\n\nref-transactions-send-pack-post\n----------------------------------------------\nVarious other changes to add a strbuf *err argument to error handling.\n\nNot sent to the list yet.\n\n\n\nAnd that concludes the basic ref-transaction stuff.\n\n\nOnce this is in I have a very large patch series that is mainly\nrearranging code and to add support for pluggable ref backends.\nBut that is for when the ref transaction patches are in.\n\n\n\nregards\n"},{"id":"247508","messageId":"CAL=YDWka47hV2TMcwcY1hm+RhbiD6HD=_ED4zB84zX5e5ABf4Q@mail.gmail.com","threadId":"37248","inReplyTo":"CAL=YDWnd=GNycrPO-5yq+a_g569fZDOmzpat+AWrXd+5+bXDQA@mail.gmail.com","subject":"Re: Transaction patch series overview","fromName":"Ronnie Sahlberg","fromEmail":"sahlberg@google.com","sentAt":"2014-08-08T16:50:47Z","receivedAt":"2014-08-08T16:50:47Z","isPatch":false,"sender":{"key":"sahlberg@google.com","avatar":"https://avatars.githubusercontent.com/u/7320636?v=4"},"body":"List, please see here an overview and ordering of the ref transaction\npatch series.\nThese series build on each other and needs to be applied in the order\nlisted below.\n\nThis is an update.\n\n\n\n\nrs/ref-transaction-0\n---------------------------\n    Early part of the \"ref transaction\" topic.\n\n    * rs/ref-transaction-0:\n      refs.c: change ref_transaction_update() to do error checking and\nreturn status\n      refs.c: remove the onerr argument to ref_transaction_commit\n      update-ref: use err argument to get error from ref_transaction_commit\n      refs.c: make update_ref_write update a strbuf on failure\n      refs.c: make ref_update_reject_duplicates take a strbuf argument\nfor errors\n      refs.c: log_ref_write should try to return meaningful errno\n      refs.c: make resolve_ref_unsafe set errno to something meaningful on error\n      refs.c: commit_packed_refs to return a meaningful errno on failure\n      refs.c: make remove_empty_directories always set errno to something sane\n      refs.c: verify_lock should set errno to something meaningful\n      refs.c: make sure log_ref_setup returns a meaningful errno\n      refs.c: add an err argument to repack_without_refs\n      lockfile.c: make lock_file return a meaningful errno on failurei\n      lockfile.c: add a new public function unable_to_lock_message\n      refs.c: add a strbuf argument to ref_transaction_commit for error logging\n      refs.c: allow passing NULL to ref_transaction_free\n      refs.c: constify the sha arguments for\nref_transaction_create|delete|update\n      refs.c: ref_transaction_commit should not free the transaction\n      refs.c: remove ref_transaction_rollback\n\nHas been merged into next.\n\n\n\nref-transaction-1 (2014-07-16) 20 commits\n-------------------------------------------------------------\nSecond batch of ref transactions\n\n - refs.c: make delete_ref use a transaction\n - refs.c: make prune_ref use a transaction to delete the ref\n - refs.c: remove lock_ref_sha1\n - refs.c: remove the update_ref_write function\n - refs.c: remove the update_ref_lock function\n - refs.c: make lock_ref_sha1 static\n - walker.c: use ref transaction for ref updates\n - fast-import.c: use a ref transaction when dumping tags\n - receive-pack.c: use a reference transaction for updating the refs\n - refs.c: change update_ref to use a transaction\n - branch.c: use ref transaction for all ref updates\n - fast-import.c: change update_branch to use ref transactions\n - sequencer.c: use ref transactions for all ref updates\n - commit.c: use ref transactions for updates\n - replace.c: use the ref transaction functions for updates\n - tag.c: use ref transactions when doing updates\n - refs.c: add transaction.status and track OPEN/CLOSED/ERROR\n - refs.c: make ref_transaction_begin take an err argument\n - refs.c: update ref_transaction_delete to check for error and return status\n - refs.c: change ref_transaction_create to do error checking and return status\n (this branch is used by rs/ref-transaction, rs/ref-transaction-multi,\nrs/ref-transaction-reflog and rs/ref-transaction-rename.)\n\n The second batch of the transactional ref update series.\n\nHas been merged into pu\n\n\n\nrs/ref-transaction (2014-07-17) 12 commits\n-----------------------------------------------------------------\n - refs.c: fix handling of badly named refs\n - refs.c: make write_ref_sha1 static\n - fetch.c: change s_update_ref to use a ref transaction\n - refs.c: propagate any errno==ENOTDIR from _commit back to the callers\n - refs.c: pass a skip list to name_conflict_fn\n - refs.c: call lock_ref_sha1_basic directly from commit\n - refs.c: move the check for valid refname to lock_ref_sha1_basic\n - refs.c: pass NULL as *flags to read_ref_full\n - refs.c: pass the ref log message to _create/delete/update instead of _commit\n - refs.c: add an err argument to delete_ref_loose\n - wrapper.c: add a new function unlink_or_msg\n - wrapper.c: simplify warn_if_unremovable\n (this branch is used by rs/ref-transaction-multi,\nrs/ref-transaction-reflog and rs/ref-transaction-rename; uses\nrs/ref-transaction-1.)\n\nThe third and final part of the basic ref-transaction work.\n\nHas been merged into pu.\n\n\n\n\nrs/ref-transaction-reflog (2014-07-23) 15 commits\n-----------------------------------------------------------------------\n - refs.c: allow deleting refs with a broken sha1\n - refs.c: remove lock_any_ref_for_update\n - refs.c: make unlock_ref/close_ref/commit_ref static\n - refs.c: rename log_ref_setup to create_reflog\n - reflog.c: use a reflog transaction when writing during expire\n - refs.c: allow multiple reflog updates during a single transaction\n - refs.c: only write reflog update if msg is non-NULL\n - refs.c: add a flag to allow reflog updates to truncate the log\n - refs.c: add a transaction function to append a reflog entry\n - lockfile.c: make hold_lock_file_for_append preserve meaningful errno\n - refs.c: add a function to append a reflog entry to a fd\n - refs.c: add a new update_type field to ref_update\n - refs.c: rename the transaction functions\n - refs.c: make ref_transaction_delete a wrapper for ref_transaction_update\n - refs.c: make ref_transaction_create a wrapper to ref_transaction_update\n (this branch is used by rs/ref-transaction-multi and\nrs/ref-transaction-rename; uses rs/ref-transaction and\nrs/ref-transaction-1.)\n\nThis patch series adds support for reflog updates to the transaction subsystem.\nOnce it has refactored the builtin/reflog.c to use a transaction\ninstead of accessing the refs and\nreflogs directly it allows us to remove\nunlock_ref/close_ref/commit_ref/lock_any_ref_for_update\nfrom the public API.\n\nAs part of the reflog work I also refactor how reflog creation works\nrenaming log_ref_setup to create_reflog. These changes allow us then\nto reduce the number of places where\nwe expose the global log_all_ref_updates to the callers.\nThis leads to a much cleaner api for reflog creation and avoids completely the\n\"manipulate the global log_all_ref_updates before calling\nlog_ref_setup\" thing that was done\nin checkout.c. Now checkout.c can just call create_reflog() and the\nright thing will happen.\n\nAt the end of the patch series is an unrelated patch for repairing the\n\"allow deletion of a broken ref\" functionality that was lost a while\nback. This patch is independent of the reflog work but is placed here\njust to avoid creating additional conflicts with this series and\nseries that follow.\n\nHas been merged to pu.\n\n\nref-transactions-rename\n-----------------------------------\nrefs.c: allow passing raw git_committer_info as email to _update_reflog\nrefs.c: return error instead of dying when locking fails during transaction\nrefs.c: use packed refs when deleting refs during a transaction\nrefs.c: update rename_ref to use a transaction\nrefs.c: rollback the lockfile before we die() in repack_without_refs\n\nThis series focuses on reworking how rename_ref works and to make it\nuse an atomic transaction for the rename.\nThe first three patches lead up to a point where we can now perform\nthe \"delete the old ref and add the new ref\" as a single atomic\noperation to the packed refs file followed by the fourth patch where\nwe rework rename_ref() completely to perform the rename and the reflog\nupdates as a single atomic transaction.\n\nAs part of this the rename_ref logic is greatly simplified and we\nreach several nice goals:\n* no need to disallow rename_ref() if the reflog is a symlink\n* no need to rename the reflog via a third filename\n* make the rename become completely atomic both for the caller but\nalso for any external observers.\n\nThe fifth patch is independent to the rename work itself but was\ndiscovered during the work for this patch. It could be applied\nseparately but I did not see any urgency to push it separetely and\nleft it as a patch in this series.\n\nHas been merged into pu.\n\n\n\nref-transactions-req-packed-refs\n----------------------------------------------\nrefs.c: move reflog updates into its own function\nrefs.c: write updates to packed refs when a transaction has more than one ref\nremote.c: use a transaction for deleting refs\nrefs.c: make repack_without_refs static\nrefs.c: make the *_packed_refs functions static\n\nThese patches expands on the \"perform ref operations as a packed refs\nfile commit\" and changes the transaction subsystem to always perform\nmulti-ref updates as a packed ref commit instead of discreete\noperations on loose refs.\n\nThe new logic is basically:\nIF the transaction only touches a single ref, then just do the normal\nloose ref operation as before.\n   This is to make sure that the very common \"git commit\" command will\nstill always be fast.\n\nIF the transaction touches multiple refs, then\n * first copy/move all affected refs to the packed refs file and\ncommit, then re-lock the packed\n   refs file until the transaction has completed.\n * then delete any affected loose refs files (they are already in the\npacked refs file)\n * perform the operation by adding/removing/updating packed refs entries\n * finally, commit the packed refs file\n\nThis leads to several new nice properties.\nFirst, the transaction subsystem will now do the \"right thing\" and\nautomatically convert any\nmulti-ref updates to use the packed refs file. This then means that we\nno longer need to have\nspecial casing for clone.c to use a special packed_refs api. Clone.c\ncan now just use a transaction for its \"add all refs\" and the right\nthing will happen.\nSimilar optimizations can be done for remote.c which now also can just\nuse a transaction\nand know that the right thing will happen.\nAs we now no longer need to expose the special purpose packed-refs api\nto external callers we can now make all the packed refs functions\nprivate to refs.c and shrink the size of the public api.\n\nNote: we still do have packed refs and they still work the same way as\nbefore. It is just that the packed refs are now an internal\noptimization for the refs code and no longer part of the public api.\n\nSecond, all transaction that affects multiple refs will now also\nbecome atomic for any external observer regardless of whether they\nused the packed refs api or not.\n\n\nThe following functions are now static to refs.c and no longer part of\nthe public api:\n add_packed_ref/lock_packed_refs/commit_packed_refs/rollback_packed_refs/repack_without_refs\n\nHas been merged into pu.\n\n\n\nFollowing are the patch series that has not yet made its way into pu.\n\n\nref-transactions-req-strbuf-err\n-------------------------------------------\nThis series just contains various changes internally to refs.c to\nexpand on the use of a strbuf *err as argument for passing error\nmessages back to the caller.\nOnly thing by note here is tgat by doing this to update_ref() we can\nfinally get rid of the action_on_err enum:\n\n-enum action_on_err {\n-       UPDATE_REFS_MSG_ON_ERR,\n-       UPDATE_REFS_DIE_ON_ERR,\n-       UPDATE_REFS_QUIET_ON_ERR\n-};\n\nwooohooo\n\nAs part of this patch series I noticed that there were two places in\ntransport*.c where we were calling update_ref() and passing '0' as\nargument instead of UPDATE_REFS_MSG_ON_ERR.\nNot a bug and not a change in functionality but it would be nice if\nthe compiler could warn for this.\n\nSent to the list.\n\n\nref-transactions-send-pack\n---------------------------------------\nVarious changes to allow a new argument  --atomic-push, and protocol\nchanges, to allow a client to request/negotiate that a multi-ref push\nshould be atomic. I.e. apply all changes or none.\n\nSent to the list.\n\n\nbackend-struct-db\n--------------------------\nVarious changes that mainly moves functions between files to split the\ncurrent refs.c file into functions that are backend agnostic and the\nfunctions that implement the files based ref backend.\nIt finally adds a backend structure and the methods that define a\nbackend datastore for refs.\n\nThis series concludes the work to define pluggable backends for refs.\nAt this stage it will now be possible to start building and adding new\ntypes of ref backends to git.\n\nSent to the list\n\n\n\n\nregards\nronnie sahlberg\n"},{"id":"247963","messageId":"CAL=YDWm9VaKUBRAmmybHzOBhAg_VvNc0KMG0W_uTA02YYzQrzA@mail.gmail.com","threadId":"37248","inReplyTo":"CAL=YDWka47hV2TMcwcY1hm+RhbiD6HD=_ED4zB84zX5e5ABf4Q@mail.gmail.com","subject":"Re: Transaction patch series overview","fromName":"Ronnie Sahlberg","fromEmail":"sahlberg@google.com","sentAt":"2014-08-19T19:54:20Z","receivedAt":"2014-08-19T19:54:20Z","isPatch":false,"sender":{"key":"sahlberg@google.com","avatar":"https://avatars.githubusercontent.com/u/7320636?v=4"},"body":"List, please see here an overview and ordering of the ref transaction\npatch series.\nThese series build on each other and needs to be applied in the order\nlisted below.\n\nThis is an update.\n\n\n\n\nrs/ref-transaction-0\n---------------------------\n    Early part of the \"ref transaction\" topic.\n\n    * rs/ref-transaction-0:\n      refs.c: change ref_transaction_update() to do error checking and\nreturn status\n      refs.c: remove the onerr argument to ref_transaction_commit\n      update-ref: use err argument to get error from ref_transaction_commit\n      refs.c: make update_ref_write update a strbuf on failure\n      refs.c: make ref_update_reject_duplicates take a strbuf argument\nfor errors\n      refs.c: log_ref_write should try to return meaningful errno\n      refs.c: make resolve_ref_unsafe set errno to something meaningful on error\n      refs.c: commit_packed_refs to return a meaningful errno on failure\n      refs.c: make remove_empty_directories always set errno to something sane\n      refs.c: verify_lock should set errno to something meaningful\n      refs.c: make sure log_ref_setup returns a meaningful errno\n      refs.c: add an err argument to repack_without_refs\n      lockfile.c: make lock_file return a meaningful errno on failurei\n      lockfile.c: add a new public function unable_to_lock_message\n      refs.c: add a strbuf argument to ref_transaction_commit for error logging\n      refs.c: allow passing NULL to ref_transaction_free\n      refs.c: constify the sha arguments for\nref_transaction_create|delete|update\n      refs.c: ref_transaction_commit should not free the transaction\n      refs.c: remove ref_transaction_rollback\n\nHas been merged into next.\n\n\n\nref-transaction-1 (2014-07-16) 20 commits\n-------------------------------------------------------------\nSecond batch of ref transactions\n\n - refs.c: make delete_ref use a transaction\n - refs.c: make prune_ref use a transaction to delete the ref\n - refs.c: remove lock_ref_sha1\n - refs.c: remove the update_ref_write function\n - refs.c: remove the update_ref_lock function\n - refs.c: make lock_ref_sha1 static\n - walker.c: use ref transaction for ref updates\n - fast-import.c: use a ref transaction when dumping tags\n - receive-pack.c: use a reference transaction for updating the refs\n - refs.c: change update_ref to use a transaction\n - branch.c: use ref transaction for all ref updates\n - fast-import.c: change update_branch to use ref transactions\n - sequencer.c: use ref transactions for all ref updates\n - commit.c: use ref transactions for updates\n - replace.c: use the ref transaction functions for updates\n - tag.c: use ref transactions when doing updates\n - refs.c: add transaction.status and track OPEN/CLOSED/ERROR\n - refs.c: make ref_transaction_begin take an err argument\n - refs.c: update ref_transaction_delete to check for error and return status\n - refs.c: change ref_transaction_create to do error checking and return status\n (this branch is used by rs/ref-transaction, rs/ref-transaction-multi,\nrs/ref-transaction-reflog and rs/ref-transaction-rename.)\n\n The second batch of the transactional ref update series.\n\nHas been merged into pu\n\n\n\nrs/ref-transaction (2014-07-17) 12 commits\n-----------------------------------------------------------------\n - refs.c: fix handling of badly named refs\n - refs.c: make write_ref_sha1 static\n - fetch.c: change s_update_ref to use a ref transaction\n - refs.c: propagate any errno==ENOTDIR from _commit back to the callers\n - refs.c: pass a skip list to name_conflict_fn\n - refs.c: call lock_ref_sha1_basic directly from commit\n - refs.c: move the check for valid refname to lock_ref_sha1_basic\n - refs.c: pass NULL as *flags to read_ref_full\n - refs.c: pass the ref log message to _create/delete/update instead of _commit\n - refs.c: add an err argument to delete_ref_loose\n - wrapper.c: add a new function unlink_or_msg\n - wrapper.c: simplify warn_if_unremovable\n (this branch is used by rs/ref-transaction-multi,\nrs/ref-transaction-reflog and rs/ref-transaction-rename; uses\nrs/ref-transaction-1.)\n\nThe third and final part of the basic ref-transaction work.\n\nHas been merged into pu.\n\n\n\n\nrs/ref-transaction-reflog (2014-07-23) 15 commits\n-----------------------------------------------------------------------\n - refs.c: allow deleting refs with a broken sha1\n - refs.c: remove lock_any_ref_for_update\n - refs.c: make unlock_ref/close_ref/commit_ref static\n - refs.c: rename log_ref_setup to create_reflog\n - reflog.c: use a reflog transaction when writing during expire\n - refs.c: allow multiple reflog updates during a single transaction\n - refs.c: only write reflog update if msg is non-NULL\n - refs.c: add a flag to allow reflog updates to truncate the log\n - refs.c: add a transaction function to append a reflog entry\n - lockfile.c: make hold_lock_file_for_append preserve meaningful errno\n - refs.c: add a function to append a reflog entry to a fd\n - refs.c: add a new update_type field to ref_update\n - refs.c: rename the transaction functions\n - refs.c: make ref_transaction_delete a wrapper for ref_transaction_update\n - refs.c: make ref_transaction_create a wrapper to ref_transaction_update\n (this branch is used by rs/ref-transaction-multi and\nrs/ref-transaction-rename; uses rs/ref-transaction and\nrs/ref-transaction-1.)\n\nThis patch series adds support for reflog updates to the transaction subsystem.\nOnce it has refactored the builtin/reflog.c to use a transaction\ninstead of accessing the refs and\nreflogs directly it allows us to remove\nunlock_ref/close_ref/commit_ref/lock_any_ref_for_update\nfrom the public API.\n\nAs part of the reflog work I also refactor how reflog creation works\nrenaming log_ref_setup to create_reflog. These changes allow us then\nto reduce the number of places where\nwe expose the global log_all_ref_updates to the callers.\nThis leads to a much cleaner api for reflog creation and avoids completely the\n\"manipulate the global log_all_ref_updates before calling\nlog_ref_setup\" thing that was done\nin checkout.c. Now checkout.c can just call create_reflog() and the\nright thing will happen.\n\nAt the end of the patch series is an unrelated patch for repairing the\n\"allow deletion of a broken ref\" functionality that was lost a while\nback. This patch is independent of the reflog work but is placed here\njust to avoid creating additional conflicts with this series and\nseries that follow.\n\nHas been merged to pu.\n\n\nref-transactions-rename\n-----------------------------------\nrefs.c: allow passing raw git_committer_info as email to _update_reflog\nrefs.c: return error instead of dying when locking fails during transaction\nrefs.c: use packed refs when deleting refs during a transaction\nrefs.c: update rename_ref to use a transaction\nrefs.c: rollback the lockfile before we die() in repack_without_refs\n\nThis series focuses on reworking how rename_ref works and to make it\nuse an atomic transaction for the rename.\nThe first three patches lead up to a point where we can now perform\nthe \"delete the old ref and add the new ref\" as a single atomic\noperation to the packed refs file followed by the fourth patch where\nwe rework rename_ref() completely to perform the rename and the reflog\nupdates as a single atomic transaction.\n\nAs part of this the rename_ref logic is greatly simplified and we\nreach several nice goals:\n* no need to disallow rename_ref() if the reflog is a symlink\n* no need to rename the reflog via a third filename\n* make the rename become completely atomic both for the caller but\nalso for any external observers.\n\nThe fifth patch is independent to the rename work itself but was\ndiscovered during the work for this patch. It could be applied\nseparately but I did not see any urgency to push it separetely and\nleft it as a patch in this series.\n\nHas been merged into pu.\n\n\n\nref-transactions-req-packed-refs\n----------------------------------------------\nrefs.c: move reflog updates into its own function\nrefs.c: write updates to packed refs when a transaction has more than one ref\nremote.c: use a transaction for deleting refs\nrefs.c: make repack_without_refs static\nrefs.c: make the *_packed_refs functions static\n\nThese patches expands on the \"perform ref operations as a packed refs\nfile commit\" and changes the transaction subsystem to always perform\nmulti-ref updates as a packed ref commit instead of discreete\noperations on loose refs.\n\nThe new logic is basically:\nIF the transaction only touches a single ref, then just do the normal\nloose ref operation as before.\n   This is to make sure that the very common \"git commit\" command will\nstill always be fast.\n\nIF the transaction touches multiple refs, then\n * first copy/move all affected refs to the packed refs file and\ncommit, then re-lock the packed\n   refs file until the transaction has completed.\n * then delete any affected loose refs files (they are already in the\npacked refs file)\n * perform the operation by adding/removing/updating packed refs entries\n * finally, commit the packed refs file\n\nThis leads to several new nice properties.\nFirst, the transaction subsystem will now do the \"right thing\" and\nautomatically convert any\nmulti-ref updates to use the packed refs file. This then means that we\nno longer need to have\nspecial casing for clone.c to use a special packed_refs api. Clone.c\ncan now just use a transaction for its \"add all refs\" and the right\nthing will happen.\nSimilar optimizations can be done for remote.c which now also can just\nuse a transaction\nand know that the right thing will happen.\nAs we now no longer need to expose the special purpose packed-refs api\nto external callers we can now make all the packed refs functions\nprivate to refs.c and shrink the size of the public api.\n\nNote: we still do have packed refs and they still work the same way as\nbefore. It is just that the packed refs are now an internal\noptimization for the refs code and no longer part of the public api.\n\nSecond, all transaction that affects multiple refs will now also\nbecome atomic for any external observer regardless of whether they\nused the packed refs api or not.\n\n\nThe following functions are now static to refs.c and no longer part of\nthe public api:\n add_packed_ref/lock_packed_refs/commit_packed_refs/rollback_packed_refs/repack_without_refs\n\nHas been merged into pu.\n\n\n\nFollowing are the patch series that has not yet made its way into pu.\n\n\nref-transactions-req-strbuf-err\n-------------------------------------------\nThis series just contains various changes internally to refs.c to\nexpand on the use of a strbuf *err as argument for passing error\nmessages back to the caller.\nOnly thing by note here is tgat by doing this to update_ref() we can\nfinally get rid of the action_on_err enum:\n\n-enum action_on_err {\n-       UPDATE_REFS_MSG_ON_ERR,\n-       UPDATE_REFS_DIE_ON_ERR,\n-       UPDATE_REFS_QUIET_ON_ERR\n-};\n\nwooohooo\n\nAs part of this patch series I noticed that there were two places in\ntransport*.c where we were calling update_ref() and passing '0' as\nargument instead of UPDATE_REFS_MSG_ON_ERR.\nNot a bug and not a change in functionality but it would be nice if\nthe compiler could warn for this.\n\nSent to the list.\n\n\nref-transactions-send-pack\n---------------------------------------\nVarious changes to allow a new argument  --atomic-push, and protocol\nchanges, to allow a client to request/negotiate that a multi-ref push\nshould be atomic. I.e. apply all changes or none.\n\nSent to the list.\n\n\nbackend-struct-db\n--------------------------\nVarious changes that mainly moves functions between files to split the\ncurrent refs.c file into functions that are backend agnostic and the\nfunctions that implement the files based ref backend.\nIt finally adds a backend structure and the methods that define a\nbackend datastore for refs.\n\nThis series concludes the work to define pluggable backends for refs.\nAt this stage it will now be possible to start building and adding new\ntypes of ref backends to git.\n\nSent to the list\n\n\n\n\n\nIn addition to these patches, there is one more patchseries with 4 patches at :\nhttps://github.com/rsahlberg/git/tree/backend-struct-db-2\n\nThis series is for once the previous transaction changes are in and\nthis series will add\na new backend for refs: refs-be-db.c which offloads all refs access to\nan external database daemon.\nAn example reference implementation for an external daemon is also\nprovided and can be used as basis for\ncreating a daemon to plug into, say, a SQL database or similar.\n\n\nregards\nronnie sahlberg\n"},{"id":"247989","messageId":"xmqqfvgsunal.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"CAL=YDWm9VaKUBRAmmybHzOBhAg_VvNc0KMG0W_uTA02YYzQrzA@mail.gmail.com","subject":"Re: Transaction patch series overview","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-08-19T22:28:34Z","receivedAt":"2014-08-19T22:28:34Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ronnie Sahlberg <sahlberg@google.com> writes:\n\n> List, please see here an overview and ordering of the ref transaction\n> patch series.\n> These series build on each other and needs to be applied in the order\n> listed below.\n>\n> This is an update.\n>\n> rs/ref-transaction-0\n> ---------------------------\n>     Early part of the \"ref transaction\" topic.\n>\n>     * rs/ref-transaction-0:\n>       refs.c: change ref_transaction_update() to do error checking and\n> return status\n>       refs.c: remove the onerr argument to ref_transaction_commit\n>       update-ref: use err argument to get error from ref_transaction_commit\n>       refs.c: make update_ref_write update a strbuf on failure\n>       refs.c: make ref_update_reject_duplicates take a strbuf argument\n> for errors\n>       refs.c: log_ref_write should try to return meaningful errno\n>       refs.c: make resolve_ref_unsafe set errno to something meaningful on error\n>       refs.c: commit_packed_refs to return a meaningful errno on failure\n>       refs.c: make remove_empty_directories always set errno to something sane\n>       refs.c: verify_lock should set errno to something meaningful\n>       refs.c: make sure log_ref_setup returns a meaningful errno\n>       refs.c: add an err argument to repack_without_refs\n>       lockfile.c: make lock_file return a meaningful errno on failurei\n>       lockfile.c: add a new public function unable_to_lock_message\n>       refs.c: add a strbuf argument to ref_transaction_commit for error logging\n>       refs.c: allow passing NULL to ref_transaction_free\n>       refs.c: constify the sha arguments for\n> ref_transaction_create|delete|update\n>       refs.c: ref_transaction_commit should not free the transaction\n>       refs.c: remove ref_transaction_rollback\n>\n> Has been merged into next.\n\nI gave a quick re-read-thru on this and did not find anything\nquestionable, nor recall any issue pending.  Unless somebody\nobjects in the coming few days, let's move it to 'master' as part of\nthe first batch for 2.2.\n\n> ref-transaction-1 (2014-07-16) 20 commits\n> ...\n>  The second batch of the transactional ref update series.\n>\n> Has been merged into pu\n>\n> rs/ref-transaction (2014-07-17) 12 commits\n> ...\n> The third and final part of the basic ref-transaction work.\n> \n> Has been merged into pu.\n\nI'll re-read these two before deciding to merge them to 'next'.  Any\npending issues from the previous discussions?\n\n> This series is for once the previous transaction changes are in and\n> this series will add\n> a new backend for refs: refs-be-db.c which offloads all refs access to\n> an external database daemon.\n> An example reference implementation for an external daemon is also\n> provided and can be used as basis for\n> creating a daemon to plug into, say, a SQL database or similar.\n\nA filesystem based backend/external daemon that is compatible with\nthe traditional on-disk format might be another fun demonstration.\n\nLooking forward to shepherding the whole thing ahead ;-)  The most\nfun part awaits us but there is a long way.\n\nThanks.\n"},{"id":"248038","messageId":"20140820231723.GF20185@google.com","threadId":"37248","inReplyTo":"CAL=YDWm9VaKUBRAmmybHzOBhAg_VvNc0KMG0W_uTA02YYzQrzA@mail.gmail.com","subject":"Re: Transaction patch series overview","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-20T23:17:23Z","receivedAt":"2014-08-20T23:17:23Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nRonnie Sahlberg wrote:\n\n> List, please see here an overview and ordering of the ref transaction\n> patch series.\n\nThanks much for this.\n\n[...]\n> rs/ref-transaction-0\n[...]\n> Has been merged into next.\n\nThis is even part of \"master\" now, so if people have review comments\nthen they can make them most easily by submitting new patches on top.\n\n> ref-transaction-1 (2014-07-16) 20 commits\n> -------------------------------------------------------------\n> Second batch of ref transactions\n>\n>  - refs.c: make delete_ref use a transaction\n>  - refs.c: make prune_ref use a transaction to delete the ref\n>  - refs.c: remove lock_ref_sha1\n>  - refs.c: remove the update_ref_write function\n>  - refs.c: remove the update_ref_lock function\n>  - refs.c: make lock_ref_sha1 static\n>  - walker.c: use ref transaction for ref updates\n>  - fast-import.c: use a ref transaction when dumping tags\n>  - receive-pack.c: use a reference transaction for updating the refs\n>  - refs.c: change update_ref to use a transaction\n>  - branch.c: use ref transaction for all ref updates\n>  - fast-import.c: change update_branch to use ref transactions\n>  - sequencer.c: use ref transactions for all ref updates\n>  - commit.c: use ref transactions for updates\n>  - replace.c: use the ref transaction functions for updates\n>  - tag.c: use ref transactions when doing updates\n>  - refs.c: add transaction.status and track OPEN/CLOSED/ERROR\n>  - refs.c: make ref_transaction_begin take an err argument\n>  - refs.c: update ref_transaction_delete to check for error and return status\n>  - refs.c: change ref_transaction_create to do error checking and return status\n>  (this branch is used by rs/ref-transaction, rs/ref-transaction-multi,\n> rs/ref-transaction-reflog and rs/ref-transaction-rename.)\n>\n>  The second batch of the transactional ref update series.\n>\n> Has been merged into pu\n\nLast reviewed at\nhttp://thread.gmane.org/gmane.comp.version-control.git/252226, if\nI am using gmane search correctly.\n\nAre there any pending questions for this part?\n\nI've having trouble keeping track of how patches change, which patches\nhave been reviewed and which haven't, unaddressed comments, and so on,\nso as an experiment I've pushed this part of the series to the Gerrit\nserver at\n\n https://code-review.googlesource.com/#/q/topic:ref-transaction-1\n\nIt's running a copy of gerrit code review\n(https://code.google.com/p/gerrit).\n\nMaybe this can be useful as a way of keeping track of the state of\nlong and long-lived series like this one.  It works something like\nthis:\n\nPreparation\n-----------\nGet a password from https://code-review.googlesource.com/new-password\nand put it in netrc.\n\nInstall the hook from\nhttps://code-review.googlesource.com/tools/hooks/commit-msg to\n.git/hooks/commit-msg.  This automatically populates a Change-Id\nline in the commit message if none is present so gerrit can\ntell when you are uploading a new version of an existing patch.\n\n(The commit-msg hook can be annoying when not working with gerrit\ncode review.  To turn it off, use 'git config gerrit.createChangeId false'.)\n\nUploading patches\n-----------------\nWrite a patch series against 'maint' or 'master' as usual, then push:\n\n\tgit push https://code.googlesource.com/git \\\n\t\tHEAD:refs/for/master; # or refs/for/maint\n\nThere can be a parameter '%topic=<name>' after the refs/for/<branch>\n(e.g., refs/for/master%topic=ref-transaction-1) if the series should\nbe labelled with a topic name, or that can be set in the web UI or\nusing the HTTP API after the fact.\n\nCommenting on patches\n---------------------\nVisit https://code-review.googlesource.com.  Patches can be found\nunder \"All\" -> \"Open\".  Patches you're involved with somehow are\nat \"My\" -> \"Changes\".\n\nTo prepare inline comments, choose a file, highlight the text to comment\non or click a line number, and comment.\n\nTo publish comments, go back up to the change overview screen (using\nthe up arrow button or by pressing \"u\"), click \"Reply\", and say\nsomething.\n\n'?' brings up keyboard shortcuts.\n\nDownloading patches\n-------------------\nIn the change overview screen, there's a 'Download' menu in the\ntop-right corner with commands to download the patch.\n\nRevising patches\n----------------\nAfter modifying a patch locally using the usual tools such as rebase\n--interactive, push again:\n\n\tgit push https://code.googlesource.com/git \\\n\t\tHEAD:refs/for/master; # or refs/for/maint\n\nWhen a patch seems polished\n---------------------------\nGet rid of the Change-Id lines --- they shouldn't be needed any\nmore.  Send the patches or a request to pull from a public git\nrepository, as usual.  A link (in the top-left corner where it says\n'Change 1000', the \"1000\" is a link to the change) can be helpful\nfor letting people on-list see how the patch got into the form it's\nin today.\n\nMaybe it can be useful.\n\nThanks,\nJonathan\n"},{"id":"248279","messageId":"20140826000354.GW20185@google.com","threadId":"37248","inReplyTo":"20140820231723.GF20185@google.com","subject":"Re: Transaction patch series overview","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-26T00:03:54Z","receivedAt":"2014-08-26T00:03:54Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jonathan Nieder wrote:\n> Ronnie Sahlberg wrote:\n\n>> ref-transaction-1 (2014-07-16) 20 commits\n>> -------------------------------------------------------------\n>> Second batch of ref transactions\n>>\n>>  - refs.c: make delete_ref use a transaction\n>>  - refs.c: make prune_ref use a transaction to delete the ref\n>>  - refs.c: remove lock_ref_sha1\n>>  - refs.c: remove the update_ref_write function\n>>  - refs.c: remove the update_ref_lock function\n>>  - refs.c: make lock_ref_sha1 static\n>>  - walker.c: use ref transaction for ref updates\n>>  - fast-import.c: use a ref transaction when dumping tags\n>>  - receive-pack.c: use a reference transaction for updating the refs\n>>  - refs.c: change update_ref to use a transaction\n>>  - branch.c: use ref transaction for all ref updates\n>>  - fast-import.c: change update_branch to use ref transactions\n>>  - sequencer.c: use ref transactions for all ref updates\n>>  - commit.c: use ref transactions for updates\n>>  - replace.c: use the ref transaction functions for updates\n>>  - tag.c: use ref transactions when doing updates\n>>  - refs.c: add transaction.status and track OPEN/CLOSED/ERROR\n>>  - refs.c: make ref_transaction_begin take an err argument\n>>  - refs.c: update ref_transaction_delete to check for error and return status\n>>  - refs.c: change ref_transaction_create to do error checking and return status\n>>  (this branch is used by rs/ref-transaction, rs/ref-transaction-multi,\n>> rs/ref-transaction-reflog and rs/ref-transaction-rename.)\n[...]\n> I've having trouble keeping track of how patches change, which patches\n> have been reviewed and which haven't, unaddressed comments, and so on,\n> so as an experiment I've pushed this part of the series to the Gerrit\n> server at\n>\n>  https://code-review.googlesource.com/#/q/topic:ref-transaction-1\n\nOutcome of the experiment: patches gained some minor changes and then\n\n 1-12\n\tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n\n 13\n\tReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n\tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n\n 14\n\tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n\n 15-16\n\tReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n\tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n\n 17\n\tReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n\n 18\n\tReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n\tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n\n 19\n\tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n\n 20\n\tReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n\tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n\nI found the web UI helpful in seeing how each patch evolved since I\nlast looked at it.  Interdiff relative to the version in pu is below.\nI'm still hoping for a tweak in response to a minor comment and then I\ncan put up a copy of the updated series to pull.\n\nThe next series from Ronnie's collection is available at\nhttps://code-review.googlesource.com/#/q/topic:ref-transaction in case\nsomeone wants a fresh series to look at.\n\ndiff --git a/branch.c b/branch.c\nindex c1eae00..37ac555 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -305,6 +305,7 @@ void create_branch(const char *head,\n \t\t    ref_transaction_commit(transaction, msg, &err))\n \t\t\tdie(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n+\t\tstrbuf_release(&err);\n \t}\n \n \tif (real_ref && track)\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 668fa6a..9bf1003 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1765,8 +1765,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\", sha1,\n-\t\t\t\t   current_head ?\n-\t\t\t\t   current_head->object.sha1 : NULL,\n+\t\t\t\t   current_head\n+\t\t\t\t   ? current_head->object.sha1 : NULL,\n \t\t\t\t   0, !!current_head, &err) ||\n \t    ref_transaction_commit(transaction, sb.buf, &err)) {\n \t\trollback_index_files();\n@@ -1801,5 +1801,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tif (!quiet)\n \t\tprint_summary(prefix, sha1, !current_head);\n \n+\tstrbuf_release(&err);\n \treturn 0;\n }\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 91099ad..224fadc 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -194,7 +194,7 @@ static void write_head_info(void)\n \n struct command {\n \tstruct command *next;\n-\tchar *error_string;\n+\tconst char *error_string;\n \tunsigned int skip_update:1,\n \t\t     did_not_exist:1;\n \tint index;\n@@ -468,7 +468,7 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)\n \treturn 0;\n }\n \n-static char *update(struct command *cmd, struct shallow_info *si)\n+static const char *update(struct command *cmd, struct shallow_info *si)\n {\n \tconst char *name = cmd->ref_name;\n \tstruct strbuf namespaced_name_buf = STRBUF_INIT;\n@@ -479,7 +479,7 @@ static char *update(struct command *cmd, struct shallow_info *si)\n \t/* only refs/... are allowed */\n \tif (!starts_with(name, \"refs/\") || check_refname_format(name + 5, 0)) {\n \t\trp_error(\"refusing to create funny ref '%s' remotely\", name);\n-\t\treturn xstrdup(\"funny refname\");\n+\t\treturn \"funny refname\";\n \t}\n \n \tstrbuf_addf(&namespaced_name_buf, \"%s%s\", get_git_namespace(), name);\n@@ -497,20 +497,20 @@ static char *update(struct command *cmd, struct shallow_info *si)\n \t\t\trp_error(\"refusing to update checked out branch: %s\", name);\n \t\t\tif (deny_current_branch == DENY_UNCONFIGURED)\n \t\t\t\trefuse_unconfigured_deny();\n-\t\t\treturn xstrdup(\"branch is currently checked out\");\n+\t\t\treturn \"branch is currently checked out\";\n \t\t}\n \t}\n \n \tif (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {\n \t\terror(\"unpack should have generated %s, \"\n \t\t      \"but I can't find it!\", sha1_to_hex(new_sha1));\n-\t\treturn xstrdup(\"bad pack\");\n+\t\treturn \"bad pack\";\n \t}\n \n \tif (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {\n \t\tif (deny_deletes && starts_with(name, \"refs/heads/\")) {\n \t\t\trp_error(\"denying ref deletion for %s\", name);\n-\t\t\treturn xstrdup(\"deletion prohibited\");\n+\t\t\treturn \"deletion prohibited\";\n \t\t}\n \n \t\tif (!strcmp(namespaced_name, head_name)) {\n@@ -525,7 +525,7 @@ static char *update(struct command *cmd, struct shallow_info *si)\n \t\t\t\tif (deny_delete_current == DENY_UNCONFIGURED)\n \t\t\t\t\trefuse_unconfigured_deny_delete_current();\n \t\t\t\trp_error(\"refusing to delete the current branch: %s\", name);\n-\t\t\t\treturn xstrdup(\"deletion of the current branch prohibited\");\n+\t\t\t\treturn \"deletion of the current branch prohibited\";\n \t\t\t}\n \t\t}\n \t}\n@@ -543,19 +543,19 @@ static char *update(struct command *cmd, struct shallow_info *si)\n \t\t    old_object->type != OBJ_COMMIT ||\n \t\t    new_object->type != OBJ_COMMIT) {\n \t\t\terror(\"bad sha1 objects for %s\", name);\n-\t\t\treturn xstrdup(\"bad ref\");\n+\t\t\treturn \"bad ref\";\n \t\t}\n \t\told_commit = (struct commit *)old_object;\n \t\tnew_commit = (struct commit *)new_object;\n \t\tif (!in_merge_bases(old_commit, new_commit)) {\n \t\t\trp_error(\"denying non-fast-forward %s\"\n \t\t\t\t \" (you should pull first)\", name);\n-\t\t\treturn xstrdup(\"non-fast-forward\");\n+\t\t\treturn \"non-fast-forward\";\n \t\t}\n \t}\n \tif (run_update_hook(cmd)) {\n \t\trp_error(\"hook declined to update %s\", name);\n-\t\treturn xstrdup(\"hook declined\");\n+\t\treturn \"hook declined\";\n \t}\n \n \tif (is_null_sha1(new_sha1)) {\n@@ -570,7 +570,7 @@ static char *update(struct command *cmd, struct shallow_info *si)\n \t\t}\n \t\tif (delete_ref(namespaced_name, old_sha1, 0)) {\n \t\t\trp_error(\"failed to delete %s\", name);\n-\t\t\treturn xstrdup(\"failed to delete\");\n+\t\t\treturn \"failed to delete\";\n \t\t}\n \t\treturn NULL; /* good */\n \t}\n@@ -580,18 +580,18 @@ static char *update(struct command *cmd, struct shallow_info *si)\n \n \t\tif (shallow_update && si->shallow_ref[cmd->index] &&\n \t\t    update_shallow_ref(cmd, si))\n-\t\t\treturn xstrdup(\"shallow error\");\n+\t\t\treturn \"shallow error\";\n \n \t\ttransaction = ref_transaction_begin(&err);\n \t\tif (!transaction ||\n \t\t    ref_transaction_update(transaction, namespaced_name,\n \t\t\t\t\t   new_sha1, old_sha1, 0, 1, &err) ||\n \t\t    ref_transaction_commit(transaction, \"push\", &err)) {\n-\t\t\tchar *str = strbuf_detach(&err, NULL);\n \t\t\tref_transaction_free(transaction);\n \n-\t\t\trp_error(\"%s\", str);\n-\t\t\treturn str;\n+\t\t\trp_error(\"%s\", err.buf);\n+\t\t\tstrbuf_release(&err);\n+\t\t\treturn \"failed to update ref\";\n \t\t}\n \n \t\tref_transaction_free(transaction);\n@@ -654,9 +654,6 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)\n \tchar cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];\n \tint flag;\n \n-\tif (cmd->error_string)\n-\t\tdie(\"BUG: check_aliased_update called with failed cmd\");\n-\n \tstrbuf_addf(&buf, \"%s%s\", get_git_namespace(), cmd->ref_name);\n \tdst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag);\n \tstrbuf_release(&buf);\n@@ -668,7 +665,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)\n \tif (!dst_name) {\n \t\trp_error(\"refusing update to broken symref '%s'\", cmd->ref_name);\n \t\tcmd->skip_update = 1;\n-\t\tcmd->error_string = xstrdup(\"broken symref\");\n+\t\tcmd->error_string = \"broken symref\";\n \t\treturn;\n \t}\n \n@@ -694,9 +691,8 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)\n \t\t cmd->ref_name, cmd_oldh, cmd_newh,\n \t\t dst_cmd->ref_name, dst_oldh, dst_newh);\n \n-\tcmd->error_string = xstrdup(\"inconsistent aliased update\");\n-\tfree(dst_cmd->error_string);\n-\tdst_cmd->error_string = xstrdup(\"inconsistent aliased update\");\n+\tcmd->error_string = dst_cmd->error_string =\n+\t\t\"inconsistent aliased update\";\n }\n \n static void check_aliased_updates(struct command *commands)\n@@ -744,9 +740,7 @@ static void set_connectivity_errors(struct command *commands,\n \t\tif (!check_everything_connected(command_singleton_iterator,\n \t\t\t\t\t\t0, &singleton))\n \t\t\tcontinue;\n-\t\tif (cmd->error_string)  /* can't happen */\n-\t\t\tcontinue;\n-\t\tcmd->error_string = xstrdup(\"missing necessary objects\");\n+\t\tcmd->error_string = \"missing necessary objects\";\n \t}\n }\n \n@@ -783,9 +777,9 @@ static void reject_updates_to_hidden(struct command *commands)\n \t\tif (cmd->error_string || !ref_is_hidden(cmd->ref_name))\n \t\t\tcontinue;\n \t\tif (is_null_sha1(cmd->new_sha1))\n-\t\t\tcmd->error_string = xstrdup(\"deny deleting a hidden ref\");\n+\t\t\tcmd->error_string = \"deny deleting a hidden ref\";\n \t\telse\n-\t\t\tcmd->error_string = xstrdup(\"deny updating a hidden ref\");\n+\t\t\tcmd->error_string = \"deny updating a hidden ref\";\n \t}\n }\n \n@@ -799,11 +793,8 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \n \tif (unpacker_error) {\n-\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\t\tif (cmd->error_string)  /* can't happen */\n-\t\t\t\tcontinue;\n-\t\t\tcmd->error_string = xstrdup(\"unpacker error\");\n-\t\t}\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tcmd->error_string = \"unpacker error\";\n \t\treturn;\n \t}\n \n@@ -816,9 +807,8 @@ static void execute_commands(struct command *commands,\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\t\tif (cmd->error_string)\n-\t\t\t\tcontinue;\n-\t\t\tcmd->error_string = xstrdup(\"pre-receive hook declined\");\n+\t\t\tif (!cmd->error_string)\n+\t\t\t\tcmd->error_string = \"pre-receive hook declined\";\n \t\t}\n \t\treturn;\n \t}\n@@ -1096,8 +1086,7 @@ static void update_shallow_info(struct command *commands,\n \t\tif (is_null_sha1(cmd->new_sha1))\n \t\t\tcontinue;\n \t\tif (ref_status[cmd->index]) {\n-\t\t\tfree(cmd->error_string);\n-\t\t\tcmd->error_string = xstrdup(\"shallow update not allowed\");\n+\t\t\tcmd->error_string = \"shallow update not allowed\";\n \t\t\tcmd->skip_update = 1;\n \t\t}\n \t}\n@@ -1138,17 +1127,6 @@ static int delete_only(struct command *commands)\n \treturn 1;\n }\n \n-static void free_commands(struct command *commands)\n-{\n-\twhile (commands) {\n-\t\tstruct command *next = commands->next;\n-\n-\t\tfree(commands->error_string);\n-\t\tfree(commands);\n-\t\tcommands = next;\n-\t}\n-}\n-\n int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n {\n \tint advertise_refs = 0;\n@@ -1244,6 +1222,5 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tpacket_flush(1);\n \tsha1_array_clear(&shallow);\n \tsha1_array_clear(&ref);\n-\tfree_commands(commands);\n \treturn 0;\n }\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 7528f3d..1fcd06d 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -169,8 +169,7 @@ static int replace_object_sha1(const char *object_ref,\n \n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, repl, prev,\n-\t\t\t\t   0, !is_null_sha1(prev), &err) ||\n+\t    ref_transaction_update(transaction, ref, repl, prev, 0, 1, &err) ||\n \t    ref_transaction_commit(transaction, NULL, &err))\n \t\tdie(\"%s\", err.buf);\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 1aa88a2..f3f172f 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -712,6 +712,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tif (force && !is_null_sha1(prev) && hashcmp(prev, object))\n \t\tprintf(_(\"Updated tag '%s' (was %s)\\n\"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV));\n \n+\tstrbuf_release(&err);\n \tstrbuf_release(&buf);\n \tstrbuf_release(&ref);\n \treturn 0;\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex c6ad0be..96a53b9 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -366,6 +366,8 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \n \tif (read_stdin) {\n \t\ttransaction = ref_transaction_begin(&err);\n+\t\tif (!transaction)\n+\t\t\tdie(\"%s\", err.buf);\n \t\tif (delete || no_deref || argc > 0)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n \t\tif (end_null)\n@@ -374,6 +376,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \t\tif (ref_transaction_commit(transaction, msg, &err))\n \t\t\tdie(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n+\t\tstrbuf_release(&err);\n \t\treturn 0;\n \t}\n \ndiff --git a/fast-import.c b/fast-import.c\nindex a95e1be..e7f6e37 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1716,6 +1716,7 @@ static int update_branch(struct branch *b)\n \t\treturn -1;\n \t}\n \tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n \treturn 0;\n }\n \ndiff --git a/refs.c b/refs.c\nindex 0017d9c..819e25f 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2074,7 +2074,10 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n \treturn logs_found;\n }\n \n-/* This function should make sure errno is meaningful on error */\n+/*\n+ * Locks a \"refs/\" ref returning the lock on success and NULL on failure.\n+ * On failure errno is set to something meaningful.\n+ */\n static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\t\t\t    const unsigned char *old_sha1,\n \t\t\t\t\t    int flags, int *type_p)\n@@ -2401,6 +2404,7 @@ static void prune_ref(struct ref_to_prune *r)\n \t\treturn;\n \t}\n \tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n \ttry_remove_empty_parents(r->name);\n }\n \n@@ -2575,6 +2579,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n \t\treturn 1;\n \t}\n \tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n \treturn 0;\n }\n \n@@ -3352,19 +3357,15 @@ struct ref_update {\n  * Transaction states.\n  * OPEN:   The transaction is in a valid state and can accept new updates.\n  *         An OPEN transaction can be committed.\n- * CLOSED: If an open transaction is successfully committed the state will\n- *         change to CLOSED. No further changes can be made to a CLOSED\n- *         transaction.\n- *         CLOSED means that all updates have been successfully committed and\n- *         the only thing that remains is to free the completed transaction.\n- * ERROR:  The transaction has failed and is no longer committable.\n- *         No further changes can be made to a CLOSED transaction and it must\n- *         be rolled back using transaction_free.\n+ * CLOSED: A closed transaction is no longer active and no other operations\n+ *         than free can be used on it in this state.\n+ *         A transaction can either become closed by successfully committing\n+ *         an active transaction or if there is a failure while building\n+ *         the transaction thus rendering it failed/inactive.\n  */\n enum ref_transaction_state {\n \tREF_TRANSACTION_OPEN   = 0,\n-\tREF_TRANSACTION_CLOSED = 1,\n-\tREF_TRANSACTION_ERROR  = 2,\n+\tREF_TRANSACTION_CLOSED = 1\n };\n \n /*\n@@ -3509,6 +3510,7 @@ int update_ref(const char *action, const char *refname,\n \t\tstrbuf_release(&err);\n \t\treturn 1;\n \t}\n+\tstrbuf_release(&err);\n \treturn 0;\n }\n \n@@ -3588,10 +3590,10 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t\t     msg);\n \t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n \t\t\tif (ret) {\n-\t\t\t\tconst char *str = \"Cannot update the ref '%s'.\";\n-\n \t\t\t\tif (err)\n-\t\t\t\t\tstrbuf_addf(err, str, update->refname);\n+\t\t\t\t\tstrbuf_addf(err, \"Cannot update the \"\n+\t\t\t\t\t\t    \"ref '%s'.\",\n+\t\t\t\t\t\t    update->refname);\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \t\t}\n@@ -3614,8 +3616,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \tclear_loose_ref_cache(&ref_cache);\n \n cleanup:\n-\ttransaction->state = ret ? REF_TRANSACTION_ERROR\n-\t\t: REF_TRANSACTION_CLOSED;\n+\ttransaction->state = REF_TRANSACTION_CLOSED;\n \n \tfor (i = 0; i < n; i++)\n \t\tif (updates[i]->lock)\ndiff --git a/refs.h b/refs.h\nindex aad846c..69ef28c 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -180,8 +180,7 @@ extern int peel_ref(const char *refname, unsigned char *sha1);\n  */\n #define REF_NODEREF\t0x01\n /*\n- * Locks any ref (for 'HEAD' type refs) and sets errno to something\n- * meaningful on failure.\n+ * This function sets errno to something meaningful on failure.\n  */\n extern struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t\tconst unsigned char *old_sha1,\ndiff --git a/sequencer.c b/sequencer.c\nindex cf17c69..5e93b6a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -296,6 +296,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,\n \t}\n \n \tstrbuf_release(&sb);\n+\tstrbuf_release(&err);\n \tref_transaction_free(transaction);\n \treturn 0;\n }\ndiff --git a/walker.c b/walker.c\nindex 60d9f9e..b8a5441 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -251,12 +251,12 @@ void walker_targets_free(int targets, char **target, const char **write_ref)\n int walker_fetch(struct walker *walker, int targets, char **target,\n \t\t const char **write_ref, const char *write_ref_log_details)\n {\n-\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct strbuf refname = STRBUF_INIT;\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct ref_transaction *transaction = NULL;\n \tunsigned char *sha1 = xmalloc(targets * 20);\n \tchar *msg = NULL;\n-\tint i;\n+\tint i, ret = -1;\n \n \tsave_commit_buffer = 0;\n \n@@ -264,7 +264,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\ttransaction = ref_transaction_begin(&err);\n \t\tif (!transaction) {\n \t\t\terror(\"%s\", err.buf);\n-\t\t\tgoto rollback_and_fail;\n+\t\t\tgoto done;\n \t\t}\n \t}\n \tif (!walker->get_recover)\n@@ -273,15 +273,18 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \tfor (i = 0; i < targets; i++) {\n \t\tif (interpret_target(walker, target[i], &sha1[20 * i])) {\n \t\t\terror(\"Could not interpret response from server '%s' as something to pull\", target[i]);\n-\t\t\tgoto rollback_and_fail;\n+\t\t\tgoto done;\n \t\t}\n \t\tif (process(walker, lookup_unknown_object(&sha1[20 * i])))\n-\t\t\tgoto rollback_and_fail;\n+\t\t\tgoto done;\n \t}\n \n \tif (loop(walker))\n-\t\tgoto rollback_and_fail;\n-\n+\t\tgoto done;\n+\tif (!write_ref) {\n+\t\tret = 0;\n+\t\tgoto done;\n+\t}\n \tif (write_ref_log_details) {\n \t\tmsg = xmalloc(strlen(write_ref_log_details) + 12);\n \t\tsprintf(msg, \"fetch from %s\", write_ref_log_details);\n@@ -289,37 +292,33 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\tmsg = NULL;\n \t}\n \tfor (i = 0; i < targets; i++) {\n-\t\tif (!write_ref || !write_ref[i])\n+\t\tif (!write_ref[i])\n \t\t\tcontinue;\n-\t\tstrbuf_reset(&ref_name);\n-\t\tstrbuf_addf(&ref_name, \"refs/%s\", write_ref[i]);\n-\t\tif (ref_transaction_update(transaction, ref_name.buf,\n+\t\tstrbuf_reset(&refname);\n+\t\tstrbuf_addf(&refname, \"refs/%s\", write_ref[i]);\n+\t\tif (ref_transaction_update(transaction, refname.buf,\n \t\t\t\t\t   &sha1[20 * i], NULL, 0, 0,\n \t\t\t\t\t   &err)) {\n \t\t\terror(\"%s\", err.buf);\n-\t\t\tgoto rollback_and_fail;\n+\t\t\tgoto done;\n \t\t}\n \t}\n-\tif (write_ref) {\n-\t\tif (ref_transaction_commit(transaction,\n-\t\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n-\t\t\t\t\t   &err)) {\n-\t\t\terror(\"%s\", err.buf);\n-\t\t\tgoto rollback_and_fail;\n-\t\t}\n-\t\tref_transaction_free(transaction);\n+\tif (ref_transaction_commit(transaction,\n+\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n+\t\t\t\t   &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tgoto done;\n \t}\n \n-\tfree(msg);\n-\treturn 0;\n+\tret = 0;\n \n-rollback_and_fail:\n+done:\n \tref_transaction_free(transaction);\n \tfree(msg);\n+\tfree(sha1);\n \tstrbuf_release(&err);\n-\tstrbuf_release(&ref_name);\n-\n-\treturn -1;\n+\tstrbuf_release(&refname);\n+\treturn ret;\n }\n \n void walker_free(struct walker *walker)\n"},{"id":"248346","messageId":"xmqqlhqbge3a.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"20140826000354.GW20185@google.com","subject":"Re: Transaction patch series overview","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-08-26T21:01:29Z","receivedAt":"2014-08-26T21:01:29Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>>  https://code-review.googlesource.com/#/q/topic:ref-transaction-1\n>\n> Outcome of the experiment: patches gained some minor changes and then\n>\n>  1-12\n> \tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n>\n>  13\n> \tReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n> \tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n>\n>  14\n> \tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n>\n>  15-16\n> \tReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n> \tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n>\n>  17\n> \tReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n>\n>  18\n> \tReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n> \tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n>\n>  19\n> \tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n>\n>  20\n> \tReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n> \tReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n>\n> I found the web UI helpful in seeing how each patch evolved since I\n> last looked at it.  Interdiff relative to the version in pu is below.\n\nThanks for the interdiff, it really helps to be able to take a\nglance without having to click around.\n\nIt seems that I can hold the topic in 'pu' a bit longer and expect a\nreroll from this effort before merging it to 'next'?\n\n> The next series from Ronnie's collection is available at\n> https://code-review.googlesource.com/#/q/topic:ref-transaction in case\n> someone wants a fresh series to look at.\n\nThanks.\n\n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 668fa6a..9bf1003 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -1765,8 +1765,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>  \ttransaction = ref_transaction_begin(&err);\n>  \tif (!transaction ||\n>  \t    ref_transaction_update(transaction, \"HEAD\", sha1,\n> -\t\t\t\t   current_head ?\n> -\t\t\t\t   current_head->object.sha1 : NULL,\n> +\t\t\t\t   current_head\n> +\t\t\t\t   ? current_head->object.sha1 : NULL,\n>  \t\t\t\t   0, !!current_head, &err) ||\n>  \t    ref_transaction_commit(transaction, sb.buf, &err)) {\n>  \t\trollback_index_files();\n\nPerhaps this is nicer, but probably most people would not care.\n\n> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\n> index 91099ad..224fadc 100644\n> --- a/builtin/receive-pack.c\n> +++ b/builtin/receive-pack.c\n> @@ -580,18 +580,18 @@ static char *update(struct command *cmd, struct shallow_info *si)\n>  \n>  \t\tif (shallow_update && si->shallow_ref[cmd->index] &&\n>  \t\t    update_shallow_ref(cmd, si))\n> -\t\t\treturn xstrdup(\"shallow error\");\n> +\t\t\treturn \"shallow error\";\n>  \n>  \t\ttransaction = ref_transaction_begin(&err);\n>  \t\tif (!transaction ||\n>  \t\t    ref_transaction_update(transaction, namespaced_name,\n>  \t\t\t\t\t   new_sha1, old_sha1, 0, 1, &err) ||\n>  \t\t    ref_transaction_commit(transaction, \"push\", &err)) {\n> -\t\t\tchar *str = strbuf_detach(&err, NULL);\n>  \t\t\tref_transaction_free(transaction);\n>  \n> -\t\t\trp_error(\"%s\", str);\n> -\t\t\treturn str;\n> +\t\t\trp_error(\"%s\", err.buf);\n> +\t\t\tstrbuf_release(&err);\n> +\t\t\treturn \"failed to update ref\";\n>  \t\t}\n\nI am guessing that the purpose of this change is to make sure that\ncmd->error_string is taken from a fixed set of strings, not an\narbitrary collection of errors from the transaction subsystem, but\nwhat is the significance of doing so?  Do the other side i18n while\nrunning print_ref_status() or something?\n\n> diff --git a/refs.h b/refs.h\n> index aad846c..69ef28c 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -180,8 +180,7 @@ extern int peel_ref(const char *refname, unsigned char *sha1);\n>   */\n>  #define REF_NODEREF\t0x01\n>  /*\n> - * Locks any ref (for 'HEAD' type refs) and sets errno to something\n> - * meaningful on failure.\n> + * This function sets errno to something meaningful on failure.\n>   */\n>  extern struct ref_lock *lock_any_ref_for_update(const char *refname,\n>  \t\t\t\t\t\tconst unsigned char *old_sha1,\n\nTo contrast this function with lock_ref_sha1() that only allowed\nrefs under refs/ hierarchy, the comment may have made sense, but now\nthat other function is gone, losing the comment makes sense.\n\nI removed from the above the interdiff hunks I did not have any\ncomments or questions on.\n\nThanks again.\n"},{"id":"248354","messageId":"20140826221448.GY20185@google.com","threadId":"37248","inReplyTo":"xmqqlhqbge3a.fsf@gitster.dls.corp.google.com","subject":"Re: Transaction patch series overview","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-26T22:14:48Z","receivedAt":"2014-08-26T22:14:48Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi again,\n\nJunio C Hamano wrote:\n\n> It seems that I can hold the topic in 'pu' a bit longer and expect a\n> reroll from this effort before merging it to 'next'?\n\nSorry for the delay.  The following changes on top of \"master\"\n(refs.c: change ref_transaction_update() to do error checking and\nreturn status, 2014-07-14) are available in the git repository at\n\n  git://repo.or.cz/git/jrn.git tags/rs/ref-transaction-1\n\nfor you to fetch changes up to 432ad1f39fd773b37757dd8f10b3075dc3d0d0a2:\n\n  refs.c: make delete_ref use a transaction (2014-08-26 14:22:53 -0700)\n\n----------------------------------------------------------------\n\"Use ref transactions\", early part\n\nRonnie explains:\n\n\tThis patch series expands on the transaction API. It converts\n\tref updates, inside refs.c as well as external, to use the\n\ttransaction API for updates. This makes most ref updates\n\tatomic when there are failures locking or writing to a ref.\n\nThis series teaches the tag, replace, commit, cherry-pick,\nfast-import, checkout -b, branch, receive-pack, and http-fetch\ncommands and all update_ref and delete_ref callers to use the ref\ntransaction API instead of lock_ref_sha1.\n\nThe main user-visible change should be some cosmetic changes to error\nmessages.  The series also combines multiple ref updates into a single\ntransaction in 'git http-fetch -w' and when writing tags in\nfast-import but otherwise doesn't change the granularity of\ntransactions.\n\nReviewed at https://code-review.googlesource.com/#/q/topic:ref-transaction-1\n\n----------------------------------------------------------------\nRonnie Sahlberg (20):\n      refs.c: change ref_transaction_create to do error checking and return status\n      refs.c: update ref_transaction_delete to check for error and return status\n      refs.c: make ref_transaction_begin take an err argument\n      refs.c: add transaction.status and track OPEN/CLOSED\n      tag.c: use ref transactions when doing updates\n      replace.c: use the ref transaction functions for updates\n      commit.c: use ref transactions for updates\n      sequencer.c: use ref transactions for all ref updates\n      fast-import.c: change update_branch to use ref transactions\n      branch.c: use ref transaction for all ref updates\n      refs.c: change update_ref to use a transaction\n      receive-pack.c: use a reference transaction for updating the refs\n      fast-import.c: use a ref transaction when dumping tags\n      walker.c: use ref transaction for ref updates\n      refs.c: make lock_ref_sha1 static\n      refs.c: remove the update_ref_lock function\n      refs.c: remove the update_ref_write function\n      refs.c: remove lock_ref_sha1\n      refs.c: make prune_ref use a transaction to delete the ref\n      refs.c: make delete_ref use a transaction\n\n branch.c               |  31 ++++---\n builtin/commit.c       |  25 +++--\n builtin/receive-pack.c |  25 +++--\n builtin/replace.c      |  14 +--\n builtin/tag.c          |  16 ++--\n builtin/update-ref.c   |  14 ++-\n fast-import.c          |  54 +++++++----\n refs.c                 | 244 ++++++++++++++++++++++++++++---------------------\n refs.h                 |  77 ++++++++++++----\n sequencer.c            |  26 ++++--\n walker.c               |  70 ++++++++------\n 11 files changed, 367 insertions(+), 229 deletions(-)\n\n[...]\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>> --- a/builtin/receive-pack.c\n>> +++ b/builtin/receive-pack.c\n>> @@ -580,18 +580,18 @@ static char *update(struct command *cmd, struct shallow_info *si)\n[...]\n>>  \t\tif (!transaction ||\n>>  \t\t    ref_transaction_update(transaction, namespaced_name,\n>>  \t\t\t\t\t   new_sha1, old_sha1, 0, 1, &err) ||\n>>  \t\t    ref_transaction_commit(transaction, \"push\", &err)) {\n>> -\t\t\tchar *str = strbuf_detach(&err, NULL);\n>>  \t\t\tref_transaction_free(transaction);\n>>  \n>> -\t\t\trp_error(\"%s\", str);\n>> -\t\t\treturn str;\n>> +\t\t\trp_error(\"%s\", err.buf);\n>> +\t\t\tstrbuf_release(&err);\n>> +\t\t\treturn \"failed to update ref\";\n>>  \t\t}\n>\n> I am guessing that the purpose of this change is to make sure that\n> cmd->error_string is taken from a fixed set of strings, not an\n> arbitrary collection of errors from the transaction subsystem, but\n> what is the significance of doing so?  Do the other side i18n while\n> running print_ref_status() or something?\n\nIt's about keeping the message in parentheses on the \"! rejected\nmaster -> master\" line short and simple, since the more detailed\ndiagnosis is redundant next to the full message that is sent over\nsideband earlier and gets a line to itself.\n\nSide effects:\n\n * a less invasive patch --- no more need to change the calling\n   convention to return a dynamically allocated string, with\n   assertions throughout the file that check for leaks\n\n * using the same \"all lowercase and brief\" convention makes the\n   message less jarring next to other statuses for \"! rejected\" lines,\n   like \"non-fast-forward\"\n\n * no more O(n) stall from traversing the linked list to free messages\n   when receive-pack is about to exit\n\n[...]\n>> diff --git a/refs.h b/refs.h\n>> index aad846c..69ef28c 100644\n>> --- a/refs.h\n>> +++ b/refs.h\n>> @@ -180,8 +180,7 @@ extern int peel_ref(const char *refname, unsigned char *sha1);\n>>   */\n>>  #define REF_NODEREF\t0x01\n>>  /*\n>> - * Locks any ref (for 'HEAD' type refs) and sets errno to something\n>> - * meaningful on failure.\n>> + * This function sets errno to something meaningful on failure.\n>>   */\n>>  extern struct ref_lock *lock_any_ref_for_update(const char *refname,\n>>  \t\t\t\t\t\tconst unsigned char *old_sha1,\n>\n> To contrast this function with lock_ref_sha1() that only allowed\n> refs under refs/ hierarchy, the comment may have made sense, but now\n> that other function is gone, losing the comment makes sense.\n\nAhhh.  Thanks for explaining.\n\nJonathan\n"},{"id":"248357","messageId":"20140827002804.GA20185@google.com","threadId":"37248","inReplyTo":"20140826221448.GY20185@google.com","subject":"[PATCH 0/20] rs/ref-transaction-1 (Re: Transaction patch series overview)","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:28:04Z","receivedAt":"2014-08-27T00:28:04Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jonathan Nieder wrote:\n\n> This series teaches the tag, replace, commit, cherry-pick,\n> fast-import, checkout -b, branch, receive-pack, and http-fetch\n> commands and all update_ref and delete_ref callers to use the ref\n> transaction API instead of lock_ref_sha1.\n>\n> The main user-visible change should be some cosmetic changes to error\n> messages.  The series also combines multiple ref updates into a single\n> transaction in 'git http-fetch -w' and when writing tags in\n> fast-import but otherwise doesn't change the granularity of\n> transactions.\n>\n> Reviewed at https://code-review.googlesource.com/#/q/topic:ref-transaction-1\n>\n> ----------------------------------------------------------------\n> Ronnie Sahlberg (20):\n>       refs.c: change ref_transaction_create to do error checking and return status\n>       refs.c: update ref_transaction_delete to check for error and return status\n>       refs.c: make ref_transaction_begin take an err argument\n>       refs.c: add transaction.status and track OPEN/CLOSED\n>       tag.c: use ref transactions when doing updates\n>       replace.c: use the ref transaction functions for updates\n>       commit.c: use ref transactions for updates\n>       sequencer.c: use ref transactions for all ref updates\n>       fast-import.c: change update_branch to use ref transactions\n>       branch.c: use ref transaction for all ref updates\n>       refs.c: change update_ref to use a transaction\n>       receive-pack.c: use a reference transaction for updating the refs\n>       fast-import.c: use a ref transaction when dumping tags\n>       walker.c: use ref transaction for ref updates\n>       refs.c: make lock_ref_sha1 static\n>       refs.c: remove the update_ref_lock function\n>       refs.c: remove the update_ref_write function\n>       refs.c: remove lock_ref_sha1\n>       refs.c: make prune_ref use a transaction to delete the ref\n>       refs.c: make delete_ref use a transaction\n\nAnd here are the patches.\n"},{"id":"248358","messageId":"20140827002904.GB20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 01/20] refs.c: change ref_transaction_create to do error checking and return status","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:29:04Z","receivedAt":"2014-08-27T00:29:04Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 15:26:44 -0700\n\nDo basic error checking in ref_transaction_create() and make it return\nnon-zero on error. Update all callers to check the result of\nref_transaction_create(). There are currently no conditions in _create that\nwill return error but there will be in the future. Add an err argument that\nwill be updated on failure.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/update-ref.c |  4 +++-\n refs.c               | 18 ++++++++++++------\n refs.h               | 48 +++++++++++++++++++++++++++++++++++++++++-------\n 3 files changed, 56 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 3067b11..41121fa 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -226,7 +226,9 @@ static const char *parse_cmd_create(struct strbuf *input, const char *next)\n \tif (*next != line_termination)\n \t\tdie(\"create %s: extra input: %s\", refname, next);\n \n-\tref_transaction_create(transaction, refname, new_sha1, update_flags);\n+\tif (ref_transaction_create(transaction, refname, new_sha1,\n+\t\t\t\t   update_flags, &err))\n+\t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = 0;\n \tfree(refname);\ndiff --git a/refs.c b/refs.c\nindex 3f05e88..c49f1c6 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3449,18 +3449,24 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \treturn 0;\n }\n \n-void ref_transaction_create(struct ref_transaction *transaction,\n-\t\t\t    const char *refname,\n-\t\t\t    const unsigned char *new_sha1,\n-\t\t\t    int flags)\n+int ref_transaction_create(struct ref_transaction *transaction,\n+\t\t\t   const char *refname,\n+\t\t\t   const unsigned char *new_sha1,\n+\t\t\t   int flags,\n+\t\t\t   struct strbuf *err)\n {\n-\tstruct ref_update *update = add_update(transaction, refname);\n+\tstruct ref_update *update;\n+\n+\tif (!new_sha1 || is_null_sha1(new_sha1))\n+\t\tdie(\"BUG: create ref with null new_sha1\");\n+\n+\tupdate = add_update(transaction, refname);\n \n-\tassert(!is_null_sha1(new_sha1));\n \thashcpy(update->new_sha1, new_sha1);\n \thashclr(update->old_sha1);\n \tupdate->flags = flags;\n \tupdate->have_old = 1;\n+\treturn 0;\n }\n \n void ref_transaction_delete(struct ref_transaction *transaction,\ndiff --git a/refs.h b/refs.h\nindex c5376ce..b648819 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -10,6 +10,38 @@ struct ref_lock {\n \tint force_write;\n };\n \n+/*\n+ * A ref_transaction represents a collection of ref updates\n+ * that should succeed or fail together.\n+ *\n+ * Calling sequence\n+ * ----------------\n+ * - Allocate and initialize a `struct ref_transaction` by calling\n+ *   `ref_transaction_begin()`.\n+ *\n+ * - List intended ref updates by calling functions like\n+ *   `ref_transaction_update()` and `ref_transaction_create()`.\n+ *\n+ * - Call `ref_transaction_commit()` to execute the transaction.\n+ *   If this succeeds, the ref updates will have taken place and\n+ *   the transaction cannot be rolled back.\n+ *\n+ * - At any time call `ref_transaction_free()` to discard the\n+ *   transaction and free associated resources.  In particular,\n+ *   this rolls back the transaction if it has not been\n+ *   successfully committed.\n+ *\n+ * Error handling\n+ * --------------\n+ *\n+ * On error, transaction functions append a message about what\n+ * went wrong to the 'err' argument.  The message mentions what\n+ * ref was being updated (if any) when the error occurred so it\n+ * can be passed to 'die' or 'error' as-is.\n+ *\n+ * The message is appended to err without first clearing err.\n+ * err will not be '\\n' terminated.\n+ */\n struct ref_transaction;\n \n /*\n@@ -248,7 +280,7 @@ struct ref_transaction *ref_transaction_begin(void);\n  * it must not have existed beforehand.\n  * Function returns 0 on success and non-zero on failure. A failure to update\n  * means that the transaction as a whole has failed and will need to be\n- * rolled back. On failure the err buffer will be updated.\n+ * rolled back.\n  */\n int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n@@ -262,11 +294,15 @@ int ref_transaction_update(struct ref_transaction *transaction,\n  * that the reference should have after the update; it must not be the\n  * null SHA-1.  It is verified that the reference does not exist\n  * already.\n+ * Function returns 0 on success and non-zero on failure. A failure to create\n+ * means that the transaction as a whole has failed and will need to be\n+ * rolled back.\n  */\n-void ref_transaction_create(struct ref_transaction *transaction,\n-\t\t\t    const char *refname,\n-\t\t\t    const unsigned char *new_sha1,\n-\t\t\t    int flags);\n+int ref_transaction_create(struct ref_transaction *transaction,\n+\t\t\t   const char *refname,\n+\t\t\t   const unsigned char *new_sha1,\n+\t\t\t   int flags,\n+\t\t\t   struct strbuf *err);\n \n /*\n  * Add a reference deletion to transaction.  If have_old is true, then\n@@ -282,8 +318,6 @@ void ref_transaction_delete(struct ref_transaction *transaction,\n  * Commit all of the changes that have been queued in transaction, as\n  * atomically as possible.  Return a nonzero value if there is a\n  * problem.\n- * If err is non-NULL we will add an error string to it to explain why\n- * the transaction failed. The string does not end in newline.\n  */\n int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t   const char *msg, struct strbuf *err);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248359","messageId":"20140827002944.GC20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 02/20] refs.c: update ref_transaction_delete to check for error and return status","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:29:44Z","receivedAt":"2014-08-27T00:29:44Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 15:27:45 -0700\n\nChange ref_transaction_delete() to do basic error checking and return\nnon-zero of error. Update all callers to check the return for\nref_transaction_delete(). There are currently no conditions in _delete that\nwill return error but there will be in the future. Add an err argument that\nwill be updated on failure.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/update-ref.c |  5 +++--\n refs.c               | 16 +++++++++++-----\n refs.h               | 12 ++++++++----\n 3 files changed, 22 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 41121fa..7c9c248 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -258,8 +258,9 @@ static const char *parse_cmd_delete(struct strbuf *input, const char *next)\n \tif (*next != line_termination)\n \t\tdie(\"delete %s: extra input: %s\", refname, next);\n \n-\tref_transaction_delete(transaction, refname, old_sha1,\n-\t\t\t       update_flags, have_old);\n+\tif (ref_transaction_delete(transaction, refname, old_sha1,\n+\t\t\t\t   update_flags, have_old, &err))\n+\t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = 0;\n \tfree(refname);\ndiff --git a/refs.c b/refs.c\nindex c49f1c6..40f04f4 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3469,19 +3469,25 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \treturn 0;\n }\n \n-void ref_transaction_delete(struct ref_transaction *transaction,\n-\t\t\t    const char *refname,\n-\t\t\t    const unsigned char *old_sha1,\n-\t\t\t    int flags, int have_old)\n+int ref_transaction_delete(struct ref_transaction *transaction,\n+\t\t\t   const char *refname,\n+\t\t\t   const unsigned char *old_sha1,\n+\t\t\t   int flags, int have_old,\n+\t\t\t   struct strbuf *err)\n {\n-\tstruct ref_update *update = add_update(transaction, refname);\n+\tstruct ref_update *update;\n \n+\tif (have_old && !old_sha1)\n+\t\tdie(\"BUG: have_old is true but old_sha1 is NULL\");\n+\n+\tupdate = add_update(transaction, refname);\n \tupdate->flags = flags;\n \tupdate->have_old = have_old;\n \tif (have_old) {\n \t\tassert(!is_null_sha1(old_sha1));\n \t\thashcpy(update->old_sha1, old_sha1);\n \t}\n+\treturn 0;\n }\n \n int update_ref(const char *action, const char *refname,\ndiff --git a/refs.h b/refs.h\nindex b648819..71389a1 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -308,11 +308,15 @@ int ref_transaction_create(struct ref_transaction *transaction,\n  * Add a reference deletion to transaction.  If have_old is true, then\n  * old_sha1 holds the value that the reference should have had before\n  * the update (which must not be the null SHA-1).\n+ * Function returns 0 on success and non-zero on failure. A failure to delete\n+ * means that the transaction as a whole has failed and will need to be\n+ * rolled back.\n  */\n-void ref_transaction_delete(struct ref_transaction *transaction,\n-\t\t\t    const char *refname,\n-\t\t\t    const unsigned char *old_sha1,\n-\t\t\t    int flags, int have_old);\n+int ref_transaction_delete(struct ref_transaction *transaction,\n+\t\t\t   const char *refname,\n+\t\t\t   const unsigned char *old_sha1,\n+\t\t\t   int flags, int have_old,\n+\t\t\t   struct strbuf *err);\n \n /*\n  * Commit all of the changes that have been queued in transaction, as\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248361","messageId":"20140827003006.GD20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 03/20] refs.c: make ref_transaction_begin take an err argument","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:30:06Z","receivedAt":"2014-08-27T00:30:06Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Mon, 19 May 2014 10:42:34 -0700\n\nAdd an err argument to _begin so that on non-fatal failures in future ref\nbackends we can report a nice error back to the caller.\nWhile _begin can currently never fail for other reasons than OOM, in which\ncase we die() anyway, we may add other types of backends in the future.\nFor example, a hypothetical MySQL backend could fail in _begin with\n\"Can not connect to MySQL server. No route to host\".\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/update-ref.c | 5 ++++-\n refs.c               | 2 +-\n refs.h               | 2 +-\n 3 files changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 7c9c248..96a53b9 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -365,7 +365,9 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \t\tdie(\"Refusing to perform update with empty message.\");\n \n \tif (read_stdin) {\n-\t\ttransaction = ref_transaction_begin();\n+\t\ttransaction = ref_transaction_begin(&err);\n+\t\tif (!transaction)\n+\t\t\tdie(\"%s\", err.buf);\n \t\tif (delete || no_deref || argc > 0)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n \t\tif (end_null)\n@@ -374,6 +376,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \t\tif (ref_transaction_commit(transaction, msg, &err))\n \t\t\tdie(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n+\t\tstrbuf_release(&err);\n \t\treturn 0;\n \t}\n \ndiff --git a/refs.c b/refs.c\nindex 40f04f4..9cb7908 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3397,7 +3397,7 @@ struct ref_transaction {\n \tsize_t nr;\n };\n \n-struct ref_transaction *ref_transaction_begin(void)\n+struct ref_transaction *ref_transaction_begin(struct strbuf *err)\n {\n \treturn xcalloc(1, sizeof(struct ref_transaction));\n }\ndiff --git a/refs.h b/refs.h\nindex 71389a1..3f37c65 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -262,7 +262,7 @@ enum action_on_err {\n  * Begin a reference transaction.  The reference transaction must\n  * be freed by calling ref_transaction_free().\n  */\n-struct ref_transaction *ref_transaction_begin(void);\n+struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n \n /*\n  * The following functions add a reference check or update to a\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248362","messageId":"20140827003033.GE20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 04/20] refs.c: add transaction.status and track OPEN/CLOSED","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:30:33Z","receivedAt":"2014-08-27T00:30:33Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Tue, 29 Apr 2014 12:06:19 -0700\n\nTrack the state of a transaction in a new state field. Check the field for\nsanity, i.e. that state must be OPEN when _commit/_create/_delete or\n_update is called or else die(BUG:...)\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 34 +++++++++++++++++++++++++++++++++-\n 1 file changed, 33 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 9cb7908..cc63056 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3387,6 +3387,21 @@ struct ref_update {\n };\n \n /*\n+ * Transaction states.\n+ * OPEN:   The transaction is in a valid state and can accept new updates.\n+ *         An OPEN transaction can be committed.\n+ * CLOSED: A closed transaction is no longer active and no other operations\n+ *         than free can be used on it in this state.\n+ *         A transaction can either become closed by successfully committing\n+ *         an active transaction or if there is a failure while building\n+ *         the transaction thus rendering it failed/inactive.\n+ */\n+enum ref_transaction_state {\n+\tREF_TRANSACTION_OPEN   = 0,\n+\tREF_TRANSACTION_CLOSED = 1\n+};\n+\n+/*\n  * Data structure for holding a reference transaction, which can\n  * consist of checks and updates to multiple references, carried out\n  * as atomically as possible.  This structure is opaque to callers.\n@@ -3395,6 +3410,7 @@ struct ref_transaction {\n \tstruct ref_update **updates;\n \tsize_t alloc;\n \tsize_t nr;\n+\tenum ref_transaction_state state;\n };\n \n struct ref_transaction *ref_transaction_begin(struct strbuf *err)\n@@ -3437,6 +3453,9 @@ int ref_transaction_update(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tif (transaction->state != REF_TRANSACTION_OPEN)\n+\t\tdie(\"BUG: update called for transaction that is not open\");\n+\n \tif (have_old && !old_sha1)\n \t\tdie(\"BUG: have_old is true but old_sha1 is NULL\");\n \n@@ -3457,6 +3476,9 @@ int ref_transaction_create(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tif (transaction->state != REF_TRANSACTION_OPEN)\n+\t\tdie(\"BUG: create called for transaction that is not open\");\n+\n \tif (!new_sha1 || is_null_sha1(new_sha1))\n \t\tdie(\"BUG: create ref with null new_sha1\");\n \n@@ -3477,6 +3499,9 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tif (transaction->state != REF_TRANSACTION_OPEN)\n+\t\tdie(\"BUG: delete called for transaction that is not open\");\n+\n \tif (have_old && !old_sha1)\n \t\tdie(\"BUG: have_old is true but old_sha1 is NULL\");\n \n@@ -3532,8 +3557,13 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \tint n = transaction->nr;\n \tstruct ref_update **updates = transaction->updates;\n \n-\tif (!n)\n+\tif (transaction->state != REF_TRANSACTION_OPEN)\n+\t\tdie(\"BUG: commit called for transaction that is not open\");\n+\n+\tif (!n) {\n+\t\ttransaction->state = REF_TRANSACTION_CLOSED;\n \t\treturn 0;\n+\t}\n \n \t/* Allocate work space */\n \tdelnames = xmalloc(sizeof(*delnames) * n);\n@@ -3595,6 +3625,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \tclear_loose_ref_cache(&ref_cache);\n \n cleanup:\n+\ttransaction->state = REF_TRANSACTION_CLOSED;\n+\n \tfor (i = 0; i < n; i++)\n \t\tif (updates[i]->lock)\n \t\t\tunlock_ref(updates[i]->lock);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248363","messageId":"20140827003055.GF20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 05/20] tag.c: use ref transactions when doing updates","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:30:55Z","receivedAt":"2014-08-27T00:30:55Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 15:30:41 -0700\n\nChange tag.c to use ref transactions for all ref updates.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/tag.c | 16 ++++++++++------\n 1 file changed, 10 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex c6e8a71..f3f172f 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -548,7 +548,6 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct strbuf ref = STRBUF_INIT;\n \tunsigned char object[20], prev[20];\n \tconst char *object_ref, *tag;\n-\tstruct ref_lock *lock;\n \tstruct create_tag_options opt;\n \tchar *cleanup_arg = NULL;\n \tint annotate = 0, force = 0, lines = -1;\n@@ -556,6 +555,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tconst char *msgfile = NULL, *keyid = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n \tstruct commit_list *with_commit = NULL;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n \tstruct option options[] = {\n \t\tOPT_CMDMODE('l', \"list\", &cmdmode, N_(\"list tag names\"), 'l'),\n \t\t{ OPTION_INTEGER, 'n', NULL, &lines, N_(\"n\"),\n@@ -701,14 +702,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tif (annotate)\n \t\tcreate_tag(object, tag, &buf, &opt, prev, object);\n \n-\tlock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);\n-\tif (!lock)\n-\t\tdie(_(\"%s: cannot lock the ref\"), ref.buf);\n-\tif (write_ref_sha1(lock, object, NULL) < 0)\n-\t\tdie(_(\"%s: cannot update the ref\"), ref.buf);\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, ref.buf, object, prev,\n+\t\t\t\t   0, 1, &err) ||\n+\t    ref_transaction_commit(transaction, NULL, &err))\n+\t\tdie(\"%s\", err.buf);\n+\tref_transaction_free(transaction);\n \tif (force && !is_null_sha1(prev) && hashcmp(prev, object))\n \t\tprintf(_(\"Updated tag '%s' (was %s)\\n\"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV));\n \n+\tstrbuf_release(&err);\n \tstrbuf_release(&buf);\n \tstrbuf_release(&ref);\n \treturn 0;\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248364","messageId":"20140827003121.GG20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 06/20] replace.c: use the ref transaction functions for updates","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:31:21Z","receivedAt":"2014-08-27T00:31:21Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 15:32:29 -0700\n\nUpdate replace.c to use ref transactions for updates.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/replace.c | 14 ++++++++------\n 1 file changed, 8 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 1bb491d..1fcd06d 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -153,7 +153,8 @@ static int replace_object_sha1(const char *object_ref,\n \tunsigned char prev[20];\n \tenum object_type obj_type, repl_type;\n \tchar ref[PATH_MAX];\n-\tstruct ref_lock *lock;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n \n \tobj_type = sha1_object_info(object, NULL);\n \trepl_type = sha1_object_info(repl, NULL);\n@@ -166,12 +167,13 @@ static int replace_object_sha1(const char *object_ref,\n \n \tcheck_ref_valid(object, prev, ref, sizeof(ref), force);\n \n-\tlock = lock_any_ref_for_update(ref, prev, 0, NULL);\n-\tif (!lock)\n-\t\tdie(\"%s: cannot lock the ref\", ref);\n-\tif (write_ref_sha1(lock, repl, NULL) < 0)\n-\t\tdie(\"%s: cannot update the ref\", ref);\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, ref, repl, prev, 0, 1, &err) ||\n+\t    ref_transaction_commit(transaction, NULL, &err))\n+\t\tdie(\"%s\", err.buf);\n \n+\tref_transaction_free(transaction);\n \treturn 0;\n }\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248365","messageId":"20140827003140.GH20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 07/20] commit.c: use ref transactions for updates","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:31:40Z","receivedAt":"2014-08-27T00:31:40Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 15:34:19 -0700\n\nChange commit.c to use ref transactions for all ref updates.\nMake sure we pass a NULL pointer to ref_transaction_update if have_old\nis false.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/commit.c | 25 ++++++++++++-------------\n 1 file changed, 12 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 5e2221c..9bf1003 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1627,11 +1627,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tconst char *index_file, *reflog_msg;\n \tchar *nl;\n \tunsigned char sha1[20];\n-\tstruct ref_lock *ref_lock;\n \tstruct commit_list *parents = NULL, **pptr = &parents;\n \tstruct stat statbuf;\n \tstruct commit *current_head = NULL;\n \tstruct commit_extra_header *extra = NULL;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n \n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n \t\tusage_with_options(builtin_commit_usage, builtin_commit_options);\n@@ -1753,16 +1754,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&author_ident);\n \tfree_commit_extra_headers(extra);\n \n-\tref_lock = lock_any_ref_for_update(\"HEAD\",\n-\t\t\t\t\t   !current_head\n-\t\t\t\t\t   ? NULL\n-\t\t\t\t\t   : current_head->object.sha1,\n-\t\t\t\t\t   0, NULL);\n-\tif (!ref_lock) {\n-\t\trollback_index_files();\n-\t\tdie(_(\"cannot lock HEAD ref\"));\n-\t}\n-\n \tnl = strchr(sb.buf, '\\n');\n \tif (nl)\n \t\tstrbuf_setlen(&sb, nl + 1 - sb.buf);\n@@ -1771,10 +1762,17 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tstrbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));\n \tstrbuf_insert(&sb, strlen(reflog_msg), \": \", 2);\n \n-\tif (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) {\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, \"HEAD\", sha1,\n+\t\t\t\t   current_head\n+\t\t\t\t   ? current_head->object.sha1 : NULL,\n+\t\t\t\t   0, !!current_head, &err) ||\n+\t    ref_transaction_commit(transaction, sb.buf, &err)) {\n \t\trollback_index_files();\n-\t\tdie(_(\"cannot update HEAD ref\"));\n+\t\tdie(\"%s\", err.buf);\n \t}\n+\tref_transaction_free(transaction);\n \n \tunlink(git_path(\"CHERRY_PICK_HEAD\"));\n \tunlink(git_path(\"REVERT_HEAD\"));\n@@ -1803,5 +1801,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tif (!quiet)\n \t\tprint_summary(prefix, sha1, !current_head);\n \n+\tstrbuf_release(&err);\n \treturn 0;\n }\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248366","messageId":"20140827003201.GI20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 08/20] sequencer.c: use ref transactions for all ref updates","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:32:01Z","receivedAt":"2014-08-27T00:32:01Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 15:37:45 -0700\n\nChange to use ref transactions for all updates to refs.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n sequencer.c | 26 ++++++++++++++++++--------\n 1 file changed, 18 insertions(+), 8 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 0a80c58..5e93b6a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -272,23 +272,33 @@ static int error_dirty_index(struct replay_opts *opts)\n static int fast_forward_to(const unsigned char *to, const unsigned char *from,\n \t\t\tint unborn, struct replay_opts *opts)\n {\n-\tstruct ref_lock *ref_lock;\n+\tstruct ref_transaction *transaction;\n \tstruct strbuf sb = STRBUF_INIT;\n-\tint ret;\n+\tstruct strbuf err = STRBUF_INIT;\n \n \tread_cache();\n \tif (checkout_fast_forward(from, to, 1))\n \t\texit(1); /* the callee should have complained already */\n-\tref_lock = lock_any_ref_for_update(\"HEAD\", unborn ? null_sha1 : from,\n-\t\t\t\t\t   0, NULL);\n-\tif (!ref_lock)\n-\t\treturn error(_(\"Failed to lock HEAD during fast_forward_to\"));\n \n \tstrbuf_addf(&sb, \"%s: fast-forward\", action_name(opts));\n-\tret = write_ref_sha1(ref_lock, to, sb.buf);\n+\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, \"HEAD\",\n+\t\t\t\t   to, unborn ? null_sha1 : from,\n+\t\t\t\t   0, 1, &err) ||\n+\t    ref_transaction_commit(transaction, sb.buf, &err)) {\n+\t\tref_transaction_free(transaction);\n+\t\terror(\"%s\", err.buf);\n+\t\tstrbuf_release(&sb);\n+\t\tstrbuf_release(&err);\n+\t\treturn -1;\n+\t}\n \n \tstrbuf_release(&sb);\n-\treturn ret;\n+\tstrbuf_release(&err);\n+\tref_transaction_free(transaction);\n+\treturn 0;\n }\n \n static int do_recursive_merge(struct commit *base, struct commit *next,\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248367","messageId":"20140827003225.GJ20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 09/20] fast-import.c: change update_branch to use ref transactions","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:32:26Z","receivedAt":"2014-08-27T00:32:26Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 16:21:13 -0700\n\nChange update_branch() to use ref transactions for updates.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n fast-import.c | 25 +++++++++++++++----------\n 1 file changed, 15 insertions(+), 10 deletions(-)\n\ndiff --git a/fast-import.c b/fast-import.c\nindex 6707a66..79160d5 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1679,8 +1679,9 @@ found_entry:\n static int update_branch(struct branch *b)\n {\n \tstatic const char *msg = \"fast-import\";\n-\tstruct ref_lock *lock;\n+\tstruct ref_transaction *transaction;\n \tunsigned char old_sha1[20];\n+\tstruct strbuf err = STRBUF_INIT;\n \n \tif (read_ref(b->name, old_sha1))\n \t\thashclr(old_sha1);\n@@ -1689,29 +1690,33 @@ static int update_branch(struct branch *b)\n \t\t\tdelete_ref(b->name, old_sha1, 0);\n \t\treturn 0;\n \t}\n-\tlock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL);\n-\tif (!lock)\n-\t\treturn error(\"Unable to lock %s\", b->name);\n \tif (!force_update && !is_null_sha1(old_sha1)) {\n \t\tstruct commit *old_cmit, *new_cmit;\n \n \t\told_cmit = lookup_commit_reference_gently(old_sha1, 0);\n \t\tnew_cmit = lookup_commit_reference_gently(b->sha1, 0);\n-\t\tif (!old_cmit || !new_cmit) {\n-\t\t\tunlock_ref(lock);\n+\t\tif (!old_cmit || !new_cmit)\n \t\t\treturn error(\"Branch %s is missing commits.\", b->name);\n-\t\t}\n \n \t\tif (!in_merge_bases(old_cmit, new_cmit)) {\n-\t\t\tunlock_ref(lock);\n \t\t\twarning(\"Not updating %s\"\n \t\t\t\t\" (new tip %s does not contain %s)\",\n \t\t\t\tb->name, sha1_to_hex(b->sha1), sha1_to_hex(old_sha1));\n \t\t\treturn -1;\n \t\t}\n \t}\n-\tif (write_ref_sha1(lock, b->sha1, msg) < 0)\n-\t\treturn error(\"Unable to update %s\", b->name);\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, b->name, b->sha1, old_sha1,\n+\t\t\t\t   0, 1, &err) ||\n+\t    ref_transaction_commit(transaction, msg, &err)) {\n+\t\tref_transaction_free(transaction);\n+\t\terror(\"%s\", err.buf);\n+\t\tstrbuf_release(&err);\n+\t\treturn -1;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n \treturn 0;\n }\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248368","messageId":"20140827003242.GK20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 10/20] branch.c: use ref transaction for all ref updates","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:32:42Z","receivedAt":"2014-08-27T00:32:42Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 16:21:53 -0700\n\nChange create_branch to use a ref transaction when creating the new branch.\n\nThis also fixes a race condition in the old code where two concurrent\ncreate_branch could race since the lock_any_ref_for_update/write_ref_sha1\ndid not protect against the ref already existing. I.e. one thread could end up\noverwriting a branch even if the forcing flag is false.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n branch.c | 31 +++++++++++++++++--------------\n 1 file changed, 17 insertions(+), 14 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 660097b..37ac555 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -226,7 +226,6 @@ void create_branch(const char *head,\n \t\t   int force, int reflog, int clobber_head,\n \t\t   int quiet, enum branch_track track)\n {\n-\tstruct ref_lock *lock = NULL;\n \tstruct commit *commit;\n \tunsigned char sha1[20];\n \tchar *real_ref, msg[PATH_MAX + 20];\n@@ -285,15 +284,6 @@ void create_branch(const char *head,\n \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n \thashcpy(sha1, commit->object.sha1);\n \n-\tif (!dont_change_ref) {\n-\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL);\n-\t\tif (!lock)\n-\t\t\tdie_errno(_(\"Failed to lock ref for update\"));\n-\t}\n-\n-\tif (reflog)\n-\t\tlog_all_ref_updates = 1;\n-\n \tif (forcing)\n \t\tsnprintf(msg, sizeof msg, \"branch: Reset to %s\",\n \t\t\t start_name);\n@@ -301,13 +291,26 @@ void create_branch(const char *head,\n \t\tsnprintf(msg, sizeof msg, \"branch: Created from %s\",\n \t\t\t start_name);\n \n+\tif (reflog)\n+\t\tlog_all_ref_updates = 1;\n+\n+\tif (!dont_change_ref) {\n+\t\tstruct ref_transaction *transaction;\n+\t\tstruct strbuf err = STRBUF_INIT;\n+\n+\t\ttransaction = ref_transaction_begin(&err);\n+\t\tif (!transaction ||\n+\t\t    ref_transaction_update(transaction, ref.buf, sha1,\n+\t\t\t\t\t   null_sha1, 0, !forcing, &err) ||\n+\t\t    ref_transaction_commit(transaction, msg, &err))\n+\t\t\tdie(\"%s\", err.buf);\n+\t\tref_transaction_free(transaction);\n+\t\tstrbuf_release(&err);\n+\t}\n+\n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\n \n-\tif (!dont_change_ref)\n-\t\tif (write_ref_sha1(lock, sha1, msg) < 0)\n-\t\t\tdie_errno(_(\"Failed to write ref\"));\n-\n \tstrbuf_release(&ref);\n \tfree(real_ref);\n }\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248369","messageId":"20140827003302.GL20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 11/20] refs.c: change update_ref to use a transaction","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:33:03Z","receivedAt":"2014-08-27T00:33:03Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 24 Apr 2014 16:36:55 -0700\n\nChange the update_ref helper function to use a ref transaction internally.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 29 +++++++++++++++++++++++++----\n 1 file changed, 25 insertions(+), 4 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex cc63056..dcc877b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3519,11 +3519,32 @@ int update_ref(const char *action, const char *refname,\n \t       const unsigned char *sha1, const unsigned char *oldval,\n \t       int flags, enum action_on_err onerr)\n {\n-\tstruct ref_lock *lock;\n-\tlock = update_ref_lock(refname, oldval, flags, NULL, onerr);\n-\tif (!lock)\n+\tstruct ref_transaction *t;\n+\tstruct strbuf err = STRBUF_INIT;\n+\n+\tt = ref_transaction_begin(&err);\n+\tif (!t ||\n+\t    ref_transaction_update(t, refname, sha1, oldval, flags,\n+\t\t\t\t   !!oldval, &err) ||\n+\t    ref_transaction_commit(t, action, &err)) {\n+\t\tconst char *str = \"update_ref failed for ref '%s': %s\";\n+\n+\t\tref_transaction_free(t);\n+\t\tswitch (onerr) {\n+\t\tcase UPDATE_REFS_MSG_ON_ERR:\n+\t\t\terror(str, refname, err.buf);\n+\t\t\tbreak;\n+\t\tcase UPDATE_REFS_DIE_ON_ERR:\n+\t\t\tdie(str, refname, err.buf);\n+\t\t\tbreak;\n+\t\tcase UPDATE_REFS_QUIET_ON_ERR:\n+\t\t\tbreak;\n+\t\t}\n+\t\tstrbuf_release(&err);\n \t\treturn 1;\n-\treturn update_ref_write(action, refname, sha1, lock, NULL, onerr);\n+\t}\n+\tstrbuf_release(&err);\n+\treturn 0;\n }\n \n static int ref_update_compare(const void *r1, const void *r2)\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248370","messageId":"20140827003328.GM20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 12/20] receive-pack.c: use a reference transaction for updating the refs","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:33:28Z","receivedAt":"2014-08-27T00:33:28Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Mon, 28 Apr 2014 14:36:15 -0700\n\nWrap all the ref updates inside a transaction.\n\nIn the new API there is no distinction between failure to lock and\nfailure to write a ref.  Both can be permanent (e.g., a ref\n\"refs/heads/topic\" is blocking creation of the lock file\n\"refs/heads/topic/1.lock\") or transient (e.g., file system full) and\nthere's no clear difference in how the client should respond, so\nreplace the two statuses \"failed to lock\" and \"failed to write\" with\na single status \"failed to update ref\".  In both cases a more\ndetailed message is sent by sideband to diagnose the problem.\n\nExample, before:\n\n error: there are still refs under 'refs/heads/topic'\n remote: error: failed to lock refs/heads/topic\n To foo\n  ! [remote rejected] HEAD -> topic (failed to lock)\n\nAfter:\n\n error: there are still refs under 'refs/heads/topic'\n remote: error: Cannot lock the ref 'refs/heads/topic'.\n To foo\n  ! [remote rejected] HEAD -> topic (failed to update ref)\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/receive-pack.c | 25 ++++++++++++++++---------\n 1 file changed, 16 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex c323081..224fadc 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -475,7 +475,6 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \tconst char *namespaced_name;\n \tunsigned char *old_sha1 = cmd->old_sha1;\n \tunsigned char *new_sha1 = cmd->new_sha1;\n-\tstruct ref_lock *lock;\n \n \t/* only refs/... are allowed */\n \tif (!starts_with(name, \"refs/\") || check_refname_format(name + 5, 0)) {\n@@ -576,19 +575,27 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\treturn NULL; /* good */\n \t}\n \telse {\n+\t\tstruct strbuf err = STRBUF_INIT;\n+\t\tstruct ref_transaction *transaction;\n+\n \t\tif (shallow_update && si->shallow_ref[cmd->index] &&\n \t\t    update_shallow_ref(cmd, si))\n \t\t\treturn \"shallow error\";\n \n-\t\tlock = lock_any_ref_for_update(namespaced_name, old_sha1,\n-\t\t\t\t\t       0, NULL);\n-\t\tif (!lock) {\n-\t\t\trp_error(\"failed to lock %s\", name);\n-\t\t\treturn \"failed to lock\";\n-\t\t}\n-\t\tif (write_ref_sha1(lock, new_sha1, \"push\")) {\n-\t\t\treturn \"failed to write\"; /* error() already called */\n+\t\ttransaction = ref_transaction_begin(&err);\n+\t\tif (!transaction ||\n+\t\t    ref_transaction_update(transaction, namespaced_name,\n+\t\t\t\t\t   new_sha1, old_sha1, 0, 1, &err) ||\n+\t\t    ref_transaction_commit(transaction, \"push\", &err)) {\n+\t\t\tref_transaction_free(transaction);\n+\n+\t\t\trp_error(\"%s\", err.buf);\n+\t\t\tstrbuf_release(&err);\n+\t\t\treturn \"failed to update ref\";\n \t\t}\n+\n+\t\tref_transaction_free(transaction);\n+\t\tstrbuf_release(&err);\n \t\treturn NULL; /* good */\n \t}\n }\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248371","messageId":"20140827003349.GN20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 13/20] fast-import.c: use a ref transaction when dumping tags","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:33:49Z","receivedAt":"2014-08-27T00:33:49Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Mon, 28 Apr 2014 15:23:58 -0700\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n fast-import.c | 29 +++++++++++++++++++++++------\n 1 file changed, 23 insertions(+), 6 deletions(-)\n\ndiff --git a/fast-import.c b/fast-import.c\nindex 79160d5..e7f6e37 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1735,15 +1735,32 @@ static void dump_tags(void)\n {\n \tstatic const char *msg = \"fast-import\";\n \tstruct tag *t;\n-\tstruct ref_lock *lock;\n-\tchar ref_name[PATH_MAX];\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tstruct ref_transaction *transaction;\n \n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction) {\n+\t\tfailure |= error(\"%s\", err.buf);\n+\t\tgoto cleanup;\n+\t}\n \tfor (t = first_tag; t; t = t->next_tag) {\n-\t\tsprintf(ref_name, \"tags/%s\", t->name);\n-\t\tlock = lock_ref_sha1(ref_name, NULL);\n-\t\tif (!lock || write_ref_sha1(lock, t->sha1, msg) < 0)\n-\t\t\tfailure |= error(\"Unable to update %s\", ref_name);\n+\t\tstrbuf_reset(&ref_name);\n+\t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n+\n+\t\tif (ref_transaction_update(transaction, ref_name.buf, t->sha1,\n+\t\t\t\t\t   NULL, 0, 0, &err)) {\n+\t\t\tfailure |= error(\"%s\", err.buf);\n+\t\t\tgoto cleanup;\n+\t\t}\n \t}\n+\tif (ref_transaction_commit(transaction, msg, &err))\n+\t\tfailure |= error(\"%s\", err.buf);\n+\n+ cleanup:\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&ref_name);\n+\tstrbuf_release(&err);\n }\n \n static void dump_marks_helper(FILE *f,\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248372","messageId":"20140827003424.GO20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 14/20] walker.c: use ref transaction for ref updates","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:34:24Z","receivedAt":"2014-08-27T00:34:24Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 17 Apr 2014 11:31:06 -0700\n\nSwitch to using ref transactions in walker_fetch(). As part of the refactoring\nto use ref transactions we also fix a potential memory leak where in the\noriginal code if write_ref_sha1() would fail we would end up returning from\nthe function without free()ing the msg string.\n\nNote that this function is only called when fetching from a remote HTTP\nrepository onto the local (most of the time single-user) repository which\nlikely means that the type of collisions that the previous locking would\nprotect against and cause the fetch to fail for are even more rare.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n walker.c | 72 ++++++++++++++++++++++++++++++++++++----------------------------\n 1 file changed, 41 insertions(+), 31 deletions(-)\n\ndiff --git a/walker.c b/walker.c\nindex 1dd86b8..b8a5441 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -251,40 +251,40 @@ void walker_targets_free(int targets, char **target, const char **write_ref)\n int walker_fetch(struct walker *walker, int targets, char **target,\n \t\t const char **write_ref, const char *write_ref_log_details)\n {\n-\tstruct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *));\n+\tstruct strbuf refname = STRBUF_INIT;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tstruct ref_transaction *transaction = NULL;\n \tunsigned char *sha1 = xmalloc(targets * 20);\n-\tchar *msg;\n-\tint ret;\n-\tint i;\n+\tchar *msg = NULL;\n+\tint i, ret = -1;\n \n \tsave_commit_buffer = 0;\n \n-\tfor (i = 0; i < targets; i++) {\n-\t\tif (!write_ref || !write_ref[i])\n-\t\t\tcontinue;\n-\n-\t\tlock[i] = lock_ref_sha1(write_ref[i], NULL);\n-\t\tif (!lock[i]) {\n-\t\t\terror(\"Can't lock ref %s\", write_ref[i]);\n-\t\t\tgoto unlock_and_fail;\n+\tif (write_ref) {\n+\t\ttransaction = ref_transaction_begin(&err);\n+\t\tif (!transaction) {\n+\t\t\terror(\"%s\", err.buf);\n+\t\t\tgoto done;\n \t\t}\n \t}\n-\n \tif (!walker->get_recover)\n \t\tfor_each_ref(mark_complete, NULL);\n \n \tfor (i = 0; i < targets; i++) {\n \t\tif (interpret_target(walker, target[i], &sha1[20 * i])) {\n \t\t\terror(\"Could not interpret response from server '%s' as something to pull\", target[i]);\n-\t\t\tgoto unlock_and_fail;\n+\t\t\tgoto done;\n \t\t}\n \t\tif (process(walker, lookup_unknown_object(&sha1[20 * i])))\n-\t\t\tgoto unlock_and_fail;\n+\t\t\tgoto done;\n \t}\n \n \tif (loop(walker))\n-\t\tgoto unlock_and_fail;\n-\n+\t\tgoto done;\n+\tif (!write_ref) {\n+\t\tret = 0;\n+\t\tgoto done;\n+\t}\n \tif (write_ref_log_details) {\n \t\tmsg = xmalloc(strlen(write_ref_log_details) + 12);\n \t\tsprintf(msg, \"fetch from %s\", write_ref_log_details);\n@@ -292,23 +292,33 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\tmsg = NULL;\n \t}\n \tfor (i = 0; i < targets; i++) {\n-\t\tif (!write_ref || !write_ref[i])\n+\t\tif (!write_ref[i])\n \t\t\tcontinue;\n-\t\tret = write_ref_sha1(lock[i], &sha1[20 * i], msg ? msg : \"fetch (unknown)\");\n-\t\tlock[i] = NULL;\n-\t\tif (ret)\n-\t\t\tgoto unlock_and_fail;\n+\t\tstrbuf_reset(&refname);\n+\t\tstrbuf_addf(&refname, \"refs/%s\", write_ref[i]);\n+\t\tif (ref_transaction_update(transaction, refname.buf,\n+\t\t\t\t\t   &sha1[20 * i], NULL, 0, 0,\n+\t\t\t\t\t   &err)) {\n+\t\t\terror(\"%s\", err.buf);\n+\t\t\tgoto done;\n+\t\t}\n \t}\n+\tif (ref_transaction_commit(transaction,\n+\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n+\t\t\t\t   &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tgoto done;\n+\t}\n+\n+\tret = 0;\n+\n+done:\n+\tref_transaction_free(transaction);\n \tfree(msg);\n-\n-\treturn 0;\n-\n-unlock_and_fail:\n-\tfor (i = 0; i < targets; i++)\n-\t\tif (lock[i])\n-\t\t\tunlock_ref(lock[i]);\n-\n-\treturn -1;\n+\tfree(sha1);\n+\tstrbuf_release(&err);\n+\tstrbuf_release(&refname);\n+\treturn ret;\n }\n \n void walker_free(struct walker *walker)\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248373","messageId":"20140827003441.GP20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 15/20] refs.c: make lock_ref_sha1 static","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:34:41Z","receivedAt":"2014-08-27T00:34:41Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Mon, 28 Apr 2014 15:38:47 -0700\n\nNo external callers reference lock_ref_sha1 any more so let's declare it\nstatic.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 7 +++++--\n refs.h | 6 ------\n 2 files changed, 5 insertions(+), 8 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex dcc877b..0dc093c 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2069,7 +2069,10 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n \treturn logs_found;\n }\n \n-/* This function should make sure errno is meaningful on error */\n+/*\n+ * Locks a \"refs/\" ref returning the lock on success and NULL on failure.\n+ * On failure errno is set to something meaningful.\n+ */\n static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\t\t\t    const unsigned char *old_sha1,\n \t\t\t\t\t    int flags, int *type_p)\n@@ -2170,7 +2173,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \treturn NULL;\n }\n \n-struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)\n+static struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)\n {\n \tchar refpath[PATH_MAX];\n \tif (check_refname_format(refname, 0))\ndiff --git a/refs.h b/refs.h\nindex 3f37c65..65dd593 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -170,12 +170,6 @@ extern int ref_exists(const char *);\n  */\n extern int peel_ref(const char *refname, unsigned char *sha1);\n \n-/*\n- * Locks a \"refs/\" ref returning the lock on success and NULL on failure.\n- * On failure errno is set to something meaningful.\n- */\n-extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1);\n-\n /** Locks any ref (for 'HEAD' type refs). */\n #define REF_NODEREF\t0x01\n /* errno is set to something meaningful on failure */\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248374","messageId":"20140827003500.GQ20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 16/20] refs.c: remove the update_ref_lock function","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:35:00Z","receivedAt":"2014-08-27T00:35:00Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Tue, 29 Apr 2014 12:14:47 -0700\n\nSince we now only call update_ref_lock with onerr==QUIET_ON_ERR we no longer\nneed this function and can replace it with just calling lock_any_ref_for_update\ndirectly.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 30 ++++++------------------------\n 1 file changed, 6 insertions(+), 24 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 0dc093c..de07791 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3336,24 +3336,6 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)\n \treturn retval;\n }\n \n-static struct ref_lock *update_ref_lock(const char *refname,\n-\t\t\t\t\tconst unsigned char *oldval,\n-\t\t\t\t\tint flags, int *type_p,\n-\t\t\t\t\tenum action_on_err onerr)\n-{\n-\tstruct ref_lock *lock;\n-\tlock = lock_any_ref_for_update(refname, oldval, flags, type_p);\n-\tif (!lock) {\n-\t\tconst char *str = \"Cannot lock the ref '%s'.\";\n-\t\tswitch (onerr) {\n-\t\tcase UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;\n-\t\tcase UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;\n-\t\tcase UPDATE_REFS_QUIET_ON_ERR: break;\n-\t\t}\n-\t}\n-\treturn lock;\n-}\n-\n static int update_ref_write(const char *action, const char *refname,\n \t\t\t    const unsigned char *sha1, struct ref_lock *lock,\n \t\t\t    struct strbuf *err, enum action_on_err onerr)\n@@ -3602,12 +3584,12 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n \n-\t\tupdate->lock = update_ref_lock(update->refname,\n-\t\t\t\t\t       (update->have_old ?\n-\t\t\t\t\t\tupdate->old_sha1 : NULL),\n-\t\t\t\t\t       update->flags,\n-\t\t\t\t\t       &update->type,\n-\t\t\t\t\t       UPDATE_REFS_QUIET_ON_ERR);\n+\t\tupdate->lock = lock_any_ref_for_update(update->refname,\n+\t\t\t\t\t\t       (update->have_old ?\n+\t\t\t\t\t\t\tupdate->old_sha1 :\n+\t\t\t\t\t\t\tNULL),\n+\t\t\t\t\t\t       update->flags,\n+\t\t\t\t\t\t       &update->type);\n \t\tif (!update->lock) {\n \t\t\tif (err)\n \t\t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248375","messageId":"20140827003520.GR20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 17/20] refs.c: remove the update_ref_write function","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:35:20Z","receivedAt":"2014-08-27T00:35:20Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Tue, 29 Apr 2014 13:42:07 -0700\n\nSince we only call update_ref_write from a single place and we only call it\nwith onerr==QUIET_ON_ERR we can just as well get rid of it and just call\nwrite_ref_sha1 directly. This changes the return status for _commit from\n1 to -1 on failures when writing to the ref. Eventually we will want\n_commit to start returning more detailed error conditions than the current\nsimple success/failure.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 34 ++++++++--------------------------\n 1 file changed, 8 insertions(+), 26 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex de07791..ef7660a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3336,25 +3336,6 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)\n \treturn retval;\n }\n \n-static int update_ref_write(const char *action, const char *refname,\n-\t\t\t    const unsigned char *sha1, struct ref_lock *lock,\n-\t\t\t    struct strbuf *err, enum action_on_err onerr)\n-{\n-\tif (write_ref_sha1(lock, sha1, action) < 0) {\n-\t\tconst char *str = \"Cannot update the ref '%s'.\";\n-\t\tif (err)\n-\t\t\tstrbuf_addf(err, str, refname);\n-\n-\t\tswitch (onerr) {\n-\t\tcase UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;\n-\t\tcase UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;\n-\t\tcase UPDATE_REFS_QUIET_ON_ERR: break;\n-\t\t}\n-\t\treturn 1;\n-\t}\n-\treturn 0;\n-}\n-\n /**\n  * Information needed for a single ref update.  Set new_sha1 to the\n  * new value or to zero to delete the ref.  To check the old value\n@@ -3604,14 +3585,15 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (!is_null_sha1(update->new_sha1)) {\n-\t\t\tret = update_ref_write(msg,\n-\t\t\t\t\t       update->refname,\n-\t\t\t\t\t       update->new_sha1,\n-\t\t\t\t\t       update->lock, err,\n-\t\t\t\t\t       UPDATE_REFS_QUIET_ON_ERR);\n-\t\t\tupdate->lock = NULL; /* freed by update_ref_write */\n-\t\t\tif (ret)\n+\t\t\tret = write_ref_sha1(update->lock, update->new_sha1,\n+\t\t\t\t\t     msg);\n+\t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n+\t\t\tif (ret) {\n+\t\t\t\tif (err)\n+\t\t\t\t\tstrbuf_addf(err, \"Cannot update the ref '%s'.\",\n+\t\t\t\t\t\t    update->refname);\n \t\t\t\tgoto cleanup;\n+\t\t\t}\n \t\t}\n \t}\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248376","messageId":"20140827003538.GS20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 18/20] refs.c: remove lock_ref_sha1","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:35:38Z","receivedAt":"2014-08-27T00:35:38Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Tue, 29 Apr 2014 15:45:52 -0700\n\nlock_ref_sha1 was only called from one place in refs.c and only provided\na check that the refname was sane before adding back the initial \"refs/\"\npart of the ref path name, the initial \"refs/\" that this caller had already\nstripped off before calling lock_ref_sha1.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 15 +++++----------\n 1 file changed, 5 insertions(+), 10 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex ef7660a..f0883d0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2173,15 +2173,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \treturn NULL;\n }\n \n-static struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)\n-{\n-\tchar refpath[PATH_MAX];\n-\tif (check_refname_format(refname, 0))\n-\t\treturn NULL;\n-\tstrcpy(refpath, mkpath(\"refs/%s\", refname));\n-\treturn lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);\n-}\n-\n struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t const unsigned char *old_sha1,\n \t\t\t\t\t int flags, int *type_p)\n@@ -2391,8 +2382,12 @@ static void try_remove_empty_parents(char *name)\n /* make sure nobody touched the ref, and unlink */\n static void prune_ref(struct ref_to_prune *r)\n {\n-\tstruct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);\n+\tstruct ref_lock *lock;\n \n+\tif (check_refname_format(r->name + 5, 0))\n+\t\treturn;\n+\n+\tlock = lock_ref_sha1_basic(r->name, r->sha1, 0, NULL);\n \tif (lock) {\n \t\tunlink_or_warn(git_path(\"%s\", r->name));\n \t\tunlock_ref(lock);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248377","messageId":"20140827003610.GT20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 19/20] refs.c: make prune_ref use a transaction to delete the ref","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:36:10Z","receivedAt":"2014-08-27T00:36:10Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 30 Apr 2014 09:03:36 -0700\n\nChange prune_ref to delete the ref using a ref transaction. To do this we also\nneed to add a new flag REF_ISPRUNING that will tell the transaction that we\ndo not want to delete this ref from the packed refs. This flag is private to\nrefs.c and not exposed to external callers.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 28 +++++++++++++++++++++-------\n refs.h | 13 +++++++++++--\n 2 files changed, 32 insertions(+), 9 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex f0883d0..5b2d335 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -25,6 +25,11 @@ static unsigned char refname_disposition[256] = {\n };\n \n /*\n+ * Used as a flag to ref_transaction_delete when a loose ref is being\n+ * pruned.\n+ */\n+#define REF_ISPRUNING\t0x0100\n+/*\n  * Try to read one refname component from the front of refname.\n  * Return the length of the component found, or -1 if the component is\n  * not legal.  It is legal if it is something reasonable to have under\n@@ -2382,17 +2387,25 @@ static void try_remove_empty_parents(char *name)\n /* make sure nobody touched the ref, and unlink */\n static void prune_ref(struct ref_to_prune *r)\n {\n-\tstruct ref_lock *lock;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n \n \tif (check_refname_format(r->name + 5, 0))\n \t\treturn;\n \n-\tlock = lock_ref_sha1_basic(r->name, r->sha1, 0, NULL);\n-\tif (lock) {\n-\t\tunlink_or_warn(git_path(\"%s\", r->name));\n-\t\tunlock_ref(lock);\n-\t\ttry_remove_empty_parents(r->name);\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_delete(transaction, r->name, r->sha1,\n+\t\t\t\t   REF_ISPRUNING, 1, &err) ||\n+\t    ref_transaction_commit(transaction, NULL, &err)) {\n+\t\tref_transaction_free(transaction);\n+\t\terror(\"%s\", err.buf);\n+\t\tstrbuf_release(&err);\n+\t\treturn;\n \t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\ttry_remove_empty_parents(r->name);\n }\n \n static void prune_refs(struct ref_to_prune *r)\n@@ -3597,8 +3610,9 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (update->lock) {\n-\t\t\tdelnames[delnum++] = update->lock->ref_name;\n \t\t\tret |= delete_ref_loose(update->lock, update->type);\n+\t\t\tif (!(update->flags & REF_ISPRUNING))\n+\t\t\t\tdelnames[delnum++] = update->lock->ref_name;\n \t\t}\n \t}\n \ndiff --git a/refs.h b/refs.h\nindex 65dd593..69ef28c 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -170,9 +170,18 @@ extern int ref_exists(const char *);\n  */\n extern int peel_ref(const char *refname, unsigned char *sha1);\n \n-/** Locks any ref (for 'HEAD' type refs). */\n+/*\n+ * Flags controlling lock_any_ref_for_update(), ref_transaction_update(),\n+ * ref_transaction_create(), etc.\n+ * REF_NODEREF: act on the ref directly, instead of dereferencing\n+ *              symbolic references.\n+ *\n+ * Flags >= 0x100 are reserved for internal use.\n+ */\n #define REF_NODEREF\t0x01\n-/* errno is set to something meaningful on failure */\n+/*\n+ * This function sets errno to something meaningful on failure.\n+ */\n extern struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t\tconst unsigned char *old_sha1,\n \t\t\t\t\t\tint flags, int *type_p);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248378","messageId":"20140827003638.GU20185@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH 20/20] refs.c: make delete_ref use a transaction","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-08-27T00:36:38Z","receivedAt":"2014-08-27T00:36:38Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 30 Apr 2014 09:22:45 -0700\n\nChange delete_ref to use a ref transaction for the deletion. At the same time\nsince we no longer have any callers of repack_without_ref we can now delete\nthis function.\n\nChange delete_ref to return 0 on success and 1 on failure instead of the\nprevious 0 on success either 1 or -1 on failure.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nThanks for reading.\n\n refs.c | 35 ++++++++++++++---------------------\n 1 file changed, 14 insertions(+), 21 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5b2d335..7996be9 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2548,11 +2548,6 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \treturn ret;\n }\n \n-static int repack_without_ref(const char *refname)\n-{\n-\treturn repack_without_refs(&refname, 1, NULL);\n-}\n-\n static int delete_ref_loose(struct ref_lock *lock, int flag)\n {\n \tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n@@ -2570,24 +2565,22 @@ static int delete_ref_loose(struct ref_lock *lock, int flag)\n \n int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n {\n-\tstruct ref_lock *lock;\n-\tint ret = 0, flag = 0;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n \n-\tlock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);\n-\tif (!lock)\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_delete(transaction, refname, sha1, delopt,\n+\t\t\t\t   sha1 && !is_null_sha1(sha1), &err) ||\n+\t    ref_transaction_commit(transaction, NULL, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tref_transaction_free(transaction);\n+\t\tstrbuf_release(&err);\n \t\treturn 1;\n-\tret |= delete_ref_loose(lock, flag);\n-\n-\t/* removing the loose one could have resurrected an earlier\n-\t * packed one.  Also, if it was not loose we need to repack\n-\t * without it.\n-\t */\n-\tret |= repack_without_ref(lock->ref_name);\n-\n-\tunlink_or_warn(git_path(\"logs/%s\", lock->ref_name));\n-\tclear_loose_ref_cache(&ref_cache);\n-\tunlock_ref(lock);\n-\treturn ret;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\treturn 0;\n }\n \n /*\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248448","messageId":"xmqq1ts1d3jx.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"Re: [PATCH 0/20] rs/ref-transaction-1 (Re: Transaction patch series overview)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-08-27T21:29:38Z","receivedAt":"2014-08-27T21:29:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> Jonathan Nieder wrote:\n>\n>> This series teaches the tag, replace, commit, cherry-pick,\n>> fast-import, checkout -b, branch, receive-pack, and http-fetch\n>> commands and all update_ref and delete_ref callers to use the ref\n>> transaction API instead of lock_ref_sha1.\n>>\n>> The main user-visible change should be some cosmetic changes to error\n>> messages.  The series also combines multiple ref updates into a single\n>> transaction in 'git http-fetch -w' and when writing tags in\n>> fast-import but otherwise doesn't change the granularity of\n>> transactions.\n>>\n>> Reviewed at https://code-review.googlesource.com/#/q/topic:ref-transaction-1\n\nThanks.\n\nWill queue, but the some other topics may have to disappear from my\ntree while I try to rebase them on top (or while I wait for you guys\nto send a reroll, making it unnecesary to do the rebase myself).\n"},{"id":"248450","messageId":"53FE5359.4030403@alum.mit.edu","threadId":"37248","inReplyTo":"20140826000354.GW20185@google.com","subject":"Using Gerrit to review Git patches (was: Re: Transaction patch series overview)","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2014-08-27T21:53:29Z","receivedAt":"2014-08-27T21:53:29Z","isPatch":false,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 08/26/2014 02:03 AM, Jonathan Nieder wrote:\n> Jonathan Nieder wrote:\n> [...]\n>> I've having trouble keeping track of how patches change, which patches\n>> have been reviewed and which haven't, unaddressed comments, and so on,\n>> so as an experiment I've pushed this part of the series to the Gerrit\n>> server at\n>>\n>>  https://code-review.googlesource.com/#/q/topic:ref-transaction-1\n> \n> Outcome of the experiment: patches gained some minor changes and then\n> [...]\n> \n> I found the web UI helpful in seeing how each patch evolved since I\n> last looked at it.  Interdiff relative to the version in pu is below.\n> I'm still hoping for a tweak in response to a minor comment and then I\n> can put up a copy of the updated series to pull.\n\nThanks for organizing this \"experiment\". I was one of the guinea pigs\n:-) I have wanted to review more of Ronnie's patches (actually, all of\nthem!) but have been overwhelmed by the number of iterations and the\nnumber of patch series flying around in parallel. I was also interested\nto try out Gerrit, which I haven't used before. So I took up Jonathan's\ninvitation and reviewed the first patch series in Gerrit. Here are some\nof my first impressions.\n\n* Overall, I found it easier to review commits in Gerrit than on the\nmailing list, especially a long patch series like this one that has seen\nso much flux. It was easier to see the comments from all reviewers that\napply to a patch, which is difficult on the mailing list when comments\nare scattered over the many iterations of the patch series. It was\neasier to incrementally increase the context around a patch. It was easy\nto use the copy-paste commands provided in the \"download\" menu to fetch\nthe commit that I was reviewing into my local Git repository, and from\nthere to build it or investigate it using other tools.\n\n* The Gerrit interface is very busy. It was somewhat overwhelming to me\nas a beginner. On the other hand, the help menus (\"?\" key) are good and\nthe keyboard shortcuts are convenient. I didn't have to read much\ndocumentation to get started doing review in Gerrit, at least at a basic\nlevel.\n\n* During two of my big Gerrit sessions the website was very responsive\nand pleasant to use. During the third, it was terribly slow, like 5 - 15\nseconds per page update. If I had only experienced the slow behavior, I\nwould have rejected Gerrit immediately. I hope that the slow behavior\nwas a rare anomaly.\n\n* Gerrit sends out an endless flood of emails that mostly seem pretty\nuseless to me. I wish it weren't so chatty and that its emails were\nbetter organized.\n\n* At one point a back-and-forth in a line comment grew into a more\ngeneral issue that was more appropriate for the mailing list. The\ntransition from Gerrit to mailing list was a bit awkward.\n\nSo, overall I found it pleasant and efficient to review patches in\nGerrit. I would welcome more such \"experiments\". It would have been even\nbetter if Gerrit would generate more useful notification emails.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"248456","messageId":"xmqqwq9tbltl.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"xmqq1ts1d3jx.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH 0/20] rs/ref-transaction-1 (Re: Transaction patch series overview)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-08-27T22:37:58Z","receivedAt":"2014-08-27T22:37:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n>\n>> Jonathan Nieder wrote:\n>>\n>>> This series teaches the tag, replace, commit, cherry-pick,\n>>> fast-import, checkout -b, branch, receive-pack, and http-fetch\n>>> commands and all update_ref and delete_ref callers to use the ref\n>>> transaction API instead of lock_ref_sha1.\n>>>\n>>> The main user-visible change should be some cosmetic changes to error\n>>> messages.  The series also combines multiple ref updates into a single\n>>> transaction in 'git http-fetch -w' and when writing tags in\n>>> fast-import but otherwise doesn't change the granularity of\n>>> transactions.\n>>>\n>>> Reviewed at https://code-review.googlesource.com/#/q/topic:ref-transaction-1\n>\n> Thanks.\n>\n> Will queue, but the some other topics may have to disappear from my\n> tree while I try to rebase them on top (or while I wait for you guys\n> to send a reroll, making it unnecesary to do the rebase myself).\n\nI managed to rebase all the rs/ref-transaction* topics and pushed\nout the result in 'pu'.  Please eyeball the result.\n\nI had to bump nd/multiple-work-trees, which Duy asked to kick out of\n'next' earlier, out of 'pu' because I did not want to deal with\nconflict resolution that will go to waste when the topic is rerolled\nwhich would happen soon.\n"},{"id":"248473","messageId":"CAJo=hJv7+iMHC7rO1w2Hmnq4Ho3W0hwh77Z_gRvY4hzE=VnxGQ@mail.gmail.com","threadId":"37248","inReplyTo":"53FE5359.4030403@alum.mit.edu","subject":"Re: Using Gerrit to review Git patches (was: Re: Transaction patch series overview)","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2014-08-28T05:07:28Z","receivedAt":"2014-08-28T05:07:28Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"On Wed, Aug 27, 2014 at 2:53 PM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n> On 08/26/2014 02:03 AM, Jonathan Nieder wrote:\n>> Jonathan Nieder wrote:\n>> [...]\n>>> I've having trouble keeping track of how patches change, which patches\n>>> have been reviewed and which haven't, unaddressed comments, and so on,\n>>> so as an experiment I've pushed this part of the series to the Gerrit\n>>> server at\n>>>\n>>>  https://code-review.googlesource.com/#/q/topic:ref-transaction-1\n>>\n>> Outcome of the experiment: patches gained some minor changes and then\n>> [...]\n>>\n>> I found the web UI helpful in seeing how each patch evolved since I\n>> last looked at it.  Interdiff relative to the version in pu is below.\n>> I'm still hoping for a tweak in response to a minor comment and then I\n>> can put up a copy of the updated series to pull.\n>\n> Thanks for organizing this \"experiment\". I was one of the guinea pigs\n> :-) I have wanted to review more of Ronnie's patches (actually, all of\n> them!) but have been overwhelmed by the number of iterations and the\n> number of patch series flying around in parallel. I was also interested\n> to try out Gerrit, which I haven't used before. So I took up Jonathan's\n> invitation and reviewed the first patch series in Gerrit. Here are some\n> of my first impressions.\n\nThank you for making this writeup. It contains many valuable nuggets\nthat we should follow up on.\n\n> * Overall, I found it easier to review commits in Gerrit than on the\n> mailing list, especially a long patch series like this one that has seen\n> so much flux. It was easier to see the comments from all reviewers that\n> apply to a patch, which is difficult on the mailing list when comments\n> are scattered over the many iterations of the patch series. It was\n> easier to incrementally increase the context around a patch. It was easy\n> to use the copy-paste commands provided in the \"download\" menu to fetch\n> the commit that I was reviewing into my local Git repository, and from\n> there to build it or investigate it using other tools.\n>\n> * The Gerrit interface is very busy. It was somewhat overwhelming to me\n> as a beginner.\n\nVery astute observation. I have been using it for 5+ years so its hard\nfor me to see things like this.\n\nUnfortunately many parts of the UI have grown over the years.\nContributors come along and want to add X, months later another adds\nfield Y. Suddenly we have the entire alphabet and we don't know how it\ngot that way. Sounds like git reset --{hard|soft|mixed|wtf}. :)\n\nIts not an excuse. The UI should be less overwhelming. Its going to be\na challenge.\n\n> On the other hand, the help menus (\"?\" key) are good and\n> the keyboard shortcuts are convenient. I didn't have to read much\n> documentation to get started doing review in Gerrit, at least at a basic\n> level.\n\nYay!\n\n> * During two of my big Gerrit sessions the website was very responsive\n> and pleasant to use. During the third, it was terribly slow, like 5 - 15\n> seconds per page update. If I had only experienced the slow behavior, I\n> would have rejected Gerrit immediately. I hope that the slow behavior\n> was a rare anomaly.\n\nThis third session was a rare anomaly unique to\ncode-review.googlesource.com. It isn't typical for Gerrit Code Review.\n\nWe have a server set in Europe that was mostly answering you in the\nfirst two sessions. During the third session this server set was\ntemporarily offline. Requests degraded to a server set in the US,\nwhich pushed us into a worst-case scenario due to a misconfiguration\nof our load balancers. The lag you experienced was mostly due to this\nmisconfiguration, not EU-US networking.\n\nEarlier today I brought back up the European server set, so latency\nshould be reduced again.\n\nUnfortunately I haven't figured out how to fix the load balancer\nmisconfiguration. :(\n\n> * Gerrit sends out an endless flood of emails that mostly seem pretty\n> useless to me. I wish it weren't so chatty and that its emails were\n> better organized.\n\nAbsolutely agree.\n\n> * At one point a back-and-forth in a line comment grew into a more\n> general issue that was more appropriate for the mailing list. The\n> transition from Gerrit to mailing list was a bit awkward.\n\nWe find this a bit awkward in JGit too. Our solution has been to\n(mostly) keep discussion within the code review context. This has to\nsome extent forced more discussion to be around specific code, and\navoid wandering off into the philosophical weeds. Without concrete\ncode to talk about, there is nothing to say. :)\n\nA nice feature of Gerrit is another contributor can add their own\niterations to the same commit in the series, offering up alternative\nproposals. Like on the mailing list, this allows someone to just say\n\"what if you did it this way...\", and give everyone a concrete chunk\nof code to consider and discuss. Again this avoids wandering off into\nthe weeds.\n\n> So, overall I found it pleasant and efficient to review patches in\n> Gerrit. I would welcome more such \"experiments\". It would have been even\n> better if Gerrit would generate more useful notification emails.\n\nOne idea has been to just stop sending automatic emails, and instead\nrequire the author to send a message saying \"please take another look\nat my series\". Just disable the #@!(@(! automatic email delivery that\nstems out of `git push ...` creating or updating pending reviews.\n\nUnfortunately there are factions within the Gerrit contributor base\nthat find value in those automated emails. :(\n"},{"id":"248738","messageId":"20140902205841.GA18279@google.com","threadId":"37248","inReplyTo":"20140827002804.GA20185@google.com","subject":"[PATCH v22 0/22] rs/ref-transaction-1 (Re: Transaction patch series overview)","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T20:58:42Z","receivedAt":"2014-09-02T20:58:42Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi Junio,\n\nMichael reviewed the rest of rs/ref-transaction-1 (thanks!) and some\nmore small tweaks resulted --- in particular, there was a missing call\nto ref_transaction_free.  Interdiff below.  This is meant to replace\nrs/ref-transaction-1 in 'pu' and should be ready for 'next'.\n\nThese patches are also available from the git repository at\n\n  git://repo.or.cz/git/jrn.git tags/rs/ref-transaction-1\n\n\"Use ref transactions\", early part\n\nRonnie explains:\n\n\tThis patch series expands on the transaction API. It converts\n\tref updates, inside refs.c as well as external, to use the\n\ttransaction API for updates. This makes most ref updates\n\tatomic when there are failures locking or writing to a ref.\n\nThis series teaches the tag, replace, commit, cherry-pick, fast-import,\ncheckout -b, branch, receive-pack, and http-fetch commands and all\nupdate_ref and delete_ref callers to use the ref transaction API instead\nof lock_ref_sha1.\n\nThe main user-visible change should be some cosmetic changes to error\nmessages.  The series also combines multiple ref updates into a single\ntransaction in 'git http-fetch -w' and when writing tags in fast-import\nbut otherwise doesn't change the granularity of transactions.\n\nJonathan Nieder (2):\n      update-ref --stdin: narrow scope of err strbuf\n      update-ref --stdin: pass transaction around explicitly\n\nRonnie Sahlberg (20):\n      refs.c: change ref_transaction_create to do error checking and return status\n      refs.c: update ref_transaction_delete to check for error and return status\n      refs.c: make ref_transaction_begin take an err argument\n      refs.c: add transaction.status and track OPEN/CLOSED\n      tag.c: use ref transactions when doing updates\n      replace.c: use the ref transaction functions for updates\n      commit.c: use ref transactions for updates\n      sequencer.c: use ref transactions for all ref updates\n      fast-import.c: change update_branch to use ref transactions\n      branch.c: use ref transaction for all ref updates\n      refs.c: change update_ref to use a transaction\n      receive-pack.c: use a reference transaction for updating the refs\n      fast-import.c: use a ref transaction when dumping tags\n      walker.c: use ref transaction for ref updates\n      refs.c: make lock_ref_sha1 static\n      refs.c: remove the update_ref_lock function\n      refs.c: remove the update_ref_write function\n      refs.c: remove lock_ref_sha1\n      refs.c: make prune_ref use a transaction to delete the ref\n      refs.c: make delete_ref use a transaction\n\n branch.c               |  31 ++++---\n builtin/commit.c       |  25 +++--\n builtin/receive-pack.c |  25 +++--\n builtin/replace.c      |  14 +--\n builtin/tag.c          |  16 ++--\n builtin/update-ref.c   |  52 +++++++----\n fast-import.c          |  54 +++++++----\n refs.c                 | 245 ++++++++++++++++++++++++++++---------------------\n refs.h                 |  77 ++++++++++++----\n sequencer.c            |  26 ++++--\n walker.c               |  70 ++++++++------\n 11 files changed, 393 insertions(+), 242 deletions(-)\n---\nChanges since v21:\n\n builtin/update-ref.c | 38 +++++++++++++++++++++++++-------------\n refs.c               |  1 +\n 2 files changed, 26 insertions(+), 13 deletions(-)\n\ndiff --git c/builtin/update-ref.c w/builtin/update-ref.c\nindex 96a53b9..54a48c0 100644\n--- c/builtin/update-ref.c\n+++ w/builtin/update-ref.c\n@@ -12,11 +12,8 @@ static const char * const git_update_ref_usage[] = {\n \tNULL\n };\n \n-static struct ref_transaction *transaction;\n-\n static char line_termination = '\\n';\n static int update_flags;\n-static struct strbuf err = STRBUF_INIT;\n \n /*\n  * Parse one whitespace- or NUL-terminated, possibly C-quoted argument\n@@ -177,8 +174,10 @@ static int parse_next_sha1(struct strbuf *input, const char **next,\n  * depending on how line_termination is set.\n  */\n \n-static const char *parse_cmd_update(struct strbuf *input, const char *next)\n+static const char *parse_cmd_update(struct ref_transaction *transaction,\n+\t\t\t\t    struct strbuf *input, const char *next)\n {\n+\tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n \tunsigned char new_sha1[20];\n \tunsigned char old_sha1[20];\n@@ -204,12 +203,15 @@ static const char *parse_cmd_update(struct strbuf *input, const char *next)\n \n \tupdate_flags = 0;\n \tfree(refname);\n+\tstrbuf_release(&err);\n \n \treturn next;\n }\n \n-static const char *parse_cmd_create(struct strbuf *input, const char *next)\n+static const char *parse_cmd_create(struct ref_transaction *transaction,\n+\t\t\t\t    struct strbuf *input, const char *next)\n {\n+\tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n \tunsigned char new_sha1[20];\n \n@@ -232,12 +234,15 @@ static const char *parse_cmd_create(struct strbuf *input, const char *next)\n \n \tupdate_flags = 0;\n \tfree(refname);\n+\tstrbuf_release(&err);\n \n \treturn next;\n }\n \n-static const char *parse_cmd_delete(struct strbuf *input, const char *next)\n+static const char *parse_cmd_delete(struct ref_transaction *transaction,\n+\t\t\t\t    struct strbuf *input, const char *next)\n {\n+\tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n \tunsigned char old_sha1[20];\n \tint have_old;\n@@ -264,12 +269,15 @@ static const char *parse_cmd_delete(struct strbuf *input, const char *next)\n \n \tupdate_flags = 0;\n \tfree(refname);\n+\tstrbuf_release(&err);\n \n \treturn next;\n }\n \n-static const char *parse_cmd_verify(struct strbuf *input, const char *next)\n+static const char *parse_cmd_verify(struct ref_transaction *transaction,\n+\t\t\t\t    struct strbuf *input, const char *next)\n {\n+\tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n \tunsigned char new_sha1[20];\n \tunsigned char old_sha1[20];\n@@ -297,6 +305,7 @@ static const char *parse_cmd_verify(struct strbuf *input, const char *next)\n \n \tupdate_flags = 0;\n \tfree(refname);\n+\tstrbuf_release(&err);\n \n \treturn next;\n }\n@@ -310,7 +319,7 @@ static const char *parse_cmd_option(struct strbuf *input, const char *next)\n \treturn next + 8;\n }\n \n-static void update_refs_stdin(void)\n+static void update_refs_stdin(struct ref_transaction *transaction)\n {\n \tstruct strbuf input = STRBUF_INIT;\n \tconst char *next;\n@@ -325,13 +334,13 @@ static void update_refs_stdin(void)\n \t\telse if (isspace(*next))\n \t\t\tdie(\"whitespace before command: %s\", next);\n \t\telse if (starts_with(next, \"update \"))\n-\t\t\tnext = parse_cmd_update(&input, next + 7);\n+\t\t\tnext = parse_cmd_update(transaction, &input, next + 7);\n \t\telse if (starts_with(next, \"create \"))\n-\t\t\tnext = parse_cmd_create(&input, next + 7);\n+\t\t\tnext = parse_cmd_create(transaction, &input, next + 7);\n \t\telse if (starts_with(next, \"delete \"))\n-\t\t\tnext = parse_cmd_delete(&input, next + 7);\n+\t\t\tnext = parse_cmd_delete(transaction, &input, next + 7);\n \t\telse if (starts_with(next, \"verify \"))\n-\t\t\tnext = parse_cmd_verify(&input, next + 7);\n+\t\t\tnext = parse_cmd_verify(transaction, &input, next + 7);\n \t\telse if (starts_with(next, \"option \"))\n \t\t\tnext = parse_cmd_option(&input, next + 7);\n \t\telse\n@@ -365,6 +374,9 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \t\tdie(\"Refusing to perform update with empty message.\");\n \n \tif (read_stdin) {\n+\t\tstruct strbuf err = STRBUF_INIT;\n+\t\tstruct ref_transaction *transaction;\n+\n \t\ttransaction = ref_transaction_begin(&err);\n \t\tif (!transaction)\n \t\t\tdie(\"%s\", err.buf);\n@@ -372,7 +384,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n \t\tif (end_null)\n \t\t\tline_termination = '\\0';\n-\t\tupdate_refs_stdin();\n+\t\tupdate_refs_stdin(transaction);\n \t\tif (ref_transaction_commit(transaction, msg, &err))\n \t\t\tdie(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\ndiff --git c/refs.c w/refs.c\nindex 7996be9..7235574 100644\n--- c/refs.c\n+++ w/refs.c\n@@ -3511,6 +3511,7 @@ int update_ref(const char *action, const char *refname,\n \t\treturn 1;\n \t}\n \tstrbuf_release(&err);\n+\tref_transaction_free(t);\n \treturn 0;\n }\n \n"},{"id":"248739","messageId":"20140902205944.GB18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 01/22] refs.c: change ref_transaction_create to do error checking and return status","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T20:59:44Z","receivedAt":"2014-09-02T20:59:44Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 15:26:44 -0700\n\nDo basic error checking in ref_transaction_create() and make it return\nnon-zero on error. Update all callers to check the result of\nref_transaction_create(). There are currently no conditions in _create that\nwill return error but there will be in the future. Add an err argument that\nwill be updated on failure.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/update-ref.c |  4 +++-\n refs.c               | 18 ++++++++++++------\n refs.h               | 48 +++++++++++++++++++++++++++++++++++++++++-------\n 3 files changed, 56 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 3067b11..41121fa 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -226,7 +226,9 @@ static const char *parse_cmd_create(struct strbuf *input, const char *next)\n \tif (*next != line_termination)\n \t\tdie(\"create %s: extra input: %s\", refname, next);\n \n-\tref_transaction_create(transaction, refname, new_sha1, update_flags);\n+\tif (ref_transaction_create(transaction, refname, new_sha1,\n+\t\t\t\t   update_flags, &err))\n+\t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = 0;\n \tfree(refname);\ndiff --git a/refs.c b/refs.c\nindex 3f05e88..c49f1c6 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3449,18 +3449,24 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \treturn 0;\n }\n \n-void ref_transaction_create(struct ref_transaction *transaction,\n-\t\t\t    const char *refname,\n-\t\t\t    const unsigned char *new_sha1,\n-\t\t\t    int flags)\n+int ref_transaction_create(struct ref_transaction *transaction,\n+\t\t\t   const char *refname,\n+\t\t\t   const unsigned char *new_sha1,\n+\t\t\t   int flags,\n+\t\t\t   struct strbuf *err)\n {\n-\tstruct ref_update *update = add_update(transaction, refname);\n+\tstruct ref_update *update;\n+\n+\tif (!new_sha1 || is_null_sha1(new_sha1))\n+\t\tdie(\"BUG: create ref with null new_sha1\");\n+\n+\tupdate = add_update(transaction, refname);\n \n-\tassert(!is_null_sha1(new_sha1));\n \thashcpy(update->new_sha1, new_sha1);\n \thashclr(update->old_sha1);\n \tupdate->flags = flags;\n \tupdate->have_old = 1;\n+\treturn 0;\n }\n \n void ref_transaction_delete(struct ref_transaction *transaction,\ndiff --git a/refs.h b/refs.h\nindex c5376ce..b648819 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -10,6 +10,38 @@ struct ref_lock {\n \tint force_write;\n };\n \n+/*\n+ * A ref_transaction represents a collection of ref updates\n+ * that should succeed or fail together.\n+ *\n+ * Calling sequence\n+ * ----------------\n+ * - Allocate and initialize a `struct ref_transaction` by calling\n+ *   `ref_transaction_begin()`.\n+ *\n+ * - List intended ref updates by calling functions like\n+ *   `ref_transaction_update()` and `ref_transaction_create()`.\n+ *\n+ * - Call `ref_transaction_commit()` to execute the transaction.\n+ *   If this succeeds, the ref updates will have taken place and\n+ *   the transaction cannot be rolled back.\n+ *\n+ * - At any time call `ref_transaction_free()` to discard the\n+ *   transaction and free associated resources.  In particular,\n+ *   this rolls back the transaction if it has not been\n+ *   successfully committed.\n+ *\n+ * Error handling\n+ * --------------\n+ *\n+ * On error, transaction functions append a message about what\n+ * went wrong to the 'err' argument.  The message mentions what\n+ * ref was being updated (if any) when the error occurred so it\n+ * can be passed to 'die' or 'error' as-is.\n+ *\n+ * The message is appended to err without first clearing err.\n+ * err will not be '\\n' terminated.\n+ */\n struct ref_transaction;\n \n /*\n@@ -248,7 +280,7 @@ struct ref_transaction *ref_transaction_begin(void);\n  * it must not have existed beforehand.\n  * Function returns 0 on success and non-zero on failure. A failure to update\n  * means that the transaction as a whole has failed and will need to be\n- * rolled back. On failure the err buffer will be updated.\n+ * rolled back.\n  */\n int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n@@ -262,11 +294,15 @@ int ref_transaction_update(struct ref_transaction *transaction,\n  * that the reference should have after the update; it must not be the\n  * null SHA-1.  It is verified that the reference does not exist\n  * already.\n+ * Function returns 0 on success and non-zero on failure. A failure to create\n+ * means that the transaction as a whole has failed and will need to be\n+ * rolled back.\n  */\n-void ref_transaction_create(struct ref_transaction *transaction,\n-\t\t\t    const char *refname,\n-\t\t\t    const unsigned char *new_sha1,\n-\t\t\t    int flags);\n+int ref_transaction_create(struct ref_transaction *transaction,\n+\t\t\t   const char *refname,\n+\t\t\t   const unsigned char *new_sha1,\n+\t\t\t   int flags,\n+\t\t\t   struct strbuf *err);\n \n /*\n  * Add a reference deletion to transaction.  If have_old is true, then\n@@ -282,8 +318,6 @@ void ref_transaction_delete(struct ref_transaction *transaction,\n  * Commit all of the changes that have been queued in transaction, as\n  * atomically as possible.  Return a nonzero value if there is a\n  * problem.\n- * If err is non-NULL we will add an error string to it to explain why\n- * the transaction failed. The string does not end in newline.\n  */\n int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t   const char *msg, struct strbuf *err);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248740","messageId":"20140902210015.GC18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 02/22] refs.c: update ref_transaction_delete to check for error and return status","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:00:15Z","receivedAt":"2014-09-02T21:00:15Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 15:27:45 -0700\n\nChange ref_transaction_delete() to do basic error checking and return\nnon-zero on error. Update all callers to check the return for\nref_transaction_delete(). There are currently no conditions in _delete that\nwill return error but there will be in the future. Add an err argument that\nwill be updated on failure.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/update-ref.c |  5 +++--\n refs.c               | 16 +++++++++++-----\n refs.h               | 12 ++++++++----\n 3 files changed, 22 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 41121fa..7c9c248 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -258,8 +258,9 @@ static const char *parse_cmd_delete(struct strbuf *input, const char *next)\n \tif (*next != line_termination)\n \t\tdie(\"delete %s: extra input: %s\", refname, next);\n \n-\tref_transaction_delete(transaction, refname, old_sha1,\n-\t\t\t       update_flags, have_old);\n+\tif (ref_transaction_delete(transaction, refname, old_sha1,\n+\t\t\t\t   update_flags, have_old, &err))\n+\t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = 0;\n \tfree(refname);\ndiff --git a/refs.c b/refs.c\nindex c49f1c6..40f04f4 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3469,19 +3469,25 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \treturn 0;\n }\n \n-void ref_transaction_delete(struct ref_transaction *transaction,\n-\t\t\t    const char *refname,\n-\t\t\t    const unsigned char *old_sha1,\n-\t\t\t    int flags, int have_old)\n+int ref_transaction_delete(struct ref_transaction *transaction,\n+\t\t\t   const char *refname,\n+\t\t\t   const unsigned char *old_sha1,\n+\t\t\t   int flags, int have_old,\n+\t\t\t   struct strbuf *err)\n {\n-\tstruct ref_update *update = add_update(transaction, refname);\n+\tstruct ref_update *update;\n \n+\tif (have_old && !old_sha1)\n+\t\tdie(\"BUG: have_old is true but old_sha1 is NULL\");\n+\n+\tupdate = add_update(transaction, refname);\n \tupdate->flags = flags;\n \tupdate->have_old = have_old;\n \tif (have_old) {\n \t\tassert(!is_null_sha1(old_sha1));\n \t\thashcpy(update->old_sha1, old_sha1);\n \t}\n+\treturn 0;\n }\n \n int update_ref(const char *action, const char *refname,\ndiff --git a/refs.h b/refs.h\nindex b648819..71389a1 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -308,11 +308,15 @@ int ref_transaction_create(struct ref_transaction *transaction,\n  * Add a reference deletion to transaction.  If have_old is true, then\n  * old_sha1 holds the value that the reference should have had before\n  * the update (which must not be the null SHA-1).\n+ * Function returns 0 on success and non-zero on failure. A failure to delete\n+ * means that the transaction as a whole has failed and will need to be\n+ * rolled back.\n  */\n-void ref_transaction_delete(struct ref_transaction *transaction,\n-\t\t\t    const char *refname,\n-\t\t\t    const unsigned char *old_sha1,\n-\t\t\t    int flags, int have_old);\n+int ref_transaction_delete(struct ref_transaction *transaction,\n+\t\t\t   const char *refname,\n+\t\t\t   const unsigned char *old_sha1,\n+\t\t\t   int flags, int have_old,\n+\t\t\t   struct strbuf *err);\n \n /*\n  * Commit all of the changes that have been queued in transaction, as\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248741","messageId":"20140902210039.GD18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 03/22] refs.c: make ref_transaction_begin take an err argument","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:00:39Z","receivedAt":"2014-09-02T21:00:39Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Mon, 19 May 2014 10:42:34 -0700\n\nAdd an err argument to _begin so that on non-fatal failures in future ref\nbackends we can report a nice error back to the caller.\nWhile _begin can currently never fail for other reasons than OOM, in which\ncase we die() anyway, we may add other types of backends in the future.\nFor example, a hypothetical MySQL backend could fail in _begin with\n\"Can not connect to MySQL server. No route to host\".\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/update-ref.c | 5 ++++-\n refs.c               | 2 +-\n refs.h               | 2 +-\n 3 files changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 7c9c248..96a53b9 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -365,7 +365,9 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \t\tdie(\"Refusing to perform update with empty message.\");\n \n \tif (read_stdin) {\n-\t\ttransaction = ref_transaction_begin();\n+\t\ttransaction = ref_transaction_begin(&err);\n+\t\tif (!transaction)\n+\t\t\tdie(\"%s\", err.buf);\n \t\tif (delete || no_deref || argc > 0)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n \t\tif (end_null)\n@@ -374,6 +376,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \t\tif (ref_transaction_commit(transaction, msg, &err))\n \t\t\tdie(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n+\t\tstrbuf_release(&err);\n \t\treturn 0;\n \t}\n \ndiff --git a/refs.c b/refs.c\nindex 40f04f4..9cb7908 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3397,7 +3397,7 @@ struct ref_transaction {\n \tsize_t nr;\n };\n \n-struct ref_transaction *ref_transaction_begin(void)\n+struct ref_transaction *ref_transaction_begin(struct strbuf *err)\n {\n \treturn xcalloc(1, sizeof(struct ref_transaction));\n }\ndiff --git a/refs.h b/refs.h\nindex 71389a1..3f37c65 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -262,7 +262,7 @@ enum action_on_err {\n  * Begin a reference transaction.  The reference transaction must\n  * be freed by calling ref_transaction_free().\n  */\n-struct ref_transaction *ref_transaction_begin(void);\n+struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n \n /*\n  * The following functions add a reference check or update to a\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248742","messageId":"20140902210059.GE18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 04/22] refs.c: add transaction.status and track OPEN/CLOSED","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:00:59Z","receivedAt":"2014-09-02T21:00:59Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Tue, 29 Apr 2014 12:06:19 -0700\n\nTrack the state of a transaction in a new state field. Check the field for\nsanity, i.e. that state must be OPEN when _commit/_create/_delete or\n_update is called or else die(BUG:...)\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 34 +++++++++++++++++++++++++++++++++-\n 1 file changed, 33 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 9cb7908..cc63056 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3387,6 +3387,21 @@ struct ref_update {\n };\n \n /*\n+ * Transaction states.\n+ * OPEN:   The transaction is in a valid state and can accept new updates.\n+ *         An OPEN transaction can be committed.\n+ * CLOSED: A closed transaction is no longer active and no other operations\n+ *         than free can be used on it in this state.\n+ *         A transaction can either become closed by successfully committing\n+ *         an active transaction or if there is a failure while building\n+ *         the transaction thus rendering it failed/inactive.\n+ */\n+enum ref_transaction_state {\n+\tREF_TRANSACTION_OPEN   = 0,\n+\tREF_TRANSACTION_CLOSED = 1\n+};\n+\n+/*\n  * Data structure for holding a reference transaction, which can\n  * consist of checks and updates to multiple references, carried out\n  * as atomically as possible.  This structure is opaque to callers.\n@@ -3395,6 +3410,7 @@ struct ref_transaction {\n \tstruct ref_update **updates;\n \tsize_t alloc;\n \tsize_t nr;\n+\tenum ref_transaction_state state;\n };\n \n struct ref_transaction *ref_transaction_begin(struct strbuf *err)\n@@ -3437,6 +3453,9 @@ int ref_transaction_update(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tif (transaction->state != REF_TRANSACTION_OPEN)\n+\t\tdie(\"BUG: update called for transaction that is not open\");\n+\n \tif (have_old && !old_sha1)\n \t\tdie(\"BUG: have_old is true but old_sha1 is NULL\");\n \n@@ -3457,6 +3476,9 @@ int ref_transaction_create(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tif (transaction->state != REF_TRANSACTION_OPEN)\n+\t\tdie(\"BUG: create called for transaction that is not open\");\n+\n \tif (!new_sha1 || is_null_sha1(new_sha1))\n \t\tdie(\"BUG: create ref with null new_sha1\");\n \n@@ -3477,6 +3499,9 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tif (transaction->state != REF_TRANSACTION_OPEN)\n+\t\tdie(\"BUG: delete called for transaction that is not open\");\n+\n \tif (have_old && !old_sha1)\n \t\tdie(\"BUG: have_old is true but old_sha1 is NULL\");\n \n@@ -3532,8 +3557,13 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \tint n = transaction->nr;\n \tstruct ref_update **updates = transaction->updates;\n \n-\tif (!n)\n+\tif (transaction->state != REF_TRANSACTION_OPEN)\n+\t\tdie(\"BUG: commit called for transaction that is not open\");\n+\n+\tif (!n) {\n+\t\ttransaction->state = REF_TRANSACTION_CLOSED;\n \t\treturn 0;\n+\t}\n \n \t/* Allocate work space */\n \tdelnames = xmalloc(sizeof(*delnames) * n);\n@@ -3595,6 +3625,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \tclear_loose_ref_cache(&ref_cache);\n \n cleanup:\n+\ttransaction->state = REF_TRANSACTION_CLOSED;\n+\n \tfor (i = 0; i < n; i++)\n \t\tif (updates[i]->lock)\n \t\t\tunlock_ref(updates[i]->lock);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248743","messageId":"20140902210122.GF18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 05/22] tag.c: use ref transactions when doing updates","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:01:22Z","receivedAt":"2014-09-02T21:01:22Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 15:30:41 -0700\n\nChange tag.c to use ref transactions for all ref updates.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/tag.c | 16 ++++++++++------\n 1 file changed, 10 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex c6e8a71..f3f172f 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -548,7 +548,6 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct strbuf ref = STRBUF_INIT;\n \tunsigned char object[20], prev[20];\n \tconst char *object_ref, *tag;\n-\tstruct ref_lock *lock;\n \tstruct create_tag_options opt;\n \tchar *cleanup_arg = NULL;\n \tint annotate = 0, force = 0, lines = -1;\n@@ -556,6 +555,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tconst char *msgfile = NULL, *keyid = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n \tstruct commit_list *with_commit = NULL;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n \tstruct option options[] = {\n \t\tOPT_CMDMODE('l', \"list\", &cmdmode, N_(\"list tag names\"), 'l'),\n \t\t{ OPTION_INTEGER, 'n', NULL, &lines, N_(\"n\"),\n@@ -701,14 +702,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tif (annotate)\n \t\tcreate_tag(object, tag, &buf, &opt, prev, object);\n \n-\tlock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);\n-\tif (!lock)\n-\t\tdie(_(\"%s: cannot lock the ref\"), ref.buf);\n-\tif (write_ref_sha1(lock, object, NULL) < 0)\n-\t\tdie(_(\"%s: cannot update the ref\"), ref.buf);\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, ref.buf, object, prev,\n+\t\t\t\t   0, 1, &err) ||\n+\t    ref_transaction_commit(transaction, NULL, &err))\n+\t\tdie(\"%s\", err.buf);\n+\tref_transaction_free(transaction);\n \tif (force && !is_null_sha1(prev) && hashcmp(prev, object))\n \t\tprintf(_(\"Updated tag '%s' (was %s)\\n\"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV));\n \n+\tstrbuf_release(&err);\n \tstrbuf_release(&buf);\n \tstrbuf_release(&ref);\n \treturn 0;\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248744","messageId":"20140902210148.GG18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 06/22] replace.c: use the ref transaction functions for updates","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:01:48Z","receivedAt":"2014-09-02T21:01:48Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 15:32:29 -0700\n\nUpdate replace.c to use ref transactions for updates.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/replace.c | 14 ++++++++------\n 1 file changed, 8 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 1bb491d..1fcd06d 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -153,7 +153,8 @@ static int replace_object_sha1(const char *object_ref,\n \tunsigned char prev[20];\n \tenum object_type obj_type, repl_type;\n \tchar ref[PATH_MAX];\n-\tstruct ref_lock *lock;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n \n \tobj_type = sha1_object_info(object, NULL);\n \trepl_type = sha1_object_info(repl, NULL);\n@@ -166,12 +167,13 @@ static int replace_object_sha1(const char *object_ref,\n \n \tcheck_ref_valid(object, prev, ref, sizeof(ref), force);\n \n-\tlock = lock_any_ref_for_update(ref, prev, 0, NULL);\n-\tif (!lock)\n-\t\tdie(\"%s: cannot lock the ref\", ref);\n-\tif (write_ref_sha1(lock, repl, NULL) < 0)\n-\t\tdie(\"%s: cannot update the ref\", ref);\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, ref, repl, prev, 0, 1, &err) ||\n+\t    ref_transaction_commit(transaction, NULL, &err))\n+\t\tdie(\"%s\", err.buf);\n \n+\tref_transaction_free(transaction);\n \treturn 0;\n }\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248745","messageId":"20140902210219.GH18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 07/22] commit.c: use ref transactions for updates","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:02:19Z","receivedAt":"2014-09-02T21:02:19Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 15:34:19 -0700\n\nChange commit.c to use ref transactions for all ref updates.\nMake sure we pass a NULL pointer to ref_transaction_update if have_old\nis false.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/commit.c | 25 ++++++++++++-------------\n 1 file changed, 12 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 5e2221c..9bf1003 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1627,11 +1627,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tconst char *index_file, *reflog_msg;\n \tchar *nl;\n \tunsigned char sha1[20];\n-\tstruct ref_lock *ref_lock;\n \tstruct commit_list *parents = NULL, **pptr = &parents;\n \tstruct stat statbuf;\n \tstruct commit *current_head = NULL;\n \tstruct commit_extra_header *extra = NULL;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n \n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n \t\tusage_with_options(builtin_commit_usage, builtin_commit_options);\n@@ -1753,16 +1754,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&author_ident);\n \tfree_commit_extra_headers(extra);\n \n-\tref_lock = lock_any_ref_for_update(\"HEAD\",\n-\t\t\t\t\t   !current_head\n-\t\t\t\t\t   ? NULL\n-\t\t\t\t\t   : current_head->object.sha1,\n-\t\t\t\t\t   0, NULL);\n-\tif (!ref_lock) {\n-\t\trollback_index_files();\n-\t\tdie(_(\"cannot lock HEAD ref\"));\n-\t}\n-\n \tnl = strchr(sb.buf, '\\n');\n \tif (nl)\n \t\tstrbuf_setlen(&sb, nl + 1 - sb.buf);\n@@ -1771,10 +1762,17 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tstrbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));\n \tstrbuf_insert(&sb, strlen(reflog_msg), \": \", 2);\n \n-\tif (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) {\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, \"HEAD\", sha1,\n+\t\t\t\t   current_head\n+\t\t\t\t   ? current_head->object.sha1 : NULL,\n+\t\t\t\t   0, !!current_head, &err) ||\n+\t    ref_transaction_commit(transaction, sb.buf, &err)) {\n \t\trollback_index_files();\n-\t\tdie(_(\"cannot update HEAD ref\"));\n+\t\tdie(\"%s\", err.buf);\n \t}\n+\tref_transaction_free(transaction);\n \n \tunlink(git_path(\"CHERRY_PICK_HEAD\"));\n \tunlink(git_path(\"REVERT_HEAD\"));\n@@ -1803,5 +1801,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tif (!quiet)\n \t\tprint_summary(prefix, sha1, !current_head);\n \n+\tstrbuf_release(&err);\n \treturn 0;\n }\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248746","messageId":"20140902210247.GI18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 08/22] sequencer.c: use ref transactions for all ref updates","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:02:47Z","receivedAt":"2014-09-02T21:02:47Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 15:37:45 -0700\n\nChange to use ref transactions for all updates to refs.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n sequencer.c | 26 ++++++++++++++++++--------\n 1 file changed, 18 insertions(+), 8 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 0a80c58..5e93b6a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -272,23 +272,33 @@ static int error_dirty_index(struct replay_opts *opts)\n static int fast_forward_to(const unsigned char *to, const unsigned char *from,\n \t\t\tint unborn, struct replay_opts *opts)\n {\n-\tstruct ref_lock *ref_lock;\n+\tstruct ref_transaction *transaction;\n \tstruct strbuf sb = STRBUF_INIT;\n-\tint ret;\n+\tstruct strbuf err = STRBUF_INIT;\n \n \tread_cache();\n \tif (checkout_fast_forward(from, to, 1))\n \t\texit(1); /* the callee should have complained already */\n-\tref_lock = lock_any_ref_for_update(\"HEAD\", unborn ? null_sha1 : from,\n-\t\t\t\t\t   0, NULL);\n-\tif (!ref_lock)\n-\t\treturn error(_(\"Failed to lock HEAD during fast_forward_to\"));\n \n \tstrbuf_addf(&sb, \"%s: fast-forward\", action_name(opts));\n-\tret = write_ref_sha1(ref_lock, to, sb.buf);\n+\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, \"HEAD\",\n+\t\t\t\t   to, unborn ? null_sha1 : from,\n+\t\t\t\t   0, 1, &err) ||\n+\t    ref_transaction_commit(transaction, sb.buf, &err)) {\n+\t\tref_transaction_free(transaction);\n+\t\terror(\"%s\", err.buf);\n+\t\tstrbuf_release(&sb);\n+\t\tstrbuf_release(&err);\n+\t\treturn -1;\n+\t}\n \n \tstrbuf_release(&sb);\n-\treturn ret;\n+\tstrbuf_release(&err);\n+\tref_transaction_free(transaction);\n+\treturn 0;\n }\n \n static int do_recursive_merge(struct commit *base, struct commit *next,\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248747","messageId":"20140902210325.GJ18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 09/22] fast-import.c: change update_branch to use ref transactions","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:03:25Z","receivedAt":"2014-09-02T21:03:25Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 16:21:13 -0700\n\nChange update_branch() to use ref transactions for updates.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n fast-import.c | 25 +++++++++++++++----------\n 1 file changed, 15 insertions(+), 10 deletions(-)\n\ndiff --git a/fast-import.c b/fast-import.c\nindex 6707a66..79160d5 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1679,8 +1679,9 @@ found_entry:\n static int update_branch(struct branch *b)\n {\n \tstatic const char *msg = \"fast-import\";\n-\tstruct ref_lock *lock;\n+\tstruct ref_transaction *transaction;\n \tunsigned char old_sha1[20];\n+\tstruct strbuf err = STRBUF_INIT;\n \n \tif (read_ref(b->name, old_sha1))\n \t\thashclr(old_sha1);\n@@ -1689,29 +1690,33 @@ static int update_branch(struct branch *b)\n \t\t\tdelete_ref(b->name, old_sha1, 0);\n \t\treturn 0;\n \t}\n-\tlock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL);\n-\tif (!lock)\n-\t\treturn error(\"Unable to lock %s\", b->name);\n \tif (!force_update && !is_null_sha1(old_sha1)) {\n \t\tstruct commit *old_cmit, *new_cmit;\n \n \t\told_cmit = lookup_commit_reference_gently(old_sha1, 0);\n \t\tnew_cmit = lookup_commit_reference_gently(b->sha1, 0);\n-\t\tif (!old_cmit || !new_cmit) {\n-\t\t\tunlock_ref(lock);\n+\t\tif (!old_cmit || !new_cmit)\n \t\t\treturn error(\"Branch %s is missing commits.\", b->name);\n-\t\t}\n \n \t\tif (!in_merge_bases(old_cmit, new_cmit)) {\n-\t\t\tunlock_ref(lock);\n \t\t\twarning(\"Not updating %s\"\n \t\t\t\t\" (new tip %s does not contain %s)\",\n \t\t\t\tb->name, sha1_to_hex(b->sha1), sha1_to_hex(old_sha1));\n \t\t\treturn -1;\n \t\t}\n \t}\n-\tif (write_ref_sha1(lock, b->sha1, msg) < 0)\n-\t\treturn error(\"Unable to update %s\", b->name);\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, b->name, b->sha1, old_sha1,\n+\t\t\t\t   0, 1, &err) ||\n+\t    ref_transaction_commit(transaction, msg, &err)) {\n+\t\tref_transaction_free(transaction);\n+\t\terror(\"%s\", err.buf);\n+\t\tstrbuf_release(&err);\n+\t\treturn -1;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n \treturn 0;\n }\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248748","messageId":"20140902210405.GK18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 10/22] branch.c: use ref transaction for all ref updates","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:04:05Z","receivedAt":"2014-09-02T21:04:05Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Apr 2014 16:21:53 -0700\n\nChange create_branch to use a ref transaction when creating the new branch.\n\nThis also fixes a race condition in the old code where two concurrent\ncreate_branch could race since the lock_any_ref_for_update/write_ref_sha1\ndid not protect against the ref already existing. I.e. one thread could end up\noverwriting a branch even if the forcing flag is false.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n branch.c | 31 +++++++++++++++++--------------\n 1 file changed, 17 insertions(+), 14 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 660097b..37ac555 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -226,7 +226,6 @@ void create_branch(const char *head,\n \t\t   int force, int reflog, int clobber_head,\n \t\t   int quiet, enum branch_track track)\n {\n-\tstruct ref_lock *lock = NULL;\n \tstruct commit *commit;\n \tunsigned char sha1[20];\n \tchar *real_ref, msg[PATH_MAX + 20];\n@@ -285,15 +284,6 @@ void create_branch(const char *head,\n \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n \thashcpy(sha1, commit->object.sha1);\n \n-\tif (!dont_change_ref) {\n-\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL);\n-\t\tif (!lock)\n-\t\t\tdie_errno(_(\"Failed to lock ref for update\"));\n-\t}\n-\n-\tif (reflog)\n-\t\tlog_all_ref_updates = 1;\n-\n \tif (forcing)\n \t\tsnprintf(msg, sizeof msg, \"branch: Reset to %s\",\n \t\t\t start_name);\n@@ -301,13 +291,26 @@ void create_branch(const char *head,\n \t\tsnprintf(msg, sizeof msg, \"branch: Created from %s\",\n \t\t\t start_name);\n \n+\tif (reflog)\n+\t\tlog_all_ref_updates = 1;\n+\n+\tif (!dont_change_ref) {\n+\t\tstruct ref_transaction *transaction;\n+\t\tstruct strbuf err = STRBUF_INIT;\n+\n+\t\ttransaction = ref_transaction_begin(&err);\n+\t\tif (!transaction ||\n+\t\t    ref_transaction_update(transaction, ref.buf, sha1,\n+\t\t\t\t\t   null_sha1, 0, !forcing, &err) ||\n+\t\t    ref_transaction_commit(transaction, msg, &err))\n+\t\t\tdie(\"%s\", err.buf);\n+\t\tref_transaction_free(transaction);\n+\t\tstrbuf_release(&err);\n+\t}\n+\n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\n \n-\tif (!dont_change_ref)\n-\t\tif (write_ref_sha1(lock, sha1, msg) < 0)\n-\t\t\tdie_errno(_(\"Failed to write ref\"));\n-\n \tstrbuf_release(&ref);\n \tfree(real_ref);\n }\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248749","messageId":"20140902210438.GL18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 11/22] refs.c: change update_ref to use a transaction","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:04:38Z","receivedAt":"2014-09-02T21:04:38Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 24 Apr 2014 16:36:55 -0700\n\nChange the update_ref helper function to use a ref transaction internally.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 30 ++++++++++++++++++++++++++----\n 1 file changed, 26 insertions(+), 4 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex cc63056..a3532ab 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3519,11 +3519,33 @@ int update_ref(const char *action, const char *refname,\n \t       const unsigned char *sha1, const unsigned char *oldval,\n \t       int flags, enum action_on_err onerr)\n {\n-\tstruct ref_lock *lock;\n-\tlock = update_ref_lock(refname, oldval, flags, NULL, onerr);\n-\tif (!lock)\n+\tstruct ref_transaction *t;\n+\tstruct strbuf err = STRBUF_INIT;\n+\n+\tt = ref_transaction_begin(&err);\n+\tif (!t ||\n+\t    ref_transaction_update(t, refname, sha1, oldval, flags,\n+\t\t\t\t   !!oldval, &err) ||\n+\t    ref_transaction_commit(t, action, &err)) {\n+\t\tconst char *str = \"update_ref failed for ref '%s': %s\";\n+\n+\t\tref_transaction_free(t);\n+\t\tswitch (onerr) {\n+\t\tcase UPDATE_REFS_MSG_ON_ERR:\n+\t\t\terror(str, refname, err.buf);\n+\t\t\tbreak;\n+\t\tcase UPDATE_REFS_DIE_ON_ERR:\n+\t\t\tdie(str, refname, err.buf);\n+\t\t\tbreak;\n+\t\tcase UPDATE_REFS_QUIET_ON_ERR:\n+\t\t\tbreak;\n+\t\t}\n+\t\tstrbuf_release(&err);\n \t\treturn 1;\n-\treturn update_ref_write(action, refname, sha1, lock, NULL, onerr);\n+\t}\n+\tstrbuf_release(&err);\n+\tref_transaction_free(t);\n+\treturn 0;\n }\n \n static int ref_update_compare(const void *r1, const void *r2)\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248750","messageId":"20140902210525.GM18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 12/22] receive-pack.c: use a reference transaction for updating the refs","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:05:25Z","receivedAt":"2014-09-02T21:05:25Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Mon, 28 Apr 2014 14:36:15 -0700\n\nWrap all the ref updates inside a transaction.\n\nIn the new API there is no distinction between failure to lock and\nfailure to write a ref.  Both can be permanent (e.g., a ref\n\"refs/heads/topic\" is blocking creation of the lock file\n\"refs/heads/topic/1.lock\") or transient (e.g., file system full) and\nthere's no clear difference in how the client should respond, so\nreplace the two statuses \"failed to lock\" and \"failed to write\" with\na single status \"failed to update ref\".  In both cases a more\ndetailed message is sent by sideband to diagnose the problem.\n\nExample, before:\n\n error: there are still refs under 'refs/heads/topic'\n remote: error: failed to lock refs/heads/topic\n To foo\n  ! [remote rejected] HEAD -> topic (failed to lock)\n\nAfter:\n\n error: there are still refs under 'refs/heads/topic'\n remote: error: Cannot lock the ref 'refs/heads/topic'.\n To foo\n  ! [remote rejected] HEAD -> topic (failed to update ref)\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/receive-pack.c | 25 ++++++++++++++++---------\n 1 file changed, 16 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex c323081..224fadc 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -475,7 +475,6 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \tconst char *namespaced_name;\n \tunsigned char *old_sha1 = cmd->old_sha1;\n \tunsigned char *new_sha1 = cmd->new_sha1;\n-\tstruct ref_lock *lock;\n \n \t/* only refs/... are allowed */\n \tif (!starts_with(name, \"refs/\") || check_refname_format(name + 5, 0)) {\n@@ -576,19 +575,27 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\treturn NULL; /* good */\n \t}\n \telse {\n+\t\tstruct strbuf err = STRBUF_INIT;\n+\t\tstruct ref_transaction *transaction;\n+\n \t\tif (shallow_update && si->shallow_ref[cmd->index] &&\n \t\t    update_shallow_ref(cmd, si))\n \t\t\treturn \"shallow error\";\n \n-\t\tlock = lock_any_ref_for_update(namespaced_name, old_sha1,\n-\t\t\t\t\t       0, NULL);\n-\t\tif (!lock) {\n-\t\t\trp_error(\"failed to lock %s\", name);\n-\t\t\treturn \"failed to lock\";\n-\t\t}\n-\t\tif (write_ref_sha1(lock, new_sha1, \"push\")) {\n-\t\t\treturn \"failed to write\"; /* error() already called */\n+\t\ttransaction = ref_transaction_begin(&err);\n+\t\tif (!transaction ||\n+\t\t    ref_transaction_update(transaction, namespaced_name,\n+\t\t\t\t\t   new_sha1, old_sha1, 0, 1, &err) ||\n+\t\t    ref_transaction_commit(transaction, \"push\", &err)) {\n+\t\t\tref_transaction_free(transaction);\n+\n+\t\t\trp_error(\"%s\", err.buf);\n+\t\t\tstrbuf_release(&err);\n+\t\t\treturn \"failed to update ref\";\n \t\t}\n+\n+\t\tref_transaction_free(transaction);\n+\t\tstrbuf_release(&err);\n \t\treturn NULL; /* good */\n \t}\n }\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248751","messageId":"20140902210659.GN18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 13/22] fast-import.c: use a ref transaction when dumping tags","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:06:59Z","receivedAt":"2014-09-02T21:06:59Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Mon, 28 Apr 2014 15:23:58 -0700\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n fast-import.c | 29 +++++++++++++++++++++++------\n 1 file changed, 23 insertions(+), 6 deletions(-)\n\ndiff --git a/fast-import.c b/fast-import.c\nindex 79160d5..e7f6e37 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1735,15 +1735,32 @@ static void dump_tags(void)\n {\n \tstatic const char *msg = \"fast-import\";\n \tstruct tag *t;\n-\tstruct ref_lock *lock;\n-\tchar ref_name[PATH_MAX];\n+\tstruct strbuf ref_name = STRBUF_INIT;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tstruct ref_transaction *transaction;\n \n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction) {\n+\t\tfailure |= error(\"%s\", err.buf);\n+\t\tgoto cleanup;\n+\t}\n \tfor (t = first_tag; t; t = t->next_tag) {\n-\t\tsprintf(ref_name, \"tags/%s\", t->name);\n-\t\tlock = lock_ref_sha1(ref_name, NULL);\n-\t\tif (!lock || write_ref_sha1(lock, t->sha1, msg) < 0)\n-\t\t\tfailure |= error(\"Unable to update %s\", ref_name);\n+\t\tstrbuf_reset(&ref_name);\n+\t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n+\n+\t\tif (ref_transaction_update(transaction, ref_name.buf, t->sha1,\n+\t\t\t\t\t   NULL, 0, 0, &err)) {\n+\t\t\tfailure |= error(\"%s\", err.buf);\n+\t\t\tgoto cleanup;\n+\t\t}\n \t}\n+\tif (ref_transaction_commit(transaction, msg, &err))\n+\t\tfailure |= error(\"%s\", err.buf);\n+\n+ cleanup:\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&ref_name);\n+\tstrbuf_release(&err);\n }\n \n static void dump_marks_helper(FILE *f,\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248752","messageId":"20140902210733.GO18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 14/22] walker.c: use ref transaction for ref updates","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:07:33Z","receivedAt":"2014-09-02T21:07:33Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 17 Apr 2014 11:31:06 -0700\n\nSwitch to using ref transactions in walker_fetch(). As part of the refactoring\nto use ref transactions we also fix a potential memory leak where in the\noriginal code if write_ref_sha1() would fail we would end up returning from\nthe function without free()ing the msg string.\n\nNote that this function is only called when fetching from a remote HTTP\nrepository onto the local (most of the time single-user) repository which\nlikely means that the type of collisions that the previous locking would\nprotect against and cause the fetch to fail for are even more rare.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n walker.c | 72 ++++++++++++++++++++++++++++++++++++----------------------------\n 1 file changed, 41 insertions(+), 31 deletions(-)\n\ndiff --git a/walker.c b/walker.c\nindex 1dd86b8..b8a5441 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -251,40 +251,40 @@ void walker_targets_free(int targets, char **target, const char **write_ref)\n int walker_fetch(struct walker *walker, int targets, char **target,\n \t\t const char **write_ref, const char *write_ref_log_details)\n {\n-\tstruct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *));\n+\tstruct strbuf refname = STRBUF_INIT;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tstruct ref_transaction *transaction = NULL;\n \tunsigned char *sha1 = xmalloc(targets * 20);\n-\tchar *msg;\n-\tint ret;\n-\tint i;\n+\tchar *msg = NULL;\n+\tint i, ret = -1;\n \n \tsave_commit_buffer = 0;\n \n-\tfor (i = 0; i < targets; i++) {\n-\t\tif (!write_ref || !write_ref[i])\n-\t\t\tcontinue;\n-\n-\t\tlock[i] = lock_ref_sha1(write_ref[i], NULL);\n-\t\tif (!lock[i]) {\n-\t\t\terror(\"Can't lock ref %s\", write_ref[i]);\n-\t\t\tgoto unlock_and_fail;\n+\tif (write_ref) {\n+\t\ttransaction = ref_transaction_begin(&err);\n+\t\tif (!transaction) {\n+\t\t\terror(\"%s\", err.buf);\n+\t\t\tgoto done;\n \t\t}\n \t}\n-\n \tif (!walker->get_recover)\n \t\tfor_each_ref(mark_complete, NULL);\n \n \tfor (i = 0; i < targets; i++) {\n \t\tif (interpret_target(walker, target[i], &sha1[20 * i])) {\n \t\t\terror(\"Could not interpret response from server '%s' as something to pull\", target[i]);\n-\t\t\tgoto unlock_and_fail;\n+\t\t\tgoto done;\n \t\t}\n \t\tif (process(walker, lookup_unknown_object(&sha1[20 * i])))\n-\t\t\tgoto unlock_and_fail;\n+\t\t\tgoto done;\n \t}\n \n \tif (loop(walker))\n-\t\tgoto unlock_and_fail;\n-\n+\t\tgoto done;\n+\tif (!write_ref) {\n+\t\tret = 0;\n+\t\tgoto done;\n+\t}\n \tif (write_ref_log_details) {\n \t\tmsg = xmalloc(strlen(write_ref_log_details) + 12);\n \t\tsprintf(msg, \"fetch from %s\", write_ref_log_details);\n@@ -292,23 +292,33 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\tmsg = NULL;\n \t}\n \tfor (i = 0; i < targets; i++) {\n-\t\tif (!write_ref || !write_ref[i])\n+\t\tif (!write_ref[i])\n \t\t\tcontinue;\n-\t\tret = write_ref_sha1(lock[i], &sha1[20 * i], msg ? msg : \"fetch (unknown)\");\n-\t\tlock[i] = NULL;\n-\t\tif (ret)\n-\t\t\tgoto unlock_and_fail;\n+\t\tstrbuf_reset(&refname);\n+\t\tstrbuf_addf(&refname, \"refs/%s\", write_ref[i]);\n+\t\tif (ref_transaction_update(transaction, refname.buf,\n+\t\t\t\t\t   &sha1[20 * i], NULL, 0, 0,\n+\t\t\t\t\t   &err)) {\n+\t\t\terror(\"%s\", err.buf);\n+\t\t\tgoto done;\n+\t\t}\n \t}\n+\tif (ref_transaction_commit(transaction,\n+\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n+\t\t\t\t   &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tgoto done;\n+\t}\n+\n+\tret = 0;\n+\n+done:\n+\tref_transaction_free(transaction);\n \tfree(msg);\n-\n-\treturn 0;\n-\n-unlock_and_fail:\n-\tfor (i = 0; i < targets; i++)\n-\t\tif (lock[i])\n-\t\t\tunlock_ref(lock[i]);\n-\n-\treturn -1;\n+\tfree(sha1);\n+\tstrbuf_release(&err);\n+\tstrbuf_release(&refname);\n+\treturn ret;\n }\n \n void walker_free(struct walker *walker)\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248753","messageId":"20140902210800.GP18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 15/22] refs.c: make lock_ref_sha1 static","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:08:00Z","receivedAt":"2014-09-02T21:08:00Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Mon, 28 Apr 2014 15:38:47 -0700\n\nNo external callers reference lock_ref_sha1 any more so let's declare it\nstatic.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 7 +++++--\n refs.h | 6 ------\n 2 files changed, 5 insertions(+), 8 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex a3532ab..d4cd44b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2069,7 +2069,10 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n \treturn logs_found;\n }\n \n-/* This function should make sure errno is meaningful on error */\n+/*\n+ * Locks a \"refs/\" ref returning the lock on success and NULL on failure.\n+ * On failure errno is set to something meaningful.\n+ */\n static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\t\t\t    const unsigned char *old_sha1,\n \t\t\t\t\t    int flags, int *type_p)\n@@ -2170,7 +2173,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \treturn NULL;\n }\n \n-struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)\n+static struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)\n {\n \tchar refpath[PATH_MAX];\n \tif (check_refname_format(refname, 0))\ndiff --git a/refs.h b/refs.h\nindex 3f37c65..65dd593 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -170,12 +170,6 @@ extern int ref_exists(const char *);\n  */\n extern int peel_ref(const char *refname, unsigned char *sha1);\n \n-/*\n- * Locks a \"refs/\" ref returning the lock on success and NULL on failure.\n- * On failure errno is set to something meaningful.\n- */\n-extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1);\n-\n /** Locks any ref (for 'HEAD' type refs). */\n #define REF_NODEREF\t0x01\n /* errno is set to something meaningful on failure */\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248754","messageId":"20140902210819.GQ18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 16/22] refs.c: remove the update_ref_lock function","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:08:20Z","receivedAt":"2014-09-02T21:08:20Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Tue, 29 Apr 2014 12:14:47 -0700\n\nSince we now only call update_ref_lock with onerr==QUIET_ON_ERR we no longer\nneed this function and can replace it with just calling lock_any_ref_for_update\ndirectly.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 30 ++++++------------------------\n 1 file changed, 6 insertions(+), 24 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex d4cd44b..a4445a1 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3336,24 +3336,6 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)\n \treturn retval;\n }\n \n-static struct ref_lock *update_ref_lock(const char *refname,\n-\t\t\t\t\tconst unsigned char *oldval,\n-\t\t\t\t\tint flags, int *type_p,\n-\t\t\t\t\tenum action_on_err onerr)\n-{\n-\tstruct ref_lock *lock;\n-\tlock = lock_any_ref_for_update(refname, oldval, flags, type_p);\n-\tif (!lock) {\n-\t\tconst char *str = \"Cannot lock the ref '%s'.\";\n-\t\tswitch (onerr) {\n-\t\tcase UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;\n-\t\tcase UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;\n-\t\tcase UPDATE_REFS_QUIET_ON_ERR: break;\n-\t\t}\n-\t}\n-\treturn lock;\n-}\n-\n static int update_ref_write(const char *action, const char *refname,\n \t\t\t    const unsigned char *sha1, struct ref_lock *lock,\n \t\t\t    struct strbuf *err, enum action_on_err onerr)\n@@ -3603,12 +3585,12 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n \n-\t\tupdate->lock = update_ref_lock(update->refname,\n-\t\t\t\t\t       (update->have_old ?\n-\t\t\t\t\t\tupdate->old_sha1 : NULL),\n-\t\t\t\t\t       update->flags,\n-\t\t\t\t\t       &update->type,\n-\t\t\t\t\t       UPDATE_REFS_QUIET_ON_ERR);\n+\t\tupdate->lock = lock_any_ref_for_update(update->refname,\n+\t\t\t\t\t\t       (update->have_old ?\n+\t\t\t\t\t\t\tupdate->old_sha1 :\n+\t\t\t\t\t\t\tNULL),\n+\t\t\t\t\t\t       update->flags,\n+\t\t\t\t\t\t       &update->type);\n \t\tif (!update->lock) {\n \t\t\tif (err)\n \t\t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248755","messageId":"20140902210843.GR18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 17/22] refs.c: remove the update_ref_write function","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:08:43Z","receivedAt":"2014-09-02T21:08:43Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Tue, 29 Apr 2014 13:42:07 -0700\n\nSince we only call update_ref_write from a single place and we only call it\nwith onerr==QUIET_ON_ERR we can just as well get rid of it and just call\nwrite_ref_sha1 directly. This changes the return status for _commit from\n1 to -1 on failures when writing to the ref. Eventually we will want\n_commit to start returning more detailed error conditions than the current\nsimple success/failure.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 34 ++++++++--------------------------\n 1 file changed, 8 insertions(+), 26 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex a4445a1..a6b39ec 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3336,25 +3336,6 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)\n \treturn retval;\n }\n \n-static int update_ref_write(const char *action, const char *refname,\n-\t\t\t    const unsigned char *sha1, struct ref_lock *lock,\n-\t\t\t    struct strbuf *err, enum action_on_err onerr)\n-{\n-\tif (write_ref_sha1(lock, sha1, action) < 0) {\n-\t\tconst char *str = \"Cannot update the ref '%s'.\";\n-\t\tif (err)\n-\t\t\tstrbuf_addf(err, str, refname);\n-\n-\t\tswitch (onerr) {\n-\t\tcase UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;\n-\t\tcase UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;\n-\t\tcase UPDATE_REFS_QUIET_ON_ERR: break;\n-\t\t}\n-\t\treturn 1;\n-\t}\n-\treturn 0;\n-}\n-\n /**\n  * Information needed for a single ref update.  Set new_sha1 to the\n  * new value or to zero to delete the ref.  To check the old value\n@@ -3605,14 +3586,15 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (!is_null_sha1(update->new_sha1)) {\n-\t\t\tret = update_ref_write(msg,\n-\t\t\t\t\t       update->refname,\n-\t\t\t\t\t       update->new_sha1,\n-\t\t\t\t\t       update->lock, err,\n-\t\t\t\t\t       UPDATE_REFS_QUIET_ON_ERR);\n-\t\t\tupdate->lock = NULL; /* freed by update_ref_write */\n-\t\t\tif (ret)\n+\t\t\tret = write_ref_sha1(update->lock, update->new_sha1,\n+\t\t\t\t\t     msg);\n+\t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n+\t\t\tif (ret) {\n+\t\t\t\tif (err)\n+\t\t\t\t\tstrbuf_addf(err, \"Cannot update the ref '%s'.\",\n+\t\t\t\t\t\t    update->refname);\n \t\t\t\tgoto cleanup;\n+\t\t\t}\n \t\t}\n \t}\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248756","messageId":"20140902210912.GS18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 18/22] refs.c: remove lock_ref_sha1","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:09:12Z","receivedAt":"2014-09-02T21:09:12Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Tue, 29 Apr 2014 15:45:52 -0700\n\nlock_ref_sha1 was only called from one place in refs.c and only provided\na check that the refname was sane before adding back the initial \"refs/\"\npart of the ref path name, the initial \"refs/\" that this caller had already\nstripped off before calling lock_ref_sha1.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 15 +++++----------\n 1 file changed, 5 insertions(+), 10 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex a6b39ec..fd67684 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2173,15 +2173,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \treturn NULL;\n }\n \n-static struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)\n-{\n-\tchar refpath[PATH_MAX];\n-\tif (check_refname_format(refname, 0))\n-\t\treturn NULL;\n-\tstrcpy(refpath, mkpath(\"refs/%s\", refname));\n-\treturn lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);\n-}\n-\n struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t const unsigned char *old_sha1,\n \t\t\t\t\t int flags, int *type_p)\n@@ -2391,8 +2382,12 @@ static void try_remove_empty_parents(char *name)\n /* make sure nobody touched the ref, and unlink */\n static void prune_ref(struct ref_to_prune *r)\n {\n-\tstruct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);\n+\tstruct ref_lock *lock;\n \n+\tif (check_refname_format(r->name + 5, 0))\n+\t\treturn;\n+\n+\tlock = lock_ref_sha1_basic(r->name, r->sha1, 0, NULL);\n \tif (lock) {\n \t\tunlink_or_warn(git_path(\"%s\", r->name));\n \t\tunlock_ref(lock);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248757","messageId":"20140902210946.GT18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 19/22] refs.c: make prune_ref use a transaction to delete the ref","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:09:46Z","receivedAt":"2014-09-02T21:09:46Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 30 Apr 2014 09:03:36 -0700\n\nChange prune_ref to delete the ref using a ref transaction. To do this we also\nneed to add a new flag REF_ISPRUNING that will tell the transaction that we\ndo not want to delete this ref from the packed refs. This flag is private to\nrefs.c and not exposed to external callers.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 28 +++++++++++++++++++++-------\n refs.h | 13 +++++++++++--\n 2 files changed, 32 insertions(+), 9 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex fd67684..22eb3dd 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -25,6 +25,11 @@ static unsigned char refname_disposition[256] = {\n };\n \n /*\n+ * Used as a flag to ref_transaction_delete when a loose ref is being\n+ * pruned.\n+ */\n+#define REF_ISPRUNING\t0x0100\n+/*\n  * Try to read one refname component from the front of refname.\n  * Return the length of the component found, or -1 if the component is\n  * not legal.  It is legal if it is something reasonable to have under\n@@ -2382,17 +2387,25 @@ static void try_remove_empty_parents(char *name)\n /* make sure nobody touched the ref, and unlink */\n static void prune_ref(struct ref_to_prune *r)\n {\n-\tstruct ref_lock *lock;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n \n \tif (check_refname_format(r->name + 5, 0))\n \t\treturn;\n \n-\tlock = lock_ref_sha1_basic(r->name, r->sha1, 0, NULL);\n-\tif (lock) {\n-\t\tunlink_or_warn(git_path(\"%s\", r->name));\n-\t\tunlock_ref(lock);\n-\t\ttry_remove_empty_parents(r->name);\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_delete(transaction, r->name, r->sha1,\n+\t\t\t\t   REF_ISPRUNING, 1, &err) ||\n+\t    ref_transaction_commit(transaction, NULL, &err)) {\n+\t\tref_transaction_free(transaction);\n+\t\terror(\"%s\", err.buf);\n+\t\tstrbuf_release(&err);\n+\t\treturn;\n \t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\ttry_remove_empty_parents(r->name);\n }\n \n static void prune_refs(struct ref_to_prune *r)\n@@ -3598,8 +3611,9 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (update->lock) {\n-\t\t\tdelnames[delnum++] = update->lock->ref_name;\n \t\t\tret |= delete_ref_loose(update->lock, update->type);\n+\t\t\tif (!(update->flags & REF_ISPRUNING))\n+\t\t\t\tdelnames[delnum++] = update->lock->ref_name;\n \t\t}\n \t}\n \ndiff --git a/refs.h b/refs.h\nindex 65dd593..69ef28c 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -170,9 +170,18 @@ extern int ref_exists(const char *);\n  */\n extern int peel_ref(const char *refname, unsigned char *sha1);\n \n-/** Locks any ref (for 'HEAD' type refs). */\n+/*\n+ * Flags controlling lock_any_ref_for_update(), ref_transaction_update(),\n+ * ref_transaction_create(), etc.\n+ * REF_NODEREF: act on the ref directly, instead of dereferencing\n+ *              symbolic references.\n+ *\n+ * Flags >= 0x100 are reserved for internal use.\n+ */\n #define REF_NODEREF\t0x01\n-/* errno is set to something meaningful on failure */\n+/*\n+ * This function sets errno to something meaningful on failure.\n+ */\n extern struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t\tconst unsigned char *old_sha1,\n \t\t\t\t\t\tint flags, int *type_p);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248758","messageId":"20140902211017.GU18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 20/22] refs.c: make delete_ref use a transaction","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:10:17Z","receivedAt":"2014-09-02T21:10:17Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 30 Apr 2014 09:22:45 -0700\n\nChange delete_ref to use a ref transaction for the deletion. At the same time\nsince we no longer have any callers of repack_without_ref we can now delete\nthis function.\n\nChange delete_ref to return 0 on success and 1 on failure instead of the\nprevious 0 on success either 1 or -1 on failure.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 35 ++++++++++++++---------------------\n 1 file changed, 14 insertions(+), 21 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 22eb3dd..7235574 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2548,11 +2548,6 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \treturn ret;\n }\n \n-static int repack_without_ref(const char *refname)\n-{\n-\treturn repack_without_refs(&refname, 1, NULL);\n-}\n-\n static int delete_ref_loose(struct ref_lock *lock, int flag)\n {\n \tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n@@ -2570,24 +2565,22 @@ static int delete_ref_loose(struct ref_lock *lock, int flag)\n \n int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n {\n-\tstruct ref_lock *lock;\n-\tint ret = 0, flag = 0;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n \n-\tlock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);\n-\tif (!lock)\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_delete(transaction, refname, sha1, delopt,\n+\t\t\t\t   sha1 && !is_null_sha1(sha1), &err) ||\n+\t    ref_transaction_commit(transaction, NULL, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tref_transaction_free(transaction);\n+\t\tstrbuf_release(&err);\n \t\treturn 1;\n-\tret |= delete_ref_loose(lock, flag);\n-\n-\t/* removing the loose one could have resurrected an earlier\n-\t * packed one.  Also, if it was not loose we need to repack\n-\t * without it.\n-\t */\n-\tret |= repack_without_ref(lock->ref_name);\n-\n-\tunlink_or_warn(git_path(\"logs/%s\", lock->ref_name));\n-\tclear_loose_ref_cache(&ref_cache);\n-\tunlock_ref(lock);\n-\treturn ret;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\treturn 0;\n }\n \n /*\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248759","messageId":"20140902211052.GV18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 21/22] update-ref --stdin: narrow scope of err strbuf","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:10:52Z","receivedAt":"2014-09-02T21:10:52Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Making the strbuf local in each function that needs to print errors\nsaves the reader from having to think about action at a distance,\nsuch as\n\n * errors piling up and being concatenated with no newline between\n   them\n * errors unhandled in one function, to be later handled in another\n * concurrency issues, if this code starts using threads some day\n\nNo functional change intended.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n builtin/update-ref.c | 11 ++++++++++-\n 1 file changed, 10 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 96a53b9..866bbee 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -16,7 +16,6 @@ static struct ref_transaction *transaction;\n \n static char line_termination = '\\n';\n static int update_flags;\n-static struct strbuf err = STRBUF_INIT;\n \n /*\n  * Parse one whitespace- or NUL-terminated, possibly C-quoted argument\n@@ -179,6 +178,7 @@ static int parse_next_sha1(struct strbuf *input, const char **next,\n \n static const char *parse_cmd_update(struct strbuf *input, const char *next)\n {\n+\tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n \tunsigned char new_sha1[20];\n \tunsigned char old_sha1[20];\n@@ -204,12 +204,14 @@ static const char *parse_cmd_update(struct strbuf *input, const char *next)\n \n \tupdate_flags = 0;\n \tfree(refname);\n+\tstrbuf_release(&err);\n \n \treturn next;\n }\n \n static const char *parse_cmd_create(struct strbuf *input, const char *next)\n {\n+\tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n \tunsigned char new_sha1[20];\n \n@@ -232,12 +234,14 @@ static const char *parse_cmd_create(struct strbuf *input, const char *next)\n \n \tupdate_flags = 0;\n \tfree(refname);\n+\tstrbuf_release(&err);\n \n \treturn next;\n }\n \n static const char *parse_cmd_delete(struct strbuf *input, const char *next)\n {\n+\tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n \tunsigned char old_sha1[20];\n \tint have_old;\n@@ -264,12 +268,14 @@ static const char *parse_cmd_delete(struct strbuf *input, const char *next)\n \n \tupdate_flags = 0;\n \tfree(refname);\n+\tstrbuf_release(&err);\n \n \treturn next;\n }\n \n static const char *parse_cmd_verify(struct strbuf *input, const char *next)\n {\n+\tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n \tunsigned char new_sha1[20];\n \tunsigned char old_sha1[20];\n@@ -297,6 +303,7 @@ static const char *parse_cmd_verify(struct strbuf *input, const char *next)\n \n \tupdate_flags = 0;\n \tfree(refname);\n+\tstrbuf_release(&err);\n \n \treturn next;\n }\n@@ -365,6 +372,8 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \t\tdie(\"Refusing to perform update with empty message.\");\n \n \tif (read_stdin) {\n+\t\tstruct strbuf err = STRBUF_INIT;\n+\n \t\ttransaction = ref_transaction_begin(&err);\n \t\tif (!transaction)\n \t\t\tdie(\"%s\", err.buf);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"248761","messageId":"20140902211121.GW18279@google.com","threadId":"37248","inReplyTo":"20140902205841.GA18279@google.com","subject":"[PATCH 22/22] update-ref --stdin: pass transaction around explicitly","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-02T21:11:21Z","receivedAt":"2014-09-02T21:11:21Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"This makes it more obvious at a glance where the output of functions\nparsing the --stdin stream goes.\n\nNo functional change intended.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\nThanks for reading.\n\n builtin/update-ref.c | 27 +++++++++++++++------------\n 1 file changed, 15 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 866bbee..54a48c0 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -12,8 +12,6 @@ static const char * const git_update_ref_usage[] = {\n \tNULL\n };\n \n-static struct ref_transaction *transaction;\n-\n static char line_termination = '\\n';\n static int update_flags;\n \n@@ -176,7 +174,8 @@ static int parse_next_sha1(struct strbuf *input, const char **next,\n  * depending on how line_termination is set.\n  */\n \n-static const char *parse_cmd_update(struct strbuf *input, const char *next)\n+static const char *parse_cmd_update(struct ref_transaction *transaction,\n+\t\t\t\t    struct strbuf *input, const char *next)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -209,7 +208,8 @@ static const char *parse_cmd_update(struct strbuf *input, const char *next)\n \treturn next;\n }\n \n-static const char *parse_cmd_create(struct strbuf *input, const char *next)\n+static const char *parse_cmd_create(struct ref_transaction *transaction,\n+\t\t\t\t    struct strbuf *input, const char *next)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -239,7 +239,8 @@ static const char *parse_cmd_create(struct strbuf *input, const char *next)\n \treturn next;\n }\n \n-static const char *parse_cmd_delete(struct strbuf *input, const char *next)\n+static const char *parse_cmd_delete(struct ref_transaction *transaction,\n+\t\t\t\t    struct strbuf *input, const char *next)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -273,7 +274,8 @@ static const char *parse_cmd_delete(struct strbuf *input, const char *next)\n \treturn next;\n }\n \n-static const char *parse_cmd_verify(struct strbuf *input, const char *next)\n+static const char *parse_cmd_verify(struct ref_transaction *transaction,\n+\t\t\t\t    struct strbuf *input, const char *next)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -317,7 +319,7 @@ static const char *parse_cmd_option(struct strbuf *input, const char *next)\n \treturn next + 8;\n }\n \n-static void update_refs_stdin(void)\n+static void update_refs_stdin(struct ref_transaction *transaction)\n {\n \tstruct strbuf input = STRBUF_INIT;\n \tconst char *next;\n@@ -332,13 +334,13 @@ static void update_refs_stdin(void)\n \t\telse if (isspace(*next))\n \t\t\tdie(\"whitespace before command: %s\", next);\n \t\telse if (starts_with(next, \"update \"))\n-\t\t\tnext = parse_cmd_update(&input, next + 7);\n+\t\t\tnext = parse_cmd_update(transaction, &input, next + 7);\n \t\telse if (starts_with(next, \"create \"))\n-\t\t\tnext = parse_cmd_create(&input, next + 7);\n+\t\t\tnext = parse_cmd_create(transaction, &input, next + 7);\n \t\telse if (starts_with(next, \"delete \"))\n-\t\t\tnext = parse_cmd_delete(&input, next + 7);\n+\t\t\tnext = parse_cmd_delete(transaction, &input, next + 7);\n \t\telse if (starts_with(next, \"verify \"))\n-\t\t\tnext = parse_cmd_verify(&input, next + 7);\n+\t\t\tnext = parse_cmd_verify(transaction, &input, next + 7);\n \t\telse if (starts_with(next, \"option \"))\n \t\t\tnext = parse_cmd_option(&input, next + 7);\n \t\telse\n@@ -373,6 +375,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \n \tif (read_stdin) {\n \t\tstruct strbuf err = STRBUF_INIT;\n+\t\tstruct ref_transaction *transaction;\n \n \t\ttransaction = ref_transaction_begin(&err);\n \t\tif (!transaction)\n@@ -381,7 +384,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n \t\tif (end_null)\n \t\t\tline_termination = '\\0';\n-\t\tupdate_refs_stdin();\n+\t\tupdate_refs_stdin(transaction);\n \t\tif (ref_transaction_commit(transaction, msg, &err))\n \t\t\tdie(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249195","messageId":"20140911030318.GD18279@google.com","threadId":"37248","inReplyTo":"20140820231723.GF20185@google.com","subject":"[PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:03:18Z","receivedAt":"2014-09-11T03:03:18Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jonathan Nieder wrote:\n\n> The next series from Ronnie's collection is available at\n> https://code-review.googlesource.com/#/q/topic:ref-transaction in case\n> someone wants a fresh series to look at.\n\nHere is the outcome of that review.  It could use another set of eyes\n(hint, hint) but should be mostly ready.  Interdiff below.  This is meant\nto replace rs/ref-transaction in 'pu' and I'd prefer to wait a little\nfor more comments before merging to 'next'.\n\nThese patches are also available from the git repository at\n\n  git://repo.or.cz/git/jrn.git tags/rs/ref-transaction\n\n\"Use ref transactions\", part 3\n\nRonnie explains:\n\n\tThis is the third and final part of the original 48 patch\n\tseries for basic transaction support.  This version implements\n\tsome changes suggested by mhagger for the warn_if_unremovable\n\tchanges.  It also adds a new patch \"fix handling of badly\n\tnamed refs\" that repairs the handling of badly named refs.\n\nThis includes some improvements to the transaction API (in\nparticular allowing different reflog messages per ref update in\na transaction), some cleanups and consistency improvements, and\npreparation for an implementation of ref renaming in terms of\nthe transaction API.\n\nIt also improves handling of refs with invalid names.  Today \"git\nbranch --list\" and \"git for-each-ref\" do not provide a way to discover\nrefs with invalid names and \"git branch -d\" and \"git update-ref -d\"\ncannot delete them.  After this series, such bad refs will be\ndiscoverable and deletable again as in olden times.\n\nJonathan Nieder (5):\n  mv test: recreate mod/ directory instead of relying on stale copy\n  branch -d: avoid repeated symref resolution\n  refs.c: do not permit err == NULL\n  lockfile: remove unable_to_lock_error\n  ref_transaction_commit: bail out on failure to remove a ref\n\nRonnie Sahlberg (14):\n  wrapper.c: remove/unlink_or_warn: simplify, treat ENOENT as success\n  wrapper.c: add a new function unlink_or_msg\n  refs.c: add an err argument to delete_ref_loose\n  refs.c: pass the ref log message to _create/delete/update instead of\n    _commit\n  rename_ref: don't ask read_ref_full where the ref came from\n  refs.c: move the check for valid refname to lock_ref_sha1_basic\n  refs.c: call lock_ref_sha1_basic directly from commit\n  refs.c: pass a skip list to name_conflict_fn\n  refs.c: ref_transaction_commit: distinguish name conflicts from other\n    errors\n  fetch.c: change s_update_ref to use a ref transaction\n  refs.c: make write_ref_sha1 static\n  refs.c: change resolve_ref_unsafe reading argument to be a flags field\n  refs.c: fix handling of badly named refs\n  for-each-ref.c: improve message before aborting on broken ref\n\n branch.c                    |   6 +-\n builtin/blame.c             |   2 +-\n builtin/branch.c            |  19 ++-\n builtin/checkout.c          |   6 +-\n builtin/clone.c             |   2 +-\n builtin/commit.c            |   6 +-\n builtin/fetch.c             |  34 +++--\n builtin/fmt-merge-msg.c     |   2 +-\n builtin/for-each-ref.c      |  12 +-\n builtin/fsck.c              |   2 +-\n builtin/log.c               |   3 +-\n builtin/merge.c             |   2 +-\n builtin/notes.c             |   2 +-\n builtin/receive-pack.c      |   9 +-\n builtin/remote.c            |   5 +-\n builtin/replace.c           |   5 +-\n builtin/show-branch.c       |   6 +-\n builtin/symbolic-ref.c      |   2 +-\n builtin/tag.c               |   4 +-\n builtin/update-ref.c        |  16 ++-\n bundle.c                    |   2 +-\n cache.h                     |  31 +++--\n fast-import.c               |   8 +-\n git-compat-util.h           |  16 ++-\n http-backend.c              |   3 +-\n lockfile.c                  |  10 --\n notes-merge.c               |   2 +-\n reflog-walk.c               |   5 +-\n refs.c                      | 321 ++++++++++++++++++++++++++++++--------------\n refs.h                      |  18 ++-\n remote.c                    |  10 +-\n sequencer.c                 |   8 +-\n t/t1400-update-ref.sh       |  16 +++\n t/t1402-check-ref-format.sh |  48 +++++++\n t/t3200-branch.sh           |   9 ++\n t/t7001-mv.sh               |  15 ++-\n transport-helper.c          |   4 +-\n transport.c                 |   5 +-\n upload-pack.c               |   2 +-\n walker.c                    |   5 +-\n wrapper.c                   |  28 ++--\n wt-status.c                 |   2 +-\n 42 files changed, 487 insertions(+), 226 deletions(-)\n---\nChanges since last appearance:\n\ndiff --git c/branch.c w/branch.c\nindex 76a8ec9..ba3e1c1 100644\n--- c/branch.c\n+++ w/branch.c\n@@ -186,7 +186,7 @@ int validate_new_branchname(const char *name, struct strbuf *ref,\n \t\tconst char *head;\n \t\tunsigned char sha1[20];\n \n-\t\thead = resolve_ref_unsafe(\"HEAD\", sha1, 0, NULL);\n+\t\thead = resolve_ref_unsafe(\"HEAD\", sha1, NULL, 0);\n \t\tif (!is_bare_repository() && head && !strcmp(head, ref->buf))\n \t\t\tdie(_(\"Cannot force update the current branch.\"));\n \t}\ndiff --git c/builtin/blame.c w/builtin/blame.c\nindex 1568bd8..b8bec0a 100644\n--- c/builtin/blame.c\n+++ w/builtin/blame.c\n@@ -2292,7 +2292,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,\n \tcommit->object.type = OBJ_COMMIT;\n \tparent_tail = &commit->parents;\n \n-\tif (!resolve_ref_unsafe(\"HEAD\", head_sha1, RESOLVE_REF_READING, NULL))\n+\tif (!resolve_ref_unsafe(\"HEAD\", head_sha1, NULL, RESOLVE_REF_READING))\n \t\tdie(\"no such ref: HEAD\");\n \n \tparent_tail = append_parent(parent_tail, head_sha1);\ndiff --git c/builtin/branch.c w/builtin/branch.c\nindex 5c95656..5d5bc56 100644\n--- c/builtin/branch.c\n+++ w/builtin/branch.c\n@@ -130,7 +130,7 @@ static int branch_merged(int kind, const char *name,\n \t\t    branch->merge[0]->dst &&\n \t\t    (reference_name = reference_name_to_free =\n \t\t     resolve_refdup(branch->merge[0]->dst, sha1,\n-\t\t\t\t    RESOLVE_REF_READING, NULL)) != NULL)\n+\t\t\t\t    NULL, RESOLVE_REF_READING)) != NULL)\n \t\t\treference_rev = lookup_commit_reference(sha1);\n \t}\n \tif (!reference_rev)\n@@ -234,10 +234,13 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\tfree(name);\n \n \t\tname = mkpathdup(fmt, bname.buf);\n-\t\ttarget = resolve_ref_unsafe(name, sha1,\n-\t\t\t\t\t    RESOLVE_REF_ALLOW_BAD_NAME, &flags);\n+\t\ttarget = resolve_ref_unsafe(name, sha1, &flags,\n+\t\t\t\t\t    RESOLVE_REF_READING\n+\t\t\t\t\t    | RESOLVE_REF_NODEREF\n+\t\t\t\t\t    | RESOLVE_REF_ALLOW_BAD_NAME);\n \t\tif (!target ||\n-\t\t    (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) {\n+\t\t    (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&\n+\t\t     is_null_sha1(sha1))) {\n \t\t\terror(remote_branch\n \t\t\t      ? _(\"remote branch '%s' not found.\")\n \t\t\t      : _(\"branch '%s' not found.\"), bname.buf);\n@@ -245,14 +248,14 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (!(flags & REF_ISSYMREF) &&\n+\t\tif (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&\n \t\t    check_branch_commit(bname.buf, name, sha1, head_rev, kinds,\n \t\t\t\t\tforce)) {\n \t\t\tret = 1;\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (delete_ref(name, sha1, REF_NODEREF)) {\n+\t\tif (delete_ref(name, sha1, REF_NODEREF|REF_BADNAMEOK)) {\n \t\t\terror(remote_branch\n \t\t\t      ? _(\"Error deleting remote branch '%s'\")\n \t\t\t      : _(\"Error deleting branch '%s'\"),\n@@ -298,7 +301,7 @@ static char *resolve_symref(const char *src, const char *prefix)\n \tint flag;\n \tconst char *dst, *cp;\n \n-\tdst = resolve_ref_unsafe(src, sha1, 0, &flag);\n+\tdst = resolve_ref_unsafe(src, sha1, &flag, 0);\n \tif (!(dst && (flag & REF_ISSYMREF)))\n \t\treturn NULL;\n \tif (prefix && (cp = skip_prefix(dst, prefix)))\n@@ -864,7 +867,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \ttrack = git_branch_track;\n \n-\thead = resolve_refdup(\"HEAD\", head_sha1, 0, NULL);\n+\thead = resolve_refdup(\"HEAD\", head_sha1, NULL, 0);\n \tif (!head)\n \t\tdie(_(\"Failed to resolve HEAD as a valid ref.\"));\n \tif (!strcmp(head, \"HEAD\")) {\ndiff --git c/builtin/checkout.c w/builtin/checkout.c\nindex f1dc56e..64af1f7 100644\n--- c/builtin/checkout.c\n+++ w/builtin/checkout.c\n@@ -356,7 +356,7 @@ static int checkout_paths(const struct checkout_opts *opts,\n \t    commit_locked_index(lock_file))\n \t\tdie(_(\"unable to write new index file\"));\n \n-\tread_ref_full(\"HEAD\", rev, 0, &flag);\n+\tread_ref_full(\"HEAD\", rev, &flag, 0);\n \thead = lookup_commit_reference_gently(rev, 1);\n \n \terrs |= post_checkout_hook(head, head, 0);\n@@ -771,7 +771,7 @@ static int switch_branches(const struct checkout_opts *opts,\n \tunsigned char rev[20];\n \tint flag, writeout_error = 0;\n \tmemset(&old, 0, sizeof(old));\n-\told.path = path_to_free = resolve_refdup(\"HEAD\", rev, 0, &flag);\n+\told.path = path_to_free = resolve_refdup(\"HEAD\", rev, &flag, 0);\n \told.commit = lookup_commit_reference_gently(rev, 1);\n \tif (!(flag & REF_ISSYMREF))\n \t\told.path = NULL;\n@@ -1068,7 +1068,7 @@ static int checkout_branch(struct checkout_opts *opts,\n \t\tunsigned char rev[20];\n \t\tint flag;\n \n-\t\tif (!read_ref_full(\"HEAD\", rev, 0, &flag) &&\n+\t\tif (!read_ref_full(\"HEAD\", rev, &flag, 0) &&\n \t\t    (flag & REF_ISSYMREF) && is_null_sha1(rev))\n \t\t\treturn switch_unborn_to_new_branch(opts);\n \t}\ndiff --git c/builtin/clone.c w/builtin/clone.c\nindex f7307e6..12a78e1 100644\n--- c/builtin/clone.c\n+++ w/builtin/clone.c\n@@ -622,7 +622,7 @@ static int checkout(void)\n \tif (option_no_checkout)\n \t\treturn 0;\n \n-\thead = resolve_refdup(\"HEAD\", sha1, RESOLVE_REF_READING, NULL);\n+\thead = resolve_refdup(\"HEAD\", sha1, NULL, RESOLVE_REF_READING);\n \tif (!head) {\n \t\twarning(_(\"remote HEAD refers to nonexistent ref, \"\n \t\t\t  \"unable to checkout.\\n\"));\ndiff --git c/builtin/commit.c w/builtin/commit.c\nindex d23e876..3536330 100644\n--- c/builtin/commit.c\n+++ w/builtin/commit.c\n@@ -1468,7 +1468,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,\n \trev.diffopt.break_opt = 0;\n \tdiff_setup_done(&rev.diffopt);\n \n-\thead = resolve_ref_unsafe(\"HEAD\", junk_sha1, 0, NULL);\n+\thead = resolve_ref_unsafe(\"HEAD\", junk_sha1, NULL, 0);\n \tprintf(\"[%s%s \",\n \t\tstarts_with(head, \"refs/heads/\") ?\n \t\t\thead + 11 :\ndiff --git c/builtin/fetch.c w/builtin/fetch.c\nindex 52f1ebc..2e3bc73 100644\n--- c/builtin/fetch.c\n+++ w/builtin/fetch.c\n@@ -398,6 +398,7 @@ static int s_update_ref(const char *action,\n \t\tgoto fail;\n \n \tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n \treturn 0;\n fail:\n \tref_transaction_free(transaction);\n@@ -686,9 +687,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,\n \t\t\t}\n \t\t}\n \t}\n+\n \tif (rc & STORE_REF_ERROR_DF_CONFLICT)\n \t\terror(_(\"some local refs could not be updated; try running\\n\"\n-\t\t      \"'git remote prune %s' to remove any old, conflicting \"\n+\t\t      \" 'git remote prune %s' to remove any old, conflicting \"\n \t\t      \"branches\"), remote_name);\n \n  abort:\ndiff --git c/builtin/fmt-merge-msg.c w/builtin/fmt-merge-msg.c\nindex d8ab177..b2355ad 100644\n--- c/builtin/fmt-merge-msg.c\n+++ w/builtin/fmt-merge-msg.c\n@@ -602,7 +602,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \n \t/* get current branch */\n \tcurrent_branch = current_branch_to_free =\n-\t\tresolve_refdup(\"HEAD\", head_sha1, RESOLVE_REF_READING, NULL);\n+\t\tresolve_refdup(\"HEAD\", head_sha1, NULL, RESOLVE_REF_READING);\n \tif (!current_branch)\n \t\tdie(\"No current branch\");\n \tif (starts_with(current_branch, \"refs/heads/\"))\ndiff --git c/builtin/for-each-ref.c w/builtin/for-each-ref.c\nindex 7c5b479..090390c 100644\n--- c/builtin/for-each-ref.c\n+++ w/builtin/for-each-ref.c\n@@ -650,7 +650,7 @@ static void populate_value(struct refinfo *ref)\n \tif (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {\n \t\tunsigned char unused1[20];\n \t\tref->symref = resolve_refdup(ref->refname, unused1,\n-\t\t\t\t\t     RESOLVE_REF_READING, NULL);\n+\t\t\t\t\t     NULL, RESOLVE_REF_READING);\n \t\tif (!ref->symref)\n \t\t\tref->symref = \"\";\n \t}\n@@ -709,7 +709,7 @@ static void populate_value(struct refinfo *ref)\n \t\t\tunsigned char sha1[20];\n \n \t\t\thead = resolve_ref_unsafe(\"HEAD\", sha1,\n-\t\t\t\t\t\t  RESOLVE_REF_READING, NULL);\n+\t\t\t\t\t\t  NULL, RESOLVE_REF_READING);\n \t\t\tif (!strcmp(ref->refname, head))\n \t\t\t\tv->s = \"*\";\n \t\t\telse\n@@ -853,6 +853,12 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f\n \tstruct refinfo *ref;\n \tint cnt;\n \n+\tif ((flag & REF_ISBROKEN) &&\n+\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\t  warning(\"ignoring ref with broken name %s\", refname);\n+\t\t  return 0;\n+\t}\n+\n \tif (*cb->grab_pattern) {\n \t\tconst char **pattern;\n \t\tint namelen = strlen(refname);\ndiff --git c/builtin/fsck.c w/builtin/fsck.c\nindex fc150c8..506e32b 100644\n--- c/builtin/fsck.c\n+++ w/builtin/fsck.c\n@@ -560,7 +560,7 @@ static int fsck_head_link(void)\n \tif (verbose)\n \t\tfprintf(stderr, \"Checking HEAD link\\n\");\n \n-\thead_points_at = resolve_ref_unsafe(\"HEAD\", head_sha1, 0, &flag);\n+\thead_points_at = resolve_ref_unsafe(\"HEAD\", head_sha1, &flag, 0);\n \tif (!head_points_at)\n \t\treturn error(\"Invalid HEAD\");\n \tif (!strcmp(head_points_at, \"HEAD\"))\ndiff --git c/builtin/log.c w/builtin/log.c\nindex 92db809..230a9ef 100644\n--- c/builtin/log.c\n+++ w/builtin/log.c\n@@ -1395,8 +1395,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \t\tif (check_head) {\n \t\t\tunsigned char sha1[20];\n \t\t\tconst char *ref;\n-\t\t\tref = resolve_ref_unsafe(\"HEAD\", sha1,\n-\t\t\t\t\t\t RESOLVE_REF_READING, NULL);\n+\t\t\tref = resolve_ref_unsafe(\"HEAD\", sha1, NULL,\n+\t\t\t\t\t\t RESOLVE_REF_READING);\n \t\t\tif (ref && starts_with(ref, \"refs/heads/\"))\n \t\t\t\tbranch_name = xstrdup(ref + strlen(\"refs/heads/\"));\n \t\t\telse\ndiff --git c/builtin/merge.c w/builtin/merge.c\nindex 428ca24..d262279 100644\n--- c/builtin/merge.c\n+++ w/builtin/merge.c\n@@ -1108,7 +1108,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t * Check if we are _not_ on a detached HEAD, i.e. if there is a\n \t * current branch.\n \t */\n-\tbranch = branch_to_free = resolve_refdup(\"HEAD\", head_sha1, 0, &flag);\n+\tbranch = branch_to_free = resolve_refdup(\"HEAD\", head_sha1, &flag, 0);\n \tif (branch && starts_with(branch, \"refs/heads/\"))\n \t\tbranch += 11;\n \tif (!branch || is_null_sha1(head_sha1))\ndiff --git c/builtin/notes.c w/builtin/notes.c\nindex 820c341..16df78c 100644\n--- c/builtin/notes.c\n+++ w/builtin/notes.c\n@@ -703,7 +703,7 @@ static int merge_commit(struct notes_merge_options *o)\n \tinit_notes(t, \"NOTES_MERGE_PARTIAL\", combine_notes_overwrite, 0);\n \n \to->local_ref = local_ref_to_free =\n-\t\tresolve_refdup(\"NOTES_MERGE_REF\", sha1, 0, NULL);\n+\t\tresolve_refdup(\"NOTES_MERGE_REF\", sha1, NULL, 0);\n \tif (!o->local_ref)\n \t\tdie(\"Failed to resolve NOTES_MERGE_REF\");\n \ndiff --git c/builtin/receive-pack.c w/builtin/receive-pack.c\nindex d1f4cf7..555a4a6 100644\n--- c/builtin/receive-pack.c\n+++ w/builtin/receive-pack.c\n@@ -656,7 +656,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)\n \tint flag;\n \n \tstrbuf_addf(&buf, \"%s%s\", get_git_namespace(), cmd->ref_name);\n-\tdst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag);\n+\tdst_name = resolve_ref_unsafe(buf.buf, sha1, &flag, 0);\n \tstrbuf_release(&buf);\n \n \tif (!(flag & REF_ISSYMREF))\n@@ -817,7 +817,7 @@ static void execute_commands(struct command *commands,\n \tcheck_aliased_updates(commands);\n \n \tfree(head_name_to_free);\n-\thead_name = head_name_to_free = resolve_refdup(\"HEAD\", sha1, 0, NULL);\n+\thead_name = head_name_to_free = resolve_refdup(\"HEAD\", sha1, NULL, 0);\n \n \tchecked_connectivity = 1;\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\ndiff --git c/builtin/remote.c w/builtin/remote.c\nindex be8ebac..6eaeee7 100644\n--- c/builtin/remote.c\n+++ w/builtin/remote.c\n@@ -568,8 +568,8 @@ static int read_remote_branches(const char *refname,\n \tstrbuf_addf(&buf, \"refs/remotes/%s/\", rename->old);\n \tif (starts_with(refname, buf.buf)) {\n \t\titem = string_list_append(rename->remote_branches, xstrdup(refname));\n-\t\tsymref = resolve_ref_unsafe(refname, orig_sha1,\n-\t\t\t\t\t    RESOLVE_REF_READING, &flag);\n+\t\tsymref = resolve_ref_unsafe(refname, orig_sha1, &flag,\n+\t\t\t\t\t    RESOLVE_REF_READING);\n \t\tif (flag & REF_ISSYMREF)\n \t\t\titem->util = xstrdup(symref);\n \t\telse\n@@ -705,7 +705,7 @@ static int mv(int argc, const char **argv)\n \t\tint flag = 0;\n \t\tunsigned char sha1[20];\n \n-\t\tread_ref_full(item->string, sha1, RESOLVE_REF_READING, &flag);\n+\t\tread_ref_full(item->string, sha1, &flag, RESOLVE_REF_READING);\n \t\tif (!(flag & REF_ISSYMREF))\n \t\t\tcontinue;\n \t\tif (delete_ref(item->string, NULL, REF_NODEREF))\ndiff --git c/builtin/replace.c w/builtin/replace.c\nindex df060f8..9d03b84 100644\n--- c/builtin/replace.c\n+++ w/builtin/replace.c\n@@ -170,7 +170,7 @@ static int replace_object_sha1(const char *object_ref,\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref, repl, prev,\n-\t\t\t\t   0, !is_null_sha1(prev), NULL, &err) ||\n+\t\t\t\t   0, 1, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n \t\tdie(\"%s\", err.buf);\n \ndiff --git c/builtin/show-branch.c w/builtin/show-branch.c\nindex a9a5eb3..ef6ea52 100644\n--- c/builtin/show-branch.c\n+++ w/builtin/show-branch.c\n@@ -727,8 +727,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)\n \t\tif (ac == 0) {\n \t\t\tstatic const char *fake_av[2];\n \n-\t\t\tfake_av[0] = resolve_refdup(\"HEAD\", sha1,\n-\t\t\t\t\t\t    RESOLVE_REF_READING, NULL);\n+\t\t\tfake_av[0] = resolve_refdup(\"HEAD\", sha1, NULL,\n+\t\t\t\t\t\t    RESOLVE_REF_READING);\n \t\t\tfake_av[1] = NULL;\n \t\t\tav = fake_av;\n \t\t\tac = 1;\n@@ -790,8 +790,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)\n \t\t}\n \t}\n \n-\thead_p = resolve_ref_unsafe(\"HEAD\", head_sha1,\n-\t\t\t\t    RESOLVE_REF_READING, NULL);\n+\thead_p = resolve_ref_unsafe(\"HEAD\", head_sha1, NULL,\n+\t\t\t\t    RESOLVE_REF_READING);\n \tif (head_p) {\n \t\thead_len = strlen(head_p);\n \t\tmemcpy(head, head_p, head_len + 1);\ndiff --git c/builtin/symbolic-ref.c w/builtin/symbolic-ref.c\nindex b6a711d..1dd04af 100644\n--- c/builtin/symbolic-ref.c\n+++ w/builtin/symbolic-ref.c\n@@ -13,7 +13,7 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print)\n {\n \tunsigned char sha1[20];\n \tint flag;\n-\tconst char *refname = resolve_ref_unsafe(HEAD, sha1, 0, &flag);\n+\tconst char *refname = resolve_ref_unsafe(HEAD, sha1, &flag, 0);\n \n \tif (!refname)\n \t\tdie(\"No such ref: %s\", HEAD);\ndiff --git c/builtin/update-ref.c w/builtin/update-ref.c\nindex 6c9be05..e379fdd 100644\n--- c/builtin/update-ref.c\n+++ w/builtin/update-ref.c\n@@ -419,7 +419,8 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \tif (no_deref)\n \t\tflags = REF_NODEREF;\n \tif (delete)\n-\t\treturn delete_ref(refname, oldval ? oldsha1 : NULL, flags);\n+\t\treturn delete_ref(refname, oldval ? oldsha1 : NULL,\n+\t\t\t\t  flags|REF_BADNAMEOK);\n \telse\n \t\treturn update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,\n \t\t\t\t  flags, UPDATE_REFS_DIE_ON_ERR);\ndiff --git c/bundle.c w/bundle.c\nindex 8aaf5f8..32dd2f7 100644\n--- c/bundle.c\n+++ w/bundle.c\n@@ -311,7 +311,7 @@ int create_bundle(struct bundle_header *header, const char *path,\n \t\t\tcontinue;\n \t\tif (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)\n \t\t\tcontinue;\n-\t\tif (read_ref_full(e->name, sha1, RESOLVE_REF_READING, &flag))\n+\t\tif (read_ref_full(e->name, sha1, &flag, RESOLVE_REF_READING))\n \t\t\tflag = 0;\n \t\tdisplay_ref = (flag & REF_ISSYMREF) ? e->name : ref;\n \ndiff --git c/cache.h w/cache.h\nindex 3a4b087..995729f 100644\n--- c/cache.h\n+++ w/cache.h\n@@ -558,7 +558,6 @@ struct lock_file {\n };\n #define LOCK_DIE_ON_ERROR 1\n #define LOCK_NODEREF 2\n-extern int unable_to_lock_error(const char *path, int err);\n extern void unable_to_lock_message(const char *path, int err,\n \t\t\t\t   struct strbuf *buf);\n extern NORETURN void unable_to_lock_index_die(const char *path, int err);\n@@ -948,7 +947,7 @@ extern int get_sha1_hex(const char *hex, unsigned char *sha1);\n \n extern char *sha1_to_hex(const unsigned char *sha1);\t/* static buffer result! */\n extern int read_ref_full(const char *refname, unsigned char *sha1,\n-\t\t\t int flags, int *ref_flag);\n+\t\t\t int *flags, int resolve_flags);\n extern int read_ref(const char *refname, unsigned char *sha1);\n \n /*\n@@ -970,21 +969,27 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n  *   \"writing\" to the ref, the return value is the name of the ref\n  *   that will actually be created or changed.\n  *\n- * If ref_flag is non-NULL, set the value that it points to the\n+ * If flags is non-NULL, set the value that it points to the\n  * combination of REF_ISPACKED (if the reference was found among the\n- * packed references) and REF_ISSYMREF (if the initial reference was a\n- * symbolic reference).\n+ * packed references), REF_ISSYMREF (if the initial reference was a\n+ * symbolic reference) and REF_ISBROKEN (if the ref is malformed).\n  *\n  * If ref is not a properly-formatted, normalized reference, return\n  * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,\n  * give up and return NULL.\n  *\n- * errno is set to something meaningful on error.\n+ * RESOLVE_REF_ALLOW_BAD_NAME disables most of the ref name checking except\n+ * for names that are absolute paths or contain \"..\" components. For both\n+ * these cases the function will return NULL and set errno to EINVAL.\n+ * If the name is bad then the function will set the REF_ISBROKEN flag and\n+ * return the name, if the ref exists, or NULL, if it does not.\n+ * When this flag is set, any badly named refs will resolve to nullsha1.\n  */\n-#define RESOLVE_REF_READING        0x01\n-#define RESOLVE_REF_ALLOW_BAD_NAME 0x02\n-extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int flags, int *ref_flag);\n-extern char *resolve_refdup(const char *ref, unsigned char *sha1, int flags, int *ref_flag);\n+#define RESOLVE_REF_READING 0x01\n+#define RESOLVE_REF_NODEREF 0x02\n+#define RESOLVE_REF_ALLOW_BAD_NAME 0x04\n+extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int *flags, int resolve_flags);\n+extern char *resolve_refdup(const char *ref, unsigned char *sha1, int *flags, int resolve_flags);\n \n extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);\n extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);\ndiff --git c/git-compat-util.h w/git-compat-util.h\nindex 426bc98..5ee140c 100644\n--- c/git-compat-util.h\n+++ w/git-compat-util.h\n@@ -307,6 +307,8 @@ extern char *gitbasename(char *);\n \n #include \"wildmatch.h\"\n \n+struct strbuf;\n+\n /* General helper functions */\n extern void vreportf(const char *prefix, const char *err, va_list params);\n extern void vwritef(int fd, const char *prefix, const char *err, va_list params);\n@@ -704,19 +706,23 @@ void git_qsort(void *base, size_t nmemb, size_t size,\n #endif\n #endif\n \n-#include \"strbuf.h\"\n-\n /*\n  * Preserves errno, prints a message, but gives no warning for ENOENT.\n- * Always returns the return value of unlink(2).\n+ * Returns 0 on success which includes trying to unlink an object that does\n+ * not exist.\n  */\n int unlink_or_warn(const char *path);\n-/*\n- * Like unlink_or_warn but populates a strbuf\n- */\n+ /*\n+  * Tries to unlink file.  Returns 0 if unlink succeeded\n+  * or the file already didn't exist.  Returns -1 and\n+  * appends a message to err suitable for\n+  * 'error(\"%s\", err->buf)' on error.\n+  */\n int unlink_or_msg(const char *file, struct strbuf *err);\n /*\n- * Likewise for rmdir(2).\n+ * Preserves errno, prints a message, but gives no warning for ENOENT.\n+ * Returns 0 on success which includes trying to remove a directory that does\n+ * not exist.\n  */\n int rmdir_or_warn(const char *path);\n /*\ndiff --git c/http-backend.c w/http-backend.c\nindex 059f790..8f94f9b 100644\n--- c/http-backend.c\n+++ w/http-backend.c\n@@ -418,7 +418,7 @@ static int show_head_ref(const char *refname, const unsigned char *sha1,\n \tif (flag & REF_ISSYMREF) {\n \t\tunsigned char unused[20];\n \t\tconst char *target = resolve_ref_unsafe(refname, unused,\n-\t\t\t\t\t\tRESOLVE_REF_READING, NULL);\n+\t\t\t\t\t\tNULL, RESOLVE_REF_READING);\n \t\tconst char *target_nons = strip_namespace(target);\n \n \t\tstrbuf_addf(buf, \"ref: %s\\n\", target_nons);\ndiff --git c/lockfile.c w/lockfile.c\nindex a921d77..dbd4101 100644\n--- c/lockfile.c\n+++ w/lockfile.c\n@@ -176,16 +176,6 @@ void unable_to_lock_message(const char *path, int err, struct strbuf *buf)\n \t\t\t    absolute_path(path), strerror(err));\n }\n \n-int unable_to_lock_error(const char *path, int err)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tunable_to_lock_message(path, err, &buf);\n-\terror(\"%s\", buf.buf);\n-\tstrbuf_release(&buf);\n-\treturn -1;\n-}\n-\n NORETURN void unable_to_lock_index_die(const char *path, int err)\n {\n \tstruct strbuf buf = STRBUF_INIT;\ndiff --git c/notes-merge.c w/notes-merge.c\nindex 94a1a8a..ffca602 100644\n--- c/notes-merge.c\n+++ w/notes-merge.c\n@@ -549,7 +549,7 @@ int notes_merge(struct notes_merge_options *o,\n \t       o->local_ref, o->remote_ref);\n \n \t/* Dereference o->local_ref into local_sha1 */\n-\tif (read_ref_full(o->local_ref, local_sha1, 0, NULL))\n+\tif (read_ref_full(o->local_ref, local_sha1, NULL, 0))\n \t\tdie(\"Failed to resolve local notes ref '%s'\", o->local_ref);\n \telse if (!check_refname_format(o->local_ref, 0) &&\n \t\tis_null_sha1(local_sha1))\ndiff --git c/reflog-walk.c w/reflog-walk.c\nindex d80a42a..feeaf0a 100644\n--- c/reflog-walk.c\n+++ w/reflog-walk.c\n@@ -48,8 +48,8 @@ static struct complete_reflogs *read_complete_reflog(const char *ref)\n \t\tunsigned char sha1[20];\n \t\tconst char *name;\n \t\tvoid *name_to_free;\n-\t\tname = name_to_free = resolve_refdup(ref, sha1,\n-\t\t\t\t\t\t     RESOLVE_REF_READING, NULL);\n+\t\tname = name_to_free = resolve_refdup(ref, sha1, NULL,\n+\t\t\t\t\t\t     RESOLVE_REF_READING);\n \t\tif (name) {\n \t\t\tfor_each_reflog_ent(name, read_one_reflog, reflogs);\n \t\t\tfree(name_to_free);\n@@ -175,7 +175,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info,\n \t\tif (*branch == '\\0') {\n \t\t\tunsigned char sha1[20];\n \t\t\tfree(branch);\n-\t\t\tbranch = resolve_refdup(\"HEAD\", sha1, 0, NULL);\n+\t\t\tbranch = resolve_refdup(\"HEAD\", sha1, NULL, 0);\n \t\t\tif (!branch)\n \t\t\t\tdie (\"No current branch\");\n \ndiff --git c/refs.c w/refs.c\nindex b14bbf0..3b27758 100644\n--- c/refs.c\n+++ w/refs.c\n@@ -281,11 +281,15 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)\n }\n \n static struct ref_entry *create_ref_entry(const char *refname,\n-\t\t\t\t\t  const unsigned char *sha1, int flag)\n+\t\t\t\t\t  const unsigned char *sha1, int flag,\n+\t\t\t\t\t  int check_name)\n {\n \tint len;\n \tstruct ref_entry *ref;\n \n+\tif (check_name &&\n+\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))\n+\t\tdie(\"Reference has invalid format: '%s'\", refname);\n \tlen = strlen(refname) + 1;\n \tref = xmalloc(sizeof(struct ref_entry) + len);\n \thashcpy(ref->u.value.sha1, sha1);\n@@ -798,17 +802,16 @@ static int names_conflict(const char *refname1, const char *refname2)\n struct name_conflict_cb {\n \tconst char *refname;\n \tconst char *conflicting_refname;\n-\tconst char **skip;\n-\tint skipnum;\n+\tstruct string_list *skiplist;\n };\n \n static int name_conflict_fn(struct ref_entry *entry, void *cb_data)\n {\n \tstruct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;\n-\tint i;\n-\tfor (i = 0; i < data->skipnum; i++)\n-\t\tif (!strcmp(entry->name, data->skip[i]))\n-\t\t\treturn 0;\n+\n+\tif (data->skiplist &&\n+\t    string_list_has_string(data->skiplist, entry->name))\n+\t\treturn 0;\n \tif (names_conflict(data->refname, entry->name)) {\n \t\tdata->conflicting_refname = entry->name;\n \t\treturn 1;\n@@ -821,18 +824,17 @@ static int name_conflict_fn(struct ref_entry *entry, void *cb_data)\n  * conflicting with the name of an existing reference in dir.  If\n  * oldrefname is non-NULL, ignore potential conflicts with oldrefname\n  * (e.g., because oldrefname is scheduled for deletion in the same\n- * operation). skip contains a list of refs we want to skip checking for\n- * conflicts with.\n+ * operation). skiplist contains a list of refs we want to skip checking for\n+ * conflicts with. skiplist must be sorted.\n  */\n static int is_refname_available(const char *refname,\n \t\t\t\tstruct ref_dir *dir,\n-\t\t\t\tconst char **skip, int skipnum)\n+\t\t\t\tstruct string_list *skiplist)\n {\n \tstruct name_conflict_cb data;\n \tdata.refname = refname;\n \tdata.conflicting_refname = NULL;\n-\tdata.skip = skip;\n-\tdata.skipnum = skipnum;\n+\tdata.skiplist = skiplist;\n \n \tsort_ref_dir(dir);\n \tif (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, &data)) {\n@@ -1062,8 +1064,9 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)\n \n \t\t\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT)) {\n \t\t\t\tflag |= REF_ISBROKEN;\n+\t\t\t\thashclr(sha1);\n \t\t\t}\n-\t\t\tlast = create_ref_entry(refname, sha1, flag);\n+\t\t\tlast = create_ref_entry(refname, sha1, flag, 0);\n \t\t\tif (peeled == PEELED_FULLY ||\n \t\t\t    (peeled == PEELED_TAGS && starts_with(refname, \"refs/tags/\")))\n \t\t\t\tlast->flag |= REF_KNOWS_PEELED;\n@@ -1136,10 +1139,8 @@ void add_packed_ref(const char *refname, const unsigned char *sha1)\n \n \tif (!packed_ref_cache->lock)\n \t\tdie(\"internal error: packed refs not locked\");\n-\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))\n-\t\tdie(\"Reference has invalid format: '%s'\", refname);\n \tadd_ref(get_packed_ref_dir(packed_ref_cache),\n-\t\tcreate_ref_entry(refname, sha1, REF_ISPACKED));\n+\t\tcreate_ref_entry(refname, sha1, REF_ISPACKED, 1));\n }\n \n /*\n@@ -1197,17 +1198,19 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)\n \t\t\t\t\thashclr(sha1);\n \t\t\t\t\tflag |= REF_ISBROKEN;\n \t\t\t\t}\n-\t\t\t} else if (read_ref_full(refname.buf, sha1,\n-\t\t\t\t\t\t RESOLVE_REF_READING, &flag)) {\n+\t\t\t} else if (read_ref_full(refname.buf, sha1, &flag,\n+\t\t\t\t\t\t RESOLVE_REF_READING)) {\n \t\t\t\thashclr(sha1);\n \t\t\t\tflag |= REF_ISBROKEN;\n \t\t\t}\n-\t\t\tif (check_refname_format(refname.buf, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT)) {\n+\t\t\tif (check_refname_format(refname.buf,\n+\t\t\t\t\t\t REFNAME_ALLOW_ONELEVEL|\n+\t\t\t\t\t\t REFNAME_DOT_COMPONENT)) {\n \t\t\t\thashclr(sha1);\n \t\t\t\tflag |= REF_ISBROKEN;\n \t\t\t}\n \t\t\tadd_entry_to_dir(dir,\n-\t\t\t\t\t create_ref_entry(refname.buf, sha1, flag));\n+\t\t\t\t\t create_ref_entry(refname.buf, sha1, flag, 0));\n \t\t}\n \t\tstrbuf_setlen(&refname, dirnamelen);\n \t}\n@@ -1353,21 +1356,40 @@ static const char *handle_missing_loose_ref(const char *refname,\n \t}\n }\n \n+static int escapes_cwd(const char *path) {\n+\tchar *buf;\n+\tint result;\n+\n+\tif (is_absolute_path(path))\n+\t\treturn 1;\n+\tbuf = xmalloc(strlen(path) + 1);\n+\tresult = normalize_path_copy(buf, path);\n+\tfree(buf);\n+\treturn result;\n+}\n+\n /* This function needs to return a meaningful errno on failure */\n-const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int flags, int *ref_flag)\n+const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *flags, int resolve_flags)\n {\n \tint depth = MAXDEPTH;\n \tssize_t len;\n \tchar buffer[256];\n \tstatic char refname_buffer[256];\n+\tint bad_name = 0;\n \n-\tif (ref_flag)\n-\t\t*ref_flag = 0;\n+\tif (flags)\n+\t\t*flags = 0;\n \n-\tif (!(flags & RESOLVE_REF_ALLOW_BAD_NAME) &&\n-\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n-\t\terrno = EINVAL;\n-\t\treturn NULL;\n+\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\tif (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||\n+\t\t    escapes_cwd(refname)) {\n+\t\t\terrno = EINVAL;\n+\t\t\treturn NULL;\n+\t\t}\n+\t\thashclr(sha1);\n+\t\tif (flags)\n+\t\t\t*flags |= REF_ISBROKEN;\n+\t\tbad_name = 1;\n \t}\n \tfor (;;) {\n \t\tchar path[PATH_MAX];\n@@ -1395,8 +1417,8 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int fla\n \t\tif (lstat(path, &st) < 0) {\n \t\t\tif (errno == ENOENT)\n \t\t\t\treturn handle_missing_loose_ref(refname, sha1,\n-\t\t\t\t\t\tflags & RESOLVE_REF_READING,\n-\t\t\t\t\t\tref_flag);\n+\t\t\t\t\tresolve_flags & RESOLVE_REF_READING,\n+\t\t\t\t\tflags);\n \t\t\telse\n \t\t\t\treturn NULL;\n \t\t}\n@@ -1416,8 +1438,12 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int fla\n \t\t\t\t\t!check_refname_format(buffer, 0)) {\n \t\t\t\tstrcpy(refname_buffer, buffer);\n \t\t\t\trefname = refname_buffer;\n-\t\t\t\tif (ref_flag)\n-\t\t\t\t\t*ref_flag |= REF_ISSYMREF;\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISSYMREF;\n+\t\t\t\tif (resolve_flags & RESOLVE_REF_NODEREF) {\n+\t\t\t\t\thashclr(sha1);\n+\t\t\t\t\treturn refname;\n+\t\t\t\t}\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t}\n@@ -1462,31 +1488,37 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int fla\n \t\t\t */\n \t\t\tif (get_sha1_hex(buffer, sha1) ||\n \t\t\t    (buffer[40] != '\\0' && !isspace(buffer[40]))) {\n-\t\t\t\tif (ref_flag)\n-\t\t\t\t\t*ref_flag |= REF_ISBROKEN;\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISBROKEN;\n \t\t\t\terrno = EINVAL;\n \t\t\t\treturn NULL;\n \t\t\t}\n+\t\t\tif (bad_name)\n+\t\t\t\thashclr(sha1);\n \t\t\treturn refname;\n \t\t}\n-\t\tif (ref_flag)\n-\t\t\t*ref_flag |= REF_ISSYMREF;\n+\t\tif (flags)\n+\t\t\t*flags |= REF_ISSYMREF;\n \t\tbuf = buffer + 4;\n \t\twhile (isspace(*buf))\n \t\t\tbuf++;\n \t\tif (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {\n-\t\t\tif (ref_flag)\n-\t\t\t\t*ref_flag |= REF_ISBROKEN;\n+\t\t\tif (flags)\n+\t\t\t\t*flags |= REF_ISBROKEN;\n \t\t\terrno = EINVAL;\n \t\t\treturn NULL;\n \t\t}\n \t\trefname = strcpy(refname_buffer, buf);\n+\t\tif (resolve_flags & RESOLVE_REF_NODEREF) {\n+\t\t\thashclr(sha1);\n+\t\t\treturn refname;\n+\t\t}\n \t}\n }\n \n-char *resolve_refdup(const char *ref, unsigned char *sha1, int flags, int *ref_flag)\n+char *resolve_refdup(const char *ref, unsigned char *sha1, int *flags, int resolve_flags)\n {\n-\tconst char *ret = resolve_ref_unsafe(ref, sha1, flags, ref_flag);\n+\tconst char *ret = resolve_ref_unsafe(ref, sha1, flags, resolve_flags);\n \treturn ret ? xstrdup(ret) : NULL;\n }\n \n@@ -1497,22 +1529,22 @@ struct ref_filter {\n \tvoid *cb_data;\n };\n \n-int read_ref_full(const char *refname, unsigned char *sha1, int flags, int *ref_flag)\n+int read_ref_full(const char *refname, unsigned char *sha1, int *flags, int resolve_flags)\n {\n-\tif (resolve_ref_unsafe(refname, sha1, flags, ref_flag))\n+\tif (resolve_ref_unsafe(refname, sha1, flags, resolve_flags))\n \t\treturn 0;\n \treturn -1;\n }\n \n int read_ref(const char *refname, unsigned char *sha1)\n {\n-\treturn read_ref_full(refname, sha1, RESOLVE_REF_READING, NULL);\n+\treturn read_ref_full(refname, sha1, NULL, RESOLVE_REF_READING);\n }\n \n int ref_exists(const char *refname)\n {\n \tunsigned char sha1[20];\n-\treturn !!resolve_ref_unsafe(refname, sha1, RESOLVE_REF_READING, NULL);\n+\treturn !!resolve_ref_unsafe(refname, sha1, NULL, RESOLVE_REF_READING);\n }\n \n static int filter_refs(const char *refname, const unsigned char *sha1, int flags,\n@@ -1626,7 +1658,7 @@ int peel_ref(const char *refname, unsigned char *sha1)\n \t\treturn 0;\n \t}\n \n-\tif (read_ref_full(refname, base, RESOLVE_REF_READING, &flag))\n+\tif (read_ref_full(refname, base, &flag, RESOLVE_REF_READING))\n \t\treturn -1;\n \n \t/*\n@@ -1667,7 +1699,7 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha\n \tif (!(flags & REF_ISSYMREF))\n \t\treturn 0;\n \n-\tresolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);\n+\tresolves_to = resolve_ref_unsafe(refname, junk, NULL, 0);\n \tif (!resolves_to\n \t    || (d->refname\n \t\t? strcmp(resolves_to, d->refname)\n@@ -1792,7 +1824,7 @@ static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)\n \t\treturn 0;\n \t}\n \n-\tif (!read_ref_full(\"HEAD\", sha1, RESOLVE_REF_READING, &flag))\n+\tif (!read_ref_full(\"HEAD\", sha1, &flag, RESOLVE_REF_READING))\n \t\treturn fn(\"HEAD\", sha1, flag, cb_data);\n \n \treturn 0;\n@@ -1872,7 +1904,7 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)\n \tint flag;\n \n \tstrbuf_addf(&buf, \"%sHEAD\", get_git_namespace());\n-\tif (!read_ref_full(buf.buf, sha1, RESOLVE_REF_READING, &flag))\n+\tif (!read_ref_full(buf.buf, sha1, &flag, RESOLVE_REF_READING))\n \t\tret = fn(buf.buf, sha1, flag, cb_data);\n \tstrbuf_release(&buf);\n \n@@ -1967,8 +1999,8 @@ int refname_match(const char *abbrev_name, const char *full_name)\n static struct ref_lock *verify_lock(struct ref_lock *lock,\n \tconst unsigned char *old_sha1, int mustexist)\n {\n-\tif (read_ref_full(lock->ref_name, lock->old_sha1,\n-\t\t\t  mustexist ? RESOLVE_REF_READING : 0, NULL)) {\n+\tif (read_ref_full(lock->ref_name, lock->old_sha1, NULL,\n+\t\t\t  mustexist ? RESOLVE_REF_READING : 0)) {\n \t\tint save_errno = errno;\n \t\terror(\"Can't verify ref %s\", lock->ref_name);\n \t\tunlock_ref(lock);\n@@ -2041,8 +2073,8 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)\n \n \t\tthis_result = refs_found ? sha1_from_ref : sha1;\n \t\tmksnpath(fullref, sizeof(fullref), *p, len, str);\n-\t\tr = resolve_ref_unsafe(fullref, this_result,\n-\t\t\t\t       RESOLVE_REF_READING, &flag);\n+\t\tr = resolve_ref_unsafe(fullref, this_result, &flag,\n+\t\t\t\t       RESOLVE_REF_READING);\n \t\tif (r) {\n \t\t\tif (!refs_found++)\n \t\t\t\t*ref = xstrdup(r);\n@@ -2071,7 +2103,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n \t\tconst char *ref, *it;\n \n \t\tmksnpath(path, sizeof(path), *p, len, str);\n-\t\tref = resolve_ref_unsafe(path, hash, RESOLVE_REF_READING, NULL);\n+\t\tref = resolve_ref_unsafe(path, hash, NULL, RESOLVE_REF_READING);\n \t\tif (!ref)\n \t\t\tcontinue;\n \t\tif (reflog_exists(path))\n@@ -2097,8 +2129,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n  */\n static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\t\t\t    const unsigned char *old_sha1,\n-\t\t\t\t\t    int flags, int *type_p,\n-\t\t\t\t\t    const char **skip, int skipnum)\n+\t\t\t\t\t    struct string_list *skiplist,\n+\t\t\t\t\t    int flags, int *type_p)\n {\n \tchar *ref_file;\n \tconst char *orig_refname = refname;\n@@ -2106,22 +2138,23 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \tint last_errno = 0;\n \tint type, lflags;\n \tint mustexist = (old_sha1 && !is_null_sha1(old_sha1));\n-\tint resolve_flags;\n+\tint resolve_flags = 0;\n \tint missing = 0;\n \tint attempts_remaining = 3;\n-\tint bad_refname;\n \n \tlock = xcalloc(1, sizeof(struct ref_lock));\n \tlock->lock_fd = -1;\n \n-\tbad_refname = check_refname_format(refname, REFNAME_ALLOW_ONELEVEL);\n+\tif (flags & REF_BADNAMEOK)\n+\t\tresolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;\n \n-\tresolve_flags = RESOLVE_REF_ALLOW_BAD_NAME;\n \tif (mustexist)\n \t\tresolve_flags |= RESOLVE_REF_READING;\n+\tif (flags & REF_NODEREF)\n+\t\tresolve_flags |= RESOLVE_REF_NODEREF;\n \n-\trefname = resolve_ref_unsafe(refname, lock->old_sha1, resolve_flags,\n-\t\t\t\t     &type);\n+\trefname = resolve_ref_unsafe(refname, lock->old_sha1, &type,\n+\t\t\t\t     resolve_flags);\n \tif (!refname && errno == EISDIR) {\n \t\t/* we are trying to lock foo but we used to\n \t\t * have foo/bar which now does not exist;\n@@ -2135,7 +2168,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\tgoto error_return;\n \t\t}\n \t\trefname = resolve_ref_unsafe(orig_refname, lock->old_sha1,\n-\t\t\t\t\t     resolve_flags, &type);\n+\t\t\t\t\t     &type, resolve_flags);\n \t}\n \tif (type_p)\n \t    *type_p = type;\n@@ -2153,7 +2186,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t */\n \tif (missing &&\n \t     !is_refname_available(refname, get_packed_refs(&ref_cache),\n-\t\t\t\t   skip, skipnum)) {\n+\t\t\t\t   skiplist)) {\n \t\tlast_errno = ENOTDIR;\n \t\tgoto error_return;\n \t}\n@@ -2199,8 +2232,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\telse\n \t\t\tunable_to_lock_index_die(ref_file, errno);\n \t}\n-\tif (bad_refname)\n-\t\treturn lock;\n \treturn old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;\n \n  error_return:\n@@ -2213,7 +2244,7 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t const unsigned char *old_sha1,\n \t\t\t\t\t int flags, int *type_p)\n {\n-\treturn lock_ref_sha1_basic(refname, old_sha1, flags, type_p, NULL, 0);\n+\treturn lock_ref_sha1_basic(refname, old_sha1, NULL, flags, type_p);\n }\n \n /*\n@@ -2364,9 +2395,8 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)\n \t\tpacked_entry->flag = REF_ISPACKED | REF_KNOWS_PEELED;\n \t\thashcpy(packed_entry->u.value.sha1, entry->u.value.sha1);\n \t} else {\n-\t\tpacked_entry = create_ref_entry(entry->name,\n-\t\t\t\t\tentry->u.value.sha1,\n-\t\t\t\t\tREF_ISPACKED | REF_KNOWS_PEELED);\n+\t\tpacked_entry = create_ref_entry(entry->name, entry->u.value.sha1,\n+\t\t\t\t\t\tREF_ISPACKED | REF_KNOWS_PEELED, 0);\n \t\tadd_ref(cb->packed_refs, packed_entry);\n \t}\n \thashcpy(packed_entry->u.value.peeled, entry->u.value.peeled);\n@@ -2491,7 +2521,7 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)\n \t\tunsigned char sha1[20];\n \t\tint flags;\n \n-\t\tif (read_ref_full(entry->name, sha1, 0, &flags))\n+\t\tif (read_ref_full(entry->name, sha1, &flags, 0))\n \t\t\t/* We should at least have found the packed ref. */\n \t\t\tdie(\"Internal error\");\n \t\tif ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED)) {\n@@ -2530,6 +2560,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \tstruct string_list_item *ref_to_delete;\n \tint i, ret, removed = 0;\n \n+\tassert(err);\n+\n \t/* Look for a packed ref */\n \tfor (i = 0; i < n; i++)\n \t\tif (get_packed_ref(refnames[i]))\n@@ -2540,13 +2572,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \t\treturn 0; /* no refname exists in packed refs */\n \n \tif (lock_packed_refs(0)) {\n-\t\tif (err) {\n-\t\t\tunable_to_lock_message(git_path(\"packed-refs\"), errno,\n-\t\t\t\t\t       err);\n-\t\t\treturn -1;\n-\t\t}\n-\t\tunable_to_lock_error(git_path(\"packed-refs\"), errno);\n-\t\treturn error(\"cannot delete '%s' from packed refs\", refnames[i]);\n+\t\tunable_to_lock_message(git_path(\"packed-refs\"), errno, err);\n+\t\treturn -1;\n \t}\n \tpacked = get_packed_refs(&ref_cache);\n \n@@ -2572,7 +2599,7 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \n \t/* Write what remains */\n \tret = commit_packed_refs();\n-\tif (ret && err)\n+\tif (ret)\n \t\tstrbuf_addf(err, \"unable to overwrite old ref-pack file: %s\",\n \t\t\t    strerror(errno));\n \treturn ret;\n@@ -2580,6 +2607,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \n static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)\n {\n+\tassert(err);\n+\n \tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n \t\t/* loose */\n \t\tint res, i = strlen(lock->lk->filename) - 5; /* .lock */\n@@ -2678,25 +2707,31 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \tstruct stat loginfo;\n \tint log = !lstat(git_path(\"logs/%s\", oldrefname), &loginfo);\n \tconst char *symref = NULL;\n+\tstruct string_list skiplist = STRING_LIST_INIT_NODUP;\n \n \tif (log && S_ISLNK(loginfo.st_mode))\n \t\treturn error(\"reflog for %s is a symlink\", oldrefname);\n \n-\tsymref = resolve_ref_unsafe(oldrefname, orig_sha1,\n-\t\t\t\t    RESOLVE_REF_READING, &flag);\n+\tsymref = resolve_ref_unsafe(oldrefname, orig_sha1, &flag,\n+\t\t\t\t    RESOLVE_REF_READING);\n \tif (flag & REF_ISSYMREF)\n \t\treturn error(\"refname %s is a symbolic ref, renaming it is not supported\",\n \t\t\toldrefname);\n \tif (!symref)\n \t\treturn error(\"refname %s not found\", oldrefname);\n \n+\tstring_list_insert(&skiplist, oldrefname);\n \tif (!is_refname_available(newrefname, get_packed_refs(&ref_cache),\n-\t\t\t\t  &oldrefname, 1))\n+\t\t\t\t  &skiplist)) {\n+\t\tstring_list_clear(&skiplist, 0);\n \t\treturn 1;\n-\n+\t}\n \tif (!is_refname_available(newrefname, get_loose_refs(&ref_cache),\n-\t\t\t\t  &oldrefname, 1))\n+\t\t\t\t  &skiplist)) {\n+\t\tstring_list_clear(&skiplist, 0);\n \t\treturn 1;\n+\t}\n+\tstring_list_clear(&skiplist, 0);\n \n \tif (log && rename(git_path(\"logs/%s\", oldrefname), git_path(TMP_RENAMED_LOG)))\n \t\treturn error(\"unable to move logfile logs/%s to \"TMP_RENAMED_LOG\": %s\",\n@@ -2707,7 +2742,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \t\tgoto rollback;\n \t}\n \n-\tif (!read_ref_full(newrefname, sha1, RESOLVE_REF_READING, NULL) &&\n+\tif (!read_ref_full(newrefname, sha1, NULL, RESOLVE_REF_READING) &&\n \t    delete_ref(newrefname, sha1, REF_NODEREF)) {\n \t\tif (errno==EISDIR) {\n \t\t\tif (remove_empty_directories(git_path(\"%s\", newrefname))) {\n@@ -2725,7 +2760,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \n \tlogmoved = log;\n \n-\tlock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL, NULL, 0);\n+\tlock = lock_ref_sha1_basic(newrefname, NULL, NULL, 0, NULL);\n \tif (!lock) {\n \t\terror(\"unable to lock %s for update\", newrefname);\n \t\tgoto rollback;\n@@ -2740,7 +2775,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \treturn 0;\n \n  rollback:\n-\tlock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL, NULL, 0);\n+\tlock = lock_ref_sha1_basic(oldrefname, NULL, NULL, 0, NULL);\n \tif (!lock) {\n \t\terror(\"unable to lock %s for rollback\", oldrefname);\n \t\tgoto rollbacklog;\n@@ -2985,8 +3020,8 @@ static int write_ref_sha1(struct ref_lock *lock,\n \t\tunsigned char head_sha1[20];\n \t\tint head_flag;\n \t\tconst char *head_ref;\n-\t\thead_ref = resolve_ref_unsafe(\"HEAD\", head_sha1,\n-\t\t\t\t\t      RESOLVE_REF_READING, &head_flag);\n+\t\thead_ref = resolve_ref_unsafe(\"HEAD\", head_sha1, &head_flag,\n+\t\t\t\t\t      RESOLVE_REF_READING);\n \t\tif (head_ref && (head_flag & REF_ISSYMREF) &&\n \t\t    !strcmp(head_ref, lock->ref_name))\n \t\t\tlog_ref_write(\"HEAD\", lock->old_sha1, sha1, logmsg);\n@@ -3353,7 +3388,7 @@ static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data\n \t\t\t\tretval = do_for_each_reflog(name, fn, cb_data);\n \t\t\t} else {\n \t\t\t\tunsigned char sha1[20];\n-\t\t\t\tif (read_ref_full(name->buf, sha1, 0, NULL))\n+\t\t\t\tif (read_ref_full(name->buf, sha1, NULL, 0))\n \t\t\t\t\tretval = error(\"bad ref for %s\", name->buf);\n \t\t\t\telse\n \t\t\t\t\tretval = fn(name->buf, sha1, 0, cb_data);\n@@ -3423,6 +3458,8 @@ struct ref_transaction {\n \n struct ref_transaction *ref_transaction_begin(struct strbuf *err)\n {\n+\tassert(err);\n+\n \treturn xcalloc(1, sizeof(struct ref_transaction));\n }\n \n@@ -3462,6 +3499,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tassert(err);\n+\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: update called for transaction that is not open\");\n \n@@ -3470,7 +3509,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \n \tif (!is_null_sha1(new_sha1) &&\n \t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n-\t\tstrbuf_addf(err, \"Bad refname: %s\", refname);\n+\t\tstrbuf_addf(err, \"refusing to update ref with bad name %s\",\n+\t\t\t    refname);\n \t\treturn -1;\n \t}\n \n@@ -3493,6 +3533,8 @@ int ref_transaction_create(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tassert(err);\n+\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: create called for transaction that is not open\");\n \n@@ -3500,7 +3542,8 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \t\tdie(\"BUG: create ref with null new_sha1\");\n \n \tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n-\t\tstrbuf_addf(err, \"Bad refname: %s\", refname);\n+\t\tstrbuf_addf(err, \"refusing to create ref with bad name %s\",\n+\t\t\t    refname);\n \t\treturn -1;\n \t}\n \n@@ -3523,6 +3566,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tassert(err);\n+\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: delete called for transaction that is not open\");\n \n@@ -3585,13 +3630,14 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,\n \t\t\t\t\tstruct strbuf *err)\n {\n \tint i;\n+\n+\tassert(err);\n+\n \tfor (i = 1; i < n; i++)\n \t\tif (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {\n-\t\t\tconst char *str =\n-\t\t\t\t\"Multiple updates for ref '%s' not allowed.\";\n-\t\t\tif (err)\n-\t\t\t\tstrbuf_addf(err, str, updates[i]->refname);\n-\n+\t\t\tstrbuf_addf(err,\n+\t\t\t\t    \"Multiple updates for ref '%s' not allowed.\",\n+\t\t\t\t    updates[i]->refname);\n \t\t\treturn 1;\n \t\t}\n \treturn 0;\n@@ -3600,11 +3646,13 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,\n int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t   struct strbuf *err)\n {\n-\tint ret = 0, delnum = 0, i, df_conflict = 0;\n+\tint ret = 0, delnum = 0, i;\n \tconst char **delnames;\n \tint n = transaction->nr;\n \tstruct ref_update **updates = transaction->updates;\n \n+\tassert(err);\n+\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: commit called for transaction that is not open\");\n \n@@ -3631,16 +3679,15 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t\t\t   (update->have_old ?\n \t\t\t\t\t\t    update->old_sha1 :\n \t\t\t\t\t\t    NULL),\n+\t\t\t\t\t\t   NULL,\n \t\t\t\t\t\t   update->flags,\n-\t\t\t\t\t\t   &update->type,\n-\t\t\t\t\t\t   delnames, delnum);\n+\t\t\t\t\t\t   &update->type);\n \t\tif (!update->lock) {\n-\t\t\tif (errno == ENOTDIR)\n-\t\t\t\tdf_conflict = 1;\n-\t\t\tif (err)\n-\t\t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n-\t\t\t\t\t    update->refname);\n-\t\t\tret = -1;\n+\t\t\tint df_conflict = (errno == ENOTDIR);\n+\n+\t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n+\t\t\t\t    update->refname);\n+\t\t\tret = df_conflict ? UPDATE_REFS_NAME_CONFLICT : -1;\n \t\t\tgoto cleanup;\n \t\t}\n \t}\n@@ -3654,9 +3701,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t\t     update->msg);\n \t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n \t\t\tif (ret) {\n-\t\t\t\tif (err)\n-\t\t\t\t\tstrbuf_addf(err, \"Cannot update the ref '%s'.\",\n-\t\t\t\t\t\t    update->refname);\n+\t\t\t\tstrbuf_addf(err, \"Cannot update the ref '%s'.\",\n+\t\t\t\t\t    update->refname);\n \t\t\t\tret = -1;\n \t\t\t\tgoto cleanup;\n \t\t\t}\n@@ -3668,16 +3714,20 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (update->lock) {\n-\t\t\tif (delete_ref_loose(update->lock, update->type, err))\n+\t\t\tif (delete_ref_loose(update->lock, update->type, err)) {\n \t\t\t\tret = -1;\n+\t\t\t\tgoto cleanup;\n+\t\t\t}\n \n \t\t\tif (!(update->flags & REF_ISPRUNING))\n \t\t\t\tdelnames[delnum++] = update->lock->ref_name;\n \t\t}\n \t}\n \n-\tif (repack_without_refs(delnames, delnum, err))\n+\tif (repack_without_refs(delnames, delnum, err)) {\n \t\tret = -1;\n+\t\tgoto cleanup;\n+\t}\n \tfor (i = 0; i < delnum; i++)\n \t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n \tclear_loose_ref_cache(&ref_cache);\n@@ -3689,8 +3739,6 @@ cleanup:\n \t\tif (updates[i]->lock)\n \t\t\tunlock_ref(updates[i]->lock);\n \tfree(delnames);\n-\tif (df_conflict)\n-\t\tret = -2;\n \treturn ret;\n }\n \ndiff --git c/refs.h w/refs.h\nindex a7b8009..a96e174 100644\n--- c/refs.h\n+++ w/refs.h\n@@ -175,10 +175,12 @@ extern int peel_ref(const char *refname, unsigned char *sha1);\n  * ref_transaction_create(), etc.\n  * REF_NODEREF: act on the ref directly, instead of dereferencing\n  *              symbolic references.\n+ * REF_BADNAMEOK: allow locking a ref that has a bad name.\n  *\n  * Flags >= 0x100 are reserved for internal use.\n  */\n #define REF_NODEREF\t0x01\n+#define REF_BADNAMEOK\t0x02\n /*\n  * This function sets errno to something meaningful on failure.\n  */\n@@ -322,11 +324,10 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n  * Commit all of the changes that have been queued in transaction, as\n  * atomically as possible.  Return a nonzero value if there is a\n  * problem.\n- * If the transaction is already in failed state this function will return\n- * an error.\n+ *\n  * Function returns 0 on success, -1 for generic failures and\n- * UPDATE_REFS_NAME_CONFLICT (-2) if the failure was due to a name\n- * collision (ENOTDIR).\n+ * UPDATE_REFS_NAME_CONFLICT (-2) if the failure was due to a naming conflict.\n+ * For example, the ref names A and A/B conflict.\n  */\n #define UPDATE_REFS_NAME_CONFLICT -2\n int ref_transaction_commit(struct ref_transaction *transaction,\ndiff --git c/remote.c w/remote.c\nindex 2adb31c..67c375d 100644\n--- c/remote.c\n+++ w/remote.c\n@@ -486,7 +486,7 @@ static void read_config(void)\n \t\treturn;\n \tdefault_remote_name = \"origin\";\n \tcurrent_branch = NULL;\n-\thead_ref = resolve_ref_unsafe(\"HEAD\", sha1, 0, &flag);\n+\thead_ref = resolve_ref_unsafe(\"HEAD\", sha1, &flag, 0);\n \tif (head_ref && (flag & REF_ISSYMREF) &&\n \t    starts_with(head_ref, \"refs/heads/\")) {\n \t\tcurrent_branch =\n@@ -1121,8 +1121,8 @@ static char *guess_ref(const char *name, struct ref *peer)\n \tstruct strbuf buf = STRBUF_INIT;\n \tunsigned char sha1[20];\n \n-\tconst char *r = resolve_ref_unsafe(peer->name, sha1,\n-\t\t\t\t\t   RESOLVE_REF_READING, NULL);\n+\tconst char *r = resolve_ref_unsafe(peer->name, sha1, NULL,\n+\t\t\t\t\t   RESOLVE_REF_READING);\n \tif (!r)\n \t\treturn NULL;\n \n@@ -1183,8 +1183,8 @@ static int match_explicit(struct ref *src, struct ref *dst,\n \t\tunsigned char sha1[20];\n \t\tint flag;\n \n-\t\tdst_value = resolve_ref_unsafe(matched_src->name, sha1,\n-\t\t\t\t\t       RESOLVE_REF_READING, &flag);\n+\t\tdst_value = resolve_ref_unsafe(matched_src->name, sha1, &flag,\n+\t\t\t\t\t       RESOLVE_REF_READING);\n \t\tif (!dst_value ||\n \t\t    ((flag & REF_ISSYMREF) &&\n \t\t     !starts_with(dst_value, \"refs/heads/\")))\n@@ -1658,7 +1658,7 @@ static int ignore_symref_update(const char *refname)\n \tunsigned char sha1[20];\n \tint flag;\n \n-\tif (!resolve_ref_unsafe(refname, sha1, 0, &flag))\n+\tif (!resolve_ref_unsafe(refname, sha1, &flag, 0))\n \t\treturn 0; /* non-existing refs are OK */\n \treturn (flag & REF_ISSYMREF);\n }\ndiff --git c/sequencer.c w/sequencer.c\nindex ebe8713..6a05ad4 100644\n--- c/sequencer.c\n+++ w/sequencer.c\n@@ -366,7 +366,7 @@ static int is_index_unchanged(void)\n \tunsigned char head_sha1[20];\n \tstruct commit *head_commit;\n \n-\tif (!resolve_ref_unsafe(\"HEAD\", head_sha1, RESOLVE_REF_READING, NULL))\n+\tif (!resolve_ref_unsafe(\"HEAD\", head_sha1, NULL, RESOLVE_REF_READING))\n \t\treturn error(_(\"Could not resolve HEAD commit\\n\"));\n \n \thead_commit = lookup_commit(head_sha1);\n@@ -912,7 +912,7 @@ static int rollback_single_pick(void)\n \tif (!file_exists(git_path(\"CHERRY_PICK_HEAD\")) &&\n \t    !file_exists(git_path(\"REVERT_HEAD\")))\n \t\treturn error(_(\"no cherry-pick or revert in progress\"));\n-\tif (read_ref_full(\"HEAD\", head_sha1, 0, NULL))\n+\tif (read_ref_full(\"HEAD\", head_sha1, NULL, 0))\n \t\treturn error(_(\"cannot resolve HEAD\"));\n \tif (is_null_sha1(head_sha1))\n \t\treturn error(_(\"cannot abort from a branch yet to be born\"));\ndiff --git c/t/t1400-update-ref.sh w/t/t1400-update-ref.sh\nindex 0218e96..ff4607b 100755\n--- c/t/t1400-update-ref.sh\n+++ w/t/t1400-update-ref.sh\n@@ -110,6 +110,22 @@ test_expect_success \"delete symref without dereference when the referred ref is\n cp -f .git/HEAD.orig .git/HEAD\n git update-ref -d $m\n \n+test_expect_success 'update-ref -d is not confused by self-reference' '\n+\tgit symbolic-ref refs/heads/self refs/heads/self &&\n+\ttest_when_finished \"rm -f .git/refs/heads/self\" &&\n+\ttest_path_is_file .git/refs/heads/self &&\n+\ttest_must_fail git update-ref -d refs/heads/self &&\n+\ttest_path_is_file .git/refs/heads/self\n+'\n+\n+test_expect_success 'update-ref --no-deref -d can delete self-reference' '\n+\tgit symbolic-ref refs/heads/self refs/heads/self &&\n+\ttest_when_finished \"rm -f .git/refs/heads/self\" &&\n+\ttest_path_is_file .git/refs/heads/self &&\n+\tgit update-ref --no-deref -d refs/heads/self &&\n+\ttest_path_is_missing .git/refs/heads/self\n+'\n+\n test_expect_success '(not) create HEAD with old sha1' \"\n \ttest_must_fail git update-ref HEAD $A $B\n \"\ndiff --git c/t/t1402-check-ref-format.sh w/t/t1402-check-ref-format.sh\nindex 1a5a5f3..058fa37 100755\n--- c/t/t1402-check-ref-format.sh\n+++ w/t/t1402-check-ref-format.sh\n@@ -196,4 +196,52 @@ invalid_ref_normalized 'heads///foo.lock'\n invalid_ref_normalized 'foo.lock/bar'\n invalid_ref_normalized 'foo.lock///bar'\n \n+test_expect_success 'git branch shows badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch >output &&\n+\tgrep -e \"broken...ref\" output\n+'\n+\n+test_expect_success 'git branch -d can delete badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch -d broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken...ref\" output\n+'\n+\n+test_expect_success 'git branch -D can delete badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch -D broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken...ref\" output\n+'\n+\n+test_expect_success 'git update-ref -d can delete badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit update-ref -d refs/heads/broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken...ref\" output\n+'\n+\n+test_expect_success 'git branch can not create a badly named ref' '\n+\ttest_must_fail git branch broken...ref\n+'\n+\n+test_expect_success 'git branch can not rename to a bad ref name' '\n+\tgit branch goodref &&\n+\ttest_must_fail git branch -m goodref broken...ref\n+'\n+\n+test_expect_failure 'git branch can rename from a bad ref name' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch -m broken...ref renamed &&\n+\ttest_must_fail git rev-parse broken...ref &&\n+\ttest_cmp_rev master renamed\n+'\n+\n test_done\ndiff --git c/t/t3200-branch.sh w/t/t3200-branch.sh\nindex ac31b71..432921b 100755\n--- c/t/t3200-branch.sh\n+++ w/t/t3200-branch.sh\n@@ -285,6 +285,15 @@ test_expect_success 'deleting a dangling symref' '\n \ttest_i18ncmp expect actual\n '\n \n+test_expect_success 'deleting a self-referential symref' '\n+\tgit symbolic-ref refs/heads/self-reference refs/heads/self-reference &&\n+\ttest_path_is_file .git/refs/heads/self-reference &&\n+\techo \"Deleted branch self-reference (was refs/heads/self-reference).\" >expect &&\n+\tgit branch -d self-reference >actual &&\n+\ttest_path_is_missing .git/refs/heads/self-reference &&\n+\ttest_i18ncmp expect actual\n+'\n+\n test_expect_success 'renaming a symref is not allowed' '\n \tgit symbolic-ref refs/heads/master2 refs/heads/master &&\n \ttest_must_fail git branch -m master2 master3 &&\ndiff --git c/t/t7001-mv.sh w/t/t7001-mv.sh\nindex 54d7807..69f11bd 100755\n--- c/t/t7001-mv.sh\n+++ w/t/t7001-mv.sh\n@@ -350,10 +350,11 @@ test_expect_success 'git mv moves a submodule with a .git directory and .gitmodu\n '\n \n test_expect_success 'git mv moves a submodule with gitfile' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n \tentry=\"$(git ls-files --stage sub | cut -f 1)\" &&\n+\tmkdir mod &&\n \t(\n \t\tcd mod &&\n \t\tgit mv ../sub/ .\n@@ -372,11 +373,12 @@ test_expect_success 'git mv moves a submodule with gitfile' '\n '\n \n test_expect_success 'mv does not complain when no .gitmodules file is found' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n \tgit rm .gitmodules &&\n \tentry=\"$(git ls-files --stage sub | cut -f 1)\" &&\n+\tmkdir mod &&\n \tgit mv sub mod/sub 2>actual.err &&\n \t! test -s actual.err &&\n \t! test -e sub &&\n@@ -390,11 +392,12 @@ test_expect_success 'mv does not complain when no .gitmodules file is found' '\n '\n \n test_expect_success 'mv will error out on a modified .gitmodules file unless staged' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n \tgit config -f .gitmodules foo.bar true &&\n \tentry=\"$(git ls-files --stage sub | cut -f 1)\" &&\n+\tmkdir mod &&\n \ttest_must_fail git mv sub mod/sub 2>actual.err &&\n \ttest -s actual.err &&\n \ttest -e sub &&\n@@ -413,13 +416,14 @@ test_expect_success 'mv will error out on a modified .gitmodules file unless sta\n '\n \n test_expect_success 'mv issues a warning when section is not found in .gitmodules' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n \tgit config -f .gitmodules --remove-section submodule.sub &&\n \tgit add .gitmodules &&\n \tentry=\"$(git ls-files --stage sub | cut -f 1)\" &&\n \techo \"warning: Could not find section in .gitmodules where path=sub\" >expect.err &&\n+\tmkdir mod &&\n \tgit mv sub mod/sub 2>actual.err &&\n \ttest_i18ncmp expect.err actual.err &&\n \t! test -e sub &&\n@@ -433,9 +437,10 @@ test_expect_success 'mv issues a warning when section is not found in .gitmodule\n '\n \n test_expect_success 'mv --dry-run does not touch the submodule or .gitmodules' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n+\tmkdir mod &&\n \tgit mv -n sub mod/sub 2>actual.err &&\n \ttest -f sub/.git &&\n \tgit diff-index --exit-code HEAD &&\ndiff --git c/transport-helper.c w/transport-helper.c\nindex 270ae28..8365441 100644\n--- c/transport-helper.c\n+++ w/transport-helper.c\n@@ -889,7 +889,9 @@ static int push_refs_with_export(struct transport *transport,\n \t\t\t\t\tint flag;\n \n \t\t\t\t\t/* Follow symbolic refs (mainly for HEAD). */\n-\t\t\t\t\tname = resolve_ref_unsafe(ref->peer_ref->name, sha1, RESOLVE_REF_READING, &flag);\n+\t\t\t\t\tname = resolve_ref_unsafe(\n+\t\t\t\t\t\t ref->peer_ref->name, sha1,\n+\t\t\t\t\t\t &flag, RESOLVE_REF_READING);\n \t\t\t\t\tif (!name || !(flag & REF_ISSYMREF))\n \t\t\t\t\t\tname = ref->peer_ref->name;\n \ndiff --git c/transport.c w/transport.c\nindex f40e950..3ba7bbf 100644\n--- c/transport.c\n+++ w/transport.c\n@@ -168,8 +168,8 @@ static void set_upstreams(struct transport *transport, struct ref *refs,\n \t\t/* Follow symbolic refs (mainly for HEAD). */\n \t\tlocalname = ref->peer_ref->name;\n \t\tremotename = ref->name;\n-\t\ttmp = resolve_ref_unsafe(localname, sha,\n-\t\t\t\t\t RESOLVE_REF_READING, &flag);\n+\t\ttmp = resolve_ref_unsafe(localname, sha, &flag,\n+\t\t\t\t\t RESOLVE_REF_READING);\n \t\tif (tmp && flag & REF_ISSYMREF &&\n \t\t\tstarts_with(tmp, \"refs/heads/\"))\n \t\t\tlocalname = tmp;\n@@ -754,7 +754,7 @@ void transport_print_push_status(const char *dest, struct ref *refs,\n \tunsigned char head_sha1[20];\n \tchar *head;\n \n-\thead = resolve_refdup(\"HEAD\", head_sha1, RESOLVE_REF_READING, NULL);\n+\thead = resolve_refdup(\"HEAD\", head_sha1, NULL, RESOLVE_REF_READING);\n \n \tif (verbose) {\n \t\tfor (ref = refs; ref; ref = ref->next)\ndiff --git c/upload-pack.c w/upload-pack.c\nindex 01de944..3b51ccb 100644\n--- c/upload-pack.c\n+++ w/upload-pack.c\n@@ -743,7 +743,7 @@ static int find_symref(const char *refname, const unsigned char *sha1, int flag,\n \n \tif ((flag & REF_ISSYMREF) == 0)\n \t\treturn 0;\n-\tsymref_target = resolve_ref_unsafe(refname, unused, 0, &flag);\n+\tsymref_target = resolve_ref_unsafe(refname, unused, &flag, 0);\n \tif (!symref_target || (flag & REF_ISSYMREF) == 0)\n \t\tdie(\"'%s' is a symref but it is not?\", refname);\n \titem = string_list_append(cb_data, refname);\ndiff --git c/wrapper.c w/wrapper.c\nindex 74a0cc0..ec7a08b 100644\n--- c/wrapper.c\n+++ w/wrapper.c\n@@ -430,8 +430,8 @@ int xmkstemp_mode(char *template, int mode)\n static int warn_if_unremovable(const char *op, const char *file, int rc)\n {\n \tint err;\n-\tif (rc >= 0 || errno == ENOENT)\n-\t\treturn rc;\n+\tif (!rc || errno == ENOENT)\n+\t\treturn 0;\n \terr = errno;\n \twarning(\"unable to %s %s: %s\", op, file, strerror(errno));\n \terrno = err;\n@@ -440,20 +440,16 @@ static int warn_if_unremovable(const char *op, const char *file, int rc)\n \n int unlink_or_msg(const char *file, struct strbuf *err)\n {\n-\tif (err) {\n-\t\tint rc = unlink(file);\n-\t\tint save_errno = errno;\n+\tint rc = unlink(file);\n \n-\t\tif (rc < 0 && errno != ENOENT) {\n-\t\t\tstrbuf_addf(err, \"unable to unlink %s: %s\",\n-\t\t\t\t    file, strerror(errno));\n-\t\t\terrno = save_errno;\n-\t\t\treturn -1;\n-\t\t}\n+\tassert(err);\n+\n+\tif (!rc || errno == ENOENT)\n \t\treturn 0;\n-\t}\n \n-\treturn unlink_or_warn(file);\n+\tstrbuf_addf(err, \"unable to unlink %s: %s\",\n+\t\t    file, strerror(errno));\n+\treturn -1;\n }\n \n int unlink_or_warn(const char *file)\ndiff --git c/wt-status.c w/wt-status.c\nindex 318a191..6819e2b 100644\n--- c/wt-status.c\n+++ w/wt-status.c\n@@ -128,7 +128,7 @@ void wt_status_prepare(struct wt_status *s)\n \ts->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;\n \ts->use_color = -1;\n \ts->relative_paths = 1;\n-\ts->branch = resolve_refdup(\"HEAD\", sha1, 0, NULL);\n+\ts->branch = resolve_refdup(\"HEAD\", sha1, NULL, 0);\n \ts->reference = \"HEAD\";\n \ts->fp = stdout;\n \ts->index_file = get_index_file();\n"},{"id":"249196","messageId":"20140911030403.GE18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 01/19] mv test: recreate mod/ directory instead of relying on stale copy","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:04:03Z","receivedAt":"2014-09-11T03:04:03Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"The tests for 'git mv moves a submodule' functionality often run\ncommands like\n\n\tgit mv sub mod/sub\n\nto move a submodule into a subdirectory.  Just like plain /bin/mv,\nthis is supposed to succeed if the mod/ parent directory exists\nand fail if it doesn't exist.\n\nUsually these tests mkdir the parent directory beforehand, but some\ninstead rely on it being left behind by previous tests.\n\nMore precisely, when 'git reset --hard' tries to move to a state where\nmod/sub is not present any more, it would perform the following\noperations:\n\n\trmdir(\"mod/sub\")\n\trmdir(\"mod\")\n\nThe first fails with ENOENT because the test script removed mod/sub\nwith \"rm -rf\" already, so 'reset --hard' doesn't bother to move on to\nthe second, and the mod/ directory is kept around.\n\nBetter to explicitly remove and re-create the mod/ directory so later\ntests don't have to depend on the directory left behind by the earlier\nones at all (making it easier to rearrange or skip some tests in the\nfile or to tweak 'reset --hard' behavior without breaking unrelated\ntests).\n\nNoticed while testing a patch that fixes the reset --hard behavior\ndescribed above.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Ronnie Sahlberg <sahlberg@google.com>\n---\n t/t7001-mv.sh | 15 ++++++++++-----\n 1 file changed, 10 insertions(+), 5 deletions(-)\n\ndiff --git a/t/t7001-mv.sh b/t/t7001-mv.sh\nindex 54d7807..69f11bd 100755\n--- a/t/t7001-mv.sh\n+++ b/t/t7001-mv.sh\n@@ -350,10 +350,11 @@ test_expect_success 'git mv moves a submodule with a .git directory and .gitmodu\n '\n \n test_expect_success 'git mv moves a submodule with gitfile' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n \tentry=\"$(git ls-files --stage sub | cut -f 1)\" &&\n+\tmkdir mod &&\n \t(\n \t\tcd mod &&\n \t\tgit mv ../sub/ .\n@@ -372,11 +373,12 @@ test_expect_success 'git mv moves a submodule with gitfile' '\n '\n \n test_expect_success 'mv does not complain when no .gitmodules file is found' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n \tgit rm .gitmodules &&\n \tentry=\"$(git ls-files --stage sub | cut -f 1)\" &&\n+\tmkdir mod &&\n \tgit mv sub mod/sub 2>actual.err &&\n \t! test -s actual.err &&\n \t! test -e sub &&\n@@ -390,11 +392,12 @@ test_expect_success 'mv does not complain when no .gitmodules file is found' '\n '\n \n test_expect_success 'mv will error out on a modified .gitmodules file unless staged' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n \tgit config -f .gitmodules foo.bar true &&\n \tentry=\"$(git ls-files --stage sub | cut -f 1)\" &&\n+\tmkdir mod &&\n \ttest_must_fail git mv sub mod/sub 2>actual.err &&\n \ttest -s actual.err &&\n \ttest -e sub &&\n@@ -413,13 +416,14 @@ test_expect_success 'mv will error out on a modified .gitmodules file unless sta\n '\n \n test_expect_success 'mv issues a warning when section is not found in .gitmodules' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n \tgit config -f .gitmodules --remove-section submodule.sub &&\n \tgit add .gitmodules &&\n \tentry=\"$(git ls-files --stage sub | cut -f 1)\" &&\n \techo \"warning: Could not find section in .gitmodules where path=sub\" >expect.err &&\n+\tmkdir mod &&\n \tgit mv sub mod/sub 2>actual.err &&\n \ttest_i18ncmp expect.err actual.err &&\n \t! test -e sub &&\n@@ -433,9 +437,10 @@ test_expect_success 'mv issues a warning when section is not found in .gitmodule\n '\n \n test_expect_success 'mv --dry-run does not touch the submodule or .gitmodules' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n+\tmkdir mod &&\n \tgit mv -n sub mod/sub 2>actual.err &&\n \ttest -f sub/.git &&\n \tgit diff-index --exit-code HEAD &&\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249197","messageId":"20140911030444.GF18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 02/19] wrapper.c: remove/unlink_or_warn: simplify, treat ENOENT as success","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:04:44Z","receivedAt":"2014-09-11T03:04:44Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nSimplify the function warn_if_unremovable slightly. Additionally, change\nbehaviour slightly. If we failed to remove the object because the object\ndoes not exist, we can still return success back to the caller since none of\nthe callers depend on \"fail if the file did not exist\".\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n git-compat-util.h |  7 +++++--\n wrapper.c         | 14 ++++++--------\n 2 files changed, 11 insertions(+), 10 deletions(-)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex b6f03b3..611e77b 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -706,11 +706,14 @@ void git_qsort(void *base, size_t nmemb, size_t size,\n \n /*\n  * Preserves errno, prints a message, but gives no warning for ENOENT.\n- * Always returns the return value of unlink(2).\n+ * Returns 0 on success which includes trying to unlink an object that does\n+ * not exist.\n  */\n int unlink_or_warn(const char *path);\n /*\n- * Likewise for rmdir(2).\n+ * Preserves errno, prints a message, but gives no warning for ENOENT.\n+ * Returns 0 on success which includes trying to remove a directory that does\n+ * not exist.\n  */\n int rmdir_or_warn(const char *path);\n /*\ndiff --git a/wrapper.c b/wrapper.c\nindex bc1bfb8..c9605cd 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -429,14 +429,12 @@ int xmkstemp_mode(char *template, int mode)\n \n static int warn_if_unremovable(const char *op, const char *file, int rc)\n {\n-\tif (rc < 0) {\n-\t\tint err = errno;\n-\t\tif (ENOENT != err) {\n-\t\t\twarning(\"unable to %s %s: %s\",\n-\t\t\t\top, file, strerror(errno));\n-\t\t\terrno = err;\n-\t\t}\n-\t}\n+\tint err;\n+\tif (!rc || errno == ENOENT)\n+\t\treturn 0;\n+\terr = errno;\n+\twarning(\"unable to %s %s: %s\", op, file, strerror(errno));\n+\terrno = err;\n \treturn rc;\n }\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249198","messageId":"20140911030540.GG18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 03/19] wrapper.c: add a new function unlink_or_msg","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:05:40Z","receivedAt":"2014-09-11T03:05:40Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Jul 2014 11:20:36 -0700\n\nThis behaves like unlink_or_warn except that on failure it writes the message\nto its 'err' argument, which the caller can display in an appropriate way or\nignore.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n git-compat-util.h |  9 +++++++++\n wrapper.c         | 14 ++++++++++++++\n 2 files changed, 23 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 611e77b..5ee140c 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -307,6 +307,8 @@ extern char *gitbasename(char *);\n \n #include \"wildmatch.h\"\n \n+struct strbuf;\n+\n /* General helper functions */\n extern void vreportf(const char *prefix, const char *err, va_list params);\n extern void vwritef(int fd, const char *prefix, const char *err, va_list params);\n@@ -710,6 +712,13 @@ void git_qsort(void *base, size_t nmemb, size_t size,\n  * not exist.\n  */\n int unlink_or_warn(const char *path);\n+ /*\n+  * Tries to unlink file.  Returns 0 if unlink succeeded\n+  * or the file already didn't exist.  Returns -1 and\n+  * appends a message to err suitable for\n+  * 'error(\"%s\", err->buf)' on error.\n+  */\n+int unlink_or_msg(const char *file, struct strbuf *err);\n /*\n  * Preserves errno, prints a message, but gives no warning for ENOENT.\n  * Returns 0 on success which includes trying to remove a directory that does\ndiff --git a/wrapper.c b/wrapper.c\nindex c9605cd..ec7a08b 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -438,6 +438,20 @@ static int warn_if_unremovable(const char *op, const char *file, int rc)\n \treturn rc;\n }\n \n+int unlink_or_msg(const char *file, struct strbuf *err)\n+{\n+\tint rc = unlink(file);\n+\n+\tassert(err);\n+\n+\tif (!rc || errno == ENOENT)\n+\t\treturn 0;\n+\n+\tstrbuf_addf(err, \"unable to unlink %s: %s\",\n+\t\t    file, strerror(errno));\n+\treturn -1;\n+}\n+\n int unlink_or_warn(const char *file)\n {\n \treturn warn_if_unremovable(\"unlink\", file, unlink(file));\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249199","messageId":"20140911030602.GH18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 04/19] refs.c: add an err argument to delete_ref_loose","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:06:02Z","receivedAt":"2014-09-11T03:06:02Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 15 May 2014 08:25:23 -0700\n\nAdd an err argument to delete_loose_ref so that we can pass a descriptive\nerror string back to the caller. Pass the err argument from transaction\ncommit to this function so that transaction users will have a nice error\nstring if the transaction failed due to delete_loose_ref.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 11 ++++++-----\n 1 file changed, 6 insertions(+), 5 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 7235574..5609622 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2548,16 +2548,16 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \treturn ret;\n }\n \n-static int delete_ref_loose(struct ref_lock *lock, int flag)\n+static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)\n {\n \tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n \t\t/* loose */\n-\t\tint err, i = strlen(lock->lk->filename) - 5; /* .lock */\n+\t\tint res, i = strlen(lock->lk->filename) - 5; /* .lock */\n \n \t\tlock->lk->filename[i] = 0;\n-\t\terr = unlink_or_warn(lock->lk->filename);\n+\t\tres = unlink_or_msg(lock->lk->filename, err);\n \t\tlock->lk->filename[i] = '.';\n-\t\tif (err && errno != ENOENT)\n+\t\tif (res)\n \t\t\treturn 1;\n \t}\n \treturn 0;\n@@ -3604,7 +3604,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (update->lock) {\n-\t\t\tret |= delete_ref_loose(update->lock, update->type);\n+\t\t\tret |= delete_ref_loose(update->lock, update->type,\n+\t\t\t\t\t\terr);\n \t\t\tif (!(update->flags & REF_ISPRUNING))\n \t\t\t\tdelnames[delnum++] = update->lock->ref_name;\n \t\t}\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249200","messageId":"20140911030630.GI18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 05/19] refs.c: pass the ref log message to _create/delete/update instead of _commit","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:06:30Z","receivedAt":"2014-09-11T03:06:30Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 30 Apr 2014 12:22:42 -0700\n\nChange the reference transactions so that we pass the reflog message\nthrough to the create/delete/update function instead of the commit message.\nThis allows for individual messages for each change in a multi ref\ntransaction.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n branch.c               |  4 ++--\n builtin/commit.c       |  4 ++--\n builtin/receive-pack.c |  5 +++--\n builtin/replace.c      |  5 +++--\n builtin/tag.c          |  4 ++--\n builtin/update-ref.c   | 13 +++++++------\n fast-import.c          |  8 ++++----\n refs.c                 | 34 +++++++++++++++++++++-------------\n refs.h                 |  8 ++++----\n sequencer.c            |  4 ++--\n walker.c               |  5 ++---\n 11 files changed, 52 insertions(+), 42 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 37ac555..76a8ec9 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -301,8 +301,8 @@ void create_branch(const char *head,\n \t\ttransaction = ref_transaction_begin(&err);\n \t\tif (!transaction ||\n \t\t    ref_transaction_update(transaction, ref.buf, sha1,\n-\t\t\t\t\t   null_sha1, 0, !forcing, &err) ||\n-\t\t    ref_transaction_commit(transaction, msg, &err))\n+\t\t\t\t\t   null_sha1, 0, !forcing, msg, &err) ||\n+\t\t    ref_transaction_commit(transaction, &err))\n \t\t\tdie(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n \t\tstrbuf_release(&err);\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 9bf1003..d23e876 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1767,8 +1767,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t    ref_transaction_update(transaction, \"HEAD\", sha1,\n \t\t\t\t   current_head\n \t\t\t\t   ? current_head->object.sha1 : NULL,\n-\t\t\t\t   0, !!current_head, &err) ||\n-\t    ref_transaction_commit(transaction, sb.buf, &err)) {\n+\t\t\t\t   0, !!current_head, sb.buf, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n \t\trollback_index_files();\n \t\tdie(\"%s\", err.buf);\n \t}\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 224fadc..d1f4cf7 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -585,8 +585,9 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\ttransaction = ref_transaction_begin(&err);\n \t\tif (!transaction ||\n \t\t    ref_transaction_update(transaction, namespaced_name,\n-\t\t\t\t\t   new_sha1, old_sha1, 0, 1, &err) ||\n-\t\t    ref_transaction_commit(transaction, \"push\", &err)) {\n+\t\t\t\t\t   new_sha1, old_sha1, 0, 1, \"push\",\n+\t\t\t\t\t   &err) ||\n+\t\t    ref_transaction_commit(transaction, &err)) {\n \t\t\tref_transaction_free(transaction);\n \n \t\t\trp_error(\"%s\", err.buf);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 1fcd06d..9d03b84 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -169,8 +169,9 @@ static int replace_object_sha1(const char *object_ref,\n \n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, repl, prev, 0, 1, &err) ||\n-\t    ref_transaction_commit(transaction, NULL, &err))\n+\t    ref_transaction_update(transaction, ref, repl, prev,\n+\t\t\t\t   0, 1, NULL, &err) ||\n+\t    ref_transaction_commit(transaction, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tref_transaction_free(transaction);\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex f3f172f..70d28c5 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -705,8 +705,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, object, prev,\n-\t\t\t\t   0, 1, &err) ||\n-\t    ref_transaction_commit(transaction, NULL, &err))\n+\t\t\t\t   0, 1, NULL, &err) ||\n+\t    ref_transaction_commit(transaction, &err))\n \t\tdie(\"%s\", err.buf);\n \tref_transaction_free(transaction);\n \tif (force && !is_null_sha1(prev) && hashcmp(prev, object))\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 54a48c0..6c9be05 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -14,6 +14,7 @@ static const char * const git_update_ref_usage[] = {\n \n static char line_termination = '\\n';\n static int update_flags;\n+static const char *msg;\n \n /*\n  * Parse one whitespace- or NUL-terminated, possibly C-quoted argument\n@@ -198,7 +199,7 @@ static const char *parse_cmd_update(struct ref_transaction *transaction,\n \t\tdie(\"update %s: extra input: %s\", refname, next);\n \n \tif (ref_transaction_update(transaction, refname, new_sha1, old_sha1,\n-\t\t\t\t   update_flags, have_old, &err))\n+\t\t\t\t   update_flags, have_old, msg, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = 0;\n@@ -229,7 +230,7 @@ static const char *parse_cmd_create(struct ref_transaction *transaction,\n \t\tdie(\"create %s: extra input: %s\", refname, next);\n \n \tif (ref_transaction_create(transaction, refname, new_sha1,\n-\t\t\t\t   update_flags, &err))\n+\t\t\t\t   update_flags, msg, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = 0;\n@@ -264,7 +265,7 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction,\n \t\tdie(\"delete %s: extra input: %s\", refname, next);\n \n \tif (ref_transaction_delete(transaction, refname, old_sha1,\n-\t\t\t\t   update_flags, have_old, &err))\n+\t\t\t\t   update_flags, have_old, msg, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = 0;\n@@ -300,7 +301,7 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction,\n \t\tdie(\"verify %s: extra input: %s\", refname, next);\n \n \tif (ref_transaction_update(transaction, refname, new_sha1, old_sha1,\n-\t\t\t\t   update_flags, have_old, &err))\n+\t\t\t\t   update_flags, have_old, msg, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = 0;\n@@ -354,7 +355,7 @@ static void update_refs_stdin(struct ref_transaction *transaction)\n \n int cmd_update_ref(int argc, const char **argv, const char *prefix)\n {\n-\tconst char *refname, *oldval, *msg = NULL;\n+\tconst char *refname, *oldval;\n \tunsigned char sha1[20], oldsha1[20];\n \tint delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;\n \tstruct option options[] = {\n@@ -385,7 +386,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \t\tif (end_null)\n \t\t\tline_termination = '\\0';\n \t\tupdate_refs_stdin(transaction);\n-\t\tif (ref_transaction_commit(transaction, msg, &err))\n+\t\tif (ref_transaction_commit(transaction, &err))\n \t\t\tdie(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n \t\tstrbuf_release(&err);\ndiff --git a/fast-import.c b/fast-import.c\nindex e7f6e37..51dfaad 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1708,8 +1708,8 @@ static int update_branch(struct branch *b)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, b->name, b->sha1, old_sha1,\n-\t\t\t\t   0, 1, &err) ||\n-\t    ref_transaction_commit(transaction, msg, &err)) {\n+\t\t\t\t   0, 1, msg, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n \t\tstrbuf_release(&err);\n@@ -1749,12 +1749,12 @@ static void dump_tags(void)\n \t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n \n \t\tif (ref_transaction_update(transaction, ref_name.buf, t->sha1,\n-\t\t\t\t\t   NULL, 0, 0, &err)) {\n+\t\t\t\t\t   NULL, 0, 0, msg, &err)) {\n \t\t\tfailure |= error(\"%s\", err.buf);\n \t\t\tgoto cleanup;\n \t\t}\n \t}\n-\tif (ref_transaction_commit(transaction, msg, &err))\n+\tif (ref_transaction_commit(transaction, &err))\n \t\tfailure |= error(\"%s\", err.buf);\n \n  cleanup:\ndiff --git a/refs.c b/refs.c\nindex 5609622..99a9b86 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2396,8 +2396,8 @@ static void prune_ref(struct ref_to_prune *r)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_delete(transaction, r->name, r->sha1,\n-\t\t\t\t   REF_ISPRUNING, 1, &err) ||\n-\t    ref_transaction_commit(transaction, NULL, &err)) {\n+\t\t\t\t   REF_ISPRUNING, 1, NULL, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n \t\tstrbuf_release(&err);\n@@ -2571,8 +2571,8 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_delete(transaction, refname, sha1, delopt,\n-\t\t\t\t   sha1 && !is_null_sha1(sha1), &err) ||\n-\t    ref_transaction_commit(transaction, NULL, &err)) {\n+\t\t\t\t   sha1 && !is_null_sha1(sha1), NULL, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n \t\tstrbuf_release(&err);\n@@ -3350,6 +3350,7 @@ struct ref_update {\n \tint have_old; /* 1 if old_sha1 is valid, 0 otherwise */\n \tstruct ref_lock *lock;\n \tint type;\n+\tchar *msg;\n \tconst char refname[FLEX_ARRAY];\n };\n \n@@ -3392,9 +3393,10 @@ void ref_transaction_free(struct ref_transaction *transaction)\n \tif (!transaction)\n \t\treturn;\n \n-\tfor (i = 0; i < transaction->nr; i++)\n+\tfor (i = 0; i < transaction->nr; i++) {\n+\t\tfree(transaction->updates[i]->msg);\n \t\tfree(transaction->updates[i]);\n-\n+\t}\n \tfree(transaction->updates);\n \tfree(transaction);\n }\n@@ -3415,7 +3417,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const unsigned char *new_sha1,\n \t\t\t   const unsigned char *old_sha1,\n-\t\t\t   int flags, int have_old,\n+\t\t\t   int flags, int have_old, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n \tstruct ref_update *update;\n@@ -3432,13 +3434,15 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tupdate->have_old = have_old;\n \tif (have_old)\n \t\thashcpy(update->old_sha1, old_sha1);\n+\tif (msg)\n+\t\tupdate->msg = xstrdup(msg);\n \treturn 0;\n }\n \n int ref_transaction_create(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const unsigned char *new_sha1,\n-\t\t\t   int flags,\n+\t\t\t   int flags, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n \tstruct ref_update *update;\n@@ -3455,13 +3459,15 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \thashclr(update->old_sha1);\n \tupdate->flags = flags;\n \tupdate->have_old = 1;\n+\tif (msg)\n+\t\tupdate->msg = xstrdup(msg);\n \treturn 0;\n }\n \n int ref_transaction_delete(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const unsigned char *old_sha1,\n-\t\t\t   int flags, int have_old,\n+\t\t\t   int flags, int have_old, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n \tstruct ref_update *update;\n@@ -3479,6 +3485,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n \t\tassert(!is_null_sha1(old_sha1));\n \t\thashcpy(update->old_sha1, old_sha1);\n \t}\n+\tif (msg)\n+\t\tupdate->msg = xstrdup(msg);\n \treturn 0;\n }\n \n@@ -3492,8 +3500,8 @@ int update_ref(const char *action, const char *refname,\n \tt = ref_transaction_begin(&err);\n \tif (!t ||\n \t    ref_transaction_update(t, refname, sha1, oldval, flags,\n-\t\t\t\t   !!oldval, &err) ||\n-\t    ref_transaction_commit(t, action, &err)) {\n+\t\t\t\t   !!oldval, action, &err) ||\n+\t    ref_transaction_commit(t, &err)) {\n \t\tconst char *str = \"update_ref failed for ref '%s': %s\";\n \n \t\tref_transaction_free(t);\n@@ -3539,7 +3547,7 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,\n }\n \n int ref_transaction_commit(struct ref_transaction *transaction,\n-\t\t\t   const char *msg, struct strbuf *err)\n+\t\t\t   struct strbuf *err)\n {\n \tint ret = 0, delnum = 0, i;\n \tconst char **delnames;\n@@ -3588,7 +3596,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \n \t\tif (!is_null_sha1(update->new_sha1)) {\n \t\t\tret = write_ref_sha1(update->lock, update->new_sha1,\n-\t\t\t\t\t     msg);\n+\t\t\t\t\t     update->msg);\n \t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n \t\t\tif (ret) {\n \t\t\t\tif (err)\ndiff --git a/refs.h b/refs.h\nindex 69ef28c..7c1bf95 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -289,7 +289,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const unsigned char *new_sha1,\n \t\t\t   const unsigned char *old_sha1,\n-\t\t\t   int flags, int have_old,\n+\t\t\t   int flags, int have_old, const char *msg,\n \t\t\t   struct strbuf *err);\n \n /*\n@@ -304,7 +304,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n int ref_transaction_create(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const unsigned char *new_sha1,\n-\t\t\t   int flags,\n+\t\t\t   int flags, const char *msg,\n \t\t\t   struct strbuf *err);\n \n /*\n@@ -318,7 +318,7 @@ int ref_transaction_create(struct ref_transaction *transaction,\n int ref_transaction_delete(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const unsigned char *old_sha1,\n-\t\t\t   int flags, int have_old,\n+\t\t\t   int flags, int have_old, const char *msg,\n \t\t\t   struct strbuf *err);\n \n /*\n@@ -327,7 +327,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n  * problem.\n  */\n int ref_transaction_commit(struct ref_transaction *transaction,\n-\t\t\t   const char *msg, struct strbuf *err);\n+\t\t\t   struct strbuf *err);\n \n /*\n  * Free an existing transaction and all associated data.\ndiff --git a/sequencer.c b/sequencer.c\nindex 5e93b6a..c5b7b8a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -286,8 +286,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\",\n \t\t\t\t   to, unborn ? null_sha1 : from,\n-\t\t\t\t   0, 1, &err) ||\n-\t    ref_transaction_commit(transaction, sb.buf, &err)) {\n+\t\t\t\t   0, 1, sb.buf, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n \t\tstrbuf_release(&sb);\ndiff --git a/walker.c b/walker.c\nindex b8a5441..3c6fb8a 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -298,14 +298,13 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\tstrbuf_addf(&refname, \"refs/%s\", write_ref[i]);\n \t\tif (ref_transaction_update(transaction, refname.buf,\n \t\t\t\t\t   &sha1[20 * i], NULL, 0, 0,\n+\t\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n \t\t\t\t\t   &err)) {\n \t\t\terror(\"%s\", err.buf);\n \t\t\tgoto done;\n \t\t}\n \t}\n-\tif (ref_transaction_commit(transaction,\n-\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n-\t\t\t\t   &err)) {\n+\tif (ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tgoto done;\n \t}\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249201","messageId":"20140911030652.GJ18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 06/19] rename_ref: don't ask read_ref_full where the ref came from","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:06:52Z","receivedAt":"2014-09-11T03:06:52Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 30 Apr 2014 12:41:04 -0700\n\nWe call read_ref_full with a pointer to flags from rename_ref but since\nwe never actually use the returned flags we can just pass NULL here instead.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 99a9b86..39571f5 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2671,7 +2671,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \t\tgoto rollback;\n \t}\n \n-\tif (!read_ref_full(newrefname, sha1, 1, &flag) &&\n+\tif (!read_ref_full(newrefname, sha1, 1, NULL) &&\n \t    delete_ref(newrefname, sha1, REF_NODEREF)) {\n \t\tif (errno==EISDIR) {\n \t\t\tif (remove_empty_directories(git_path(\"%s\", newrefname))) {\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249202","messageId":"20140911030721.GK18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 07/19] refs.c: move the check for valid refname to lock_ref_sha1_basic","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:07:22Z","receivedAt":"2014-09-11T03:07:22Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 1 May 2014 10:40:10 -0700\n\nMove the check for check_refname_format from lock_any_ref_for_update\nto lock_ref_sha1_basic. At some later stage we will get rid of\nlock_any_ref_for_update completely. This has no visible impact to callers,\nexcept for inability to lock badly named refs which is not possible today\nanyway but for other reasons.\n\nKeep lock_any_ref_for_update as a no-op wrapper --- it is the public facing\nversion of this interface and keeping it as a separate function will make\nit easier to experiment with the internal lock_ref_sha1_basic signature.\n\nNote that if lock_ref_sha1_basic now checks the refname format and fails to\nlock the ref it will not be possible to delete such a ref since deletion\nimplies we first lock the ref. In fact, we currently fail even earlier than\nthat since these refs are not even recognized to exist.\n\nExample:\n$ cp .git/refs/heads/master .git/refs/heads/echo...\\*\\*\n$ ./git branch -D .git/refs/heads/echo...\\*\\*\nerror: branch '.git/refs/heads/echo...**' not found.\n\nThis is not a new regression and this has been broken for a while.\nLater patches in the series will start repairing the handling of badly\nnamed refs, at which time we will need to modify lock_ref_sha1_basic once more\nin order to allow locking these refs for certain use cases such as rename\nand delete.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 39571f5..3c2ce57 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2091,6 +2091,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \tint missing = 0;\n \tint attempts_remaining = 3;\n \n+\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\terrno = EINVAL;\n+\t\treturn NULL;\n+\t}\n+\n \tlock = xcalloc(1, sizeof(struct ref_lock));\n \tlock->lock_fd = -1;\n \n@@ -2182,8 +2187,6 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t const unsigned char *old_sha1,\n \t\t\t\t\t int flags, int *type_p)\n {\n-\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))\n-\t\treturn NULL;\n \treturn lock_ref_sha1_basic(refname, old_sha1, flags, type_p);\n }\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249203","messageId":"20140911030739.GL18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 08/19] refs.c: call lock_ref_sha1_basic directly from commit","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:07:39Z","receivedAt":"2014-09-11T03:07:39Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 1 May 2014 10:43:39 -0700\n\nSkip using the lock_any_ref_for_update wrapper and call lock_ref_sha1_basic\ndirectly from the commit function.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 3c2ce57..f124c2b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3578,12 +3578,12 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n \n-\t\tupdate->lock = lock_any_ref_for_update(update->refname,\n-\t\t\t\t\t\t       (update->have_old ?\n-\t\t\t\t\t\t\tupdate->old_sha1 :\n-\t\t\t\t\t\t\tNULL),\n-\t\t\t\t\t\t       update->flags,\n-\t\t\t\t\t\t       &update->type);\n+\t\tupdate->lock = lock_ref_sha1_basic(update->refname,\n+\t\t\t\t\t\t   (update->have_old ?\n+\t\t\t\t\t\t    update->old_sha1 :\n+\t\t\t\t\t\t    NULL),\n+\t\t\t\t\t\t   update->flags,\n+\t\t\t\t\t\t   &update->type);\n \t\tif (!update->lock) {\n \t\t\tif (err)\n \t\t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249204","messageId":"20140911030800.GM18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 09/19] refs.c: pass a skip list to name_conflict_fn","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:08:00Z","receivedAt":"2014-09-11T03:08:00Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 1 May 2014 11:16:07 -0700\n\nAllow passing a list of refs to skip checking to name_conflict_fn.\nThere are some conditions where we want to allow a temporary conflict and skip\nchecking those refs. For example if we have a transaction that\n1, guarantees that m is a packed refs and there is no loose ref for m\n2, the transaction will delete m from the packed ref\n3, the transaction will create conflicting m/m\n\nFor this case we want to be able to lock and create m/m since we know that the\nconflict is only transient. I.e. the conflict will be automatically resolved\nby the transaction when it deletes m.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 41 ++++++++++++++++++++++++++++-------------\n 1 file changed, 28 insertions(+), 13 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex f124c2b..b63ab2f 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -801,14 +801,16 @@ static int names_conflict(const char *refname1, const char *refname2)\n \n struct name_conflict_cb {\n \tconst char *refname;\n-\tconst char *oldrefname;\n \tconst char *conflicting_refname;\n+\tstruct string_list *skiplist;\n };\n \n static int name_conflict_fn(struct ref_entry *entry, void *cb_data)\n {\n \tstruct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;\n-\tif (data->oldrefname && !strcmp(data->oldrefname, entry->name))\n+\n+\tif (data->skiplist &&\n+\t    string_list_has_string(data->skiplist, entry->name))\n \t\treturn 0;\n \tif (names_conflict(data->refname, entry->name)) {\n \t\tdata->conflicting_refname = entry->name;\n@@ -822,15 +824,17 @@ static int name_conflict_fn(struct ref_entry *entry, void *cb_data)\n  * conflicting with the name of an existing reference in dir.  If\n  * oldrefname is non-NULL, ignore potential conflicts with oldrefname\n  * (e.g., because oldrefname is scheduled for deletion in the same\n- * operation).\n+ * operation). skiplist contains a list of refs we want to skip checking for\n+ * conflicts with. skiplist must be sorted.\n  */\n-static int is_refname_available(const char *refname, const char *oldrefname,\n-\t\t\t\tstruct ref_dir *dir)\n+static int is_refname_available(const char *refname,\n+\t\t\t\tstruct ref_dir *dir,\n+\t\t\t\tstruct string_list *skiplist)\n {\n \tstruct name_conflict_cb data;\n \tdata.refname = refname;\n-\tdata.oldrefname = oldrefname;\n \tdata.conflicting_refname = NULL;\n+\tdata.skiplist = skiplist;\n \n \tsort_ref_dir(dir);\n \tif (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, &data)) {\n@@ -2080,6 +2084,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n  */\n static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\t\t\t    const unsigned char *old_sha1,\n+\t\t\t\t\t    struct string_list *skiplist,\n \t\t\t\t\t    int flags, int *type_p)\n {\n \tchar *ref_file;\n@@ -2129,7 +2134,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t * name is a proper prefix of our refname.\n \t */\n \tif (missing &&\n-\t     !is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) {\n+\t     !is_refname_available(refname, get_packed_refs(&ref_cache),\n+\t\t\t\t   skiplist)) {\n \t\tlast_errno = ENOTDIR;\n \t\tgoto error_return;\n \t}\n@@ -2187,7 +2193,7 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t const unsigned char *old_sha1,\n \t\t\t\t\t int flags, int *type_p)\n {\n-\treturn lock_ref_sha1_basic(refname, old_sha1, flags, type_p);\n+\treturn lock_ref_sha1_basic(refname, old_sha1, NULL, flags, type_p);\n }\n \n /*\n@@ -2648,6 +2654,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \tstruct stat loginfo;\n \tint log = !lstat(git_path(\"logs/%s\", oldrefname), &loginfo);\n \tconst char *symref = NULL;\n+\tstruct string_list skiplist = STRING_LIST_INIT_NODUP;\n \n \tif (log && S_ISLNK(loginfo.st_mode))\n \t\treturn error(\"reflog for %s is a symlink\", oldrefname);\n@@ -2659,11 +2666,18 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \tif (!symref)\n \t\treturn error(\"refname %s not found\", oldrefname);\n \n-\tif (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache)))\n+\tstring_list_insert(&skiplist, oldrefname);\n+\tif (!is_refname_available(newrefname, get_packed_refs(&ref_cache),\n+\t\t\t\t  &skiplist)) {\n+\t\tstring_list_clear(&skiplist, 0);\n \t\treturn 1;\n-\n-\tif (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache)))\n+\t}\n+\tif (!is_refname_available(newrefname, get_loose_refs(&ref_cache),\n+\t\t\t\t  &skiplist)) {\n+\t\tstring_list_clear(&skiplist, 0);\n \t\treturn 1;\n+\t}\n+\tstring_list_clear(&skiplist, 0);\n \n \tif (log && rename(git_path(\"logs/%s\", oldrefname), git_path(TMP_RENAMED_LOG)))\n \t\treturn error(\"unable to move logfile logs/%s to \"TMP_RENAMED_LOG\": %s\",\n@@ -2692,7 +2706,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \n \tlogmoved = log;\n \n-\tlock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL);\n+\tlock = lock_ref_sha1_basic(newrefname, NULL, NULL, 0, NULL);\n \tif (!lock) {\n \t\terror(\"unable to lock %s for update\", newrefname);\n \t\tgoto rollback;\n@@ -2707,7 +2721,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \treturn 0;\n \n  rollback:\n-\tlock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL);\n+\tlock = lock_ref_sha1_basic(oldrefname, NULL, NULL, 0, NULL);\n \tif (!lock) {\n \t\terror(\"unable to lock %s for rollback\", oldrefname);\n \t\tgoto rollbacklog;\n@@ -3582,6 +3596,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t\t\t   (update->have_old ?\n \t\t\t\t\t\t    update->old_sha1 :\n \t\t\t\t\t\t    NULL),\n+\t\t\t\t\t\t   NULL,\n \t\t\t\t\t\t   update->flags,\n \t\t\t\t\t\t   &update->type);\n \t\tif (!update->lock) {\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249205","messageId":"20140911030828.GN18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 10/19] refs.c: ref_transaction_commit: distinguish name conflicts from other errors","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:08:28Z","receivedAt":"2014-09-11T03:08:28Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Fri, 16 May 2014 14:14:38 -0700\n\nIn _commit, ENOTDIR can happen in the call to lock_ref_sha1_basic, either when\nwe lstat the new refname and it returns ENOTDIR or if the name checking\nfunction reports that the same type of conflict happened. In both cases it\nmeans that we can not create the new ref due to a name conflict.\n\nStart defining specific return codes for _commit: assign -1 as a generic\nerror and UPDATE_REFS_NAME_CONFLICT (-2) as the error that refers to a name\nconflict.\n\nWhen \"git fetch\" is creating refs, name conflicts differ from other errors in\nthat they are likely to be resolved by running \"git remote prune <remote>\".\n\"git fetch\" currently inspects errno to decide whether to give that advice.\nOnce it switches to the transaction API, it can check for\nUPDATE_REFS_NAME_CONFLICT instead.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 18 ++++++++++++------\n refs.h |  5 +++++\n 2 files changed, 17 insertions(+), 6 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex b63ab2f..86c708a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3584,9 +3584,10 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \n \t/* Copy, sort, and reject duplicate refs */\n \tqsort(updates, n, sizeof(*updates), ref_update_compare);\n-\tret = ref_update_reject_duplicates(updates, n, err);\n-\tif (ret)\n+\tif (ref_update_reject_duplicates(updates, n, err)) {\n+\t\tret = -1;\n \t\tgoto cleanup;\n+\t}\n \n \t/* Acquire all locks while verifying old values */\n \tfor (i = 0; i < n; i++) {\n@@ -3600,10 +3601,12 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t\t\t   update->flags,\n \t\t\t\t\t\t   &update->type);\n \t\tif (!update->lock) {\n+\t\t\tint df_conflict = (errno == ENOTDIR);\n+\n \t\t\tif (err)\n \t\t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n \t\t\t\t\t    update->refname);\n-\t\t\tret = 1;\n+\t\t\tret = df_conflict ? UPDATE_REFS_NAME_CONFLICT : -1;\n \t\t\tgoto cleanup;\n \t\t}\n \t}\n@@ -3620,6 +3623,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\tif (err)\n \t\t\t\t\tstrbuf_addf(err, \"Cannot update the ref '%s'.\",\n \t\t\t\t\t\t    update->refname);\n+\t\t\t\tret = -1;\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \t\t}\n@@ -3630,14 +3634,16 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (update->lock) {\n-\t\t\tret |= delete_ref_loose(update->lock, update->type,\n-\t\t\t\t\t\terr);\n+\t\t\tif (delete_ref_loose(update->lock, update->type, err))\n+\t\t\t\tret = -1;\n+\n \t\t\tif (!(update->flags & REF_ISPRUNING))\n \t\t\t\tdelnames[delnum++] = update->lock->ref_name;\n \t\t}\n \t}\n \n-\tret |= repack_without_refs(delnames, delnum, err);\n+\tif (repack_without_refs(delnames, delnum, err))\n+\t\tret = -1;\n \tfor (i = 0; i < delnum; i++)\n \t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n \tclear_loose_ref_cache(&ref_cache);\ndiff --git a/refs.h b/refs.h\nindex 7c1bf95..e14aa31 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -325,7 +325,12 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n  * Commit all of the changes that have been queued in transaction, as\n  * atomically as possible.  Return a nonzero value if there is a\n  * problem.\n+ *\n+ * Function returns 0 on success, -1 for generic failures and\n+ * UPDATE_REFS_NAME_CONFLICT (-2) if the failure was due to a naming conflict.\n+ * For example, the ref names A and A/B conflict.\n  */\n+#define UPDATE_REFS_NAME_CONFLICT -2\n int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t   struct strbuf *err);\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249206","messageId":"20140911030843.GO18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 11/19] fetch.c: change s_update_ref to use a ref transaction","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:08:43Z","receivedAt":"2014-09-11T03:08:43Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Mon, 28 Apr 2014 13:49:07 -0700\n\nChange s_update_ref to use a ref transaction for the ref update.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/fetch.c | 34 ++++++++++++++++++++++++----------\n 1 file changed, 24 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 55f457c..2e3bc73 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -375,23 +375,37 @@ static int s_update_ref(const char *action,\n {\n \tchar msg[1024];\n \tchar *rla = getenv(\"GIT_REFLOG_ACTION\");\n-\tstatic struct ref_lock *lock;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint ret, df_conflict = 0;\n \n \tif (dry_run)\n \t\treturn 0;\n \tif (!rla)\n \t\trla = default_rla.buf;\n \tsnprintf(msg, sizeof(msg), \"%s: %s\", rla, action);\n-\tlock = lock_any_ref_for_update(ref->name,\n-\t\t\t\t       check_old ? ref->old_sha1 : NULL,\n-\t\t\t\t       0, NULL);\n-\tif (!lock)\n-\t\treturn errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :\n-\t\t\t\t\t  STORE_REF_ERROR_OTHER;\n-\tif (write_ref_sha1(lock, ref->new_sha1, msg) < 0)\n-\t\treturn errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :\n-\t\t\t\t\t  STORE_REF_ERROR_OTHER;\n+\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, ref->name, ref->new_sha1,\n+\t\t\t\t   ref->old_sha1, 0, check_old, msg, &err))\n+\t\tgoto fail;\n+\n+\tret = ref_transaction_commit(transaction, &err);\n+\tif (ret == UPDATE_REFS_NAME_CONFLICT)\n+\t\tdf_conflict = 1;\n+\tif (ret)\n+\t\tgoto fail;\n+\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n \treturn 0;\n+fail:\n+\tref_transaction_free(transaction);\n+\terror(\"%s\", err.buf);\n+\tstrbuf_release(&err);\n+\treturn df_conflict ? STORE_REF_ERROR_DF_CONFLICT\n+\t\t\t   : STORE_REF_ERROR_OTHER;\n }\n \n #define REFCOL_WIDTH  10\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249207","messageId":"20140911030858.GP18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 12/19] refs.c: make write_ref_sha1 static","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:08:58Z","receivedAt":"2014-09-11T03:08:58Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Mon, 28 Apr 2014 15:36:58 -0700\n\nNo external users call write_ref_sha1 any more so lets declare it static.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 10 ++++++++--\n refs.h |  3 ---\n 2 files changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 86c708a..c2dab4a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2646,6 +2646,9 @@ static int rename_tmp_log(const char *newrefname)\n \treturn 0;\n }\n \n+static int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1,\n+\t\t\t  const char *logmsg);\n+\n int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)\n {\n \tunsigned char sha1[20], orig_sha1[20];\n@@ -2901,8 +2904,11 @@ static int is_branch(const char *refname)\n \treturn !strcmp(refname, \"HEAD\") || starts_with(refname, \"refs/heads/\");\n }\n \n-/* This function must return a meaningful errno */\n-int write_ref_sha1(struct ref_lock *lock,\n+/*\n+ * Writes sha1 into the ref specified by the lock. Makes sure that errno\n+ * is sane on error.\n+ */\n+static int write_ref_sha1(struct ref_lock *lock,\n \tconst unsigned char *sha1, const char *logmsg)\n {\n \tstatic char term = '\\n';\ndiff --git a/refs.h b/refs.h\nindex e14aa31..fafa493 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -195,9 +195,6 @@ extern int commit_ref(struct ref_lock *lock);\n /** Release any lock taken but not written. **/\n extern void unlock_ref(struct ref_lock *lock);\n \n-/** Writes sha1 into the ref specified by the lock. **/\n-extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);\n-\n /*\n  * Setup reflog before using. Set errno to something meaningful on failure.\n  */\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249208","messageId":"20140911030927.GQ18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 13/19] refs.c: change resolve_ref_unsafe reading argument to be a flags field","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:09:27Z","receivedAt":"2014-09-11T03:09:27Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Tue, 15 Jul 2014 12:59:36 -0700\n\nresolve_ref_unsafe takes a boolean argument for reading.\nChange this to be a flags field instead and pass the new constant\nRESOLVE_REF_READING when we want this behaviour.\n\nSwap two of the arguments in the function to make sure that we capture any\ninstances where callers have not been updated/aware of the new api so that\nthey can be audited.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n branch.c                |  2 +-\n builtin/blame.c         |  2 +-\n builtin/branch.c        |  9 +++---\n builtin/checkout.c      |  6 ++--\n builtin/clone.c         |  2 +-\n builtin/commit.c        |  2 +-\n builtin/fmt-merge-msg.c |  2 +-\n builtin/for-each-ref.c  |  6 ++--\n builtin/fsck.c          |  2 +-\n builtin/log.c           |  3 +-\n builtin/merge.c         |  2 +-\n builtin/notes.c         |  2 +-\n builtin/receive-pack.c  |  4 +--\n builtin/remote.c        |  5 ++--\n builtin/show-branch.c   |  6 ++--\n builtin/symbolic-ref.c  |  2 +-\n bundle.c                |  2 +-\n cache.h                 | 21 ++++++-------\n http-backend.c          |  3 +-\n notes-merge.c           |  2 +-\n reflog-walk.c           |  5 ++--\n refs.c                  | 79 ++++++++++++++++++++++++++++---------------------\n remote.c                | 10 ++++---\n sequencer.c             |  4 +--\n transport-helper.c      |  4 ++-\n transport.c             |  5 ++--\n upload-pack.c           |  2 +-\n wt-status.c             |  2 +-\n 28 files changed, 111 insertions(+), 85 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 76a8ec9..ba3e1c1 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -186,7 +186,7 @@ int validate_new_branchname(const char *name, struct strbuf *ref,\n \t\tconst char *head;\n \t\tunsigned char sha1[20];\n \n-\t\thead = resolve_ref_unsafe(\"HEAD\", sha1, 0, NULL);\n+\t\thead = resolve_ref_unsafe(\"HEAD\", sha1, NULL, 0);\n \t\tif (!is_bare_repository() && head && !strcmp(head, ref->buf))\n \t\t\tdie(_(\"Cannot force update the current branch.\"));\n \t}\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex a52a279..b8bec0a 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -2292,7 +2292,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,\n \tcommit->object.type = OBJ_COMMIT;\n \tparent_tail = &commit->parents;\n \n-\tif (!resolve_ref_unsafe(\"HEAD\", head_sha1, 1, NULL))\n+\tif (!resolve_ref_unsafe(\"HEAD\", head_sha1, NULL, RESOLVE_REF_READING))\n \t\tdie(\"no such ref: HEAD\");\n \n \tparent_tail = append_parent(parent_tail, head_sha1);\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 652b1d2..f144808 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -129,7 +129,8 @@ static int branch_merged(int kind, const char *name,\n \t\t    branch->merge[0] &&\n \t\t    branch->merge[0]->dst &&\n \t\t    (reference_name = reference_name_to_free =\n-\t\t     resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)\n+\t\t     resolve_refdup(branch->merge[0]->dst, sha1,\n+\t\t\t\t    NULL, RESOLVE_REF_READING)) != NULL)\n \t\t\treference_rev = lookup_commit_reference(sha1);\n \t}\n \tif (!reference_rev)\n@@ -233,7 +234,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\tfree(name);\n \n \t\tname = mkpathdup(fmt, bname.buf);\n-\t\ttarget = resolve_ref_unsafe(name, sha1, 0, &flags);\n+\t\ttarget = resolve_ref_unsafe(name, sha1, &flags, 0);\n \t\tif (!target ||\n \t\t    (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) {\n \t\t\terror(remote_branch\n@@ -296,7 +297,7 @@ static char *resolve_symref(const char *src, const char *prefix)\n \tint flag;\n \tconst char *dst, *cp;\n \n-\tdst = resolve_ref_unsafe(src, sha1, 0, &flag);\n+\tdst = resolve_ref_unsafe(src, sha1, &flag, 0);\n \tif (!(dst && (flag & REF_ISSYMREF)))\n \t\treturn NULL;\n \tif (prefix && (cp = skip_prefix(dst, prefix)))\n@@ -862,7 +863,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \ttrack = git_branch_track;\n \n-\thead = resolve_refdup(\"HEAD\", head_sha1, 0, NULL);\n+\thead = resolve_refdup(\"HEAD\", head_sha1, NULL, 0);\n \tif (!head)\n \t\tdie(_(\"Failed to resolve HEAD as a valid ref.\"));\n \tif (!strcmp(head, \"HEAD\")) {\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex f1dc56e..64af1f7 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -356,7 +356,7 @@ static int checkout_paths(const struct checkout_opts *opts,\n \t    commit_locked_index(lock_file))\n \t\tdie(_(\"unable to write new index file\"));\n \n-\tread_ref_full(\"HEAD\", rev, 0, &flag);\n+\tread_ref_full(\"HEAD\", rev, &flag, 0);\n \thead = lookup_commit_reference_gently(rev, 1);\n \n \terrs |= post_checkout_hook(head, head, 0);\n@@ -771,7 +771,7 @@ static int switch_branches(const struct checkout_opts *opts,\n \tunsigned char rev[20];\n \tint flag, writeout_error = 0;\n \tmemset(&old, 0, sizeof(old));\n-\told.path = path_to_free = resolve_refdup(\"HEAD\", rev, 0, &flag);\n+\told.path = path_to_free = resolve_refdup(\"HEAD\", rev, &flag, 0);\n \told.commit = lookup_commit_reference_gently(rev, 1);\n \tif (!(flag & REF_ISSYMREF))\n \t\told.path = NULL;\n@@ -1068,7 +1068,7 @@ static int checkout_branch(struct checkout_opts *opts,\n \t\tunsigned char rev[20];\n \t\tint flag;\n \n-\t\tif (!read_ref_full(\"HEAD\", rev, 0, &flag) &&\n+\t\tif (!read_ref_full(\"HEAD\", rev, &flag, 0) &&\n \t\t    (flag & REF_ISSYMREF) && is_null_sha1(rev))\n \t\t\treturn switch_unborn_to_new_branch(opts);\n \t}\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex b12989d..12a78e1 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -622,7 +622,7 @@ static int checkout(void)\n \tif (option_no_checkout)\n \t\treturn 0;\n \n-\thead = resolve_refdup(\"HEAD\", sha1, 1, NULL);\n+\thead = resolve_refdup(\"HEAD\", sha1, NULL, RESOLVE_REF_READING);\n \tif (!head) {\n \t\twarning(_(\"remote HEAD refers to nonexistent ref, \"\n \t\t\t  \"unable to checkout.\\n\"));\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex d23e876..3536330 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1468,7 +1468,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,\n \trev.diffopt.break_opt = 0;\n \tdiff_setup_done(&rev.diffopt);\n \n-\thead = resolve_ref_unsafe(\"HEAD\", junk_sha1, 0, NULL);\n+\thead = resolve_ref_unsafe(\"HEAD\", junk_sha1, NULL, 0);\n \tprintf(\"[%s%s \",\n \t\tstarts_with(head, \"refs/heads/\") ?\n \t\t\thead + 11 :\ndiff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c\nindex 3906eda..b2355ad 100644\n--- a/builtin/fmt-merge-msg.c\n+++ b/builtin/fmt-merge-msg.c\n@@ -602,7 +602,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \n \t/* get current branch */\n \tcurrent_branch = current_branch_to_free =\n-\t\tresolve_refdup(\"HEAD\", head_sha1, 1, NULL);\n+\t\tresolve_refdup(\"HEAD\", head_sha1, NULL, RESOLVE_REF_READING);\n \tif (!current_branch)\n \t\tdie(\"No current branch\");\n \tif (starts_with(current_branch, \"refs/heads/\"))\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex 3e1d5c3..e193073 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -649,7 +649,8 @@ static void populate_value(struct refinfo *ref)\n \n \tif (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {\n \t\tunsigned char unused1[20];\n-\t\tref->symref = resolve_refdup(ref->refname, unused1, 1, NULL);\n+\t\tref->symref = resolve_refdup(ref->refname, unused1,\n+\t\t\t\t\t     NULL, RESOLVE_REF_READING);\n \t\tif (!ref->symref)\n \t\t\tref->symref = \"\";\n \t}\n@@ -707,7 +708,8 @@ static void populate_value(struct refinfo *ref)\n \t\t\tconst char *head;\n \t\t\tunsigned char sha1[20];\n \n-\t\t\thead = resolve_ref_unsafe(\"HEAD\", sha1, 1, NULL);\n+\t\t\thead = resolve_ref_unsafe(\"HEAD\", sha1,\n+\t\t\t\t\t\t  NULL, RESOLVE_REF_READING);\n \t\t\tif (!strcmp(ref->refname, head))\n \t\t\t\tv->s = \"*\";\n \t\t\telse\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex fc150c8..506e32b 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -560,7 +560,7 @@ static int fsck_head_link(void)\n \tif (verbose)\n \t\tfprintf(stderr, \"Checking HEAD link\\n\");\n \n-\thead_points_at = resolve_ref_unsafe(\"HEAD\", head_sha1, 0, &flag);\n+\thead_points_at = resolve_ref_unsafe(\"HEAD\", head_sha1, &flag, 0);\n \tif (!head_points_at)\n \t\treturn error(\"Invalid HEAD\");\n \tif (!strcmp(head_points_at, \"HEAD\"))\ndiff --git a/builtin/log.c b/builtin/log.c\nindex a7ba211..230a9ef 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1395,7 +1395,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \t\tif (check_head) {\n \t\t\tunsigned char sha1[20];\n \t\t\tconst char *ref;\n-\t\t\tref = resolve_ref_unsafe(\"HEAD\", sha1, 1, NULL);\n+\t\t\tref = resolve_ref_unsafe(\"HEAD\", sha1, NULL,\n+\t\t\t\t\t\t RESOLVE_REF_READING);\n \t\t\tif (ref && starts_with(ref, \"refs/heads/\"))\n \t\t\t\tbranch_name = xstrdup(ref + strlen(\"refs/heads/\"));\n \t\t\telse\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 428ca24..d262279 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1108,7 +1108,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t * Check if we are _not_ on a detached HEAD, i.e. if there is a\n \t * current branch.\n \t */\n-\tbranch = branch_to_free = resolve_refdup(\"HEAD\", head_sha1, 0, &flag);\n+\tbranch = branch_to_free = resolve_refdup(\"HEAD\", head_sha1, &flag, 0);\n \tif (branch && starts_with(branch, \"refs/heads/\"))\n \t\tbranch += 11;\n \tif (!branch || is_null_sha1(head_sha1))\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 820c341..16df78c 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -703,7 +703,7 @@ static int merge_commit(struct notes_merge_options *o)\n \tinit_notes(t, \"NOTES_MERGE_PARTIAL\", combine_notes_overwrite, 0);\n \n \to->local_ref = local_ref_to_free =\n-\t\tresolve_refdup(\"NOTES_MERGE_REF\", sha1, 0, NULL);\n+\t\tresolve_refdup(\"NOTES_MERGE_REF\", sha1, NULL, 0);\n \tif (!o->local_ref)\n \t\tdie(\"Failed to resolve NOTES_MERGE_REF\");\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex d1f4cf7..555a4a6 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -656,7 +656,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)\n \tint flag;\n \n \tstrbuf_addf(&buf, \"%s%s\", get_git_namespace(), cmd->ref_name);\n-\tdst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag);\n+\tdst_name = resolve_ref_unsafe(buf.buf, sha1, &flag, 0);\n \tstrbuf_release(&buf);\n \n \tif (!(flag & REF_ISSYMREF))\n@@ -817,7 +817,7 @@ static void execute_commands(struct command *commands,\n \tcheck_aliased_updates(commands);\n \n \tfree(head_name_to_free);\n-\thead_name = head_name_to_free = resolve_refdup(\"HEAD\", sha1, 0, NULL);\n+\thead_name = head_name_to_free = resolve_refdup(\"HEAD\", sha1, NULL, 0);\n \n \tchecked_connectivity = 1;\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 401feb3..6eaeee7 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -568,7 +568,8 @@ static int read_remote_branches(const char *refname,\n \tstrbuf_addf(&buf, \"refs/remotes/%s/\", rename->old);\n \tif (starts_with(refname, buf.buf)) {\n \t\titem = string_list_append(rename->remote_branches, xstrdup(refname));\n-\t\tsymref = resolve_ref_unsafe(refname, orig_sha1, 1, &flag);\n+\t\tsymref = resolve_ref_unsafe(refname, orig_sha1, &flag,\n+\t\t\t\t\t    RESOLVE_REF_READING);\n \t\tif (flag & REF_ISSYMREF)\n \t\t\titem->util = xstrdup(symref);\n \t\telse\n@@ -704,7 +705,7 @@ static int mv(int argc, const char **argv)\n \t\tint flag = 0;\n \t\tunsigned char sha1[20];\n \n-\t\tread_ref_full(item->string, sha1, 1, &flag);\n+\t\tread_ref_full(item->string, sha1, &flag, RESOLVE_REF_READING);\n \t\tif (!(flag & REF_ISSYMREF))\n \t\t\tcontinue;\n \t\tif (delete_ref(item->string, NULL, REF_NODEREF))\ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex d873172..ef6ea52 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -727,7 +727,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)\n \t\tif (ac == 0) {\n \t\t\tstatic const char *fake_av[2];\n \n-\t\t\tfake_av[0] = resolve_refdup(\"HEAD\", sha1, 1, NULL);\n+\t\t\tfake_av[0] = resolve_refdup(\"HEAD\", sha1, NULL,\n+\t\t\t\t\t\t    RESOLVE_REF_READING);\n \t\t\tfake_av[1] = NULL;\n \t\t\tav = fake_av;\n \t\t\tac = 1;\n@@ -789,7 +790,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)\n \t\t}\n \t}\n \n-\thead_p = resolve_ref_unsafe(\"HEAD\", head_sha1, 1, NULL);\n+\thead_p = resolve_ref_unsafe(\"HEAD\", head_sha1, NULL,\n+\t\t\t\t    RESOLVE_REF_READING);\n \tif (head_p) {\n \t\thead_len = strlen(head_p);\n \t\tmemcpy(head, head_p, head_len + 1);\ndiff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\nindex b6a711d..1dd04af 100644\n--- a/builtin/symbolic-ref.c\n+++ b/builtin/symbolic-ref.c\n@@ -13,7 +13,7 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print)\n {\n \tunsigned char sha1[20];\n \tint flag;\n-\tconst char *refname = resolve_ref_unsafe(HEAD, sha1, 0, &flag);\n+\tconst char *refname = resolve_ref_unsafe(HEAD, sha1, &flag, 0);\n \n \tif (!refname)\n \t\tdie(\"No such ref: %s\", HEAD);\ndiff --git a/bundle.c b/bundle.c\nindex 1222952..32dd2f7 100644\n--- a/bundle.c\n+++ b/bundle.c\n@@ -311,7 +311,7 @@ int create_bundle(struct bundle_header *header, const char *path,\n \t\t\tcontinue;\n \t\tif (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)\n \t\t\tcontinue;\n-\t\tif (read_ref_full(e->name, sha1, 1, &flag))\n+\t\tif (read_ref_full(e->name, sha1, &flag, RESOLVE_REF_READING))\n \t\t\tflag = 0;\n \t\tdisplay_ref = (flag & REF_ISSYMREF) ? e->name : ref;\n \ndiff --git a/cache.h b/cache.h\nindex e7ec626..2d3c5ed 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -948,7 +948,7 @@ extern int get_sha1_hex(const char *hex, unsigned char *sha1);\n \n extern char *sha1_to_hex(const unsigned char *sha1);\t/* static buffer result! */\n extern int read_ref_full(const char *refname, unsigned char *sha1,\n-\t\t\t int reading, int *flags);\n+\t\t\t int *flags, int resolve_flags);\n extern int read_ref(const char *refname, unsigned char *sha1);\n \n /*\n@@ -960,20 +960,20 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n  * or the input ref.\n  *\n  * If the reference cannot be resolved to an object, the behavior\n- * depends on the \"reading\" argument:\n+ * depends on the RESOLVE_REF_READING flag:\n  *\n- * - If reading is set, return NULL.\n+ * - If RESOLVE_REF_READING is set, return NULL.\n  *\n- * - If reading is not set, clear sha1 and return the name of the last\n- *   reference name in the chain, which will either be a non-symbolic\n+ * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of\n+ *   the last reference name in the chain, which will either be a non-symbolic\n  *   reference or an undefined reference.  If this is a prelude to\n  *   \"writing\" to the ref, the return value is the name of the ref\n  *   that will actually be created or changed.\n  *\n- * If flag is non-NULL, set the value that it points to the\n+ * If flags is non-NULL, set the value that it points to the\n  * combination of REF_ISPACKED (if the reference was found among the\n- * packed references) and REF_ISSYMREF (if the initial reference was a\n- * symbolic reference).\n+ * packed references), REF_ISSYMREF (if the initial reference was a\n+ * symbolic reference) and REF_ISBROKEN (if the ref is malformed).\n  *\n  * If ref is not a properly-formatted, normalized reference, return\n  * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,\n@@ -981,8 +981,9 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n  *\n  * errno is set to something meaningful on error.\n  */\n-extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag);\n-extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag);\n+#define RESOLVE_REF_READING 0x01\n+extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int *flags, int resolve_flags);\n+extern char *resolve_refdup(const char *ref, unsigned char *sha1, int *flags, int resolve_flags);\n \n extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);\n extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);\ndiff --git a/http-backend.c b/http-backend.c\nindex d2c0a62..8f94f9b 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -417,7 +417,8 @@ static int show_head_ref(const char *refname, const unsigned char *sha1,\n \n \tif (flag & REF_ISSYMREF) {\n \t\tunsigned char unused[20];\n-\t\tconst char *target = resolve_ref_unsafe(refname, unused, 1, NULL);\n+\t\tconst char *target = resolve_ref_unsafe(refname, unused,\n+\t\t\t\t\t\tNULL, RESOLVE_REF_READING);\n \t\tconst char *target_nons = strip_namespace(target);\n \n \t\tstrbuf_addf(buf, \"ref: %s\\n\", target_nons);\ndiff --git a/notes-merge.c b/notes-merge.c\nindex 94a1a8a..ffca602 100644\n--- a/notes-merge.c\n+++ b/notes-merge.c\n@@ -549,7 +549,7 @@ int notes_merge(struct notes_merge_options *o,\n \t       o->local_ref, o->remote_ref);\n \n \t/* Dereference o->local_ref into local_sha1 */\n-\tif (read_ref_full(o->local_ref, local_sha1, 0, NULL))\n+\tif (read_ref_full(o->local_ref, local_sha1, NULL, 0))\n \t\tdie(\"Failed to resolve local notes ref '%s'\", o->local_ref);\n \telse if (!check_refname_format(o->local_ref, 0) &&\n \t\tis_null_sha1(local_sha1))\ndiff --git a/reflog-walk.c b/reflog-walk.c\nindex 9ce8b53..feeaf0a 100644\n--- a/reflog-walk.c\n+++ b/reflog-walk.c\n@@ -48,7 +48,8 @@ static struct complete_reflogs *read_complete_reflog(const char *ref)\n \t\tunsigned char sha1[20];\n \t\tconst char *name;\n \t\tvoid *name_to_free;\n-\t\tname = name_to_free = resolve_refdup(ref, sha1, 1, NULL);\n+\t\tname = name_to_free = resolve_refdup(ref, sha1, NULL,\n+\t\t\t\t\t\t     RESOLVE_REF_READING);\n \t\tif (name) {\n \t\t\tfor_each_reflog_ent(name, read_one_reflog, reflogs);\n \t\t\tfree(name_to_free);\n@@ -174,7 +175,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info,\n \t\tif (*branch == '\\0') {\n \t\t\tunsigned char sha1[20];\n \t\t\tfree(branch);\n-\t\t\tbranch = resolve_refdup(\"HEAD\", sha1, 0, NULL);\n+\t\t\tbranch = resolve_refdup(\"HEAD\", sha1, NULL, 0);\n \t\t\tif (!branch)\n \t\t\t\tdie (\"No current branch\");\n \ndiff --git a/refs.c b/refs.c\nindex c2dab4a..a5f9734 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1192,7 +1192,8 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)\n \t\t\t\t\thashclr(sha1);\n \t\t\t\t\tflag |= REF_ISBROKEN;\n \t\t\t\t}\n-\t\t\t} else if (read_ref_full(refname.buf, sha1, 1, &flag)) {\n+\t\t\t} else if (read_ref_full(refname.buf, sha1, &flag,\n+\t\t\t\t\t\t RESOLVE_REF_READING)) {\n \t\t\t\thashclr(sha1);\n \t\t\t\tflag |= REF_ISBROKEN;\n \t\t\t}\n@@ -1344,21 +1345,20 @@ static const char *handle_missing_loose_ref(const char *refname,\n }\n \n /* This function needs to return a meaningful errno on failure */\n-const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)\n+const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *flags, int resolve_flags)\n {\n \tint depth = MAXDEPTH;\n \tssize_t len;\n \tchar buffer[256];\n \tstatic char refname_buffer[256];\n \n-\tif (flag)\n-\t\t*flag = 0;\n+\tif (flags)\n+\t\t*flags = 0;\n \n \tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n \t\terrno = EINVAL;\n \t\treturn NULL;\n \t}\n-\n \tfor (;;) {\n \t\tchar path[PATH_MAX];\n \t\tstruct stat st;\n@@ -1385,7 +1385,8 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\tif (lstat(path, &st) < 0) {\n \t\t\tif (errno == ENOENT)\n \t\t\t\treturn handle_missing_loose_ref(refname, sha1,\n-\t\t\t\t\t\t\t\treading, flag);\n+\t\t\t\t\tresolve_flags & RESOLVE_REF_READING,\n+\t\t\t\t\tflags);\n \t\t\telse\n \t\t\t\treturn NULL;\n \t\t}\n@@ -1405,8 +1406,8 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\t\t\t\t!check_refname_format(buffer, 0)) {\n \t\t\t\tstrcpy(refname_buffer, buffer);\n \t\t\t\trefname = refname_buffer;\n-\t\t\t\tif (flag)\n-\t\t\t\t\t*flag |= REF_ISSYMREF;\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISSYMREF;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t}\n@@ -1451,21 +1452,21 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\t\t */\n \t\t\tif (get_sha1_hex(buffer, sha1) ||\n \t\t\t    (buffer[40] != '\\0' && !isspace(buffer[40]))) {\n-\t\t\t\tif (flag)\n-\t\t\t\t\t*flag |= REF_ISBROKEN;\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISBROKEN;\n \t\t\t\terrno = EINVAL;\n \t\t\t\treturn NULL;\n \t\t\t}\n \t\t\treturn refname;\n \t\t}\n-\t\tif (flag)\n-\t\t\t*flag |= REF_ISSYMREF;\n+\t\tif (flags)\n+\t\t\t*flags |= REF_ISSYMREF;\n \t\tbuf = buffer + 4;\n \t\twhile (isspace(*buf))\n \t\t\tbuf++;\n \t\tif (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {\n-\t\t\tif (flag)\n-\t\t\t\t*flag |= REF_ISBROKEN;\n+\t\t\tif (flags)\n+\t\t\t\t*flags |= REF_ISBROKEN;\n \t\t\terrno = EINVAL;\n \t\t\treturn NULL;\n \t\t}\n@@ -1473,9 +1474,9 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t}\n }\n \n-char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)\n+char *resolve_refdup(const char *ref, unsigned char *sha1, int *flags, int resolve_flags)\n {\n-\tconst char *ret = resolve_ref_unsafe(ref, sha1, reading, flag);\n+\tconst char *ret = resolve_ref_unsafe(ref, sha1, flags, resolve_flags);\n \treturn ret ? xstrdup(ret) : NULL;\n }\n \n@@ -1486,22 +1487,22 @@ struct ref_filter {\n \tvoid *cb_data;\n };\n \n-int read_ref_full(const char *refname, unsigned char *sha1, int reading, int *flags)\n+int read_ref_full(const char *refname, unsigned char *sha1, int *flags, int resolve_flags)\n {\n-\tif (resolve_ref_unsafe(refname, sha1, reading, flags))\n+\tif (resolve_ref_unsafe(refname, sha1, flags, resolve_flags))\n \t\treturn 0;\n \treturn -1;\n }\n \n int read_ref(const char *refname, unsigned char *sha1)\n {\n-\treturn read_ref_full(refname, sha1, 1, NULL);\n+\treturn read_ref_full(refname, sha1, NULL, RESOLVE_REF_READING);\n }\n \n int ref_exists(const char *refname)\n {\n \tunsigned char sha1[20];\n-\treturn !!resolve_ref_unsafe(refname, sha1, 1, NULL);\n+\treturn !!resolve_ref_unsafe(refname, sha1, NULL, RESOLVE_REF_READING);\n }\n \n static int filter_refs(const char *refname, const unsigned char *sha1, int flags,\n@@ -1615,7 +1616,7 @@ int peel_ref(const char *refname, unsigned char *sha1)\n \t\treturn 0;\n \t}\n \n-\tif (read_ref_full(refname, base, 1, &flag))\n+\tif (read_ref_full(refname, base, &flag, RESOLVE_REF_READING))\n \t\treturn -1;\n \n \t/*\n@@ -1656,7 +1657,7 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha\n \tif (!(flags & REF_ISSYMREF))\n \t\treturn 0;\n \n-\tresolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);\n+\tresolves_to = resolve_ref_unsafe(refname, junk, NULL, 0);\n \tif (!resolves_to\n \t    || (d->refname\n \t\t? strcmp(resolves_to, d->refname)\n@@ -1781,7 +1782,7 @@ static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)\n \t\treturn 0;\n \t}\n \n-\tif (!read_ref_full(\"HEAD\", sha1, 1, &flag))\n+\tif (!read_ref_full(\"HEAD\", sha1, &flag, RESOLVE_REF_READING))\n \t\treturn fn(\"HEAD\", sha1, flag, cb_data);\n \n \treturn 0;\n@@ -1861,7 +1862,7 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)\n \tint flag;\n \n \tstrbuf_addf(&buf, \"%sHEAD\", get_git_namespace());\n-\tif (!read_ref_full(buf.buf, sha1, 1, &flag))\n+\tif (!read_ref_full(buf.buf, sha1, &flag, RESOLVE_REF_READING))\n \t\tret = fn(buf.buf, sha1, flag, cb_data);\n \tstrbuf_release(&buf);\n \n@@ -1956,7 +1957,8 @@ int refname_match(const char *abbrev_name, const char *full_name)\n static struct ref_lock *verify_lock(struct ref_lock *lock,\n \tconst unsigned char *old_sha1, int mustexist)\n {\n-\tif (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) {\n+\tif (read_ref_full(lock->ref_name, lock->old_sha1, NULL,\n+\t\t\t  mustexist ? RESOLVE_REF_READING : 0)) {\n \t\tint save_errno = errno;\n \t\terror(\"Can't verify ref %s\", lock->ref_name);\n \t\tunlock_ref(lock);\n@@ -2029,7 +2031,8 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)\n \n \t\tthis_result = refs_found ? sha1_from_ref : sha1;\n \t\tmksnpath(fullref, sizeof(fullref), *p, len, str);\n-\t\tr = resolve_ref_unsafe(fullref, this_result, 1, &flag);\n+\t\tr = resolve_ref_unsafe(fullref, this_result, &flag,\n+\t\t\t\t       RESOLVE_REF_READING);\n \t\tif (r) {\n \t\t\tif (!refs_found++)\n \t\t\t\t*ref = xstrdup(r);\n@@ -2058,7 +2061,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n \t\tconst char *ref, *it;\n \n \t\tmksnpath(path, sizeof(path), *p, len, str);\n-\t\tref = resolve_ref_unsafe(path, hash, 1, NULL);\n+\t\tref = resolve_ref_unsafe(path, hash, NULL, RESOLVE_REF_READING);\n \t\tif (!ref)\n \t\t\tcontinue;\n \t\tif (reflog_exists(path))\n@@ -2093,6 +2096,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \tint last_errno = 0;\n \tint type, lflags;\n \tint mustexist = (old_sha1 && !is_null_sha1(old_sha1));\n+\tint resolve_flags = 0;\n \tint missing = 0;\n \tint attempts_remaining = 3;\n \n@@ -2104,7 +2108,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \tlock = xcalloc(1, sizeof(struct ref_lock));\n \tlock->lock_fd = -1;\n \n-\trefname = resolve_ref_unsafe(refname, lock->old_sha1, mustexist, &type);\n+\tif (mustexist)\n+\t\tresolve_flags |= RESOLVE_REF_READING;\n+\n+\trefname = resolve_ref_unsafe(refname, lock->old_sha1, &type,\n+\t\t\t\t     resolve_flags);\n \tif (!refname && errno == EISDIR) {\n \t\t/* we are trying to lock foo but we used to\n \t\t * have foo/bar which now does not exist;\n@@ -2117,7 +2125,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\terror(\"there are still refs under '%s'\", orig_refname);\n \t\t\tgoto error_return;\n \t\t}\n-\t\trefname = resolve_ref_unsafe(orig_refname, lock->old_sha1, mustexist, &type);\n+\t\trefname = resolve_ref_unsafe(orig_refname, lock->old_sha1,\n+\t\t\t\t\t     &type, resolve_flags);\n \t}\n \tif (type_p)\n \t    *type_p = type;\n@@ -2470,7 +2479,7 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)\n \t\tunsigned char sha1[20];\n \t\tint flags;\n \n-\t\tif (read_ref_full(entry->name, sha1, 0, &flags))\n+\t\tif (read_ref_full(entry->name, sha1, &flags, 0))\n \t\t\t/* We should at least have found the packed ref. */\n \t\t\tdie(\"Internal error\");\n \t\tif ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED)) {\n@@ -2662,7 +2671,8 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \tif (log && S_ISLNK(loginfo.st_mode))\n \t\treturn error(\"reflog for %s is a symlink\", oldrefname);\n \n-\tsymref = resolve_ref_unsafe(oldrefname, orig_sha1, 1, &flag);\n+\tsymref = resolve_ref_unsafe(oldrefname, orig_sha1, &flag,\n+\t\t\t\t    RESOLVE_REF_READING);\n \tif (flag & REF_ISSYMREF)\n \t\treturn error(\"refname %s is a symbolic ref, renaming it is not supported\",\n \t\t\toldrefname);\n@@ -2691,7 +2701,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \t\tgoto rollback;\n \t}\n \n-\tif (!read_ref_full(newrefname, sha1, 1, NULL) &&\n+\tif (!read_ref_full(newrefname, sha1, NULL, RESOLVE_REF_READING) &&\n \t    delete_ref(newrefname, sha1, REF_NODEREF)) {\n \t\tif (errno==EISDIR) {\n \t\t\tif (remove_empty_directories(git_path(\"%s\", newrefname))) {\n@@ -2969,7 +2979,8 @@ static int write_ref_sha1(struct ref_lock *lock,\n \t\tunsigned char head_sha1[20];\n \t\tint head_flag;\n \t\tconst char *head_ref;\n-\t\thead_ref = resolve_ref_unsafe(\"HEAD\", head_sha1, 1, &head_flag);\n+\t\thead_ref = resolve_ref_unsafe(\"HEAD\", head_sha1, &head_flag,\n+\t\t\t\t\t      RESOLVE_REF_READING);\n \t\tif (head_ref && (head_flag & REF_ISSYMREF) &&\n \t\t    !strcmp(head_ref, lock->ref_name))\n \t\t\tlog_ref_write(\"HEAD\", lock->old_sha1, sha1, logmsg);\n@@ -3336,7 +3347,7 @@ static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data\n \t\t\t\tretval = do_for_each_reflog(name, fn, cb_data);\n \t\t\t} else {\n \t\t\t\tunsigned char sha1[20];\n-\t\t\t\tif (read_ref_full(name->buf, sha1, 0, NULL))\n+\t\t\t\tif (read_ref_full(name->buf, sha1, NULL, 0))\n \t\t\t\t\tretval = error(\"bad ref for %s\", name->buf);\n \t\t\t\telse\n \t\t\t\t\tretval = fn(name->buf, sha1, 0, cb_data);\ndiff --git a/remote.c b/remote.c\nindex 0e9459c..67c375d 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -486,7 +486,7 @@ static void read_config(void)\n \t\treturn;\n \tdefault_remote_name = \"origin\";\n \tcurrent_branch = NULL;\n-\thead_ref = resolve_ref_unsafe(\"HEAD\", sha1, 0, &flag);\n+\thead_ref = resolve_ref_unsafe(\"HEAD\", sha1, &flag, 0);\n \tif (head_ref && (flag & REF_ISSYMREF) &&\n \t    starts_with(head_ref, \"refs/heads/\")) {\n \t\tcurrent_branch =\n@@ -1121,7 +1121,8 @@ static char *guess_ref(const char *name, struct ref *peer)\n \tstruct strbuf buf = STRBUF_INIT;\n \tunsigned char sha1[20];\n \n-\tconst char *r = resolve_ref_unsafe(peer->name, sha1, 1, NULL);\n+\tconst char *r = resolve_ref_unsafe(peer->name, sha1, NULL,\n+\t\t\t\t\t   RESOLVE_REF_READING);\n \tif (!r)\n \t\treturn NULL;\n \n@@ -1182,7 +1183,8 @@ static int match_explicit(struct ref *src, struct ref *dst,\n \t\tunsigned char sha1[20];\n \t\tint flag;\n \n-\t\tdst_value = resolve_ref_unsafe(matched_src->name, sha1, 1, &flag);\n+\t\tdst_value = resolve_ref_unsafe(matched_src->name, sha1, &flag,\n+\t\t\t\t\t       RESOLVE_REF_READING);\n \t\tif (!dst_value ||\n \t\t    ((flag & REF_ISSYMREF) &&\n \t\t     !starts_with(dst_value, \"refs/heads/\")))\n@@ -1656,7 +1658,7 @@ static int ignore_symref_update(const char *refname)\n \tunsigned char sha1[20];\n \tint flag;\n \n-\tif (!resolve_ref_unsafe(refname, sha1, 0, &flag))\n+\tif (!resolve_ref_unsafe(refname, sha1, &flag, 0))\n \t\treturn 0; /* non-existing refs are OK */\n \treturn (flag & REF_ISSYMREF);\n }\ndiff --git a/sequencer.c b/sequencer.c\nindex c5b7b8a..6a05ad4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -366,7 +366,7 @@ static int is_index_unchanged(void)\n \tunsigned char head_sha1[20];\n \tstruct commit *head_commit;\n \n-\tif (!resolve_ref_unsafe(\"HEAD\", head_sha1, 1, NULL))\n+\tif (!resolve_ref_unsafe(\"HEAD\", head_sha1, NULL, RESOLVE_REF_READING))\n \t\treturn error(_(\"Could not resolve HEAD commit\\n\"));\n \n \thead_commit = lookup_commit(head_sha1);\n@@ -912,7 +912,7 @@ static int rollback_single_pick(void)\n \tif (!file_exists(git_path(\"CHERRY_PICK_HEAD\")) &&\n \t    !file_exists(git_path(\"REVERT_HEAD\")))\n \t\treturn error(_(\"no cherry-pick or revert in progress\"));\n-\tif (read_ref_full(\"HEAD\", head_sha1, 0, NULL))\n+\tif (read_ref_full(\"HEAD\", head_sha1, NULL, 0))\n \t\treturn error(_(\"cannot resolve HEAD\"));\n \tif (is_null_sha1(head_sha1))\n \t\treturn error(_(\"cannot abort from a branch yet to be born\"));\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 84c616f..8365441 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -889,7 +889,9 @@ static int push_refs_with_export(struct transport *transport,\n \t\t\t\t\tint flag;\n \n \t\t\t\t\t/* Follow symbolic refs (mainly for HEAD). */\n-\t\t\t\t\tname = resolve_ref_unsafe(ref->peer_ref->name, sha1, 1, &flag);\n+\t\t\t\t\tname = resolve_ref_unsafe(\n+\t\t\t\t\t\t ref->peer_ref->name, sha1,\n+\t\t\t\t\t\t &flag, RESOLVE_REF_READING);\n \t\t\t\t\tif (!name || !(flag & REF_ISSYMREF))\n \t\t\t\t\t\tname = ref->peer_ref->name;\n \ndiff --git a/transport.c b/transport.c\nindex 325f03e..3ba7bbf 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -168,7 +168,8 @@ static void set_upstreams(struct transport *transport, struct ref *refs,\n \t\t/* Follow symbolic refs (mainly for HEAD). */\n \t\tlocalname = ref->peer_ref->name;\n \t\tremotename = ref->name;\n-\t\ttmp = resolve_ref_unsafe(localname, sha, 1, &flag);\n+\t\ttmp = resolve_ref_unsafe(localname, sha, &flag,\n+\t\t\t\t\t RESOLVE_REF_READING);\n \t\tif (tmp && flag & REF_ISSYMREF &&\n \t\t\tstarts_with(tmp, \"refs/heads/\"))\n \t\t\tlocalname = tmp;\n@@ -753,7 +754,7 @@ void transport_print_push_status(const char *dest, struct ref *refs,\n \tunsigned char head_sha1[20];\n \tchar *head;\n \n-\thead = resolve_refdup(\"HEAD\", head_sha1, 1, NULL);\n+\thead = resolve_refdup(\"HEAD\", head_sha1, NULL, RESOLVE_REF_READING);\n \n \tif (verbose) {\n \t\tfor (ref = refs; ref; ref = ref->next)\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 01de944..3b51ccb 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -743,7 +743,7 @@ static int find_symref(const char *refname, const unsigned char *sha1, int flag,\n \n \tif ((flag & REF_ISSYMREF) == 0)\n \t\treturn 0;\n-\tsymref_target = resolve_ref_unsafe(refname, unused, 0, &flag);\n+\tsymref_target = resolve_ref_unsafe(refname, unused, &flag, 0);\n \tif (!symref_target || (flag & REF_ISSYMREF) == 0)\n \t\tdie(\"'%s' is a symref but it is not?\", refname);\n \titem = string_list_append(cb_data, refname);\ndiff --git a/wt-status.c b/wt-status.c\nindex 318a191..6819e2b 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -128,7 +128,7 @@ void wt_status_prepare(struct wt_status *s)\n \ts->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;\n \ts->use_color = -1;\n \ts->relative_paths = 1;\n-\ts->branch = resolve_refdup(\"HEAD\", sha1, 0, NULL);\n+\ts->branch = resolve_refdup(\"HEAD\", sha1, NULL, 0);\n \ts->reference = \"HEAD\";\n \ts->fp = stdout;\n \ts->index_file = get_index_file();\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249214","messageId":"20140911031003.GR18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 14/19] branch -d: avoid repeated symref resolution","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:10:03Z","receivedAt":"2014-09-11T03:10:03Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"If a repository gets in a broken state with too much symref nesting,\nit cannot be repaired with \"git branch -d\":\n\n $ git symbolic-ref refs/heads/nonsense refs/heads/nonsense\n $ git branch -d nonsense\n error: branch 'nonsense' not found.\n\nWorse, \"git update-ref --no-deref -d\" doesn't work for such repairs\neither:\n\n $ git update-ref -d refs/heads/nonsense\n error: unable to resolve reference refs/heads/nonsense: Too many levels of symbolic links\n\nFix both by teaching resolve_ref_unsafe a new RESOLVE_REF_NODEREF flag\nand passing it when appropriate.\n\nCallers can still read the value of a symref (for example to print a\nmessage about it) with that flag set --- resolve_ref_unsafe will\nresolve one level of symrefs and stop there.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/branch.c      |  3 ++-\n cache.h               |  1 +\n refs.c                | 10 ++++++++++\n t/t1400-update-ref.sh | 16 ++++++++++++++++\n t/t3200-branch.sh     |  9 +++++++++\n 5 files changed, 38 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex f144808..7925660 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -234,7 +234,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\tfree(name);\n \n \t\tname = mkpathdup(fmt, bname.buf);\n-\t\ttarget = resolve_ref_unsafe(name, sha1, &flags, 0);\n+\t\ttarget = resolve_ref_unsafe(name, sha1,\n+\t\t\t\t\t    &flags, RESOLVE_REF_NODEREF);\n \t\tif (!target ||\n \t\t    (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) {\n \t\t\terror(remote_branch\ndiff --git a/cache.h b/cache.h\nindex 2d3c5ed..03ade12 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -982,6 +982,7 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n  * errno is set to something meaningful on error.\n  */\n #define RESOLVE_REF_READING 0x01\n+#define RESOLVE_REF_NODEREF 0x02\n extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int *flags, int resolve_flags);\n extern char *resolve_refdup(const char *ref, unsigned char *sha1, int *flags, int resolve_flags);\n \ndiff --git a/refs.c b/refs.c\nindex a5f9734..f11df33 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1408,6 +1408,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *fl\n \t\t\t\trefname = refname_buffer;\n \t\t\t\tif (flags)\n \t\t\t\t\t*flags |= REF_ISSYMREF;\n+\t\t\t\tif (resolve_flags & RESOLVE_REF_NODEREF) {\n+\t\t\t\t\thashclr(sha1);\n+\t\t\t\t\treturn refname;\n+\t\t\t\t}\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t}\n@@ -1471,6 +1475,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *fl\n \t\t\treturn NULL;\n \t\t}\n \t\trefname = strcpy(refname_buffer, buf);\n+\t\tif (resolve_flags & RESOLVE_REF_NODEREF) {\n+\t\t\thashclr(sha1);\n+\t\t\treturn refname;\n+\t\t}\n \t}\n }\n \n@@ -2110,6 +2118,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \n \tif (mustexist)\n \t\tresolve_flags |= RESOLVE_REF_READING;\n+\tif (flags & REF_NODEREF)\n+\t\tresolve_flags |= RESOLVE_REF_NODEREF;\n \n \trefname = resolve_ref_unsafe(refname, lock->old_sha1, &type,\n \t\t\t\t     resolve_flags);\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 0218e96..ff4607b 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -110,6 +110,22 @@ test_expect_success \"delete symref without dereference when the referred ref is\n cp -f .git/HEAD.orig .git/HEAD\n git update-ref -d $m\n \n+test_expect_success 'update-ref -d is not confused by self-reference' '\n+\tgit symbolic-ref refs/heads/self refs/heads/self &&\n+\ttest_when_finished \"rm -f .git/refs/heads/self\" &&\n+\ttest_path_is_file .git/refs/heads/self &&\n+\ttest_must_fail git update-ref -d refs/heads/self &&\n+\ttest_path_is_file .git/refs/heads/self\n+'\n+\n+test_expect_success 'update-ref --no-deref -d can delete self-reference' '\n+\tgit symbolic-ref refs/heads/self refs/heads/self &&\n+\ttest_when_finished \"rm -f .git/refs/heads/self\" &&\n+\ttest_path_is_file .git/refs/heads/self &&\n+\tgit update-ref --no-deref -d refs/heads/self &&\n+\ttest_path_is_missing .git/refs/heads/self\n+'\n+\n test_expect_success '(not) create HEAD with old sha1' \"\n \ttest_must_fail git update-ref HEAD $A $B\n \"\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex ac31b71..432921b 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -285,6 +285,15 @@ test_expect_success 'deleting a dangling symref' '\n \ttest_i18ncmp expect actual\n '\n \n+test_expect_success 'deleting a self-referential symref' '\n+\tgit symbolic-ref refs/heads/self-reference refs/heads/self-reference &&\n+\ttest_path_is_file .git/refs/heads/self-reference &&\n+\techo \"Deleted branch self-reference (was refs/heads/self-reference).\" >expect &&\n+\tgit branch -d self-reference >actual &&\n+\ttest_path_is_missing .git/refs/heads/self-reference &&\n+\ttest_i18ncmp expect actual\n+'\n+\n test_expect_success 'renaming a symref is not allowed' '\n \tgit symbolic-ref refs/heads/master2 refs/heads/master &&\n \ttest_must_fail git branch -m master2 master3 &&\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249210","messageId":"20140911031038.GS18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 15/19] refs.c: fix handling of badly named refs","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:10:38Z","receivedAt":"2014-09-11T03:10:38Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 3 Sep 2014 11:45:43 -0700\n\nWe currently do not handle badly named refs well:\n\n  $ cp .git/refs/heads/master .git/refs/heads/master.....@\\*@\\\\.\n  $ git branch\n    fatal: Reference has invalid format: 'refs/heads/master.....@*@\\.'\n  $ git branch -D master.....@\\*@\\\\.\n    error: branch 'master.....@*@\\.' not found.\n\nCurrently we can not really recover from a badly named ref with less than\nmanually deleting the .git/refs/heads/<refname> file. This will also help\nif change the naming rules in the future. For example if we decide to remove\n`, \" and such from the set of valid characters.\n\nThe purpose of this change is to allow git branch --list to show these refs\nand to allow git branch -d/-D and update-ref -d to delete them.\nOther functions will continue to not handle these refs but can be changed in\nlater patches.\n\nIntroduce two new internal flags: RESOLVE_REF_ALLOW_BAD_NAME and REF_BADNAMEOK.\n\nIn resolving functions, refuse to resolve badly named refs unless the new\nRESOLVE_REF_ALLOW_BAD_NAME flag is passed. For these cases, if the badly named\nref exists then flag it as REF_ISBROKEN and resolve it to nullsha1.\n\nIn locking functions, refuse to act on badly named refs unless the new\nREF_BADNAMEOK flag is passed. This is currently used only from branch.c and\nupdate-ref.c when deleting a ref.\n\nChange the internal functions that read loose and packed refs to allow\nbadly named refs but flag them as REF_ISBROKEN just like unresolvable refs\nare.\n\nDuring ref iteration only include these refs during for_each_rawref but\nnone of the other iterators. I.e. just like unresolvable refs, flag\ninclusion of these refs based on DO_FOR_EACH_INCLUDE_BROKEN during\niteration.\n\nIn the transaction API, always refuse to create or update badly named refs\nand refuse to delete them unless REF_BADNAMEOK flag is passed.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/branch.c            | 13 ++++++----\n builtin/update-ref.c        |  3 ++-\n cache.h                     |  8 +++++-\n refs.c                      | 63 ++++++++++++++++++++++++++++++++++++++-------\n refs.h                      |  2 ++\n t/t1402-check-ref-format.sh | 48 ++++++++++++++++++++++++++++++++++\n 6 files changed, 121 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 7925660..5d5bc56 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -234,10 +234,13 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\tfree(name);\n \n \t\tname = mkpathdup(fmt, bname.buf);\n-\t\ttarget = resolve_ref_unsafe(name, sha1,\n-\t\t\t\t\t    &flags, RESOLVE_REF_NODEREF);\n+\t\ttarget = resolve_ref_unsafe(name, sha1, &flags,\n+\t\t\t\t\t    RESOLVE_REF_READING\n+\t\t\t\t\t    | RESOLVE_REF_NODEREF\n+\t\t\t\t\t    | RESOLVE_REF_ALLOW_BAD_NAME);\n \t\tif (!target ||\n-\t\t    (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) {\n+\t\t    (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&\n+\t\t     is_null_sha1(sha1))) {\n \t\t\terror(remote_branch\n \t\t\t      ? _(\"remote branch '%s' not found.\")\n \t\t\t      : _(\"branch '%s' not found.\"), bname.buf);\n@@ -245,14 +248,14 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (!(flags & REF_ISSYMREF) &&\n+\t\tif (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&\n \t\t    check_branch_commit(bname.buf, name, sha1, head_rev, kinds,\n \t\t\t\t\tforce)) {\n \t\t\tret = 1;\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (delete_ref(name, sha1, REF_NODEREF)) {\n+\t\tif (delete_ref(name, sha1, REF_NODEREF|REF_BADNAMEOK)) {\n \t\t\terror(remote_branch\n \t\t\t      ? _(\"Error deleting remote branch '%s'\")\n \t\t\t      : _(\"Error deleting branch '%s'\"),\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 6c9be05..e379fdd 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -419,7 +419,8 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \tif (no_deref)\n \t\tflags = REF_NODEREF;\n \tif (delete)\n-\t\treturn delete_ref(refname, oldval ? oldsha1 : NULL, flags);\n+\t\treturn delete_ref(refname, oldval ? oldsha1 : NULL,\n+\t\t\t\t  flags|REF_BADNAMEOK);\n \telse\n \t\treturn update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,\n \t\t\t\t  flags, UPDATE_REFS_DIE_ON_ERR);\ndiff --git a/cache.h b/cache.h\nindex 03ade12..03a6144 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -979,10 +979,16 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n  * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,\n  * give up and return NULL.\n  *\n- * errno is set to something meaningful on error.\n+ * RESOLVE_REF_ALLOW_BAD_NAME disables most of the ref name checking except\n+ * for names that are absolute paths or contain \"..\" components. For both\n+ * these cases the function will return NULL and set errno to EINVAL.\n+ * If the name is bad then the function will set the REF_ISBROKEN flag and\n+ * return the name, if the ref exists, or NULL, if it does not.\n+ * When this flag is set, any badly named refs will resolve to nullsha1.\n  */\n #define RESOLVE_REF_READING 0x01\n #define RESOLVE_REF_NODEREF 0x02\n+#define RESOLVE_REF_ALLOW_BAD_NAME 0x04\n extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int *flags, int resolve_flags);\n extern char *resolve_refdup(const char *ref, unsigned char *sha1, int *flags, int resolve_flags);\n \ndiff --git a/refs.c b/refs.c\nindex f11df33..6faf0c8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1060,7 +1060,13 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)\n \n \t\trefname = parse_ref_line(refline, sha1);\n \t\tif (refname) {\n-\t\t\tlast = create_ref_entry(refname, sha1, REF_ISPACKED, 1);\n+\t\t\tint flag = REF_ISPACKED;\n+\n+\t\t\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT)) {\n+\t\t\t\tflag |= REF_ISBROKEN;\n+\t\t\t\thashclr(sha1);\n+\t\t\t}\n+\t\t\tlast = create_ref_entry(refname, sha1, flag, 0);\n \t\t\tif (peeled == PEELED_FULLY ||\n \t\t\t    (peeled == PEELED_TAGS && starts_with(refname, \"refs/tags/\")))\n \t\t\t\tlast->flag |= REF_KNOWS_PEELED;\n@@ -1197,8 +1203,14 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)\n \t\t\t\thashclr(sha1);\n \t\t\t\tflag |= REF_ISBROKEN;\n \t\t\t}\n+\t\t\tif (check_refname_format(refname.buf,\n+\t\t\t\t\t\t REFNAME_ALLOW_ONELEVEL|\n+\t\t\t\t\t\t REFNAME_DOT_COMPONENT)) {\n+\t\t\t\thashclr(sha1);\n+\t\t\t\tflag |= REF_ISBROKEN;\n+\t\t\t}\n \t\t\tadd_entry_to_dir(dir,\n-\t\t\t\t\t create_ref_entry(refname.buf, sha1, flag, 1));\n+\t\t\t\t\t create_ref_entry(refname.buf, sha1, flag, 0));\n \t\t}\n \t\tstrbuf_setlen(&refname, dirnamelen);\n \t}\n@@ -1344,6 +1356,18 @@ static const char *handle_missing_loose_ref(const char *refname,\n \t}\n }\n \n+static int escapes_cwd(const char *path) {\n+\tchar *buf;\n+\tint result;\n+\n+\tif (is_absolute_path(path))\n+\t\treturn 1;\n+\tbuf = xmalloc(strlen(path) + 1);\n+\tresult = normalize_path_copy(buf, path);\n+\tfree(buf);\n+\treturn result;\n+}\n+\n /* This function needs to return a meaningful errno on failure */\n const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *flags, int resolve_flags)\n {\n@@ -1351,13 +1375,21 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *fl\n \tssize_t len;\n \tchar buffer[256];\n \tstatic char refname_buffer[256];\n+\tint bad_name = 0;\n \n \tif (flags)\n \t\t*flags = 0;\n \n \tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n-\t\terrno = EINVAL;\n-\t\treturn NULL;\n+\t\tif (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||\n+\t\t    escapes_cwd(refname)) {\n+\t\t\terrno = EINVAL;\n+\t\t\treturn NULL;\n+\t\t}\n+\t\thashclr(sha1);\n+\t\tif (flags)\n+\t\t\t*flags |= REF_ISBROKEN;\n+\t\tbad_name = 1;\n \t}\n \tfor (;;) {\n \t\tchar path[PATH_MAX];\n@@ -1461,6 +1493,8 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *fl\n \t\t\t\terrno = EINVAL;\n \t\t\t\treturn NULL;\n \t\t\t}\n+\t\t\tif (bad_name)\n+\t\t\t\thashclr(sha1);\n \t\t\treturn refname;\n \t\t}\n \t\tif (flags)\n@@ -2108,14 +2142,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \tint missing = 0;\n \tint attempts_remaining = 3;\n \n-\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n-\t\terrno = EINVAL;\n-\t\treturn NULL;\n-\t}\n-\n \tlock = xcalloc(1, sizeof(struct ref_lock));\n \tlock->lock_fd = -1;\n \n+\tif (flags & REF_BADNAMEOK)\n+\t\tresolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;\n+\n \tif (mustexist)\n \t\tresolve_flags |= RESOLVE_REF_READING;\n \tif (flags & REF_NODEREF)\n@@ -3472,6 +3504,13 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tif (have_old && !old_sha1)\n \t\tdie(\"BUG: have_old is true but old_sha1 is NULL\");\n \n+\tif (!is_null_sha1(new_sha1) &&\n+\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\tstrbuf_addf(err, \"refusing to update ref with bad name %s\",\n+\t\t\t    refname);\n+\t\treturn -1;\n+\t}\n+\n \tupdate = add_update(transaction, refname);\n \thashcpy(update->new_sha1, new_sha1);\n \tupdate->flags = flags;\n@@ -3497,6 +3536,12 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \tif (!new_sha1 || is_null_sha1(new_sha1))\n \t\tdie(\"BUG: create ref with null new_sha1\");\n \n+\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\tstrbuf_addf(err, \"refusing to create ref with bad name %s\",\n+\t\t\t    refname);\n+\t\treturn -1;\n+\t}\n+\n \tupdate = add_update(transaction, refname);\n \n \thashcpy(update->new_sha1, new_sha1);\ndiff --git a/refs.h b/refs.h\nindex fafa493..a96e174 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -175,10 +175,12 @@ extern int peel_ref(const char *refname, unsigned char *sha1);\n  * ref_transaction_create(), etc.\n  * REF_NODEREF: act on the ref directly, instead of dereferencing\n  *              symbolic references.\n+ * REF_BADNAMEOK: allow locking a ref that has a bad name.\n  *\n  * Flags >= 0x100 are reserved for internal use.\n  */\n #define REF_NODEREF\t0x01\n+#define REF_BADNAMEOK\t0x02\n /*\n  * This function sets errno to something meaningful on failure.\n  */\ndiff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh\nindex 1a5a5f3..058fa37 100755\n--- a/t/t1402-check-ref-format.sh\n+++ b/t/t1402-check-ref-format.sh\n@@ -196,4 +196,52 @@ invalid_ref_normalized 'heads///foo.lock'\n invalid_ref_normalized 'foo.lock/bar'\n invalid_ref_normalized 'foo.lock///bar'\n \n+test_expect_success 'git branch shows badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch >output &&\n+\tgrep -e \"broken...ref\" output\n+'\n+\n+test_expect_success 'git branch -d can delete badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch -d broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken...ref\" output\n+'\n+\n+test_expect_success 'git branch -D can delete badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch -D broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken...ref\" output\n+'\n+\n+test_expect_success 'git update-ref -d can delete badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit update-ref -d refs/heads/broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken...ref\" output\n+'\n+\n+test_expect_success 'git branch can not create a badly named ref' '\n+\ttest_must_fail git branch broken...ref\n+'\n+\n+test_expect_success 'git branch can not rename to a bad ref name' '\n+\tgit branch goodref &&\n+\ttest_must_fail git branch -m goodref broken...ref\n+'\n+\n+test_expect_failure 'git branch can rename from a bad ref name' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch -m broken...ref renamed &&\n+\ttest_must_fail git rev-parse broken...ref &&\n+\ttest_cmp_rev master renamed\n+'\n+\n test_done\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249209","messageId":"20140911031113.GT18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 16/19] for-each-ref.c: improve message before aborting on broken ref","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:11:13Z","receivedAt":"2014-09-11T03:11:13Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Fri, 5 Sep 2014 14:35:17 -0700\n\nPrint a warning message for any badly named refs we find in the repo.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n builtin/for-each-ref.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex e193073..090390c 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -853,6 +853,12 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f\n \tstruct refinfo *ref;\n \tint cnt;\n \n+\tif ((flag & REF_ISBROKEN) &&\n+\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\t  warning(\"ignoring ref with broken name %s\", refname);\n+\t\t  return 0;\n+\t}\n+\n \tif (*cb->grab_pattern) {\n \t\tconst char **pattern;\n \t\tint namelen = strlen(refname);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249211","messageId":"20140911031150.GU18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 17/19] refs.c: do not permit err == NULL","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:11:51Z","receivedAt":"2014-09-11T03:11:51Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Some functions that take a strbuf argument to append an error to treat\n!err as an indication that the message should be suppressed (e.g.,\nref_update_reject_duplicates).  Others write the message to stderr on\n!err (e.g., repack_without_refs).  Others crash (e.g.,\nref_transaction_update).\n\nSome of these behaviors are for historical reasons and others were\naccidents.  Luckily no callers pass err == NULL any more.  Simplify\nby consistently requiring the strbuf argument.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n refs.c | 46 +++++++++++++++++++++++++++-------------------\n 1 file changed, 27 insertions(+), 19 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 6faf0c8..550223c 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2560,6 +2560,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \tstruct string_list_item *ref_to_delete;\n \tint i, ret, removed = 0;\n \n+\tassert(err);\n+\n \t/* Look for a packed ref */\n \tfor (i = 0; i < n; i++)\n \t\tif (get_packed_ref(refnames[i]))\n@@ -2570,13 +2572,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \t\treturn 0; /* no refname exists in packed refs */\n \n \tif (lock_packed_refs(0)) {\n-\t\tif (err) {\n-\t\t\tunable_to_lock_message(git_path(\"packed-refs\"), errno,\n-\t\t\t\t\t       err);\n-\t\t\treturn -1;\n-\t\t}\n-\t\tunable_to_lock_error(git_path(\"packed-refs\"), errno);\n-\t\treturn error(\"cannot delete '%s' from packed refs\", refnames[i]);\n+\t\tunable_to_lock_message(git_path(\"packed-refs\"), errno, err);\n+\t\treturn -1;\n \t}\n \tpacked = get_packed_refs(&ref_cache);\n \n@@ -2602,7 +2599,7 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \n \t/* Write what remains */\n \tret = commit_packed_refs();\n-\tif (ret && err)\n+\tif (ret)\n \t\tstrbuf_addf(err, \"unable to overwrite old ref-pack file: %s\",\n \t\t\t    strerror(errno));\n \treturn ret;\n@@ -2610,6 +2607,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \n static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)\n {\n+\tassert(err);\n+\n \tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n \t\t/* loose */\n \t\tint res, i = strlen(lock->lk->filename) - 5; /* .lock */\n@@ -3459,6 +3458,8 @@ struct ref_transaction {\n \n struct ref_transaction *ref_transaction_begin(struct strbuf *err)\n {\n+\tassert(err);\n+\n \treturn xcalloc(1, sizeof(struct ref_transaction));\n }\n \n@@ -3498,6 +3499,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tassert(err);\n+\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: update called for transaction that is not open\");\n \n@@ -3530,6 +3533,8 @@ int ref_transaction_create(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tassert(err);\n+\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: create called for transaction that is not open\");\n \n@@ -3561,6 +3566,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tassert(err);\n+\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: delete called for transaction that is not open\");\n \n@@ -3623,13 +3630,14 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,\n \t\t\t\t\tstruct strbuf *err)\n {\n \tint i;\n+\n+\tassert(err);\n+\n \tfor (i = 1; i < n; i++)\n \t\tif (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {\n-\t\t\tconst char *str =\n-\t\t\t\t\"Multiple updates for ref '%s' not allowed.\";\n-\t\t\tif (err)\n-\t\t\t\tstrbuf_addf(err, str, updates[i]->refname);\n-\n+\t\t\tstrbuf_addf(err,\n+\t\t\t\t    \"Multiple updates for ref '%s' not allowed.\",\n+\t\t\t\t    updates[i]->refname);\n \t\t\treturn 1;\n \t\t}\n \treturn 0;\n@@ -3643,6 +3651,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \tint n = transaction->nr;\n \tstruct ref_update **updates = transaction->updates;\n \n+\tassert(err);\n+\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: commit called for transaction that is not open\");\n \n@@ -3675,9 +3685,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tif (!update->lock) {\n \t\t\tint df_conflict = (errno == ENOTDIR);\n \n-\t\t\tif (err)\n-\t\t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n-\t\t\t\t\t    update->refname);\n+\t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n+\t\t\t\t    update->refname);\n \t\t\tret = df_conflict ? UPDATE_REFS_NAME_CONFLICT : -1;\n \t\t\tgoto cleanup;\n \t\t}\n@@ -3692,9 +3701,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t\t     update->msg);\n \t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n \t\t\tif (ret) {\n-\t\t\t\tif (err)\n-\t\t\t\t\tstrbuf_addf(err, \"Cannot update the ref '%s'.\",\n-\t\t\t\t\t\t    update->refname);\n+\t\t\t\tstrbuf_addf(err, \"Cannot update the ref '%s'.\",\n+\t\t\t\t\t    update->refname);\n \t\t\t\tret = -1;\n \t\t\t\tgoto cleanup;\n \t\t\t}\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249212","messageId":"20140911031211.GV18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 18/19] lockfile: remove unable_to_lock_error","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:12:11Z","receivedAt":"2014-09-11T03:12:11Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"The former caller uses unable_to_lock_message now.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Ronnie Sahlberg <sahlberg@google.com>\n---\n cache.h    |  1 -\n lockfile.c | 10 ----------\n 2 files changed, 11 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 03a6144..995729f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -558,7 +558,6 @@ struct lock_file {\n };\n #define LOCK_DIE_ON_ERROR 1\n #define LOCK_NODEREF 2\n-extern int unable_to_lock_error(const char *path, int err);\n extern void unable_to_lock_message(const char *path, int err,\n \t\t\t\t   struct strbuf *buf);\n extern NORETURN void unable_to_lock_index_die(const char *path, int err);\ndiff --git a/lockfile.c b/lockfile.c\nindex a921d77..dbd4101 100644\n--- a/lockfile.c\n+++ b/lockfile.c\n@@ -176,16 +176,6 @@ void unable_to_lock_message(const char *path, int err, struct strbuf *buf)\n \t\t\t    absolute_path(path), strerror(err));\n }\n \n-int unable_to_lock_error(const char *path, int err)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tunable_to_lock_message(path, err, &buf);\n-\terror(\"%s\", buf.buf);\n-\tstrbuf_release(&buf);\n-\treturn -1;\n-}\n-\n NORETURN void unable_to_lock_index_die(const char *path, int err)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249213","messageId":"20140911031238.GW18279@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH 19/19] ref_transaction_commit: bail out on failure to remove a ref","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-11T03:12:38Z","receivedAt":"2014-09-11T03:12:38Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"When removal of a loose or packed ref fails, bail out instead of\ntrying to finish the transaction.  This way, a single error message\ncan be printed (instead of multiple messages being concatenated by\nmistake) and the operator can try to solve the underlying problem\nbefore there is a chance to muck things up even more.\n\nIn particular, when git fails to remove a ref, git goes on to try to\ndelete the reflog.  Exiting early lets us keep the reflog.\n\nWhen git succeeds in deleting a ref A and fails to remove a ref B, it\ngoes on to try to delete both reflogs.  It would be better to just\nremove the reflog for A, but that would be a more invasive change.\nFailing early means we keep both reflogs, which puts the operator in a\ngood position to understand the problem and recover.\n\nA long term goal is to avoid these problems altogether and roll back\nthe transaction on failure.  That kind of transactionality will have\nto wait for a later series (the plan for which is to make all\ndestructive work happen in a single update of the packed-refs file).\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Ronnie Sahlberg <sahlberg@google.com>\n---\nThanks for reading.\n\n refs.c | 8 ++++++--\n 1 file changed, 6 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 550223c..3b27758 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3714,16 +3714,20 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (update->lock) {\n-\t\t\tif (delete_ref_loose(update->lock, update->type, err))\n+\t\t\tif (delete_ref_loose(update->lock, update->type, err)) {\n \t\t\t\tret = -1;\n+\t\t\t\tgoto cleanup;\n+\t\t\t}\n \n \t\t\tif (!(update->flags & REF_ISPRUNING))\n \t\t\t\tdelnames[delnum++] = update->lock->ref_name;\n \t\t}\n \t}\n \n-\tif (repack_without_refs(delnames, delnum, err))\n+\tif (repack_without_refs(delnames, delnum, err)) {\n \t\tret = -1;\n+\t\tgoto cleanup;\n+\t}\n \tfor (i = 0; i < delnum; i++)\n \t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n \tclear_loose_ref_cache(&ref_cache);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249264","messageId":"xmqqk359defg.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-11T21:40:03Z","receivedAt":"2014-09-11T21:40:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> Jonathan Nieder wrote:\n>\n>> The next series from Ronnie's collection is available at\n>> https://code-review.googlesource.com/#/q/topic:ref-transaction in case\n>> someone wants a fresh series to look at.\n>\n> Here is the outcome of that review.  It could use another set of eyes\n> (hint, hint) but should be mostly ready.  Interdiff below.  This is meant\n> to replace rs/ref-transaction in 'pu' and I'd prefer to wait a little\n> for more comments before merging to 'next'.\n\nAlright.  I'd assume that all the other rs/ref-transaction* topics\nthat depends on rs/ref-transaction series will be rerolled on top of\nthis new round when ready.\n\nThanks.\n"},{"id":"249266","messageId":"xmqqfvfxdcjz.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-11T22:20:32Z","receivedAt":"2014-09-11T22:20:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> These patches are also available from the git repository at\n>\n>   git://repo.or.cz/git/jrn.git tags/rs/ref-transaction\n\nThe tag fetched and built as-is seems to break 5514 among other\nthings (\"git remote rm\" segfaults).\n"},{"id":"249272","messageId":"20140912004717.GY18279@google.com","threadId":"37248","inReplyTo":"xmqqfvfxdcjz.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-12T00:47:17Z","receivedAt":"2014-09-12T00:47:17Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Junio C Hamano wrote:\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>> These patches are also available from the git repository at\n>>\n>>   git://repo.or.cz/git/jrn.git tags/rs/ref-transaction\n>\n> The tag fetched and built as-is seems to break 5514 among other\n> things (\"git remote rm\" segfaults).\n\nYeah, I noticed that right after sending the series out. :/\n\nThe patch below fixes it[1].\n\n-- >8 --\nFrom: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 11 Sep 2014 08:42:57 -0700\nSubject: remote rm/prune: print a message when writing packed-refs fails\n\nUntil v2.1.0-rc0~22^2~11 (refs.c: add an err argument to\nrepack_without_refs, 2014-06-20), repack_without_refs forgot to\nprovide an error message when commit_packed_refs fails.  Even today,\nit only provides a message for callers that pass a non-NULL err\nparameter.  Internal callers in refs.c pass non-NULL err but\n\"git remote\" does not.\n\nThat means that \"git remote rm\" and \"git remote prune\" can fail\nwithout printing a message about why.  Fix them by passing in a\nnon-NULL err parameter and printing the returned message.\n\nThis is the last caller to a ref handling function passing err ==\nNULL.  A later patch can drop support for err == NULL, avoiding such\nproblems in the future.\n\nChange-Id: Ifb8a726ef03d0aa282a25a102313064d2e8ec283\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n[1] https://code-review.googlesource.com/1110\n    https://code-review.googlesource.com/1060\n\n builtin/remote.c | 15 +++++++++++----\n 1 file changed, 11 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 6eaeee7..ef1ffc3 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -750,13 +750,16 @@ static int mv(int argc, const char **argv)\n \n static int remove_branches(struct string_list *branches)\n {\n+\tstruct strbuf err = STRBUF_INIT;\n \tconst char **branch_names;\n \tint i, result = 0;\n \n \tbranch_names = xmalloc(branches->nr * sizeof(*branch_names));\n \tfor (i = 0; i < branches->nr; i++)\n \t\tbranch_names[i] = branches->items[i].string;\n-\tresult |= repack_without_refs(branch_names, branches->nr, NULL);\n+\tif (repack_without_refs(branch_names, branches->nr, &err))\n+\t\tresult |= error(\"%s\", err.buf);\n+\tstrbuf_release(&err);\n \tfree(branch_names);\n \n \tfor (i = 0; i < branches->nr; i++) {\n@@ -1333,9 +1336,13 @@ static int prune_remote(const char *remote, int dry_run)\n \t\tdelete_refs = xmalloc(states.stale.nr * sizeof(*delete_refs));\n \t\tfor (i = 0; i < states.stale.nr; i++)\n \t\t\tdelete_refs[i] = states.stale.items[i].util;\n-\t\tif (!dry_run)\n-\t\t\tresult |= repack_without_refs(delete_refs,\n-\t\t\t\t\t\t      states.stale.nr, NULL);\n+\t\tif (!dry_run) {\n+\t\t\tstruct strbuf err = STRBUF_INIT;\n+\t\t\tif (repack_without_refs(delete_refs, states.stale.nr,\n+\t\t\t\t\t\t&err))\n+\t\t\t\tresult |= error(\"%s\", err.buf);\n+\t\t\tstrbuf_release(&err);\n+\t\t}\n \t\tfree(delete_refs);\n \t}\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"249322","messageId":"xmqqsijwaclo.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"20140912004717.GY18279@google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-12T19:00:03Z","receivedAt":"2014-09-12T19:00:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> Junio C Hamano wrote:\n>> Jonathan Nieder <jrnieder@gmail.com> writes:\n>\n>>> These patches are also available from the git repository at\n>>>\n>>>   git://repo.or.cz/git/jrn.git tags/rs/ref-transaction\n>>\n>> The tag fetched and built as-is seems to break 5514 among other\n>> things (\"git remote rm\" segfaults).\n>\n> Yeah, I noticed that right after sending the series out. :/\n>\n> The patch below fixes it[1].\n\nIs this meant to replace anything, or is it \"Oops, the previous ones\nare broken, and this is to patch it up on top\"?\n\n> -- >8 --\n> From: Ronnie Sahlberg <sahlberg@google.com>\n> Date: Thu, 11 Sep 2014 08:42:57 -0700\n> Subject: remote rm/prune: print a message when writing packed-refs fails\n>\n> Until v2.1.0-rc0~22^2~11 (refs.c: add an err argument to\n> repack_without_refs, 2014-06-20), repack_without_refs forgot to\n> provide an error message when commit_packed_refs fails.  Even today,\n> it only provides a message for callers that pass a non-NULL err\n> parameter.  Internal callers in refs.c pass non-NULL err but\n> \"git remote\" does not.\n>\n> That means that \"git remote rm\" and \"git remote prune\" can fail\n> without printing a message about why.  Fix them by passing in a\n> non-NULL err parameter and printing the returned message.\n>\n> This is the last caller to a ref handling function passing err ==\n> NULL.  A later patch can drop support for err == NULL, avoiding such\n> problems in the future.\n>\n> Change-Id: Ifb8a726ef03d0aa282a25a102313064d2e8ec283\n> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>\n> ---\n> [1] https://code-review.googlesource.com/1110\n>     https://code-review.googlesource.com/1060\n>\n>  builtin/remote.c | 15 +++++++++++----\n>  1 file changed, 11 insertions(+), 4 deletions(-)\n>\n> diff --git a/builtin/remote.c b/builtin/remote.c\n> index 6eaeee7..ef1ffc3 100644\n> --- a/builtin/remote.c\n> +++ b/builtin/remote.c\n> @@ -750,13 +750,16 @@ static int mv(int argc, const char **argv)\n>  \n>  static int remove_branches(struct string_list *branches)\n>  {\n> +\tstruct strbuf err = STRBUF_INIT;\n>  \tconst char **branch_names;\n>  \tint i, result = 0;\n>  \n>  \tbranch_names = xmalloc(branches->nr * sizeof(*branch_names));\n>  \tfor (i = 0; i < branches->nr; i++)\n>  \t\tbranch_names[i] = branches->items[i].string;\n> -\tresult |= repack_without_refs(branch_names, branches->nr, NULL);\n> +\tif (repack_without_refs(branch_names, branches->nr, &err))\n> +\t\tresult |= error(\"%s\", err.buf);\n> +\tstrbuf_release(&err);\n>  \tfree(branch_names);\n>  \n>  \tfor (i = 0; i < branches->nr; i++) {\n> @@ -1333,9 +1336,13 @@ static int prune_remote(const char *remote, int dry_run)\n>  \t\tdelete_refs = xmalloc(states.stale.nr * sizeof(*delete_refs));\n>  \t\tfor (i = 0; i < states.stale.nr; i++)\n>  \t\t\tdelete_refs[i] = states.stale.items[i].util;\n> -\t\tif (!dry_run)\n> -\t\t\tresult |= repack_without_refs(delete_refs,\n> -\t\t\t\t\t\t      states.stale.nr, NULL);\n> +\t\tif (!dry_run) {\n> +\t\t\tstruct strbuf err = STRBUF_INIT;\n> +\t\t\tif (repack_without_refs(delete_refs, states.stale.nr,\n> +\t\t\t\t\t\t&err))\n> +\t\t\t\tresult |= error(\"%s\", err.buf);\n> +\t\t\tstrbuf_release(&err);\n> +\t\t}\n>  \t\tfree(delete_refs);\n>  \t}\n"},{"id":"249327","messageId":"20140912191812.GZ18279@google.com","threadId":"37248","inReplyTo":"xmqqsijwaclo.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-12T19:18:12Z","receivedAt":"2014-09-12T19:18:12Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Junio C Hamano wrote:\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n> > Junio C Hamano wrote:\n\n>>> The tag fetched and built as-is seems to break 5514 among other\n>>> things (\"git remote rm\" segfaults).\n>>\n>> Yeah, I noticed that right after sending the series out. :/\n>>\n>> The patch below fixes it[1].\n>\n> Is this meant to replace anything, or is it \"Oops, the previous ones\n> are broken, and this is to patch it up on top\"?\n\nIt's \"Oops, the next one (refs.c: do not permit err == NULL) is broken,\nand this is to patch it up in advance\". :)\n\nBut it should apply on top, too.\n\nThere were a few other minor changes, and I think with them the series\nshould be ready for \"next\".  My \"send and hope that more reviewers\nappear\" strategy didn't really work, so I'll send a reroll of the\nseries as-is in an hour or so.\n\nHere's an interdiff as a preview.\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 5d5bc56..4bf931e 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -238,9 +238,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\t\t\t\t    RESOLVE_REF_READING\n \t\t\t\t\t    | RESOLVE_REF_NODEREF\n \t\t\t\t\t    | RESOLVE_REF_ALLOW_BAD_NAME);\n-\t\tif (!target ||\n-\t\t    (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&\n-\t\t     is_null_sha1(sha1))) {\n+\t\tif (!target) {\n \t\t\terror(remote_branch\n \t\t\t      ? _(\"remote branch '%s' not found.\")\n \t\t\t      : _(\"branch '%s' not found.\"), bname.buf);\n@@ -268,8 +266,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\t\t       ? _(\"Deleted remote branch %s (was %s).\\n\")\n \t\t\t       : _(\"Deleted branch %s (was %s).\\n\"),\n \t\t\t       bname.buf,\n-\t\t\t       (flags & REF_ISSYMREF)\n-\t\t\t       ? target\n+\t\t\t       (flags & REF_ISBROKEN) ? \"broken\"\n+\t\t\t       : (flags & REF_ISSYMREF) ? target\n \t\t\t       : find_unique_abbrev(sha1, DEFAULT_ABBREV));\n \t\t}\n \t\tdelete_branch_config(bname.buf);\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 6eaeee7..ef1ffc3 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -750,13 +750,16 @@ static int mv(int argc, const char **argv)\n \n static int remove_branches(struct string_list *branches)\n {\n+\tstruct strbuf err = STRBUF_INIT;\n \tconst char **branch_names;\n \tint i, result = 0;\n \n \tbranch_names = xmalloc(branches->nr * sizeof(*branch_names));\n \tfor (i = 0; i < branches->nr; i++)\n \t\tbranch_names[i] = branches->items[i].string;\n-\tresult |= repack_without_refs(branch_names, branches->nr, NULL);\n+\tif (repack_without_refs(branch_names, branches->nr, &err))\n+\t\tresult |= error(\"%s\", err.buf);\n+\tstrbuf_release(&err);\n \tfree(branch_names);\n \n \tfor (i = 0; i < branches->nr; i++) {\n@@ -1333,9 +1336,13 @@ static int prune_remote(const char *remote, int dry_run)\n \t\tdelete_refs = xmalloc(states.stale.nr * sizeof(*delete_refs));\n \t\tfor (i = 0; i < states.stale.nr; i++)\n \t\t\tdelete_refs[i] = states.stale.items[i].util;\n-\t\tif (!dry_run)\n-\t\t\tresult |= repack_without_refs(delete_refs,\n-\t\t\t\t\t\t      states.stale.nr, NULL);\n+\t\tif (!dry_run) {\n+\t\t\tstruct strbuf err = STRBUF_INIT;\n+\t\t\tif (repack_without_refs(delete_refs, states.stale.nr,\n+\t\t\t\t\t\t&err))\n+\t\t\t\tresult |= error(\"%s\", err.buf);\n+\t\t\tstrbuf_release(&err);\n+\t\t}\n \t\tfree(delete_refs);\n \t}\n \n"},{"id":"249330","messageId":"xmqqk358a9yz.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"20140912191812.GZ18279@google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-12T19:56:52Z","receivedAt":"2014-09-12T19:56:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> It's \"Oops, the next one (refs.c: do not permit err == NULL) is broken,\n> and this is to patch it up in advance\". :)\n>\n> But it should apply on top, too.\n\nI think \"in advance\" makes sense for this one, too.\n\n> There were a few other minor changes, and I think with them the series\n> should be ready for \"next\".  My \"send and hope that more reviewers\n> appear\" strategy didn't really work,...\n\nYou sent them just a few days ago.  Expecting anybody to have a\nsolid time to sit thru a 19-patch series inside that timeframe is\nnot so realistic.\n\n> so I'll send a reroll of the series as-is in an hour or so.\n\nOK.  Thanks.\n"},{"id":"249331","messageId":"xmqqd2b0a7m9.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"xmqqk358a9yz.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-12T20:47:42Z","receivedAt":"2014-09-12T20:47:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n>\n>> It's \"Oops, the next one (refs.c: do not permit err == NULL) is broken,\n>> and this is to patch it up in advance\". :)\n>>\n>> But it should apply on top, too.\n>\n> I think \"in advance\" makes sense for this one, too.\n>\n>> There were a few other minor changes, and I think with them the series\n>> should be ready for \"next\".  My \"send and hope that more reviewers\n>> appear\" strategy didn't really work,...\n>\n> You sent them just a few days ago.  Expecting anybody to have a\n> solid time to sit thru a 19-patch series inside that timeframe is\n> not so realistic.\n>\n>> so I'll send a reroll of the series as-is in an hour or so.\n>\n> OK.  Thanks.\n\nI do not think I have enough time to pick a reroll up to redo the\nday's integration, so please take time to make it perfect.\n\nI noticed that with this series merged to the version I use daily,\ndetaching HEAD (i.e. \"git checkout HEAD^0\") breaks my HEAD reflog,\nwhich made me redo the integration ejecting the series out of 'pu'.\n\nNot happy, as my workflow relies fairly heavily on detached HEAD\nX-<.\n"},{"id":"249332","messageId":"54136B10.4050001@alum.mit.edu","threadId":"37248","inReplyTo":"xmqqk358a9yz.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2014-09-12T21:52:16Z","receivedAt":"2014-09-12T21:52:16Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 09/12/2014 09:56 PM, Junio C Hamano wrote:\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n> [...]\n>> There were a few other minor changes, and I think with them the series\n>> should be ready for \"next\".  My \"send and hope that more reviewers\n>> appear\" strategy didn't really work,...\n> \n> You sent them just a few days ago.  Expecting anybody to have a\n> solid time to sit thru a 19-patch series inside that timeframe is\n> not so realistic.\n\nIt's hard to tell from my glacial (tectonic?) pace, but I really do plan\nto work through all of Ronnie's ref-related patches. Of course it's up\nto you whether to wait for me. I really hope to get through the third\nseries by the end of next week.\n\n>> so I'll send a reroll of the series as-is in an hour or so.\n\nJonathan: Is a current version of this patch series set up for review in\nGerrit?\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"249334","messageId":"20140912235745.GB18279@google.com","threadId":"37248","inReplyTo":"54136B10.4050001@alum.mit.edu","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-12T23:57:45Z","receivedAt":"2014-09-12T23:57:45Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Michael Haggerty wrote:\n>> Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>>> so I'll send a reroll of the series as-is in an hour or so.\n>\n> Jonathan: Is a current version of this patch series set up for review in\n> Gerrit?\n\nYes.\n(https://code-review.googlesource.com/#/q/project:git+topic:ref-transaction)\n\nThanks,\nJonathan\n"},{"id":"249351","messageId":"xmqqvbor8l2e.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"xmqqd2b0a7m9.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-13T17:52:25Z","receivedAt":"2014-09-13T17:52:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> I noticed that with this series merged to the version I use daily,\n> detaching HEAD (i.e. \"git checkout HEAD^0\") breaks my HEAD reflog,\n> which made me redo the integration ejecting the series out of 'pu'.\n>\n> Not happy, as my workflow relies fairly heavily on detached HEAD\n> X-<.\n\nJust FYI.\n\nBisecting the series using the attached as a test script points\n\"branch -d: avoid repeated symref resolution\" as a possible culprit.\nPerhaps these tests may want to be added to t3200 which is touched\nby the said commit (or add them earlier in the series)?\n\n-- >8 --\n\n#!/bin/sh\n\ntest_description='reflog not nuked with co HEAD^0'\n. ./test-lib.sh\n\ncheck_reflog () {\n\twhile read name\n\tdo\n\t\tgit rev-parse --verify \"$name\"\n\tdone >expect &&\n\tif test -f \"$2\"\n\tthen\n\t\twhile read object rest\n\t\tdo\n\t\t\tgit rev-parse --verify \"$object\"\n\t\tdone >>expect <\"$2\"\n\tfi &&\n\twhile read object rest\n\tdo\n\t\tgit rev-parse --verify \"$object\"\n\tdone >actual <\"$1\" &&\n\ttest_cmp expect actual\n}\n\ntest_expect_success setup '\n\ttest_tick &&\n\tgit commit --allow-empty -m initial &&\n\tgit branch side &&\n\ttest_tick &&\n\tgit commit --allow-empty -m second &&\n\tgit log -g --oneline >baseline &&\n\tcheck_reflog baseline <<-\\EOF\n\tmaster\n\tmaster^\n\tEOF\n'\n\ntest_expect_success 'switch to branch' '\n\tgit checkout side &&\n\tgit log -g --oneline >switched &&\n\tcheck_reflog switched baseline <<-\\EOF\n\tside\n\tEOF\n'\n\ntest_expect_success 'detach to other' '\n\tgit checkout master^0 &&\n\tgit log -g --oneline >detach-1 &&\n\tcheck_reflog detach-1 switched <<-\\EOF\n\tmaster\n\tEOF\n'\n\ntest_expect_success 'attach to self' '\n\tgit checkout master &&\n\tgit log -g --oneline >detach-2 &&\n\tcheck_reflog detach-2 detach-1 <<-\\EOF\n\tmaster\n\tEOF\n'\n\ntest_expect_success 'detach to self' '\n\tgit checkout master^0 &&\n\tgit log -g --oneline >detach-3 &&\n\tcheck_reflog detach-3 detach-2 <<-\\EOF\n\tmaster\n\tEOF\n'\n\ntest_expect_success 'attach to other' '\n\tgit checkout HEAD^0 &&\n\tgit checkout side &&\n\tgit log -g --oneline >detach-4 &&\n\tcheck_reflog detach-4 detach-3 <<-\\EOF\n\tside\n\tEOF\n'\n\ntest_done\n"},{"id":"249597","messageId":"54198B39.8020405@alum.mit.edu","threadId":"37248","inReplyTo":"20140912235745.GB18279@google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2014-09-17T13:23:05Z","receivedAt":"2014-09-17T13:23:05Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 09/13/2014 01:57 AM, Jonathan Nieder wrote:\n> Michael Haggerty wrote:\n>>> Jonathan Nieder <jrnieder@gmail.com> writes:\n> \n>>>> so I'll send a reroll of the series as-is in an hour or so.\n>>\n>> Jonathan: Is a current version of this patch series set up for review in\n>> Gerrit?\n> \n> Yes.\n> (https://code-review.googlesource.com/#/q/project:git+topic:ref-transaction)\n\nI just worked through the patch series, leaving lots of comments in\nGerrit. Overall it looks pretty good and makes a lot of very worthwhile\nprogress. The only patch that gives me a bit of heartburn is\n\n    [PATCH 15/19] refs.c: fix handling of badly named refs\n\nnot because it is necessarily wrong, but because it has a lot of\nnon-local effects that are hard to evaluate. I made a bunch of comments\nin Gerrit about that patch, too, and will wait for a response before\nhaving another go at it.\n\nThanks for all your hard and detailed work, Ronnie and Jonathan!\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"249638","messageId":"xmqqfvfoq3ra.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"54198B39.8020405@alum.mit.edu","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-18T16:42:33Z","receivedAt":"2014-09-18T16:42:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael Haggerty <mhagger@alum.mit.edu> writes:\n\n> On 09/13/2014 01:57 AM, Jonathan Nieder wrote:\n>> Michael Haggerty wrote:\n>>>> Jonathan Nieder <jrnieder@gmail.com> writes:\n>> \n>>>>> so I'll send a reroll of the series as-is in an hour or so.\n>>>\n>>> Jonathan: Is a current version of this patch series set up for review in\n>>> Gerrit?\n>> \n>> Yes.\n>> (https://code-review.googlesource.com/#/q/project:git+topic:ref-transaction)\n>\n> I just worked through the patch series, leaving lots of comments in\n> Gerrit. Overall it looks pretty good and makes a lot of very worthwhile\n> progress. The only patch that gives me a bit of heartburn is\n>\n>     [PATCH 15/19] refs.c: fix handling of badly named refs\n>\n> not because it is necessarily wrong, but because it has a lot of\n> non-local effects that are hard to evaluate. I made a bunch of comments\n> in Gerrit about that patch, too, and will wait for a response before\n> having another go at it.\n>\n> Thanks for all your hard and detailed work, Ronnie and Jonathan!\n>\n> Michael\n\nJonathan: Is a current version of this patch series set up to be\nfetched so that it can be reviewed outside Gerrit?\n\nRunning ls-remote against https://code.googlesource.com/git shows\nmany refs under refs/changes/* but it is unclear to me if there is a\ncoherent single \"here is the latest and greatest, dependents rebased\non dependeds in the right order\" thing that I can fetch and look at\nwith \"log -p master..\".\n"},{"id":"249640","messageId":"20140918165721.GA772@google.com","threadId":"37248","inReplyTo":"xmqqfvfoq3ra.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-18T16:57:22Z","receivedAt":"2014-09-18T16:57:22Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Junio C Hamano wrote:\n\n> Jonathan: Is a current version of this patch series set up to be\n> fetched so that it can be reviewed outside Gerrit?\n\nThe current tip is 06d707cb63e34fc55a18ecc47e668f3c44acae57 from\nhttps://code.googlesource.com/git (fetch-by-sha1 should work).  Each\nreroll gets its own refname of the form refs/changes/62/1062/<number>\nwith <number> increasing.  The \"Download\" widget in the top-right\ncorner of https://code-review.googlesource.com/1062 shows the refname.\n\nThere are plenty of unaddressed comments from Michael, so I'd hold off\non picking up the latest for pu for now.\n\nThanks,\nJonathan\n"},{"id":"249642","messageId":"xmqq4mw4q1qc.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"20140918165721.GA772@google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-18T17:26:19Z","receivedAt":"2014-09-18T17:26:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> Junio C Hamano wrote:\n>\n>> Jonathan: Is a current version of this patch series set up to be\n>> fetched so that it can be reviewed outside Gerrit?\n>\n> The current tip is 06d707cb63e34fc55a18ecc47e668f3c44acae57 from\n> https://code.googlesource.com/git (fetch-by-sha1 should work).  Each\n> reroll gets its own refname of the form refs/changes/62/1062/<number>\n> with <number> increasing.  The \"Download\" widget in the top-right\n> corner of https://code-review.googlesource.com/1062 shows the refname.\n\nWhile I am showing my naiveté, how does one figure out that 1062 is\nthe last one in the series?\n\nDoes the order of changes that appear in\n\nhttps://code-review.googlesource.com/#/q/project:git+branch:master+topic:ref-transaction\n\nhave any significance?  e.g. is a \"topic\" supposed to be a single\nstrand of pearls on top of the \"branch\", and the top one is the tip,\nor something?\n\n> There are plenty of unaddressed comments from Michael, so I'd hold off\n> on picking up the latest for pu for now.\n\nOh, the request was not for that.  I simply cannot read the patch\nwith only limited context in the web browser without having a ready\naccess to a coherent whole, i.e. a full tree with history, to review\na change like this.\n"},{"id":"249645","messageId":"20140918173801.GC1175@google.com","threadId":"37248","inReplyTo":"xmqq4mw4q1qc.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-18T17:38:01Z","receivedAt":"2014-09-18T17:38:01Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Junio C Hamano wrote:\n\n> Does the order of changes that appear in\n>\n> https://code-review.googlesource.com/#/q/project:git+branch:master+topic:ref-transaction\n>\n> have any significance?  e.g. is a \"topic\" supposed to be a single\n> strand of pearls on top of the \"branch\", and the top one is the tip,\n> or something?\n\nAlas no, though that would be a good feature.\n\nSearch results are ordered by which was last updated (for example when\nsomeone adds a comment).\n\nThe 'Related Changes' shown on the change screen are in topological\norder, with the tip at the top.\n\nJonathan\n"},{"id":"249866","messageId":"xmqqfvff9ydi.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-25T21:35:53Z","receivedAt":"2014-09-25T21:35:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"I know that a review-update cycle is still going in the dark at\n\n  https://code-review.googlesource.com/#/q/topic:ref-transaction\n\nfor this series.  Are we almost there for v22 which hopefully be the\nfinal before we merge it to 'next' and go incremental?  \n"},{"id":"249867","messageId":"20140925214049.GP1175@google.com","threadId":"37248","inReplyTo":"xmqqfvff9ydi.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-09-25T21:40:49Z","receivedAt":"2014-09-25T21:40:49Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Junio C Hamano wrote:\n\n> I know that a review-update cycle is still going in the dark at\n>\n>   https://code-review.googlesource.com/#/q/topic:ref-transaction\n>\n> for this series.\n\nEh, it's at least public and doesn't flood the list with rebased\nversions of the series.\n\nWould you prefer if there were some list archived on gmane with the\nautomated mails from gerrit, to make it easier to look back at later?\n\n>                  Are we almost there for v22 which hopefully be the\n> final before we merge it to 'next' and go incremental?\n\nThe patch \"fix handling of badly named refs\"[1] is still undergoing\nheavy churn.\n\nI think it's worth getting that one right.\n\nThanks,\nJonathan\n\n[1] https://code-review.googlesource.com/1070\n"},{"id":"249868","messageId":"xmqqbnq39xh2.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"20140925214049.GP1175@google.com","subject":"Re: [PATCH v21 0/19] rs/ref-transaction (Re: Transaction patch series overview)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-25T21:55:21Z","receivedAt":"2014-09-25T21:55:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> The patch \"fix handling of badly named refs\"[1] is still undergoing\n> heavy churn.\n>\n> I think it's worth getting that one right.\n\nOh, no question about it.  I was only making sure that I didn't miss\navailability of updates for larger series we saw during this cycle.\n"},{"id":"250134","messageId":"20141002014817.GS1175@google.com","threadId":"37248","inReplyTo":"20140911030318.GD18279@google.com","subject":"[PATCH v22 0/24] rs/ref-transaction","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T01:48:17Z","receivedAt":"2014-10-02T01:48:17Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jonathan Nieder wrote:\n> Jonathan Nieder wrote:\n\n>> The next series from Ronnie's collection is available at\n>> https://code-review.googlesource.com/#/q/topic:ref-transaction in case\n>> someone wants a fresh series to look at.\n>\n> Here is the outcome of that review.  It could use another set of eyes\n> (hint, hint)\n\nAnother set of eyes arrived and helped.  Here's a reroll.\n\nJonathan Nieder (6):\n  mv test: recreate mod/ directory instead of relying on stale copy\n  branch -d: avoid repeated symref resolution\n  packed-ref cache: forbid dot-components in refnames\n  refs.c: do not permit err == NULL\n  lockfile: remove unable_to_lock_error\n  ref_transaction_commit: bail out on failure to remove a ref\n\nJunio C Hamano (1):\n  reflog test: test interaction with detached HEAD\n\nRonnie Sahlberg (17):\n  wrapper.c: remove/unlink_or_warn: simplify, treat ENOENT as success\n  wrapper.c: add a new function unlink_or_msg\n  refs.c: add an err argument to delete_ref_loose\n  refs.c: pass the ref log message to _create/delete/update instead of\n    _commit\n  rename_ref: don't ask read_ref_full where the ref came from\n  refs.c: refuse to lock badly named refs in lock_ref_sha1_basic\n  refs.c: call lock_ref_sha1_basic directly from commit\n  refs.c: pass a list of names to skip to is_refname_available\n  refs.c: ref_transaction_commit: distinguish name conflicts from other\n    errors\n  fetch.c: change s_update_ref to use a ref transaction\n  refs.c: make write_ref_sha1 static\n  refs.c: change resolve_ref_unsafe reading argument to be a flags field\n  branch -d: simplify by using RESOLVE_REF_READING flag\n  test: put tests for handling of bad ref names in one place\n  refs.c: allow listing and deleting badly named refs\n  for-each-ref.c: improve message before aborting on broken ref\n  remote rm/prune: print a message when writing packed-refs fails\n\n branch.c                 |   6 +-\n builtin/blame.c          |   2 +-\n builtin/branch.c         |  22 ++-\n builtin/checkout.c       |   6 +-\n builtin/clone.c          |   2 +-\n builtin/commit.c         |   6 +-\n builtin/fetch.c          |  34 ++--\n builtin/fmt-merge-msg.c  |   2 +-\n builtin/for-each-ref.c   |  11 +-\n builtin/fsck.c           |   2 +-\n builtin/log.c            |   3 +-\n builtin/merge.c          |   2 +-\n builtin/notes.c          |   2 +-\n builtin/receive-pack.c   |   9 +-\n builtin/remote.c         |  20 ++-\n builtin/replace.c        |   5 +-\n builtin/show-branch.c    |   7 +-\n builtin/symbolic-ref.c   |   2 +-\n builtin/tag.c            |   4 +-\n builtin/update-ref.c     |  13 +-\n bundle.c                 |   2 +-\n cache.h                  |  42 +++--\n fast-import.c            |   8 +-\n git-compat-util.h        |  16 +-\n http-backend.c           |   4 +-\n lockfile.c               |  10 --\n notes-merge.c            |   2 +-\n reflog-walk.c            |   5 +-\n refs.c                   | 438 ++++++++++++++++++++++++++++++++---------------\n refs.h                   |  40 +++--\n remote.c                 |  11 +-\n sequencer.c              |   8 +-\n t/t1400-update-ref.sh    |  62 +++----\n t/t1413-reflog-detach.sh |  70 ++++++++\n t/t1430-bad-ref-name.sh  | 207 ++++++++++++++++++++++\n t/t3200-branch.sh        |   9 +\n t/t7001-mv.sh            |  15 +-\n t/t9300-fast-import.sh   |  30 ----\n transport-helper.c       |   5 +-\n transport.c              |   5 +-\n upload-pack.c            |   2 +-\n walker.c                 |   5 +-\n wrapper.c                |  28 ++-\n wt-status.c              |   2 +-\n 44 files changed, 838 insertions(+), 348 deletions(-)\n create mode 100755 t/t1413-reflog-detach.sh\n create mode 100755 t/t1430-bad-ref-name.sh\n\n-- \n2.0.0.450.ga793d96\n\n---\nChanges since v21:\n\n branch.c                    |   2 +-\n builtin/blame.c             |   2 +-\n builtin/branch.c            |  25 ++---\n builtin/checkout.c          |   6 +-\n builtin/clone.c             |   2 +-\n builtin/commit.c            |   2 +-\n builtin/fetch.c             |   6 +-\n builtin/fmt-merge-msg.c     |   2 +-\n builtin/for-each-ref.c      |  11 +-\n builtin/fsck.c              |   2 +-\n builtin/log.c               |   4 +-\n builtin/merge.c             |   2 +-\n builtin/notes.c             |   2 +-\n builtin/receive-pack.c      |   4 +-\n builtin/remote.c            |  21 ++--\n builtin/show-branch.c       |   9 +-\n builtin/symbolic-ref.c      |   2 +-\n builtin/update-ref.c        |   3 +-\n bundle.c                    |   2 +-\n cache.h                     |  33 ++++--\n http-backend.c              |   5 +-\n notes-merge.c               |   2 +-\n reflog-walk.c               |   6 +-\n refs.c                      | 263 +++++++++++++++++++++++++-------------------\n refs.h                      |  34 +++---\n remote.c                    |  13 ++-\n sequencer.c                 |   4 +-\n t/t1400-update-ref.sh       |  46 ++------\n t/t1402-check-ref-format.sh |  48 --------\n t/t1413-reflog-detach.sh    |  70 ++++++++++++\n t/t1430-bad-ref-name.sh     | 207 ++++++++++++++++++++++++++++++++++\n t/t9300-fast-import.sh      |  30 -----\n transport-helper.c          |   5 +-\n transport.c                 |   6 +-\n upload-pack.c               |   2 +-\n wt-status.c                 |   2 +-\n 36 files changed, 557 insertions(+), 328 deletions(-)\n\ndiff --git c/branch.c w/branch.c\nindex ba3e1c1..adb07c6 100644\n--- c/branch.c\n+++ w/branch.c\n@@ -186,7 +186,7 @@ int validate_new_branchname(const char *name, struct strbuf *ref,\n \t\tconst char *head;\n \t\tunsigned char sha1[20];\n \n-\t\thead = resolve_ref_unsafe(\"HEAD\", sha1, NULL, 0);\n+\t\thead = resolve_ref_unsafe(\"HEAD\", 0, sha1, NULL);\n \t\tif (!is_bare_repository() && head && !strcmp(head, ref->buf))\n \t\t\tdie(_(\"Cannot force update the current branch.\"));\n \t}\ndiff --git c/builtin/blame.c w/builtin/blame.c\nindex b8bec0a..5cbd38f 100644\n--- c/builtin/blame.c\n+++ w/builtin/blame.c\n@@ -2292,7 +2292,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,\n \tcommit->object.type = OBJ_COMMIT;\n \tparent_tail = &commit->parents;\n \n-\tif (!resolve_ref_unsafe(\"HEAD\", head_sha1, NULL, RESOLVE_REF_READING))\n+\tif (!resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING, head_sha1, NULL))\n \t\tdie(\"no such ref: HEAD\");\n \n \tparent_tail = append_parent(parent_tail, head_sha1);\ndiff --git c/builtin/branch.c w/builtin/branch.c\nindex 5d5bc56..94aaea1 100644\n--- c/builtin/branch.c\n+++ w/builtin/branch.c\n@@ -129,8 +129,8 @@ static int branch_merged(int kind, const char *name,\n \t\t    branch->merge[0] &&\n \t\t    branch->merge[0]->dst &&\n \t\t    (reference_name = reference_name_to_free =\n-\t\t     resolve_refdup(branch->merge[0]->dst, sha1,\n-\t\t\t\t    NULL, RESOLVE_REF_READING)) != NULL)\n+\t\t     resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING,\n+\t\t\t\t    sha1, NULL)) != NULL)\n \t\t\treference_rev = lookup_commit_reference(sha1);\n \t}\n \tif (!reference_rev)\n@@ -234,13 +234,12 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\tfree(name);\n \n \t\tname = mkpathdup(fmt, bname.buf);\n-\t\ttarget = resolve_ref_unsafe(name, sha1, &flags,\n+\t\ttarget = resolve_ref_unsafe(name,\n \t\t\t\t\t    RESOLVE_REF_READING\n-\t\t\t\t\t    | RESOLVE_REF_NODEREF\n-\t\t\t\t\t    | RESOLVE_REF_ALLOW_BAD_NAME);\n-\t\tif (!target ||\n-\t\t    (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&\n-\t\t     is_null_sha1(sha1))) {\n+\t\t\t\t\t    | RESOLVE_REF_NO_RECURSE\n+\t\t\t\t\t    | RESOLVE_REF_ALLOW_BAD_NAME,\n+\t\t\t\t\t    sha1, &flags);\n+\t\tif (!target) {\n \t\t\terror(remote_branch\n \t\t\t      ? _(\"remote branch '%s' not found.\")\n \t\t\t      : _(\"branch '%s' not found.\"), bname.buf);\n@@ -255,7 +254,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (delete_ref(name, sha1, REF_NODEREF|REF_BADNAMEOK)) {\n+\t\tif (delete_ref(name, sha1, REF_NODEREF)) {\n \t\t\terror(remote_branch\n \t\t\t      ? _(\"Error deleting remote branch '%s'\")\n \t\t\t      : _(\"Error deleting branch '%s'\"),\n@@ -268,8 +267,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\t\t       ? _(\"Deleted remote branch %s (was %s).\\n\")\n \t\t\t       : _(\"Deleted branch %s (was %s).\\n\"),\n \t\t\t       bname.buf,\n-\t\t\t       (flags & REF_ISSYMREF)\n-\t\t\t       ? target\n+\t\t\t       (flags & REF_ISBROKEN) ? \"broken\"\n+\t\t\t       : (flags & REF_ISSYMREF) ? target\n \t\t\t       : find_unique_abbrev(sha1, DEFAULT_ABBREV));\n \t\t}\n \t\tdelete_branch_config(bname.buf);\n@@ -301,7 +300,7 @@ static char *resolve_symref(const char *src, const char *prefix)\n \tint flag;\n \tconst char *dst, *cp;\n \n-\tdst = resolve_ref_unsafe(src, sha1, &flag, 0);\n+\tdst = resolve_ref_unsafe(src, 0, sha1, &flag);\n \tif (!(dst && (flag & REF_ISSYMREF)))\n \t\treturn NULL;\n \tif (prefix && (cp = skip_prefix(dst, prefix)))\n@@ -867,7 +866,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \ttrack = git_branch_track;\n \n-\thead = resolve_refdup(\"HEAD\", head_sha1, NULL, 0);\n+\thead = resolve_refdup(\"HEAD\", 0, head_sha1, NULL);\n \tif (!head)\n \t\tdie(_(\"Failed to resolve HEAD as a valid ref.\"));\n \tif (!strcmp(head, \"HEAD\")) {\ndiff --git c/builtin/checkout.c w/builtin/checkout.c\nindex 64af1f7..a5fef2d 100644\n--- c/builtin/checkout.c\n+++ w/builtin/checkout.c\n@@ -356,7 +356,7 @@ static int checkout_paths(const struct checkout_opts *opts,\n \t    commit_locked_index(lock_file))\n \t\tdie(_(\"unable to write new index file\"));\n \n-\tread_ref_full(\"HEAD\", rev, &flag, 0);\n+\tread_ref_full(\"HEAD\", 0, rev, &flag);\n \thead = lookup_commit_reference_gently(rev, 1);\n \n \terrs |= post_checkout_hook(head, head, 0);\n@@ -771,7 +771,7 @@ static int switch_branches(const struct checkout_opts *opts,\n \tunsigned char rev[20];\n \tint flag, writeout_error = 0;\n \tmemset(&old, 0, sizeof(old));\n-\told.path = path_to_free = resolve_refdup(\"HEAD\", rev, &flag, 0);\n+\told.path = path_to_free = resolve_refdup(\"HEAD\", 0, rev, &flag);\n \told.commit = lookup_commit_reference_gently(rev, 1);\n \tif (!(flag & REF_ISSYMREF))\n \t\told.path = NULL;\n@@ -1068,7 +1068,7 @@ static int checkout_branch(struct checkout_opts *opts,\n \t\tunsigned char rev[20];\n \t\tint flag;\n \n-\t\tif (!read_ref_full(\"HEAD\", rev, &flag, 0) &&\n+\t\tif (!read_ref_full(\"HEAD\", 0, rev, &flag) &&\n \t\t    (flag & REF_ISSYMREF) && is_null_sha1(rev))\n \t\t\treturn switch_unborn_to_new_branch(opts);\n \t}\ndiff --git c/builtin/clone.c w/builtin/clone.c\nindex 12a78e1..0f5c880 100644\n--- c/builtin/clone.c\n+++ w/builtin/clone.c\n@@ -622,7 +622,7 @@ static int checkout(void)\n \tif (option_no_checkout)\n \t\treturn 0;\n \n-\thead = resolve_refdup(\"HEAD\", sha1, NULL, RESOLVE_REF_READING);\n+\thead = resolve_refdup(\"HEAD\", RESOLVE_REF_READING, sha1, NULL);\n \tif (!head) {\n \t\twarning(_(\"remote HEAD refers to nonexistent ref, \"\n \t\t\t  \"unable to checkout.\\n\"));\ndiff --git c/builtin/commit.c w/builtin/commit.c\nindex 3536330..9ccc78b 100644\n--- c/builtin/commit.c\n+++ w/builtin/commit.c\n@@ -1468,7 +1468,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,\n \trev.diffopt.break_opt = 0;\n \tdiff_setup_done(&rev.diffopt);\n \n-\thead = resolve_ref_unsafe(\"HEAD\", junk_sha1, NULL, 0);\n+\thead = resolve_ref_unsafe(\"HEAD\", 0, junk_sha1, NULL);\n \tprintf(\"[%s%s \",\n \t\tstarts_with(head, \"refs/heads/\") ?\n \t\t\thead + 11 :\ndiff --git c/builtin/fetch.c w/builtin/fetch.c\nindex 2e3bc73..30b40f6 100644\n--- c/builtin/fetch.c\n+++ w/builtin/fetch.c\n@@ -392,10 +392,10 @@ static int s_update_ref(const char *action,\n \t\tgoto fail;\n \n \tret = ref_transaction_commit(transaction, &err);\n-\tif (ret == UPDATE_REFS_NAME_CONFLICT)\n-\t\tdf_conflict = 1;\n-\tif (ret)\n+\tif (ret) {\n+\t\tdf_conflict = (ret == TRANSACTION_NAME_CONFLICT);\n \t\tgoto fail;\n+\t}\n \n \tref_transaction_free(transaction);\n \tstrbuf_release(&err);\ndiff --git c/builtin/fmt-merge-msg.c w/builtin/fmt-merge-msg.c\nindex b2355ad..afe05dc 100644\n--- c/builtin/fmt-merge-msg.c\n+++ w/builtin/fmt-merge-msg.c\n@@ -602,7 +602,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \n \t/* get current branch */\n \tcurrent_branch = current_branch_to_free =\n-\t\tresolve_refdup(\"HEAD\", head_sha1, NULL, RESOLVE_REF_READING);\n+\t\tresolve_refdup(\"HEAD\", RESOLVE_REF_READING, head_sha1, NULL);\n \tif (!current_branch)\n \t\tdie(\"No current branch\");\n \tif (starts_with(current_branch, \"refs/heads/\"))\ndiff --git c/builtin/for-each-ref.c w/builtin/for-each-ref.c\nindex 090390c..a88d681 100644\n--- c/builtin/for-each-ref.c\n+++ w/builtin/for-each-ref.c\n@@ -649,8 +649,8 @@ static void populate_value(struct refinfo *ref)\n \n \tif (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {\n \t\tunsigned char unused1[20];\n-\t\tref->symref = resolve_refdup(ref->refname, unused1,\n-\t\t\t\t\t     NULL, RESOLVE_REF_READING);\n+\t\tref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING,\n+\t\t\t\t\t     unused1, NULL);\n \t\tif (!ref->symref)\n \t\t\tref->symref = \"\";\n \t}\n@@ -708,8 +708,8 @@ static void populate_value(struct refinfo *ref)\n \t\t\tconst char *head;\n \t\t\tunsigned char sha1[20];\n \n-\t\t\thead = resolve_ref_unsafe(\"HEAD\", sha1,\n-\t\t\t\t\t\t  NULL, RESOLVE_REF_READING);\n+\t\t\thead = resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t\t\t  sha1, NULL);\n \t\t\tif (!strcmp(ref->refname, head))\n \t\t\t\tv->s = \"*\";\n \t\t\telse\n@@ -853,8 +853,7 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f\n \tstruct refinfo *ref;\n \tint cnt;\n \n-\tif ((flag & REF_ISBROKEN) &&\n-\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\tif (flag & REF_BAD_NAME) {\n \t\t  warning(\"ignoring ref with broken name %s\", refname);\n \t\t  return 0;\n \t}\ndiff --git c/builtin/fsck.c w/builtin/fsck.c\nindex 506e32b..7cd109a 100644\n--- c/builtin/fsck.c\n+++ w/builtin/fsck.c\n@@ -560,7 +560,7 @@ static int fsck_head_link(void)\n \tif (verbose)\n \t\tfprintf(stderr, \"Checking HEAD link\\n\");\n \n-\thead_points_at = resolve_ref_unsafe(\"HEAD\", head_sha1, &flag, 0);\n+\thead_points_at = resolve_ref_unsafe(\"HEAD\", 0, head_sha1, &flag);\n \tif (!head_points_at)\n \t\treturn error(\"Invalid HEAD\");\n \tif (!strcmp(head_points_at, \"HEAD\"))\ndiff --git c/builtin/log.c w/builtin/log.c\nindex 230a9ef..493440a 100644\n--- c/builtin/log.c\n+++ w/builtin/log.c\n@@ -1395,8 +1395,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \t\tif (check_head) {\n \t\t\tunsigned char sha1[20];\n \t\t\tconst char *ref;\n-\t\t\tref = resolve_ref_unsafe(\"HEAD\", sha1, NULL,\n-\t\t\t\t\t\t RESOLVE_REF_READING);\n+\t\t\tref = resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t\t\t sha1, NULL);\n \t\t\tif (ref && starts_with(ref, \"refs/heads/\"))\n \t\t\t\tbranch_name = xstrdup(ref + strlen(\"refs/heads/\"));\n \t\t\telse\ndiff --git c/builtin/merge.c w/builtin/merge.c\nindex d262279..6f56967 100644\n--- c/builtin/merge.c\n+++ w/builtin/merge.c\n@@ -1108,7 +1108,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t * Check if we are _not_ on a detached HEAD, i.e. if there is a\n \t * current branch.\n \t */\n-\tbranch = branch_to_free = resolve_refdup(\"HEAD\", head_sha1, &flag, 0);\n+\tbranch = branch_to_free = resolve_refdup(\"HEAD\", 0, head_sha1, &flag);\n \tif (branch && starts_with(branch, \"refs/heads/\"))\n \t\tbranch += 11;\n \tif (!branch || is_null_sha1(head_sha1))\ndiff --git c/builtin/notes.c w/builtin/notes.c\nindex 16df78c..eaf297d 100644\n--- c/builtin/notes.c\n+++ w/builtin/notes.c\n@@ -703,7 +703,7 @@ static int merge_commit(struct notes_merge_options *o)\n \tinit_notes(t, \"NOTES_MERGE_PARTIAL\", combine_notes_overwrite, 0);\n \n \to->local_ref = local_ref_to_free =\n-\t\tresolve_refdup(\"NOTES_MERGE_REF\", sha1, NULL, 0);\n+\t\tresolve_refdup(\"NOTES_MERGE_REF\", 0, sha1, NULL);\n \tif (!o->local_ref)\n \t\tdie(\"Failed to resolve NOTES_MERGE_REF\");\n \ndiff --git c/builtin/receive-pack.c w/builtin/receive-pack.c\nindex 555a4a6..8a6e7e3 100644\n--- c/builtin/receive-pack.c\n+++ w/builtin/receive-pack.c\n@@ -656,7 +656,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)\n \tint flag;\n \n \tstrbuf_addf(&buf, \"%s%s\", get_git_namespace(), cmd->ref_name);\n-\tdst_name = resolve_ref_unsafe(buf.buf, sha1, &flag, 0);\n+\tdst_name = resolve_ref_unsafe(buf.buf, 0, sha1, &flag);\n \tstrbuf_release(&buf);\n \n \tif (!(flag & REF_ISSYMREF))\n@@ -817,7 +817,7 @@ static void execute_commands(struct command *commands,\n \tcheck_aliased_updates(commands);\n \n \tfree(head_name_to_free);\n-\thead_name = head_name_to_free = resolve_refdup(\"HEAD\", sha1, NULL, 0);\n+\thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, sha1, NULL);\n \n \tchecked_connectivity = 1;\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\ndiff --git c/builtin/remote.c w/builtin/remote.c\nindex 6eaeee7..8517cfa 100644\n--- c/builtin/remote.c\n+++ w/builtin/remote.c\n@@ -568,8 +568,8 @@ static int read_remote_branches(const char *refname,\n \tstrbuf_addf(&buf, \"refs/remotes/%s/\", rename->old);\n \tif (starts_with(refname, buf.buf)) {\n \t\titem = string_list_append(rename->remote_branches, xstrdup(refname));\n-\t\tsymref = resolve_ref_unsafe(refname, orig_sha1, &flag,\n-\t\t\t\t\t    RESOLVE_REF_READING);\n+\t\tsymref = resolve_ref_unsafe(refname, RESOLVE_REF_READING,\n+\t\t\t\t\t    orig_sha1, &flag);\n \t\tif (flag & REF_ISSYMREF)\n \t\t\titem->util = xstrdup(symref);\n \t\telse\n@@ -705,7 +705,7 @@ static int mv(int argc, const char **argv)\n \t\tint flag = 0;\n \t\tunsigned char sha1[20];\n \n-\t\tread_ref_full(item->string, sha1, &flag, RESOLVE_REF_READING);\n+\t\tread_ref_full(item->string, RESOLVE_REF_READING, sha1, &flag);\n \t\tif (!(flag & REF_ISSYMREF))\n \t\t\tcontinue;\n \t\tif (delete_ref(item->string, NULL, REF_NODEREF))\n@@ -750,13 +750,16 @@ static int mv(int argc, const char **argv)\n \n static int remove_branches(struct string_list *branches)\n {\n+\tstruct strbuf err = STRBUF_INIT;\n \tconst char **branch_names;\n \tint i, result = 0;\n \n \tbranch_names = xmalloc(branches->nr * sizeof(*branch_names));\n \tfor (i = 0; i < branches->nr; i++)\n \t\tbranch_names[i] = branches->items[i].string;\n-\tresult |= repack_without_refs(branch_names, branches->nr, NULL);\n+\tif (repack_without_refs(branch_names, branches->nr, &err))\n+\t\tresult |= error(\"%s\", err.buf);\n+\tstrbuf_release(&err);\n \tfree(branch_names);\n \n \tfor (i = 0; i < branches->nr; i++) {\n@@ -1333,9 +1336,13 @@ static int prune_remote(const char *remote, int dry_run)\n \t\tdelete_refs = xmalloc(states.stale.nr * sizeof(*delete_refs));\n \t\tfor (i = 0; i < states.stale.nr; i++)\n \t\t\tdelete_refs[i] = states.stale.items[i].util;\n-\t\tif (!dry_run)\n-\t\t\tresult |= repack_without_refs(delete_refs,\n-\t\t\t\t\t\t      states.stale.nr, NULL);\n+\t\tif (!dry_run) {\n+\t\t\tstruct strbuf err = STRBUF_INIT;\n+\t\t\tif (repack_without_refs(delete_refs, states.stale.nr,\n+\t\t\t\t\t\t&err))\n+\t\t\t\tresult |= error(\"%s\", err.buf);\n+\t\t\tstrbuf_release(&err);\n+\t\t}\n \t\tfree(delete_refs);\n \t}\n \ndiff --git c/builtin/show-branch.c w/builtin/show-branch.c\nindex ef6ea52..acc8dc1 100644\n--- c/builtin/show-branch.c\n+++ w/builtin/show-branch.c\n@@ -727,8 +727,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)\n \t\tif (ac == 0) {\n \t\t\tstatic const char *fake_av[2];\n \n-\t\t\tfake_av[0] = resolve_refdup(\"HEAD\", sha1, NULL,\n-\t\t\t\t\t\t    RESOLVE_REF_READING);\n+\t\t\tfake_av[0] = resolve_refdup(\"HEAD\",\n+\t\t\t\t\t\t    RESOLVE_REF_READING,\n+\t\t\t\t\t\t    sha1, NULL);\n \t\t\tfake_av[1] = NULL;\n \t\t\tav = fake_av;\n \t\t\tac = 1;\n@@ -790,8 +791,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)\n \t\t}\n \t}\n \n-\thead_p = resolve_ref_unsafe(\"HEAD\", head_sha1, NULL,\n-\t\t\t\t    RESOLVE_REF_READING);\n+\thead_p = resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t    head_sha1, NULL);\n \tif (head_p) {\n \t\thead_len = strlen(head_p);\n \t\tmemcpy(head, head_p, head_len + 1);\ndiff --git c/builtin/symbolic-ref.c w/builtin/symbolic-ref.c\nindex 1dd04af..29fb3f1 100644\n--- c/builtin/symbolic-ref.c\n+++ w/builtin/symbolic-ref.c\n@@ -13,7 +13,7 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print)\n {\n \tunsigned char sha1[20];\n \tint flag;\n-\tconst char *refname = resolve_ref_unsafe(HEAD, sha1, &flag, 0);\n+\tconst char *refname = resolve_ref_unsafe(HEAD, 0, sha1, &flag);\n \n \tif (!refname)\n \t\tdie(\"No such ref: %s\", HEAD);\ndiff --git c/builtin/update-ref.c w/builtin/update-ref.c\nindex e379fdd..6c9be05 100644\n--- c/builtin/update-ref.c\n+++ w/builtin/update-ref.c\n@@ -419,8 +419,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \tif (no_deref)\n \t\tflags = REF_NODEREF;\n \tif (delete)\n-\t\treturn delete_ref(refname, oldval ? oldsha1 : NULL,\n-\t\t\t\t  flags|REF_BADNAMEOK);\n+\t\treturn delete_ref(refname, oldval ? oldsha1 : NULL, flags);\n \telse\n \t\treturn update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,\n \t\t\t\t  flags, UPDATE_REFS_DIE_ON_ERR);\ndiff --git c/bundle.c w/bundle.c\nindex 32dd2f7..d92e49c 100644\n--- c/bundle.c\n+++ w/bundle.c\n@@ -311,7 +311,7 @@ int create_bundle(struct bundle_header *header, const char *path,\n \t\t\tcontinue;\n \t\tif (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)\n \t\t\tcontinue;\n-\t\tif (read_ref_full(e->name, sha1, &flag, RESOLVE_REF_READING))\n+\t\tif (read_ref_full(e->name, RESOLVE_REF_READING, sha1, &flag))\n \t\t\tflag = 0;\n \t\tdisplay_ref = (flag & REF_ISSYMREF) ? e->name : ref;\n \ndiff --git c/cache.h w/cache.h\nindex 995729f..f582b6c 100644\n--- c/cache.h\n+++ w/cache.h\n@@ -946,8 +946,8 @@ extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *);\n extern int get_sha1_hex(const char *hex, unsigned char *sha1);\n \n extern char *sha1_to_hex(const unsigned char *sha1);\t/* static buffer result! */\n-extern int read_ref_full(const char *refname, unsigned char *sha1,\n-\t\t\t int *flags, int resolve_flags);\n+extern int read_ref_full(const char *refname, int resolve_flags,\n+\t\t\t unsigned char *sha1, int *flags);\n extern int read_ref(const char *refname, unsigned char *sha1);\n \n /*\n@@ -969,27 +969,36 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n  *   \"writing\" to the ref, the return value is the name of the ref\n  *   that will actually be created or changed.\n  *\n+ * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one\n+ * level of symbolic reference.  The value stored in sha1 for a symbolic\n+ * reference will always be null_sha1 in this case, and the return\n+ * value is the reference that the symref refers to directly.\n+ *\n  * If flags is non-NULL, set the value that it points to the\n  * combination of REF_ISPACKED (if the reference was found among the\n  * packed references), REF_ISSYMREF (if the initial reference was a\n- * symbolic reference) and REF_ISBROKEN (if the ref is malformed).\n+ * symbolic reference), REF_BAD_NAME (if the reference name is ill\n+ * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN\n+ * (if the ref is malformed).\n  *\n  * If ref is not a properly-formatted, normalized reference, return\n  * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,\n  * give up and return NULL.\n  *\n- * RESOLVE_REF_ALLOW_BAD_NAME disables most of the ref name checking except\n- * for names that are absolute paths or contain \"..\" components. For both\n- * these cases the function will return NULL and set errno to EINVAL.\n- * If the name is bad then the function will set the REF_ISBROKEN flag and\n- * return the name, if the ref exists, or NULL, if it does not.\n- * When this flag is set, any badly named refs will resolve to nullsha1.\n+ * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their\n+ * name is invalid according to git-check-ref-format(1).  If the name\n+ * is bad then the value stored in sha1 will be null_sha1 and the\n+ * REF_ISBROKEN and REF_BAD_NAME flags will be set.\n+ *\n+ * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/\n+ * directory and do not consist of all caps and underscores cannot be\n+ * resolved.  The function returns NULL for such ref names.\n  */\n #define RESOLVE_REF_READING 0x01\n-#define RESOLVE_REF_NODEREF 0x02\n+#define RESOLVE_REF_NO_RECURSE 0x02\n #define RESOLVE_REF_ALLOW_BAD_NAME 0x04\n-extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int *flags, int resolve_flags);\n-extern char *resolve_refdup(const char *ref, unsigned char *sha1, int *flags, int resolve_flags);\n+extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);\n+extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);\n \n extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);\n extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);\ndiff --git c/http-backend.c w/http-backend.c\nindex 8f94f9b..e172886 100644\n--- c/http-backend.c\n+++ w/http-backend.c\n@@ -417,8 +417,9 @@ static int show_head_ref(const char *refname, const unsigned char *sha1,\n \n \tif (flag & REF_ISSYMREF) {\n \t\tunsigned char unused[20];\n-\t\tconst char *target = resolve_ref_unsafe(refname, unused,\n-\t\t\t\t\t\tNULL, RESOLVE_REF_READING);\n+\t\tconst char *target = resolve_ref_unsafe(refname,\n+\t\t\t\t\t\t\tRESOLVE_REF_READING,\n+\t\t\t\t\t\t\tunused, NULL);\n \t\tconst char *target_nons = strip_namespace(target);\n \n \t\tstrbuf_addf(buf, \"ref: %s\\n\", target_nons);\ndiff --git c/notes-merge.c w/notes-merge.c\nindex ffca602..3c88d17 100644\n--- c/notes-merge.c\n+++ w/notes-merge.c\n@@ -549,7 +549,7 @@ int notes_merge(struct notes_merge_options *o,\n \t       o->local_ref, o->remote_ref);\n \n \t/* Dereference o->local_ref into local_sha1 */\n-\tif (read_ref_full(o->local_ref, local_sha1, NULL, 0))\n+\tif (read_ref_full(o->local_ref, 0, local_sha1, NULL))\n \t\tdie(\"Failed to resolve local notes ref '%s'\", o->local_ref);\n \telse if (!check_refname_format(o->local_ref, 0) &&\n \t\tis_null_sha1(local_sha1))\ndiff --git c/reflog-walk.c w/reflog-walk.c\nindex feeaf0a..23345ea 100644\n--- c/reflog-walk.c\n+++ w/reflog-walk.c\n@@ -48,8 +48,8 @@ static struct complete_reflogs *read_complete_reflog(const char *ref)\n \t\tunsigned char sha1[20];\n \t\tconst char *name;\n \t\tvoid *name_to_free;\n-\t\tname = name_to_free = resolve_refdup(ref, sha1, NULL,\n-\t\t\t\t\t\t     RESOLVE_REF_READING);\n+\t\tname = name_to_free = resolve_refdup(ref, RESOLVE_REF_READING,\n+\t\t\t\t\t\t     sha1, NULL);\n \t\tif (name) {\n \t\t\tfor_each_reflog_ent(name, read_one_reflog, reflogs);\n \t\t\tfree(name_to_free);\n@@ -175,7 +175,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info,\n \t\tif (*branch == '\\0') {\n \t\t\tunsigned char sha1[20];\n \t\t\tfree(branch);\n-\t\t\tbranch = resolve_refdup(\"HEAD\", sha1, NULL, 0);\n+\t\t\tbranch = resolve_refdup(\"HEAD\", 0, sha1, NULL);\n \t\t\tif (!branch)\n \t\t\t\tdie (\"No current branch\");\n \ndiff --git c/refs.c w/refs.c\nindex 3b27758..d9d327d 100644\n--- c/refs.c\n+++ w/refs.c\n@@ -69,16 +69,8 @@ static int check_refname_component(const char *refname, int flags)\n out:\n \tif (cp == refname)\n \t\treturn 0; /* Component has zero length. */\n-\tif (refname[0] == '.') {\n-\t\tif (!(flags & REFNAME_DOT_COMPONENT))\n-\t\t\treturn -1; /* Component starts with '.'. */\n-\t\t/*\n-\t\t * Even if leading dots are allowed, don't allow \".\"\n-\t\t * as a component (\"..\" is prevented by a rule above).\n-\t\t */\n-\t\tif (refname[1] == '\\0')\n-\t\t\treturn -1; /* Component equals \".\". */\n-\t}\n+\tif (refname[0] == '.')\n+\t\treturn -1; /* Component starts with '.'. */\n \tif (cp - refname >= 5 && !memcmp(cp - 5, \".lock\", 5))\n \t\treturn -1; /* Refname ends with \".lock\". */\n \treturn cp - refname;\n@@ -193,8 +185,8 @@ struct ref_dir {\n \n /*\n  * Bit values for ref_entry::flag.  REF_ISSYMREF=0x01,\n- * REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see\n- * refs.h.\n+ * REF_ISPACKED=0x02, REF_ISBROKEN=0x04 and REF_BAD_NAME=0x08 are\n+ * public values; see refs.h.\n  */\n \n /*\n@@ -202,16 +194,16 @@ struct ref_dir {\n  * the correct peeled value for the reference, which might be\n  * null_sha1 if the reference is not a tag or if it is broken.\n  */\n-#define REF_KNOWS_PEELED 0x08\n+#define REF_KNOWS_PEELED 0x10\n \n /* ref_entry represents a directory of references */\n-#define REF_DIR 0x10\n+#define REF_DIR 0x20\n \n /*\n  * Entry has not yet been read from disk (used only for REF_DIR\n  * entries representing loose references)\n  */\n-#define REF_INCOMPLETE 0x20\n+#define REF_INCOMPLETE 0x40\n \n /*\n  * A ref_entry represents either a reference or a \"subdirectory\" of\n@@ -280,6 +272,37 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)\n \treturn dir;\n }\n \n+static int escapes_cwd(const char *path) {\n+\tchar *buf;\n+\tint result;\n+\n+\tif (is_absolute_path(path))\n+\t\treturn 1;\n+\tbuf = xmalloc(strlen(path) + 1);\n+\tresult = !!normalize_path_copy(buf, path);\n+\tfree(buf);\n+\treturn result;\n+}\n+\n+/*\n+ * Check if a refname is safe.\n+ * For refs that start with \"refs/\" we consider it safe as long as the rest\n+ * of the path components does not allow it to escape from this directory.\n+ * For all other refs we only consider them safe iff they only contain\n+ * upper case characters and '_'.\n+ */\n+static int refname_is_safe(const char *refname)\n+{\n+\tif (starts_with(refname, \"refs/\"))\n+\t\treturn !escapes_cwd(refname + strlen(\"refs/\"));\n+\twhile (*refname) {\n+\t\tif (!isupper(*refname) && *refname != '_')\n+\t\t\treturn 0;\n+\t\trefname++;\n+\t}\n+\treturn 1;\n+}\n+\n static struct ref_entry *create_ref_entry(const char *refname,\n \t\t\t\t\t  const unsigned char *sha1, int flag,\n \t\t\t\t\t  int check_name)\n@@ -288,8 +311,10 @@ static struct ref_entry *create_ref_entry(const char *refname,\n \tstruct ref_entry *ref;\n \n \tif (check_name &&\n-\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))\n+\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))\n \t\tdie(\"Reference has invalid format: '%s'\", refname);\n+\tif (!check_name && !refname_is_safe(refname))\n+\t\tdie(\"Reference has invalid name: '%s'\", refname);\n \tlen = strlen(refname) + 1;\n \tref = xmalloc(sizeof(struct ref_entry) + len);\n \thashcpy(ref->u.value.sha1, sha1);\n@@ -822,10 +847,9 @@ static int name_conflict_fn(struct ref_entry *entry, void *cb_data)\n /*\n  * Return true iff a reference named refname could be created without\n  * conflicting with the name of an existing reference in dir.  If\n- * oldrefname is non-NULL, ignore potential conflicts with oldrefname\n- * (e.g., because oldrefname is scheduled for deletion in the same\n- * operation). skiplist contains a list of refs we want to skip checking for\n- * conflicts with. skiplist must be sorted.\n+ * skiplist is non-NULL, ignore potential conflicts with names in\n+ * skiplist (e.g., because those refs are scheduled for deletion in\n+ * the same operation).  skiplist must be sorted.\n  */\n static int is_refname_available(const char *refname,\n \t\t\t\tstruct ref_dir *dir,\n@@ -1062,9 +1086,9 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)\n \t\tif (refname) {\n \t\t\tint flag = REF_ISPACKED;\n \n-\t\t\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT)) {\n-\t\t\t\tflag |= REF_ISBROKEN;\n+\t\t\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n \t\t\t\thashclr(sha1);\n+\t\t\t\tflag |= REF_BAD_NAME | REF_ISBROKEN;\n \t\t\t}\n \t\t\tlast = create_ref_entry(refname, sha1, flag, 0);\n \t\t\tif (peeled == PEELED_FULLY ||\n@@ -1198,16 +1222,16 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)\n \t\t\t\t\thashclr(sha1);\n \t\t\t\t\tflag |= REF_ISBROKEN;\n \t\t\t\t}\n-\t\t\t} else if (read_ref_full(refname.buf, sha1, &flag,\n-\t\t\t\t\t\t RESOLVE_REF_READING)) {\n+\t\t\t} else if (read_ref_full(refname.buf,\n+\t\t\t\t\t\t RESOLVE_REF_READING,\n+\t\t\t\t\t\t sha1, &flag)) {\n \t\t\t\thashclr(sha1);\n \t\t\t\tflag |= REF_ISBROKEN;\n \t\t\t}\n \t\t\tif (check_refname_format(refname.buf,\n-\t\t\t\t\t\t REFNAME_ALLOW_ONELEVEL|\n-\t\t\t\t\t\t REFNAME_DOT_COMPONENT)) {\n+\t\t\t\t\t\t REFNAME_ALLOW_ONELEVEL)) {\n \t\t\t\thashclr(sha1);\n-\t\t\t\tflag |= REF_ISBROKEN;\n+\t\t\t\tflag |= REF_BAD_NAME | REF_ISBROKEN;\n \t\t\t}\n \t\t\tadd_entry_to_dir(dir,\n \t\t\t\t\t create_ref_entry(refname.buf, sha1, flag, 0));\n@@ -1329,10 +1353,10 @@ static struct ref_entry *get_packed_ref(const char *refname)\n  * A loose ref file doesn't exist; check for a packed ref.  The\n  * options are forwarded from resolve_safe_unsafe().\n  */\n-static const char *handle_missing_loose_ref(const char *refname,\n-\t\t\t\t\t    unsigned char *sha1,\n-\t\t\t\t\t    int reading,\n-\t\t\t\t\t    int *flag)\n+static int resolve_missing_loose_ref(const char *refname,\n+\t\t\t\t     int resolve_flags,\n+\t\t\t\t     unsigned char *sha1,\n+\t\t\t\t     int *flags)\n {\n \tstruct ref_entry *entry;\n \n@@ -1343,33 +1367,22 @@ static const char *handle_missing_loose_ref(const char *refname,\n \tentry = get_packed_ref(refname);\n \tif (entry) {\n \t\thashcpy(sha1, entry->u.value.sha1);\n-\t\tif (flag)\n-\t\t\t*flag |= REF_ISPACKED;\n-\t\treturn refname;\n+\t\tif (flags)\n+\t\t\t*flags |= REF_ISPACKED;\n+\t\treturn 0;\n \t}\n \t/* The reference is not a packed reference, either. */\n-\tif (reading) {\n-\t\treturn NULL;\n+\tif (resolve_flags & RESOLVE_REF_READING) {\n+\t\terrno = ENOENT;\n+\t\treturn -1;\n \t} else {\n \t\thashclr(sha1);\n-\t\treturn refname;\n+\t\treturn 0;\n \t}\n }\n \n-static int escapes_cwd(const char *path) {\n-\tchar *buf;\n-\tint result;\n-\n-\tif (is_absolute_path(path))\n-\t\treturn 1;\n-\tbuf = xmalloc(strlen(path) + 1);\n-\tresult = normalize_path_copy(buf, path);\n-\tfree(buf);\n-\treturn result;\n-}\n-\n /* This function needs to return a meaningful errno on failure */\n-const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *flags, int resolve_flags)\n+const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)\n {\n \tint depth = MAXDEPTH;\n \tssize_t len;\n@@ -1381,14 +1394,22 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *fl\n \t\t*flags = 0;\n \n \tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\tif (flags)\n+\t\t\t*flags |= REF_BAD_NAME;\n+\n \t\tif (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||\n-\t\t    escapes_cwd(refname)) {\n+\t\t    !refname_is_safe(refname)) {\n \t\t\terrno = EINVAL;\n \t\t\treturn NULL;\n \t\t}\n-\t\thashclr(sha1);\n-\t\tif (flags)\n-\t\t\t*flags |= REF_ISBROKEN;\n+\t\t/*\n+\t\t * dwim_ref() uses REF_ISBROKEN to distinguish between\n+\t\t * missing refs and refs that were present but invalid,\n+\t\t * to complain about the latter to stderr.\n+\t\t *\n+\t\t * We don't know whether the ref exists, so don't set\n+\t\t * REF_ISBROKEN yet.\n+\t\t */\n \t\tbad_name = 1;\n \t}\n \tfor (;;) {\n@@ -1415,12 +1436,17 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *fl\n \t\t */\n \tstat_ref:\n \t\tif (lstat(path, &st) < 0) {\n-\t\t\tif (errno == ENOENT)\n-\t\t\t\treturn handle_missing_loose_ref(refname, sha1,\n-\t\t\t\t\tresolve_flags & RESOLVE_REF_READING,\n-\t\t\t\t\tflags);\n-\t\t\telse\n+\t\t\tif (errno != ENOENT)\n \t\t\t\treturn NULL;\n+\t\t\tif (resolve_missing_loose_ref(refname, resolve_flags,\n+\t\t\t\t\t\t      sha1, flags))\n+\t\t\t\treturn NULL;\n+\t\t\tif (bad_name) {\n+\t\t\t\thashclr(sha1);\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISBROKEN;\n+\t\t\t}\n+\t\t\treturn refname;\n \t\t}\n \n \t\t/* Follow \"normalized\" - ie \"refs/..\" symlinks by hand */\n@@ -1440,7 +1466,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *fl\n \t\t\t\trefname = refname_buffer;\n \t\t\t\tif (flags)\n \t\t\t\t\t*flags |= REF_ISSYMREF;\n-\t\t\t\tif (resolve_flags & RESOLVE_REF_NODEREF) {\n+\t\t\t\tif (resolve_flags & RESOLVE_REF_NO_RECURSE) {\n \t\t\t\t\thashclr(sha1);\n \t\t\t\t\treturn refname;\n \t\t\t\t}\n@@ -1493,8 +1519,11 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *fl\n \t\t\t\terrno = EINVAL;\n \t\t\t\treturn NULL;\n \t\t\t}\n-\t\t\tif (bad_name)\n+\t\t\tif (bad_name) {\n \t\t\t\thashclr(sha1);\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISBROKEN;\n+\t\t\t}\n \t\t\treturn refname;\n \t\t}\n \t\tif (flags)\n@@ -1502,23 +1531,28 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int *fl\n \t\tbuf = buffer + 4;\n \t\twhile (isspace(*buf))\n \t\t\tbuf++;\n-\t\tif (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {\n-\t\t\tif (flags)\n-\t\t\t\t*flags |= REF_ISBROKEN;\n-\t\t\terrno = EINVAL;\n-\t\t\treturn NULL;\n-\t\t}\n \t\trefname = strcpy(refname_buffer, buf);\n-\t\tif (resolve_flags & RESOLVE_REF_NODEREF) {\n+\t\tif (resolve_flags & RESOLVE_REF_NO_RECURSE) {\n \t\t\thashclr(sha1);\n \t\t\treturn refname;\n \t\t}\n+\t\tif (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {\n+\t\t\tif (flags)\n+\t\t\t\t*flags |= REF_BAD_NAME | REF_ISBROKEN;\n+\n+\t\t\tif (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||\n+\t\t\t    !refname_is_safe(buf)) {\n+\t\t\t\terrno = EINVAL;\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t\tbad_name = 1;\n+\t\t}\n \t}\n }\n \n-char *resolve_refdup(const char *ref, unsigned char *sha1, int *flags, int resolve_flags)\n+char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags)\n {\n-\tconst char *ret = resolve_ref_unsafe(ref, sha1, flags, resolve_flags);\n+\tconst char *ret = resolve_ref_unsafe(ref, resolve_flags, sha1, flags);\n \treturn ret ? xstrdup(ret) : NULL;\n }\n \n@@ -1529,22 +1563,22 @@ struct ref_filter {\n \tvoid *cb_data;\n };\n \n-int read_ref_full(const char *refname, unsigned char *sha1, int *flags, int resolve_flags)\n+int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)\n {\n-\tif (resolve_ref_unsafe(refname, sha1, flags, resolve_flags))\n+\tif (resolve_ref_unsafe(refname, resolve_flags, sha1, flags))\n \t\treturn 0;\n \treturn -1;\n }\n \n int read_ref(const char *refname, unsigned char *sha1)\n {\n-\treturn read_ref_full(refname, sha1, NULL, RESOLVE_REF_READING);\n+\treturn read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL);\n }\n \n int ref_exists(const char *refname)\n {\n \tunsigned char sha1[20];\n-\treturn !!resolve_ref_unsafe(refname, sha1, NULL, RESOLVE_REF_READING);\n+\treturn !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);\n }\n \n static int filter_refs(const char *refname, const unsigned char *sha1, int flags,\n@@ -1658,7 +1692,7 @@ int peel_ref(const char *refname, unsigned char *sha1)\n \t\treturn 0;\n \t}\n \n-\tif (read_ref_full(refname, base, &flag, RESOLVE_REF_READING))\n+\tif (read_ref_full(refname, RESOLVE_REF_READING, base, &flag))\n \t\treturn -1;\n \n \t/*\n@@ -1699,7 +1733,7 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha\n \tif (!(flags & REF_ISSYMREF))\n \t\treturn 0;\n \n-\tresolves_to = resolve_ref_unsafe(refname, junk, NULL, 0);\n+\tresolves_to = resolve_ref_unsafe(refname, 0, junk, NULL);\n \tif (!resolves_to\n \t    || (d->refname\n \t\t? strcmp(resolves_to, d->refname)\n@@ -1824,7 +1858,7 @@ static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)\n \t\treturn 0;\n \t}\n \n-\tif (!read_ref_full(\"HEAD\", sha1, &flag, RESOLVE_REF_READING))\n+\tif (!read_ref_full(\"HEAD\", RESOLVE_REF_READING, sha1, &flag))\n \t\treturn fn(\"HEAD\", sha1, flag, cb_data);\n \n \treturn 0;\n@@ -1904,7 +1938,7 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)\n \tint flag;\n \n \tstrbuf_addf(&buf, \"%sHEAD\", get_git_namespace());\n-\tif (!read_ref_full(buf.buf, sha1, &flag, RESOLVE_REF_READING))\n+\tif (!read_ref_full(buf.buf, RESOLVE_REF_READING, sha1, &flag))\n \t\tret = fn(buf.buf, sha1, flag, cb_data);\n \tstrbuf_release(&buf);\n \n@@ -1999,8 +2033,9 @@ int refname_match(const char *abbrev_name, const char *full_name)\n static struct ref_lock *verify_lock(struct ref_lock *lock,\n \tconst unsigned char *old_sha1, int mustexist)\n {\n-\tif (read_ref_full(lock->ref_name, lock->old_sha1, NULL,\n-\t\t\t  mustexist ? RESOLVE_REF_READING : 0)) {\n+\tif (read_ref_full(lock->ref_name,\n+\t\t\t  mustexist ? RESOLVE_REF_READING : 0,\n+\t\t\t  lock->old_sha1, NULL)) {\n \t\tint save_errno = errno;\n \t\terror(\"Can't verify ref %s\", lock->ref_name);\n \t\tunlock_ref(lock);\n@@ -2073,8 +2108,8 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)\n \n \t\tthis_result = refs_found ? sha1_from_ref : sha1;\n \t\tmksnpath(fullref, sizeof(fullref), *p, len, str);\n-\t\tr = resolve_ref_unsafe(fullref, this_result, &flag,\n-\t\t\t\t       RESOLVE_REF_READING);\n+\t\tr = resolve_ref_unsafe(fullref, RESOLVE_REF_READING,\n+\t\t\t\t       this_result, &flag);\n \t\tif (r) {\n \t\t\tif (!refs_found++)\n \t\t\t\t*ref = xstrdup(r);\n@@ -2103,7 +2138,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n \t\tconst char *ref, *it;\n \n \t\tmksnpath(path, sizeof(path), *p, len, str);\n-\t\tref = resolve_ref_unsafe(path, hash, NULL, RESOLVE_REF_READING);\n+\t\tref = resolve_ref_unsafe(path, RESOLVE_REF_READING,\n+\t\t\t\t\t hash, NULL);\n \t\tif (!ref)\n \t\t\tcontinue;\n \t\tif (reflog_exists(path))\n@@ -2145,16 +2181,16 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \tlock = xcalloc(1, sizeof(struct ref_lock));\n \tlock->lock_fd = -1;\n \n-\tif (flags & REF_BADNAMEOK)\n-\t\tresolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;\n-\n \tif (mustexist)\n \t\tresolve_flags |= RESOLVE_REF_READING;\n-\tif (flags & REF_NODEREF)\n-\t\tresolve_flags |= RESOLVE_REF_NODEREF;\n+\tif (flags & REF_DELETING) {\n+\t\tresolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;\n+\t\tif (flags & REF_NODEREF)\n+\t\t\tresolve_flags |= RESOLVE_REF_NO_RECURSE;\n+\t}\n \n-\trefname = resolve_ref_unsafe(refname, lock->old_sha1, &type,\n-\t\t\t\t     resolve_flags);\n+\trefname = resolve_ref_unsafe(refname, resolve_flags,\n+\t\t\t\t     lock->old_sha1, &type);\n \tif (!refname && errno == EISDIR) {\n \t\t/* we are trying to lock foo but we used to\n \t\t * have foo/bar which now does not exist;\n@@ -2167,8 +2203,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\terror(\"there are still refs under '%s'\", orig_refname);\n \t\t\tgoto error_return;\n \t\t}\n-\t\trefname = resolve_ref_unsafe(orig_refname, lock->old_sha1,\n-\t\t\t\t\t     &type, resolve_flags);\n+\t\trefname = resolve_ref_unsafe(orig_refname, resolve_flags,\n+\t\t\t\t\t     lock->old_sha1, &type);\n \t}\n \tif (type_p)\n \t    *type_p = type;\n@@ -2521,7 +2557,7 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)\n \t\tunsigned char sha1[20];\n \t\tint flags;\n \n-\t\tif (read_ref_full(entry->name, sha1, &flags, 0))\n+\t\tif (read_ref_full(entry->name, 0, sha1, &flags))\n \t\t\t/* We should at least have found the packed ref. */\n \t\t\tdie(\"Internal error\");\n \t\tif ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED)) {\n@@ -2712,8 +2748,8 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \tif (log && S_ISLNK(loginfo.st_mode))\n \t\treturn error(\"reflog for %s is a symlink\", oldrefname);\n \n-\tsymref = resolve_ref_unsafe(oldrefname, orig_sha1, &flag,\n-\t\t\t\t    RESOLVE_REF_READING);\n+\tsymref = resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING,\n+\t\t\t\t    orig_sha1, &flag);\n \tif (flag & REF_ISSYMREF)\n \t\treturn error(\"refname %s is a symbolic ref, renaming it is not supported\",\n \t\t\toldrefname);\n@@ -2742,7 +2778,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \t\tgoto rollback;\n \t}\n \n-\tif (!read_ref_full(newrefname, sha1, NULL, RESOLVE_REF_READING) &&\n+\tif (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) &&\n \t    delete_ref(newrefname, sha1, REF_NODEREF)) {\n \t\tif (errno==EISDIR) {\n \t\t\tif (remove_empty_directories(git_path(\"%s\", newrefname))) {\n@@ -2956,7 +2992,7 @@ static int is_branch(const char *refname)\n }\n \n /*\n- * Writes sha1 into the ref specified by the lock. Makes sure that errno\n+ * Write sha1 into the ref specified by the lock. Make sure that errno\n  * is sane on error.\n  */\n static int write_ref_sha1(struct ref_lock *lock,\n@@ -3020,8 +3056,8 @@ static int write_ref_sha1(struct ref_lock *lock,\n \t\tunsigned char head_sha1[20];\n \t\tint head_flag;\n \t\tconst char *head_ref;\n-\t\thead_ref = resolve_ref_unsafe(\"HEAD\", head_sha1, &head_flag,\n-\t\t\t\t\t      RESOLVE_REF_READING);\n+\t\thead_ref = resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t\t      head_sha1, &head_flag);\n \t\tif (head_ref && (head_flag & REF_ISSYMREF) &&\n \t\t    !strcmp(head_ref, lock->ref_name))\n \t\t\tlog_ref_write(\"HEAD\", lock->old_sha1, sha1, logmsg);\n@@ -3388,7 +3424,7 @@ static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data\n \t\t\t\tretval = do_for_each_reflog(name, fn, cb_data);\n \t\t\t} else {\n \t\t\t\tunsigned char sha1[20];\n-\t\t\t\tif (read_ref_full(name->buf, sha1, NULL, 0))\n+\t\t\t\tif (read_ref_full(name->buf, 0, sha1, NULL))\n \t\t\t\t\tretval = error(\"bad ref for %s\", name->buf);\n \t\t\t\telse\n \t\t\t\t\tretval = fn(name->buf, sha1, 0, cb_data);\n@@ -3667,27 +3703,30 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t/* Copy, sort, and reject duplicate refs */\n \tqsort(updates, n, sizeof(*updates), ref_update_compare);\n \tif (ref_update_reject_duplicates(updates, n, err)) {\n-\t\tret = -1;\n+\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\tgoto cleanup;\n \t}\n \n \t/* Acquire all locks while verifying old values */\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n+\t\tint flags = update->flags;\n \n+\t\tif (is_null_sha1(update->new_sha1))\n+\t\t\tflags |= REF_DELETING;\n \t\tupdate->lock = lock_ref_sha1_basic(update->refname,\n \t\t\t\t\t\t   (update->have_old ?\n \t\t\t\t\t\t    update->old_sha1 :\n \t\t\t\t\t\t    NULL),\n \t\t\t\t\t\t   NULL,\n-\t\t\t\t\t\t   update->flags,\n+\t\t\t\t\t\t   flags,\n \t\t\t\t\t\t   &update->type);\n \t\tif (!update->lock) {\n-\t\t\tint df_conflict = (errno == ENOTDIR);\n-\n+\t\t\tret = (errno == ENOTDIR)\n+\t\t\t\t? TRANSACTION_NAME_CONFLICT\n+\t\t\t\t: TRANSACTION_GENERIC_ERROR;\n \t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n \t\t\t\t    update->refname);\n-\t\t\tret = df_conflict ? UPDATE_REFS_NAME_CONFLICT : -1;\n \t\t\tgoto cleanup;\n \t\t}\n \t}\n@@ -3697,15 +3736,15 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (!is_null_sha1(update->new_sha1)) {\n-\t\t\tret = write_ref_sha1(update->lock, update->new_sha1,\n-\t\t\t\t\t     update->msg);\n-\t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n-\t\t\tif (ret) {\n+\t\t\tif (write_ref_sha1(update->lock, update->new_sha1,\n+\t\t\t\t\t   update->msg)) {\n+\t\t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n \t\t\t\tstrbuf_addf(err, \"Cannot update the ref '%s'.\",\n \t\t\t\t\t    update->refname);\n-\t\t\t\tret = -1;\n+\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto cleanup;\n \t\t\t}\n+\t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n \t\t}\n \t}\n \n@@ -3715,7 +3754,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \n \t\tif (update->lock) {\n \t\t\tif (delete_ref_loose(update->lock, update->type, err)) {\n-\t\t\t\tret = -1;\n+\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \n@@ -3725,7 +3764,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t}\n \n \tif (repack_without_refs(delnames, delnum, err)) {\n-\t\tret = -1;\n+\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\tgoto cleanup;\n \t}\n \tfor (i = 0; i < delnum; i++)\ndiff --git c/refs.h w/refs.h\nindex a96e174..3b35387 100644\n--- c/refs.h\n+++ w/refs.h\n@@ -56,11 +56,15 @@ struct ref_transaction;\n \n /*\n  * Reference cannot be resolved to an object name: dangling symbolic\n- * reference (directly or indirectly), corrupt reference file, or\n- * symbolic reference refers to ill-formatted reference name.\n+ * reference (directly or indirectly), corrupt reference file,\n+ * reference exists but name is bad, or symbolic reference refers to\n+ * ill-formatted reference name.\n  */\n #define REF_ISBROKEN 0x04\n \n+/* Reference name is not well formed (see git-check-ref-format(1)). */\n+#define REF_BAD_NAME 0x08\n+\n /*\n  * The signature for the callback function for the for_each_*()\n  * functions below.  The memory pointed to by the refname and sha1\n@@ -175,12 +179,12 @@ extern int peel_ref(const char *refname, unsigned char *sha1);\n  * ref_transaction_create(), etc.\n  * REF_NODEREF: act on the ref directly, instead of dereferencing\n  *              symbolic references.\n- * REF_BADNAMEOK: allow locking a ref that has a bad name.\n+ * REF_DELETING: tolerate broken refs\n  *\n  * Flags >= 0x100 are reserved for internal use.\n  */\n #define REF_NODEREF\t0x01\n-#define REF_BADNAMEOK\t0x02\n+#define REF_DELETING\t0x02\n /*\n  * This function sets errno to something meaningful on failure.\n  */\n@@ -226,7 +230,6 @@ extern int for_each_reflog(each_ref_fn, void *);\n \n #define REFNAME_ALLOW_ONELEVEL 1\n #define REFNAME_REFSPEC_PATTERN 2\n-#define REFNAME_DOT_COMPONENT 4\n \n /*\n  * Return 0 iff refname has the correct format for a refname according\n@@ -234,10 +237,7 @@ extern int for_each_reflog(each_ref_fn, void *);\n  * If REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level\n  * reference names.  If REFNAME_REFSPEC_PATTERN is set in flags, then\n  * allow a \"*\" wildcard character in place of one of the name\n- * components.  No leading or repeated slashes are accepted.  If\n- * REFNAME_DOT_COMPONENT is set in flags, then allow refname\n- * components to start with \".\" (but not a whole component equal to\n- * \".\" or \"..\").\n+ * components.  No leading or repeated slashes are accepted.\n  */\n extern int check_refname_format(const char *refname, int flags);\n \n@@ -270,8 +270,8 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  * The following functions add a reference check or update to a\n  * ref_transaction.  In all of them, refname is the name of the\n  * reference to be affected.  The functions make internal copies of\n- * refname, so the caller retains ownership of the parameter.  flags\n- * can be REF_NODEREF; it is passed to update_ref_lock().\n+ * refname and msg, so the caller retains ownership of these parameters.\n+ * flags can be REF_NODEREF; it is passed to update_ref_lock().\n  */\n \n /*\n@@ -322,14 +322,14 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n \n /*\n  * Commit all of the changes that have been queued in transaction, as\n- * atomically as possible.  Return a nonzero value if there is a\n- * problem.\n+ * atomically as possible.\n  *\n- * Function returns 0 on success, -1 for generic failures and\n- * UPDATE_REFS_NAME_CONFLICT (-2) if the failure was due to a naming conflict.\n- * For example, the ref names A and A/B conflict.\n+ * Returns 0 for success, or one of the below error codes for errors.\n  */\n-#define UPDATE_REFS_NAME_CONFLICT -2\n+/* Naming conflict (for example, the ref names A and A/B conflict). */\n+#define TRANSACTION_NAME_CONFLICT -1\n+/* All other errors. */\n+#define TRANSACTION_GENERIC_ERROR -2\n int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t   struct strbuf *err);\n \ndiff --git c/remote.c w/remote.c\nindex 67c375d..25b07ac 100644\n--- c/remote.c\n+++ w/remote.c\n@@ -486,7 +486,7 @@ static void read_config(void)\n \t\treturn;\n \tdefault_remote_name = \"origin\";\n \tcurrent_branch = NULL;\n-\thead_ref = resolve_ref_unsafe(\"HEAD\", sha1, &flag, 0);\n+\thead_ref = resolve_ref_unsafe(\"HEAD\", 0, sha1, &flag);\n \tif (head_ref && (flag & REF_ISSYMREF) &&\n \t    starts_with(head_ref, \"refs/heads/\")) {\n \t\tcurrent_branch =\n@@ -1121,8 +1121,8 @@ static char *guess_ref(const char *name, struct ref *peer)\n \tstruct strbuf buf = STRBUF_INIT;\n \tunsigned char sha1[20];\n \n-\tconst char *r = resolve_ref_unsafe(peer->name, sha1, NULL,\n-\t\t\t\t\t   RESOLVE_REF_READING);\n+\tconst char *r = resolve_ref_unsafe(peer->name, RESOLVE_REF_READING,\n+\t\t\t\t\t   sha1, NULL);\n \tif (!r)\n \t\treturn NULL;\n \n@@ -1183,8 +1183,9 @@ static int match_explicit(struct ref *src, struct ref *dst,\n \t\tunsigned char sha1[20];\n \t\tint flag;\n \n-\t\tdst_value = resolve_ref_unsafe(matched_src->name, sha1, &flag,\n-\t\t\t\t\t       RESOLVE_REF_READING);\n+\t\tdst_value = resolve_ref_unsafe(matched_src->name,\n+\t\t\t\t\t       RESOLVE_REF_READING,\n+\t\t\t\t\t       sha1, &flag);\n \t\tif (!dst_value ||\n \t\t    ((flag & REF_ISSYMREF) &&\n \t\t     !starts_with(dst_value, \"refs/heads/\")))\n@@ -1658,7 +1659,7 @@ static int ignore_symref_update(const char *refname)\n \tunsigned char sha1[20];\n \tint flag;\n \n-\tif (!resolve_ref_unsafe(refname, sha1, &flag, 0))\n+\tif (!resolve_ref_unsafe(refname, 0, sha1, &flag))\n \t\treturn 0; /* non-existing refs are OK */\n \treturn (flag & REF_ISSYMREF);\n }\ndiff --git c/sequencer.c w/sequencer.c\nindex 6a05ad4..70fb7a8 100644\n--- c/sequencer.c\n+++ w/sequencer.c\n@@ -366,7 +366,7 @@ static int is_index_unchanged(void)\n \tunsigned char head_sha1[20];\n \tstruct commit *head_commit;\n \n-\tif (!resolve_ref_unsafe(\"HEAD\", head_sha1, NULL, RESOLVE_REF_READING))\n+\tif (!resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING, head_sha1, NULL))\n \t\treturn error(_(\"Could not resolve HEAD commit\\n\"));\n \n \thead_commit = lookup_commit(head_sha1);\n@@ -912,7 +912,7 @@ static int rollback_single_pick(void)\n \tif (!file_exists(git_path(\"CHERRY_PICK_HEAD\")) &&\n \t    !file_exists(git_path(\"REVERT_HEAD\")))\n \t\treturn error(_(\"no cherry-pick or revert in progress\"));\n-\tif (read_ref_full(\"HEAD\", head_sha1, NULL, 0))\n+\tif (read_ref_full(\"HEAD\", 0, head_sha1, NULL))\n \t\treturn error(_(\"cannot resolve HEAD\"));\n \tif (is_null_sha1(head_sha1))\n \t\treturn error(_(\"cannot abort from a branch yet to be born\"));\ndiff --git c/t/t1400-update-ref.sh w/t/t1400-update-ref.sh\nindex ff4607b..7b4707b 100755\n--- c/t/t1400-update-ref.sh\n+++ w/t/t1400-update-ref.sh\n@@ -126,6 +126,16 @@ test_expect_success 'update-ref --no-deref -d can delete self-reference' '\n \ttest_path_is_missing .git/refs/heads/self\n '\n \n+test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' '\n+\t>.git/refs/heads/bad &&\n+\ttest_when_finished \"rm -f .git/refs/heads/bad\" &&\n+\tgit symbolic-ref refs/heads/ref-to-bad refs/heads/bad &&\n+\ttest_when_finished \"rm -f .git/refs/heads/ref-to-bad\" &&\n+\ttest_path_is_file .git/refs/heads/ref-to-bad &&\n+\tgit update-ref --no-deref -d refs/heads/ref-to-bad &&\n+\ttest_path_is_missing .git/refs/heads/ref-to-bad\n+'\n+\n test_expect_success '(not) create HEAD with old sha1' \"\n \ttest_must_fail git update-ref HEAD $A $B\n \"\n@@ -390,12 +400,6 @@ test_expect_success 'stdin fails create with no ref' '\n \tgrep \"fatal: create: missing <ref>\" err\n '\n \n-test_expect_success 'stdin fails create with bad ref name' '\n-\techo \"create ~a $m\" >stdin &&\n-\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n-\tgrep \"fatal: invalid ref format: ~a\" err\n-'\n-\n test_expect_success 'stdin fails create with no new value' '\n \techo \"create $a\" >stdin &&\n \ttest_must_fail git update-ref --stdin <stdin 2>err &&\n@@ -414,12 +418,6 @@ test_expect_success 'stdin fails update with no ref' '\n \tgrep \"fatal: update: missing <ref>\" err\n '\n \n-test_expect_success 'stdin fails update with bad ref name' '\n-\techo \"update ~a $m\" >stdin &&\n-\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n-\tgrep \"fatal: invalid ref format: ~a\" err\n-'\n-\n test_expect_success 'stdin fails update with no new value' '\n \techo \"update $a\" >stdin &&\n \ttest_must_fail git update-ref --stdin <stdin 2>err &&\n@@ -438,12 +436,6 @@ test_expect_success 'stdin fails delete with no ref' '\n \tgrep \"fatal: delete: missing <ref>\" err\n '\n \n-test_expect_success 'stdin fails delete with bad ref name' '\n-\techo \"delete ~a $m\" >stdin &&\n-\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n-\tgrep \"fatal: invalid ref format: ~a\" err\n-'\n-\n test_expect_success 'stdin fails delete with too many arguments' '\n \techo \"delete $a $m $m\" >stdin &&\n \ttest_must_fail git update-ref --stdin <stdin 2>err &&\n@@ -716,12 +708,6 @@ test_expect_success 'stdin -z fails create with no ref' '\n \tgrep \"fatal: create: missing <ref>\" err\n '\n \n-test_expect_success 'stdin -z fails create with bad ref name' '\n-\tprintf $F \"create ~a \" \"$m\" >stdin &&\n-\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n-\tgrep \"fatal: invalid ref format: ~a \" err\n-'\n-\n test_expect_success 'stdin -z fails create with no new value' '\n \tprintf $F \"create $a\" >stdin &&\n \ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n@@ -746,12 +732,6 @@ test_expect_success 'stdin -z fails update with too few args' '\n \tgrep \"fatal: update $a: unexpected end of input when reading <oldvalue>\" err\n '\n \n-test_expect_success 'stdin -z fails update with bad ref name' '\n-\tprintf $F \"update ~a\" \"$m\" \"\" >stdin &&\n-\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n-\tgrep \"fatal: invalid ref format: ~a\" err\n-'\n-\n test_expect_success 'stdin -z emits warning with empty new value' '\n \tgit update-ref $a $m &&\n \tprintf $F \"update $a\" \"\" \"\" >stdin &&\n@@ -784,12 +764,6 @@ test_expect_success 'stdin -z fails delete with no ref' '\n \tgrep \"fatal: delete: missing <ref>\" err\n '\n \n-test_expect_success 'stdin -z fails delete with bad ref name' '\n-\tprintf $F \"delete ~a\" \"$m\" >stdin &&\n-\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n-\tgrep \"fatal: invalid ref format: ~a\" err\n-'\n-\n test_expect_success 'stdin -z fails delete with no old value' '\n \tprintf $F \"delete $a\" >stdin &&\n \ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\ndiff --git c/t/t1402-check-ref-format.sh w/t/t1402-check-ref-format.sh\nindex 058fa37..1a5a5f3 100755\n--- c/t/t1402-check-ref-format.sh\n+++ w/t/t1402-check-ref-format.sh\n@@ -196,52 +196,4 @@ invalid_ref_normalized 'heads///foo.lock'\n invalid_ref_normalized 'foo.lock/bar'\n invalid_ref_normalized 'foo.lock///bar'\n \n-test_expect_success 'git branch shows badly named ref' '\n-\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n-\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n-\tgit branch >output &&\n-\tgrep -e \"broken...ref\" output\n-'\n-\n-test_expect_success 'git branch -d can delete badly named ref' '\n-\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n-\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n-\tgit branch -d broken...ref &&\n-\tgit branch >output &&\n-\t! grep -e \"broken...ref\" output\n-'\n-\n-test_expect_success 'git branch -D can delete badly named ref' '\n-\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n-\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n-\tgit branch -D broken...ref &&\n-\tgit branch >output &&\n-\t! grep -e \"broken...ref\" output\n-'\n-\n-test_expect_success 'git update-ref -d can delete badly named ref' '\n-\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n-\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n-\tgit update-ref -d refs/heads/broken...ref &&\n-\tgit branch >output &&\n-\t! grep -e \"broken...ref\" output\n-'\n-\n-test_expect_success 'git branch can not create a badly named ref' '\n-\ttest_must_fail git branch broken...ref\n-'\n-\n-test_expect_success 'git branch can not rename to a bad ref name' '\n-\tgit branch goodref &&\n-\ttest_must_fail git branch -m goodref broken...ref\n-'\n-\n-test_expect_failure 'git branch can rename from a bad ref name' '\n-\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n-\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n-\tgit branch -m broken...ref renamed &&\n-\ttest_must_fail git rev-parse broken...ref &&\n-\ttest_cmp_rev master renamed\n-'\n-\n test_done\ndiff --git c/t/t1413-reflog-detach.sh w/t/t1413-reflog-detach.sh\nnew file mode 100755\nindex 0000000..c730600\n--- /dev/null\n+++ w/t/t1413-reflog-detach.sh\n@@ -0,0 +1,70 @@\n+#!/bin/sh\n+\n+test_description='Test reflog interaction with detached HEAD'\n+. ./test-lib.sh\n+\n+reset_state () {\n+\tgit checkout master &&\n+\tcp saved_reflog .git/logs/HEAD\n+}\n+\n+test_expect_success setup '\n+\ttest_tick &&\n+\tgit commit --allow-empty -m initial &&\n+\tgit branch side &&\n+\ttest_tick &&\n+\tgit commit --allow-empty -m second &&\n+\tcat .git/logs/HEAD >saved_reflog\n+'\n+\n+test_expect_success baseline '\n+\treset_state &&\n+\tgit rev-parse master master^ >expect &&\n+\tgit log -g --format=%H >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'switch to branch' '\n+\treset_state &&\n+\tgit rev-parse side master master^ >expect &&\n+\tgit checkout side &&\n+\tgit log -g --format=%H >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'detach to other' '\n+\treset_state &&\n+\tgit rev-parse master side master master^ >expect &&\n+\tgit checkout side &&\n+\tgit checkout master^0 &&\n+\tgit log -g --format=%H >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'detach to self' '\n+\treset_state &&\n+\tgit rev-parse master master master^ >expect &&\n+\tgit checkout master^0 &&\n+\tgit log -g --format=%H >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'attach to self' '\n+\treset_state &&\n+\tgit rev-parse master master master master^ >expect &&\n+\tgit checkout master^0 &&\n+\tgit checkout master &&\n+\tgit log -g --format=%H >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'attach to other' '\n+\treset_state &&\n+\tgit rev-parse side master master master^ >expect &&\n+\tgit checkout master^0 &&\n+\tgit checkout side &&\n+\tgit log -g --format=%H >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git c/t/t1430-bad-ref-name.sh w/t/t1430-bad-ref-name.sh\nnew file mode 100755\nindex 0000000..468e856\n--- /dev/null\n+++ w/t/t1430-bad-ref-name.sh\n@@ -0,0 +1,207 @@\n+#!/bin/sh\n+\n+test_description='Test handling of ref names that check-ref-format rejects'\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\ttest_commit one &&\n+\ttest_commit two\n+'\n+\n+test_expect_success 'fast-import: fail on invalid branch name \".badbranchname\"' '\n+\ttest_when_finished \"rm -f .git/objects/pack_* .git/objects/index_*\" &&\n+\tcat >input <<-INPUT_END &&\n+\t\tcommit .badbranchname\n+\t\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\t\tdata <<COMMIT\n+\t\tcorrupt\n+\t\tCOMMIT\n+\n+\t\tfrom refs/heads/master\n+\n+\tINPUT_END\n+\ttest_must_fail git fast-import <input\n+'\n+\n+test_expect_success 'fast-import: fail on invalid branch name \"bad[branch]name\"' '\n+\ttest_when_finished \"rm -f .git/objects/pack_* .git/objects/index_*\" &&\n+\tcat >input <<-INPUT_END &&\n+\t\tcommit bad[branch]name\n+\t\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\t\tdata <<COMMIT\n+\t\tcorrupt\n+\t\tCOMMIT\n+\n+\t\tfrom refs/heads/master\n+\n+\tINPUT_END\n+\ttest_must_fail git fast-import <input\n+'\n+\n+test_expect_success 'git branch shows badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch >output &&\n+\tgrep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'branch -d can delete badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch -d broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'branch -D can delete badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch -D broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'branch -D cannot delete non-ref in .git dir' '\n+\techo precious >.git/my-private-file &&\n+\techo precious >expect &&\n+\ttest_must_fail git branch -D ../../my-private-file &&\n+\ttest_cmp expect .git/my-private-file\n+'\n+\n+test_expect_success 'branch -D cannot delete absolute path' '\n+\tgit branch -f extra &&\n+\ttest_must_fail git branch -D \"$(pwd)/.git/refs/heads/extra\" &&\n+\ttest_cmp_rev HEAD extra\n+'\n+\n+test_expect_success 'git branch cannot create a badly named ref' '\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\ttest_must_fail git branch broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'branch -m cannot rename to a bad ref name' '\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\ttest_might_fail git branch -D goodref &&\n+\tgit branch goodref &&\n+\ttest_must_fail git branch -m goodref broken...ref &&\n+\ttest_cmp_rev master goodref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_failure 'branch -m can rename from a bad ref name' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch -m broken...ref renamed &&\n+\ttest_cmp_rev master renamed &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'push cannot create a badly named ref' '\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\ttest_must_fail git push \"file://$(pwd)\" HEAD:refs/heads/broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_failure 'push --mirror can delete badly named ref' '\n+\ttop=$(pwd) &&\n+\tgit init src &&\n+\tgit init dest &&\n+\n+\t(\n+\t\tcd src &&\n+\t\ttest_commit one\n+\t) &&\n+\t(\n+\t\tcd dest &&\n+\t\ttest_commit two &&\n+\t\tgit checkout --detach &&\n+\t\tcp .git/refs/heads/master .git/refs/heads/broken...ref\n+\t) &&\n+\tgit -C src push --mirror \"file://$top/dest\" &&\n+\tgit -C dest branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'rev-parse skips symref pointing to broken name' '\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch shadow one &&\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\tgit symbolic-ref refs/tags/shadow refs/heads/broken...ref &&\n+\n+\tgit rev-parse --verify one >expect &&\n+\tgit rev-parse --verify shadow >actual 2>err &&\n+\ttest_cmp expect actual &&\n+\ttest_i18ngrep \"ignoring.*refs/tags/shadow\" err\n+'\n+\n+test_expect_success 'update-ref --no-deref -d can delete reference to broken name' '\n+\tgit symbolic-ref refs/heads/badname refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/badname\" &&\n+\ttest_path_is_file .git/refs/heads/badname &&\n+\tgit update-ref --no-deref -d refs/heads/badname &&\n+\ttest_path_is_missing .git/refs/heads/badname\n+'\n+\n+test_expect_success 'update-ref -d can delete broken name' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit update-ref -d refs/heads/broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'update-ref -d cannot delete non-ref in .git dir' '\n+\techo precious >.git/my-private-file &&\n+\techo precious >expect &&\n+\ttest_must_fail git update-ref -d my-private-file &&\n+\ttest_cmp expect .git/my-private-file\n+'\n+\n+test_expect_success 'update-ref -d cannot delete absolute path' '\n+\tgit branch -f extra &&\n+\ttest_must_fail git update-ref -d \"$(pwd)/.git/refs/heads/extra\" &&\n+\ttest_cmp_rev HEAD extra\n+'\n+\n+test_expect_success 'update-ref --stdin fails create with bad ref name' '\n+\techo \"create ~a refs/heads/master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'update-ref --stdin fails update with bad ref name' '\n+\techo \"update ~a refs/heads/master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'update-ref --stdin fails delete with bad ref name' '\n+\techo \"delete ~a refs/heads/master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'update-ref --stdin -z fails create with bad ref name' '\n+\tprintf \"%s\\0\" \"create ~a \" refs/heads/master >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a \" err\n+'\n+\n+test_expect_success 'update-ref --stdin -z fails update with bad ref name' '\n+\tprintf \"%s\\0\" \"update ~a\" refs/heads/master \"\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'update-ref --stdin -z fails delete with bad ref name' '\n+\tprintf \"%s\\0\" \"delete ~a\" refs/heads/master >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_done\ndiff --git c/t/t9300-fast-import.sh w/t/t9300-fast-import.sh\nindex 5fc9ef2..3d156f9 100755\n--- c/t/t9300-fast-import.sh\n+++ w/t/t9300-fast-import.sh\n@@ -347,36 +347,6 @@ test_expect_success 'B: fail on invalid blob sha1' '\n rm -f .git/objects/pack_* .git/objects/index_*\n \n cat >input <<INPUT_END\n-commit .badbranchname\n-committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n-data <<COMMIT\n-corrupt\n-COMMIT\n-\n-from refs/heads/master\n-\n-INPUT_END\n-test_expect_success 'B: fail on invalid branch name \".badbranchname\"' '\n-    test_must_fail git fast-import <input\n-'\n-rm -f .git/objects/pack_* .git/objects/index_*\n-\n-cat >input <<INPUT_END\n-commit bad[branch]name\n-committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n-data <<COMMIT\n-corrupt\n-COMMIT\n-\n-from refs/heads/master\n-\n-INPUT_END\n-test_expect_success 'B: fail on invalid branch name \"bad[branch]name\"' '\n-    test_must_fail git fast-import <input\n-'\n-rm -f .git/objects/pack_* .git/objects/index_*\n-\n-cat >input <<INPUT_END\n commit TEMP_TAG\n committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n data <<COMMIT\ndiff --git c/transport-helper.c w/transport-helper.c\nindex 8365441..3497a5e 100644\n--- c/transport-helper.c\n+++ w/transport-helper.c\n@@ -890,8 +890,9 @@ static int push_refs_with_export(struct transport *transport,\n \n \t\t\t\t\t/* Follow symbolic refs (mainly for HEAD). */\n \t\t\t\t\tname = resolve_ref_unsafe(\n-\t\t\t\t\t\t ref->peer_ref->name, sha1,\n-\t\t\t\t\t\t &flag, RESOLVE_REF_READING);\n+\t\t\t\t\t\t ref->peer_ref->name,\n+\t\t\t\t\t\t RESOLVE_REF_READING,\n+\t\t\t\t\t\t sha1, &flag);\n \t\t\t\t\tif (!name || !(flag & REF_ISSYMREF))\n \t\t\t\t\t\tname = ref->peer_ref->name;\n \ndiff --git c/transport.c w/transport.c\nindex 3ba7bbf..76e0a9a 100644\n--- c/transport.c\n+++ w/transport.c\n@@ -168,8 +168,8 @@ static void set_upstreams(struct transport *transport, struct ref *refs,\n \t\t/* Follow symbolic refs (mainly for HEAD). */\n \t\tlocalname = ref->peer_ref->name;\n \t\tremotename = ref->name;\n-\t\ttmp = resolve_ref_unsafe(localname, sha, &flag,\n-\t\t\t\t\t RESOLVE_REF_READING);\n+\t\ttmp = resolve_ref_unsafe(localname, RESOLVE_REF_READING,\n+\t\t\t\t\t sha, &flag);\n \t\tif (tmp && flag & REF_ISSYMREF &&\n \t\t\tstarts_with(tmp, \"refs/heads/\"))\n \t\t\tlocalname = tmp;\n@@ -754,7 +754,7 @@ void transport_print_push_status(const char *dest, struct ref *refs,\n \tunsigned char head_sha1[20];\n \tchar *head;\n \n-\thead = resolve_refdup(\"HEAD\", head_sha1, NULL, RESOLVE_REF_READING);\n+\thead = resolve_refdup(\"HEAD\", RESOLVE_REF_READING, head_sha1, NULL);\n \n \tif (verbose) {\n \t\tfor (ref = refs; ref; ref = ref->next)\ndiff --git c/upload-pack.c w/upload-pack.c\nindex 3b51ccb..4542565 100644\n--- c/upload-pack.c\n+++ w/upload-pack.c\n@@ -743,7 +743,7 @@ static int find_symref(const char *refname, const unsigned char *sha1, int flag,\n \n \tif ((flag & REF_ISSYMREF) == 0)\n \t\treturn 0;\n-\tsymref_target = resolve_ref_unsafe(refname, unused, &flag, 0);\n+\tsymref_target = resolve_ref_unsafe(refname, 0, unused, &flag);\n \tif (!symref_target || (flag & REF_ISSYMREF) == 0)\n \t\tdie(\"'%s' is a symref but it is not?\", refname);\n \titem = string_list_append(cb_data, refname);\ndiff --git c/wt-status.c w/wt-status.c\nindex 6819e2b..c3cbf50 100644\n--- c/wt-status.c\n+++ w/wt-status.c\n@@ -128,7 +128,7 @@ void wt_status_prepare(struct wt_status *s)\n \ts->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;\n \ts->use_color = -1;\n \ts->relative_paths = 1;\n-\ts->branch = resolve_refdup(\"HEAD\", sha1, NULL, 0);\n+\ts->branch = resolve_refdup(\"HEAD\", 0, sha1, NULL);\n \ts->reference = \"HEAD\";\n \ts->fp = stdout;\n \ts->index_file = get_index_file();\n"},{"id":"250135","messageId":"20141002015010.GT1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 01/24] mv test: recreate mod/ directory instead of relying on stale copy","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T01:50:10Z","receivedAt":"2014-10-02T01:50:10Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Date: Wed, 10 Sep 2014 14:01:46 -0700\n\nThe tests for 'git mv moves a submodule' functionality often run\ncommands like\n\n\tgit mv sub mod/sub\n\nto move a submodule into a subdirectory.  Just like plain /bin/mv,\nthis is supposed to succeed if the mod/ parent directory exists\nand fail if it doesn't exist.\n\nUsually these tests mkdir the parent directory beforehand, but some\ninstead rely on it being left behind by previous tests.\n\nMore precisely, when 'git reset --hard' tries to move to a state where\nmod/sub is not present any more, it would perform the following\noperations:\n\n\trmdir(\"mod/sub\")\n\trmdir(\"mod\")\n\nThe first fails with ENOENT because the test script removed mod/sub\nwith \"rm -rf\" already, so 'reset --hard' doesn't bother to move on to\nthe second, and the mod/ directory is kept around.\n\nBetter to explicitly remove and re-create the mod/ directory so later\ntests don't have to depend on the directory left behind by the earlier\nones at all (making it easier to rearrange or skip some tests in the\nfile or to tweak 'reset --hard' behavior without breaking unrelated\ntests).\n\nNoticed while testing a patch that fixes the reset --hard behavior\ndescribed above.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Ronnie Sahlberg <sahlberg@google.com>\n---\nAs before.\n\n t/t7001-mv.sh | 15 ++++++++++-----\n 1 file changed, 10 insertions(+), 5 deletions(-)\n\ndiff --git a/t/t7001-mv.sh b/t/t7001-mv.sh\nindex 54d7807..69f11bd 100755\n--- a/t/t7001-mv.sh\n+++ b/t/t7001-mv.sh\n@@ -350,10 +350,11 @@ test_expect_success 'git mv moves a submodule with a .git directory and .gitmodu\n '\n \n test_expect_success 'git mv moves a submodule with gitfile' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n \tentry=\"$(git ls-files --stage sub | cut -f 1)\" &&\n+\tmkdir mod &&\n \t(\n \t\tcd mod &&\n \t\tgit mv ../sub/ .\n@@ -372,11 +373,12 @@ test_expect_success 'git mv moves a submodule with gitfile' '\n '\n \n test_expect_success 'mv does not complain when no .gitmodules file is found' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n \tgit rm .gitmodules &&\n \tentry=\"$(git ls-files --stage sub | cut -f 1)\" &&\n+\tmkdir mod &&\n \tgit mv sub mod/sub 2>actual.err &&\n \t! test -s actual.err &&\n \t! test -e sub &&\n@@ -390,11 +392,12 @@ test_expect_success 'mv does not complain when no .gitmodules file is found' '\n '\n \n test_expect_success 'mv will error out on a modified .gitmodules file unless staged' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n \tgit config -f .gitmodules foo.bar true &&\n \tentry=\"$(git ls-files --stage sub | cut -f 1)\" &&\n+\tmkdir mod &&\n \ttest_must_fail git mv sub mod/sub 2>actual.err &&\n \ttest -s actual.err &&\n \ttest -e sub &&\n@@ -413,13 +416,14 @@ test_expect_success 'mv will error out on a modified .gitmodules file unless sta\n '\n \n test_expect_success 'mv issues a warning when section is not found in .gitmodules' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n \tgit config -f .gitmodules --remove-section submodule.sub &&\n \tgit add .gitmodules &&\n \tentry=\"$(git ls-files --stage sub | cut -f 1)\" &&\n \techo \"warning: Could not find section in .gitmodules where path=sub\" >expect.err &&\n+\tmkdir mod &&\n \tgit mv sub mod/sub 2>actual.err &&\n \ttest_i18ncmp expect.err actual.err &&\n \t! test -e sub &&\n@@ -433,9 +437,10 @@ test_expect_success 'mv issues a warning when section is not found in .gitmodule\n '\n \n test_expect_success 'mv --dry-run does not touch the submodule or .gitmodules' '\n-\trm -rf mod/sub &&\n+\trm -rf mod &&\n \tgit reset --hard &&\n \tgit submodule update &&\n+\tmkdir mod &&\n \tgit mv -n sub mod/sub 2>actual.err &&\n \ttest -f sub/.git &&\n \tgit diff-index --exit-code HEAD &&\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250136","messageId":"20141002015435.GU1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 02/24] wrapper.c: remove/unlink_or_warn: simplify, treat ENOENT as success","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T01:54:35Z","receivedAt":"2014-10-02T01:54:35Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Jul 2014 11:01:18 -0700\n\nSimplify the function warn_if_unremovable slightly. Additionally, change\nbehaviour slightly. If we failed to remove the object because the object\ndoes not exist, we can still return success back to the caller since none of\nthe callers depend on \"fail if the file did not exist\".\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nChange since v21:\n- adjust caller to remove redundant errno check\n\n git-compat-util.h |  7 +++++--\n refs.c            |  2 +-\n wrapper.c         | 14 ++++++--------\n 3 files changed, 12 insertions(+), 11 deletions(-)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex b6f03b3..611e77b 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -706,11 +706,14 @@ void git_qsort(void *base, size_t nmemb, size_t size,\n \n /*\n  * Preserves errno, prints a message, but gives no warning for ENOENT.\n- * Always returns the return value of unlink(2).\n+ * Returns 0 on success which includes trying to unlink an object that does\n+ * not exist.\n  */\n int unlink_or_warn(const char *path);\n /*\n- * Likewise for rmdir(2).\n+ * Preserves errno, prints a message, but gives no warning for ENOENT.\n+ * Returns 0 on success which includes trying to remove a directory that does\n+ * not exist.\n  */\n int rmdir_or_warn(const char *path);\n /*\ndiff --git a/refs.c b/refs.c\nindex 7235574..d0565b1 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2557,7 +2557,7 @@ static int delete_ref_loose(struct ref_lock *lock, int flag)\n \t\tlock->lk->filename[i] = 0;\n \t\terr = unlink_or_warn(lock->lk->filename);\n \t\tlock->lk->filename[i] = '.';\n-\t\tif (err && errno != ENOENT)\n+\t\tif (err)\n \t\t\treturn 1;\n \t}\n \treturn 0;\ndiff --git a/wrapper.c b/wrapper.c\nindex bc1bfb8..c9605cd 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -429,14 +429,12 @@ int xmkstemp_mode(char *template, int mode)\n \n static int warn_if_unremovable(const char *op, const char *file, int rc)\n {\n-\tif (rc < 0) {\n-\t\tint err = errno;\n-\t\tif (ENOENT != err) {\n-\t\t\twarning(\"unable to %s %s: %s\",\n-\t\t\t\top, file, strerror(errno));\n-\t\t\terrno = err;\n-\t\t}\n-\t}\n+\tint err;\n+\tif (!rc || errno == ENOENT)\n+\t\treturn 0;\n+\terr = errno;\n+\twarning(\"unable to %s %s: %s\", op, file, strerror(errno));\n+\terrno = err;\n \treturn rc;\n }\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250137","messageId":"20141002015545.GV1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 03/24] wrapper.c: add a new function unlink_or_msg","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T01:55:45Z","receivedAt":"2014-10-02T01:55:45Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 16 Jul 2014 11:20:36 -0700\n\nThis behaves like unlink_or_warn except that on failure it writes the message\nto its 'err' argument, which the caller can display in an appropriate way or\nignore.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\nAs before.\n\n git-compat-util.h |  9 +++++++++\n wrapper.c         | 14 ++++++++++++++\n 2 files changed, 23 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 611e77b..5ee140c 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -307,6 +307,8 @@ extern char *gitbasename(char *);\n \n #include \"wildmatch.h\"\n \n+struct strbuf;\n+\n /* General helper functions */\n extern void vreportf(const char *prefix, const char *err, va_list params);\n extern void vwritef(int fd, const char *prefix, const char *err, va_list params);\n@@ -710,6 +712,13 @@ void git_qsort(void *base, size_t nmemb, size_t size,\n  * not exist.\n  */\n int unlink_or_warn(const char *path);\n+ /*\n+  * Tries to unlink file.  Returns 0 if unlink succeeded\n+  * or the file already didn't exist.  Returns -1 and\n+  * appends a message to err suitable for\n+  * 'error(\"%s\", err->buf)' on error.\n+  */\n+int unlink_or_msg(const char *file, struct strbuf *err);\n /*\n  * Preserves errno, prints a message, but gives no warning for ENOENT.\n  * Returns 0 on success which includes trying to remove a directory that does\ndiff --git a/wrapper.c b/wrapper.c\nindex c9605cd..ec7a08b 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -438,6 +438,20 @@ static int warn_if_unremovable(const char *op, const char *file, int rc)\n \treturn rc;\n }\n \n+int unlink_or_msg(const char *file, struct strbuf *err)\n+{\n+\tint rc = unlink(file);\n+\n+\tassert(err);\n+\n+\tif (!rc || errno == ENOENT)\n+\t\treturn 0;\n+\n+\tstrbuf_addf(err, \"unable to unlink %s: %s\",\n+\t\t    file, strerror(errno));\n+\treturn -1;\n+}\n+\n int unlink_or_warn(const char *file)\n {\n \treturn warn_if_unremovable(\"unlink\", file, unlink(file));\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250138","messageId":"20141002015819.GW1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 04/24] refs.c: add an err argument to delete_ref_loose","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T01:58:19Z","receivedAt":"2014-10-02T01:58:19Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 15 May 2014 08:25:23 -0700\n\nAdd an err argument to delete_loose_ref so that we can pass a descriptive\nerror string back to the caller. Pass the err argument from transaction\ncommit to this function so that transaction users will have a nice error\nstring if the transaction failed due to delete_ref_loose.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nChanges since v21:\n- s/delete_loose_ref/delete_ref_loose/ once in commit message (but\n  the other one still needs fixing)\n\n refs.c | 11 ++++++-----\n 1 file changed, 6 insertions(+), 5 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex d0565b1..5609622 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2548,16 +2548,16 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \treturn ret;\n }\n \n-static int delete_ref_loose(struct ref_lock *lock, int flag)\n+static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)\n {\n \tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n \t\t/* loose */\n-\t\tint err, i = strlen(lock->lk->filename) - 5; /* .lock */\n+\t\tint res, i = strlen(lock->lk->filename) - 5; /* .lock */\n \n \t\tlock->lk->filename[i] = 0;\n-\t\terr = unlink_or_warn(lock->lk->filename);\n+\t\tres = unlink_or_msg(lock->lk->filename, err);\n \t\tlock->lk->filename[i] = '.';\n-\t\tif (err)\n+\t\tif (res)\n \t\t\treturn 1;\n \t}\n \treturn 0;\n@@ -3604,7 +3604,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (update->lock) {\n-\t\t\tret |= delete_ref_loose(update->lock, update->type);\n+\t\t\tret |= delete_ref_loose(update->lock, update->type,\n+\t\t\t\t\t\terr);\n \t\t\tif (!(update->flags & REF_ISPRUNING))\n \t\t\t\tdelnames[delnum++] = update->lock->ref_name;\n \t\t}\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250139","messageId":"20141002015952.GX1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 05/24] refs.c: pass the ref log message to _create/delete/update instead of _commit","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T01:59:52Z","receivedAt":"2014-10-02T01:59:52Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 30 Apr 2014 12:22:42 -0700\n\nChange the ref transaction API so that we pass the reflog message to the\ncreate/delete/update functions instead of to ref_transaction_commit.\nThis allows different reflog messages for each ref update in a multi-ref\ntransaction.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nChanges since v21:\n- cleaned up commit message\n- clarified ownership of msg in comment in refs.h\n\n branch.c               |  4 ++--\n builtin/commit.c       |  4 ++--\n builtin/receive-pack.c |  5 +++--\n builtin/replace.c      |  5 +++--\n builtin/tag.c          |  4 ++--\n builtin/update-ref.c   | 13 +++++++------\n fast-import.c          |  8 ++++----\n refs.c                 | 34 +++++++++++++++++++++-------------\n refs.h                 | 12 ++++++------\n sequencer.c            |  4 ++--\n walker.c               |  5 ++---\n 11 files changed, 54 insertions(+), 44 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 37ac555..76a8ec9 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -301,8 +301,8 @@ void create_branch(const char *head,\n \t\ttransaction = ref_transaction_begin(&err);\n \t\tif (!transaction ||\n \t\t    ref_transaction_update(transaction, ref.buf, sha1,\n-\t\t\t\t\t   null_sha1, 0, !forcing, &err) ||\n-\t\t    ref_transaction_commit(transaction, msg, &err))\n+\t\t\t\t\t   null_sha1, 0, !forcing, msg, &err) ||\n+\t\t    ref_transaction_commit(transaction, &err))\n \t\t\tdie(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n \t\tstrbuf_release(&err);\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 9bf1003..d23e876 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1767,8 +1767,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t    ref_transaction_update(transaction, \"HEAD\", sha1,\n \t\t\t\t   current_head\n \t\t\t\t   ? current_head->object.sha1 : NULL,\n-\t\t\t\t   0, !!current_head, &err) ||\n-\t    ref_transaction_commit(transaction, sb.buf, &err)) {\n+\t\t\t\t   0, !!current_head, sb.buf, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n \t\trollback_index_files();\n \t\tdie(\"%s\", err.buf);\n \t}\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 224fadc..d1f4cf7 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -585,8 +585,9 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\ttransaction = ref_transaction_begin(&err);\n \t\tif (!transaction ||\n \t\t    ref_transaction_update(transaction, namespaced_name,\n-\t\t\t\t\t   new_sha1, old_sha1, 0, 1, &err) ||\n-\t\t    ref_transaction_commit(transaction, \"push\", &err)) {\n+\t\t\t\t\t   new_sha1, old_sha1, 0, 1, \"push\",\n+\t\t\t\t\t   &err) ||\n+\t\t    ref_transaction_commit(transaction, &err)) {\n \t\t\tref_transaction_free(transaction);\n \n \t\t\trp_error(\"%s\", err.buf);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 1fcd06d..9d03b84 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -169,8 +169,9 @@ static int replace_object_sha1(const char *object_ref,\n \n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, repl, prev, 0, 1, &err) ||\n-\t    ref_transaction_commit(transaction, NULL, &err))\n+\t    ref_transaction_update(transaction, ref, repl, prev,\n+\t\t\t\t   0, 1, NULL, &err) ||\n+\t    ref_transaction_commit(transaction, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tref_transaction_free(transaction);\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex f3f172f..70d28c5 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -705,8 +705,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, object, prev,\n-\t\t\t\t   0, 1, &err) ||\n-\t    ref_transaction_commit(transaction, NULL, &err))\n+\t\t\t\t   0, 1, NULL, &err) ||\n+\t    ref_transaction_commit(transaction, &err))\n \t\tdie(\"%s\", err.buf);\n \tref_transaction_free(transaction);\n \tif (force && !is_null_sha1(prev) && hashcmp(prev, object))\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 54a48c0..6c9be05 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -14,6 +14,7 @@ static const char * const git_update_ref_usage[] = {\n \n static char line_termination = '\\n';\n static int update_flags;\n+static const char *msg;\n \n /*\n  * Parse one whitespace- or NUL-terminated, possibly C-quoted argument\n@@ -198,7 +199,7 @@ static const char *parse_cmd_update(struct ref_transaction *transaction,\n \t\tdie(\"update %s: extra input: %s\", refname, next);\n \n \tif (ref_transaction_update(transaction, refname, new_sha1, old_sha1,\n-\t\t\t\t   update_flags, have_old, &err))\n+\t\t\t\t   update_flags, have_old, msg, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = 0;\n@@ -229,7 +230,7 @@ static const char *parse_cmd_create(struct ref_transaction *transaction,\n \t\tdie(\"create %s: extra input: %s\", refname, next);\n \n \tif (ref_transaction_create(transaction, refname, new_sha1,\n-\t\t\t\t   update_flags, &err))\n+\t\t\t\t   update_flags, msg, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = 0;\n@@ -264,7 +265,7 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction,\n \t\tdie(\"delete %s: extra input: %s\", refname, next);\n \n \tif (ref_transaction_delete(transaction, refname, old_sha1,\n-\t\t\t\t   update_flags, have_old, &err))\n+\t\t\t\t   update_flags, have_old, msg, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = 0;\n@@ -300,7 +301,7 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction,\n \t\tdie(\"verify %s: extra input: %s\", refname, next);\n \n \tif (ref_transaction_update(transaction, refname, new_sha1, old_sha1,\n-\t\t\t\t   update_flags, have_old, &err))\n+\t\t\t\t   update_flags, have_old, msg, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = 0;\n@@ -354,7 +355,7 @@ static void update_refs_stdin(struct ref_transaction *transaction)\n \n int cmd_update_ref(int argc, const char **argv, const char *prefix)\n {\n-\tconst char *refname, *oldval, *msg = NULL;\n+\tconst char *refname, *oldval;\n \tunsigned char sha1[20], oldsha1[20];\n \tint delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;\n \tstruct option options[] = {\n@@ -385,7 +386,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \t\tif (end_null)\n \t\t\tline_termination = '\\0';\n \t\tupdate_refs_stdin(transaction);\n-\t\tif (ref_transaction_commit(transaction, msg, &err))\n+\t\tif (ref_transaction_commit(transaction, &err))\n \t\t\tdie(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n \t\tstrbuf_release(&err);\ndiff --git a/fast-import.c b/fast-import.c\nindex e7f6e37..51dfaad 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1708,8 +1708,8 @@ static int update_branch(struct branch *b)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, b->name, b->sha1, old_sha1,\n-\t\t\t\t   0, 1, &err) ||\n-\t    ref_transaction_commit(transaction, msg, &err)) {\n+\t\t\t\t   0, 1, msg, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n \t\tstrbuf_release(&err);\n@@ -1749,12 +1749,12 @@ static void dump_tags(void)\n \t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n \n \t\tif (ref_transaction_update(transaction, ref_name.buf, t->sha1,\n-\t\t\t\t\t   NULL, 0, 0, &err)) {\n+\t\t\t\t\t   NULL, 0, 0, msg, &err)) {\n \t\t\tfailure |= error(\"%s\", err.buf);\n \t\t\tgoto cleanup;\n \t\t}\n \t}\n-\tif (ref_transaction_commit(transaction, msg, &err))\n+\tif (ref_transaction_commit(transaction, &err))\n \t\tfailure |= error(\"%s\", err.buf);\n \n  cleanup:\ndiff --git a/refs.c b/refs.c\nindex 5609622..99a9b86 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2396,8 +2396,8 @@ static void prune_ref(struct ref_to_prune *r)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_delete(transaction, r->name, r->sha1,\n-\t\t\t\t   REF_ISPRUNING, 1, &err) ||\n-\t    ref_transaction_commit(transaction, NULL, &err)) {\n+\t\t\t\t   REF_ISPRUNING, 1, NULL, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n \t\tstrbuf_release(&err);\n@@ -2571,8 +2571,8 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_delete(transaction, refname, sha1, delopt,\n-\t\t\t\t   sha1 && !is_null_sha1(sha1), &err) ||\n-\t    ref_transaction_commit(transaction, NULL, &err)) {\n+\t\t\t\t   sha1 && !is_null_sha1(sha1), NULL, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n \t\tstrbuf_release(&err);\n@@ -3350,6 +3350,7 @@ struct ref_update {\n \tint have_old; /* 1 if old_sha1 is valid, 0 otherwise */\n \tstruct ref_lock *lock;\n \tint type;\n+\tchar *msg;\n \tconst char refname[FLEX_ARRAY];\n };\n \n@@ -3392,9 +3393,10 @@ void ref_transaction_free(struct ref_transaction *transaction)\n \tif (!transaction)\n \t\treturn;\n \n-\tfor (i = 0; i < transaction->nr; i++)\n+\tfor (i = 0; i < transaction->nr; i++) {\n+\t\tfree(transaction->updates[i]->msg);\n \t\tfree(transaction->updates[i]);\n-\n+\t}\n \tfree(transaction->updates);\n \tfree(transaction);\n }\n@@ -3415,7 +3417,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const unsigned char *new_sha1,\n \t\t\t   const unsigned char *old_sha1,\n-\t\t\t   int flags, int have_old,\n+\t\t\t   int flags, int have_old, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n \tstruct ref_update *update;\n@@ -3432,13 +3434,15 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tupdate->have_old = have_old;\n \tif (have_old)\n \t\thashcpy(update->old_sha1, old_sha1);\n+\tif (msg)\n+\t\tupdate->msg = xstrdup(msg);\n \treturn 0;\n }\n \n int ref_transaction_create(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const unsigned char *new_sha1,\n-\t\t\t   int flags,\n+\t\t\t   int flags, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n \tstruct ref_update *update;\n@@ -3455,13 +3459,15 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \thashclr(update->old_sha1);\n \tupdate->flags = flags;\n \tupdate->have_old = 1;\n+\tif (msg)\n+\t\tupdate->msg = xstrdup(msg);\n \treturn 0;\n }\n \n int ref_transaction_delete(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const unsigned char *old_sha1,\n-\t\t\t   int flags, int have_old,\n+\t\t\t   int flags, int have_old, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n \tstruct ref_update *update;\n@@ -3479,6 +3485,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n \t\tassert(!is_null_sha1(old_sha1));\n \t\thashcpy(update->old_sha1, old_sha1);\n \t}\n+\tif (msg)\n+\t\tupdate->msg = xstrdup(msg);\n \treturn 0;\n }\n \n@@ -3492,8 +3500,8 @@ int update_ref(const char *action, const char *refname,\n \tt = ref_transaction_begin(&err);\n \tif (!t ||\n \t    ref_transaction_update(t, refname, sha1, oldval, flags,\n-\t\t\t\t   !!oldval, &err) ||\n-\t    ref_transaction_commit(t, action, &err)) {\n+\t\t\t\t   !!oldval, action, &err) ||\n+\t    ref_transaction_commit(t, &err)) {\n \t\tconst char *str = \"update_ref failed for ref '%s': %s\";\n \n \t\tref_transaction_free(t);\n@@ -3539,7 +3547,7 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,\n }\n \n int ref_transaction_commit(struct ref_transaction *transaction,\n-\t\t\t   const char *msg, struct strbuf *err)\n+\t\t\t   struct strbuf *err)\n {\n \tint ret = 0, delnum = 0, i;\n \tconst char **delnames;\n@@ -3588,7 +3596,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \n \t\tif (!is_null_sha1(update->new_sha1)) {\n \t\t\tret = write_ref_sha1(update->lock, update->new_sha1,\n-\t\t\t\t\t     msg);\n+\t\t\t\t\t     update->msg);\n \t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n \t\t\tif (ret) {\n \t\t\t\tif (err)\ndiff --git a/refs.h b/refs.h\nindex 69ef28c..aded545 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -271,8 +271,8 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  * The following functions add a reference check or update to a\n  * ref_transaction.  In all of them, refname is the name of the\n  * reference to be affected.  The functions make internal copies of\n- * refname, so the caller retains ownership of the parameter.  flags\n- * can be REF_NODEREF; it is passed to update_ref_lock().\n+ * refname and msg, so the caller retains ownership of these parameters.\n+ * flags can be REF_NODEREF; it is passed to update_ref_lock().\n  */\n \n /*\n@@ -289,7 +289,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const unsigned char *new_sha1,\n \t\t\t   const unsigned char *old_sha1,\n-\t\t\t   int flags, int have_old,\n+\t\t\t   int flags, int have_old, const char *msg,\n \t\t\t   struct strbuf *err);\n \n /*\n@@ -304,7 +304,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n int ref_transaction_create(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const unsigned char *new_sha1,\n-\t\t\t   int flags,\n+\t\t\t   int flags, const char *msg,\n \t\t\t   struct strbuf *err);\n \n /*\n@@ -318,7 +318,7 @@ int ref_transaction_create(struct ref_transaction *transaction,\n int ref_transaction_delete(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const unsigned char *old_sha1,\n-\t\t\t   int flags, int have_old,\n+\t\t\t   int flags, int have_old, const char *msg,\n \t\t\t   struct strbuf *err);\n \n /*\n@@ -327,7 +327,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n  * problem.\n  */\n int ref_transaction_commit(struct ref_transaction *transaction,\n-\t\t\t   const char *msg, struct strbuf *err);\n+\t\t\t   struct strbuf *err);\n \n /*\n  * Free an existing transaction and all associated data.\ndiff --git a/sequencer.c b/sequencer.c\nindex 5e93b6a..c5b7b8a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -286,8 +286,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\",\n \t\t\t\t   to, unborn ? null_sha1 : from,\n-\t\t\t\t   0, 1, &err) ||\n-\t    ref_transaction_commit(transaction, sb.buf, &err)) {\n+\t\t\t\t   0, 1, sb.buf, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n \t\tstrbuf_release(&sb);\ndiff --git a/walker.c b/walker.c\nindex b8a5441..3c6fb8a 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -298,14 +298,13 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\tstrbuf_addf(&refname, \"refs/%s\", write_ref[i]);\n \t\tif (ref_transaction_update(transaction, refname.buf,\n \t\t\t\t\t   &sha1[20 * i], NULL, 0, 0,\n+\t\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n \t\t\t\t\t   &err)) {\n \t\t\terror(\"%s\", err.buf);\n \t\t\tgoto done;\n \t\t}\n \t}\n-\tif (ref_transaction_commit(transaction,\n-\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n-\t\t\t\t   &err)) {\n+\tif (ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tgoto done;\n \t}\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250140","messageId":"20141002020036.GY1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 06/24] rename_ref: don't ask read_ref_full where the ref came from","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:00:36Z","receivedAt":"2014-10-02T02:00:36Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 30 Apr 2014 12:41:04 -0700\n\nWe call read_ref_full with a pointer to flags from rename_ref but since\nwe never actually use the returned flags we can just pass NULL here instead.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\nAs before.\n\n refs.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 99a9b86..39571f5 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2671,7 +2671,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \t\tgoto rollback;\n \t}\n \n-\tif (!read_ref_full(newrefname, sha1, 1, &flag) &&\n+\tif (!read_ref_full(newrefname, sha1, 1, NULL) &&\n \t    delete_ref(newrefname, sha1, REF_NODEREF)) {\n \t\tif (errno==EISDIR) {\n \t\t\tif (remove_empty_directories(git_path(\"%s\", newrefname))) {\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250141","messageId":"20141002020131.GZ1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 07/24] refs.c: refuse to lock badly named refs in lock_ref_sha1_basic","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:01:31Z","receivedAt":"2014-10-02T02:01:31Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 1 May 2014 10:40:10 -0700\n\nMove the check for check_refname_format from lock_any_ref_for_update to\nlock_ref_sha1_basic.  At some later stage we will get rid of\nlock_any_ref_for_update completely.  This has no visible impact to callers\nexcept for the inability to lock badly named refs, which is not possible\ntoday already for other reasons.(*)\n\nKeep lock_any_ref_for_update as a no-op wrapper.  It is the public facing\nversion of this interface and keeping it as a separate function will make\nit easier to experiment with the internal lock_ref_sha1_basic signature.\n\n(*) For example, if lock_ref_sha1_basic checks the refname format and\nrefuses to lock badly named refs, it will not be possible to delete\nsuch refs because the first step of deletion is to lock the ref.  We\ncurrently already fail in that case because these refs are not recognized\nto exist:\n\n $ cp .git/refs/heads/master .git/refs/heads/echo...\\*\\*\n $ git branch -D .git/refs/heads/echo...\\*\\*\n error: branch '.git/refs/heads/echo...**' not found.\n\nThis has been broken for a while.  Later patches in the series will start\nrepairing the handling of badly named refs.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nChange since v21:\n- clarified commit message\n\n refs.c | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 39571f5..3c2ce57 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2091,6 +2091,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \tint missing = 0;\n \tint attempts_remaining = 3;\n \n+\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\terrno = EINVAL;\n+\t\treturn NULL;\n+\t}\n+\n \tlock = xcalloc(1, sizeof(struct ref_lock));\n \tlock->lock_fd = -1;\n \n@@ -2182,8 +2187,6 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t const unsigned char *old_sha1,\n \t\t\t\t\t int flags, int *type_p)\n {\n-\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))\n-\t\treturn NULL;\n \treturn lock_ref_sha1_basic(refname, old_sha1, flags, type_p);\n }\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250142","messageId":"20141002020207.GA1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 08/24] refs.c: call lock_ref_sha1_basic directly from commit","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:02:07Z","receivedAt":"2014-10-02T02:02:07Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 1 May 2014 10:43:39 -0700\n\nSkip using the lock_any_ref_for_update wrapper and call lock_ref_sha1_basic\ndirectly from the commit function.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\nAs before.\n\n refs.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 3c2ce57..f124c2b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3578,12 +3578,12 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n \n-\t\tupdate->lock = lock_any_ref_for_update(update->refname,\n-\t\t\t\t\t\t       (update->have_old ?\n-\t\t\t\t\t\t\tupdate->old_sha1 :\n-\t\t\t\t\t\t\tNULL),\n-\t\t\t\t\t\t       update->flags,\n-\t\t\t\t\t\t       &update->type);\n+\t\tupdate->lock = lock_ref_sha1_basic(update->refname,\n+\t\t\t\t\t\t   (update->have_old ?\n+\t\t\t\t\t\t    update->old_sha1 :\n+\t\t\t\t\t\t    NULL),\n+\t\t\t\t\t\t   update->flags,\n+\t\t\t\t\t\t   &update->type);\n \t\tif (!update->lock) {\n \t\t\tif (err)\n \t\t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250143","messageId":"20141002020332.GB1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 09/24] refs.c: pass a list of names to skip to is_refname_available","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:03:32Z","receivedAt":"2014-10-02T02:03:32Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 1 May 2014 11:16:07 -0700\n\nChange is_refname_available to take a list of strings to exclude when\nchecking for conflicts instead of just one single name. We can already\nexclude a single name for the sake of renames. This generalizes that support.\n\nref_transaction_commit already tracks a set of refs that are being deleted\nin an array.  This array is then used to exclude refs from being written to\nthe packed-refs file.  At some stage we will want to change this array to a\nstruct string_list and then we can pass it to is_refname_available via the\ncall to lock_ref_sha1_basic.  That will allow us to perform transactions\nthat perform multiple renames as long as there are no conflicts within the\nstarting or ending state.\n\nFor example, that would allow a single transaction that contains two\nrenames that are both individually conflicting:\n\n   m -> n/n\n   n -> m/m\n\nNo functional change intended yet.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nSince v21:\n- clarified commit message\n- clarified comments\n\n refs.c | 44 +++++++++++++++++++++++++++++---------------\n 1 file changed, 29 insertions(+), 15 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex f124c2b..6820c93 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -801,14 +801,16 @@ static int names_conflict(const char *refname1, const char *refname2)\n \n struct name_conflict_cb {\n \tconst char *refname;\n-\tconst char *oldrefname;\n \tconst char *conflicting_refname;\n+\tstruct string_list *skiplist;\n };\n \n static int name_conflict_fn(struct ref_entry *entry, void *cb_data)\n {\n \tstruct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;\n-\tif (data->oldrefname && !strcmp(data->oldrefname, entry->name))\n+\n+\tif (data->skiplist &&\n+\t    string_list_has_string(data->skiplist, entry->name))\n \t\treturn 0;\n \tif (names_conflict(data->refname, entry->name)) {\n \t\tdata->conflicting_refname = entry->name;\n@@ -820,17 +822,18 @@ static int name_conflict_fn(struct ref_entry *entry, void *cb_data)\n /*\n  * Return true iff a reference named refname could be created without\n  * conflicting with the name of an existing reference in dir.  If\n- * oldrefname is non-NULL, ignore potential conflicts with oldrefname\n- * (e.g., because oldrefname is scheduled for deletion in the same\n- * operation).\n+ * skiplist is non-NULL, ignore potential conflicts with names in\n+ * skiplist (e.g., because those refs are scheduled for deletion in\n+ * the same operation).  skiplist must be sorted.\n  */\n-static int is_refname_available(const char *refname, const char *oldrefname,\n-\t\t\t\tstruct ref_dir *dir)\n+static int is_refname_available(const char *refname,\n+\t\t\t\tstruct ref_dir *dir,\n+\t\t\t\tstruct string_list *skiplist)\n {\n \tstruct name_conflict_cb data;\n \tdata.refname = refname;\n-\tdata.oldrefname = oldrefname;\n \tdata.conflicting_refname = NULL;\n+\tdata.skiplist = skiplist;\n \n \tsort_ref_dir(dir);\n \tif (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, &data)) {\n@@ -2080,6 +2083,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n  */\n static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\t\t\t    const unsigned char *old_sha1,\n+\t\t\t\t\t    struct string_list *skiplist,\n \t\t\t\t\t    int flags, int *type_p)\n {\n \tchar *ref_file;\n@@ -2129,7 +2133,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t * name is a proper prefix of our refname.\n \t */\n \tif (missing &&\n-\t     !is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) {\n+\t     !is_refname_available(refname, get_packed_refs(&ref_cache),\n+\t\t\t\t   skiplist)) {\n \t\tlast_errno = ENOTDIR;\n \t\tgoto error_return;\n \t}\n@@ -2187,7 +2192,7 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t const unsigned char *old_sha1,\n \t\t\t\t\t int flags, int *type_p)\n {\n-\treturn lock_ref_sha1_basic(refname, old_sha1, flags, type_p);\n+\treturn lock_ref_sha1_basic(refname, old_sha1, NULL, flags, type_p);\n }\n \n /*\n@@ -2648,6 +2653,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \tstruct stat loginfo;\n \tint log = !lstat(git_path(\"logs/%s\", oldrefname), &loginfo);\n \tconst char *symref = NULL;\n+\tstruct string_list skiplist = STRING_LIST_INIT_NODUP;\n \n \tif (log && S_ISLNK(loginfo.st_mode))\n \t\treturn error(\"reflog for %s is a symlink\", oldrefname);\n@@ -2659,11 +2665,18 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \tif (!symref)\n \t\treturn error(\"refname %s not found\", oldrefname);\n \n-\tif (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache)))\n+\tstring_list_insert(&skiplist, oldrefname);\n+\tif (!is_refname_available(newrefname, get_packed_refs(&ref_cache),\n+\t\t\t\t  &skiplist)) {\n+\t\tstring_list_clear(&skiplist, 0);\n \t\treturn 1;\n-\n-\tif (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache)))\n+\t}\n+\tif (!is_refname_available(newrefname, get_loose_refs(&ref_cache),\n+\t\t\t\t  &skiplist)) {\n+\t\tstring_list_clear(&skiplist, 0);\n \t\treturn 1;\n+\t}\n+\tstring_list_clear(&skiplist, 0);\n \n \tif (log && rename(git_path(\"logs/%s\", oldrefname), git_path(TMP_RENAMED_LOG)))\n \t\treturn error(\"unable to move logfile logs/%s to \"TMP_RENAMED_LOG\": %s\",\n@@ -2692,7 +2705,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \n \tlogmoved = log;\n \n-\tlock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL);\n+\tlock = lock_ref_sha1_basic(newrefname, NULL, NULL, 0, NULL);\n \tif (!lock) {\n \t\terror(\"unable to lock %s for update\", newrefname);\n \t\tgoto rollback;\n@@ -2707,7 +2720,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \treturn 0;\n \n  rollback:\n-\tlock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL);\n+\tlock = lock_ref_sha1_basic(oldrefname, NULL, NULL, 0, NULL);\n \tif (!lock) {\n \t\terror(\"unable to lock %s for rollback\", oldrefname);\n \t\tgoto rollbacklog;\n@@ -3582,6 +3595,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t\t\t   (update->have_old ?\n \t\t\t\t\t\t    update->old_sha1 :\n \t\t\t\t\t\t    NULL),\n+\t\t\t\t\t\t   NULL,\n \t\t\t\t\t\t   update->flags,\n \t\t\t\t\t\t   &update->type);\n \t\tif (!update->lock) {\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250144","messageId":"20141002020557.GC1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 10/24] refs.c: ref_transaction_commit: distinguish name conflicts from other errors","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:05:57Z","receivedAt":"2014-10-02T02:05:57Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Fri, 16 May 2014 14:14:38 -0700\n\nIn _commit, ENOTDIR can happen in the call to lock_ref_sha1_basic, either\nwhen we lstat the new refname or if the name checking function reports that\nthe same type of conflict happened.  In both cases, it means that we can not\ncreate the new ref due to a name conflict.\n\nStart defining specific return codes for _commit.  TRANSACTION_NAME_CONFLICT\nrefers to a failure to create a ref due to a name conflict with another ref.\nTRANSACTION_GENERIC_ERROR is for all other errors.\n\nWhen \"git fetch\" is creating refs, name conflicts differ from other errors in\nthat they are likely to be resolved by running \"git remote prune <remote>\".\n\"git fetch\" currently inspects errno to decide whether to give that advice.\nOnce it switches to the transaction API, it can check for\nTRANSACTION_NAME_CONFLICT instead.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nSince v21:\n- clarified commit message and updated to match code\n- small code cleanups\n- clarified API doc, introduced TRANSACTION_GENERIC_ERROR so both error\n  codes have names\n\n refs.c | 26 ++++++++++++++++----------\n refs.h |  9 +++++++--\n 2 files changed, 23 insertions(+), 12 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 6820c93..623a1ae 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3583,9 +3583,10 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \n \t/* Copy, sort, and reject duplicate refs */\n \tqsort(updates, n, sizeof(*updates), ref_update_compare);\n-\tret = ref_update_reject_duplicates(updates, n, err);\n-\tif (ret)\n+\tif (ref_update_reject_duplicates(updates, n, err)) {\n+\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\tgoto cleanup;\n+\t}\n \n \t/* Acquire all locks while verifying old values */\n \tfor (i = 0; i < n; i++) {\n@@ -3599,10 +3600,12 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t\t\t   update->flags,\n \t\t\t\t\t\t   &update->type);\n \t\tif (!update->lock) {\n+\t\t\tret = (errno == ENOTDIR)\n+\t\t\t\t? TRANSACTION_NAME_CONFLICT\n+\t\t\t\t: TRANSACTION_GENERIC_ERROR;\n \t\t\tif (err)\n \t\t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n \t\t\t\t\t    update->refname);\n-\t\t\tret = 1;\n \t\t\tgoto cleanup;\n \t\t}\n \t}\n@@ -3612,15 +3615,16 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (!is_null_sha1(update->new_sha1)) {\n-\t\t\tret = write_ref_sha1(update->lock, update->new_sha1,\n-\t\t\t\t\t     update->msg);\n-\t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n-\t\t\tif (ret) {\n+\t\t\tif (write_ref_sha1(update->lock, update->new_sha1,\n+\t\t\t\t\t   update->msg)) {\n+\t\t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n \t\t\t\tif (err)\n \t\t\t\t\tstrbuf_addf(err, \"Cannot update the ref '%s'.\",\n \t\t\t\t\t\t    update->refname);\n+\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto cleanup;\n \t\t\t}\n+\t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n \t\t}\n \t}\n \n@@ -3629,14 +3633,16 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (update->lock) {\n-\t\t\tret |= delete_ref_loose(update->lock, update->type,\n-\t\t\t\t\t\terr);\n+\t\t\tif (delete_ref_loose(update->lock, update->type, err))\n+\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\n \t\t\tif (!(update->flags & REF_ISPRUNING))\n \t\t\t\tdelnames[delnum++] = update->lock->ref_name;\n \t\t}\n \t}\n \n-\tret |= repack_without_refs(delnames, delnum, err);\n+\tif (repack_without_refs(delnames, delnum, err))\n+\t\tret = TRANSACTION_GENERIC_ERROR;\n \tfor (i = 0; i < delnum; i++)\n \t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n \tclear_loose_ref_cache(&ref_cache);\ndiff --git a/refs.h b/refs.h\nindex aded545..fd63b47 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -323,9 +323,14 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n \n /*\n  * Commit all of the changes that have been queued in transaction, as\n- * atomically as possible.  Return a nonzero value if there is a\n- * problem.\n+ * atomically as possible.\n+ *\n+ * Returns 0 for success, or one of the below error codes for errors.\n  */\n+/* Naming conflict (for example, the ref names A and A/B conflict). */\n+#define TRANSACTION_NAME_CONFLICT -1\n+/* All other errors. */\n+#define TRANSACTION_GENERIC_ERROR -2\n int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t   struct strbuf *err);\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250145","messageId":"20141002020722.GD1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 11/24] fetch.c: change s_update_ref to use a ref transaction","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:07:22Z","receivedAt":"2014-10-02T02:07:22Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Mon, 28 Apr 2014 13:49:07 -0700\n\nChange s_update_ref to use a ref transaction for the ref update.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nSince v21:\n- tweaked handling of ref_transaction_commit result (no functional\n  change)\n\n builtin/fetch.c | 34 ++++++++++++++++++++++++----------\n 1 file changed, 24 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 55f457c..30b40f6 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -375,23 +375,37 @@ static int s_update_ref(const char *action,\n {\n \tchar msg[1024];\n \tchar *rla = getenv(\"GIT_REFLOG_ACTION\");\n-\tstatic struct ref_lock *lock;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint ret, df_conflict = 0;\n \n \tif (dry_run)\n \t\treturn 0;\n \tif (!rla)\n \t\trla = default_rla.buf;\n \tsnprintf(msg, sizeof(msg), \"%s: %s\", rla, action);\n-\tlock = lock_any_ref_for_update(ref->name,\n-\t\t\t\t       check_old ? ref->old_sha1 : NULL,\n-\t\t\t\t       0, NULL);\n-\tif (!lock)\n-\t\treturn errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :\n-\t\t\t\t\t  STORE_REF_ERROR_OTHER;\n-\tif (write_ref_sha1(lock, ref->new_sha1, msg) < 0)\n-\t\treturn errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :\n-\t\t\t\t\t  STORE_REF_ERROR_OTHER;\n+\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, ref->name, ref->new_sha1,\n+\t\t\t\t   ref->old_sha1, 0, check_old, msg, &err))\n+\t\tgoto fail;\n+\n+\tret = ref_transaction_commit(transaction, &err);\n+\tif (ret) {\n+\t\tdf_conflict = (ret == TRANSACTION_NAME_CONFLICT);\n+\t\tgoto fail;\n+\t}\n+\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n \treturn 0;\n+fail:\n+\tref_transaction_free(transaction);\n+\terror(\"%s\", err.buf);\n+\tstrbuf_release(&err);\n+\treturn df_conflict ? STORE_REF_ERROR_DF_CONFLICT\n+\t\t\t   : STORE_REF_ERROR_OTHER;\n }\n \n #define REFCOL_WIDTH  10\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250146","messageId":"20141002020851.GE1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 12/24] refs.c: make write_ref_sha1 static","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:08:51Z","receivedAt":"2014-10-02T02:08:51Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Mon, 28 Apr 2014 15:36:58 -0700\n\nNo external users call write_ref_sha1 any more so let's declare it static.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nSince v21:\n- punctuation fix in commit message\n- grammar tweak in doc comment\n\n refs.c | 10 ++++++++--\n refs.h |  3 ---\n 2 files changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 623a1ae..f596a9f 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2645,6 +2645,9 @@ static int rename_tmp_log(const char *newrefname)\n \treturn 0;\n }\n \n+static int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1,\n+\t\t\t  const char *logmsg);\n+\n int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)\n {\n \tunsigned char sha1[20], orig_sha1[20];\n@@ -2900,8 +2903,11 @@ static int is_branch(const char *refname)\n \treturn !strcmp(refname, \"HEAD\") || starts_with(refname, \"refs/heads/\");\n }\n \n-/* This function must return a meaningful errno */\n-int write_ref_sha1(struct ref_lock *lock,\n+/*\n+ * Write sha1 into the ref specified by the lock. Make sure that errno\n+ * is sane on error.\n+ */\n+static int write_ref_sha1(struct ref_lock *lock,\n \tconst unsigned char *sha1, const char *logmsg)\n {\n \tstatic char term = '\\n';\ndiff --git a/refs.h b/refs.h\nindex fd63b47..3bb16db 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -195,9 +195,6 @@ extern int commit_ref(struct ref_lock *lock);\n /** Release any lock taken but not written. **/\n extern void unlock_ref(struct ref_lock *lock);\n \n-/** Writes sha1 into the ref specified by the lock. **/\n-extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);\n-\n /*\n  * Setup reflog before using. Set errno to something meaningful on failure.\n  */\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250147","messageId":"20141002021019.GF1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 13/24] refs.c: change resolve_ref_unsafe reading argument to be a flags field","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:10:19Z","receivedAt":"2014-10-02T02:10:19Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Tue, 15 Jul 2014 12:59:36 -0700\n\nresolve_ref_unsafe takes a boolean argument for reading (a nonexistent ref\nresolves successfully for writing but not for reading).  Change this to be\na flags field instead, and pass the new constant RESOLVE_REF_READING when\nwe want this behaviour.\n\nWhile at it, swap two of the arguments in the function to put output\narguments at the end.  As a nice side effect, this ensures that we can\ncatch callers that were unaware of the new API so they can be audited.\n\nGive the wrapper functions resolve_refdup and read_ref_full the same\ntreatment for consistency.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nSince v21:\n- clarified commit message\n- put output parameters last\n\n branch.c                |  2 +-\n builtin/blame.c         |  2 +-\n builtin/branch.c        |  9 ++---\n builtin/checkout.c      |  6 ++--\n builtin/clone.c         |  2 +-\n builtin/commit.c        |  2 +-\n builtin/fmt-merge-msg.c |  2 +-\n builtin/for-each-ref.c  |  6 ++--\n builtin/fsck.c          |  2 +-\n builtin/log.c           |  3 +-\n builtin/merge.c         |  2 +-\n builtin/notes.c         |  2 +-\n builtin/receive-pack.c  |  4 +--\n builtin/remote.c        |  5 +--\n builtin/show-branch.c   |  7 ++--\n builtin/symbolic-ref.c  |  2 +-\n bundle.c                |  2 +-\n cache.h                 | 23 ++++++------\n http-backend.c          |  4 ++-\n notes-merge.c           |  2 +-\n reflog-walk.c           |  5 +--\n refs.c                  | 93 ++++++++++++++++++++++++++++---------------------\n remote.c                | 11 +++---\n sequencer.c             |  4 +--\n transport-helper.c      |  5 ++-\n transport.c             |  5 +--\n upload-pack.c           |  2 +-\n wt-status.c             |  2 +-\n 28 files changed, 124 insertions(+), 92 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 76a8ec9..adb07c6 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -186,7 +186,7 @@ int validate_new_branchname(const char *name, struct strbuf *ref,\n \t\tconst char *head;\n \t\tunsigned char sha1[20];\n \n-\t\thead = resolve_ref_unsafe(\"HEAD\", sha1, 0, NULL);\n+\t\thead = resolve_ref_unsafe(\"HEAD\", 0, sha1, NULL);\n \t\tif (!is_bare_repository() && head && !strcmp(head, ref->buf))\n \t\t\tdie(_(\"Cannot force update the current branch.\"));\n \t}\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex a52a279..5cbd38f 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -2292,7 +2292,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,\n \tcommit->object.type = OBJ_COMMIT;\n \tparent_tail = &commit->parents;\n \n-\tif (!resolve_ref_unsafe(\"HEAD\", head_sha1, 1, NULL))\n+\tif (!resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING, head_sha1, NULL))\n \t\tdie(\"no such ref: HEAD\");\n \n \tparent_tail = append_parent(parent_tail, head_sha1);\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 652b1d2..e5d1377 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -129,7 +129,8 @@ static int branch_merged(int kind, const char *name,\n \t\t    branch->merge[0] &&\n \t\t    branch->merge[0]->dst &&\n \t\t    (reference_name = reference_name_to_free =\n-\t\t     resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)\n+\t\t     resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING,\n+\t\t\t\t    sha1, NULL)) != NULL)\n \t\t\treference_rev = lookup_commit_reference(sha1);\n \t}\n \tif (!reference_rev)\n@@ -233,7 +234,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\tfree(name);\n \n \t\tname = mkpathdup(fmt, bname.buf);\n-\t\ttarget = resolve_ref_unsafe(name, sha1, 0, &flags);\n+\t\ttarget = resolve_ref_unsafe(name, 0, sha1, &flags);\n \t\tif (!target ||\n \t\t    (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) {\n \t\t\terror(remote_branch\n@@ -296,7 +297,7 @@ static char *resolve_symref(const char *src, const char *prefix)\n \tint flag;\n \tconst char *dst, *cp;\n \n-\tdst = resolve_ref_unsafe(src, sha1, 0, &flag);\n+\tdst = resolve_ref_unsafe(src, 0, sha1, &flag);\n \tif (!(dst && (flag & REF_ISSYMREF)))\n \t\treturn NULL;\n \tif (prefix && (cp = skip_prefix(dst, prefix)))\n@@ -862,7 +863,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \ttrack = git_branch_track;\n \n-\thead = resolve_refdup(\"HEAD\", head_sha1, 0, NULL);\n+\thead = resolve_refdup(\"HEAD\", 0, head_sha1, NULL);\n \tif (!head)\n \t\tdie(_(\"Failed to resolve HEAD as a valid ref.\"));\n \tif (!strcmp(head, \"HEAD\")) {\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex f1dc56e..a5fef2d 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -356,7 +356,7 @@ static int checkout_paths(const struct checkout_opts *opts,\n \t    commit_locked_index(lock_file))\n \t\tdie(_(\"unable to write new index file\"));\n \n-\tread_ref_full(\"HEAD\", rev, 0, &flag);\n+\tread_ref_full(\"HEAD\", 0, rev, &flag);\n \thead = lookup_commit_reference_gently(rev, 1);\n \n \terrs |= post_checkout_hook(head, head, 0);\n@@ -771,7 +771,7 @@ static int switch_branches(const struct checkout_opts *opts,\n \tunsigned char rev[20];\n \tint flag, writeout_error = 0;\n \tmemset(&old, 0, sizeof(old));\n-\told.path = path_to_free = resolve_refdup(\"HEAD\", rev, 0, &flag);\n+\told.path = path_to_free = resolve_refdup(\"HEAD\", 0, rev, &flag);\n \told.commit = lookup_commit_reference_gently(rev, 1);\n \tif (!(flag & REF_ISSYMREF))\n \t\told.path = NULL;\n@@ -1068,7 +1068,7 @@ static int checkout_branch(struct checkout_opts *opts,\n \t\tunsigned char rev[20];\n \t\tint flag;\n \n-\t\tif (!read_ref_full(\"HEAD\", rev, 0, &flag) &&\n+\t\tif (!read_ref_full(\"HEAD\", 0, rev, &flag) &&\n \t\t    (flag & REF_ISSYMREF) && is_null_sha1(rev))\n \t\t\treturn switch_unborn_to_new_branch(opts);\n \t}\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex b12989d..0f5c880 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -622,7 +622,7 @@ static int checkout(void)\n \tif (option_no_checkout)\n \t\treturn 0;\n \n-\thead = resolve_refdup(\"HEAD\", sha1, 1, NULL);\n+\thead = resolve_refdup(\"HEAD\", RESOLVE_REF_READING, sha1, NULL);\n \tif (!head) {\n \t\twarning(_(\"remote HEAD refers to nonexistent ref, \"\n \t\t\t  \"unable to checkout.\\n\"));\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex d23e876..9ccc78b 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1468,7 +1468,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,\n \trev.diffopt.break_opt = 0;\n \tdiff_setup_done(&rev.diffopt);\n \n-\thead = resolve_ref_unsafe(\"HEAD\", junk_sha1, 0, NULL);\n+\thead = resolve_ref_unsafe(\"HEAD\", 0, junk_sha1, NULL);\n \tprintf(\"[%s%s \",\n \t\tstarts_with(head, \"refs/heads/\") ?\n \t\t\thead + 11 :\ndiff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c\nindex 3906eda..afe05dc 100644\n--- a/builtin/fmt-merge-msg.c\n+++ b/builtin/fmt-merge-msg.c\n@@ -602,7 +602,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \n \t/* get current branch */\n \tcurrent_branch = current_branch_to_free =\n-\t\tresolve_refdup(\"HEAD\", head_sha1, 1, NULL);\n+\t\tresolve_refdup(\"HEAD\", RESOLVE_REF_READING, head_sha1, NULL);\n \tif (!current_branch)\n \t\tdie(\"No current branch\");\n \tif (starts_with(current_branch, \"refs/heads/\"))\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex 3e1d5c3..20949b7 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -649,7 +649,8 @@ static void populate_value(struct refinfo *ref)\n \n \tif (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {\n \t\tunsigned char unused1[20];\n-\t\tref->symref = resolve_refdup(ref->refname, unused1, 1, NULL);\n+\t\tref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING,\n+\t\t\t\t\t     unused1, NULL);\n \t\tif (!ref->symref)\n \t\t\tref->symref = \"\";\n \t}\n@@ -707,7 +708,8 @@ static void populate_value(struct refinfo *ref)\n \t\t\tconst char *head;\n \t\t\tunsigned char sha1[20];\n \n-\t\t\thead = resolve_ref_unsafe(\"HEAD\", sha1, 1, NULL);\n+\t\t\thead = resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t\t\t  sha1, NULL);\n \t\t\tif (!strcmp(ref->refname, head))\n \t\t\t\tv->s = \"*\";\n \t\t\telse\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex fc150c8..7cd109a 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -560,7 +560,7 @@ static int fsck_head_link(void)\n \tif (verbose)\n \t\tfprintf(stderr, \"Checking HEAD link\\n\");\n \n-\thead_points_at = resolve_ref_unsafe(\"HEAD\", head_sha1, 0, &flag);\n+\thead_points_at = resolve_ref_unsafe(\"HEAD\", 0, head_sha1, &flag);\n \tif (!head_points_at)\n \t\treturn error(\"Invalid HEAD\");\n \tif (!strcmp(head_points_at, \"HEAD\"))\ndiff --git a/builtin/log.c b/builtin/log.c\nindex a7ba211..493440a 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1395,7 +1395,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \t\tif (check_head) {\n \t\t\tunsigned char sha1[20];\n \t\t\tconst char *ref;\n-\t\t\tref = resolve_ref_unsafe(\"HEAD\", sha1, 1, NULL);\n+\t\t\tref = resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t\t\t sha1, NULL);\n \t\t\tif (ref && starts_with(ref, \"refs/heads/\"))\n \t\t\t\tbranch_name = xstrdup(ref + strlen(\"refs/heads/\"));\n \t\t\telse\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 428ca24..6f56967 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1108,7 +1108,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t * Check if we are _not_ on a detached HEAD, i.e. if there is a\n \t * current branch.\n \t */\n-\tbranch = branch_to_free = resolve_refdup(\"HEAD\", head_sha1, 0, &flag);\n+\tbranch = branch_to_free = resolve_refdup(\"HEAD\", 0, head_sha1, &flag);\n \tif (branch && starts_with(branch, \"refs/heads/\"))\n \t\tbranch += 11;\n \tif (!branch || is_null_sha1(head_sha1))\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 820c341..eaf297d 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -703,7 +703,7 @@ static int merge_commit(struct notes_merge_options *o)\n \tinit_notes(t, \"NOTES_MERGE_PARTIAL\", combine_notes_overwrite, 0);\n \n \to->local_ref = local_ref_to_free =\n-\t\tresolve_refdup(\"NOTES_MERGE_REF\", sha1, 0, NULL);\n+\t\tresolve_refdup(\"NOTES_MERGE_REF\", 0, sha1, NULL);\n \tif (!o->local_ref)\n \t\tdie(\"Failed to resolve NOTES_MERGE_REF\");\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex d1f4cf7..8a6e7e3 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -656,7 +656,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)\n \tint flag;\n \n \tstrbuf_addf(&buf, \"%s%s\", get_git_namespace(), cmd->ref_name);\n-\tdst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag);\n+\tdst_name = resolve_ref_unsafe(buf.buf, 0, sha1, &flag);\n \tstrbuf_release(&buf);\n \n \tif (!(flag & REF_ISSYMREF))\n@@ -817,7 +817,7 @@ static void execute_commands(struct command *commands,\n \tcheck_aliased_updates(commands);\n \n \tfree(head_name_to_free);\n-\thead_name = head_name_to_free = resolve_refdup(\"HEAD\", sha1, 0, NULL);\n+\thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, sha1, NULL);\n \n \tchecked_connectivity = 1;\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 401feb3..c7f82f4 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -568,7 +568,8 @@ static int read_remote_branches(const char *refname,\n \tstrbuf_addf(&buf, \"refs/remotes/%s/\", rename->old);\n \tif (starts_with(refname, buf.buf)) {\n \t\titem = string_list_append(rename->remote_branches, xstrdup(refname));\n-\t\tsymref = resolve_ref_unsafe(refname, orig_sha1, 1, &flag);\n+\t\tsymref = resolve_ref_unsafe(refname, RESOLVE_REF_READING,\n+\t\t\t\t\t    orig_sha1, &flag);\n \t\tif (flag & REF_ISSYMREF)\n \t\t\titem->util = xstrdup(symref);\n \t\telse\n@@ -704,7 +705,7 @@ static int mv(int argc, const char **argv)\n \t\tint flag = 0;\n \t\tunsigned char sha1[20];\n \n-\t\tread_ref_full(item->string, sha1, 1, &flag);\n+\t\tread_ref_full(item->string, RESOLVE_REF_READING, sha1, &flag);\n \t\tif (!(flag & REF_ISSYMREF))\n \t\t\tcontinue;\n \t\tif (delete_ref(item->string, NULL, REF_NODEREF))\ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex d873172..acc8dc1 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -727,7 +727,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)\n \t\tif (ac == 0) {\n \t\t\tstatic const char *fake_av[2];\n \n-\t\t\tfake_av[0] = resolve_refdup(\"HEAD\", sha1, 1, NULL);\n+\t\t\tfake_av[0] = resolve_refdup(\"HEAD\",\n+\t\t\t\t\t\t    RESOLVE_REF_READING,\n+\t\t\t\t\t\t    sha1, NULL);\n \t\t\tfake_av[1] = NULL;\n \t\t\tav = fake_av;\n \t\t\tac = 1;\n@@ -789,7 +791,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)\n \t\t}\n \t}\n \n-\thead_p = resolve_ref_unsafe(\"HEAD\", head_sha1, 1, NULL);\n+\thead_p = resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t    head_sha1, NULL);\n \tif (head_p) {\n \t\thead_len = strlen(head_p);\n \t\tmemcpy(head, head_p, head_len + 1);\ndiff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\nindex b6a711d..29fb3f1 100644\n--- a/builtin/symbolic-ref.c\n+++ b/builtin/symbolic-ref.c\n@@ -13,7 +13,7 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print)\n {\n \tunsigned char sha1[20];\n \tint flag;\n-\tconst char *refname = resolve_ref_unsafe(HEAD, sha1, 0, &flag);\n+\tconst char *refname = resolve_ref_unsafe(HEAD, 0, sha1, &flag);\n \n \tif (!refname)\n \t\tdie(\"No such ref: %s\", HEAD);\ndiff --git a/bundle.c b/bundle.c\nindex 1222952..d92e49c 100644\n--- a/bundle.c\n+++ b/bundle.c\n@@ -311,7 +311,7 @@ int create_bundle(struct bundle_header *header, const char *path,\n \t\t\tcontinue;\n \t\tif (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)\n \t\t\tcontinue;\n-\t\tif (read_ref_full(e->name, sha1, 1, &flag))\n+\t\tif (read_ref_full(e->name, RESOLVE_REF_READING, sha1, &flag))\n \t\t\tflag = 0;\n \t\tdisplay_ref = (flag & REF_ISSYMREF) ? e->name : ref;\n \ndiff --git a/cache.h b/cache.h\nindex e7ec626..5b54911 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -947,8 +947,8 @@ extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *);\n extern int get_sha1_hex(const char *hex, unsigned char *sha1);\n \n extern char *sha1_to_hex(const unsigned char *sha1);\t/* static buffer result! */\n-extern int read_ref_full(const char *refname, unsigned char *sha1,\n-\t\t\t int reading, int *flags);\n+extern int read_ref_full(const char *refname, int resolve_flags,\n+\t\t\t unsigned char *sha1, int *flags);\n extern int read_ref(const char *refname, unsigned char *sha1);\n \n /*\n@@ -960,20 +960,20 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n  * or the input ref.\n  *\n  * If the reference cannot be resolved to an object, the behavior\n- * depends on the \"reading\" argument:\n+ * depends on the RESOLVE_REF_READING flag:\n  *\n- * - If reading is set, return NULL.\n+ * - If RESOLVE_REF_READING is set, return NULL.\n  *\n- * - If reading is not set, clear sha1 and return the name of the last\n- *   reference name in the chain, which will either be a non-symbolic\n+ * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of\n+ *   the last reference name in the chain, which will either be a non-symbolic\n  *   reference or an undefined reference.  If this is a prelude to\n  *   \"writing\" to the ref, the return value is the name of the ref\n  *   that will actually be created or changed.\n  *\n- * If flag is non-NULL, set the value that it points to the\n+ * If flags is non-NULL, set the value that it points to the\n  * combination of REF_ISPACKED (if the reference was found among the\n- * packed references) and REF_ISSYMREF (if the initial reference was a\n- * symbolic reference).\n+ * packed references), REF_ISSYMREF (if the initial reference was a\n+ * symbolic reference) and REF_ISBROKEN (if the ref is malformed).\n  *\n  * If ref is not a properly-formatted, normalized reference, return\n  * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,\n@@ -981,8 +981,9 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n  *\n  * errno is set to something meaningful on error.\n  */\n-extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag);\n-extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag);\n+#define RESOLVE_REF_READING 0x01\n+extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);\n+extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);\n \n extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);\n extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);\ndiff --git a/http-backend.c b/http-backend.c\nindex d2c0a62..e172886 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -417,7 +417,9 @@ static int show_head_ref(const char *refname, const unsigned char *sha1,\n \n \tif (flag & REF_ISSYMREF) {\n \t\tunsigned char unused[20];\n-\t\tconst char *target = resolve_ref_unsafe(refname, unused, 1, NULL);\n+\t\tconst char *target = resolve_ref_unsafe(refname,\n+\t\t\t\t\t\t\tRESOLVE_REF_READING,\n+\t\t\t\t\t\t\tunused, NULL);\n \t\tconst char *target_nons = strip_namespace(target);\n \n \t\tstrbuf_addf(buf, \"ref: %s\\n\", target_nons);\ndiff --git a/notes-merge.c b/notes-merge.c\nindex 94a1a8a..3c88d17 100644\n--- a/notes-merge.c\n+++ b/notes-merge.c\n@@ -549,7 +549,7 @@ int notes_merge(struct notes_merge_options *o,\n \t       o->local_ref, o->remote_ref);\n \n \t/* Dereference o->local_ref into local_sha1 */\n-\tif (read_ref_full(o->local_ref, local_sha1, 0, NULL))\n+\tif (read_ref_full(o->local_ref, 0, local_sha1, NULL))\n \t\tdie(\"Failed to resolve local notes ref '%s'\", o->local_ref);\n \telse if (!check_refname_format(o->local_ref, 0) &&\n \t\tis_null_sha1(local_sha1))\ndiff --git a/reflog-walk.c b/reflog-walk.c\nindex 9ce8b53..23345ea 100644\n--- a/reflog-walk.c\n+++ b/reflog-walk.c\n@@ -48,7 +48,8 @@ static struct complete_reflogs *read_complete_reflog(const char *ref)\n \t\tunsigned char sha1[20];\n \t\tconst char *name;\n \t\tvoid *name_to_free;\n-\t\tname = name_to_free = resolve_refdup(ref, sha1, 1, NULL);\n+\t\tname = name_to_free = resolve_refdup(ref, RESOLVE_REF_READING,\n+\t\t\t\t\t\t     sha1, NULL);\n \t\tif (name) {\n \t\t\tfor_each_reflog_ent(name, read_one_reflog, reflogs);\n \t\t\tfree(name_to_free);\n@@ -174,7 +175,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info,\n \t\tif (*branch == '\\0') {\n \t\t\tunsigned char sha1[20];\n \t\t\tfree(branch);\n-\t\t\tbranch = resolve_refdup(\"HEAD\", sha1, 0, NULL);\n+\t\t\tbranch = resolve_refdup(\"HEAD\", 0, sha1, NULL);\n \t\t\tif (!branch)\n \t\t\t\tdie (\"No current branch\");\n \ndiff --git a/refs.c b/refs.c\nindex f596a9f..4916d16 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1191,7 +1191,9 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)\n \t\t\t\t\thashclr(sha1);\n \t\t\t\t\tflag |= REF_ISBROKEN;\n \t\t\t\t}\n-\t\t\t} else if (read_ref_full(refname.buf, sha1, 1, &flag)) {\n+\t\t\t} else if (read_ref_full(refname.buf,\n+\t\t\t\t\t\t RESOLVE_REF_READING,\n+\t\t\t\t\t\t sha1, &flag)) {\n \t\t\t\thashclr(sha1);\n \t\t\t\tflag |= REF_ISBROKEN;\n \t\t\t}\n@@ -1316,9 +1318,9 @@ static struct ref_entry *get_packed_ref(const char *refname)\n  * options are forwarded from resolve_safe_unsafe().\n  */\n static const char *handle_missing_loose_ref(const char *refname,\n+\t\t\t\t\t    int resolve_flags,\n \t\t\t\t\t    unsigned char *sha1,\n-\t\t\t\t\t    int reading,\n-\t\t\t\t\t    int *flag)\n+\t\t\t\t\t    int *flags)\n {\n \tstruct ref_entry *entry;\n \n@@ -1329,12 +1331,12 @@ static const char *handle_missing_loose_ref(const char *refname,\n \tentry = get_packed_ref(refname);\n \tif (entry) {\n \t\thashcpy(sha1, entry->u.value.sha1);\n-\t\tif (flag)\n-\t\t\t*flag |= REF_ISPACKED;\n+\t\tif (flags)\n+\t\t\t*flags |= REF_ISPACKED;\n \t\treturn refname;\n \t}\n \t/* The reference is not a packed reference, either. */\n-\tif (reading) {\n+\tif (resolve_flags & RESOLVE_REF_READING) {\n \t\treturn NULL;\n \t} else {\n \t\thashclr(sha1);\n@@ -1343,21 +1345,20 @@ static const char *handle_missing_loose_ref(const char *refname,\n }\n \n /* This function needs to return a meaningful errno on failure */\n-const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)\n+const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)\n {\n \tint depth = MAXDEPTH;\n \tssize_t len;\n \tchar buffer[256];\n \tstatic char refname_buffer[256];\n \n-\tif (flag)\n-\t\t*flag = 0;\n+\tif (flags)\n+\t\t*flags = 0;\n \n \tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n \t\terrno = EINVAL;\n \t\treturn NULL;\n \t}\n-\n \tfor (;;) {\n \t\tchar path[PATH_MAX];\n \t\tstruct stat st;\n@@ -1383,8 +1384,8 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \tstat_ref:\n \t\tif (lstat(path, &st) < 0) {\n \t\t\tif (errno == ENOENT)\n-\t\t\t\treturn handle_missing_loose_ref(refname, sha1,\n-\t\t\t\t\t\t\t\treading, flag);\n+\t\t\t\treturn handle_missing_loose_ref(refname,\n+\t\t\t\t\t\tresolve_flags, sha1, flags);\n \t\t\telse\n \t\t\t\treturn NULL;\n \t\t}\n@@ -1404,8 +1405,8 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\t\t\t\t!check_refname_format(buffer, 0)) {\n \t\t\t\tstrcpy(refname_buffer, buffer);\n \t\t\t\trefname = refname_buffer;\n-\t\t\t\tif (flag)\n-\t\t\t\t\t*flag |= REF_ISSYMREF;\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISSYMREF;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t}\n@@ -1450,21 +1451,21 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t\t\t */\n \t\t\tif (get_sha1_hex(buffer, sha1) ||\n \t\t\t    (buffer[40] != '\\0' && !isspace(buffer[40]))) {\n-\t\t\t\tif (flag)\n-\t\t\t\t\t*flag |= REF_ISBROKEN;\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISBROKEN;\n \t\t\t\terrno = EINVAL;\n \t\t\t\treturn NULL;\n \t\t\t}\n \t\t\treturn refname;\n \t\t}\n-\t\tif (flag)\n-\t\t\t*flag |= REF_ISSYMREF;\n+\t\tif (flags)\n+\t\t\t*flags |= REF_ISSYMREF;\n \t\tbuf = buffer + 4;\n \t\twhile (isspace(*buf))\n \t\t\tbuf++;\n \t\tif (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {\n-\t\t\tif (flag)\n-\t\t\t\t*flag |= REF_ISBROKEN;\n+\t\t\tif (flags)\n+\t\t\t\t*flags |= REF_ISBROKEN;\n \t\t\terrno = EINVAL;\n \t\t\treturn NULL;\n \t\t}\n@@ -1472,9 +1473,9 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea\n \t}\n }\n \n-char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)\n+char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags)\n {\n-\tconst char *ret = resolve_ref_unsafe(ref, sha1, reading, flag);\n+\tconst char *ret = resolve_ref_unsafe(ref, resolve_flags, sha1, flags);\n \treturn ret ? xstrdup(ret) : NULL;\n }\n \n@@ -1485,22 +1486,22 @@ struct ref_filter {\n \tvoid *cb_data;\n };\n \n-int read_ref_full(const char *refname, unsigned char *sha1, int reading, int *flags)\n+int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)\n {\n-\tif (resolve_ref_unsafe(refname, sha1, reading, flags))\n+\tif (resolve_ref_unsafe(refname, resolve_flags, sha1, flags))\n \t\treturn 0;\n \treturn -1;\n }\n \n int read_ref(const char *refname, unsigned char *sha1)\n {\n-\treturn read_ref_full(refname, sha1, 1, NULL);\n+\treturn read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL);\n }\n \n int ref_exists(const char *refname)\n {\n \tunsigned char sha1[20];\n-\treturn !!resolve_ref_unsafe(refname, sha1, 1, NULL);\n+\treturn !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);\n }\n \n static int filter_refs(const char *refname, const unsigned char *sha1, int flags,\n@@ -1614,7 +1615,7 @@ int peel_ref(const char *refname, unsigned char *sha1)\n \t\treturn 0;\n \t}\n \n-\tif (read_ref_full(refname, base, 1, &flag))\n+\tif (read_ref_full(refname, RESOLVE_REF_READING, base, &flag))\n \t\treturn -1;\n \n \t/*\n@@ -1655,7 +1656,7 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha\n \tif (!(flags & REF_ISSYMREF))\n \t\treturn 0;\n \n-\tresolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);\n+\tresolves_to = resolve_ref_unsafe(refname, 0, junk, NULL);\n \tif (!resolves_to\n \t    || (d->refname\n \t\t? strcmp(resolves_to, d->refname)\n@@ -1780,7 +1781,7 @@ static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)\n \t\treturn 0;\n \t}\n \n-\tif (!read_ref_full(\"HEAD\", sha1, 1, &flag))\n+\tif (!read_ref_full(\"HEAD\", RESOLVE_REF_READING, sha1, &flag))\n \t\treturn fn(\"HEAD\", sha1, flag, cb_data);\n \n \treturn 0;\n@@ -1860,7 +1861,7 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)\n \tint flag;\n \n \tstrbuf_addf(&buf, \"%sHEAD\", get_git_namespace());\n-\tif (!read_ref_full(buf.buf, sha1, 1, &flag))\n+\tif (!read_ref_full(buf.buf, RESOLVE_REF_READING, sha1, &flag))\n \t\tret = fn(buf.buf, sha1, flag, cb_data);\n \tstrbuf_release(&buf);\n \n@@ -1955,7 +1956,9 @@ int refname_match(const char *abbrev_name, const char *full_name)\n static struct ref_lock *verify_lock(struct ref_lock *lock,\n \tconst unsigned char *old_sha1, int mustexist)\n {\n-\tif (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) {\n+\tif (read_ref_full(lock->ref_name,\n+\t\t\t  mustexist ? RESOLVE_REF_READING : 0,\n+\t\t\t  lock->old_sha1, NULL)) {\n \t\tint save_errno = errno;\n \t\terror(\"Can't verify ref %s\", lock->ref_name);\n \t\tunlock_ref(lock);\n@@ -2028,7 +2031,8 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)\n \n \t\tthis_result = refs_found ? sha1_from_ref : sha1;\n \t\tmksnpath(fullref, sizeof(fullref), *p, len, str);\n-\t\tr = resolve_ref_unsafe(fullref, this_result, 1, &flag);\n+\t\tr = resolve_ref_unsafe(fullref, RESOLVE_REF_READING,\n+\t\t\t\t       this_result, &flag);\n \t\tif (r) {\n \t\t\tif (!refs_found++)\n \t\t\t\t*ref = xstrdup(r);\n@@ -2057,7 +2061,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n \t\tconst char *ref, *it;\n \n \t\tmksnpath(path, sizeof(path), *p, len, str);\n-\t\tref = resolve_ref_unsafe(path, hash, 1, NULL);\n+\t\tref = resolve_ref_unsafe(path, RESOLVE_REF_READING,\n+\t\t\t\t\t hash, NULL);\n \t\tif (!ref)\n \t\t\tcontinue;\n \t\tif (reflog_exists(path))\n@@ -2092,6 +2097,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \tint last_errno = 0;\n \tint type, lflags;\n \tint mustexist = (old_sha1 && !is_null_sha1(old_sha1));\n+\tint resolve_flags = 0;\n \tint missing = 0;\n \tint attempts_remaining = 3;\n \n@@ -2103,7 +2109,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \tlock = xcalloc(1, sizeof(struct ref_lock));\n \tlock->lock_fd = -1;\n \n-\trefname = resolve_ref_unsafe(refname, lock->old_sha1, mustexist, &type);\n+\tif (mustexist)\n+\t\tresolve_flags |= RESOLVE_REF_READING;\n+\n+\trefname = resolve_ref_unsafe(refname, resolve_flags,\n+\t\t\t\t     lock->old_sha1, &type);\n \tif (!refname && errno == EISDIR) {\n \t\t/* we are trying to lock foo but we used to\n \t\t * have foo/bar which now does not exist;\n@@ -2116,7 +2126,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\terror(\"there are still refs under '%s'\", orig_refname);\n \t\t\tgoto error_return;\n \t\t}\n-\t\trefname = resolve_ref_unsafe(orig_refname, lock->old_sha1, mustexist, &type);\n+\t\trefname = resolve_ref_unsafe(orig_refname, resolve_flags,\n+\t\t\t\t\t     lock->old_sha1, &type);\n \t}\n \tif (type_p)\n \t    *type_p = type;\n@@ -2469,7 +2480,7 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)\n \t\tunsigned char sha1[20];\n \t\tint flags;\n \n-\t\tif (read_ref_full(entry->name, sha1, 0, &flags))\n+\t\tif (read_ref_full(entry->name, 0, sha1, &flags))\n \t\t\t/* We should at least have found the packed ref. */\n \t\t\tdie(\"Internal error\");\n \t\tif ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED)) {\n@@ -2661,7 +2672,8 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \tif (log && S_ISLNK(loginfo.st_mode))\n \t\treturn error(\"reflog for %s is a symlink\", oldrefname);\n \n-\tsymref = resolve_ref_unsafe(oldrefname, orig_sha1, 1, &flag);\n+\tsymref = resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING,\n+\t\t\t\t    orig_sha1, &flag);\n \tif (flag & REF_ISSYMREF)\n \t\treturn error(\"refname %s is a symbolic ref, renaming it is not supported\",\n \t\t\toldrefname);\n@@ -2690,7 +2702,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \t\tgoto rollback;\n \t}\n \n-\tif (!read_ref_full(newrefname, sha1, 1, NULL) &&\n+\tif (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) &&\n \t    delete_ref(newrefname, sha1, REF_NODEREF)) {\n \t\tif (errno==EISDIR) {\n \t\t\tif (remove_empty_directories(git_path(\"%s\", newrefname))) {\n@@ -2968,7 +2980,8 @@ static int write_ref_sha1(struct ref_lock *lock,\n \t\tunsigned char head_sha1[20];\n \t\tint head_flag;\n \t\tconst char *head_ref;\n-\t\thead_ref = resolve_ref_unsafe(\"HEAD\", head_sha1, 1, &head_flag);\n+\t\thead_ref = resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING,\n+\t\t\t\t\t      head_sha1, &head_flag);\n \t\tif (head_ref && (head_flag & REF_ISSYMREF) &&\n \t\t    !strcmp(head_ref, lock->ref_name))\n \t\t\tlog_ref_write(\"HEAD\", lock->old_sha1, sha1, logmsg);\n@@ -3335,7 +3348,7 @@ static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data\n \t\t\t\tretval = do_for_each_reflog(name, fn, cb_data);\n \t\t\t} else {\n \t\t\t\tunsigned char sha1[20];\n-\t\t\t\tif (read_ref_full(name->buf, sha1, 0, NULL))\n+\t\t\t\tif (read_ref_full(name->buf, 0, sha1, NULL))\n \t\t\t\t\tretval = error(\"bad ref for %s\", name->buf);\n \t\t\t\telse\n \t\t\t\t\tretval = fn(name->buf, sha1, 0, cb_data);\ndiff --git a/remote.c b/remote.c\nindex 0e9459c..25b07ac 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -486,7 +486,7 @@ static void read_config(void)\n \t\treturn;\n \tdefault_remote_name = \"origin\";\n \tcurrent_branch = NULL;\n-\thead_ref = resolve_ref_unsafe(\"HEAD\", sha1, 0, &flag);\n+\thead_ref = resolve_ref_unsafe(\"HEAD\", 0, sha1, &flag);\n \tif (head_ref && (flag & REF_ISSYMREF) &&\n \t    starts_with(head_ref, \"refs/heads/\")) {\n \t\tcurrent_branch =\n@@ -1121,7 +1121,8 @@ static char *guess_ref(const char *name, struct ref *peer)\n \tstruct strbuf buf = STRBUF_INIT;\n \tunsigned char sha1[20];\n \n-\tconst char *r = resolve_ref_unsafe(peer->name, sha1, 1, NULL);\n+\tconst char *r = resolve_ref_unsafe(peer->name, RESOLVE_REF_READING,\n+\t\t\t\t\t   sha1, NULL);\n \tif (!r)\n \t\treturn NULL;\n \n@@ -1182,7 +1183,9 @@ static int match_explicit(struct ref *src, struct ref *dst,\n \t\tunsigned char sha1[20];\n \t\tint flag;\n \n-\t\tdst_value = resolve_ref_unsafe(matched_src->name, sha1, 1, &flag);\n+\t\tdst_value = resolve_ref_unsafe(matched_src->name,\n+\t\t\t\t\t       RESOLVE_REF_READING,\n+\t\t\t\t\t       sha1, &flag);\n \t\tif (!dst_value ||\n \t\t    ((flag & REF_ISSYMREF) &&\n \t\t     !starts_with(dst_value, \"refs/heads/\")))\n@@ -1656,7 +1659,7 @@ static int ignore_symref_update(const char *refname)\n \tunsigned char sha1[20];\n \tint flag;\n \n-\tif (!resolve_ref_unsafe(refname, sha1, 0, &flag))\n+\tif (!resolve_ref_unsafe(refname, 0, sha1, &flag))\n \t\treturn 0; /* non-existing refs are OK */\n \treturn (flag & REF_ISSYMREF);\n }\ndiff --git a/sequencer.c b/sequencer.c\nindex c5b7b8a..70fb7a8 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -366,7 +366,7 @@ static int is_index_unchanged(void)\n \tunsigned char head_sha1[20];\n \tstruct commit *head_commit;\n \n-\tif (!resolve_ref_unsafe(\"HEAD\", head_sha1, 1, NULL))\n+\tif (!resolve_ref_unsafe(\"HEAD\", RESOLVE_REF_READING, head_sha1, NULL))\n \t\treturn error(_(\"Could not resolve HEAD commit\\n\"));\n \n \thead_commit = lookup_commit(head_sha1);\n@@ -912,7 +912,7 @@ static int rollback_single_pick(void)\n \tif (!file_exists(git_path(\"CHERRY_PICK_HEAD\")) &&\n \t    !file_exists(git_path(\"REVERT_HEAD\")))\n \t\treturn error(_(\"no cherry-pick or revert in progress\"));\n-\tif (read_ref_full(\"HEAD\", head_sha1, 0, NULL))\n+\tif (read_ref_full(\"HEAD\", 0, head_sha1, NULL))\n \t\treturn error(_(\"cannot resolve HEAD\"));\n \tif (is_null_sha1(head_sha1))\n \t\treturn error(_(\"cannot abort from a branch yet to be born\"));\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 84c616f..3497a5e 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -889,7 +889,10 @@ static int push_refs_with_export(struct transport *transport,\n \t\t\t\t\tint flag;\n \n \t\t\t\t\t/* Follow symbolic refs (mainly for HEAD). */\n-\t\t\t\t\tname = resolve_ref_unsafe(ref->peer_ref->name, sha1, 1, &flag);\n+\t\t\t\t\tname = resolve_ref_unsafe(\n+\t\t\t\t\t\t ref->peer_ref->name,\n+\t\t\t\t\t\t RESOLVE_REF_READING,\n+\t\t\t\t\t\t sha1, &flag);\n \t\t\t\t\tif (!name || !(flag & REF_ISSYMREF))\n \t\t\t\t\t\tname = ref->peer_ref->name;\n \ndiff --git a/transport.c b/transport.c\nindex 325f03e..76e0a9a 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -168,7 +168,8 @@ static void set_upstreams(struct transport *transport, struct ref *refs,\n \t\t/* Follow symbolic refs (mainly for HEAD). */\n \t\tlocalname = ref->peer_ref->name;\n \t\tremotename = ref->name;\n-\t\ttmp = resolve_ref_unsafe(localname, sha, 1, &flag);\n+\t\ttmp = resolve_ref_unsafe(localname, RESOLVE_REF_READING,\n+\t\t\t\t\t sha, &flag);\n \t\tif (tmp && flag & REF_ISSYMREF &&\n \t\t\tstarts_with(tmp, \"refs/heads/\"))\n \t\t\tlocalname = tmp;\n@@ -753,7 +754,7 @@ void transport_print_push_status(const char *dest, struct ref *refs,\n \tunsigned char head_sha1[20];\n \tchar *head;\n \n-\thead = resolve_refdup(\"HEAD\", head_sha1, 1, NULL);\n+\thead = resolve_refdup(\"HEAD\", RESOLVE_REF_READING, head_sha1, NULL);\n \n \tif (verbose) {\n \t\tfor (ref = refs; ref; ref = ref->next)\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 01de944..4542565 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -743,7 +743,7 @@ static int find_symref(const char *refname, const unsigned char *sha1, int flag,\n \n \tif ((flag & REF_ISSYMREF) == 0)\n \t\treturn 0;\n-\tsymref_target = resolve_ref_unsafe(refname, unused, 0, &flag);\n+\tsymref_target = resolve_ref_unsafe(refname, 0, unused, &flag);\n \tif (!symref_target || (flag & REF_ISSYMREF) == 0)\n \t\tdie(\"'%s' is a symref but it is not?\", refname);\n \titem = string_list_append(cb_data, refname);\ndiff --git a/wt-status.c b/wt-status.c\nindex 318a191..c3cbf50 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -128,7 +128,7 @@ void wt_status_prepare(struct wt_status *s)\n \ts->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;\n \ts->use_color = -1;\n \ts->relative_paths = 1;\n-\ts->branch = resolve_refdup(\"HEAD\", sha1, 0, NULL);\n+\ts->branch = resolve_refdup(\"HEAD\", 0, sha1, NULL);\n \ts->reference = \"HEAD\";\n \ts->fp = stdout;\n \ts->index_file = get_index_file();\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250148","messageId":"20141002021057.GG1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 14/24] reflog test: test interaction with detached HEAD","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:10:57Z","receivedAt":"2014-10-02T02:10:57Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nA proposed patch produced broken HEAD reflog entries when checking out\nanything other than a branch.  The testsuite still passed, so it took\na few days for the bug to be noticed.\n\nAdd tests checking the content of the reflog after detaching and\nreattaching HEAD so we don't have to rely on manual testing to catch\nsuch problems in the future.\n\n[jn: using 'log -g --format=%H' instead of parsing --oneline output,\n resetting state in each test so they can be safely reordered or\n skipped]\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Ronnie Sahlberg <sahlberg@google.com>\n---\nNew since v21.  Thanks to Junio for noticing the bug.\n\n t/t1413-reflog-detach.sh | 70 ++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 70 insertions(+)\n create mode 100755 t/t1413-reflog-detach.sh\n\ndiff --git a/t/t1413-reflog-detach.sh b/t/t1413-reflog-detach.sh\nnew file mode 100755\nindex 0000000..c730600\n--- /dev/null\n+++ b/t/t1413-reflog-detach.sh\n@@ -0,0 +1,70 @@\n+#!/bin/sh\n+\n+test_description='Test reflog interaction with detached HEAD'\n+. ./test-lib.sh\n+\n+reset_state () {\n+\tgit checkout master &&\n+\tcp saved_reflog .git/logs/HEAD\n+}\n+\n+test_expect_success setup '\n+\ttest_tick &&\n+\tgit commit --allow-empty -m initial &&\n+\tgit branch side &&\n+\ttest_tick &&\n+\tgit commit --allow-empty -m second &&\n+\tcat .git/logs/HEAD >saved_reflog\n+'\n+\n+test_expect_success baseline '\n+\treset_state &&\n+\tgit rev-parse master master^ >expect &&\n+\tgit log -g --format=%H >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'switch to branch' '\n+\treset_state &&\n+\tgit rev-parse side master master^ >expect &&\n+\tgit checkout side &&\n+\tgit log -g --format=%H >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'detach to other' '\n+\treset_state &&\n+\tgit rev-parse master side master master^ >expect &&\n+\tgit checkout side &&\n+\tgit checkout master^0 &&\n+\tgit log -g --format=%H >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'detach to self' '\n+\treset_state &&\n+\tgit rev-parse master master master^ >expect &&\n+\tgit checkout master^0 &&\n+\tgit log -g --format=%H >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'attach to self' '\n+\treset_state &&\n+\tgit rev-parse master master master master^ >expect &&\n+\tgit checkout master^0 &&\n+\tgit checkout master &&\n+\tgit log -g --format=%H >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'attach to other' '\n+\treset_state &&\n+\tgit rev-parse side master master master^ >expect &&\n+\tgit checkout master^0 &&\n+\tgit checkout side &&\n+\tgit log -g --format=%H >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250149","messageId":"20141002021510.GH1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 15/24] branch -d: avoid repeated symref resolution","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:15:10Z","receivedAt":"2014-10-02T02:15:10Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Jonathan Nieder <jrnieder@gmail.com>\nDate: Wed, 10 Sep 2014 18:22:48 -0700\n\nIf a repository gets in a broken state with too much symref nesting,\nit cannot be repaired with \"git branch -d\":\n\n $ git symbolic-ref refs/heads/nonsense refs/heads/nonsense\n $ git branch -d nonsense\n error: branch 'nonsense' not found.\n\nWorse, \"git update-ref --no-deref -d\" doesn't work for such repairs\neither:\n\n $ git update-ref -d refs/heads/nonsense\n error: unable to resolve reference refs/heads/nonsense: Too many levels of symbolic links\n\nFix both by teaching resolve_ref_unsafe a new RESOLVE_REF_NO_RECURSE\nflag and passing it when appropriate.\n\nCallers can still read the value of a symref (for example to print a\nmessage about it) with that flag set --- resolve_ref_unsafe will\nresolve one level of symrefs and stop there.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Ronnie Sahlberg <sahlberg@google.com>\n---\nSince v21:\n- renamed flag from RESOLVE_REF_NO_DEREF to _NO_RECURSE\n- more detail in API docs\n- only set NO_RECURSE when deleting refs.  Locking refs for non-deletion\n  updates needs to recurse to get old_sha1 for the reflog.\n- add more tests (for symrefs to refs with bad names, which should also\n  be deletable now)\n\n builtin/branch.c      |  3 ++-\n cache.h               |  6 ++++++\n refs.c                | 17 +++++++++++++++--\n refs.h                |  2 ++\n t/t1400-update-ref.sh | 34 ++++++++++++++++++++++++++++++++++\n t/t3200-branch.sh     |  9 +++++++++\n 6 files changed, 68 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex e5d1377..a334380 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -234,7 +234,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\tfree(name);\n \n \t\tname = mkpathdup(fmt, bname.buf);\n-\t\ttarget = resolve_ref_unsafe(name, 0, sha1, &flags);\n+\t\ttarget = resolve_ref_unsafe(name, RESOLVE_REF_NO_RECURSE,\n+\t\t\t\t\t    sha1, &flags);\n \t\tif (!target ||\n \t\t    (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) {\n \t\t\terror(remote_branch\ndiff --git a/cache.h b/cache.h\nindex 5b54911..5ca7f2b 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -970,6 +970,11 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n  *   \"writing\" to the ref, the return value is the name of the ref\n  *   that will actually be created or changed.\n  *\n+ * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one\n+ * level of symbolic reference.  The value stored in sha1 for a symbolic\n+ * reference will always be null_sha1 in this case, and the return\n+ * value is the reference that the symref refers to directly.\n+ *\n  * If flags is non-NULL, set the value that it points to the\n  * combination of REF_ISPACKED (if the reference was found among the\n  * packed references), REF_ISSYMREF (if the initial reference was a\n@@ -982,6 +987,7 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n  * errno is set to something meaningful on error.\n  */\n #define RESOLVE_REF_READING 0x01\n+#define RESOLVE_REF_NO_RECURSE 0x02\n extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);\n extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);\n \ndiff --git a/refs.c b/refs.c\nindex 4916d16..490e788 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1407,6 +1407,10 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned\n \t\t\t\trefname = refname_buffer;\n \t\t\t\tif (flags)\n \t\t\t\t\t*flags |= REF_ISSYMREF;\n+\t\t\t\tif (resolve_flags & RESOLVE_REF_NO_RECURSE) {\n+\t\t\t\t\thashclr(sha1);\n+\t\t\t\t\treturn refname;\n+\t\t\t\t}\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t}\n@@ -1463,13 +1467,17 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned\n \t\tbuf = buffer + 4;\n \t\twhile (isspace(*buf))\n \t\t\tbuf++;\n+\t\trefname = strcpy(refname_buffer, buf);\n+\t\tif (resolve_flags & RESOLVE_REF_NO_RECURSE) {\n+\t\t\thashclr(sha1);\n+\t\t\treturn refname;\n+\t\t}\n \t\tif (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {\n \t\t\tif (flags)\n \t\t\t\t*flags |= REF_ISBROKEN;\n \t\t\terrno = EINVAL;\n \t\t\treturn NULL;\n \t\t}\n-\t\trefname = strcpy(refname_buffer, buf);\n \t}\n }\n \n@@ -2111,6 +2119,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \n \tif (mustexist)\n \t\tresolve_flags |= RESOLVE_REF_READING;\n+\tif (flags & REF_NODEREF && flags & REF_DELETING)\n+\t\tresolve_flags |= RESOLVE_REF_NO_RECURSE;\n \n \trefname = resolve_ref_unsafe(refname, resolve_flags,\n \t\t\t\t     lock->old_sha1, &type);\n@@ -3610,13 +3620,16 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t/* Acquire all locks while verifying old values */\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n+\t\tint flags = update->flags;\n \n+\t\tif (is_null_sha1(update->new_sha1))\n+\t\t\tflags |= REF_DELETING;\n \t\tupdate->lock = lock_ref_sha1_basic(update->refname,\n \t\t\t\t\t\t   (update->have_old ?\n \t\t\t\t\t\t    update->old_sha1 :\n \t\t\t\t\t\t    NULL),\n \t\t\t\t\t\t   NULL,\n-\t\t\t\t\t\t   update->flags,\n+\t\t\t\t\t\t   flags,\n \t\t\t\t\t\t   &update->type);\n \t\tif (!update->lock) {\n \t\t\tret = (errno == ENOTDIR)\ndiff --git a/refs.h b/refs.h\nindex 3bb16db..b62c4c4 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -175,10 +175,12 @@ extern int peel_ref(const char *refname, unsigned char *sha1);\n  * ref_transaction_create(), etc.\n  * REF_NODEREF: act on the ref directly, instead of dereferencing\n  *              symbolic references.\n+ * REF_DELETING: tolerate broken refs\n  *\n  * Flags >= 0x100 are reserved for internal use.\n  */\n #define REF_NODEREF\t0x01\n+#define REF_DELETING\t0x02\n /*\n  * This function sets errno to something meaningful on failure.\n  */\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 0218e96..7c8c41a 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -110,6 +110,40 @@ test_expect_success \"delete symref without dereference when the referred ref is\n cp -f .git/HEAD.orig .git/HEAD\n git update-ref -d $m\n \n+test_expect_success 'update-ref -d is not confused by self-reference' '\n+\tgit symbolic-ref refs/heads/self refs/heads/self &&\n+\ttest_when_finished \"rm -f .git/refs/heads/self\" &&\n+\ttest_path_is_file .git/refs/heads/self &&\n+\ttest_must_fail git update-ref -d refs/heads/self &&\n+\ttest_path_is_file .git/refs/heads/self\n+'\n+\n+test_expect_success 'update-ref --no-deref -d can delete self-reference' '\n+\tgit symbolic-ref refs/heads/self refs/heads/self &&\n+\ttest_when_finished \"rm -f .git/refs/heads/self\" &&\n+\ttest_path_is_file .git/refs/heads/self &&\n+\tgit update-ref --no-deref -d refs/heads/self &&\n+\ttest_path_is_missing .git/refs/heads/self\n+'\n+\n+test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' '\n+\t>.git/refs/heads/bad &&\n+\ttest_when_finished \"rm -f .git/refs/heads/bad\" &&\n+\tgit symbolic-ref refs/heads/ref-to-bad refs/heads/bad &&\n+\ttest_when_finished \"rm -f .git/refs/heads/ref-to-bad\" &&\n+\ttest_path_is_file .git/refs/heads/ref-to-bad &&\n+\tgit update-ref --no-deref -d refs/heads/ref-to-bad &&\n+\ttest_path_is_missing .git/refs/heads/ref-to-bad\n+'\n+\n+test_expect_success 'update-ref --no-deref -d can delete reference to broken name' '\n+\tgit symbolic-ref refs/heads/badname refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/badname\" &&\n+\ttest_path_is_file .git/refs/heads/badname &&\n+\tgit update-ref --no-deref -d refs/heads/badname &&\n+\ttest_path_is_missing .git/refs/heads/badname\n+'\n+\n test_expect_success '(not) create HEAD with old sha1' \"\n \ttest_must_fail git update-ref HEAD $A $B\n \"\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex ac31b71..432921b 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -285,6 +285,15 @@ test_expect_success 'deleting a dangling symref' '\n \ttest_i18ncmp expect actual\n '\n \n+test_expect_success 'deleting a self-referential symref' '\n+\tgit symbolic-ref refs/heads/self-reference refs/heads/self-reference &&\n+\ttest_path_is_file .git/refs/heads/self-reference &&\n+\techo \"Deleted branch self-reference (was refs/heads/self-reference).\" >expect &&\n+\tgit branch -d self-reference >actual &&\n+\ttest_path_is_missing .git/refs/heads/self-reference &&\n+\ttest_i18ncmp expect actual\n+'\n+\n test_expect_success 'renaming a symref is not allowed' '\n \tgit symbolic-ref refs/heads/master2 refs/heads/master &&\n \ttest_must_fail git branch -m master2 master3 &&\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250150","messageId":"20141002021554.GI1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 16/24] branch -d: simplify by using RESOLVE_REF_READING flag","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:15:54Z","receivedAt":"2014-10-02T02:15:54Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 11 Sep 2014 10:34:36 -0700\n\nWhen \"git branch -d\" reads the branch it is about to delete, it used\nto avoid passing the RESOLVE_REF_READING ('treat missing ref as\nerror') flag because a symref pointing to a nonexistent ref would show\nup as missing instead of as something that could be deleted.  To check\nif a ref is actually missing, we then check\n\n - is it a symref?\n - if not, did it resolve to null_sha1?\n\nNow we pass RESOLVE_REF_NO_RECURSE and the correct information is\nreturned for a symref even when it points to a missing ref.  Simplify\nby relying on RESOLVE_REF_READING.\n\nNo functional change intended.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nSplit out from the 'fix handling of badly named refs' patch.\n\n builtin/branch.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex a334380..a0c5601 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -234,10 +234,11 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\tfree(name);\n \n \t\tname = mkpathdup(fmt, bname.buf);\n-\t\ttarget = resolve_ref_unsafe(name, RESOLVE_REF_NO_RECURSE,\n+\t\ttarget = resolve_ref_unsafe(name,\n+\t\t\t\t\t    RESOLVE_REF_READING\n+\t\t\t\t\t    | RESOLVE_REF_NO_RECURSE,\n \t\t\t\t\t    sha1, &flags);\n-\t\tif (!target ||\n-\t\t    (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) {\n+\t\tif (!target) {\n \t\t\terror(remote_branch\n \t\t\t      ? _(\"remote branch '%s' not found.\")\n \t\t\t      : _(\"branch '%s' not found.\"), bname.buf);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250151","messageId":"20141002021652.GJ1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 17/24] packed-ref cache: forbid dot-components in refnames","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:16:52Z","receivedAt":"2014-10-02T02:16:52Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Since v1.7.9-rc1~10^2 (write_head_info(): handle \"extra refs\" locally,\n2012-01-06), this trick to keep track of \".have\" refs that are only\nvalid on the wire and not on the filesystem is not needed any more.\n\nSimplify by removing support for the REFNAME_DOT_COMPONENT flag.\n\nThis means we'll be slightly stricter with invalid refs found in a\npacked-refs file or during clone.  read_loose_refs() already checks\nfor and skips refnames with .components so it is not affected.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Ronnie Sahlberg <sahlberg@google.com>\n---\nNoticed while reviewing other patches.\n\n refs.c | 14 +++-----------\n refs.h |  6 +-----\n 2 files changed, 4 insertions(+), 16 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 490e788..94d6d89 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -69,16 +69,8 @@ static int check_refname_component(const char *refname, int flags)\n out:\n \tif (cp == refname)\n \t\treturn 0; /* Component has zero length. */\n-\tif (refname[0] == '.') {\n-\t\tif (!(flags & REFNAME_DOT_COMPONENT))\n-\t\t\treturn -1; /* Component starts with '.'. */\n-\t\t/*\n-\t\t * Even if leading dots are allowed, don't allow \".\"\n-\t\t * as a component (\"..\" is prevented by a rule above).\n-\t\t */\n-\t\tif (refname[1] == '\\0')\n-\t\t\treturn -1; /* Component equals \".\". */\n-\t}\n+\tif (refname[0] == '.')\n+\t\treturn -1; /* Component starts with '.'. */\n \tif (cp - refname >= 5 && !memcmp(cp - 5, \".lock\", 5))\n \t\treturn -1; /* Refname ends with \".lock\". */\n \treturn cp - refname;\n@@ -288,7 +280,7 @@ static struct ref_entry *create_ref_entry(const char *refname,\n \tstruct ref_entry *ref;\n \n \tif (check_name &&\n-\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))\n+\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))\n \t\tdie(\"Reference has invalid format: '%s'\", refname);\n \tlen = strlen(refname) + 1;\n \tref = xmalloc(sizeof(struct ref_entry) + len);\ndiff --git a/refs.h b/refs.h\nindex b62c4c4..e48d8b9 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -226,7 +226,6 @@ extern int for_each_reflog(each_ref_fn, void *);\n \n #define REFNAME_ALLOW_ONELEVEL 1\n #define REFNAME_REFSPEC_PATTERN 2\n-#define REFNAME_DOT_COMPONENT 4\n \n /*\n  * Return 0 iff refname has the correct format for a refname according\n@@ -234,10 +233,7 @@ extern int for_each_reflog(each_ref_fn, void *);\n  * If REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level\n  * reference names.  If REFNAME_REFSPEC_PATTERN is set in flags, then\n  * allow a \"*\" wildcard character in place of one of the name\n- * components.  No leading or repeated slashes are accepted.  If\n- * REFNAME_DOT_COMPONENT is set in flags, then allow refname\n- * components to start with \".\" (but not a whole component equal to\n- * \".\" or \"..\").\n+ * components.  No leading or repeated slashes are accepted.\n  */\n extern int check_refname_format(const char *refname, int flags);\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250152","messageId":"20141002021739.GK1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 18/24] test: put tests for handling of bad ref names in one place","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:17:39Z","receivedAt":"2014-10-02T02:17:39Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nThere's no straightforward way to grep for all tests dealing with\ninvalid refs.  Put them in a single test script so it is easy to see\nwhat functionality has not been exercised with bad ref names yet.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nNew.\n\n t/t1400-update-ref.sh   | 44 --------------------------\n t/t1430-bad-ref-name.sh | 84 +++++++++++++++++++++++++++++++++++++++++++++++++\n t/t9300-fast-import.sh  | 30 ------------------\n 3 files changed, 84 insertions(+), 74 deletions(-)\n create mode 100755 t/t1430-bad-ref-name.sh\n\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 7c8c41a..7b4707b 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -136,14 +136,6 @@ test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' '\n \ttest_path_is_missing .git/refs/heads/ref-to-bad\n '\n \n-test_expect_success 'update-ref --no-deref -d can delete reference to broken name' '\n-\tgit symbolic-ref refs/heads/badname refs/heads/broken...ref &&\n-\ttest_when_finished \"rm -f .git/refs/heads/badname\" &&\n-\ttest_path_is_file .git/refs/heads/badname &&\n-\tgit update-ref --no-deref -d refs/heads/badname &&\n-\ttest_path_is_missing .git/refs/heads/badname\n-'\n-\n test_expect_success '(not) create HEAD with old sha1' \"\n \ttest_must_fail git update-ref HEAD $A $B\n \"\n@@ -408,12 +400,6 @@ test_expect_success 'stdin fails create with no ref' '\n \tgrep \"fatal: create: missing <ref>\" err\n '\n \n-test_expect_success 'stdin fails create with bad ref name' '\n-\techo \"create ~a $m\" >stdin &&\n-\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n-\tgrep \"fatal: invalid ref format: ~a\" err\n-'\n-\n test_expect_success 'stdin fails create with no new value' '\n \techo \"create $a\" >stdin &&\n \ttest_must_fail git update-ref --stdin <stdin 2>err &&\n@@ -432,12 +418,6 @@ test_expect_success 'stdin fails update with no ref' '\n \tgrep \"fatal: update: missing <ref>\" err\n '\n \n-test_expect_success 'stdin fails update with bad ref name' '\n-\techo \"update ~a $m\" >stdin &&\n-\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n-\tgrep \"fatal: invalid ref format: ~a\" err\n-'\n-\n test_expect_success 'stdin fails update with no new value' '\n \techo \"update $a\" >stdin &&\n \ttest_must_fail git update-ref --stdin <stdin 2>err &&\n@@ -456,12 +436,6 @@ test_expect_success 'stdin fails delete with no ref' '\n \tgrep \"fatal: delete: missing <ref>\" err\n '\n \n-test_expect_success 'stdin fails delete with bad ref name' '\n-\techo \"delete ~a $m\" >stdin &&\n-\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n-\tgrep \"fatal: invalid ref format: ~a\" err\n-'\n-\n test_expect_success 'stdin fails delete with too many arguments' '\n \techo \"delete $a $m $m\" >stdin &&\n \ttest_must_fail git update-ref --stdin <stdin 2>err &&\n@@ -734,12 +708,6 @@ test_expect_success 'stdin -z fails create with no ref' '\n \tgrep \"fatal: create: missing <ref>\" err\n '\n \n-test_expect_success 'stdin -z fails create with bad ref name' '\n-\tprintf $F \"create ~a \" \"$m\" >stdin &&\n-\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n-\tgrep \"fatal: invalid ref format: ~a \" err\n-'\n-\n test_expect_success 'stdin -z fails create with no new value' '\n \tprintf $F \"create $a\" >stdin &&\n \ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n@@ -764,12 +732,6 @@ test_expect_success 'stdin -z fails update with too few args' '\n \tgrep \"fatal: update $a: unexpected end of input when reading <oldvalue>\" err\n '\n \n-test_expect_success 'stdin -z fails update with bad ref name' '\n-\tprintf $F \"update ~a\" \"$m\" \"\" >stdin &&\n-\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n-\tgrep \"fatal: invalid ref format: ~a\" err\n-'\n-\n test_expect_success 'stdin -z emits warning with empty new value' '\n \tgit update-ref $a $m &&\n \tprintf $F \"update $a\" \"\" \"\" >stdin &&\n@@ -802,12 +764,6 @@ test_expect_success 'stdin -z fails delete with no ref' '\n \tgrep \"fatal: delete: missing <ref>\" err\n '\n \n-test_expect_success 'stdin -z fails delete with bad ref name' '\n-\tprintf $F \"delete ~a\" \"$m\" >stdin &&\n-\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n-\tgrep \"fatal: invalid ref format: ~a\" err\n-'\n-\n test_expect_success 'stdin -z fails delete with no old value' '\n \tprintf $F \"delete $a\" >stdin &&\n \ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\ndiff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh\nnew file mode 100755\nindex 0000000..c7b19b0\n--- /dev/null\n+++ b/t/t1430-bad-ref-name.sh\n@@ -0,0 +1,84 @@\n+#!/bin/sh\n+\n+test_description='Test handling of ref names that check-ref-format rejects'\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\ttest_commit one\n+'\n+\n+test_expect_success 'fast-import: fail on invalid branch name \".badbranchname\"' '\n+\ttest_when_finished \"rm -f .git/objects/pack_* .git/objects/index_*\" &&\n+\tcat >input <<-INPUT_END &&\n+\t\tcommit .badbranchname\n+\t\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\t\tdata <<COMMIT\n+\t\tcorrupt\n+\t\tCOMMIT\n+\n+\t\tfrom refs/heads/master\n+\n+\tINPUT_END\n+\ttest_must_fail git fast-import <input\n+'\n+\n+test_expect_success 'fast-import: fail on invalid branch name \"bad[branch]name\"' '\n+\ttest_when_finished \"rm -f .git/objects/pack_* .git/objects/index_*\" &&\n+\tcat >input <<-INPUT_END &&\n+\t\tcommit bad[branch]name\n+\t\tcommitter $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\t\tdata <<COMMIT\n+\t\tcorrupt\n+\t\tCOMMIT\n+\n+\t\tfrom refs/heads/master\n+\n+\tINPUT_END\n+\ttest_must_fail git fast-import <input\n+'\n+\n+test_expect_success 'update-ref --no-deref -d can delete reference to broken name' '\n+\tgit symbolic-ref refs/heads/badname refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/badname\" &&\n+\ttest_path_is_file .git/refs/heads/badname &&\n+\tgit update-ref --no-deref -d refs/heads/badname &&\n+\ttest_path_is_missing .git/refs/heads/badname\n+'\n+\n+test_expect_success 'update-ref --stdin fails create with bad ref name' '\n+\techo \"create ~a refs/heads/master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'update-ref --stdin fails update with bad ref name' '\n+\techo \"update ~a refs/heads/master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'update-ref --stdin fails delete with bad ref name' '\n+\techo \"delete ~a refs/heads/master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'update-ref --stdin -z fails create with bad ref name' '\n+\tprintf \"%s\\0\" \"create ~a \" refs/heads/master >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a \" err\n+'\n+\n+test_expect_success 'update-ref --stdin -z fails update with bad ref name' '\n+\tprintf \"%s\\0\" \"update ~a\" refs/heads/master \"\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'update-ref --stdin -z fails delete with bad ref name' '\n+\tprintf \"%s\\0\" \"delete ~a\" refs/heads/master >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_done\ndiff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh\nindex 5fc9ef2..3d156f9 100755\n--- a/t/t9300-fast-import.sh\n+++ b/t/t9300-fast-import.sh\n@@ -347,36 +347,6 @@ test_expect_success 'B: fail on invalid blob sha1' '\n rm -f .git/objects/pack_* .git/objects/index_*\n \n cat >input <<INPUT_END\n-commit .badbranchname\n-committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n-data <<COMMIT\n-corrupt\n-COMMIT\n-\n-from refs/heads/master\n-\n-INPUT_END\n-test_expect_success 'B: fail on invalid branch name \".badbranchname\"' '\n-    test_must_fail git fast-import <input\n-'\n-rm -f .git/objects/pack_* .git/objects/index_*\n-\n-cat >input <<INPUT_END\n-commit bad[branch]name\n-committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n-data <<COMMIT\n-corrupt\n-COMMIT\n-\n-from refs/heads/master\n-\n-INPUT_END\n-test_expect_success 'B: fail on invalid branch name \"bad[branch]name\"' '\n-    test_must_fail git fast-import <input\n-'\n-rm -f .git/objects/pack_* .git/objects/index_*\n-\n-cat >input <<INPUT_END\n commit TEMP_TAG\n committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n data <<COMMIT\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250153","messageId":"20141002022819.GL1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 19/24] refs.c: allow listing and deleting badly named refs","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:28:19Z","receivedAt":"2014-10-02T02:28:19Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Wed, 3 Sep 2014 11:45:43 -0700\n\nWe currently do not handle badly named refs well:\n\n  $ cp .git/refs/heads/master .git/refs/heads/master.....@\\*@\\\\.\n  $ git branch\n    fatal: Reference has invalid format: 'refs/heads/master.....@*@\\.'\n  $ git branch -D master.....@\\*@\\\\.\n    error: branch 'master.....@*@\\.' not found.\n\nUsers cannot recover from a badly named ref without manually finding\nand deleting the loose ref file or appropriate line in packed-refs.\nMaking that easier will make it easier to tweak the ref naming rules\nin the future, for example to forbid shell metacharacters like '`'\nand '\"', without putting people in a state that is hard to get out of.\n\nSo allow \"git branch --list\" to show these refs and to allow \"git\nbranch -d/-D\" and \"git update-ref -d\" to delete them.  Other commands\n(for example to rename refs) will continue to not handle these refs\nbut can be changed in later patches.\n\nDetails:\n\nIn resolving functions, refuse to resolve refs that don't pass the\ncheck-ref-format(1) check unless the new RESOLVE_REF_ALLOW_BAD_NAME\nflag is passed.  Even with RESOLVE_REF_ALLOW_BAD_NAME, refuse to\nresolve refs that escape the refs/ directory and do not match the\npattern [A-Z_]* (think \"HEAD\" and \"MERGE_HEAD\").\n\nIn locking functions, refuse to act on badly named refs unless they\nare being deleted and either are in the refs/ directory or match [A-Z_]*.\n\nJust like other invalid refs, flag resolved, badly named refs with the\nREF_ISBROKEN flag, treat them as resolving to null_sha1, and skip them\nin all iteration functions except for for_each_rawref.\n\nFlag badly named refs with a REF_BAD_NAME flag to make it easier for\nfuture callers to notice and handle them specially.\n\nIn the transaction API, refuse to create or update badly named refs,\nbut allow deleting them (unless they escape refs/ and don't match\n[A-Z_]*).\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nSince v21:\n- clarified change description\n- handle REF_ISBROKEN case too when printing \"Deleted branch\" message\n- tighten fallback check, so commands like 'git branch -d ../../config'\n  won't work\n- use REF_BAD_NAME so it's easier for callers (like for-each-ref in a\n  later patch) to notice that we decided a ref's name was bad\n- set errno when failing to find ref in the packed-refs file\n- consistently don't set REF_ISBROKEN for missing refs\n- consistently clear sha1 when successfully resolving a badly named ref\n- treat refs with bad names encountered as part of symref resolution the\n  same as such refs explicitly named\n- always allow safe bad names when trying to lock a ref for deletion\n  (though some callers don't get that far yet)\n- clearer API docs, more tests\n\n builtin/branch.c        |   9 +--\n cache.h                 |  14 ++++-\n refs.c                  | 148 ++++++++++++++++++++++++++++++++++++++----------\n refs.h                  |   8 ++-\n t/t1430-bad-ref-name.sh | 125 +++++++++++++++++++++++++++++++++++++++-\n 5 files changed, 265 insertions(+), 39 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex a0c5601..94aaea1 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -236,7 +236,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\tname = mkpathdup(fmt, bname.buf);\n \t\ttarget = resolve_ref_unsafe(name,\n \t\t\t\t\t    RESOLVE_REF_READING\n-\t\t\t\t\t    | RESOLVE_REF_NO_RECURSE,\n+\t\t\t\t\t    | RESOLVE_REF_NO_RECURSE\n+\t\t\t\t\t    | RESOLVE_REF_ALLOW_BAD_NAME,\n \t\t\t\t\t    sha1, &flags);\n \t\tif (!target) {\n \t\t\terror(remote_branch\n@@ -246,7 +247,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (!(flags & REF_ISSYMREF) &&\n+\t\tif (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&\n \t\t    check_branch_commit(bname.buf, name, sha1, head_rev, kinds,\n \t\t\t\t\tforce)) {\n \t\t\tret = 1;\n@@ -266,8 +267,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\t\t       ? _(\"Deleted remote branch %s (was %s).\\n\")\n \t\t\t       : _(\"Deleted branch %s (was %s).\\n\"),\n \t\t\t       bname.buf,\n-\t\t\t       (flags & REF_ISSYMREF)\n-\t\t\t       ? target\n+\t\t\t       (flags & REF_ISBROKEN) ? \"broken\"\n+\t\t\t       : (flags & REF_ISSYMREF) ? target\n \t\t\t       : find_unique_abbrev(sha1, DEFAULT_ABBREV));\n \t\t}\n \t\tdelete_branch_config(bname.buf);\ndiff --git a/cache.h b/cache.h\nindex 5ca7f2b..0c0ac60 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -978,16 +978,26 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n  * If flags is non-NULL, set the value that it points to the\n  * combination of REF_ISPACKED (if the reference was found among the\n  * packed references), REF_ISSYMREF (if the initial reference was a\n- * symbolic reference) and REF_ISBROKEN (if the ref is malformed).\n+ * symbolic reference), REF_BAD_NAME (if the reference name is ill\n+ * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN\n+ * (if the ref is malformed).\n  *\n  * If ref is not a properly-formatted, normalized reference, return\n  * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,\n  * give up and return NULL.\n  *\n- * errno is set to something meaningful on error.\n+ * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their\n+ * name is invalid according to git-check-ref-format(1).  If the name\n+ * is bad then the value stored in sha1 will be null_sha1 and the\n+ * REF_ISBROKEN and REF_BAD_NAME flags will be set.\n+ *\n+ * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/\n+ * directory and do not consist of all caps and underscores cannot be\n+ * resolved.  The function returns NULL for such ref names.\n  */\n #define RESOLVE_REF_READING 0x01\n #define RESOLVE_REF_NO_RECURSE 0x02\n+#define RESOLVE_REF_ALLOW_BAD_NAME 0x04\n extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);\n extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);\n \ndiff --git a/refs.c b/refs.c\nindex 94d6d89..0d3abb1 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -185,8 +185,8 @@ struct ref_dir {\n \n /*\n  * Bit values for ref_entry::flag.  REF_ISSYMREF=0x01,\n- * REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see\n- * refs.h.\n+ * REF_ISPACKED=0x02, REF_ISBROKEN=0x04 and REF_BAD_NAME=0x08 are\n+ * public values; see refs.h.\n  */\n \n /*\n@@ -194,16 +194,16 @@ struct ref_dir {\n  * the correct peeled value for the reference, which might be\n  * null_sha1 if the reference is not a tag or if it is broken.\n  */\n-#define REF_KNOWS_PEELED 0x08\n+#define REF_KNOWS_PEELED 0x10\n \n /* ref_entry represents a directory of references */\n-#define REF_DIR 0x10\n+#define REF_DIR 0x20\n \n /*\n  * Entry has not yet been read from disk (used only for REF_DIR\n  * entries representing loose references)\n  */\n-#define REF_INCOMPLETE 0x20\n+#define REF_INCOMPLETE 0x40\n \n /*\n  * A ref_entry represents either a reference or a \"subdirectory\" of\n@@ -272,6 +272,37 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)\n \treturn dir;\n }\n \n+static int escapes_cwd(const char *path) {\n+\tchar *buf;\n+\tint result;\n+\n+\tif (is_absolute_path(path))\n+\t\treturn 1;\n+\tbuf = xmalloc(strlen(path) + 1);\n+\tresult = !!normalize_path_copy(buf, path);\n+\tfree(buf);\n+\treturn result;\n+}\n+\n+/*\n+ * Check if a refname is safe.\n+ * For refs that start with \"refs/\" we consider it safe as long as the rest\n+ * of the path components does not allow it to escape from this directory.\n+ * For all other refs we only consider them safe iff they only contain\n+ * upper case characters and '_'.\n+ */\n+static int refname_is_safe(const char *refname)\n+{\n+\tif (starts_with(refname, \"refs/\"))\n+\t\treturn !escapes_cwd(refname + strlen(\"refs/\"));\n+\twhile (*refname) {\n+\t\tif (!isupper(*refname) && *refname != '_')\n+\t\t\treturn 0;\n+\t\trefname++;\n+\t}\n+\treturn 1;\n+}\n+\n static struct ref_entry *create_ref_entry(const char *refname,\n \t\t\t\t\t  const unsigned char *sha1, int flag,\n \t\t\t\t\t  int check_name)\n@@ -282,6 +313,8 @@ static struct ref_entry *create_ref_entry(const char *refname,\n \tif (check_name &&\n \t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))\n \t\tdie(\"Reference has invalid format: '%s'\", refname);\n+\tif (!check_name && !refname_is_safe(refname))\n+\t\tdie(\"Reference has invalid name: '%s'\", refname);\n \tlen = strlen(refname) + 1;\n \tref = xmalloc(sizeof(struct ref_entry) + len);\n \thashcpy(ref->u.value.sha1, sha1);\n@@ -1051,7 +1084,13 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)\n \n \t\trefname = parse_ref_line(refline, sha1);\n \t\tif (refname) {\n-\t\t\tlast = create_ref_entry(refname, sha1, REF_ISPACKED, 1);\n+\t\t\tint flag = REF_ISPACKED;\n+\n+\t\t\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\t\t\thashclr(sha1);\n+\t\t\t\tflag |= REF_BAD_NAME | REF_ISBROKEN;\n+\t\t\t}\n+\t\t\tlast = create_ref_entry(refname, sha1, flag, 0);\n \t\t\tif (peeled == PEELED_FULLY ||\n \t\t\t    (peeled == PEELED_TAGS && starts_with(refname, \"refs/tags/\")))\n \t\t\t\tlast->flag |= REF_KNOWS_PEELED;\n@@ -1189,8 +1228,13 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)\n \t\t\t\thashclr(sha1);\n \t\t\t\tflag |= REF_ISBROKEN;\n \t\t\t}\n+\t\t\tif (check_refname_format(refname.buf,\n+\t\t\t\t\t\t REFNAME_ALLOW_ONELEVEL)) {\n+\t\t\t\thashclr(sha1);\n+\t\t\t\tflag |= REF_BAD_NAME | REF_ISBROKEN;\n+\t\t\t}\n \t\t\tadd_entry_to_dir(dir,\n-\t\t\t\t\t create_ref_entry(refname.buf, sha1, flag, 1));\n+\t\t\t\t\t create_ref_entry(refname.buf, sha1, flag, 0));\n \t\t}\n \t\tstrbuf_setlen(&refname, dirnamelen);\n \t}\n@@ -1309,10 +1353,10 @@ static struct ref_entry *get_packed_ref(const char *refname)\n  * A loose ref file doesn't exist; check for a packed ref.  The\n  * options are forwarded from resolve_safe_unsafe().\n  */\n-static const char *handle_missing_loose_ref(const char *refname,\n-\t\t\t\t\t    int resolve_flags,\n-\t\t\t\t\t    unsigned char *sha1,\n-\t\t\t\t\t    int *flags)\n+static int resolve_missing_loose_ref(const char *refname,\n+\t\t\t\t     int resolve_flags,\n+\t\t\t\t     unsigned char *sha1,\n+\t\t\t\t     int *flags)\n {\n \tstruct ref_entry *entry;\n \n@@ -1325,14 +1369,15 @@ static const char *handle_missing_loose_ref(const char *refname,\n \t\thashcpy(sha1, entry->u.value.sha1);\n \t\tif (flags)\n \t\t\t*flags |= REF_ISPACKED;\n-\t\treturn refname;\n+\t\treturn 0;\n \t}\n \t/* The reference is not a packed reference, either. */\n \tif (resolve_flags & RESOLVE_REF_READING) {\n-\t\treturn NULL;\n+\t\terrno = ENOENT;\n+\t\treturn -1;\n \t} else {\n \t\thashclr(sha1);\n-\t\treturn refname;\n+\t\treturn 0;\n \t}\n }\n \n@@ -1343,13 +1388,29 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned\n \tssize_t len;\n \tchar buffer[256];\n \tstatic char refname_buffer[256];\n+\tint bad_name = 0;\n \n \tif (flags)\n \t\t*flags = 0;\n \n \tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n-\t\terrno = EINVAL;\n-\t\treturn NULL;\n+\t\tif (flags)\n+\t\t\t*flags |= REF_BAD_NAME;\n+\n+\t\tif (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||\n+\t\t    !refname_is_safe(refname)) {\n+\t\t\terrno = EINVAL;\n+\t\t\treturn NULL;\n+\t\t}\n+\t\t/*\n+\t\t * dwim_ref() uses REF_ISBROKEN to distinguish between\n+\t\t * missing refs and refs that were present but invalid,\n+\t\t * to complain about the latter to stderr.\n+\t\t *\n+\t\t * We don't know whether the ref exists, so don't set\n+\t\t * REF_ISBROKEN yet.\n+\t\t */\n+\t\tbad_name = 1;\n \t}\n \tfor (;;) {\n \t\tchar path[PATH_MAX];\n@@ -1375,11 +1436,17 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned\n \t\t */\n \tstat_ref:\n \t\tif (lstat(path, &st) < 0) {\n-\t\t\tif (errno == ENOENT)\n-\t\t\t\treturn handle_missing_loose_ref(refname,\n-\t\t\t\t\t\tresolve_flags, sha1, flags);\n-\t\t\telse\n+\t\t\tif (errno != ENOENT)\n \t\t\t\treturn NULL;\n+\t\t\tif (resolve_missing_loose_ref(refname, resolve_flags,\n+\t\t\t\t\t\t      sha1, flags))\n+\t\t\t\treturn NULL;\n+\t\t\tif (bad_name) {\n+\t\t\t\thashclr(sha1);\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISBROKEN;\n+\t\t\t}\n+\t\t\treturn refname;\n \t\t}\n \n \t\t/* Follow \"normalized\" - ie \"refs/..\" symlinks by hand */\n@@ -1452,6 +1519,11 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned\n \t\t\t\terrno = EINVAL;\n \t\t\t\treturn NULL;\n \t\t\t}\n+\t\t\tif (bad_name) {\n+\t\t\t\thashclr(sha1);\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISBROKEN;\n+\t\t\t}\n \t\t\treturn refname;\n \t\t}\n \t\tif (flags)\n@@ -1466,9 +1538,14 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned\n \t\t}\n \t\tif (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {\n \t\t\tif (flags)\n-\t\t\t\t*flags |= REF_ISBROKEN;\n-\t\t\terrno = EINVAL;\n-\t\t\treturn NULL;\n+\t\t\t\t*flags |= REF_BAD_NAME | REF_ISBROKEN;\n+\n+\t\t\tif (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||\n+\t\t\t    !refname_is_safe(buf)) {\n+\t\t\t\terrno = EINVAL;\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t\tbad_name = 1;\n \t\t}\n \t}\n }\n@@ -2101,18 +2178,16 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \tint missing = 0;\n \tint attempts_remaining = 3;\n \n-\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n-\t\terrno = EINVAL;\n-\t\treturn NULL;\n-\t}\n-\n \tlock = xcalloc(1, sizeof(struct ref_lock));\n \tlock->lock_fd = -1;\n \n \tif (mustexist)\n \t\tresolve_flags |= RESOLVE_REF_READING;\n-\tif (flags & REF_NODEREF && flags & REF_DELETING)\n-\t\tresolve_flags |= RESOLVE_REF_NO_RECURSE;\n+\tif (flags & REF_DELETING) {\n+\t\tresolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;\n+\t\tif (flags & REF_NODEREF)\n+\t\t\tresolve_flags |= RESOLVE_REF_NO_RECURSE;\n+\t}\n \n \trefname = resolve_ref_unsafe(refname, resolve_flags,\n \t\t\t\t     lock->old_sha1, &type);\n@@ -3465,6 +3540,13 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tif (have_old && !old_sha1)\n \t\tdie(\"BUG: have_old is true but old_sha1 is NULL\");\n \n+\tif (!is_null_sha1(new_sha1) &&\n+\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\tstrbuf_addf(err, \"refusing to update ref with bad name %s\",\n+\t\t\t    refname);\n+\t\treturn -1;\n+\t}\n+\n \tupdate = add_update(transaction, refname);\n \thashcpy(update->new_sha1, new_sha1);\n \tupdate->flags = flags;\n@@ -3490,6 +3572,12 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \tif (!new_sha1 || is_null_sha1(new_sha1))\n \t\tdie(\"BUG: create ref with null new_sha1\");\n \n+\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\tstrbuf_addf(err, \"refusing to create ref with bad name %s\",\n+\t\t\t    refname);\n+\t\treturn -1;\n+\t}\n+\n \tupdate = add_update(transaction, refname);\n \n \thashcpy(update->new_sha1, new_sha1);\ndiff --git a/refs.h b/refs.h\nindex e48d8b9..3b35387 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -56,11 +56,15 @@ struct ref_transaction;\n \n /*\n  * Reference cannot be resolved to an object name: dangling symbolic\n- * reference (directly or indirectly), corrupt reference file, or\n- * symbolic reference refers to ill-formatted reference name.\n+ * reference (directly or indirectly), corrupt reference file,\n+ * reference exists but name is bad, or symbolic reference refers to\n+ * ill-formatted reference name.\n  */\n #define REF_ISBROKEN 0x04\n \n+/* Reference name is not well formed (see git-check-ref-format(1)). */\n+#define REF_BAD_NAME 0x08\n+\n /*\n  * The signature for the callback function for the for_each_*()\n  * functions below.  The memory pointed to by the refname and sha1\ndiff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh\nindex c7b19b0..468e856 100755\n--- a/t/t1430-bad-ref-name.sh\n+++ b/t/t1430-bad-ref-name.sh\n@@ -4,7 +4,8 @@ test_description='Test handling of ref names that check-ref-format rejects'\n . ./test-lib.sh\n \n test_expect_success setup '\n-\ttest_commit one\n+\ttest_commit one &&\n+\ttest_commit two\n '\n \n test_expect_success 'fast-import: fail on invalid branch name \".badbranchname\"' '\n@@ -37,6 +38,107 @@ test_expect_success 'fast-import: fail on invalid branch name \"bad[branch]name\"'\n \ttest_must_fail git fast-import <input\n '\n \n+test_expect_success 'git branch shows badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch >output &&\n+\tgrep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'branch -d can delete badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch -d broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'branch -D can delete badly named ref' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch -D broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'branch -D cannot delete non-ref in .git dir' '\n+\techo precious >.git/my-private-file &&\n+\techo precious >expect &&\n+\ttest_must_fail git branch -D ../../my-private-file &&\n+\ttest_cmp expect .git/my-private-file\n+'\n+\n+test_expect_success 'branch -D cannot delete absolute path' '\n+\tgit branch -f extra &&\n+\ttest_must_fail git branch -D \"$(pwd)/.git/refs/heads/extra\" &&\n+\ttest_cmp_rev HEAD extra\n+'\n+\n+test_expect_success 'git branch cannot create a badly named ref' '\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\ttest_must_fail git branch broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'branch -m cannot rename to a bad ref name' '\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\ttest_might_fail git branch -D goodref &&\n+\tgit branch goodref &&\n+\ttest_must_fail git branch -m goodref broken...ref &&\n+\ttest_cmp_rev master goodref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_failure 'branch -m can rename from a bad ref name' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch -m broken...ref renamed &&\n+\ttest_cmp_rev master renamed &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'push cannot create a badly named ref' '\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\ttest_must_fail git push \"file://$(pwd)\" HEAD:refs/heads/broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_failure 'push --mirror can delete badly named ref' '\n+\ttop=$(pwd) &&\n+\tgit init src &&\n+\tgit init dest &&\n+\n+\t(\n+\t\tcd src &&\n+\t\ttest_commit one\n+\t) &&\n+\t(\n+\t\tcd dest &&\n+\t\ttest_commit two &&\n+\t\tgit checkout --detach &&\n+\t\tcp .git/refs/heads/master .git/refs/heads/broken...ref\n+\t) &&\n+\tgit -C src push --mirror \"file://$top/dest\" &&\n+\tgit -C dest branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'rev-parse skips symref pointing to broken name' '\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit branch shadow one &&\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\tgit symbolic-ref refs/tags/shadow refs/heads/broken...ref &&\n+\n+\tgit rev-parse --verify one >expect &&\n+\tgit rev-parse --verify shadow >actual 2>err &&\n+\ttest_cmp expect actual &&\n+\ttest_i18ngrep \"ignoring.*refs/tags/shadow\" err\n+'\n+\n test_expect_success 'update-ref --no-deref -d can delete reference to broken name' '\n \tgit symbolic-ref refs/heads/badname refs/heads/broken...ref &&\n \ttest_when_finished \"rm -f .git/refs/heads/badname\" &&\n@@ -45,6 +147,27 @@ test_expect_success 'update-ref --no-deref -d can delete reference to broken nam\n \ttest_path_is_missing .git/refs/heads/badname\n '\n \n+test_expect_success 'update-ref -d can delete broken name' '\n+\tcp .git/refs/heads/master .git/refs/heads/broken...ref &&\n+\ttest_when_finished \"rm -f .git/refs/heads/broken...ref\" &&\n+\tgit update-ref -d refs/heads/broken...ref &&\n+\tgit branch >output &&\n+\t! grep -e \"broken\\.\\.\\.ref\" output\n+'\n+\n+test_expect_success 'update-ref -d cannot delete non-ref in .git dir' '\n+\techo precious >.git/my-private-file &&\n+\techo precious >expect &&\n+\ttest_must_fail git update-ref -d my-private-file &&\n+\ttest_cmp expect .git/my-private-file\n+'\n+\n+test_expect_success 'update-ref -d cannot delete absolute path' '\n+\tgit branch -f extra &&\n+\ttest_must_fail git update-ref -d \"$(pwd)/.git/refs/heads/extra\" &&\n+\ttest_cmp_rev HEAD extra\n+'\n+\n test_expect_success 'update-ref --stdin fails create with bad ref name' '\n \techo \"create ~a refs/heads/master\" >stdin &&\n \ttest_must_fail git update-ref --stdin <stdin 2>err &&\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250154","messageId":"20141002023057.GM1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 20/24] for-each-ref.c: improve message before aborting on broken ref","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:30:57Z","receivedAt":"2014-10-02T02:30:57Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Fri, 5 Sep 2014 14:35:17 -0700\n\nPrint a warning message for any badly named refs we find in the repo.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nAs before.\n\n builtin/for-each-ref.c | 5 +++++\n 1 file changed, 5 insertions(+)\n\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex 20949b7..a88d681 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -853,6 +853,11 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f\n \tstruct refinfo *ref;\n \tint cnt;\n \n+\tif (flag & REF_BAD_NAME) {\n+\t\t  warning(\"ignoring ref with broken name %s\", refname);\n+\t\t  return 0;\n+\t}\n+\n \tif (*cb->grab_pattern) {\n \t\tconst char **pattern;\n \t\tint namelen = strlen(refname);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250155","messageId":"20141002023217.GN1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 21/24] remote rm/prune: print a message when writing packed-refs fails","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:32:17Z","receivedAt":"2014-10-02T02:32:17Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 11 Sep 2014 08:42:57 -0700\n\nUntil v2.1.0-rc0~22^2~11 (refs.c: add an err argument to\nrepack_without_refs, 2014-06-20), repack_without_refs forgot to\nprovide an error message when commit_packed_refs fails.  Even today,\nit only provides a message for callers that pass a non-NULL err\nparameter.  Internal callers in refs.c pass non-NULL err but\n\"git remote\" does not.\n\nThat means that \"git remote rm\" and \"git remote prune\" can fail\nwithout printing a message about why.  Fix them by passing in a\nnon-NULL err parameter and printing the returned message.\n\nThis is the last caller to a ref handling function passing err ==\nNULL.  A later patch can drop support for err == NULL, avoiding such\nproblems in the future.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\nSince v21:\n- s/without/about/ in the commit message\n\n builtin/remote.c | 15 +++++++++++----\n 1 file changed, 11 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex c7f82f4..8517cfa 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -750,13 +750,16 @@ static int mv(int argc, const char **argv)\n \n static int remove_branches(struct string_list *branches)\n {\n+\tstruct strbuf err = STRBUF_INIT;\n \tconst char **branch_names;\n \tint i, result = 0;\n \n \tbranch_names = xmalloc(branches->nr * sizeof(*branch_names));\n \tfor (i = 0; i < branches->nr; i++)\n \t\tbranch_names[i] = branches->items[i].string;\n-\tresult |= repack_without_refs(branch_names, branches->nr, NULL);\n+\tif (repack_without_refs(branch_names, branches->nr, &err))\n+\t\tresult |= error(\"%s\", err.buf);\n+\tstrbuf_release(&err);\n \tfree(branch_names);\n \n \tfor (i = 0; i < branches->nr; i++) {\n@@ -1333,9 +1336,13 @@ static int prune_remote(const char *remote, int dry_run)\n \t\tdelete_refs = xmalloc(states.stale.nr * sizeof(*delete_refs));\n \t\tfor (i = 0; i < states.stale.nr; i++)\n \t\t\tdelete_refs[i] = states.stale.items[i].util;\n-\t\tif (!dry_run)\n-\t\t\tresult |= repack_without_refs(delete_refs,\n-\t\t\t\t\t\t      states.stale.nr, NULL);\n+\t\tif (!dry_run) {\n+\t\t\tstruct strbuf err = STRBUF_INIT;\n+\t\t\tif (repack_without_refs(delete_refs, states.stale.nr,\n+\t\t\t\t\t\t&err))\n+\t\t\t\tresult |= error(\"%s\", err.buf);\n+\t\t\tstrbuf_release(&err);\n+\t\t}\n \t\tfree(delete_refs);\n \t}\n \n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250156","messageId":"20141002023321.GO1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 22/24] refs.c: do not permit err == NULL","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:33:21Z","receivedAt":"2014-10-02T02:33:21Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Date: Thu, 28 Aug 2014 16:42:37 -0700\n\nSome functions that take a strbuf argument to append an error treat\n!err as an indication that the message should be suppressed (e.g.,\nref_update_reject_duplicates).  Others write the message to stderr on\n!err (e.g., repack_without_refs).  Others crash (e.g.,\nref_transaction_update).\n\nSome of these behaviors are for historical reasons and others were\naccidents.  Luckily no callers pass err == NULL any more.  Simplify\nby consistently requiring the strbuf argument.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nSince v21:\n- dropped spurious 'to' from commit message\n\n refs.c | 46 +++++++++++++++++++++++++++-------------------\n 1 file changed, 27 insertions(+), 19 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 0d3abb1..e13f843 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2596,6 +2596,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \tstruct string_list_item *ref_to_delete;\n \tint i, ret, removed = 0;\n \n+\tassert(err);\n+\n \t/* Look for a packed ref */\n \tfor (i = 0; i < n; i++)\n \t\tif (get_packed_ref(refnames[i]))\n@@ -2606,13 +2608,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \t\treturn 0; /* no refname exists in packed refs */\n \n \tif (lock_packed_refs(0)) {\n-\t\tif (err) {\n-\t\t\tunable_to_lock_message(git_path(\"packed-refs\"), errno,\n-\t\t\t\t\t       err);\n-\t\t\treturn -1;\n-\t\t}\n-\t\tunable_to_lock_error(git_path(\"packed-refs\"), errno);\n-\t\treturn error(\"cannot delete '%s' from packed refs\", refnames[i]);\n+\t\tunable_to_lock_message(git_path(\"packed-refs\"), errno, err);\n+\t\treturn -1;\n \t}\n \tpacked = get_packed_refs(&ref_cache);\n \n@@ -2638,7 +2635,7 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \n \t/* Write what remains */\n \tret = commit_packed_refs();\n-\tif (ret && err)\n+\tif (ret)\n \t\tstrbuf_addf(err, \"unable to overwrite old ref-pack file: %s\",\n \t\t\t    strerror(errno));\n \treturn ret;\n@@ -2646,6 +2643,8 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)\n \n static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)\n {\n+\tassert(err);\n+\n \tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n \t\t/* loose */\n \t\tint res, i = strlen(lock->lk->filename) - 5; /* .lock */\n@@ -3495,6 +3494,8 @@ struct ref_transaction {\n \n struct ref_transaction *ref_transaction_begin(struct strbuf *err)\n {\n+\tassert(err);\n+\n \treturn xcalloc(1, sizeof(struct ref_transaction));\n }\n \n@@ -3534,6 +3535,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tassert(err);\n+\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: update called for transaction that is not open\");\n \n@@ -3566,6 +3569,8 @@ int ref_transaction_create(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tassert(err);\n+\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: create called for transaction that is not open\");\n \n@@ -3597,6 +3602,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n {\n \tstruct ref_update *update;\n \n+\tassert(err);\n+\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: delete called for transaction that is not open\");\n \n@@ -3659,13 +3666,14 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,\n \t\t\t\t\tstruct strbuf *err)\n {\n \tint i;\n+\n+\tassert(err);\n+\n \tfor (i = 1; i < n; i++)\n \t\tif (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {\n-\t\t\tconst char *str =\n-\t\t\t\t\"Multiple updates for ref '%s' not allowed.\";\n-\t\t\tif (err)\n-\t\t\t\tstrbuf_addf(err, str, updates[i]->refname);\n-\n+\t\t\tstrbuf_addf(err,\n+\t\t\t\t    \"Multiple updates for ref '%s' not allowed.\",\n+\t\t\t\t    updates[i]->refname);\n \t\t\treturn 1;\n \t\t}\n \treturn 0;\n@@ -3679,6 +3687,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \tint n = transaction->nr;\n \tstruct ref_update **updates = transaction->updates;\n \n+\tassert(err);\n+\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: commit called for transaction that is not open\");\n \n@@ -3715,9 +3725,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\tret = (errno == ENOTDIR)\n \t\t\t\t? TRANSACTION_NAME_CONFLICT\n \t\t\t\t: TRANSACTION_GENERIC_ERROR;\n-\t\t\tif (err)\n-\t\t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n-\t\t\t\t\t    update->refname);\n+\t\t\tstrbuf_addf(err, \"Cannot lock the ref '%s'.\",\n+\t\t\t\t    update->refname);\n \t\t\tgoto cleanup;\n \t\t}\n \t}\n@@ -3730,9 +3739,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\tif (write_ref_sha1(update->lock, update->new_sha1,\n \t\t\t\t\t   update->msg)) {\n \t\t\t\tupdate->lock = NULL; /* freed by write_ref_sha1 */\n-\t\t\t\tif (err)\n-\t\t\t\t\tstrbuf_addf(err, \"Cannot update the ref '%s'.\",\n-\t\t\t\t\t\t    update->refname);\n+\t\t\t\tstrbuf_addf(err, \"Cannot update the ref '%s'.\",\n+\t\t\t\t\t    update->refname);\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto cleanup;\n \t\t\t}\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250157","messageId":"20141002023404.GP1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 23/24] lockfile: remove unable_to_lock_error","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:34:04Z","receivedAt":"2014-10-02T02:34:04Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Date: Thu, 28 Aug 2014 16:41:34 -0700\n\nThe former caller uses unable_to_lock_message now.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Michael Haggerty <mhagger@alum.mit.edu>\nReviewed-by: Ronnie Sahlberg <sahlberg@google.com>\n---\nAs before.\n\n cache.h    |  1 -\n lockfile.c | 10 ----------\n 2 files changed, 11 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 0c0ac60..f582b6c 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -558,7 +558,6 @@ struct lock_file {\n };\n #define LOCK_DIE_ON_ERROR 1\n #define LOCK_NODEREF 2\n-extern int unable_to_lock_error(const char *path, int err);\n extern void unable_to_lock_message(const char *path, int err,\n \t\t\t\t   struct strbuf *buf);\n extern NORETURN void unable_to_lock_index_die(const char *path, int err);\ndiff --git a/lockfile.c b/lockfile.c\nindex a921d77..dbd4101 100644\n--- a/lockfile.c\n+++ b/lockfile.c\n@@ -176,16 +176,6 @@ void unable_to_lock_message(const char *path, int err, struct strbuf *buf)\n \t\t\t    absolute_path(path), strerror(err));\n }\n \n-int unable_to_lock_error(const char *path, int err)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tunable_to_lock_message(path, err, &buf);\n-\terror(\"%s\", buf.buf);\n-\tstrbuf_release(&buf);\n-\treturn -1;\n-}\n-\n NORETURN void unable_to_lock_index_die(const char *path, int err)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250158","messageId":"20141002023510.GQ1175@google.com","threadId":"37248","inReplyTo":"20141002014817.GS1175@google.com","subject":"[PATCH 24/24] ref_transaction_commit: bail out on failure to remove a ref","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-02T02:35:10Z","receivedAt":"2014-10-02T02:35:10Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Date: Thu, 28 Aug 2014 17:01:35 -0700\n\nWhen removal of a loose or packed ref fails, bail out instead of\ntrying to finish the transaction.  This way, a single error message\ncan be printed (instead of multiple messages being concatenated by\nmistake) and the operator can try to solve the underlying problem\nbefore there is a chance to muck things up even more.\n\nIn particular, when git fails to remove a ref, git goes on to try to\ndelete the reflog.  Exiting early lets us keep the reflog.\n\nWhen git succeeds in deleting a ref A and fails to remove a ref B, it\ngoes on to try to delete both reflogs.  It would be better to just\nremove the reflog for A, but that would be a more invasive change.\nFailing early means we keep both reflogs, which puts the operator in a\ngood position to understand the problem and recover.\n\nA long term goal is to avoid these problems altogether and roll back\nthe transaction on failure.  That kind of transactionality will have\nto wait for a later series (the plan for which is to make all\ndestructive work happen in a single update of the packed-refs file).\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\nReviewed-by: Ronnie Sahlberg <sahlberg@google.com>\n---\nThat's the end of the series.  Thanks for reading.\n\n refs.c | 8 ++++++--\n 1 file changed, 6 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex e13f843..d9d327d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3753,16 +3753,20 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (update->lock) {\n-\t\t\tif (delete_ref_loose(update->lock, update->type, err))\n+\t\t\tif (delete_ref_loose(update->lock, update->type, err)) {\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\t\tgoto cleanup;\n+\t\t\t}\n \n \t\t\tif (!(update->flags & REF_ISPRUNING))\n \t\t\t\tdelnames[delnum++] = update->lock->ref_name;\n \t\t}\n \t}\n \n-\tif (repack_without_refs(delnames, delnum, err))\n+\tif (repack_without_refs(delnames, delnum, err)) {\n \t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\tgoto cleanup;\n+\t}\n \tfor (i = 0; i < delnum; i++)\n \t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n \tclear_loose_ref_cache(&ref_cache);\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250178","messageId":"xmqqa95envxa.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"20141002022819.GL1175@google.com","subject":"Re: [PATCH 19/24] refs.c: allow listing and deleting badly named refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-10-02T18:55:29Z","receivedAt":"2014-10-02T18:55:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> From: Ronnie Sahlberg <sahlberg@google.com>\n> ...\n> In resolving functions, refuse to resolve refs that don't pass the\n> check-ref-format(1) check unless the new RESOLVE_REF_ALLOW_BAD_NAME\n> flag is passed.  Even with RESOLVE_REF_ALLOW_BAD_NAME, refuse to\n> resolve refs that escape the refs/ directory and do not match the\n> pattern [A-Z_]* (think \"HEAD\" and \"MERGE_HEAD\").\n>\n> In locking functions, refuse to act on badly named refs unless they\n> are being deleted and either are in the refs/ directory or match [A-Z_]*.\n>\n> Just like other invalid refs, flag resolved, badly named refs with the\n> REF_ISBROKEN flag, treat them as resolving to null_sha1, and skip them\n> in all iteration functions except for for_each_rawref.\n>\n> Flag badly named refs with a REF_BAD_NAME flag to make it easier for\n> future callers to notice and handle them specially.\n>\n> In the transaction API, refuse to create or update badly named refs,\n> but allow deleting them (unless they escape refs/ and don't match\n> [A-Z_]*).\n>\n> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>\n\nThanks.  We originally threw all the different kind of breakages\ninto ISBROKEN, but a ref can have a malformed name or can contain a\nbad/non value and allowing us to tell them apart is a good direction\nto go.\n\n> diff --git a/cache.h b/cache.h\n> index 5ca7f2b..0c0ac60 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -978,16 +978,26 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n>   * If flags is non-NULL, set the value that it points to the\n>   * combination of REF_ISPACKED (if the reference was found among the\n>   * packed references), REF_ISSYMREF (if the initial reference was a\n> - * symbolic reference) and REF_ISBROKEN (if the ref is malformed).\n> + * symbolic reference), REF_BAD_NAME (if the reference name is ill\n> + * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN\n> + * (if the ref is malformed).\n\nYou want to define \"is malformed\" here.\n\nThe original defines REF_ISBROKEN as \"malformed\" because\n\n (1) resolve_ref_unsafe() uses get_sha1_hex() and read_loose_refs()\n     uses read_ref_full(), both to catch \"malformed values\" stored;\n\n (2) resolve_ref_unsafe() uses check_refname_format() and catches\n     \"malformed names\" stored as a symref target.\n\nI _think_ you are introducing ALLOW_BAD_NAME to allow add the third\nclass \".git/refs/remotes/origin/mal..formed..name\".  I do not know\nif they should be the same class as a symref with a good name\n\".git/refs/remotes/origin/HEAD\" that points at a bad name\n\"mal..formed..name\", which is (2) above).  Perhaps not.  (2) is\nstill above what is stored in the ref, and the ref in question may\nor may not have a well-formed name, which is orthogonal.\n\nSo probably you left only the \"value stored in the ref is malformed\"\n(in other words, \"we expected to find 40-hex object name but didn't\nfind one\") case for REF_ISBROKEN?\n\nDo we want to separate \"value is not 40-hex\" and \"a symref points at\na malformed refname\" as separate \"malformed value\" errors?\n\n> + * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their\n> + * name is invalid according to git-check-ref-format(1).  If the name\n> + * is bad then the value stored in sha1 will be null_sha1 and the\n> + * REF_ISBROKEN and REF_BAD_NAME flags will be set.\n\nViewed with that light, I am not sure if a badly named ref should\nyield null_sha1[] (REF_ISBROKEN, which I am assuming is about a\nvalue that is badly formatted and cannot be read, should keep\nyielding it as before).  Wouldn't it make it harder for the user if\nyou give null_sha1[] back to somebody who is trying to recover by\nreading \"refs/heads/mal..formed\", creating \"refs/heads/sensible\" to\npoint at the same value and then removing the former?\n\nNote that I am not saying we should give back the parsed value at\nthis step in the series.  Perhaps there are some existing callers\nthat do not check for ISBROKEN flag and instead says \"null_sha1[]\nref is to be rejected/ignored\", in which case they may need to be\ncorrected before that happens.  Or there may be some reason I\noverlooked that makes it not so useful if we returned the object\nname stored in a ref whose name is malformed.  Just wondering.\n\n> @@ -272,6 +272,37 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)\n>  \treturn dir;\n>  }\n>  \n> +static int escapes_cwd(const char *path) {\n> +\tchar *buf;\n> +\tint result;\n> +\n> +\tif (is_absolute_path(path))\n> +\t\treturn 1;\n> +\tbuf = xmalloc(strlen(path) + 1);\n> +\tresult = !!normalize_path_copy(buf, path);\n> +\tfree(buf);\n> +\treturn result;\n> +}\n\nI think this function is misnamed for two reasons.\n\n - It does not have anything to do with cwd; it does not make any\n   difference to the outcome of this function given the same input,\n   if 'pwd' says \"/u/jc/git\" or \"/u/jc/git/Documentation\", no?\n\n - Even if this had something to do with cwd, I would expect a\n   function whose name is escapes_cwd(\"/u/jc/git/Documentation\") to\n   yield false when 'pwd' says \"/u/jc/git\", but the implementation\n   unconditionally rejects absolute path.  In the context of the\n   (sole) caller of this function, which deals with a refname \"refs/...\",\n   it makes no sense to see an absolute path, but that does not have\n   anything to do with \"does this path escape cwd?\", no?\n\n> +/*\n> + * Check if a refname is safe.\n> + * For refs that start with \"refs/\" we consider it safe as long as the rest\n> + * of the path components does not allow it to escape from this directory.\n> + * For all other refs we only consider them safe iff they only contain\n> + * upper case characters and '_'.\n> + */\n\nI presume that the exception is to accomodate for \"HEAD\", \"ORIG_HEAD\",\n\"MERGE_HEAD\" and friends, but you probably do not want the readers to\nguess.\n\n> +static int refname_is_safe(const char *refname)\n> +{\n> +\tif (starts_with(refname, \"refs/\"))\n> +\t\treturn !escapes_cwd(refname + strlen(\"refs/\"));\n> +\twhile (*refname) {\n> +\t\tif (!isupper(*refname) && *refname != '_')\n> +\t\t\treturn 0;\n> +\t\trefname++;\n> +\t}\n> +\treturn 1;\n> +}\n"},{"id":"250179","messageId":"xmqq61g2nuud.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"20141002020332.GB1175@google.com","subject":"Re: [PATCH 09/24] refs.c: pass a list of names to skip to is_refname_available","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-10-02T19:18:50Z","receivedAt":"2014-10-02T19:18:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> diff --git a/refs.c b/refs.c\n> index f124c2b..6820c93 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -801,14 +801,16 @@ static int names_conflict(const char *refname1, const char *refname2)\n>  \n>  struct name_conflict_cb {\n>  \tconst char *refname;\n> -\tconst char *oldrefname;\n>  \tconst char *conflicting_refname;\n> +\tstruct string_list *skiplist;\n>  };\n\nAs cbe73331 (refs: speed up is_refname_available, 2014-09-10)\ntouches the same area and is now in 'master', the logic around here\nin this series needs to be reworked.\n\nThanks.\n"},{"id":"250204","messageId":"20141003185128.GS1175@google.com","threadId":"37248","inReplyTo":"xmqq61g2nuud.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH 09/24] refs.c: pass a list of names to skip to is_refname_available","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-03T18:51:28Z","receivedAt":"2014-10-03T18:51:28Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Junio C Hamano wrote:\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>> diff --git a/refs.c b/refs.c\n>> index f124c2b..6820c93 100644\n>> --- a/refs.c\n>> +++ b/refs.c\n>> @@ -801,14 +801,16 @@ static int names_conflict(const char *refname1, const char *refname2)\n>> \n>>  struct name_conflict_cb {\n>>  \tconst char *refname;\n>> -\tconst char *oldrefname;\n>>  \tconst char *conflicting_refname;\n>> +\tstruct string_list *skiplist;\n>>  };\n>\n> As cbe73331 (refs: speed up is_refname_available, 2014-09-10)\n> touches the same area and is now in 'master', the logic around here\n> in this series needs to be reworked.\n\nThanks for the heads up.  (I hadn't realized the ref-transaction-1 was\npart of 'master' --- yay!)  Rebased and reworked:\nhttps://code-review.googlesource.com/1027\n\nI'll give a week or so for review comments on this version of the\nseries and then post a hopefully final version.\n"},{"id":"250206","messageId":"xmqqoattkm87.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"20141003185128.GS1175@google.com","subject":"Re: [PATCH 09/24] refs.c: pass a list of names to skip to is_refname_available","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-10-03T19:05:28Z","receivedAt":"2014-10-03T19:05:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> Junio C Hamano wrote:\n> ...\n>> As cbe73331 (refs: speed up is_refname_available, 2014-09-10)\n>> touches the same area and is now in 'master', the logic around here\n>> in this series needs to be reworked.\n>\n> Thanks for the heads up.  (I hadn't realized the ref-transaction-1 was\n> part of 'master' --- yay!)  Rebased and reworked:\n> https://code-review.googlesource.com/1027\n\nHeh, I was sort of expecting that heavy-weight contributors that\nthrow many and/or large patch series would at least be paying\nattention to \"What's cooking\" reports.\n\nWill take a look over there (I really hate having to context switch\nonly to review this series, though).\n\n> I'll give a week or so for review comments on this version of the\n> series and then post a hopefully final version.\n\nThanks.\n"},{"id":"250218","messageId":"CAL=YDW=N1+XS+U=Vs0Sba0UqdN7+HRg5CTdZB28h5Ts3_yusYg@mail.gmail.com","threadId":"37248","inReplyTo":"xmqqa95envxa.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH 19/24] refs.c: allow listing and deleting badly named refs","fromName":"Ronnie Sahlberg","fromEmail":"sahlberg@google.com","sentAt":"2014-10-03T20:25:34Z","receivedAt":"2014-10-03T20:25:34Z","isPatch":true,"sender":{"key":"sahlberg@google.com","avatar":"https://avatars.githubusercontent.com/u/7320636?v=4"},"body":"On Thu, Oct 2, 2014 at 11:55 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n>\n>> From: Ronnie Sahlberg <sahlberg@google.com>\n>> ...\n>> In resolving functions, refuse to resolve refs that don't pass the\n>> check-ref-format(1) check unless the new RESOLVE_REF_ALLOW_BAD_NAME\n>> flag is passed.  Even with RESOLVE_REF_ALLOW_BAD_NAME, refuse to\n>> resolve refs that escape the refs/ directory and do not match the\n>> pattern [A-Z_]* (think \"HEAD\" and \"MERGE_HEAD\").\n>>\n>> In locking functions, refuse to act on badly named refs unless they\n>> are being deleted and either are in the refs/ directory or match [A-Z_]*.\n>>\n>> Just like other invalid refs, flag resolved, badly named refs with the\n>> REF_ISBROKEN flag, treat them as resolving to null_sha1, and skip them\n>> in all iteration functions except for for_each_rawref.\n>>\n>> Flag badly named refs with a REF_BAD_NAME flag to make it easier for\n>> future callers to notice and handle them specially.\n>>\n>> In the transaction API, refuse to create or update badly named refs,\n>> but allow deleting them (unless they escape refs/ and don't match\n>> [A-Z_]*).\n>>\n>> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n>> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>\n>\n> Thanks.  We originally threw all the different kind of breakages\n> into ISBROKEN, but a ref can have a malformed name or can contain a\n> bad/non value and allowing us to tell them apart is a good direction\n> to go.\n>\n>> diff --git a/cache.h b/cache.h\n>> index 5ca7f2b..0c0ac60 100644\n>> --- a/cache.h\n>> +++ b/cache.h\n>> @@ -978,16 +978,26 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n>>   * If flags is non-NULL, set the value that it points to the\n>>   * combination of REF_ISPACKED (if the reference was found among the\n>>   * packed references), REF_ISSYMREF (if the initial reference was a\n>> - * symbolic reference) and REF_ISBROKEN (if the ref is malformed).\n>> + * symbolic reference), REF_BAD_NAME (if the reference name is ill\n>> + * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN\n>> + * (if the ref is malformed).\n>\n> You want to define \"is malformed\" here.\n>\n> The original defines REF_ISBROKEN as \"malformed\" because\n>\n>  (1) resolve_ref_unsafe() uses get_sha1_hex() and read_loose_refs()\n>      uses read_ref_full(), both to catch \"malformed values\" stored;\n>\n>  (2) resolve_ref_unsafe() uses check_refname_format() and catches\n>      \"malformed names\" stored as a symref target.\n>\n> I _think_ you are introducing ALLOW_BAD_NAME to allow add the third\n> class \".git/refs/remotes/origin/mal..formed..name\".  I do not know\n> if they should be the same class as a symref with a good name\n> \".git/refs/remotes/origin/HEAD\" that points at a bad name\n> \"mal..formed..name\", which is (2) above).  Perhaps not.  (2) is\n> still above what is stored in the ref, and the ref in question may\n> or may not have a well-formed name, which is orthogonal.\n>\n> So probably you left only the \"value stored in the ref is malformed\"\n> (in other words, \"we expected to find 40-hex object name but didn't\n> find one\") case for REF_ISBROKEN?\n\nI updated cache.h to try to clarify it better.\nThe intention here is to expand the use of REF_ISBROKEN.\n\nFor all cases  REF_ISBROKEN will be set. This includes both \"the sha1\nvalue is bad\" as well as \"a name is bad\".\n\nFor those cases where a name is bad then REF_BAD_NAME is also set. Bad\nnames are when either the ref itself has a bad name or when a bad name\nis encountered while resolving a chain of symbilic refs.\n\n\nI.e.  REF_BAD_NAME is a special case of broken ref. All REF_BAD_NAME\nrefs are also REF_ISBROKEN but not the reverse.\n\n\n>\n> Do we want to separate \"value is not 40-hex\" and \"a symref points at\n> a malformed refname\" as separate \"malformed value\" errors?\n>\n>> + * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their\n>> + * name is invalid according to git-check-ref-format(1).  If the name\n>> + * is bad then the value stored in sha1 will be null_sha1 and the\n>> + * REF_ISBROKEN and REF_BAD_NAME flags will be set.\n>\n> Viewed with that light, I am not sure if a badly named ref should\n> yield null_sha1[] (REF_ISBROKEN, which I am assuming is about a\n> value that is badly formatted and cannot be read, should keep\n> yielding it as before).  Wouldn't it make it harder for the user if\n> you give null_sha1[] back to somebody who is trying to recover by\n> reading \"refs/heads/mal..formed\", creating \"refs/heads/sensible\" to\n> point at the same value and then removing the former?\n>\n> Note that I am not saying we should give back the parsed value at\n> this step in the series.  Perhaps there are some existing callers\n> that do not check for ISBROKEN flag and instead says \"null_sha1[]\n> ref is to be rejected/ignored\", in which case they may need to be\n> corrected before that happens.  Or there may be some reason I\n> overlooked that makes it not so useful if we returned the object\n> name stored in a ref whose name is malformed.  Just wondering.\n\nThe reason for these malformed refs resolving to null_sha1 is exactly\nthat. There may be callers that do not check ISBROKEN, so all those\ncallers need to be carefully audited before we start returning\npotentially valid non null_sha1 here.\nRight now I only want to do the minimal changes needed to open up only\nthose few paths I need to in order to allow the refs to be deleted.\nFor the delete case, returning the null_sha1 is sufficient.\n\n\nNow, IF we add support to \"rename a bad-ref-name to a good-ref-name\",\nthen for that case we would need to start allowing resolve_ref_unsafe\nto actually return the sha1 for the ref.\nAnd that would require that we do those changes and audits. Given how\nrare this should be, I am not convinced we need \"rename bad to good\"\nat this point and would thus prefer to hold off adding that.\n\nThus, returning the object name is not so useful for the problem I try\nto solve at this stage, namely \"make delete work\".\n\n\n>\n>> @@ -272,6 +272,37 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)\n>>       return dir;\n>>  }\n>>\n>> +static int escapes_cwd(const char *path) {\n>> +     char *buf;\n>> +     int result;\n>> +\n>> +     if (is_absolute_path(path))\n>> +             return 1;\n>> +     buf = xmalloc(strlen(path) + 1);\n>> +     result = !!normalize_path_copy(buf, path);\n>> +     free(buf);\n>> +     return result;\n>> +}\n>\n> I think this function is misnamed for two reasons.\n>\n>  - It does not have anything to do with cwd; it does not make any\n>    difference to the outcome of this function given the same input,\n>    if 'pwd' says \"/u/jc/git\" or \"/u/jc/git/Documentation\", no?\n>\n>  - Even if this had something to do with cwd, I would expect a\n>    function whose name is escapes_cwd(\"/u/jc/git/Documentation\") to\n>    yield false when 'pwd' says \"/u/jc/git\", but the implementation\n>    unconditionally rejects absolute path.  In the context of the\n>    (sole) caller of this function, which deals with a refname \"refs/...\",\n>    it makes no sense to see an absolute path, but that does not have\n>    anything to do with \"does this path escape cwd?\", no?\n>\n\nAgree.\nI removed this function completely and just inline the !normalize_path_copy()\nchecks to make sure that refs/* paths remain within refs/.\n\n\n>> +/*\n>> + * Check if a refname is safe.\n>> + * For refs that start with \"refs/\" we consider it safe as long as the rest\n>> + * of the path components does not allow it to escape from this directory.\n>> + * For all other refs we only consider them safe iff they only contain\n>> + * upper case characters and '_'.\n>> + */\n>\n> I presume that the exception is to accomodate for \"HEAD\", \"ORIG_HEAD\",\n> \"MERGE_HEAD\" and friends, but you probably do not want the readers to\n> guess.\n>\n\nI updated the commend to highlight that this is for \"HEAD\" and friends.\n\n>> +static int refname_is_safe(const char *refname)\n>> +{\n>> +     if (starts_with(refname, \"refs/\"))\n>> +             return !escapes_cwd(refname + strlen(\"refs/\"));\n>> +     while (*refname) {\n>> +             if (!isupper(*refname) && *refname != '_')\n>> +                     return 0;\n>> +             refname++;\n>> +     }\n>> +     return 1;\n>> +}\n"},{"id":"250224","messageId":"CAL=YDWmV89jwWqru5dgxCKpx-JYCrH=vYVLT_yPqsT9Ep9Qa8Q@mail.gmail.com","threadId":"37248","inReplyTo":"CAL=YDW=N1+XS+U=Vs0Sba0UqdN7+HRg5CTdZB28h5Ts3_yusYg@mail.gmail.com","subject":"Re: [PATCH 19/24] refs.c: allow listing and deleting badly named refs","fromName":"Ronnie Sahlberg","fromEmail":"sahlberg@google.com","sentAt":"2014-10-03T20:32:43Z","receivedAt":"2014-10-03T20:32:43Z","isPatch":true,"sender":{"key":"sahlberg@google.com","avatar":"https://avatars.githubusercontent.com/u/7320636?v=4"},"body":"On Fri, Oct 3, 2014 at 1:25 PM, Ronnie Sahlberg <sahlberg@google.com> wrote:\n> On Thu, Oct 2, 2014 at 11:55 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Jonathan Nieder <jrnieder@gmail.com> writes:\n>>\n>>> From: Ronnie Sahlberg <sahlberg@google.com>\n>>> ...\n>>> In resolving functions, refuse to resolve refs that don't pass the\n>>> check-ref-format(1) check unless the new RESOLVE_REF_ALLOW_BAD_NAME\n>>> flag is passed.  Even with RESOLVE_REF_ALLOW_BAD_NAME, refuse to\n>>> resolve refs that escape the refs/ directory and do not match the\n>>> pattern [A-Z_]* (think \"HEAD\" and \"MERGE_HEAD\").\n>>>\n>>> In locking functions, refuse to act on badly named refs unless they\n>>> are being deleted and either are in the refs/ directory or match [A-Z_]*.\n>>>\n>>> Just like other invalid refs, flag resolved, badly named refs with the\n>>> REF_ISBROKEN flag, treat them as resolving to null_sha1, and skip them\n>>> in all iteration functions except for for_each_rawref.\n>>>\n>>> Flag badly named refs with a REF_BAD_NAME flag to make it easier for\n>>> future callers to notice and handle them specially.\n>>>\n>>> In the transaction API, refuse to create or update badly named refs,\n>>> but allow deleting them (unless they escape refs/ and don't match\n>>> [A-Z_]*).\n>>>\n>>> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n>>> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>\n>>\n>> Thanks.  We originally threw all the different kind of breakages\n>> into ISBROKEN, but a ref can have a malformed name or can contain a\n>> bad/non value and allowing us to tell them apart is a good direction\n>> to go.\n>>\n>>> diff --git a/cache.h b/cache.h\n>>> index 5ca7f2b..0c0ac60 100644\n>>> --- a/cache.h\n>>> +++ b/cache.h\n>>> @@ -978,16 +978,26 @@ extern int read_ref(const char *refname, unsigned char *sha1);\n>>>   * If flags is non-NULL, set the value that it points to the\n>>>   * combination of REF_ISPACKED (if the reference was found among the\n>>>   * packed references), REF_ISSYMREF (if the initial reference was a\n>>> - * symbolic reference) and REF_ISBROKEN (if the ref is malformed).\n>>> + * symbolic reference), REF_BAD_NAME (if the reference name is ill\n>>> + * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN\n>>> + * (if the ref is malformed).\n>>\n>> You want to define \"is malformed\" here.\n>>\n>> The original defines REF_ISBROKEN as \"malformed\" because\n>>\n>>  (1) resolve_ref_unsafe() uses get_sha1_hex() and read_loose_refs()\n>>      uses read_ref_full(), both to catch \"malformed values\" stored;\n>>\n>>  (2) resolve_ref_unsafe() uses check_refname_format() and catches\n>>      \"malformed names\" stored as a symref target.\n>>\n>> I _think_ you are introducing ALLOW_BAD_NAME to allow add the third\n>> class \".git/refs/remotes/origin/mal..formed..name\".  I do not know\n>> if they should be the same class as a symref with a good name\n>> \".git/refs/remotes/origin/HEAD\" that points at a bad name\n>> \"mal..formed..name\", which is (2) above).  Perhaps not.  (2) is\n>> still above what is stored in the ref, and the ref in question may\n>> or may not have a well-formed name, which is orthogonal.\n>>\n>> So probably you left only the \"value stored in the ref is malformed\"\n>> (in other words, \"we expected to find 40-hex object name but didn't\n>> find one\") case for REF_ISBROKEN?\n>\n> I updated cache.h to try to clarify it better.\n> The intention here is to expand the use of REF_ISBROKEN.\n>\n> For all cases  REF_ISBROKEN will be set. This includes both \"the sha1\n> value is bad\" as well as \"a name is bad\".\n>\n> For those cases where a name is bad then REF_BAD_NAME is also set. Bad\n> names are when either the ref itself has a bad name or when a bad name\n> is encountered while resolving a chain of symbilic refs.\n>\n>\n> I.e.  REF_BAD_NAME is a special case of broken ref. All REF_BAD_NAME\n> refs are also REF_ISBROKEN but not the reverse.\n\nLet me add some rationale why REF_BAD_NAME and REF_ISBROKEN are\nnotorthogonal properties.\n\nI think almost all callers of these APIs are only concerned about \"is\nthe ref good or bad\" and they today only check REF_ISBROKEN.\nI think that is a reasonable API and it allows the majority of callers\nto just \"check this single flag\".\n(The alternative would be to keep all callers in sync and use a set of\nflags for all \"bad conditions\".)\n\nA very small subset of callers are actually interested in knowing why\nthe ref was bad, and in particular if the ref was bad due to a name\ncomponent.\nThose callers, that are aware that there are different types of\nISBROKEN can then inspect the REF_BAD_NAME flag in order to decide\n\"is the ref broken due to the ref name?\".\n\n\nThis is why I made REF_BAD_NAME a special case of REF_ISBROKEN.\n\n\n\n>\n>\n>>\n>> Do we want to separate \"value is not 40-hex\" and \"a symref points at\n>> a malformed refname\" as separate \"malformed value\" errors?\n>>\n>>> + * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their\n>>> + * name is invalid according to git-check-ref-format(1).  If the name\n>>> + * is bad then the value stored in sha1 will be null_sha1 and the\n>>> + * REF_ISBROKEN and REF_BAD_NAME flags will be set.\n>>\n>> Viewed with that light, I am not sure if a badly named ref should\n>> yield null_sha1[] (REF_ISBROKEN, which I am assuming is about a\n>> value that is badly formatted and cannot be read, should keep\n>> yielding it as before).  Wouldn't it make it harder for the user if\n>> you give null_sha1[] back to somebody who is trying to recover by\n>> reading \"refs/heads/mal..formed\", creating \"refs/heads/sensible\" to\n>> point at the same value and then removing the former?\n>>\n>> Note that I am not saying we should give back the parsed value at\n>> this step in the series.  Perhaps there are some existing callers\n>> that do not check for ISBROKEN flag and instead says \"null_sha1[]\n>> ref is to be rejected/ignored\", in which case they may need to be\n>> corrected before that happens.  Or there may be some reason I\n>> overlooked that makes it not so useful if we returned the object\n>> name stored in a ref whose name is malformed.  Just wondering.\n>\n> The reason for these malformed refs resolving to null_sha1 is exactly\n> that. There may be callers that do not check ISBROKEN, so all those\n> callers need to be carefully audited before we start returning\n> potentially valid non null_sha1 here.\n> Right now I only want to do the minimal changes needed to open up only\n> those few paths I need to in order to allow the refs to be deleted.\n> For the delete case, returning the null_sha1 is sufficient.\n>\n>\n> Now, IF we add support to \"rename a bad-ref-name to a good-ref-name\",\n> then for that case we would need to start allowing resolve_ref_unsafe\n> to actually return the sha1 for the ref.\n> And that would require that we do those changes and audits. Given how\n> rare this should be, I am not convinced we need \"rename bad to good\"\n> at this point and would thus prefer to hold off adding that.\n>\n> Thus, returning the object name is not so useful for the problem I try\n> to solve at this stage, namely \"make delete work\".\n>\n>\n>>\n>>> @@ -272,6 +272,37 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)\n>>>       return dir;\n>>>  }\n>>>\n>>> +static int escapes_cwd(const char *path) {\n>>> +     char *buf;\n>>> +     int result;\n>>> +\n>>> +     if (is_absolute_path(path))\n>>> +             return 1;\n>>> +     buf = xmalloc(strlen(path) + 1);\n>>> +     result = !!normalize_path_copy(buf, path);\n>>> +     free(buf);\n>>> +     return result;\n>>> +}\n>>\n>> I think this function is misnamed for two reasons.\n>>\n>>  - It does not have anything to do with cwd; it does not make any\n>>    difference to the outcome of this function given the same input,\n>>    if 'pwd' says \"/u/jc/git\" or \"/u/jc/git/Documentation\", no?\n>>\n>>  - Even if this had something to do with cwd, I would expect a\n>>    function whose name is escapes_cwd(\"/u/jc/git/Documentation\") to\n>>    yield false when 'pwd' says \"/u/jc/git\", but the implementation\n>>    unconditionally rejects absolute path.  In the context of the\n>>    (sole) caller of this function, which deals with a refname \"refs/...\",\n>>    it makes no sense to see an absolute path, but that does not have\n>>    anything to do with \"does this path escape cwd?\", no?\n>>\n>\n> Agree.\n> I removed this function completely and just inline the !normalize_path_copy()\n> checks to make sure that refs/* paths remain within refs/.\n>\n>\n>>> +/*\n>>> + * Check if a refname is safe.\n>>> + * For refs that start with \"refs/\" we consider it safe as long as the rest\n>>> + * of the path components does not allow it to escape from this directory.\n>>> + * For all other refs we only consider them safe iff they only contain\n>>> + * upper case characters and '_'.\n>>> + */\n>>\n>> I presume that the exception is to accomodate for \"HEAD\", \"ORIG_HEAD\",\n>> \"MERGE_HEAD\" and friends, but you probably do not want the readers to\n>> guess.\n>>\n>\n> I updated the commend to highlight that this is for \"HEAD\" and friends.\n>\n>>> +static int refname_is_safe(const char *refname)\n>>> +{\n>>> +     if (starts_with(refname, \"refs/\"))\n>>> +             return !escapes_cwd(refname + strlen(\"refs/\"));\n>>> +     while (*refname) {\n>>> +             if (!isupper(*refname) && *refname != '_')\n>>> +                     return 0;\n>>> +             refname++;\n>>> +     }\n>>> +     return 1;\n>>> +}\n"},{"id":"250226","messageId":"xmqqa95clwfo.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"CAL=YDW=N1+XS+U=Vs0Sba0UqdN7+HRg5CTdZB28h5Ts3_yusYg@mail.gmail.com","subject":"Re: [PATCH 19/24] refs.c: allow listing and deleting badly named refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-10-03T20:39:39Z","receivedAt":"2014-10-03T20:39:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ronnie Sahlberg <sahlberg@google.com> writes:\n\n> On Thu, Oct 2, 2014 at 11:55 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> ...\n>> Thanks.  We originally threw all the different kind of breakages\n>> into ISBROKEN, but a ref can have a malformed name or can contain a\n>> bad/non value and allowing us to tell them apart is a good direction\n>> to go.\n>> ...\n>\n> I updated cache.h to try to clarify it better.\n> The intention here is to expand the use of REF_ISBROKEN.\n>\n> For all cases  REF_ISBROKEN will be set. This includes both \"the sha1\n> value is bad\" as well as \"a name is bad\".\n\nOK.  As long as it is documented in a way to help other people who\ntouch the code later to tell what these REF_* mean, either way is\nfine between \"BADNAME is just one of the possible ways a ref is\nISBROKEN\" and \"BADNAME is a way and ISBROKEN is another way for a\nref to be bad\", and I agree that the former (i.e. the way you\ndefined) is easier on the existing code.\n\nThanks.\n"},{"id":"250231","messageId":"20141003213900.GT1175@google.com","threadId":"37248","inReplyTo":"xmqqoattkm87.fsf@gitster.dls.corp.google.com","subject":"[PATCH v22.5 09/24] refs.c: pass a list of names to skip to is_refname_available","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-10-03T21:39:00Z","receivedAt":"2014-10-03T21:39:00Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\nDate: Thu, 1 May 2014 11:16:07 -0700\n\ncommit 23acf975d74825789112a3a7ba97dbbdc76904f4 upstream.\n\nChange is_refname_available to take a list of strings to exclude when\nchecking for conflicts instead of just one single name. We can already\nexclude a single name for the sake of renames. This generalizes that support.\n\nref_transaction_commit already tracks a set of refs that are being deleted\nin an array.  This array is then used to exclude refs from being written to\nthe packed-refs file.  At some stage we will want to change this array to a\nstruct string_list and then we can pass it to is_refname_available via the\ncall to lock_ref_sha1_basic.  That will allow us to perform transactions\nthat perform multiple renames as long as there are no conflicts within the\nstarting or ending state.\n\nFor example, that would allow a single transaction that contains two\nrenames that are both individually conflicting:\n\n   m -> n/n\n   n -> m/m\n\nNo functional change intended yet.\n\nChange-Id: I8c68f0a47eef25759d572436ba683cb7b1ccb7eb\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nJunio C Hamano wrote:\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>> Thanks for the heads up.  (I hadn't realized the ref-transaction-1 was\n>> part of 'master' --- yay!)  Rebased and reworked:\n>> https://code-review.googlesource.com/1027\n>\n> Heh, I was sort of expecting that heavy-weight contributors that\n> throw many and/or large patch series would at least be paying\n> attention to \"What's cooking\" reports.\n\nThat's fair.  I don't pay enough attention to the 'graduated' section.\n\n> Will take a look over there (I really hate having to context switch\n> only to review this series, though).\n\nHere's a copy to save a trip.\n\n refs.c | 50 ++++++++++++++++++++++++++++++++------------------\n 1 file changed, 32 insertions(+), 18 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 83b2c77..05e42a7 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -785,13 +785,13 @@ static void prime_ref_dir(struct ref_dir *dir)\n \t}\n }\n \n-static int entry_matches(struct ref_entry *entry, const char *refname)\n+static int entry_matches(struct ref_entry *entry, const struct string_list *list)\n {\n-\treturn refname && !strcmp(entry->name, refname);\n+\treturn list && string_list_has_string(list, entry->name);\n }\n \n struct nonmatching_ref_data {\n-\tconst char *skip;\n+\tconst struct string_list *skip;\n \tstruct ref_entry *found;\n };\n \n@@ -815,16 +815,19 @@ static void report_refname_conflict(struct ref_entry *entry,\n /*\n  * Return true iff a reference named refname could be created without\n  * conflicting with the name of an existing reference in dir.  If\n- * oldrefname is non-NULL, ignore potential conflicts with oldrefname\n- * (e.g., because oldrefname is scheduled for deletion in the same\n+ * skip is non-NULL, ignore potential conflicts with refs in skip\n+ * (e.g., because they are scheduled for deletion in the same\n  * operation).\n  *\n  * Two reference names conflict if one of them exactly matches the\n  * leading components of the other; e.g., \"foo/bar\" conflicts with\n  * both \"foo\" and with \"foo/bar/baz\" but not with \"foo/bar\" or\n  * \"foo/barbados\".\n+ *\n+ * skip must be sorted.\n  */\n-static int is_refname_available(const char *refname, const char *oldrefname,\n+static int is_refname_available(const char *refname,\n+\t\t\t\tconst struct string_list *skip,\n \t\t\t\tstruct ref_dir *dir)\n {\n \tconst char *slash;\n@@ -838,12 +841,12 @@ static int is_refname_available(const char *refname, const char *oldrefname,\n \t\t * looking for a conflict with a leaf entry.\n \t\t *\n \t\t * If we find one, we still must make sure it is\n-\t\t * not \"oldrefname\".\n+\t\t * not in \"skip\".\n \t\t */\n \t\tpos = search_ref_dir(dir, refname, slash - refname);\n \t\tif (pos >= 0) {\n \t\t\tstruct ref_entry *entry = dir->entries[pos];\n-\t\t\tif (entry_matches(entry, oldrefname))\n+\t\t\tif (entry_matches(entry, skip))\n \t\t\t\treturn 1;\n \t\t\treport_refname_conflict(entry, refname);\n \t\t\treturn 0;\n@@ -876,13 +879,13 @@ static int is_refname_available(const char *refname, const char *oldrefname,\n \t\t/*\n \t\t * We found a directory named \"refname\". It is a\n \t\t * problem iff it contains any ref that is not\n-\t\t * \"oldrefname\".\n+\t\t * in \"skip\".\n \t\t */\n \t\tstruct ref_entry *entry = dir->entries[pos];\n \t\tstruct ref_dir *dir = get_ref_dir(entry);\n \t\tstruct nonmatching_ref_data data;\n \n-\t\tdata.skip = oldrefname;\n+\t\tdata.skip = skip;\n \t\tsort_ref_dir(dir);\n \t\tif (!do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data))\n \t\t\treturn 1;\n@@ -2137,6 +2140,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n  */\n static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\t\t\t    const unsigned char *old_sha1,\n+\t\t\t\t\t    const struct string_list *skip,\n \t\t\t\t\t    int flags, int *type_p)\n {\n \tchar *ref_file;\n@@ -2186,7 +2190,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t * name is a proper prefix of our refname.\n \t */\n \tif (missing &&\n-\t     !is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) {\n+\t     !is_refname_available(refname, skip, get_packed_refs(&ref_cache))) {\n \t\tlast_errno = ENOTDIR;\n \t\tgoto error_return;\n \t}\n@@ -2244,7 +2248,7 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t const unsigned char *old_sha1,\n \t\t\t\t\t int flags, int *type_p)\n {\n-\treturn lock_ref_sha1_basic(refname, old_sha1, flags, type_p);\n+\treturn lock_ref_sha1_basic(refname, old_sha1, NULL, flags, type_p);\n }\n \n /*\n@@ -2690,6 +2694,18 @@ static int rename_tmp_log(const char *newrefname)\n \treturn 0;\n }\n \n+static int rename_ref_available(const char *oldname, const char *newname)\n+{\n+\tstruct string_list skip = STRING_LIST_INIT_NODUP;\n+\tint ret;\n+\n+\tstring_list_insert(&skip, oldname);\n+\tret = is_refname_available(newname, &skip, get_packed_refs(&ref_cache))\n+\t    && is_refname_available(newname, &skip, get_loose_refs(&ref_cache));\n+\tstring_list_clear(&skip, 0);\n+\treturn ret;\n+}\n+\n int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)\n {\n \tunsigned char sha1[20], orig_sha1[20];\n@@ -2709,10 +2725,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \tif (!symref)\n \t\treturn error(\"refname %s not found\", oldrefname);\n \n-\tif (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache)))\n-\t\treturn 1;\n-\n-\tif (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache)))\n+\tif (!rename_ref_available(oldrefname, newrefname))\n \t\treturn 1;\n \n \tif (log && rename(git_path(\"logs/%s\", oldrefname), git_path(TMP_RENAMED_LOG)))\n@@ -2742,7 +2755,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \n \tlogmoved = log;\n \n-\tlock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL);\n+\tlock = lock_ref_sha1_basic(newrefname, NULL, NULL, 0, NULL);\n \tif (!lock) {\n \t\terror(\"unable to lock %s for update\", newrefname);\n \t\tgoto rollback;\n@@ -2757,7 +2770,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms\n \treturn 0;\n \n  rollback:\n-\tlock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL);\n+\tlock = lock_ref_sha1_basic(oldrefname, NULL, NULL, 0, NULL);\n \tif (!lock) {\n \t\terror(\"unable to lock %s for rollback\", oldrefname);\n \t\tgoto rollbacklog;\n@@ -3636,6 +3649,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t\t\t   (update->have_old ?\n \t\t\t\t\t\t    update->old_sha1 :\n \t\t\t\t\t\t    NULL),\n+\t\t\t\t\t\t   NULL,\n \t\t\t\t\t\t   update->flags,\n \t\t\t\t\t\t   &update->type);\n \t\tif (!update->lock) {\n-- \n2.1.0.rc2.206.gedb03e5\n"},{"id":"250304","messageId":"xmqqr3yjel61.fsf@gitster.dls.corp.google.com","threadId":"37248","inReplyTo":"20141003213900.GT1175@google.com","subject":"Re: [PATCH v22.5 09/24] refs.c: pass a list of names to skip to is_refname_available","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-10-07T19:26:14Z","receivedAt":"2014-10-07T19:26:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>> Will take a look over there (I really hate having to context switch\n>> only to review this series, though).\n>\n> Here's a copy to save a trip.\n\nThe patch itself looks fine, but Gerrit really needs a way to convey\n\"these changes make a single topic and here is the tip\" somehow.  It\ncould be that there is and it is not well advertised, but the end\nresult is I visit the URL,\n\n  https://code-review.googlesource.com/#/q/project:git+topic:ref-transaction\n\npick and view a few patches at random to view the changes in pretty\nside-by-side diff output (which by itself is fine), check \"Download\"\nto see the fetch URL, when it is followed it often gives me only an\nearly half of the topic, get confused and scratch my head, give up\nand abandon and the whole time doing so ends up being wasted.\n"}]}