[PATCH 0/10 v3] improve refspec handling in push

DORMANTno replies

22 messages, 4 authors, 2016-06-15 · open the first message on its own page

[PATCH 0/10 v3] improve refspec handling in push

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

This is a replacement for sp/push-refspec
(93e296613306311ef02dabb19a6538be2f52aa1c).

Compared to the v2 series the following changed (v2 patch numbers):
    1/8 implementation should be better readable.
    2/8 adjusted to 1/8 changes.
    3/8 removed.
    4/8 removed.
    5/8 much simpler implementation, second patch "git push HEAD" added.
    6/8 chose more explicit naming
        ref_abbrev_matches_full_with_rev_parse_rules;
        unified argument order with ref_matches_abbrev,
        which was renamed to ref_abbrev_matches_full_with_fetch_rules.
    7/8 adjusted to 6/8 changes.
    8/8 report summary;
        --verbose fixed;
        added test that remote tracking branches are unchanged.

All tests pass.

Here's a summary of the series:

 Documentation/git-http-push.txt |    6 ++
 Documentation/git-push.txt      |   16 +++-
 Documentation/git-send-pack.txt |   18 +++-
 builtin-push.c                  |   23 +++++-
 cache.h                         |    1 +
 http-push.c                     |    9 ++-
 remote.c                        |   50 +++++++-----
 remote.h                        |    2 +-
 send-pack.c                     |   77 +++++++++++++----
 sha1_name.c                     |   14 +++
 t/t5516-fetch-push.sh           |  181 ++++++++++++++++++++++++++++++++++++++-
 transport.c                     |   12 ++-
 transport.h                     |    2 +
 13 files changed, 358 insertions(+), 53 deletions(-)

 [PATCH 01/10] push: change push to fail if short refname does not exist
 [PATCH 02/10] push: teach push new flag --create

 [PATCH 03/10] push: support pushing HEAD to real branch name
 [PATCH 04/10] push: add "git push HEAD" shorthand for 'push current branch to default repo'
    Junio doesn't like this patch. But I had it ready, so here it is.
    Junio described an alternative in
    http://marc.info/?l=git&m=119358745026345&w=2

 [PATCH 05/10] rename ref_matches_abbrev() to ref_abbrev_matches_full_with_fetch_rules()
 [PATCH 06/10] add ref_abbrev_matches_full_with_rev_parse_rules() comparing abbrev with full ref name
 [PATCH 07/10] push: use same rules as git-rev-parse to resolve refspecs
    Maybe the matching rules could be further unified.
    Code cleanup would be needed here.
    But this is a different story.

 [PATCH 08/10] push: teach push to accept --verbose option
 [PATCH 09/10] push: teach push to pass --verbose option to transport layer
 [PATCH 10/10] push: teach push to be quiet if local ref is strict subset of remote ref

    Steffen

[PATCH 05/10] rename ref_matches_abbrev() to ref_abbrev_matches_full_with_fetch_rules()

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

The new naming makes the order of the arguments and the rules used
for matching more explicit. This will avoid confusion with
ref_abbrev_matches_full_with_rev_parse_rules(), which will be
introduced in a follow-up commit.

Signed-off-by: Steffen Prohaska <redacted>
---
 remote.c |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/remote.c b/remote.c
index 687eb8e..59e6485 100644
--- a/remote.c
+++ b/remote.c
@@ -421,7 +421,7 @@ int remote_has_url(struct remote *remote, const char *url)
  * Returns true if, under the matching rules for fetching, name is the
  * same as the given full name.
  */
