{"thread":{"id":"20164","subject":"[PATCH v3 0/3] add push --current and remote.*.pushHeadOnly","startedAt":"2009-07-20T11:58:11Z","lastAt":"2009-07-20T22:09:07Z","messageCount":6,"participants":["Paolo Bonzini","Junio C Hamano"],"isPatch":true,"patchVersion":3,"patchTotal":3},"messages":[{"id":"118302","messageId":"1248091094-31485-1-git-send-email-bonzini@gnu.org","threadId":"20164","inReplyTo":null,"subject":"[PATCH v3 0/3] add push --current and remote.*.pushHeadOnly","fromName":"Paolo Bonzini","fromEmail":"bonzini@gnu.org","sentAt":"2009-07-20T11:58:11Z","receivedAt":"2009-07-20T11:58:11Z","isPatch":true,"sender":{"key":"bonzini@gnu.org","avatar":"https://avatars.githubusercontent.com/u/42082?v=4"},"body":"This second series is gets rid of the most annoying part (IMHO) of\npush.default = tracking, i.e. the fact that its behavior cannot be\nachieved using git's ordinary tools.  While autosetuppush is enough to\nset the refspecs correctly, push.tracking does not push _all_ tracked\nbranches, but only the current one (because it implicitly adds only one\nrefspec, while autosetuppush places them all in the configuration).\n\nWhat I introduce here is \"git push --current\" and a companion\nremote.*.pushHeadOnly option to make it the default.  The difference\nbetween \"git push HEAD\" and \"git push --current\" is that the latter\nwill still walk the remote.*.push refspecs, but honor only the one\nmatching HEAD.  This means that this option, unlike the \"HEAD\" refspec,\nsupports a destination name that differs from the source name.\n\nTogether with autosetuppush, this more or less achieves the same result\nas push.tracking, at least for newly created remotes.  Two (three?)\nsubsequent series will handle the transition.\n\nPatch 1 partially reverts bba0fd2 (push: do not give big warning when\nno preference is configured, 2009-07-18).  Patch 2 is the meat of the\nimplementation.  Most of it actually touches the transport mechanism,\nnot builtin-push.c (which covers only one detail about how to handle\n\"git push --current\" when the remote does not have a corresponding\npush refspec).  Patch 3 adds remote.*.pushHeadOnly.\n\nv3:\n\tdocumentation changes from Bjoern Steinbrink\n\tincludes tests for http-push.c changes\n\nv2:\n\tupdate to recent master\n\tchanges from Nanako's review\n\nPaolo Bonzini (3):\n      reintroduce PUSH_DEFAULT_UNSPECIFIED\n      push: add --current\n      push: add remote.*.pushHeadOnly configuration\n\n Documentation/config.txt   |    6 ++++\n Documentation/git-push.txt |   17 ++++++++++-\n builtin-push.c             |   17 +++++++++--\n cache.h                    |    1 +\n environment.c              |    2 +-\n http-push.c                |   27 ++++++++++++++----\n remote.c                   |   42 ++++++++++++++++++++++++-----\n remote.h                   |    3 ++\n t/t5516-fetch-push.sh      |   64 ++++++++++++++++++++++++++++++++++++++++++++\n t/t5540-http-push.sh       |   32 ++++++++++++++++++++++\n transport.c                |   22 ++++++++++++++-\n transport.h                |    1 +\n 12 files changed, 214 insertions(+), 20 deletions(-)\n"},{"id":"118304","messageId":"1248091094-31485-2-git-send-email-bonzini@gnu.org","threadId":"20164","inReplyTo":"1248091094-31485-1-git-send-email-bonzini@gnu.org","subject":"[PATCH 1/3] reintroduce PUSH_DEFAULT_UNSPECIFIED","fromName":"Paolo Bonzini","fromEmail":"bonzini@gnu.org","sentAt":"2009-07-20T11:58:12Z","receivedAt":"2009-07-20T11:58:12Z","isPatch":true,"sender":{"key":"bonzini@gnu.org","avatar":"https://avatars.githubusercontent.com/u/42082?v=4"},"body":"With the next patch, the default refspec for push will depend on\nwhether --current is being used.  Revert part of bba0fd2 (push:\ndo not give big warning when no preference is configured, 2009-07-18)\nto simplify the next patch.\n\nSigned-off-by: Paolo Bonzini <bonzini@gnu.org>\n---\n builtin-push.c |    1 +\n cache.h        |    1 +\n environment.c  |    2 +-\n 3 files changed, 3 insertions(+), 1 deletions(-)\n\ndiff --git a/builtin-push.c b/builtin-push.c\nindex 1d92e22..e678a9d 100644\n--- a/builtin-push.c\n+++ b/builtin-push.c\n@@ -69,6 +69,7 @@ static void setup_default_push_refspecs(void)\n \tgit_config(git_default_config, NULL);\n \tswitch (push_default) {\n \tdefault:\n+\tcase PUSH_DEFAULT_UNSPECIFIED:\n \tcase PUSH_DEFAULT_MATCHING:\n \t\tadd_refspec(\":\");\n \t\tbreak;\ndiff --git a/cache.h b/cache.h\nindex c72f125..f1e5ede 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -543,6 +543,7 @@ enum rebase_setup_type {\n };\n \n enum push_default_type {\n+\tPUSH_DEFAULT_UNSPECIFIED = -1,\n \tPUSH_DEFAULT_NOTHING = 0,\n \tPUSH_DEFAULT_MATCHING,\n \tPUSH_DEFAULT_TRACKING,\ndiff --git a/environment.c b/environment.c\nindex 720f26b..801a005 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -42,7 +42,7 @@ enum safe_crlf safe_crlf = SAFE_CRLF_WARN;\n unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;\n enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;\n enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n-enum push_default_type push_default = PUSH_DEFAULT_MATCHING;\n+enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n-- \n1.6.4.rc1.10.g26dbf.dirty\n"},{"id":"118303","messageId":"1248091094-31485-3-git-send-email-bonzini@gnu.org","threadId":"20164","inReplyTo":"1248091094-31485-1-git-send-email-bonzini@gnu.org","subject":"[PATCH 2/3] push: add --current","fromName":"Paolo Bonzini","fromEmail":"bonzini@gnu.org","sentAt":"2009-07-20T11:58:13Z","receivedAt":"2009-07-20T11:58:13Z","isPatch":true,"sender":{"key":"bonzini@gnu.org","avatar":"https://avatars.githubusercontent.com/u/42082?v=4"},"body":"This patch adds the --current option to git-push.  The option restricts\npushing to the current HEAD, even in the presence of wildcard refspecs\nin the configuration.  This achieves an effect similar to the \"tracking\"\nvalue of push.default, in that git push only pushes a subset of the\nentire push possibilities (the difference, of course, is that these\nare implicitly taken from remote.*.merge in the case of push.default =\ntracking).\n\nA secondary effect of --current is that, if there is no push.default\nspecified, the default push refspec will be \"HEAD\".  This conforms to the\nidea of pushing the current branch only and is in general more intuitive.\nFor example in a normal configuration, \"git push --current FOO\" would\ngive an error when pushing to an empty destination if this special\nbehavior was not there.\n\nThe option does not make sense, and is thus disabled, if explicit refspecs\nare given on the command line.\n\nSigned-off-by: Paolo Bonzini <bonzini@gnu.org>\nCc: Tay Ray Chuan <rctay89@gmail.com>\nCc: Bjoern Steinbrink <b.steinbrink@gmx.de>\n---\n\tIn the end, I decided not to mention *how* git achieves the DWIM\n\teffect for 'git push --current origin', but rather just say what\n\tit does in which circumstances.  Having both \"magic\" refspecs\n\t'HEAD' and ':' in the same sentence was too heavy.\n\n Documentation/git-push.txt |   17 +++++++++++++-\n builtin-push.c             |   16 ++++++++++---\n http-push.c                |   27 ++++++++++++++++++-----\n remote.c                   |   40 +++++++++++++++++++++++++++++------\n remote.h                   |    2 +\n t/t5516-fetch-push.sh      |   50 ++++++++++++++++++++++++++++++++++++++++++++\n t/t5540-http-push.sh       |   32 ++++++++++++++++++++++++++++\n transport.c                |   22 ++++++++++++++++++-\n transport.h                |    1 +\n 9 files changed, 187 insertions(+), 20 deletions(-)\n\ndiff --git a/Documentation/git-push.txt b/Documentation/git-push.txt\nindex 2653388..7b1f085 100644\n--- a/Documentation/git-push.txt\n+++ b/Documentation/git-push.txt\n@@ -9,8 +9,9 @@ git-push - Update remote refs along with associated objects\n SYNOPSIS\n --------\n [verse]\n-'git push' [--all | --mirror | --tags] [--dry-run] [--receive-pack=<git-receive-pack>]\n-\t   [--repo=<repository>] [-f | --force] [-v | --verbose]\n+'git push' [--all | --mirror | --tags | --current] [--dry-run]\n+\t   [--receive-pack=<git-receive-pack>] [--repo=<repository>]\n+\t   [-f | --force] [-v | --verbose]\n \t   [<repository> <refspec>...]\n \n DESCRIPTION\n@@ -71,6 +72,18 @@ nor in any Push line of the corresponding remotes file---see below).\n \tInstead of naming each ref to push, specifies that all\n \trefs under `$GIT_DIR/refs/heads/` be pushed.\n \n+--current::\n+\tRestrict pushing to the currently checked out branch head.\n+\t`git push` will determine the destination name of the current\n+\tbranch as usual, and then push it to the given remote.  In\n+\taddition, if there is no refspec in the configuration, no\n+\t`push.default` configuration, and no remote branch whose\n+\tname matches the currently checked out branch, git will\n+\tcreate the current branch in the remote with the same name.\n++\n+This option cannot be specified if an explicit refspec is given on the\n+command line, because it would be useless and possibly confusing.\n+\n --mirror::\n \tInstead of naming each ref to push, specifies that all\n \trefs under `$GIT_DIR/refs/` (which includes but is not\ndiff --git a/builtin-push.c b/builtin-push.c\nindex e678a9d..71d94a5 100644\n--- a/builtin-push.c\n+++ b/builtin-push.c\n@@ -10,7 +10,7 @@\n #include \"parse-options.h\"\n \n static const char * const push_usage[] = {\n-\t\"git push [--all | --mirror] [--dry-run] [--porcelain] [--tags] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v] [<repository> <refspec>...]\",\n+\t\"git push [--all | --mirror] [--current] [--dry-run] [--porcelain] [--tags] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v] [<repository> <refspec>...]\",\n \tNULL,\n };\n \n@@ -64,12 +64,17 @@ static void setup_push_tracking(void)\n \tadd_refspec(refspec.buf);\n }\n \n-static void setup_default_push_refspecs(void)\n+static void setup_default_push_refspecs(int flags)\n {\n+\tpush_default = PUSH_DEFAULT_UNSPECIFIED;\n \tgit_config(git_default_config, NULL);\n+\tif (push_default == PUSH_DEFAULT_UNSPECIFIED)\n+\t\tpush_default = (flags & TRANSPORT_PUSH_CURRENT\n+\t\t\t\t? PUSH_DEFAULT_CURRENT\n+\t\t\t\t: PUSH_DEFAULT_MATCHING);\n+\n \tswitch (push_default) {\n \tdefault:\n-\tcase PUSH_DEFAULT_UNSPECIFIED:\n \tcase PUSH_DEFAULT_MATCHING:\n \t\tadd_refspec(\":\");\n \t\tbreak;\n@@ -127,7 +132,7 @@ static int do_push(const char *repo, int flags)\n \t\t\trefspec = remote->push_refspec;\n \t\t\trefspec_nr = remote->push_refspec_nr;\n \t\t} else if (!(flags & TRANSPORT_PUSH_MIRROR))\n-\t\t\tsetup_default_push_refspecs();\n+\t\t\tsetup_default_push_refspecs(flags);\n \t}\n \terrs = 0;\n \tif (remote->pushurl_nr) {\n@@ -175,6 +180,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT( 0 , \"mirror\", &flags, \"mirror all refs\",\n \t\t\t    (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),\n \t\tOPT_BOOLEAN( 0 , \"tags\", &tags, \"push tags\"),\n+\t\tOPT_BIT( 0 , \"current\", &flags, \"push current HEAD only\", TRANSPORT_PUSH_CURRENT),\n \t\tOPT_BIT( 0 , \"dry-run\", &flags, \"dry run\", TRANSPORT_PUSH_DRY_RUN),\n \t\tOPT_BIT( 0,  \"porcelain\", &flags, \"machine-readable output\", TRANSPORT_PUSH_PORCELAIN),\n \t\tOPT_BIT('f', \"force\", &flags, \"force updates\", TRANSPORT_PUSH_FORCE),\n@@ -186,6 +192,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, options, push_usage, 0);\n \n+\tif ((argc > 1 || tags) && (flags & TRANSPORT_PUSH_CURRENT))\n+\t\treturn error (\"Cannot give --current together with --tags or a refspec.\");\n \tif (tags)\n \t\tadd_refspec(\"refs/tags/*\");\n \ndiff --git a/http-push.c b/http-push.c\nindex 00e83dc..9c93e91 100644\n--- a/http-push.c\n+++ b/http-push.c\n@@ -14,7 +14,7 @@\n #include <expat.h>\n \n static const char http_push_usage[] =\n-\"git http-push [--all] [--dry-run] [--force] [--verbose] <remote> [<head>...]\\n\";\n+\"git http-push [--all] [--current] [--dry-run] [--force] [--verbose] <remote> [<head>...]\\n\";\n \n #ifndef XML_STATUS_OK\n enum XML_Status {\n@@ -75,7 +75,7 @@ static int aborted;\n static signed char remote_dir_exists[256];\n \n static int push_verbosely;\n-static int push_all = MATCH_REFS_NONE;\n+static int match_flags = MATCH_REFS_NONE;\n static int force_all;\n static int dry_run;\n \n@@ -1802,7 +1802,11 @@ int main(int argc, char **argv)\n \n \t\tif (*arg == '-') {\n \t\t\tif (!strcmp(arg, \"--all\")) {\n-\t\t\t\tpush_all = MATCH_REFS_ALL;\n+\t\t\t\tmatch_flags |= MATCH_REFS_ALL;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tif (!strcmp(arg, \"--current\")) {\n+\t\t\t\tmatch_flags |= MATCH_REFS_HEAD_ONLY;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--force\")) {\n@@ -1904,7 +1908,17 @@ int main(int argc, char **argv)\n \t\tfetch_indices();\n \n \t/* Get a list of all local and remote heads to validate refspecs */\n-\tlocal_refs = get_local_heads();\n+\tif (match_flags && MATCH_REFS_HEAD_ONLY) {\n+\t\tlocal_refs = get_current_head();\n+\t\tif (!local_refs) {\n+\t\t\tfprintf(stderr, \"--current specified with no current branch.\\n\");\n+\t\t\trc = -1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\t}\n+\telse\n+\t\tlocal_refs = get_local_heads();\n+\n \tfprintf(stderr, \"Fetching remote heads...\\n\");\n \tget_dav_remote_heads();\n \trun_request_queue();\n@@ -1919,7 +1933,7 @@ int main(int argc, char **argv)\n \n \t/* match them up */\n \tif (match_refs(local_refs, &remote_refs,\n-\t\t       nr_refspec, (const char **) refspec, push_all)) {\n+\t\t       nr_refspec, (const char **) refspec, match_flags)) {\n \t\trc = -1;\n \t\tgoto cleanup;\n \t}\n@@ -2005,7 +2019,8 @@ int main(int argc, char **argv)\n \t\told_sha1_hex = NULL;\n \t\tcommit_argv[1] = \"--objects\";\n \t\tcommit_argv[2] = new_sha1_hex;\n-\t\tif (!push_all && !is_null_sha1(ref->old_sha1)) {\n+\t\tif (!(match_flags & MATCH_REFS_ALL)\n+\t\t    && !is_null_sha1(ref->old_sha1)) {\n \t\t\told_sha1_hex = xmalloc(42);\n \t\t\tsprintf(old_sha1_hex, \"^%s\",\n \t\t\t\tsha1_to_hex(ref->old_sha1));\ndiff --git a/remote.c b/remote.c\nindex c3ada2d..b5bf9a6 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -990,7 +990,7 @@ static char *guess_ref(const char *name, struct ref *peer)\n \n static int match_explicit(struct ref *src, struct ref *dst,\n \t\t\t  struct ref ***dst_tail,\n-\t\t\t  struct refspec *rs)\n+\t\t\t  struct refspec *rs, int head_only)\n {\n \tstruct ref *matched_src, *matched_dst;\n \tint copy_src;\n@@ -1007,14 +1007,26 @@ static int match_explicit(struct ref *src, struct ref *dst,\n \t\tcopy_src = 1;\n \t\tbreak;\n \tcase 0:\n-\t\t/* The source could be in the get_sha1() format\n+\t\t/*\n+\t\t * The source could be in the get_sha1() format,\n \t\t * not a reference name.  :refs/other is a\n \t\t * way to delete 'other' ref at the remote end.\n+\t\t * This case however could cause unwanted references\n+\t\t * to be stored in matched_dst->peer_ref for --current.\n+\t\t * In that case, all we can/want handle is HEAD.\n \t\t */\n-\t\tmatched_src = try_explicit_object_name(rs->src);\n+\t\tif (head_only) {\n+\t\t\tassert (!src->next);\n+\t\t\tif (strcmp(rs->src, \"HEAD\"))\n+\t\t\t\treturn 0;\n+\t\t\tmatched_src = src;\n+\t\t\tcopy_src = 1;\n+\t\t} else {\n+\t\t\tmatched_src = try_explicit_object_name(rs->src);\n+\t\t\tcopy_src = 0;\n+\t\t}\n \t\tif (!matched_src)\n \t\t\treturn error(\"src refspec %s does not match any.\", rs->src);\n-\t\tcopy_src = 0;\n \t\tbreak;\n \tdefault:\n \t\treturn error(\"src refspec %s matches more than one.\", rs->src);\n@@ -1068,11 +1080,11 @@ static int match_explicit(struct ref *src, struct ref *dst,\n \n static int match_explicit_refs(struct ref *src, struct ref *dst,\n \t\t\t       struct ref ***dst_tail, struct refspec *rs,\n-\t\t\t       int rs_nr)\n+\t\t\t       int rs_nr, int head_only)\n {\n \tint i, errs;\n \tfor (i = errs = 0; i < rs_nr; i++)\n-\t\terrs += match_explicit(src, dst, dst_tail, &rs[i]);\n+\t\terrs += match_explicit(src, dst, dst_tail, &rs[i], head_only);\n \treturn errs;\n }\n \n@@ -1118,6 +1130,7 @@ int match_refs(struct ref *src, struct ref **dst,\n \tstruct refspec *rs;\n \tint send_all = flags & MATCH_REFS_ALL;\n \tint send_mirror = flags & MATCH_REFS_MIRROR;\n+\tint head_only = flags & MATCH_REFS_HEAD_ONLY;\n \tint errs;\n \tstatic const char *default_refspec[] = { \":\", NULL };\n \tstruct ref **dst_tail = tail_ref(dst);\n@@ -1127,7 +1140,8 @@ int match_refs(struct ref *src, struct ref **dst,\n \t\trefspec = default_refspec;\n \t}\n \trs = parse_push_refspec(nr_refspec, (const char **) refspec);\n-\terrs = match_explicit_refs(src, *dst, &dst_tail, rs, nr_refspec);\n+\terrs = match_explicit_refs(src, *dst, &dst_tail, rs, nr_refspec,\n+\t\t\t\t   head_only);\n \n \t/* pick the remainder */\n \tfor ( ; src; src = src->next) {\n@@ -1527,6 +1541,18 @@ struct ref *get_local_heads(void)\n \treturn local_refs;\n }\n \n+struct ref *get_current_head(void)\n+{\n+\tstruct ref *local_refs = NULL, **local_tail = &local_refs;\n+\tstruct branch *branch = branch_get(NULL);\n+\tunsigned char sha1[20];\n+\tif (branch) {\n+\t\tget_sha1(branch->refname, sha1);\n+\t\tone_local_ref(branch->refname, sha1, 0, &local_tail);\n+\t}\n+\treturn local_refs;\n+}\n+\n struct ref *guess_remote_head(const struct ref *head,\n \t\t\t      const struct ref *refs,\n \t\t\t      int all)\ndiff --git a/remote.h b/remote.h\nindex 5db8420..8e5d5b4 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -137,6 +137,7 @@ enum match_refs_flags {\n \tMATCH_REFS_NONE\t\t= 0,\n \tMATCH_REFS_ALL \t\t= (1 << 0),\n \tMATCH_REFS_MIRROR\t= (1 << 1),\n+\tMATCH_REFS_HEAD_ONLY\t= (1 << 2),\n };\n \n /* Reporting of tracking info */\n@@ -144,6 +145,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs);\n int format_tracking_info(struct branch *branch, struct strbuf *sb);\n \n struct ref *get_local_heads(void);\n+struct ref *get_current_head(void);\n /*\n  * Find refs from a list which are likely to be pointed to by the given HEAD\n  * ref. If 'all' is false, returns the most likely ref; otherwise, returns a\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 2d2633f..a480cb2 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -586,4 +586,54 @@ test_expect_success 'push with branches containing #' '\n \tgit checkout master\n '\n \n+test_expect_success 'push --current succeeds on empty repository' '\n+\tgit init &&\n+\tmkdir b.git &&\n+\t(cd b.git && git init --bare) &&\n+\techo a > b &&\n+\tgit add b &&\n+\tgit commit -m a &&\n+\tgit checkout -b branch &&\n+\techo bb > b &&\n+\tgit add b &&\n+\tgit commit -m branch &&\n+\tgit checkout master &&\n+\tgit push --current b.git\n+\ttest $(git rev-parse master) = $(cd b.git && git rev-parse master)\n+'\n+\n+test_expect_success 'push --current always creates current branch' '\n+\tgit checkout branch &&\n+\tgit push --current b.git &&\n+\ttest $(git rev-parse branch) = $(cd b.git && git rev-parse branch)\n+'\n+\n+test_expect_success 'push --current does not push other branches' '\n+\tgit checkout master &&\n+\techo aa > b &&\n+\tgit commit -m master2 b &&\n+\tgit checkout branch &&\n+\tgit push --current b.git 2>&1 | grep \"Everything up-to-date\" &&\n+\ttest $(git rev-parse master^) = $(cd b.git && git rev-parse master)\n+'\n+\n+test_expect_success 'push --current does update the current branches' '\n+\techo cc > b &&\n+\tgit commit -m branch2 b &&\n+\tgit checkout master &&\n+\tgit push --current b.git &&\n+\ttest $(git rev-parse master) = $(cd b.git && git rev-parse master) &&\n+\ttest $(git rev-parse branch^) = $(cd b.git && git rev-parse branch)\n+'\n+\n+test_expect_success 'push --current respects configuration' '\n+\tgit config remote.bremote.url b.git &&\n+\tgit config remote.bremote.push refs/heads/master:refs/heads/master2 &&\n+\tgit push --current bremote &&\n+\ttest $(git rev-parse master) = $(cd b.git && git rev-parse master2)\n+\tgit checkout branch &&\n+\tgit push --current bremote 2>&1 | grep \"Everything up-to-date\" &&\n+\ttest $(git rev-parse branch^) = $(cd b.git && git rev-parse branch)\n+'\n+\n test_done\ndiff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh\nindex f4a2cf6..a70d13a 100755\n--- a/t/t5540-http-push.sh\n+++ b/t/t5540-http-push.sh\n@@ -118,6 +118,38 @@ test_expect_success 'create and delete remote branch' '\n \ttest_must_fail git show-ref --verify refs/remotes/origin/dev\n '\n \n+test_expect_success 'push --current and branch creation' '\n+\tcd \"$ROOT_PATH\"/test_repo_clone &&\n+\tgit checkout dev &&\n+\tgit push origin HEAD &&\n+\t: >path4 &&\n+\tgit add path4 &&\n+\ttest_tick &&\n+\tgit commit -m dev &&\n+\tgit checkout -b dev2 &&\n+\t: >path5 &&\n+\tgit add path5 &&\n+\ttest_tick &&\n+\tgit commit -m dev2 &&\n+\tgit push --current origin &&\n+\tgit fetch &&\n+\ttest $(git rev-parse origin/dev2) = $(git rev-parse dev2) &&\n+\ttest $(git rev-parse origin/dev) = $(git rev-parse dev^)\n+'\n+\n+test_expect_success 'push --current and branch update' '\n+\tcd \"$ROOT_PATH\"/test_repo_clone &&\n+\t: >path6 &&\n+\tgit add path6 &&\n+\ttest_tick &&\n+\tgit commit -m dev2 &&\n+\tgit checkout dev &&\n+\tgit push --current origin &&\n+\tgit fetch &&\n+\ttest $(git rev-parse origin/dev) = $(git rev-parse dev) &&\n+\ttest $(git rev-parse origin/dev2) = $(git rev-parse dev2^)\n+'\n+\n test_expect_success 'MKCOL sends directory names with trailing slashes' '\n \n \t! grep \"\\\"MKCOL.*[^/] HTTP/[^ ]*\\\"\" < \"$HTTPD_ROOT_PATH\"/access.log\ndiff --git a/transport.c b/transport.c\nindex de0d587..c7b6aaa 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -327,6 +327,11 @@ static int rsync_transport_push(struct transport *transport,\n \tif (flags & TRANSPORT_PUSH_ALL) {\n \t\tif (for_each_ref(write_one_ref, &temp_dir))\n \t\t\treturn -1;\n+\t} else if (flags & TRANSPORT_PUSH_CURRENT) {\n+\t\tstruct branch *branch = branch_get(NULL);\n+\t\tunsigned char sha1[20];\n+\t\tget_sha1(branch->name, sha1);\n+\t\twrite_one_ref(branch->name, sha1, 0, &temp_dir);\n \t} else if (write_refs_to_temp_dir(&temp_dir, refspec_nr, refspec))\n \t\treturn -1;\n \n@@ -406,6 +411,8 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons\n \targc = 1;\n \tif (flags & TRANSPORT_PUSH_ALL)\n \t\targv[argc++] = \"--all\";\n+\tif (flags & TRANSPORT_PUSH_CURRENT)\n+\t\targv[argc++] = \"--current\";\n \tif (flags & TRANSPORT_PUSH_FORCE)\n \t\targv[argc++] = \"--force\";\n \tif (flags & TRANSPORT_PUSH_DRY_RUN)\n@@ -1001,12 +1008,19 @@ int transport_push(struct transport *transport,\n {\n \tverify_remote_names(refspec_nr, refspec);\n \n+\tif (flags & TRANSPORT_PUSH_CURRENT) {\n+\t\tstruct branch *branch = branch_get(NULL);\n+\t\tif (!branch)\n+\t\t\treturn error(\"Tried to push current branch, but there \"\n+\t\t\t\t     \"is no current branch!\");\n+\t}\n+\n \tif (transport->push)\n \t\treturn transport->push(transport, refspec_nr, refspec, flags);\n \tif (transport->push_refs) {\n \t\tstruct ref *remote_refs =\n \t\t\ttransport->get_refs_list(transport, 1);\n-\t\tstruct ref *local_refs = get_local_heads();\n+\t\tstruct ref *local_refs;\n \t\tint match_flags = MATCH_REFS_NONE;\n \t\tint verbose = flags & TRANSPORT_PUSH_VERBOSE;\n \t\tint porcelain = flags & TRANSPORT_PUSH_PORCELAIN;\n@@ -1017,6 +1031,12 @@ int transport_push(struct transport *transport,\n \t\tif (flags & TRANSPORT_PUSH_MIRROR)\n \t\t\tmatch_flags |= MATCH_REFS_MIRROR;\n \n+\t\tif (flags & TRANSPORT_PUSH_CURRENT) {\n+\t\t\tlocal_refs = get_current_head();\n+\t\t\tmatch_flags |= MATCH_REFS_HEAD_ONLY;\n+\t\t} else\n+\t\t\tlocal_refs = get_local_heads();\n+\n \t\tif (match_refs(local_refs, &remote_refs,\n \t\t\t       refspec_nr, refspec, match_flags)) {\n \t\t\treturn -1;\ndiff --git a/transport.h b/transport.h\nindex 51b5397..62aa243 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -36,6 +36,7 @@ struct transport {\n #define TRANSPORT_PUSH_MIRROR 8\n #define TRANSPORT_PUSH_VERBOSE 16\n #define TRANSPORT_PUSH_PORCELAIN 32\n+#define TRANSPORT_PUSH_CURRENT 64\n \n /* Returns a transport suitable for the url */\n struct transport *transport_get(struct remote *, const char *);\n-- \n1.6.4.rc1.10.g26dbf.dirty\n"},{"id":"118305","messageId":"1248091094-31485-4-git-send-email-bonzini@gnu.org","threadId":"20164","inReplyTo":"1248091094-31485-1-git-send-email-bonzini@gnu.org","subject":"[PATCH 3/3] push: add remote.*.pushHeadOnly configuration","fromName":"Paolo Bonzini","fromEmail":"bonzini@gnu.org","sentAt":"2009-07-20T11:58:14Z","receivedAt":"2009-07-20T11:58:14Z","isPatch":true,"sender":{"key":"bonzini@gnu.org","avatar":"https://avatars.githubusercontent.com/u/42082?v=4"},"body":"This patch adds a remote.*.pushHeadOnly configuration that automatically\nenables (when possible) the --current option to git push.\n\nSigned-off-by: Paolo Bonzini <bonzini@gnu.org>\n---\n Documentation/config.txt |    6 ++++++\n builtin-push.c           |    2 ++\n remote.c                 |    2 ++\n remote.h                 |    1 +\n t/t5516-fetch-push.sh    |   16 +++++++++++++++-\n 5 files changed, 26 insertions(+), 1 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex cb6832b..4ab5593 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1359,6 +1359,12 @@ remote.<name>.uploadpack::\n \tThe default program to execute on the remote side when fetching.  See\n \toption \\--upload-pack of linkgit:git-fetch-pack[1].\n \n+remote.<name>.pushHeadOnly::\n+\tIf true, whenever `git push` is invoked without a refspec and\n+\tit will try pushing to this remote, `git push` will automatically\n+\tbehave as if the `\\--current` option was given on the command line.\n+\tIn other words, only the current branch is pushed to the remote.\n+\n remote.<name>.tagopt::\n \tSetting this value to \\--no-tags disables automatic tag following when\n \tfetching from remote <name>\ndiff --git a/builtin-push.c b/builtin-push.c\nindex 71d94a5..8d5b054 100644\n--- a/builtin-push.c\n+++ b/builtin-push.c\n@@ -109,6 +109,8 @@ static int do_push(const char *repo, int flags)\n \n \tif (remote->mirror)\n \t\tflags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);\n+\tif (remote->push_head_only && !refspec_nr)\n+\t\tflags |= TRANSPORT_PUSH_CURRENT;\n \n \tif ((flags & TRANSPORT_PUSH_ALL) && refspec) {\n \t\tif (!strcmp(*refspec, \"refs/tags/*\"))\ndiff --git a/remote.c b/remote.c\nindex b5bf9a6..d46dc0d 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -379,6 +379,8 @@ static int handle_config(const char *key, const char *value, void *cb)\n \t\tremote->mirror = git_config_bool(key, value);\n \telse if (!strcmp(subkey, \".skipdefaultupdate\"))\n \t\tremote->skip_default_update = git_config_bool(key, value);\n+\telse if (!strcmp(subkey, \".pushheadonly\"))\n+\t\tremote->push_head_only = git_config_bool(key, value);\n \n \telse if (!strcmp(subkey, \".url\")) {\n \t\tconst char *v;\ndiff --git a/remote.h b/remote.h\nindex 8e5d5b4..b1e3e99 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -36,6 +36,7 @@ struct remote {\n \t * 2 to always fetch tags\n \t */\n \tint fetch_tags;\n+\tint push_head_only;\n \tint skip_default_update;\n \tint mirror;\n \ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex a480cb2..9d61ba0 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -631,9 +631,23 @@ test_expect_success 'push --current respects configuration' '\n \tgit config remote.bremote.push refs/heads/master:refs/heads/master2 &&\n \tgit push --current bremote &&\n \ttest $(git rev-parse master) = $(cd b.git && git rev-parse master2)\n+'\n+\n+test_expect_success 'remote.*.pushHeadOnly respects configuration' '\n+\techo xx > b &&\n+\tgit commit -mmaster3 b &&\n+\tgit config remote.bremote.pushHeadOnly true &&\n \tgit checkout branch &&\n-\tgit push --current bremote 2>&1 | grep \"Everything up-to-date\" &&\n+\tgit push bremote &&\n+\ttest $(git rev-parse master^) = $(cd b.git && git rev-parse master) &&\n \ttest $(git rev-parse branch^) = $(cd b.git && git rev-parse branch)\n '\n \n+test_expect_success 'remote.*.pushHeadOnly works' '\n+\tgit config --unset remote.bremote.push &&\n+\tgit push bremote &&\n+\ttest $(git rev-parse master^) = $(cd b.git && git rev-parse master) &&\n+\ttest $(git rev-parse branch) = $(cd b.git && git rev-parse branch)\n+'\n+\n test_done\n-- \n1.6.4.rc1.10.g26dbf.dirty\n"},{"id":"118342","messageId":"7vskgrum2v.fsf@alter.siamese.dyndns.org","threadId":"20164","inReplyTo":"1248091094-31485-1-git-send-email-bonzini@gnu.org","subject":"Re: [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-07-20T20:38:16Z","receivedAt":"2009-07-20T20:38:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Paolo Bonzini <bonzini@gnu.org> writes:\n\n> This second series is gets rid of the most annoying part (IMHO) of\n> push.default = tracking, i.e. the fact that its behavior cannot be\n> achieved using git's ordinary tools.\n\nHaving to read this three times made me irritated enough to comment on\nthis part of the cover letter.\n\nIf some feature X cannot be achieved by combinations of other existing\nfeatures, is it a bad thing?  Why should that be annoying?\n\nMaybe it is just the matter of phrasing, but I do not think the above\nstatement helps understanding of the issue.  I would certainly understand\nthe justification if it were \"This feature improved things somewhat, in\nthat it now allows you to do X, but it did not go far enough.  It does not\nhelp satisfying wishes Y and Z are similar enough to X and are common\nenough.  Here is an attempt to extend the mechanism in a more generic way\nto do so\".  But I do not get a clear sense of what these Y and Z are from\nthe above description, therefore I personally find it hard to judge if Y\nand Z are so important to support with more code, compared to the support\nfor X we already have.\n\nMaybe Finn Arne Gangstad, who originally did push.default, can shed some\nlight on this?  Do you agree that Paolo's \"annoyance\" is justified, and\nthis series makes things saner?\n\nI actually do agree that it feels somewhat unbalanced that \"git push\" with\nno other arguments can do a \"matching\" push of _all_ branches without any\nfunny configuration, while the same parameterless push needs many\nconfigured remote.*.push refspecs to do a \"tracking\" push of all branches.\n\nAnd as you say, branch.autosetuppush may make the remote.*.push\nconfiguration less painful to set up for the users.  So the inbalance may\nnot hurt in practice from the end user's point of view.\n\nBut more importantly, I think that inbalance is inherent to a certain\nextent, and it probably is _not_ a bad thing in the first place.\n\nIf your workflow is to use local branches with the same name as the remote\nside as your own local integration branches, \"matching\" push that pushes\nall matching branches makes a good deal of sense.  You keep your local\nintegration branches clean by never integrating premature topics into\nthem, so it is always safe to push them out in one go.\n\nIf on the other hand your workflow is to fork topics from the remote\nintegration branch(es) (e.g. topicA and fixB both forked from master taken\nfrom the remote), both topics will be set to push back to the same master\nbranch on the remote side if you use branch.autosetuppush.  In such a\nworkflow, you work on these two topics, and when one of them is done, you\nwant to push that one out, without having to push the other one that is\nnot yet ready (beside, that one won't fast-forward).\n\nA \"tracking\" push that pushes all branches is actively a wrong thing to do\nin such a workflow.\n\n\tSide note: in that sense, branch.autosetuppush might be a well\n\tintentioned but ill conceived concept, and we may want to remove\n\tit from 'next'.\n\nOf course, even if you are using \"matching\", you can (temporarily) have an\nexperimental integration on one of the matching branches while the other\none is truly ready, and in such a situation you do not want to push out\nall matching branches (hence you would occasionally need to be more\nexplicit than usual, i.e. \"git push origin master\").  Also, even if you\nare forking your topics directly from the remote integration branches and\npushing back to where they forked from, you can adopt a discipline not to\npush out when you have some topics that are not ready.\n\nBut the point is that the discipline to keep branches that can be pushed\nout clean is much easier to follow in \"matching push\" workflow, because it\nis an integral part of the workflow.  If merging a topic to an integration\nbranch is a declaration of doneness of the topic, by definition, your\nintegration branches are all ready to be pushed out at any time.\n\nIf you fork topics from remote's integration branch and push each of them\nback individually, \"keeping branches that can be pushed out clean\" is not\neven a discipline, but it is a hindrance, as the point of such a workflow\nis to push things out as they become cooked one-by-one.  In such a\nworkflow, you do want \"one-by-one, push only the current one I just\ntested\".\n\n> ...set the refspecs correctly, push.tracking does not push _all_ tracked\n> branches, but only the current one (because it implicitly adds only one\n> refspec, while autosetuppush places them all in the configuration).\n\nIn short, it is not necessarily bad that \"push all matching\" is much\neasier to set up and use than \"push all tracking\", and I think it is\nnothing to be annoyed about.\n"},{"id":"118351","messageId":"4A64EB03.4050104@gnu.org","threadId":"20164","inReplyTo":"7vskgrum2v.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly","fromName":"Paolo Bonzini","fromEmail":"bonzini@gnu.org","sentAt":"2009-07-20T22:09:07Z","receivedAt":"2009-07-20T22:09:07Z","isPatch":true,"sender":{"key":"bonzini@gnu.org","avatar":"https://avatars.githubusercontent.com/u/42082?v=4"},"body":"> Having to read this three times made me irritated enough to comment on\n> this part of the cover letter.\n\nSorry (you weren't CCed on v1, though, were you?).\n\nFor me, the inherent problem of push.default is that it is a global \nsetting.  As you say below, different remotes may have different \ncharacteristics and this can happen within the same repository.\n\nOne example is an integrator (push.default=matching) that also develops \na few topic branches and wants to publish his tree so that people can \npeek at them --- at the same time, he wants to be free to rebase there. \n  He can then use --mirror to set up the latter remote.\n\nNow, instead, we have a person working in a set up like the one Finn \ndescribed.  He is a subsystem integrator, so he publishes some stable \nbranches (push.default=matching again).  For development, however, he \nhas to branch off the repositories published by other subsystem \nintegrators; for these he wants push.default=tracking semantics.  He \nthen has to define push.default=tracking and configure the other remote \nmanually.  Unlike the first user, this one needs to make a global \ndecision and adjust the git configuration whenever this global decision \ndoes not fit.\n\nIn addition, if he wants to disable pushing to some repository, he \ncannot use push.default=nothing anymore.  The problem is that \"the \nexception [the setting of push.default that was desired for one \nrepository] became the rule [the global per-repository push.default \nsetting]\", and this happened because there is no way to realize \npush.default=tracking with the usual set of git tools, namely refspecs \nand checkout -t.\n\nWhile trying to understand the problem, I tried to see what it would \ntake to implement push.default using the existing git tools, and what \nextra features would be needed.  The resulting design had three new \nfeatures (per-remote tracking, autosetuppush and pushHeadOnly), and \neverything else built on top of those (the --push patch series I posted \ntoday).  I actually liked the design, and decided it was worth pursuing \nit instead of just turning push.default into a per-remote configuration.\n\nIt is way bigger than the push.default patches; on the other hand the \nthree new features may be otherwise desirable, and it also includes some \nnice cleanups.  However, because of this I placed too much attention to \nthe separate features and failed to show the big picture and what was \nmissing in the 1.6.3 implementation.\n\n(Now, back to your comments).\n\n> [...] If on the other hand your workflow is to fork topics from the remote\n> integration branch(es) (e.g. topicA and fixB both forked from master taken\n> from the remote), both topics will be set to push back to the same master\n> branch on the remote side if you use branch.autosetuppush.  In such a\n> workflow, you work on these two topics, and when one of them is done, you\n> want to push that one out, without having to push the other one that is\n> not yet ready (beside, that one won't fast-forward).\n> A \"tracking\" push that pushes all branches is actively a wrong thing to do\n> in such a workflow.\n\nAgreed.\n\nautosetuppush is in fact not meant to be used by the usre, at least the \ncasual user who restricts himself to the \"git remote\" porcelain and \nprefers not to deal into .git/config whenever possible.  The idea is \nthat the user can use --push=tracking to express his workflow, and this \nwill set up pushHeadOnly and autosetuppush together.  The combination of \nthe two options is safe, and provides the behavior that best suites the \nuser's workflow.\n\n(Writing this final paragraph is what made me realize me that I was \nwrong in not showing the big picture in the beginning.  Giving hints \nthrough the cover letters was not enough).\n\n > In short, it is not necessarily bad that \"push all matching\" is much\n > easier to set up and use than \"push all tracking\", and I think it is\n > nothing to be annoyed about.\n\nThe problem is, when \"the exception becomes the rule\", is it still true \nthat \"push all matching\" is easier to set up?\n\nPaolo\n"}]}