{"thread":{"id":"34515","subject":"[PATCH v3 0/6] \"git push --cas/--lockref\" renamed to \"--force-with-lease\"","startedAt":"2013-07-23T06:43:14Z","lastAt":"2013-08-02T23:14:48Z","messageCount":14,"participants":["Junio C Hamano","Jakub Narebski","Philip Oakley","Eric Sunshine"],"isPatch":true,"patchVersion":3,"patchTotal":6},"messages":[{"id":"223965","messageId":"1374561800-938-1-git-send-email-gitster@pobox.com","threadId":"34515","inReplyTo":null,"subject":"[PATCH v3 0/6] \"git push --cas/--lockref\" renamed to \"--force-with-lease\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-23T06:43:14Z","receivedAt":"2013-07-23T06:43:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This is mostly unchanged since the previous round, except that\n\n * The option is spelled \"--force-with-lease=<ref>:<expect>\".\n   Nobody liked \"cas\" as it was too technical, many disliked\n   \"lockref\" because \"lock\" sounded as if push by others were\n   excluded by it while in fact this is to fail us.\n\n   The final name implies that it is related to the \"--force\" that\n   breaks the \"must fast-forward\" safety, but \"with-lease\" part\n   conveys that there is some reservation with the forcing.  The\n   observation you make before you start rebasing (or you ensure\n   that everything is expendable and decide to delete) is like\n   taking a lease on the ref, and as long as the lease is not\n   broken by others, you can push a non-fast-forward history to\n   replace what is at the remote.\n\n * The logic to choose default when the option is not given with an\n   explicit expected value is still \"the remote-tracking branch for\n   the ref being updated\", but the documentation warns users against\n   relying on that semantics, as it was shown to be fragile during\n   the discussion, and hopefully we will come up with a better and\n   more robust one to replace it.\n\nThe first two preparatory patches are the same since v2.  For the\nremainder, other than changes necessary to rename the option, the\ndocumentation part of [PATCH 3/6] has been updated to mark forms\nwithout explicit expect value as experimental.\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 \"--force-with-lease\"\n  push --force-with-lease: implement logic to populate old_sha1_expect[]\n  push --force-with-lease: tie it all together\n  t5533: test \"push --force-with-lease\"\n\n Documentation/git-push.txt |  77 +++++++++++++++---\n builtin/fetch-pack.c       |   2 +\n builtin/push.c             |  19 ++++-\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, 588 insertions(+), 97 deletions(-)\n create mode 100644 connect.h\n create mode 100755 t/t5533-push-cas.sh\n\n-- \n1.8.3.4-980-g8decd39\n"},{"id":"223971","messageId":"1374561800-938-2-git-send-email-gitster@pobox.com","threadId":"34515","inReplyTo":"1374561800-938-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 1/6] cache.h: move remote/connect API out of it","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-23T06:43:15Z","receivedAt":"2013-07-23T06:43: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.4-980-g8decd39\n"},{"id":"223968","messageId":"1374561800-938-3-git-send-email-gitster@pobox.com","threadId":"34515","inReplyTo":"1374561800-938-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 2/6] builtin/push.c: use OPT_BOOL, not OPT_BOOLEAN","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-23T06:43:16Z","receivedAt":"2013-07-23T06:43: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.4-980-g8decd39\n"},{"id":"223970","messageId":"1374561800-938-4-git-send-email-gitster@pobox.com","threadId":"34515","inReplyTo":"1374561800-938-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 3/6] remote.c: add command line option parser for \"--force-with-lease\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-23T06:43:17Z","receivedAt":"2013-07-23T06:43: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 * \"--force-with-lease\" alone, without specifying the details, will\n   protect _all_ remote refs that are going to be updated by\n   requiring their current value to be the same as some reasonable\n   default, unless otherwise specified;\n\n * \"--force-with-lease=refname\", without specifying the expected\n   value, will protect that refname, if it is going to be updated,\n   by requiring its current value to be the same as some reasonable\n   default.\n\n * \"--force-with-lease=refname:value\" will protect that refname, if\n   it is going to be updated, by requiring its current value to be\n   the same as the specified value; and\n\n * \"--no-force-with-lease\" will cancel all the previous --force-with-lease on the\n   command line.\n\nFor now, \"some reasonable default\" is tentatively defined as \"the\nvalue of the remote-tracking branch we have for the ref of the\nremote being updated\", and it is an error if we do not have such a\nremote-tracking branch.  But this is known to be fragile, its use is\nnot yet recommended, and hopefully we will find more reasonable\ndefault as we gain experience with this feature.  The manual marks\nthe feature as experimental unless the expected value is specified\nexplicitly for this reason.\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 \"--force-with-lease\" (no specific details).\n\nThese post-processing will be the topic of the next patch.\n\nThis option was originally called \"cas\" (for \"compare and swap\"),\nthe name which nobody liked because it was too technical.  The\nsecond attempt called it \"lockref\" (because it is conceptually like\npushing after taking a lock) but the word \"lock\" was hated because\nit implied that it may reject push by others, which is not the way\nthis option works.  This round calls it \"force-with-lease\".  You\nassume you took the lease on the ref when you fetched to decide what\nthe rebased history should be, and you can push back only if the\nlease has not been broken.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-push.txt | 77 +++++++++++++++++++++++++++++++++++++++-------\n builtin/push.c             |  6 ++++\n builtin/send-pack.c        | 17 ++++++++++\n remote.c                   | 57 ++++++++++++++++++++++++++++++++++\n remote.h                   | 22 +++++++++++++\n 5 files changed, 168 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-push.txt b/Documentation/git-push.txt\nindex f7dfe48..e2992f1 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   [--force-with-lease[=<refname>[:<expect>]]]\n \t   [--no-verify] [<repository> [<refspec>...]]\n \n DESCRIPTION\n@@ -130,21 +131,75 @@ 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-]force-with-lease::\n+--force-with-lease=<refname>::\n+--force-with-lease=<refname>:<expect>::\n+\tUsually, \"git push\" 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.  \"git push\"\n+fails otherwise.\n++\n+Imagine that you have to rebase what you have already published.\n+You will have to bypass the \"must fast-forward\" rule in order to\n+replace the history you originally published with the rebased history.\n+If 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.\n++\n+This option allows you to say that you expect the history you are\n+updating is what you rebased and want to replace. If the remote ref\n+still points at the commit you specified, you can be sure that no\n+other people did anything to the ref (it is like taking a \"lease\" on\n+the ref without explicitly locking it, and you update the ref while\n+making sure that your earlier \"lease\" is still valid).\n++\n+`--force-with-lease` 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 `--force-with-lease=<refname>:<expect>`\n+option that explicitly states what the expected value is.\n++\n+`--force-with-lease=<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+`--force-with-lease=<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+Note that all forms other than `--force-with-lease=<refname>:<expect>`\n+that specifies the expected current value of the ref explicitly are\n+still experimental and their semantics may change as we gain experience\n+with this feature.\n++\n+\"--no-force-with-lease\" will cancel all the previous --force-with-lease 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 `--force-with-lease` 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..0d38353 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+ * Compare-and-swap\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[i].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-<option>\" */\n+\t\tclear_cas_option(cas);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!arg) {\n+\t\t/* just \"--<option>\" */\n+\t\tcas->use_tracking_for_rest = 1;\n+\t\treturn 0;\n+\t}\n+\n+\t/* \"--<option>=refname\" or \"--<option>=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..843c3ce 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+ * Compare-and-swap\n+ */\n+#define CAS_OPT_NAME \"force-with-lease\"\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.4-980-g8decd39\n"},{"id":"223969","messageId":"1374561800-938-5-git-send-email-gitster@pobox.com","threadId":"34515","inReplyTo":"1374561800-938-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 4/6] push --force-with-lease: implement logic to populate old_sha1_expect[]","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-23T06:43:18Z","receivedAt":"2013-07-23T06:43: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\n     the value of the object to expect at the remote.\n\nto populate the old_sha1_expect[] field of each of the remote ref.\nAs stated in the documentation, the use of remote-tracking branch\nas the default is a tentative one, and we may come up with a better\nlogic as we gain experience.\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      |  7 ++++++\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, 87 insertions(+)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 31a5ba0..2fd0a70 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -299,6 +299,13 @@ 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 --%s option\",\n+\t\t\t    CAS_OPT_NAME);\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 0d38353..52e3a12 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 --<option>=<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 \"--<option>\" 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 843c3ce..ca3c8c8 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.4-980-g8decd39\n"},{"id":"223967","messageId":"1374561800-938-6-git-send-email-gitster@pobox.com","threadId":"34515","inReplyTo":"1374561800-938-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 5/6] push --force-with-lease: tie it all together","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-23T06:43:19Z","receivedAt":"2013-07-23T06:43: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 52e3a12..922822c 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 ca3c8c8..6c42554 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.4-980-g8decd39\n"},{"id":"223966","messageId":"1374561800-938-7-git-send-email-gitster@pobox.com","threadId":"34515","inReplyTo":"1374561800-938-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 6/6] t5533: test \"push --force-with-lease\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-23T06:43:20Z","receivedAt":"2013-07-23T06:43: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, with various --force-with-lease options.\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..ba20d83\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 --force-with-lease=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 --force-with-lease=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 --force-with-lease=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 --force-with-lease=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 --force-with-lease=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 --force-with-lease=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 --force-with-lease=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 --force-with-lease=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 --force-with-lease=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 --force-with-lease=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 force-with-lease (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 --force-with-lease 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 force-with-lease (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 --force-with-lease 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.4-980-g8decd39\n"},{"id":"223977","messageId":"loom.20130723T113216-324@post.gmane.org","threadId":"34515","inReplyTo":"1374561800-938-1-git-send-email-gitster@pobox.com","subject":"Re: [PATCH v3 0/6]","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2013-07-23T09:33:02Z","receivedAt":"2013-07-23T09:33:02Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Junio C Hamano <gitster <at> pobox.com> writes:\n\n> \n> This is mostly unchanged since the previous round, except that\n> \n>  * The option is spelled \"--force-with-lease=<ref>:<expect>\".\n>    Nobody liked \"cas\" as it was too technical, many disliked\n>    \"lockref\" because \"lock\" sounded as if push by others were\n>    excluded by it while in fact this is to fail us.\n\nPerhaps \"--force-gently\" ? :-)\n\n-- \nJakub Narębski\n"},{"id":"223992","messageId":"7v8v0x874x.fsf@alter.siamese.dyndns.org","threadId":"34515","inReplyTo":"loom.20130723T113216-324@post.gmane.org","subject":"Re: [PATCH v3 0/6]","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-23T18:26:38Z","receivedAt":"2013-07-23T18:26:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jakub Narebski <jnareb@gmail.com> writes:\n\n> Junio C Hamano <gitster <at> pobox.com> writes:\n>\n>> \n>> This is mostly unchanged since the previous round, except that\n>> \n>>  * The option is spelled \"--force-with-lease=<ref>:<expect>\".\n>>    Nobody liked \"cas\" as it was too technical, many disliked\n>>    \"lockref\" because \"lock\" sounded as if push by others were\n>>    excluded by it while in fact this is to fail us.\n>\n> Perhaps \"--force-gently\" ? :-)\n\nHmph.  But we usually use \"gently\" to mean \"do not give the end user\nan error message--the caller handles the error itself\".\n\nWhile the option lets you break the usual \"must fast-forward\" rule,\nit is more precise in that the remote ref must be pointing at not\njust any ancestor of what you are pushing, but has to be at the\nexact commit you specify.\n\nE.g. if you have built one commit on top of the shared branch, and\ntry to push it with \"push --cas=pu:HEAD^ HEAD:pu\" (because you know\none commit before the tip is where you started from), your push will\nbe rejected if somebody else did an equivalent of \"reset HEAD~3\" on\nthe receiving end (perhaps because the top commits recorded some\nmaterial inappropriate for the project).  Your new commit is still a\ndecendant of that rewound tip, and usual \"must fast-forward\" rule\nwould accept the push, but with \"push --cas=pu:HEAD^ HEAD:pu\", you\nwill notice that somebody wanted to rewind the tip and pushing your\nwork that contains these dropped commit contradicts with that wish.\n\nSo I dunno.\n"},{"id":"224006","messageId":"68F84DC48FCA4226A8F3C00AC42A9598@PhilipOakley","threadId":"34515","inReplyTo":"7v8v0x874x.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3 0/6]","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2013-07-23T21:26:47Z","receivedAt":"2013-07-23T21:26:47Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Junio C Hamano\" <gitster@pobox.com>\nSent: Tuesday, July 23, 2013 7:26 PM\n> Jakub Narebski <jnareb@gmail.com> writes:\n>\n>> Junio C Hamano <gitster <at> pobox.com> writes:\n>>\n>>>\n>>> This is mostly unchanged since the previous round, except that\n>>>\n>>>  * The option is spelled \"--force-with-lease=<ref>:<expect>\".\n>>>    Nobody liked \"cas\" as it was too technical, many disliked\n>>>    \"lockref\" because \"lock\" sounded as if push by others were\n>>>    excluded by it while in fact this is to fail us.\n>>\n>> Perhaps \"--force-gently\" ? :-)\n\nOr \"--force-carefully\" to better indicate the safety / care that is \nbeing applied?\n\n>\n> Hmph.  But we usually use \"gently\" to mean \"do not give the end user\n> an error message--the caller handles the error itself\".\n>\n> While the option lets you break the usual \"must fast-forward\" rule,\n> it is more precise in that the remote ref must be pointing at not\n> just any ancestor of what you are pushing, but has to be at the\n> exact commit you specify.\n>\n> E.g. if you have built one commit on top of the shared branch, and\n> try to push it with \"push --cas=pu:HEAD^ HEAD:pu\" (because you know\n> one commit before the tip is where you started from), your push will\n> be rejected if somebody else did an equivalent of \"reset HEAD~3\" on\n> the receiving end (perhaps because the top commits recorded some\n> material inappropriate for the project).  Your new commit is still a\n> decendant of that rewound tip, and usual \"must fast-forward\" rule\n> would accept the push, but with \"push --cas=pu:HEAD^ HEAD:pu\", you\n> will notice that somebody wanted to rewind the tip and pushing your\n> work that contains these dropped commit contradicts with that wish.\n>\n> So I dunno.\n> --\n"},{"id":"224014","messageId":"CAPig+cRfPeHZ-rmOzRTqdw7QkJKf4e2CxMr4-zDvekxH=ROBew@mail.gmail.com","threadId":"34515","inReplyTo":"68F84DC48FCA4226A8F3C00AC42A9598@PhilipOakley","subject":"Re: [PATCH v3 0/6]","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-07-23T23:18:49Z","receivedAt":"2013-07-23T23:18:49Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jul 23, 2013 at 5:26 PM, Philip Oakley <philipoakley@iee.org> wrote:\n> From: \"Junio C Hamano\" <gitster@pobox.com>\n> Sent: Tuesday, July 23, 2013 7:26 PM\n>> Jakub Narebski <jnareb@gmail.com> writes:\n>>> Junio C Hamano <gitster <at> pobox.com> writes:\n>>>> This is mostly unchanged since the previous round, except that\n>>>>\n>>>>  * The option is spelled \"--force-with-lease=<ref>:<expect>\".\n>>>>    Nobody liked \"cas\" as it was too technical, many disliked\n>>>>    \"lockref\" because \"lock\" sounded as if push by others were\n>>>>    excluded by it while in fact this is to fail us.\n>>>\n>>> Perhaps \"--force-gently\" ? :-)\n>\n> Or \"--force-carefully\" to better indicate the safety / care that is being\n> applied?\n\n[bike-shedding: on]\n\n--force-if-safe\n\n[bike-shedding: off]\n"},{"id":"224425","messageId":"7vk3k5s2wh.fsf@alter.siamese.dyndns.org","threadId":"34515","inReplyTo":"1374561800-938-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 7/6] t5540/5541: smart-http does not support \"--force-with-lease\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-01T18:05:02Z","receivedAt":"2013-08-01T18:05:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The push() method in remote-curl.c is not told and does not pass the\nnecessary information to underlying send-pack, so this extension\ndoes not yet work.  Leave a note in the test suite.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * This is primarily to give a target for other people to shoot at;\n   patches to allow us to flip this expect_failure to expect_success\n   are very much welcomed ;-)\n\n t/lib-httpd.sh | 16 ++++++++++++++++\n 1 file changed, 16 insertions(+)\n\ndiff --git a/t/lib-httpd.sh b/t/lib-httpd.sh\nindex 895b925..e2eca1f 100644\n--- a/t/lib-httpd.sh\n+++ b/t/lib-httpd.sh\n@@ -167,6 +167,22 @@ test_http_push_nonff() {\n \ttest_expect_success 'non-fast-forward push shows help message' '\n \t\ttest_i18ngrep \"Updates were rejected because\" output\n \t'\n+\n+\ttest_expect_failure 'force with lease aka cas' '\n+\t\tHEAD=$(\tcd \"$REMOTE_REPO\" && git rev-parse --verify HEAD ) &&\n+\t\ttest_when_finished '\\''\n+\t\t\t(cd \"$REMOTE_REPO\" && git update-ref HEAD \"$HEAD\")\n+\t\t'\\'' &&\n+\t\t(\n+\t\t\tcd \"$LOCAL_REPO\" &&\n+\t\t\tgit push -v --force-with-lease=$BRANCH:$HEAD origin\n+\t\t) &&\n+\t\tgit rev-parse --verify \"$BRANCH\" >expect &&\n+\t\t(\n+\t\t\tcd \"$REMOTE_REPO\" && git rev-parse --verify HEAD\n+\t\t) >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n }\n \n setup_askpass_helper() {\n-- \n1.8.4-rc0-154-gc668397\n"},{"id":"224468","messageId":"7va9kzptyl.fsf@alter.siamese.dyndns.org","threadId":"34515","inReplyTo":"1374561800-938-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 8/6] send-pack: fix parsing of --force-with-lease option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-02T23:13:22Z","receivedAt":"2013-08-02T23:13:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The last argument for parse_push_cas_option() is if it is \"unset\"\n(i.e. --no-force-with-lease), and we are parsing the option with an\nexplicit value here, so it has to be 0.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/send-pack.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 41dc512..4482f16 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -183,7 +183,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\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\t\t\t  strchr(arg, '=') + 1, 0) < 0)\n \t\t\t\t\texit(1);\n \t\t\t\tcontinue;\n \t\t\t}\n-- \n1.8.4-rc1-129-g1f3472b\n"},{"id":"224469","messageId":"7v4nb7ptw7.fsf@alter.siamese.dyndns.org","threadId":"34515","inReplyTo":"1374561800-938-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 9/6] push: teach --force-with-lease to smart-http transport","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-02T23:14:48Z","receivedAt":"2013-08-02T23:14:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"We have been passing enough information to enable the\ncompare-and-swap logic down to the transport layer, but the\ntransport helper was not passing it to smart-http transport.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * I didn't bother with the dumb commit walker push for obvious\n   reasons, but if somebody is inclined to add one, it shouldn't be\n   too hard to add.\n\n remote-curl.c        | 16 +++++++++++++++-\n t/lib-httpd.sh       |  3 ++-\n t/t5541-http-push.sh |  2 +-\n transport-helper.c   | 24 ++++++++++++++++++++++--\n 4 files changed, 40 insertions(+), 5 deletions(-)\n\ndiff --git a/remote-curl.c b/remote-curl.c\nindex 60eda63..53c8a3d 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -6,6 +6,7 @@\n #include \"exec_cmd.h\"\n #include \"run-command.h\"\n #include \"pkt-line.h\"\n+#include \"string-list.h\"\n #include \"sideband.h\"\n \n static struct remote *remote;\n@@ -20,6 +21,7 @@ struct options {\n \t\tthin : 1;\n };\n static struct options options;\n+static struct string_list cas_options = STRING_LIST_INIT_DUP;\n \n static int set_option(const char *name, const char *value)\n {\n@@ -66,6 +68,13 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n+\telse if (!strcmp(name, \"cas\")) {\n+\t\tstruct strbuf val = STRBUF_INIT;\n+\t\tstrbuf_addf(&val, \"--\" CAS_OPT_NAME \"=%s\", value);\n+\t\tstring_list_append(&cas_options, val.buf);\n+\t\tstrbuf_release(&val);\n+\t\treturn 0;\n+\t}\n \telse {\n \t\treturn 1 /* unsupported */;\n \t}\n@@ -789,8 +798,9 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)\n \tstruct rpc_state rpc;\n \tconst char **argv;\n \tint argc = 0, i, err;\n+\tstruct string_list_item *cas_option;\n \n-\targv = xmalloc((10 + nr_spec) * sizeof(char*));\n+\targv = xmalloc((10 + nr_spec + cas_options.nr) * sizeof(char *));\n \targv[argc++] = \"send-pack\";\n \targv[argc++] = \"--stateless-rpc\";\n \targv[argc++] = \"--helper-status\";\n@@ -803,6 +813,10 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)\n \telse if (options.verbosity > 1)\n \t\targv[argc++] = \"--verbose\";\n \targv[argc++] = options.progress ? \"--progress\" : \"--no-progress\";\n+\n+\tfor_each_string_list_item(cas_option, &cas_options)\n+\t\targv[argc++] = cas_option->string;\n+\n \targv[argc++] = url;\n \tfor (i = 0; i < nr_spec; i++)\n \t\targv[argc++] = specs[i];\ndiff --git a/t/lib-httpd.sh b/t/lib-httpd.sh\nindex e2eca1f..dab405d 100644\n--- a/t/lib-httpd.sh\n+++ b/t/lib-httpd.sh\n@@ -141,10 +141,11 @@ stop_httpd() {\n \t\t-f \"$TEST_PATH/apache.conf\" $HTTPD_PARA -k stop\n }\n \n-test_http_push_nonff() {\n+test_http_push_nonff () {\n \tREMOTE_REPO=$1\n \tLOCAL_REPO=$2\n \tBRANCH=$3\n+\tEXPECT_CAS_RESULT=${4-failure}\n \n \ttest_expect_success 'non-fast-forward push fails' '\n \t\tcd \"$REMOTE_REPO\" &&\ndiff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh\nindex beb00be..470ac54 100755\n--- a/t/t5541-http-push.sh\n+++ b/t/t5541-http-push.sh\n@@ -153,7 +153,7 @@ test_expect_success 'used receive-pack service' '\n '\n \n test_http_push_nonff \"$HTTPD_DOCUMENT_ROOT_PATH\"/test_repo.git \\\n-\t\"$ROOT_PATH\"/test_repo_clone master\n+\t\"$ROOT_PATH\"/test_repo_clone master \t\tsuccess\n \n test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper' '\n \t# create a dissimilarly-named remote ref so that git is unable to match the\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 95d22f8..e3a60d7 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -742,13 +742,15 @@ static void push_update_refs_status(struct helper_data *data,\n }\n \n static int push_refs_with_push(struct transport *transport,\n-\t\tstruct ref *remote_refs, int flags)\n+\t\t\t       struct ref *remote_refs, int flags)\n {\n \tint force_all = flags & TRANSPORT_PUSH_FORCE;\n \tint mirror = flags & TRANSPORT_PUSH_MIRROR;\n \tstruct helper_data *data = transport->data;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct ref *ref;\n+\tstruct string_list cas_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *cas_option;\n \n \tget_helper(transport);\n \tif (!data->push)\n@@ -784,11 +786,29 @@ static int push_refs_with_push(struct transport *transport,\n \t\tstrbuf_addch(&buf, ':');\n \t\tstrbuf_addstr(&buf, ref->name);\n \t\tstrbuf_addch(&buf, '\\n');\n+\n+\t\t/*\n+\t\t * The \"--force-with-lease\" options without explicit\n+\t\t * values to expect have already been expanded into\n+\t\t * the ref->old_sha1_expect[] field; we can ignore\n+\t\t * transport->smart_options->cas altogether and instead\n+\t\t * can enumerate them from the refs.\n+\t\t */\n+\t\tif (ref->expect_old_sha1) {\n+\t\t\tstruct strbuf cas = STRBUF_INIT;\n+\t\t\tstrbuf_addf(&cas, \"%s:%s\",\n+\t\t\t\t    ref->name, sha1_to_hex(ref->old_sha1_expect));\n+\t\t\tstring_list_append(&cas_options, strbuf_detach(&cas, NULL));\n+\t\t}\n \t}\n-\tif (buf.len == 0)\n+\tif (buf.len == 0) {\n+\t\tstring_list_clear(&cas_options, 0);\n \t\treturn 0;\n+\t}\n \n \tstandard_options(transport);\n+\tfor_each_string_list_item(cas_option, &cas_options)\n+\t\tset_helper_option(transport, \"cas\", cas_option->string);\n \n \tif (flags & TRANSPORT_PUSH_DRY_RUN) {\n \t\tif (set_helper_option(transport, \"dry-run\", \"true\") != 0)\n-- \n1.8.4-rc1-129-g1f3472b\n"}]}