-static int ref_matches_abbrev(const char *name, const char *full)
+static int ref_abbrev_matches_full_with_fetch_rules(const char *name, const char *full)
 {
 	if (!prefixcmp(name, "refs/") || !strcmp(name, "HEAD"))
 		return !strcmp(name, full);
@@ -820,7 +820,7 @@ int branch_merge_matches(struct branch *branch,
 {
 	if (!branch || i < 0 || i >= branch->merge_nr)
 		return 0;
-	return ref_matches_abbrev(branch->merge[i]->src, refname);
+	return ref_abbrev_matches_full_with_fetch_rules(branch->merge[i]->src, refname);
 }
 
 static struct ref *get_expanded_map(struct ref *remote_refs,
@@ -859,7 +859,7 @@ static struct ref *find_ref_by_name_abbrev(struct ref *refs, const char *name)
 {
 	struct ref *ref;
 	for (ref = refs; ref; ref = ref->next) {
-		if (ref_matches_abbrev(name, ref->name))
+		if (ref_abbrev_matches_full_with_fetch_rules(name, ref->name))
 			return ref;
 	}
 	return NULL;
-- 
1.5.3.4.439.ge8b49

[PATCH 02/10] push: teach push new flag --create

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

If you want to push a branch that does not yet exist on the
remote side you can push using a full refspec. For example you
can "push origin refs/heads/master".

This commit changes push such that refs that do not start with
'refs/' will be created at the remote as the matching local ref
if --create is used. If you want to create a new ref at the
remote, you can now say "git push --create origin master".

Signed-off-by: Steffen Prohaska <redacted>
---
 Documentation/git-http-push.txt |    6 ++++++
 Documentation/git-push.txt      |    8 +++++++-
 Documentation/git-send-pack.txt |   14 +++++++++++---
 builtin-push.c                  |    6 +++++-
 http-push.c                     |    9 +++++++--
 remote.c                        |   24 +++++++++++++++---------
 remote.h                        |    2 +-
 send-pack.c                     |    9 +++++++--
 t/t5516-fetch-push.sh           |    8 ++++++++
 transport.c                     |    8 ++++++--
 transport.h                     |    1 +
 11 files changed, 74 insertions(+), 21 deletions(-)
diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt
index 3a69b71..8753611 100644
--- a/Documentation/git-http-push.txt
+++ b/Documentation/git-http-push.txt
@@ -30,6 +30,12 @@ OPTIONS
 	the remote repository can lose commits; use it with
 	care.
 
+\--create::
+	Usually, the command refuses to create a remote ref that is
+	not specified by its full name, i.e. starting with 'refs/'.
+	This flag tells the command to create the remote ref under
+	the full name of the local matching ref.
+
 --dry-run::
 	Do everything except actually send the updates.
 
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index e5dd4c1..67b354b 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects
 SYNOPSIS
 --------
 [verse]
-'git-push' [--all] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>]
+'git-push' [--all] [--dry-run] [--create] [--tags] [--receive-pack=<git-receive-pack>]
            [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]
 
 DESCRIPTION
@@ -86,6 +86,12 @@ the remote repository.
 	This flag disables the check.  This can cause the
 	remote repository to lose commits; use it with care.
 
+\--create::
+	Usually, the command refuses to create a remote ref that is
+	not specified by its full name, i.e. starting with 'refs/'.
+	This flag tells the command to create the remote ref under
+	the full name of the local matching ref.
+
 \--repo=<repo>::
 	When no repository is specified the command defaults to
 	"origin"; this overrides it.
diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
index 2fa01d4..01495df 100644
--- a/Documentation/git-send-pack.txt
+++ b/Documentation/git-send-pack.txt
@@ -44,6 +44,12 @@ OPTIONS
 	the remote repository can lose commits; use it with
 	care.
 
+\--create::
+	Usually, the command refuses to create a remote ref that is
+	not specified by its full name, i.e. starting with 'refs/'.
+	This flag tells the command to create the remote ref under
+	the full name of the local matching ref.
+
 \--verbose::
 	Run verbosely.
 
@@ -97,9 +103,11 @@ destination side.
    * it has to start with "refs/"; <dst> is used as the
      destination literally in this case.
 
-   * <src> == <dst> and the ref that matched the <src> must not
-     exist in the set of remote refs; the ref matched <src>
-     locally is used as the name of the destination.
+   * Only <src> is specified and the ref that matched
+     <src> must not exist in the set of remote refs;
+     and the '--create' flag is used;
+     the ref matched <src> locally is used as the name of
+     the destination.
 
 Without '--force', the <src> ref is stored at the remote only if
 <dst> does not exist, or <dst> is a proper subset (i.e. an
diff --git a/builtin-push.c b/builtin-push.c
index 4b39ef3..4ab1401 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -8,7 +8,7 @@
 #include "remote.h"
 #include "transport.h"
 
-static const char push_usage[] = "git-push [--all] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]";
+static const char push_usage[] = "git-push [--all] [--dry-run] [--create] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]";
 
 static int thin, verbose;
 static const char *receivepack;
@@ -113,6 +113,10 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 			flags |= TRANSPORT_PUSH_DRY_RUN;
 			continue;
 		}
+		if (!strcmp(arg, "--create")) {
+			flags |= TRANSPORT_PUSH_CREATE;
+			continue;
+		}
 		if (!strcmp(arg, "--tags")) {
 			add_refspec("refs/tags/*");
 			continue;
diff --git a/http-push.c b/http-push.c
index c02a3af..4ad9f26 100644
--- a/http-push.c
+++ b/http-push.c
@@ -13,7 +13,7 @@
 #include <expat.h>
 
 static const char http_push_usage[] =
-"git-http-push [--all] [--dry-run] [--force] [--verbose] <remote> [<head>...]\n";
+"git-http-push [--all] [--dry-run] [--create] [--force] [--verbose] <remote> [<head>...]\n";
 
 #ifndef XML_STATUS_OK
 enum XML_Status {
@@ -81,6 +81,7 @@ static int push_verbosely;
 static int push_all;
 static int force_all;
 static int dry_run;
+static int create;
 
 static struct object_list *objects;
 
@@ -2307,6 +2308,10 @@ int main(int argc, char **argv)
 				dry_run = 1;
 				continue;
 			}
+			if (!strcmp(arg, "--create")) {
+				create = 1;
+				continue;
+			}
 			if (!strcmp(arg, "--verbose")) {
 				push_verbosely = 1;
 				continue;
@@ -2389,7 +2394,7 @@ int main(int argc, char **argv)
 	if (!remote_tail)
 		remote_tail = &remote_refs;
 	if (match_refs(local_refs, remote_refs, &remote_tail,
-		       nr_refspec, refspec, push_all))
+		       nr_refspec, refspec, push_all, create))
 		return -1;
 	if (!remote_refs) {
 		fprintf(stderr, "No refs in common and none specified; doing nothing.\n");
diff --git a/remote.c b/remote.c
index cf6441a..687eb8e 100644
--- a/remote.c
+++ b/remote.c
@@ -606,7 +606,7 @@ static struct ref *make_linked_ref(const char *name, struct ref ***tail)
 static int match_explicit(struct ref *src, struct ref *dst,
 			  struct ref ***dst_tail,
 			  struct refspec *rs,
-			  int errs)
+			  int errs, int create)
 {
 	struct ref *matched_src, *matched_dst;
 
@@ -653,13 +653,19 @@ static int match_explicit(struct ref *src, struct ref *dst,
 	case 0:
 		if (!memcmp(lit_dst_value , "refs/", 5))
 			matched_dst = make_linked_ref(lit_dst_value, dst_tail);
-		else {
+		else if (!memcmp(search_dst_value, "refs/", 5))
+			if (create)
+				matched_dst = make_linked_ref(search_dst_value, dst_tail);
+			else
+				error("dst refspec %s does not match any "
+				      "existing ref on the remote.\n"
+				      "To create it use --create "
+				      "or the full ref %s.",
+				       lit_dst_value, search_dst_value);
+		else
 			error("dst refspec %s does not match any "
 			      "existing ref on the remote and does "
 			      "not start with refs/.", lit_dst_value);
-			if (!rs->dst)
-				error("Did you mean %s?\n", search_dst_value);
-		}
 		break;
 	default:
 		matched_dst = NULL;
@@ -683,11 +689,11 @@ static int match_explicit(struct ref *src, struct ref *dst,
 
 static int match_explicit_refs(struct ref *src, struct ref *dst,
 			       struct ref ***dst_tail, struct refspec *rs,
-			       int rs_nr)
+			       int rs_nr, int create)
 {
 	int i, errs;
 	for (i = errs = 0; i < rs_nr; i++)
-		errs |= match_explicit(src, dst, dst_tail, &rs[i], errs);
+		errs |= match_explicit(src, dst, dst_tail, &rs[i], errs, create);
 	return -errs;
 }
 
@@ -717,12 +723,12 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
  * without thinking.
  */
 int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
-	       int nr_refspec, char **refspec, int all)
+	       int nr_refspec, char **refspec, int all, int create)
 {
 	struct refspec *rs =
 		parse_ref_spec(nr_refspec, (const char **) refspec);
 
-	if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
+	if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec, create))
 		return -1;
 
 	/* pick the remainder */
diff --git a/remote.h b/remote.h
index c62636d..7d731b1 100644
--- a/remote.h
+++ b/remote.h
@@ -57,7 +57,7 @@ void ref_remove_duplicates(struct ref *ref_map);
 struct refspec *parse_ref_spec(int nr_refspec, const char **refspec);
 
 int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
-	       int nr_refspec, char **refspec, int all);
+	       int nr_refspec, char **refspec, int all, int create);
 
 /*
  * Given a list of the remote refs and the specification of things to
diff --git a/send-pack.c b/send-pack.c
index e9b9a39..77acae1 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -7,7 +7,7 @@
 #include "remote.h"
 
 static const char send_pack_usage[] =
-"git-send-pack [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
+"git-send-pack [--all] [--dry-run] [--create] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
 "  --all and explicit <ref> specification are mutually exclusive.";
 static const char *receivepack = "git-receive-pack";
 static int verbose;
@@ -15,6 +15,7 @@ static int send_all;
 static int force_update;
 static int use_thin_pack;
 static int dry_run;
+static int create;
 
 /*
  * Make a pack stream and spit it out into file descriptor fd
@@ -201,7 +202,7 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha
 	if (!remote_tail)
 		remote_tail = &remote_refs;
 	if (match_refs(local_refs, remote_refs, &remote_tail,
-		       nr_refspec, refspec, send_all))
+		       nr_refspec, refspec, send_all, create))
 		return -1;
 
 	if (!remote_refs) {
@@ -398,6 +399,10 @@ int main(int argc, char **argv)
 				dry_run = 1;
 				continue;
 			}
+			if (!strcmp(arg, "--create")) {
+				create = 1;
+				continue;
+			}
 			if (!strcmp(arg, "--force")) {
 				force_update = 1;
 				continue;
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 5ba09e2..42ca0ff 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -156,6 +156,14 @@ test_expect_success 'push nonexisting (3)' '
 
 '
 
+test_expect_success 'push nonexisting (4)' '
+
+	mk_test &&
+	git push testrepo --create master &&
+	check_push_result $the_commit heads/master
+
+'
+
 test_expect_success 'push with matching heads' '
 
 	mk_test heads/master &&
diff --git a/transport.c b/transport.c
index 400af71..fbdbd0d 100644
--- a/transport.c
+++ b/transport.c
@@ -385,7 +385,7 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons
 	int argc;
 	int err;
 
-	argv = xmalloc((refspec_nr + 11) * sizeof(char *));
+	argv = xmalloc((refspec_nr + 12) * sizeof(char *));
 	argv[0] = "http-push";
 	argc = 1;
 	if (flags & TRANSPORT_PUSH_ALL)
@@ -394,6 +394,8 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons
 		argv[argc++] = "--force";
 	if (flags & TRANSPORT_PUSH_DRY_RUN)
 		argv[argc++] = "--dry-run";
+	if (flags & TRANSPORT_PUSH_CREATE)
+		argv[argc++] = "--create";
 	argv[argc++] = transport->url;
 	while (refspec_nr--)
 		argv[argc++] = *refspec++;
@@ -658,7 +660,7 @@ static int git_transport_push(struct transport *transport, int refspec_nr, const
 	int argc;
 	int err;
 
-	argv = xmalloc((refspec_nr + 11) * sizeof(char *));
+	argv = xmalloc((refspec_nr + 12) * sizeof(char *));
 	argv[0] = "send-pack";
 	argc = 1;
 	if (flags & TRANSPORT_PUSH_ALL)
@@ -667,6 +669,8 @@ static int git_transport_push(struct transport *transport, int refspec_nr, const
 		argv[argc++] = "--force";
 	if (flags & TRANSPORT_PUSH_DRY_RUN)
 		argv[argc++] = "--dry-run";
+	if (flags & TRANSPORT_PUSH_CREATE)
+		argv[argc++] = "--create";
 	if (data->receivepack) {
 		char *rp = xmalloc(strlen(data->receivepack) + 16);
 		sprintf(rp, "--receive-pack=%s", data->receivepack);
diff --git a/transport.h b/transport.h
index df12ea7..1d6a926 100644
--- a/transport.h
+++ b/transport.h
@@ -30,6 +30,7 @@ struct transport {
 #define TRANSPORT_PUSH_ALL 1
 #define TRANSPORT_PUSH_FORCE 2
 #define TRANSPORT_PUSH_DRY_RUN 4
+#define TRANSPORT_PUSH_CREATE 8
 
 /* Returns a transport suitable for the url */
 struct transport *transport_get(struct remote *, const char *);
