{"thread":{"id":"34408","subject":"[PATCH v2 1/6] cache.h: move remote/connect API out of it","startedAt":"2013-07-11T22:26:14Z","lastAt":"2013-07-11T22:26:20Z","messageCount":7,"participants":["Junio C Hamano"],"isPatch":true,"patchVersion":2,"patchTotal":6},"messages":[{"id":"223108","messageId":"1373581580-13651-1-git-send-email-gitster@pobox.com","threadId":"34408","inReplyTo":null,"subject":"[PATCH v2 0/6] Less potent \"--force\" for \"git push\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-11T22:26:14Z","receivedAt":"2013-07-11T22:26:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"So here is a reroll to make \"--lockref\" a weaker form of \"--force\"\nthat by itself makes \"git push\" bypass the usual \"must fast-forward\"\ncheck but enforces a different check \"the old ref must be at X\"\ninstead, taking ideas from J6t.  This allows \"--force\" to be again\nthe big red button to bypass all the safety.\n\nI still am not married to the \"lockref\" name, and as I said in 3/6,\nI think \"--force-with-lease\" might be a better name for it.\n\nThe previous round is found at $gmane/229987.\n\n\n[PATCH v2 1/6] cache.h: move remote/connect API out of it\n[PATCH v2 2/6] builtin/push.c: use OPT_BOOL, not OPT_BOOLEAN\n\nThese are the same from v1\n\n[PATCH v2 3/6] remote.c: add command line option parser for \"--lockref\"\n\nThe earlier one dug the system from both ends and met in the middle,\nbut this UI level change has been moved to the front---the series\nnow digs from the UI surface to the core.  The documentation has\nalso been moved to this patch, using the same text as the proposed\nlog message, as Michael Haggerty suggested.\n\n[PATCH v2 4/6] push --lockref: implement logic to populate old_sha1_expect[]\n\nThis is unchanged.\n\n[PATCH v2 5/6] push --lockref: tie it all together\n\nThis corresponds to old 3/7, but the semantics has been adjusted to\nallow \"--force\" override it.\n\n[PATCH v2 6/6] t5533: test \"push --lockref\"\n\nThis has been adjusted to the change in 5/6 (earlier \"--force\" would\nnot break the \"--lockref\" safety, but now it does).\n\nJunio C Hamano (6):\n  cache.h: move remote/connect API out of it\n  builtin/push.c: use OPT_BOOL, not OPT_BOOLEAN\n  remote.c: add command line option parser for \"--lockref\"\n  push --lockref: implement logic to populate old_sha1_expect[]\n  push --lockref: tie it all together\n  t5533: test \"push --lockref\"\n\n Documentation/git-push.txt |  68 +++++++++++++---\n builtin/fetch-pack.c       |   2 +\n builtin/push.c             |  18 ++++-\n builtin/receive-pack.c     |   1 +\n builtin/send-pack.c        |  26 +++++++\n cache.h                    |  62 ---------------\n connect.c                  |   1 +\n connect.h                  |  13 ++++\n fetch-pack.c               |   1 +\n fetch-pack.h               |   1 +\n refs.c                     |   8 --\n remote.c                   | 175 +++++++++++++++++++++++++++++++++++++----\n remote.h                   |  83 ++++++++++++++++++++\n send-pack.c                |   2 +\n t/t5533-push-cas.sh        | 189 +++++++++++++++++++++++++++++++++++++++++++++\n transport-helper.c         |   6 ++\n transport.c                |  13 ++++\n transport.h                |   5 ++\n upload-pack.c              |   1 +\n 19 files changed, 578 insertions(+), 97 deletions(-)\n create mode 100644 connect.h\n create mode 100755 t/t5533-push-cas.sh\n\n-- \n1.8.3.2-912-g65cf5cf\n"},{"id":"223102","messageId":"1373581580-13651-2-git-send-email-gitster@pobox.com","threadId":"34408","inReplyTo":"1373581580-13651-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 1/6] cache.h: move remote/connect API out of it","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-11T22:26:15Z","receivedAt":"2013-07-11T22:26:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The definition of \"struct ref\" in \"cache.h\", a header file so\ncentral to the system, always confused me.  This structure is not\nabout the local ref used by sha1-name API to name local objects.\n\nIt is what refspecs are expanded into, after finding out what refs\nthe other side has, to define what refs are updated after object\ntransfer succeeds to what values.  It belongs to \"remote.h\" together\nwith \"struct refspec\".\n\nWhile we are at it, also move the types and functions related to the\nGit transport connection to a new header file connect.h\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/fetch-pack.c   |  2 ++\n builtin/receive-pack.c |  1 +\n builtin/send-pack.c    |  1 +\n cache.h                | 62 --------------------------------------------------\n connect.c              |  1 +\n connect.h              | 13 +++++++++++\n fetch-pack.c           |  1 +\n fetch-pack.h           |  1 +\n refs.c                 |  8 -------\n remote.c               |  8 +++++++\n remote.h               | 54 +++++++++++++++++++++++++++++++++++++++++++\n send-pack.c            |  1 +\n transport.c            |  2 ++\n transport.h            |  1 +\n upload-pack.c          |  1 +\n 15 files changed, 87 insertions(+), 70 deletions(-)\n create mode 100644 connect.h\n\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex aba4465..c6888c6 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -1,6 +1,8 @@\n #include \"builtin.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"remote.h\"\n+#include \"connect.h\"\n \n static const char fetch_pack_usage[] =\n \"git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] \"\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex e3eb5fc..7434d9b 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -8,6 +8,7 @@\n #include \"commit.h\"\n #include \"object.h\"\n #include \"remote.h\"\n+#include \"connect.h\"\n #include \"transport.h\"\n #include \"string-list.h\"\n #include \"sha1-array.h\"\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 152c4ea..e86d3b5 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -5,6 +5,7 @@\n #include \"sideband.h\"\n #include \"run-command.h\"\n #include \"remote.h\"\n+#include \"connect.h\"\n #include \"send-pack.h\"\n #include \"quote.h\"\n #include \"transport.h\"\ndiff --git a/cache.h b/cache.h\nindex dd0fb33..cb2891d 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1035,68 +1035,6 @@ struct pack_entry {\n \tstruct packed_git *p;\n };\n \n-struct ref {\n-\tstruct ref *next;\n-\tunsigned char old_sha1[20];\n-\tunsigned char new_sha1[20];\n-\tchar *symref;\n-\tunsigned int\n-\t\tforce:1,\n-\t\tforced_update:1,\n-\t\tdeletion:1,\n-\t\tmatched:1;\n-\n-\t/*\n-\t * Order is important here, as we write to FETCH_HEAD\n-\t * in numeric order. And the default NOT_FOR_MERGE\n-\t * should be 0, so that xcalloc'd structures get it\n-\t * by default.\n-\t */\n-\tenum {\n-\t\tFETCH_HEAD_MERGE = -1,\n-\t\tFETCH_HEAD_NOT_FOR_MERGE = 0,\n-\t\tFETCH_HEAD_IGNORE = 1\n-\t} fetch_head_status;\n-\n-\tenum {\n-\t\tREF_STATUS_NONE = 0,\n-\t\tREF_STATUS_OK,\n-\t\tREF_STATUS_REJECT_NONFASTFORWARD,\n-\t\tREF_STATUS_REJECT_ALREADY_EXISTS,\n-\t\tREF_STATUS_REJECT_NODELETE,\n-\t\tREF_STATUS_REJECT_FETCH_FIRST,\n-\t\tREF_STATUS_REJECT_NEEDS_FORCE,\n-\t\tREF_STATUS_UPTODATE,\n-\t\tREF_STATUS_REMOTE_REJECT,\n-\t\tREF_STATUS_EXPECTING_REPORT\n-\t} status;\n-\tchar *remote_status;\n-\tstruct ref *peer_ref; /* when renaming */\n-\tchar name[FLEX_ARRAY]; /* more */\n-};\n-\n-#define REF_NORMAL\t(1u << 0)\n-#define REF_HEADS\t(1u << 1)\n-#define REF_TAGS\t(1u << 2)\n-\n-extern struct ref *find_ref_by_name(const struct ref *list, const char *name);\n-\n-#define CONNECT_VERBOSE       (1u << 0)\n-extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);\n-extern int finish_connect(struct child_process *conn);\n-extern int git_connection_is_socket(struct child_process *conn);\n-struct extra_have_objects {\n-\tint nr, alloc;\n-\tunsigned char (*array)[20];\n-};\n-extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,\n-\t\t\t\t     struct ref **list, unsigned int flags,\n-\t\t\t\t     struct extra_have_objects *);\n-extern int server_supports(const char *feature);\n-extern int parse_feature_request(const char *features, const char *feature);\n-extern const char *server_feature_value(const char *feature, int *len_ret);\n-extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);\n-\n extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);\n \n /* A hook for count-objects to report invalid files in pack directory */\ndiff --git a/connect.c b/connect.c\nindex a0783d4..a80ebd3 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -5,6 +5,7 @@\n #include \"refs.h\"\n #include \"run-command.h\"\n #include \"remote.h\"\n+#include \"connect.h\"\n #include \"url.h\"\n \n static char *server_capabilities;\ndiff --git a/connect.h b/connect.h\nnew file mode 100644\nindex 0000000..9dff25c\n--- /dev/null\n+++ b/connect.h\n@@ -0,0 +1,13 @@\n+#ifndef CONNECT_H\n+#define CONNECT_H\n+\n+#define CONNECT_VERBOSE       (1u << 0)\n+extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);\n+extern int finish_connect(struct child_process *conn);\n+extern int git_connection_is_socket(struct child_process *conn);\n+extern int server_supports(const char *feature);\n+extern int parse_feature_request(const char *features, const char *feature);\n+extern const char *server_feature_value(const char *feature, int *len_ret);\n+extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);\n+\n+#endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex abe5ffb..c2bab42 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -9,6 +9,7 @@\n #include \"fetch-pack.h\"\n #include \"remote.h\"\n #include \"run-command.h\"\n+#include \"connect.h\"\n #include \"transport.h\"\n #include \"version.h\"\n \ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 40f08ba..461cbf3 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -2,6 +2,7 @@\n #define FETCH_PACK_H\n \n #include \"string-list.h\"\n+#include \"run-command.h\"\n \n struct fetch_pack_args {\n \tconst char *uploadpack;\ndiff --git a/refs.c b/refs.c\nindex 4302206..330060c 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3193,14 +3193,6 @@ int update_ref(const char *action, const char *refname,\n \treturn 0;\n }\n \n-struct ref *find_ref_by_name(const struct ref *list, const char *name)\n-{\n-\tfor ( ; list; list = list->next)\n-\t\tif (!strcmp(list->name, name))\n-\t\t\treturn (struct ref *)list;\n-\treturn NULL;\n-}\n-\n /*\n  * generate a format suitable for scanf from a ref_rev_parse_rules\n  * rule, that is replace the \"%.*s\" spec with a \"%s\" spec\ndiff --git a/remote.c b/remote.c\nindex 6f57830..b1ff7a2 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1302,6 +1302,14 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds\n \tfree(sent_tips.tip);\n }\n \n+struct ref *find_ref_by_name(const struct ref *list, const char *name)\n+{\n+\tfor ( ; list; list = list->next)\n+\t\tif (!strcmp(list->name, name))\n+\t\t\treturn (struct ref *)list;\n+\treturn NULL;\n+}\n+\n /*\n  * Given the set of refs the local repository has, the set of refs the\n  * remote repository has, and the refspec used for push, determine\ndiff --git a/remote.h b/remote.h\nindex cf56724..a850059 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -71,6 +71,52 @@ struct refspec {\n \n extern const struct refspec *tag_refspec;\n \n+struct ref {\n+\tstruct ref *next;\n+\tunsigned char old_sha1[20];\n+\tunsigned char new_sha1[20];\n+\tchar *symref;\n+\tunsigned int\n+\t\tforce:1,\n+\t\tforced_update:1,\n+\t\tdeletion:1,\n+\t\tmatched:1;\n+\n+\t/*\n+\t * Order is important here, as we write to FETCH_HEAD\n+\t * in numeric order. And the default NOT_FOR_MERGE\n+\t * should be 0, so that xcalloc'd structures get it\n+\t * by default.\n+\t */\n+\tenum {\n+\t\tFETCH_HEAD_MERGE = -1,\n+\t\tFETCH_HEAD_NOT_FOR_MERGE = 0,\n+\t\tFETCH_HEAD_IGNORE = 1\n+\t} fetch_head_status;\n+\n+\tenum {\n+\t\tREF_STATUS_NONE = 0,\n+\t\tREF_STATUS_OK,\n+\t\tREF_STATUS_REJECT_NONFASTFORWARD,\n+\t\tREF_STATUS_REJECT_ALREADY_EXISTS,\n+\t\tREF_STATUS_REJECT_NODELETE,\n+\t\tREF_STATUS_REJECT_FETCH_FIRST,\n+\t\tREF_STATUS_REJECT_NEEDS_FORCE,\n+\t\tREF_STATUS_UPTODATE,\n+\t\tREF_STATUS_REMOTE_REJECT,\n+\t\tREF_STATUS_EXPECTING_REPORT\n+\t} status;\n+\tchar *remote_status;\n+\tstruct ref *peer_ref; /* when renaming */\n+\tchar name[FLEX_ARRAY]; /* more */\n+};\n+\n+#define REF_NORMAL\t(1u << 0)\n+#define REF_HEADS\t(1u << 1)\n+#define REF_TAGS\t(1u << 2)\n+\n+extern struct ref *find_ref_by_name(const struct ref *list, const char *name);\n+\n struct ref *alloc_ref(const char *name);\n struct ref *copy_ref(const struct ref *ref);\n struct ref *copy_ref_list(const struct ref *ref);\n@@ -84,6 +130,14 @@ int check_ref_type(const struct ref *ref, int flags);\n  */\n void free_refs(struct ref *ref);\n \n+struct extra_have_objects {\n+\tint nr, alloc;\n+\tunsigned char (*array)[20];\n+};\n+extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,\n+\t\t\t\t     struct ref **list, unsigned int flags,\n+\t\t\t\t     struct extra_have_objects *);\n+\n int resolve_remote_symref(struct ref *ref, struct ref *list);\n int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1);\n \ndiff --git a/send-pack.c b/send-pack.c\nindex 7d172ef..9a9908c 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -5,6 +5,7 @@\n #include \"sideband.h\"\n #include \"run-command.h\"\n #include \"remote.h\"\n+#include \"connect.h\"\n #include \"send-pack.h\"\n #include \"quote.h\"\n #include \"transport.h\"\ndiff --git a/transport.c b/transport.c\nindex e15db98..b84dbf0 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -3,6 +3,8 @@\n #include \"run-command.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"remote.h\"\n+#include \"connect.h\"\n #include \"send-pack.h\"\n #include \"walker.h\"\n #include \"bundle.h\"\ndiff --git a/transport.h b/transport.h\nindex ea70ea7..b551f99 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -2,6 +2,7 @@\n #define TRANSPORT_H\n \n #include \"cache.h\"\n+#include \"run-command.h\"\n #include \"remote.h\"\n \n struct git_transport_options {\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 127e59a..b03492e 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -10,6 +10,7 @@\n #include \"revision.h\"\n #include \"list-objects.h\"\n #include \"run-command.h\"\n+#include \"connect.h\"\n #include \"sigchain.h\"\n #include \"version.h\"\n #include \"string-list.h\"\n-- \n1.8.3.2-912-g65cf5cf\n"},{"id":"223103","messageId":"1373581580-13651-3-git-send-email-gitster@pobox.com","threadId":"34408","inReplyTo":"1373581580-13651-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 2/6] builtin/push.c: use OPT_BOOL, not OPT_BOOLEAN","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-11T22:26:16Z","receivedAt":"2013-07-11T22:26:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The command line parser of \"git push\" for \"--tags\", \"--delete\", and\n\"--thin\" options still used outdated OPT_BOOLEAN.  Because these\noptions do not give escalating levels when given multiple times,\nthey should use OPT_BOOL.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/push.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 2d84d10..342d792 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -427,15 +427,15 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT( 0 , \"all\", &flags, N_(\"push all refs\"), TRANSPORT_PUSH_ALL),\n \t\tOPT_BIT( 0 , \"mirror\", &flags, N_(\"mirror all refs\"),\n \t\t\t    (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),\n-\t\tOPT_BOOLEAN( 0, \"delete\", &deleterefs, N_(\"delete refs\")),\n-\t\tOPT_BOOLEAN( 0 , \"tags\", &tags, N_(\"push tags (can't be used with --all or --mirror)\")),\n+\t\tOPT_BOOL( 0, \"delete\", &deleterefs, N_(\"delete refs\")),\n+\t\tOPT_BOOL( 0 , \"tags\", &tags, N_(\"push tags (can't be used with --all or --mirror)\")),\n \t\tOPT_BIT('n' , \"dry-run\", &flags, N_(\"dry run\"), TRANSPORT_PUSH_DRY_RUN),\n \t\tOPT_BIT( 0,  \"porcelain\", &flags, N_(\"machine-readable output\"), TRANSPORT_PUSH_PORCELAIN),\n \t\tOPT_BIT('f', \"force\", &flags, N_(\"force updates\"), TRANSPORT_PUSH_FORCE),\n \t\t{ OPTION_CALLBACK, 0, \"recurse-submodules\", &flags, N_(\"check\"),\n \t\t\tN_(\"control recursive pushing of submodules\"),\n \t\t\tPARSE_OPT_OPTARG, option_parse_recurse_submodules },\n-\t\tOPT_BOOLEAN( 0 , \"thin\", &thin, N_(\"use thin pack\")),\n+\t\tOPT_BOOL( 0 , \"thin\", &thin, N_(\"use thin pack\")),\n \t\tOPT_STRING( 0 , \"receive-pack\", &receivepack, \"receive-pack\", N_(\"receive pack program\")),\n \t\tOPT_STRING( 0 , \"exec\", &receivepack, \"receive-pack\", N_(\"receive pack program\")),\n \t\tOPT_BIT('u', \"set-upstream\", &flags, N_(\"set upstream for git pull/status\"),\n-- \n1.8.3.2-912-g65cf5cf\n"},{"id":"223104","messageId":"1373581580-13651-4-git-send-email-gitster@pobox.com","threadId":"34408","inReplyTo":"1373581580-13651-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 3/6] remote.c: add command line option parser for \"--lockref\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-11T22:26:17Z","receivedAt":"2013-07-11T22:26:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Update \"git push\" and \"git send-pack\" to parse this commnd line\noption.\n\nThe intended sematics is:\n\n * \"--lockref\" alone, without specifying the details, will protect\n   _all_ remote refs that are going to be updated by requiring their\n   current value to be the same as the remote-tracking branch we\n   have for them, unless otherwise specified;\n\n * \"--lockref=refname\", without specifying the expected value, will\n   protect that refname, if it is going to be updated, by requiring\n   its current value to be the same as the remote-tracking branch we\n   have for it;\n\n * \"--lockref=refname:value\" will protect that refname, if it is\n   going to be updated, by requiring its current value to be the\n   same as the specified value (which is allowed to be different\n   from the remote-tracking branch we have for the refname, or we do\n   not even have to have such a remote-tracking branch when this\n   form is used); and\n\n * \"--no-lockref\" will cancel all the previous --lockref on the\n   command line.\n\nIn any of the forms, when we try to use a remote-tracking branch for\nthe remote ref being updated, it is an error if there is no such\nremote-tracking branch on our end.\n\nBecause the command line options are parsed _before_ we know which\nremote we are pushing to, there needs further processing to the\nparsed data after we instantiate the transport object to:\n\n * expand \"refname\" given by the user to a full refname to be\n   matched with the list of \"struct ref\" used in match_push_refs()\n   and set_ref_status_for_push(); and\n\n * learning the actual local ref that is the remote-tracking branch\n   for the specified remote ref.\n\nFurther, some processing need to be deferred until we find the set\nof remote refs and match_push_refs() returns in order to find the\nones that need to be checked after explicit ones have been processed\nfor \"--lockref\" (no specific details).\n\nThese post-processing will be the topic of the next patch.\n\nOh, of course, the option name is still not cast in stone.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n This round makes --lockref simply a weaker form of --force.\n\n The old version used to allow \"--lockref=<refname>:\" to say \"I want\n this push to create <refname>, any existing old value should make\n it fail\", but the normal fast-forward check already implements it,\n so there is no need for such an option.  This round removes it.\n\n The old version allowed you to express \"I want the old value of\n 'master' to be at X and also I want the update to fast-forward\" by\n giving only \"--lockref=master:X\" without \"--force\".  Because you\n already know if the commit you are updating their 'master' with\n fast-forwards from X or not, such a combination is unnecessary to\n support.  This simplifies the UI greatly, as \"--force\" can again be\n the big red button that overrides all the safety.\n\n I think the option now should be renamed to \"--force-with-???\"; you\n are essentially taking a lease on the ref before you start your\n rebase, and making sure the ref hasn't been updated by other\n people, you are safely forcing your push of a rebased history.\n\n Perhaps \"--force-with-lease\"?  \"--force-leased-ref\"?\n---\n Documentation/git-push.txt | 68 ++++++++++++++++++++++++++++++++++++++--------\n builtin/push.c             |  6 ++++\n builtin/send-pack.c        | 17 ++++++++++++\n remote.c                   | 57 ++++++++++++++++++++++++++++++++++++++\n remote.h                   | 22 +++++++++++++++\n 5 files changed, 159 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-push.txt b/Documentation/git-push.txt\nindex f7dfe48..b0a793e 100644\n--- a/Documentation/git-push.txt\n+++ b/Documentation/git-push.txt\n@@ -11,6 +11,7 @@ SYNOPSIS\n [verse]\n 'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]\n \t   [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]\n+\t   [--lockref[=<refname>[:<expect>]]]\n \t   [--no-verify] [<repository> [<refspec>...]]\n \n DESCRIPTION\n@@ -130,21 +131,66 @@ already exists on the remote side.\n \trepository over ssh, and you do not have the program in\n \ta directory on the default $PATH.\n \n+--[no-]lockref::\n+--lockref=<refname>::\n+--lockref=<refname>:<expect>::\n+\tUsually, the command refuses to update a remote ref that is\n+\tnot an ancestor of the local ref used to overwrite it.\n++\n+This option bypasses the check, but instead requires that the\n+current value of the ref to be the expected value.\n++\n+Imagine that you have to rebase what you have already published.\n+You will have to bypass the \"must fast-forward\" rule to replace the\n+history you originally published with the rebased history.  If\n+somebody else built on top of your original history while you are\n+rebasing, the tip of the branch at the remote may advance with her\n+commit, and blindly pushing with `--force` will lose her work.  By\n+using this option to specify that you expect the history you are\n+updating is what you rebased and want to replace, you can make sure\n+other people's work will not be losed by a forced push. in such a\n+case.\n++\n+`--lockref` alone, without specifying the details, will protect\n+all remote refs that are going to be updated by requiring their\n+current value to be the same as the remote-tracking branch we have\n+for them, unless specified with a `--lockref=<refname>:<expect>`\n+option that explicitly states what the expected value is.\n++\n+`--lockref=<refname>`, without specifying the expected value, will\n+protect the named ref (alone), if it is going to be updated, by\n+requiring its current value to be the same as the remote-tracking\n+branch we have for it.\n++\n+`--lockref=<refname>:<expect>\" will protect the named ref (alone),\n+if it is going to be updated, by requiring its current value to be\n+the same as the specified value <expect> (which is allowed to be\n+different from the remote-tracking branch we have for the refname,\n+or we do not even have to have such a remote-tracking branch when\n+this form is used).\n++\n+\"--no-lockref\" will cancel all the previous --lockref on the\n+command line.\n+\n -f::\n --force::\n \tUsually, the command refuses to update a remote ref that is\n \tnot an ancestor of the local ref used to overwrite it.\n-\tThis flag disables the check.  This can cause the\n-\tremote repository to lose commits; use it with care.\n-\tNote that `--force` applies to all the refs that are pushed,\n-\thence using it with `push.default` set to `matching` or with\n-\tmultiple push destinations configured with `remote.*.push`\n-\tmay overwrite refs other than the current branch (including\n-\tlocal refs that are strictly behind their remote counterpart).\n-\tTo force a push to only one branch, use a `+` in front of the\n-\trefspec to push\t(e.g `git push origin +master` to force a push\n-\tto the `master`\tbranch). See the `<refspec>...` section above\n-\tfor details.\n+\tAlso, when `--lockref` option is used, the command refuses\n+\tto update a remote ref whose current value does not match\n+\twhat is expected.\n++\n+This flag disables these checks, and can cause the remote repository\n+to lose commits; use it with care.\n++\n+Note that `--force` applies to all the refs that are pushed, hence\n+using it with `push.default` set to `matching` or with multiple push\n+destinations configured with `remote.*.push` may overwrite refs\n+other than the current branch (including local refs that are\n+strictly behind their remote counterpart).  To force a push to only\n+one branch, use a `+` in front of the refspec to push (e.g `git push\n+origin +master` to force a push to the `master` branch). See the\n+`<refspec>...` section above for details.\n \n --repo=<repository>::\n \tThis option is only relevant if no <repository> argument is\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 342d792..31a5ba0 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -21,6 +21,8 @@ static const char *receivepack;\n static int verbosity;\n static int progress = -1;\n \n+static struct push_cas_option cas;\n+\n static const char **refspec;\n static int refspec_nr;\n static int refspec_alloc;\n@@ -432,6 +434,10 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT('n' , \"dry-run\", &flags, N_(\"dry run\"), TRANSPORT_PUSH_DRY_RUN),\n \t\tOPT_BIT( 0,  \"porcelain\", &flags, N_(\"machine-readable output\"), TRANSPORT_PUSH_PORCELAIN),\n \t\tOPT_BIT('f', \"force\", &flags, N_(\"force updates\"), TRANSPORT_PUSH_FORCE),\n+\t\t{ OPTION_CALLBACK,\n+\t\t  0, CAS_OPT_NAME, &cas, N_(\"refname>:<expect\"),\n+\t\t  N_(\"require old value of ref to be at this value\"),\n+\t\t  PARSE_OPT_OPTARG, parseopt_push_cas_option },\n \t\t{ OPTION_CALLBACK, 0, \"recurse-submodules\", &flags, N_(\"check\"),\n \t\t\tN_(\"control recursive pushing of submodules\"),\n \t\t\tPARSE_OPT_OPTARG, option_parse_recurse_submodules },\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex e86d3b5..a23b26d 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -103,6 +103,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\n \tint flags;\n \tunsigned int reject_reasons;\n \tint progress = -1;\n+\tstruct push_cas_option cas = {0};\n \n \targv++;\n \tfor (i = 1; i < argc; i++, argv++) {\n@@ -165,6 +166,22 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\n \t\t\t\thelper_status = 1;\n \t\t\t\tcontinue;\n \t\t\t}\n+\t\t\tif (!strcmp(arg, \"--\" CAS_OPT_NAME)) {\n+\t\t\t\tif (parse_push_cas_option(&cas, NULL, 0) < 0)\n+\t\t\t\t\texit(1);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tif (!strcmp(arg, \"--no-\" CAS_OPT_NAME)) {\n+\t\t\t\tif (parse_push_cas_option(&cas, NULL, 1) < 0)\n+\t\t\t\t\texit(1);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tif (!prefixcmp(arg, \"--\" CAS_OPT_NAME \"=\")) {\n+\t\t\t\tif (parse_push_cas_option(&cas,\n+\t\t\t\t\t\t\t  strchr(arg, '=') + 1, 1) < 0)\n+\t\t\t\t\texit(1);\n+\t\t\t\tcontinue;\n+\t\t\t}\n \t\t\tusage(send_pack_usage);\n \t\t}\n \t\tif (!dest) {\ndiff --git a/remote.c b/remote.c\nindex b1ff7a2..6014acd 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1921,3 +1921,60 @@ struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fet\n \tstring_list_clear(&ref_names, 0);\n \treturn stale_refs;\n }\n+\n+/*\n+ * Lockref aka CAS\n+ */\n+void clear_cas_option(struct push_cas_option *cas)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < cas->nr; i++)\n+\t\tfree(cas->entry->refname);\n+\tfree(cas->entry);\n+\tmemset(cas, 0, sizeof(*cas));\n+}\n+\n+static struct push_cas *add_cas_entry(struct push_cas_option *cas,\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      size_t refnamelen)\n+{\n+\tstruct push_cas *entry;\n+\tALLOC_GROW(cas->entry, cas->nr + 1, cas->alloc);\n+\tentry = &cas->entry[cas->nr++];\n+\tmemset(entry, 0, sizeof(*entry));\n+\tentry->refname = xmemdupz(refname, refnamelen);\n+\treturn entry;\n+}\n+\n+int parse_push_cas_option(struct push_cas_option *cas, const char *arg, int unset)\n+{\n+\tconst char *colon;\n+\tstruct push_cas *entry;\n+\n+\tif (unset) {\n+\t\t/* \"--no-lockref\" */\n+\t\tclear_cas_option(cas);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!arg) {\n+\t\t/* just \"--lockref\" */\n+\t\tcas->use_tracking_for_rest = 1;\n+\t\treturn 0;\n+\t}\n+\n+\t/* \"--lockref=refname\" or \"--lockref=refname:value\" */\n+\tcolon = strchrnul(arg, ':');\n+\tentry = add_cas_entry(cas, arg, colon - arg);\n+\tif (!*colon)\n+\t\tentry->use_tracking = 1;\n+\telse if (get_sha1(colon + 1, entry->expect))\n+\t\treturn error(\"cannot parse expected object name '%s'\", colon + 1);\n+\treturn 0;\n+}\n+\n+int parseopt_push_cas_option(const struct option *opt, const char *arg, int unset)\n+{\n+\treturn parse_push_cas_option(opt->value, arg, unset);\n+}\ndiff --git a/remote.h b/remote.h\nindex a850059..8070303 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -1,6 +1,8 @@\n #ifndef REMOTE_H\n #define REMOTE_H\n \n+#include \"parse-options.h\"\n+\n enum {\n \tREMOTE_CONFIG,\n \tREMOTE_REMOTES,\n@@ -226,4 +228,24 @@ struct ref *guess_remote_head(const struct ref *head,\n /* Return refs which no longer exist on remote */\n struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map);\n \n+/*\n+ * Lockref aka CAS\n+ */\n+#define CAS_OPT_NAME \"lockref\"\n+\n+struct push_cas_option {\n+\tunsigned use_tracking_for_rest:1;\n+\tstruct push_cas {\n+\t\tunsigned char expect[20];\n+\t\tunsigned use_tracking:1;\n+\t\tchar *refname;\n+\t} *entry;\n+\tint nr;\n+\tint alloc;\n+};\n+\n+extern int parseopt_push_cas_option(const struct option *, const char *arg, int unset);\n+extern int parse_push_cas_option(struct push_cas_option *, const char *arg, int unset);\n+extern void clear_cas_option(struct push_cas_option *);\n+\n #endif\n-- \n1.8.3.2-912-g65cf5cf\n"},{"id":"223107","messageId":"1373581580-13651-5-git-send-email-gitster@pobox.com","threadId":"34408","inReplyTo":"1373581580-13651-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 4/6] push --lockref: implement logic to populate old_sha1_expect[]","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-11T22:26:18Z","receivedAt":"2013-07-11T22:26:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This plugs the push_cas_option data collected by the command line\noption parser to the transport system with a new function\napply_push_cas(), which is called after match_push_refs() has\nalready been called.\n\nAt this point, we know which remote we are talking to, and what\nremote refs we are going to update, so we can fill in the details\nthat may have been missing from the command line, such as\n\n (1) what abbreviated refname the user gave us matches the actual\n     refname at the remote; and\n\n (2) which remote tracking branch in our local repository to read the\n     value of the object to expect at the remote.\n\nto populate the old_sha1_expect[] field of each of the remote ref.\n\nStill nobody uses this information, which is the topic of the next\npatch.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/push.c      |  6 ++++++\n builtin/send-pack.c |  3 +++\n remote.c            | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n remote.h            |  6 ++++++\n transport.c         |  6 ++++++\n transport.h         |  4 ++++\n 6 files changed, 86 insertions(+)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 31a5ba0..b0e3691 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -299,6 +299,12 @@ static int push_with_options(struct transport *transport, int flags)\n \tif (thin)\n \t\ttransport_set_option(transport, TRANS_OPT_THIN, \"yes\");\n \n+\tif (!is_empty_cas(&cas)) {\n+\t\tif (!transport->smart_options)\n+\t\t\tdie(\"underlying transport does not support --lockref option\");\n+\t\ttransport->smart_options->cas = &cas;\n+\t}\n+\n \tif (verbosity > 0)\n \t\tfprintf(stderr, _(\"Pushing to %s\\n\"), transport->url);\n \terr = transport_push(transport, refspec_nr, refspec, flags,\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex a23b26d..6027ead 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -242,6 +242,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\n \tif (match_push_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))\n \t\treturn -1;\n \n+\tif (!is_empty_cas(&cas))\n+\t\tapply_push_cas(&cas, remote, remote_refs);\n+\n \tset_ref_status_for_push(remote_refs, args.send_mirror,\n \t\targs.force_update);\n \ndiff --git a/remote.c b/remote.c\nindex 6014acd..93e5b65 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1978,3 +1978,64 @@ int parseopt_push_cas_option(const struct option *opt, const char *arg, int unse\n {\n \treturn parse_push_cas_option(opt->value, arg, unset);\n }\n+\n+int is_empty_cas(const struct push_cas_option *cas)\n+{\n+\treturn !cas->use_tracking_for_rest && !cas->nr;\n+}\n+\n+/*\n+ * Look at remote.fetch refspec and see if we have a remote\n+ * tracking branch for the refname there.  Fill its current\n+ * value in sha1[].\n+ * If we cannot do so, return negative to signal an error.\n+ */\n+static int remote_tracking(struct remote *remote, const char *refname,\n+\t\t\t   unsigned char sha1[20])\n+{\n+\tchar *dst;\n+\n+\tdst = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);\n+\tif (!dst)\n+\t\treturn -1; /* no tracking ref for refname at remote */\n+\tif (read_ref(dst, sha1))\n+\t\treturn -1; /* we know what the tracking ref is but we cannot read it */\n+\treturn 0;\n+}\n+\n+static void apply_cas(struct push_cas_option *cas,\n+\t\t      struct remote *remote,\n+\t\t      struct ref *ref)\n+{\n+\tint i;\n+\n+\t/* Find an explicit --lockref=<name>[:<value>] entry */\n+\tfor (i = 0; i < cas->nr; i++) {\n+\t\tstruct push_cas *entry = &cas->entry[i];\n+\t\tif (!refname_match(entry->refname, ref->name, ref_rev_parse_rules))\n+\t\t\tcontinue;\n+\t\tref->expect_old_sha1 = 1;\n+\t\tif (!entry->use_tracking)\n+\t\t\thashcpy(ref->old_sha1_expect, cas->entry[i].expect);\n+\t\telse if (remote_tracking(remote, ref->name, ref->old_sha1_expect))\n+\t\t\tref->expect_old_no_trackback = 1;\n+\t\treturn;\n+\t}\n+\n+\t/* Are we using \"--lockref\" to cover all? */\n+\tif (!cas->use_tracking_for_rest)\n+\t\treturn;\n+\n+\tref->expect_old_sha1 = 1;\n+\tif (remote_tracking(remote, ref->name, ref->old_sha1_expect))\n+\t\tref->expect_old_no_trackback = 1;\n+}\n+\n+void apply_push_cas(struct push_cas_option *cas,\n+\t\t    struct remote *remote,\n+\t\t    struct ref *remote_refs)\n+{\n+\tstruct ref *ref;\n+\tfor (ref = remote_refs; ref; ref = ref->next)\n+\t\tapply_cas(cas, remote, ref);\n+}\ndiff --git a/remote.h b/remote.h\nindex 8070303..4c564c5 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -77,10 +77,13 @@ struct ref {\n \tstruct ref *next;\n \tunsigned char old_sha1[20];\n \tunsigned char new_sha1[20];\n+\tunsigned char old_sha1_expect[20]; /* used by expect-old */\n \tchar *symref;\n \tunsigned int\n \t\tforce:1,\n \t\tforced_update:1,\n+\t\texpect_old_sha1:1,\n+\t\texpect_old_no_trackback:1,\n \t\tdeletion:1,\n \t\tmatched:1;\n \n@@ -248,4 +251,7 @@ extern int parseopt_push_cas_option(const struct option *, const char *arg, int\n extern int parse_push_cas_option(struct push_cas_option *, const char *arg, int unset);\n extern void clear_cas_option(struct push_cas_option *);\n \n+extern int is_empty_cas(const struct push_cas_option *);\n+void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);\n+\n #endif\ndiff --git a/transport.c b/transport.c\nindex b84dbf0..5dd92b7 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1142,6 +1142,12 @@ int transport_push(struct transport *transport,\n \t\t\treturn -1;\n \t\t}\n \n+\t\tif (transport->smart_options &&\n+\t\t    transport->smart_options->cas &&\n+\t\t    !is_empty_cas(transport->smart_options->cas))\n+\t\t\tapply_push_cas(transport->smart_options->cas,\n+\t\t\t\t       transport->remote, remote_refs);\n+\n \t\tset_ref_status_for_push(remote_refs,\n \t\t\tflags & TRANSPORT_PUSH_MIRROR,\n \t\t\tflags & TRANSPORT_PUSH_FORCE);\ndiff --git a/transport.h b/transport.h\nindex b551f99..10f7556 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -14,6 +14,7 @@ struct git_transport_options {\n \tint depth;\n \tconst char *uploadpack;\n \tconst char *receivepack;\n+\tstruct push_cas_option *cas;\n };\n \n struct transport {\n@@ -127,6 +128,9 @@ struct transport *transport_get(struct remote *, const char *);\n /* Transfer the data as a thin pack if not null */\n #define TRANS_OPT_THIN \"thin\"\n \n+/* Check the current value of the remote ref */\n+#define TRANS_OPT_CAS \"cas\"\n+\n /* Keep the pack that was transferred if not null */\n #define TRANS_OPT_KEEP \"keep\"\n \n-- \n1.8.3.2-912-g65cf5cf\n"},{"id":"223105","messageId":"1373581580-13651-6-git-send-email-gitster@pobox.com","threadId":"34408","inReplyTo":"1373581580-13651-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 5/6] push --lockref: tie it all together","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-11T22:26:19Z","receivedAt":"2013-07-11T22:26:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This teaches the deepest part of the callchain for \"git push\" (and\n\"git send-pack\") to enforce \"the old value of the ref must be this,\notherwise fail this push\" (aka \"compare-and-swap\" / \"--lockref\").\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/send-pack.c |  5 +++++\n remote.c            | 49 ++++++++++++++++++++++++++++++++++++-------------\n remote.h            |  1 +\n send-pack.c         |  1 +\n transport-helper.c  |  6 ++++++\n transport.c         |  5 +++++\n 6 files changed, 54 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 6027ead..41dc512 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -55,6 +55,11 @@ static void print_helper_status(struct ref *ref)\n \t\t\tmsg = \"needs force\";\n \t\t\tbreak;\n \n+\t\tcase REF_STATUS_REJECT_STALE:\n+\t\t\tres = \"error\";\n+\t\t\tmsg = \"stale info\";\n+\t\t\tbreak;\n+\n \t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\t\tres = \"error\";\n \t\t\tmsg = \"already exists\";\ndiff --git a/remote.c b/remote.c\nindex 93e5b65..0109487 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1396,12 +1396,13 @@ int match_push_refs(struct ref *src, struct ref **dst,\n }\n \n void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,\n-\tint force_update)\n+\t\t\t     int force_update)\n {\n \tstruct ref *ref;\n \n \tfor (ref = remote_refs; ref; ref = ref->next) {\n \t\tint force_ref_update = ref->force || force_update;\n+\t\tint reject_reason = 0;\n \n \t\tif (ref->peer_ref)\n \t\t\thashcpy(ref->new_sha1, ref->peer_ref->new_sha1);\n@@ -1416,6 +1417,26 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,\n \t\t}\n \n \t\t/*\n+\t\t * Bypass the usual \"must fast-forward\" check but\n+\t\t * replace it with a weaker \"the old value must be\n+\t\t * this value we observed\".  If the remote ref has\n+\t\t * moved and is now different from what we expect,\n+\t\t * reject any push.\n+\t\t *\n+\t\t * It also is an error if the user told us to check\n+\t\t * with the remote-tracking branch to find the value\n+\t\t * to expect, but we did not have such a tracking\n+\t\t * branch.\n+\t\t */\n+\t\tif (ref->expect_old_sha1) {\n+\t\t\tif (ref->expect_old_no_trackback ||\n+\t\t\t    hashcmp(ref->old_sha1, ref->old_sha1_expect))\n+\t\t\t\treject_reason = REF_STATUS_REJECT_STALE;\n+\t\t}\n+\n+\t\t/*\n+\t\t * The usual \"must fast-forward\" rules.\n+\t\t *\n \t\t * Decide whether an individual refspec A:B can be\n \t\t * pushed.  The push will succeed if any of the\n \t\t * following are true:\n@@ -1433,24 +1454,26 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,\n \t\t *     passing the --force argument\n \t\t */\n \n-\t\tif (!ref->deletion && !is_null_sha1(ref->old_sha1)) {\n-\t\t\tint why = 0; /* why would this push require --force? */\n-\n+\t\telse if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {\n \t\t\tif (!prefixcmp(ref->name, \"refs/tags/\"))\n-\t\t\t\twhy = REF_STATUS_REJECT_ALREADY_EXISTS;\n+\t\t\t\treject_reason = REF_STATUS_REJECT_ALREADY_EXISTS;\n \t\t\telse if (!has_sha1_file(ref->old_sha1))\n-\t\t\t\twhy = REF_STATUS_REJECT_FETCH_FIRST;\n+\t\t\t\treject_reason = REF_STATUS_REJECT_FETCH_FIRST;\n \t\t\telse if (!lookup_commit_reference_gently(ref->old_sha1, 1) ||\n \t\t\t\t !lookup_commit_reference_gently(ref->new_sha1, 1))\n-\t\t\t\twhy = REF_STATUS_REJECT_NEEDS_FORCE;\n+\t\t\t\treject_reason = REF_STATUS_REJECT_NEEDS_FORCE;\n \t\t\telse if (!ref_newer(ref->new_sha1, ref->old_sha1))\n-\t\t\t\twhy = REF_STATUS_REJECT_NONFASTFORWARD;\n-\n-\t\t\tif (!force_ref_update)\n-\t\t\t\tref->status = why;\n-\t\t\telse if (why)\n-\t\t\t\tref->forced_update = 1;\n+\t\t\t\treject_reason = REF_STATUS_REJECT_NONFASTFORWARD;\n \t\t}\n+\n+\t\t/*\n+\t\t * \"--force\" will defeat any rejection implemented\n+\t\t * by the rules above.\n+\t\t */\n+\t\tif (!force_ref_update)\n+\t\t\tref->status = reject_reason;\n+\t\telse if (reject_reason)\n+\t\t\tref->forced_update = 1;\n \t}\n }\n \ndiff --git a/remote.h b/remote.h\nindex 4c564c5..baa1c68 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -107,6 +107,7 @@ struct ref {\n \t\tREF_STATUS_REJECT_NODELETE,\n \t\tREF_STATUS_REJECT_FETCH_FIRST,\n \t\tREF_STATUS_REJECT_NEEDS_FORCE,\n+\t\tREF_STATUS_REJECT_STALE,\n \t\tREF_STATUS_UPTODATE,\n \t\tREF_STATUS_REMOTE_REJECT,\n \t\tREF_STATUS_EXPECTING_REPORT\ndiff --git a/send-pack.c b/send-pack.c\nindex 9a9908c..b228d65 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -227,6 +227,7 @@ int send_pack(struct send_pack_args *args,\n \t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\tcase REF_STATUS_REJECT_FETCH_FIRST:\n \t\tcase REF_STATUS_REJECT_NEEDS_FORCE:\n+\t\tcase REF_STATUS_REJECT_STALE:\n \t\tcase REF_STATUS_UPTODATE:\n \t\t\tcontinue;\n \t\tdefault:\ndiff --git a/transport-helper.c b/transport-helper.c\nindex db9bd18..95d22f8 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -683,6 +683,11 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t\tfree(msg);\n \t\t\tmsg = NULL;\n \t\t}\n+\t\telse if (!strcmp(msg, \"stale info\")) {\n+\t\t\tstatus = REF_STATUS_REJECT_STALE;\n+\t\t\tfree(msg);\n+\t\t\tmsg = NULL;\n+\t\t}\n \t}\n \n \tif (*ref)\n@@ -756,6 +761,7 @@ static int push_refs_with_push(struct transport *transport,\n \t\t/* Check for statuses set by set_ref_status_for_push() */\n \t\tswitch (ref->status) {\n \t\tcase REF_STATUS_REJECT_NONFASTFORWARD:\n+\t\tcase REF_STATUS_REJECT_STALE:\n \t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\tcase REF_STATUS_UPTODATE:\n \t\t\tcontinue;\ndiff --git a/transport.c b/transport.c\nindex 5dd92b7..b321d6a 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -709,6 +709,10 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n \t\t\t\t\t\t \"needs force\", porcelain);\n \t\tbreak;\n+\tcase REF_STATUS_REJECT_STALE:\n+\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\t\t\t\t\t \"stale info\", porcelain);\n+\t\tbreak;\n \tcase REF_STATUS_REMOTE_REJECT:\n \t\tprint_ref_status('!', \"[remote rejected]\", ref,\n \t\t\t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n@@ -1078,6 +1082,7 @@ static int run_pre_push_hook(struct transport *transport,\n \tfor (r = remote_refs; r; r = r->next) {\n \t\tif (!r->peer_ref) continue;\n \t\tif (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue;\n+\t\tif (r->status == REF_STATUS_REJECT_STALE) continue;\n \t\tif (r->status == REF_STATUS_UPTODATE) continue;\n \n \t\tstrbuf_reset(&buf);\n-- \n1.8.3.2-912-g65cf5cf\n"},{"id":"223106","messageId":"1373581580-13651-7-git-send-email-gitster@pobox.com","threadId":"34408","inReplyTo":"1373581580-13651-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 6/6] t5533: test \"push --lockref\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-11T22:26:20Z","receivedAt":"2013-07-11T22:26:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Prepare two repositories, src and dst, the latter of which is a\nclone of the former (with tracking branches), and push from the\nlatter into the former, using --lockref=name (using tracking ref for\n\"name\" when updating \"name\"), --lockref=name:value, --lockref=name:\n(i.e. check creation), and --lockref (using tracking ref for\nanything that we update).\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t5533-push-cas.sh | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 189 insertions(+)\n create mode 100755 t/t5533-push-cas.sh\n\ndiff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh\nnew file mode 100755\nindex 0000000..ea1c789\n--- /dev/null\n+++ b/t/t5533-push-cas.sh\n@@ -0,0 +1,189 @@\n+#!/bin/sh\n+\n+test_description='compare & swap push force/delete safety'\n+\n+. ./test-lib.sh\n+\n+setup_srcdst_basic () {\n+\trm -fr src dst &&\n+\tgit clone --no-local . src &&\n+\tgit clone --no-local src dst &&\n+\t(\n+\t\tcd src && git checkout HEAD^0\n+\t)\n+}\n+\n+test_expect_success setup '\n+\t: create template repository\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C\n+'\n+\n+test_expect_success 'push to update (protected)' '\n+\tsetup_srcdst_basic &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_commit D &&\n+\t\ttest_must_fail git push --lockref=master:master origin master\n+\t) &&\n+\tgit ls-remote . refs/heads/master >expect &&\n+\tgit ls-remote src refs/heads/master >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'push to update (protected, forced)' '\n+\tsetup_srcdst_basic &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_commit D &&\n+\t\tgit push --force --lockref=master:master origin master\n+\t) &&\n+\tgit ls-remote dst refs/heads/master >expect &&\n+\tgit ls-remote src refs/heads/master >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'push to update (protected, tracking)' '\n+\tsetup_srcdst_basic &&\n+\t(\n+\t\tcd src &&\n+\t\tgit checkout master &&\n+\t\ttest_commit D &&\n+\t\tgit checkout HEAD^0\n+\t) &&\n+\tgit ls-remote src refs/heads/master >expect &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_commit E &&\n+\t\tgit ls-remote . refs/remotes/origin/master >expect &&\n+\t\ttest_must_fail git push --lockref=master origin master &&\n+\t\tgit ls-remote . refs/remotes/origin/master >actual &&\n+\t\ttest_cmp expect actual\n+\t) &&\n+\tgit ls-remote src refs/heads/master >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'push to update (protected, tracking, forced)' '\n+\tsetup_srcdst_basic &&\n+\t(\n+\t\tcd src &&\n+\t\tgit checkout master &&\n+\t\ttest_commit D &&\n+\t\tgit checkout HEAD^0\n+\t) &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_commit E &&\n+\t\tgit ls-remote . refs/remotes/origin/master >expect &&\n+\t\tgit push --force --lockref=master origin master\n+\t) &&\n+\tgit ls-remote dst refs/heads/master >expect &&\n+\tgit ls-remote src refs/heads/master >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'push to update (allowed)' '\n+\tsetup_srcdst_basic &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_commit D &&\n+\t\tgit push --lockref=master:master^ origin master\n+\t) &&\n+\tgit ls-remote dst refs/heads/master >expect &&\n+\tgit ls-remote src refs/heads/master >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'push to update (allowed, tracking)' '\n+\tsetup_srcdst_basic &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_commit D &&\n+\t\tgit push --lockref=master origin master\n+\t) &&\n+\tgit ls-remote dst refs/heads/master >expect &&\n+\tgit ls-remote src refs/heads/master >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'push to update (allowed even though no-ff)' '\n+\tsetup_srcdst_basic &&\n+\t(\n+\t\tcd dst &&\n+\t\tgit reset --hard HEAD^ &&\n+\t\ttest_commit D &&\n+\t\tgit push --lockref=master origin master\n+\t) &&\n+\tgit ls-remote dst refs/heads/master >expect &&\n+\tgit ls-remote src refs/heads/master >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'push to delete (protected)' '\n+\tsetup_srcdst_basic &&\n+\tgit ls-remote src refs/heads/master >expect &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_must_fail git push --lockref=master:master^ origin :master\n+\t) &&\n+\tgit ls-remote src refs/heads/master >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'push to delete (protected, forced)' '\n+\tsetup_srcdst_basic &&\n+\t(\n+\t\tcd dst &&\n+\t\tgit push --force --lockref=master:master^ origin :master\n+\t) &&\n+\t>expect &&\n+\tgit ls-remote src refs/heads/master >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'push to delete (allowed)' '\n+\tsetup_srcdst_basic &&\n+\t(\n+\t\tcd dst &&\n+\t\tgit push --lockref=master origin :master\n+\t) &&\n+\t>expect &&\n+\tgit ls-remote src refs/heads/master >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'cover everything with default lockref (protected)' '\n+\tsetup_srcdst_basic &&\n+\t(\n+\t\tcd src &&\n+\t\tgit branch naster master^\n+\t)\n+\tgit ls-remote src refs/heads/\\* >expect &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_must_fail git push --lockref origin master master:naster\n+\t) &&\n+\tgit ls-remote src refs/heads/\\* >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'cover everything with default lockref (allowed)' '\n+\tsetup_srcdst_basic &&\n+\t(\n+\t\tcd src &&\n+\t\tgit branch naster master^\n+\t)\n+\t(\n+\t\tcd dst &&\n+\t\tgit fetch &&\n+\t\tgit push --lockref origin master master:naster\n+\t) &&\n+\tgit ls-remote dst refs/heads/master |\n+\tsed -e \"s/master/naster/\" >expect &&\n+\tgit ls-remote src refs/heads/naster >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n1.8.3.2-912-g65cf5cf\n"}]}