-- 
1.5.3.4.439.ge8b49

[PATCH 10/10] push: teach push to be quiet if local ref is strict subset of remote ref

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

git push reports errors if a remote ref is not a strict subset
of a local ref. The push wouldn't be a fast-forward and is
therefore refused. This is in general a good idea.

But these messages can be annoying if you work with a shared
remote repository. Branches at the remote may have advanced and
you haven't pulled to all of your local branches. In this
situation, local branches may be strict subsets of the remote
heads. Pushing such branches wouldn't add any information to the
remote. It would only reset the remote to an ancestor. A merge
between the remote and the local branch is not very interested
either because it would just be a fast forward of the local
branch. In these cases you're not interested in the error
message.

This commit teaches git push to be quiet for local refs that are
strict subsets of the matching remote refs and no refspec is
specified on the command line. If the --verbose flag is used a
"note" is printed instead of silently ignoring the refs.
If no notes have been printed the number of ignored refs will
be reported in the final summary.

If refs are ignored their matching remote tracking refs will not
be changed.

git push now allows you pushing a couple of branches that have
advanced, while ignoring all branches that have no local changes,
but are lagging behind their matching remote refs. This is done
without reporting errors.

Thanks to Junio C. Hamano [off-list ref] for suggesting to
report in the summary that refs have been ignored.

Signed-off-by: Steffen Prohaska <redacted>
---
 send-pack.c           |   68 +++++++++++++++++++++++++++++++---------
 t/t5516-fetch-push.sh |   84 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 137 insertions(+), 15 deletions(-)
diff --git a/send-pack.c b/send-pack.c
index 77acae1..68a4692 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -187,6 +187,7 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha
 	int ask_for_status_report = 0;
 	int allow_deleting_refs = 0;
 	int expect_status_report = 0;
+	int ignored_refs = 0;
 
 	/* No funny business with the matcher */
 	remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL);
@@ -259,24 +260,56 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha
 		    !will_delete_ref &&
 		    !is_null_sha1(ref->old_sha1) &&
 		    !ref->force) {
-			if (!has_sha1_file(ref->old_sha1) ||
-			    !ref_newer(ref->peer_ref->new_sha1,
-				       ref->old_sha1)) {
-				/* We do not have the remote ref, or
-				 * we know that the remote ref is not
-				 * an ancestor of what we are trying to
-				 * push.  Either way this can be losing
-				 * commits at the remote end and likely
-				 * we were not up to date to begin with.
+			if (!has_sha1_file(ref->old_sha1)) {
+				/* We do not have the remote ref.
+				 * This can be losing commits at
+				 * the remote end.
 				 */
-				error("remote '%s' is not a strict "
-				      "subset of local ref '%s'. "
-				      "maybe you are not up-to-date and "
-				      "need to pull first?",
-				      ref->name,
-				      ref->peer_ref->name);
+				error("You don't have the commit"
+				      "for the remote ref '%s'."
+				      "This may cause losing commits"
+				      "that cannot be recovered.",
+				      ref->name);
 				ret = -2;
 				continue;
+			} else if (!ref_newer(ref->peer_ref->new_sha1,
+			                      ref->old_sha1)) {
+				/* We know that the remote ref is not
+				 * an ancestor of what we are trying to
+				 * push. This can be losing commits at
+				 * the remote end and likely we were not
+				 * up to date to begin with.
+				 *
+				 * Therefore, we don't push.
+				 *
+				 * If no explicit refspec was passed on the
+				 * commandline, then we only report an error
+				 * if the local is not a strict subset of the
+				 * remote.  If the local is a strict subset we
+				 * don't have new commits for the remote.
+				 * Pulling and pushing wouldn't add anything to
+				 * the remote.
+				 *
+				 */
+				if (nr_refspec ||
+				    !ref_newer(ref->old_sha1, ref->peer_ref->new_sha1)) {
+					error("remote '%s' is not a strict "
+					      "subset of local ref '%s'. "
+					      "maybe you are not up-to-date and "
+					      "need to pull first?",
+					      ref->name,
+					      ref->peer_ref->name);
+					ret = -2;
+				} else if (verbose) {
+					fprintf(stderr,
+					        "note: ignoring local ref '%s' "
+					        "because it is a strict "
+					        "subset of remote '%s'.\n",
+					        ref->peer_ref->name,
+					        ref->name);
+				} else
+					ignored_refs++;
+				continue;
 			}
 		}
 		hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
@@ -335,6 +368,11 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha
 			ret = -4;
 	}
 
+	if (ignored_refs)
+		fprintf(stderr,
+			"Ignored %d local refs that are strict subsets of matching remote ref. "
+			"Use --verbose for more details.\n",
+			ignored_refs);
 	if (!new_refs && ret == 0)
 		fprintf(stderr, "Everything up-to-date\n");
 	return ret;
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 6708ec1..1f740b2 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -56,6 +56,22 @@ check_push_result () {
 	)
 }
 
+check_local_result () {
+	(
+		it="$1" &&
+		shift
+		for ref in "$@"
+		do
+			r=$(git show-ref -s --verify refs/$ref) &&
+			test "z$r" = "z$it" || {
+				echo "Oops, refs/$ref is wrong"
+				exit 1
+			}
+		done &&
+		git fsck --full
+	)
+}
+
 test_expect_success setup '
 
 	: >path1 &&
@@ -345,4 +361,72 @@ test_expect_success 'push with dry-run' '
 	check_push_result $old_commit heads/master
 '
 
+test_expect_success 'push with local is strict subset (must not report error)' '
+
+	mk_test heads/foo &&
+	git push testrepo $the_commit:refs/heads/foo &&
+	git branch -f foo $old_commit &&
+	if git push testrepo 2>&1 | grep ^error
+	then
+		echo "Oops, should not report error"
+		false
+	else
+		check_push_result $the_commit heads/foo
+	fi
+
+'
+
+test_expect_success 'push with local is strict subset (must not update remotes)' '
+
+	mk_test heads/foo &&
+	git push testrepo $the_commit:refs/heads/foo &&
+	git branch -f foo $old_commit &&
+	git fetch test &&
+	check_local_result $the_commit remotes/test/foo &&
+	if git push test 2>&1 | grep ^error
+	then
+		echo "Oops, should not report error"
+		false
+	else
+		check_push_result $the_commit heads/foo &&
+		check_local_result $the_commit remotes/test/foo
+	fi
+
+'
+
+test_expect_success 'push with explicit refname, local is strict subset (must report error)' '
+
+	mk_test heads/foo &&
+	git push testrepo $the_commit:refs/heads/foo &&
+	git branch -f foo $old_commit &&
+	if ! git push testrepo foo 2>&1 | grep ^error
+	then
+		echo "Oops, should have reported error"
+		false
+	else
+		check_push_result $the_commit heads/foo
+	fi
+
+'
+
+test_expect_success 'push with neither local nor remote is strict subset (must report error)' '
+
+	mk_test heads/foo &&
+	git push testrepo $the_commit:refs/heads/foo &&
+	git branch -f foo $old_commit &&
+	git checkout foo &&
+	: >path3 &&
+	git add path3 &&
+	test_tick &&
+	git commit -a -m branched &&
+	if ! git push testrepo 2>&1 | grep ^error
+	then
+		echo "Oops, should have reported error"
+		false
+	else
+		check_push_result $the_commit heads/foo
+	fi
+
+'
+
 test_done
-- 
1.5.3.4.439.ge8b49

[PATCH 03/10] push: support pushing HEAD to real branch name

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

This teaches "push <remote> HEAD" to resolve HEAD on the local
side to its real branch name, e.g. master, and then act as if
the real branch name was specified. So we have a shorthand for
pushing the current branch. Besides HEAD, no other symbolic ref
is resolved.

Thanks to Daniel Barkalow [off-list ref] for suggesting
this implementation, which is much simpler than the
implementation proposed before.

Signed-off-by: Steffen Prohaska <redacted>
---
 builtin-push.c        |    9 +++++++++
 t/t5516-fetch-push.sh |   31 +++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+), 0 deletions(-)
diff --git a/builtin-push.c b/builtin-push.c
index 4ab1401..2e3c8c6 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -40,6 +40,15 @@ static void set_refspecs(const char **refs, int nr)
 			strcat(tag, refs[i]);
 			ref = tag;
 		}
+		if (!strcmp("HEAD", ref)) {
+			unsigned char sha1_dummy[20];
+			ref = resolve_ref(ref, sha1_dummy, 1, NULL);
+			if (!ref)
+				die("HEAD cannot be resolved.");
+			if (strncmp(ref, "refs/heads/", 11))
+				die("HEAD cannot be resolved to branch.");
+			ref = xstrdup(ref + 11);
+		}
 		add_refspec(ref);
 	}
 }
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 42ca0ff..8becaf8 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -282,6 +282,37 @@ test_expect_success 'push with colon-less refspec (4)' '
 
 '
 
+test_expect_success 'push with HEAD' '
+
+	mk_test heads/master &&
+	git checkout master &&
+	git push testrepo HEAD &&
+	check_push_result $the_commit heads/master
+
+'
+
+test_expect_success 'push with HEAD (--create)' '
+
+	mk_test &&
+	git checkout master &&
+	git push --create testrepo HEAD &&
+	check_push_result $the_commit heads/master
+
+'
+
+test_expect_success 'push with HEAD nonexisting at remote' '
+
+	mk_test heads/master &&
+	git checkout -b local master &&
+	if git push testrepo HEAD
+	then
+		echo "Oops, should have failed"
+		false
+	else
+		check_push_result $the_first_commit heads/master
+	fi
+'
+
 test_expect_success 'push with dry-run' '
 
 	mk_test heads/master &&
-- 
1.5.3.4.439.ge8b49

[PATCH 01/10] push: change push to fail if short refname does not exist

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

Pushing a short refname used to create a new ref on on the
remote side if it did not yet exist. If you specified the wrong
branch accidentally it was created. A safety valve that pushes
only existing branches may help to avoid errors.

This commit changes push to fail if the remote ref does not yet
exist and the refspec does not start with refs/. Remote refs must
explicitly be created with their full name. If you specify a
branch name that does not yet exist on the remote side, git push
will print a suggestion to push the full refname instead.

The new behaviour is more defensive than the old one. You can
now explicitly distinguish between "push existing branch" and
"create new branch on the remote side". The old implementation
allowed the same command line in both cases.

A follow-up patch will add a flag '--create' that provides an
alternative to using full refnames if creation of new refs is
intended.

Another follow-up patch will support "push origin HEAD". In this
case, the existence check is important. If you're on the wrong
branch and push HEAD you may be surprised if a new branch is
created. This can be avoided by requiring either a full ref or
the '--create' flag.

The implementation in this patch is less "clever" and hopefully
better readable than an ealier version of the patch. Thanks for
the suggestions to Daniel Barkalow [off-list ref].

Signed-off-by: Steffen Prohaska <redacted>
---
 remote.c              |   25 ++++++++++++++++---------
 t/t5516-fetch-push.sh |   34 ++++++++++++++++++++++++++++++++--
 2 files changed, 48 insertions(+), 11 deletions(-)
diff --git a/remote.c b/remote.c
index 170015a..cf6441a 100644
--- a/remote.c
+++ b/remote.c
@@ -610,7 +610,8 @@ static int match_explicit(struct ref *src, struct ref *dst,
 {
 	struct ref *matched_src, *matched_dst;
 
-	const char *dst_value = rs->dst;
+	const char *lit_dst_value;
+	const char *search_dst_value;
 
 	if (rs->pattern)
 		return errs;
@@ -637,27 +638,33 @@ static int match_explicit(struct ref *src, struct ref *dst,
 	if (!matched_src)
 		errs = 1;
 
-	if (!dst_value) {
+	if (rs->dst) {
+		lit_dst_value = search_dst_value = rs->dst;
+	} else {
 		if (!matched_src)
 			return errs;
-		dst_value = matched_src->name;
+		lit_dst_value = rs->src;
+		search_dst_value = matched_src->name;
 	}
 
-	switch (count_refspec_match(dst_value, dst, &matched_dst)) {
+	switch (count_refspec_match(search_dst_value, dst, &matched_dst)) {
 	case 1:
 		break;
 	case 0:
-		if (!memcmp(dst_value, "refs/", 5))
-			matched_dst = make_linked_ref(dst_value, dst_tail);
-		else
+		if (!memcmp(lit_dst_value , "refs/", 5))
+			matched_dst = make_linked_ref(lit_dst_value, dst_tail);
+		else {
 			error("dst refspec %s does not match any "
 			      "existing ref on the remote and does "
-			      "not start with refs/.", dst_value);
+			      "not start with refs/.", lit_dst_value);
+			if (!rs->dst)
+				error("Did you mean %s?\n", search_dst_value);
+		}
 		break;
 	default:
 		matched_dst = NULL;
 		error("dst refspec %s matches more than one.",
-		      dst_value);
+		      lit_dst_value);
 		break;
 	}
 	if (errs || !matched_dst)
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 4fbd5b1..5ba09e2 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -126,6 +126,36 @@ test_expect_success 'push with wildcard' '
 	)
 '
 
+test_expect_success 'push nonexisting (1)' '
+
+	mk_test &&
+	if git push testrepo master
+	then
+		echo "Oops, should have failed"
+		false
+	fi
+
+'
+
+test_expect_success 'push nonexisting (2)' '
+
+	mk_test &&
+	if git push testrepo heads/master
+	then
+		echo "Oops, should have failed"
+		false
+	fi
+
+'
+
+test_expect_success 'push nonexisting (3)' '
+
+	mk_test &&
+	git push testrepo refs/heads/master &&
+	check_push_result $the_commit heads/master
+
+'
+
 test_expect_success 'push with matching heads' '
 
 	mk_test heads/master &&
@@ -225,7 +255,7 @@ test_expect_success 'push with colon-less refspec (3)' '
 		git tag -d frotz
 	fi &&
 	git branch -f frotz master &&
-	git push testrepo frotz &&
+	git push testrepo refs/heads/frotz &&
 	check_push_result $the_commit heads/frotz &&
 	test 1 = $( cd testrepo && git show-ref | wc -l )
 '
@@ -238,7 +268,7 @@ test_expect_success 'push with colon-less refspec (4)' '
 		git branch -D frotz
 	fi &&
 	git tag -f frotz &&
-	git push testrepo frotz &&
+	git push testrepo refs/tags/frotz &&
 	check_push_result $the_commit tags/frotz &&
 	test 1 = $( cd testrepo && git show-ref | wc -l )
 
-- 
1.5.3.4.439.ge8b49

[PATCH 06/10] add ref_abbrev_matches_full_with_rev_parse_rules() comparing abbrev with full ref name

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

ref_abbrev_matches_full_with_rev_parse_rules(abbrev_name, full_name)
expands abbrev_name according to the rules documented in
git-rev-parse and compares the expanded name with full_name. It
reports a match by returning 0.

This function makes the rules for resolving refs to sha1s available
for string comparison. Before this change, the rules were buried in
get_sha1*() and dwim_ref().

The function name is very long to make the rule set used
explicit. We have a different set of rules for matching refspecs.
It would be a good thing to unify all different rule sets. But
this commit doesn't address this challenge. It only makes the
git-rev-parse rules available for string comparison.

ref_abbrev_matches_full_with_rev_parse_rules() will be used for
matching refspecs in git-send-pack.

Thanks to Daniel Barkalow [off-list ref] for pointing
out that ref_matches_abbrev in remote.c solves a similar problem
and care should be take to avoid confusion.

Signed-off-by: Steffen Prohaska <redacted>
---
 cache.h     |    1 +
 sha1_name.c |   14 ++++++++++++++
 2 files changed, 15 insertions(+), 0 deletions(-)
diff --git a/cache.h b/cache.h
index 27485d3..bb10ade 100644
--- a/cache.h
+++ b/cache.h
@@ -405,6 +405,7 @@ extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 extern char *sha1_to_hex(const unsigned char *sha1);	/* static buffer result! */
 extern int read_ref(const char *filename, unsigned char *sha1);
 extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
+extern int ref_abbrev_matches_full_with_rev_parse_rules(const char *abbrev_name, const char *full_name);
 extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
 extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
 
diff --git a/sha1_name.c b/sha1_name.c
index 2d727d5..944e318 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -249,6 +249,20 @@ static const char *ref_fmt[] = {
 	NULL
 };
 
+int ref_abbrev_matches_full_with_rev_parse_rules(const char *abbrev_name, const char *full_name)
+{
+	const char **p;
+	const int abbrev_name_len = strlen(abbrev_name);
+
+	for (p = ref_fmt; *p; p++) {
+		if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) {
+			return 0;
+		}
+	}
+
+	return -1;
+}
+
 int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
 {
 	const char **p, *r;
-- 
1.5.3.4.439.ge8b49

[PATCH 04/10] push: add "git push HEAD" shorthand for 'push current branch to default repo'

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

Sometimes it is handy to push only the current branch to the
default remote repository. For example, if you created a branch
using the '--track' option git knows that the current branch
is linked to a specific remote. But up to now you needed to say
"git push <defaultremote> <thisbranch>", which was quite
annoying.  You could have said "git push" but then _all_ branches
would have been pushed to the default remote.

This commit introduces "git push HEAD", which resolves HEAD to
the current branch and pushes only the current branch to its
default remote.

Setups that have a remote named HEAD will break. But such a setup
if unlikely to exist; and is not very sensible anyway.

Signed-off-by: Steffen Prohaska <redacted>
---
 Documentation/git-push.txt |    6 +++++-
 builtin-push.c             |    2 ++
 t/t5516-fetch-push.sh      |   12 ++++++++++++
 3 files changed, 19 insertions(+), 1 deletions(-)
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 67b354b..236898f 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git-push' [--all] [--dry-run] [--create] [--tags] [--receive-pack=<git-receive-pack>]
-           [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]
+           [--repo=all] [-f | --force] [-v] [HEAD | <repository> <refspec>...]
 
 DESCRIPTION
 -----------
@@ -25,6 +25,10 @@ documentation for gitlink:git-receive-pack[1].
 
 OPTIONS
 -------
+HEAD::
+	Tells push to push the current branch to the default
+	remote repository.
+
 <repository>::
 	The "remote" repository that is destination of a push
 	operation.  See the section <<URLS,GIT URLS>> below.
diff --git a/builtin-push.c b/builtin-push.c
index 2e3c8c6..7c08e19 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -102,6 +102,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 		const char *arg = argv[i];
 
 		if (arg[0] != '-') {
+			if (!strcmp("HEAD", arg))
+				break;
 			repo = arg;
 			i++;
 			break;
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 8becaf8..2650e36 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -291,6 +291,18 @@ test_expect_success 'push with HEAD' '
 
 '
 
+test_expect_success 'push HEAD' '
+
+	mk_test heads/track &&
+	git remote add test testrepo &&
+	git fetch test &&
+	git checkout -b track test/track &&
+	git reset --hard master &&
+	git push HEAD &&
+	check_push_result $the_commit heads/track
+
+'
+
 test_expect_success 'push with HEAD (--create)' '
 
 	mk_test &&
-- 
1.5.3.4.439.ge8b49

[PATCH 07/10] push: use same rules as git-rev-parse to resolve refspecs

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

This commit changes the rules for resolving refspecs to match the
rules for resolving refs in rev-parse. git-rev-parse uses clear rules
to resolve a short ref to its full name, which are well documented.
The rules for resolving refspecs documented in git-send-pack were
less strict and harder to understand. This commit replaces them by
the rules of git-rev-parse.

The unified rules are easier to understand and better resolve ambiguous
cases. You can now push from a repository containing several branches
ending on the same short name.

Note, this may break existing setups. For example "master" will no longer
resolve to "origin/master".

Signed-off-by: Steffen Prohaska <redacted>
---
 Documentation/git-send-pack.txt |    4 +++-
 remote.c                        |    5 +----
 t/t5516-fetch-push.sh           |   12 +++++++++++-
 3 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
index 01495df..08bcc25 100644
--- a/Documentation/git-send-pack.txt
+++ b/Documentation/git-send-pack.txt
@@ -91,7 +91,9 @@ Each pattern pair consists of the source side (before the colon)
 and the destination side (after the colon).  The ref to be
 pushed is determined by finding a match that matches the source
 side, and where it is pushed is determined by using the
-destination side.
+destination side. The rules used to match a ref are the same
+rules used by gitlink:git-rev-parse[1] to resolve a symbolic ref
+name.
 
  - It is an error if <src> does not match exactly one of the
    local refs.
diff --git a/remote.c b/remote.c
index 59e6485..9c33fcf 100644
--- a/remote.c
+++ b/remote.c
@@ -519,10 +519,7 @@ static int count_refspec_match(const char *pattern,
 		char *name = refs->name;
 		int namelen = strlen(name);
 
-		if (namelen < patlen ||
-		    memcmp(name + namelen - patlen, pattern, patlen))
-			continue;
-		if (namelen != patlen && name[namelen - patlen - 1] != '/')
+		if (ref_abbrev_matches_full_with_rev_parse_rules(pattern, name))
 			continue;
 
 		/* A match is "weak" if it is with refs outside
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 2650e36..6708ec1 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -183,11 +183,21 @@ test_expect_success 'push with no ambiguity (1)' '
 test_expect_success 'push with no ambiguity (2)' '
 
 	mk_test remotes/origin/master &&
-	git push testrepo master:master &&
+	git push testrepo master:origin/master &&
 	check_push_result $the_commit remotes/origin/master
 
 '
 
+test_expect_success 'push with colon-less refspec, no ambiguity' '
+
+	mk_test heads/master heads/t/master &&
+	git branch -f t/master master &&
+	git push testrepo master &&
+	check_push_result $the_commit heads/master &&
+	check_push_result $the_first_commit heads/t/master
+
+'
+
 test_expect_success 'push with weak ambiguity (1)' '
 
 	mk_test heads/master remotes/origin/master &&
-- 
1.5.3.4.439.ge8b49

[PATCH 08/10] push: teach push to accept --verbose option

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

Before this commit, git push only knew '-v'.

Signed-off-by: Steffen Prohaska <redacted>
---
 Documentation/git-push.txt |    4 ++--
 builtin-push.c             |    4 ++++
 2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 236898f..865f183 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git-push' [--all] [--dry-run] [--create] [--tags] [--receive-pack=<git-receive-pack>]
-           [--repo=all] [-f | --force] [-v] [HEAD | <repository> <refspec>...]
+           [--repo=all] [-f | --force] [-v | --verbose] [HEAD | <repository> <refspec>...]
 
 DESCRIPTION
 -----------
@@ -105,7 +105,7 @@ the remote repository.
 	transfer spends extra cycles to minimize the number of
 	objects to be sent and meant to be used on slower connection.
 
--v::
+-v, \--verbose::
 	Run verbosely.
 
 include::urls-remotes.txt[]
diff --git a/builtin-push.c b/builtin-push.c
index 7c08e19..9103d57 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -112,6 +112,10 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 			verbose=1;
 			continue;
 		}
+		if (!strcmp(arg, "--verbose")) {
+			verbose=1;
+			continue;
+		}
 		if (!prefixcmp(arg, "--repo=")) {
 			repo = arg+7;
 			continue;
-- 
1.5.3.4.439.ge8b49

[PATCH 09/10] push: teach push to pass --verbose option to transport layer

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

A --verbose option to push should also be passed to the
transport layer, i.e. git-send-pack, git-http-push.

git push is modified to do so.

Signed-off-by: Steffen Prohaska <redacted>
---
 builtin-push.c |    2 ++
 transport.c    |    8 ++++++--
 transport.h    |    1 +
 3 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/builtin-push.c b/builtin-push.c
index 9103d57..27eaca5 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -110,10 +110,12 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 		}
 		if (!strcmp(arg, "-v")) {
 			verbose=1;
+			flags |= TRANSPORT_PUSH_VERBOSE;
 			continue;
 		}
 		if (!strcmp(arg, "--verbose")) {
 			verbose=1;
+			flags |= TRANSPORT_PUSH_VERBOSE;
 			continue;
 		}
 		if (!prefixcmp(arg, "--repo=")) {
diff --git a/transport.c b/transport.c
index fbdbd0d..e6bca93 100644
--- a/transport.c
+++ b/transport.c
@@ -385,7 +385,7 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons
 	int argc;
 	int err;
 
-	argv = xmalloc((refspec_nr + 12) * sizeof(char *));
+	argv = xmalloc((refspec_nr + 13) * sizeof(char *));
 	argv[0] = "http-push";
 	argc = 1;
 	if (flags & TRANSPORT_PUSH_ALL)
@@ -396,6 +396,8 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons
 		argv[argc++] = "--dry-run";
 	if (flags & TRANSPORT_PUSH_CREATE)
 		argv[argc++] = "--create";
+	if (flags & TRANSPORT_PUSH_VERBOSE)
+		argv[argc++] = "--verbose";
 	argv[argc++] = transport->url;
 	while (refspec_nr--)
 		argv[argc++] = *refspec++;
@@ -660,7 +662,7 @@ static int git_transport_push(struct transport *transport, int refspec_nr, const
 	int argc;
 	int err;
 
-	argv = xmalloc((refspec_nr + 12) * sizeof(char *));
+	argv = xmalloc((refspec_nr + 13) * sizeof(char *));
 	argv[0] = "send-pack";
 	argc = 1;
 	if (flags & TRANSPORT_PUSH_ALL)
@@ -671,6 +673,8 @@ static int git_transport_push(struct transport *transport, int refspec_nr, const
 		argv[argc++] = "--dry-run";
 	if (flags & TRANSPORT_PUSH_CREATE)
 		argv[argc++] = "--create";
+	if (flags & TRANSPORT_PUSH_VERBOSE)
+		argv[argc++] = "--verbose";
 	if (data->receivepack) {
 		char *rp = xmalloc(strlen(data->receivepack) + 16);
 		sprintf(rp, "--receive-pack=%s", data->receivepack);
diff --git a/transport.h b/transport.h
index 1d6a926..a387eed 100644
--- a/transport.h
+++ b/transport.h
@@ -31,6 +31,7 @@ struct transport {
 #define TRANSPORT_PUSH_FORCE 2
 #define TRANSPORT_PUSH_DRY_RUN 4
 #define TRANSPORT_PUSH_CREATE 8
+#define TRANSPORT_PUSH_VERBOSE 16
 
 /* Returns a transport suitable for the url */
 struct transport *transport_get(struct remote *, const char *);
-- 
1.5.3.4.439.ge8b49

Re: [PATCH 03/10] push: support pushing HEAD to real branch name

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:43:45

Nice.

Re: [PATCH 07/10] push: use same rules as git-rev-parse to resolve refspecs

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:43:45

Steffen Prohaska [off-list ref] writes:
This commit changes the rules for resolving refspecs to match the
rules for resolving refs in rev-parse. git-rev-parse uses clear rules
to resolve a short ref to its full name, which are well documented.
The rules for resolving refspecs documented in git-send-pack were
less strict and harder to understand. This commit replaces them by
the rules of git-rev-parse.

The unified rules are easier to understand and better resolve ambiguous
cases. You can now push from a repository containing several branches
ending on the same short name.
As you introduced long names around 5/10 to have two different
ones for clarity with the goal of unifying them, so once you
unified the rules, it probably is a good idea to rename the long
"do_this_with_X_rule()" and "do_this_with_Y_rule()" functions
back to "do_this()", isn't it?

Re: [PATCH 04/10] push: add "git push HEAD" shorthand for 'push current branch to default repo'

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:43:45

Will drop this as you already know why.

Re: [PATCH 01/10] push: change push to fail if short refname does not exist

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:43:45

Steffen Prohaska [off-list ref] writes:
Pushing a short refname used to create a new ref on on the
remote side if it did not yet exist. If you specified the wrong
branch accidentally it was created. A safety valve that pushes
only existing branches may help to avoid errors.
On the other hand, if you specified a wrong branch that exists
on the remote end accidentally, it still was pushed.  Do we want
to have a new "--i-really-want-to-push" option to make it safer?

I do not think so.  Why should a new branch be treated any
differently?

Will drop 1/10 and 2/10 for now.

Re: [PATCH 10/10] push: teach push to be quiet if local ref is strict subset of remote ref

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:43:45

Steffen Prohaska [off-list ref] writes:
git push now allows you pushing a couple of branches that have
advanced, while ignoring all branches that have no local changes,
but are lagging behind their matching remote refs. This is done
without reporting errors.

Thanks to Junio C. Hamano [off-list ref] for suggesting to
report in the summary that refs have been ignored.
I do not think this is a good idea at all.  Furthermore, I never
suggested anything about summary.  You are robbing the
information from the pusher which ones are pushed and which ones
are left behind.

It simply is insane to make this strange rule 10/10 introduces
the default behaviour.  It is too specific to a particular
workflow (that is, working with a shared central repository,
having many locally tracking branches that are not often used
and become stale, and working on only things to completion
between pushes).

I think we could live with an optional behaviour, in addition to
the current "matching refs" behaviour, that is "matching refs,
ignoring strict ancestors", though, but I doubt it is worth the
addition.

Re: [PATCH 07/10] push: use same rules as git-rev-parse to resolve refspecs

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

On Oct 30, 2007, at 9:28 AM, Junio C Hamano wrote:
Steffen Prohaska [off-list ref] writes:
quoted
This commit changes the rules for resolving refspecs to match the
rules for resolving refs in rev-parse. git-rev-parse uses clear rules
to resolve a short ref to its full name, which are well documented.
The rules for resolving refspecs documented in git-send-pack were
less strict and harder to understand. This commit replaces them by
the rules of git-rev-parse.

The unified rules are easier to understand and better resolve  
ambiguous
cases. You can now push from a repository containing several branches
ending on the same short name.
As you introduced long names around 5/10 to have two different
ones for clarity with the goal of unifying them, so once you
unified the rules, it probably is a good idea to rename the long
"do_this_with_X_rule()" and "do_this_with_Y_rule()" functions
back to "do_this()", isn't it?
Absolutely.

But I'm not sure if I'm the one who unifies them.

	Steffen

Re: [PATCH 01/10] push: change push to fail if short refname does not exist

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

On Oct 30, 2007, at 9:29 AM, Junio C Hamano wrote:
Steffen Prohaska [off-list ref] writes:
quoted
Pushing a short refname used to create a new ref on on the
remote side if it did not yet exist. If you specified the wrong
branch accidentally it was created. A safety valve that pushes
only existing branches may help to avoid errors.
On the other hand, if you specified a wrong branch that exists
on the remote end accidentally, it still was pushed.  Do we want
to have a new "--i-really-want-to-push" option to make it safer?
Maybe not a bad idea ;)

But not as a command line flag but after printing the results
of a '--dry-run' and than asking the user for confirmation:
"do you want to push this?".

I do not think so.  Why should a new branch be treated any
differently?
Because "updating an existing branch" and "creating a new branch"
are two slightly different tasks.

If git provides a way to make this difference explicit, it
would be safer to use.

Will drop 1/10 and 2/10 for now.
Then they'll be dropped and I'll rely on the the --dry-run flag.

Or someone else needs to step in and support my point.

	Steffen

Re: [PATCH 10/10] push: teach push to be quiet if local ref is strict subset of remote ref

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

On Oct 30, 2007, at 9:29 AM, Junio C Hamano wrote:
Steffen Prohaska [off-list ref] writes:
quoted
git push now allows you pushing a couple of branches that have
advanced, while ignoring all branches that have no local changes,
but are lagging behind their matching remote refs. This is done
without reporting errors.

Thanks to Junio C. Hamano [off-list ref] for suggesting to
report in the summary that refs have been ignored.
I do not think this is a good idea at all.  Furthermore, I never
suggested anything about summary.
Yeah, sorry. You only asked if the summary does mention
something; not suggesting it should do so.

You are robbing the
information from the pusher which ones are pushed and which ones
are left behind.
Absolutely; because the branches left behind are not
interesting. The remote already is ahead of the local
branches. The local branches are just left were they are. They
have no new information on them.  Forcing an push would _rewind_
the remote without adding anything to it.

If you really intended to do a rewind you should have passed
'--force' in the first place and my report would never be
printed.

It simply is insane to make this strange rule 10/10 introduces
the default behaviour.  It is too specific to a particular
workflow (that is, working with a shared central repository,
having many locally tracking branches that are not often used
and become stale, and working on only things to completion
between pushes).
I don't think its very strange behaviour if you see it in the
light of what the user wants to achieve. We are talking about
the case were only fast forward pushes are allowed. So, we
only talk about a push that has the goal of adding new local
changes to the remote. The user says "git push" and means
push my new local changes to the remote.

Unfortunately, the remote may have advanced differently from
the local branch, and the push must fail because someone needs
to merge first. git push recommends to do a pull and retry, which
is the right thing to do.

My strange rule 10/10 adds a check that verifies if the local
side has something interesting to push. Only in this case a
pull make sense. If you do not have something new, a pull will
be a fast-forward, and just a waste of time.

In this light I think the current behaviour is insane, because
it asks the user to spend time on things that do not add any
value. No new commits, no new information, no need to merge, no
need to push again, no need to report errors ...
I think we could live with an optional behaviour, in addition to
the current "matching refs" behaviour, that is "matching refs,
ignoring strict ancestors", though, but I doubt it is worth the
addition.
... just ignore strict ancestors by default.

	Steffen

Re: [PATCH 10/10] push: teach push to be quiet if local ref is strict subset of remote ref

From: Andreas Ericsson <hidden>
Date: 2016-06-15 22:43:45

Steffen Prohaska wrote:
My strange rule 10/10 adds a check that verifies if the local
side has something interesting to push. Only in this case a
pull make sense. If you do not have something new, a pull will
be a fast-forward, and just a waste of time.
Err... fast-forward pulls are not a waste of time. What a strange
notion. Perhaps I misunderstood, but this sentence jumped out at
me and immediately got filed under "decidedly odd".

-- 
Andreas Ericsson                   andreas.ericsson@op5.se
OP5 AB                             www.op5.se
Tel: +46 8-230225                  Fax: +46 8-230231

Re: [PATCH 10/10] push: teach push to be quiet if local ref is strict subset of remote ref

From: Steffen Prohaska <hidden>
Date: 2016-06-15 22:43:45

On Oct 30, 2007, at 11:26 AM, Andreas Ericsson wrote:
Steffen Prohaska wrote:
quoted
My strange rule 10/10 adds a check that verifies if the local
side has something interesting to push. Only in this case a
pull make sense. If you do not have something new, a pull will
be a fast-forward, and just a waste of time.
Err... fast-forward pulls are not a waste of time. What a strange
notion. Perhaps I misunderstood, but this sentence jumped out at
me and immediately got filed under "decidedly odd".
If the local branch is a strict ancestor, a pull is only
interesting if you want to start to work on such a branch
locally. But pull is a waste of time if you're only goal is to
push. Push suggests to pull first. So you pull; and then you
push again; and the result on the remote is the same. Only
the error message is gone that could have been avoided in the
first place. -> waste of time.

If you _pull_ it would be interesting to learn that you probably
want to merge to more than the current local branch. At that
time you expressed the intention to integrate new changes from
the remote. And it's probably a good idea to integrate changes
on all local branches that are set up to automatically merge
from the same remote you just pulled.

But if you push you want to push. You'd probably only interested
in pulls that add immediate value to the push. That is if the
result of a subsequent push modified the remote.

	Steffen

Re: [PATCH 10/10] push: teach push to be quiet if local ref is strict subset of remote ref

From: Daniel Barkalow <hidden>
Date: 2016-06-15 22:43:45

On Tue, 30 Oct 2007, Junio C Hamano wrote:
Steffen Prohaska [off-list ref] writes:
quoted
git push now allows you pushing a couple of branches that have
advanced, while ignoring all branches that have no local changes,
but are lagging behind their matching remote refs. This is done
without reporting errors.

Thanks to Junio C. Hamano [off-list ref] for suggesting to
report in the summary that refs have been ignored.
I do not think this is a good idea at all.  Furthermore, I never
suggested anything about summary.  You are robbing the
information from the pusher which ones are pushed and which ones
are left behind.
I think this case should be a warning rather than an error, though. It is 
certainly true that the user isn't intending to update those remote refs, 
because there is no local change to update them with. And it is also true 
that those local refs being stale is no impediment to updating the refs 
which are not stale, which is what the user does intend to do. I can't see 
a workflow which would be hurt by this change, because we know that, if 
the user follows the instructions and then tries the push again, it will 
have no effect.

If the concern is robbing the user of information, we should simply 
provide the information, rather than interrupting the user's work to make 
them act on the information before completing the essentially independant 
operation they're attempting.

In any case, it's misleading to suggest that the user "pull first", 
because we know that there would be no effect to pushing again after 
merging. In this case, it would be more accurate to suggest that the user 
"pull instead". Perhaps the message should be
"%s: nothing to push to %s, but you are not up-to-date and may want to 
pull"

	-Daniel
*This .sig left intentionally blank*
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help