* [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly @ 2009-07-20 11:58 Paolo Bonzini 2009-07-20 11:58 ` [PATCH 1/3] reintroduce PUSH_DEFAULT_UNSPECIFIED Paolo Bonzini ` (3 more replies) 0 siblings, 4 replies; 12+ messages in thread From: Paolo Bonzini @ 2009-07-20 11:58 UTC (permalink / raw) To: git; +Cc: gitster This second series is gets rid of the most annoying part (IMHO) of push.default = tracking, i.e. the fact that its behavior cannot be achieved using git's ordinary tools. While autosetuppush is enough to set the refspecs correctly, push.tracking does not push _all_ tracked branches, but only the current one (because it implicitly adds only one refspec, while autosetuppush places them all in the configuration). What I introduce here is "git push --current" and a companion remote.*.pushHeadOnly option to make it the default. The difference between "git push HEAD" and "git push --current" is that the latter will still walk the remote.*.push refspecs, but honor only the one matching HEAD. This means that this option, unlike the "HEAD" refspec, supports a destination name that differs from the source name. Together with autosetuppush, this more or less achieves the same result as push.tracking, at least for newly created remotes. Two (three?) subsequent series will handle the transition. Patch 1 partially reverts bba0fd2 (push: do not give big warning when no preference is configured, 2009-07-18). Patch 2 is the meat of the implementation. Most of it actually touches the transport mechanism, not builtin-push.c (which covers only one detail about how to handle "git push --current" when the remote does not have a corresponding push refspec). Patch 3 adds remote.*.pushHeadOnly. v3: documentation changes from Bjoern Steinbrink includes tests for http-push.c changes v2: update to recent master changes from Nanako's review Paolo Bonzini (3): reintroduce PUSH_DEFAULT_UNSPECIFIED push: add --current push: add remote.*.pushHeadOnly configuration Documentation/config.txt | 6 ++++ Documentation/git-push.txt | 17 ++++++++++- builtin-push.c | 17 +++++++++-- cache.h | 1 + environment.c | 2 +- http-push.c | 27 ++++++++++++++---- remote.c | 42 ++++++++++++++++++++++++----- remote.h | 3 ++ t/t5516-fetch-push.sh | 64 ++++++++++++++++++++++++++++++++++++++++++++ t/t5540-http-push.sh | 32 ++++++++++++++++++++++ transport.c | 22 ++++++++++++++- transport.h | 1 + 12 files changed, 214 insertions(+), 20 deletions(-) ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 1/3] reintroduce PUSH_DEFAULT_UNSPECIFIED 2009-07-20 11:58 [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly Paolo Bonzini @ 2009-07-20 11:58 ` Paolo Bonzini 2009-07-20 11:58 ` [PATCH 2/3] push: add --current Paolo Bonzini ` (2 subsequent siblings) 3 siblings, 0 replies; 12+ messages in thread From: Paolo Bonzini @ 2009-07-20 11:58 UTC (permalink / raw) To: git; +Cc: gitster With the next patch, the default refspec for push will depend on whether --current is being used. Revert part of bba0fd2 (push: do not give big warning when no preference is configured, 2009-07-18) to simplify the next patch. Signed-off-by: Paolo Bonzini <bonzini@gnu.org> --- builtin-push.c | 1 + cache.h | 1 + environment.c | 2 +- 3 files changed, 3 insertions(+), 1 deletions(-) diff --git a/builtin-push.c b/builtin-push.c index 1d92e22..e678a9d 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -69,6 +69,7 @@ static void setup_default_push_refspecs(void) git_config(git_default_config, NULL); switch (push_default) { default: + case PUSH_DEFAULT_UNSPECIFIED: case PUSH_DEFAULT_MATCHING: add_refspec(":"); break; diff --git a/cache.h b/cache.h index c72f125..f1e5ede 100644 --- a/cache.h +++ b/cache.h @@ -543,6 +543,7 @@ enum rebase_setup_type { }; enum push_default_type { + PUSH_DEFAULT_UNSPECIFIED = -1, PUSH_DEFAULT_NOTHING = 0, PUSH_DEFAULT_MATCHING, PUSH_DEFAULT_TRACKING, diff --git a/environment.c b/environment.c index 720f26b..801a005 100644 --- a/environment.c +++ b/environment.c @@ -42,7 +42,7 @@ enum safe_crlf safe_crlf = SAFE_CRLF_WARN; unsigned whitespace_rule_cfg = WS_DEFAULT_RULE; enum branch_track git_branch_track = BRANCH_TRACK_REMOTE; enum rebase_setup_type autorebase = AUTOREBASE_NEVER; -enum push_default_type push_default = PUSH_DEFAULT_MATCHING; +enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED; #ifndef OBJECT_CREATION_MODE #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS #endif -- 1.6.4.rc1.10.g26dbf.dirty ^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 2/3] push: add --current 2009-07-20 11:58 [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly Paolo Bonzini 2009-07-20 11:58 ` [PATCH 1/3] reintroduce PUSH_DEFAULT_UNSPECIFIED Paolo Bonzini @ 2009-07-20 11:58 ` Paolo Bonzini 2009-07-20 11:58 ` [PATCH 3/3] push: add remote.*.pushHeadOnly configuration Paolo Bonzini 2009-07-20 20:38 ` [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly Junio C Hamano 3 siblings, 0 replies; 12+ messages in thread From: Paolo Bonzini @ 2009-07-20 11:58 UTC (permalink / raw) To: git; +Cc: gitster, Tay Ray Chuan, Bjoern Steinbrink This patch adds the --current option to git-push. The option restricts pushing to the current HEAD, even in the presence of wildcard refspecs in the configuration. This achieves an effect similar to the "tracking" value of push.default, in that git push only pushes a subset of the entire push possibilities (the difference, of course, is that these are implicitly taken from remote.*.merge in the case of push.default = tracking). A secondary effect of --current is that, if there is no push.default specified, the default push refspec will be "HEAD". This conforms to the idea of pushing the current branch only and is in general more intuitive. For example in a normal configuration, "git push --current FOO" would give an error when pushing to an empty destination if this special behavior was not there. The option does not make sense, and is thus disabled, if explicit refspecs are given on the command line. Signed-off-by: Paolo Bonzini <bonzini@gnu.org> Cc: Tay Ray Chuan <rctay89@gmail.com> Cc: Bjoern Steinbrink <b.steinbrink@gmx.de> --- In the end, I decided not to mention *how* git achieves the DWIM effect for 'git push --current origin', but rather just say what it does in which circumstances. Having both "magic" refspecs 'HEAD' and ':' in the same sentence was too heavy. Documentation/git-push.txt | 17 +++++++++++++- builtin-push.c | 16 ++++++++++--- http-push.c | 27 ++++++++++++++++++----- remote.c | 40 +++++++++++++++++++++++++++++------ remote.h | 2 + t/t5516-fetch-push.sh | 50 ++++++++++++++++++++++++++++++++++++++++++++ t/t5540-http-push.sh | 32 ++++++++++++++++++++++++++++ transport.c | 22 ++++++++++++++++++- transport.h | 1 + 9 files changed, 187 insertions(+), 20 deletions(-) diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 2653388..7b1f085 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -9,8 +9,9 @@ git-push - Update remote refs along with associated objects SYNOPSIS -------- [verse] -'git push' [--all | --mirror | --tags] [--dry-run] [--receive-pack=<git-receive-pack>] - [--repo=<repository>] [-f | --force] [-v | --verbose] +'git push' [--all | --mirror | --tags | --current] [--dry-run] + [--receive-pack=<git-receive-pack>] [--repo=<repository>] + [-f | --force] [-v | --verbose] [<repository> <refspec>...] DESCRIPTION @@ -71,6 +72,18 @@ nor in any Push line of the corresponding remotes file---see below). Instead of naming each ref to push, specifies that all refs under `$GIT_DIR/refs/heads/` be pushed. +--current:: + Restrict pushing to the currently checked out branch head. + `git push` will determine the destination name of the current + branch as usual, and then push it to the given remote. In + addition, if there is no refspec in the configuration, no + `push.default` configuration, and no remote branch whose + name matches the currently checked out branch, git will + create the current branch in the remote with the same name. ++ +This option cannot be specified if an explicit refspec is given on the +command line, because it would be useless and possibly confusing. + --mirror:: Instead of naming each ref to push, specifies that all refs under `$GIT_DIR/refs/` (which includes but is not diff --git a/builtin-push.c b/builtin-push.c index e678a9d..71d94a5 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -10,7 +10,7 @@ #include "parse-options.h" static const char * const push_usage[] = { - "git push [--all | --mirror] [--dry-run] [--porcelain] [--tags] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v] [<repository> <refspec>...]", + "git push [--all | --mirror] [--current] [--dry-run] [--porcelain] [--tags] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v] [<repository> <refspec>...]", NULL, }; @@ -64,12 +64,17 @@ static void setup_push_tracking(void) add_refspec(refspec.buf); } -static void setup_default_push_refspecs(void) +static void setup_default_push_refspecs(int flags) { + push_default = PUSH_DEFAULT_UNSPECIFIED; git_config(git_default_config, NULL); + if (push_default == PUSH_DEFAULT_UNSPECIFIED) + push_default = (flags & TRANSPORT_PUSH_CURRENT + ? PUSH_DEFAULT_CURRENT + : PUSH_DEFAULT_MATCHING); + switch (push_default) { default: - case PUSH_DEFAULT_UNSPECIFIED: case PUSH_DEFAULT_MATCHING: add_refspec(":"); break; @@ -127,7 +132,7 @@ static int do_push(const char *repo, int flags) refspec = remote->push_refspec; refspec_nr = remote->push_refspec_nr; } else if (!(flags & TRANSPORT_PUSH_MIRROR)) - setup_default_push_refspecs(); + setup_default_push_refspecs(flags); } errs = 0; if (remote->pushurl_nr) { @@ -175,6 +180,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT( 0 , "mirror", &flags, "mirror all refs", (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)), OPT_BOOLEAN( 0 , "tags", &tags, "push tags"), + OPT_BIT( 0 , "current", &flags, "push current HEAD only", TRANSPORT_PUSH_CURRENT), OPT_BIT( 0 , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE), @@ -186,6 +192,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, push_usage, 0); + if ((argc > 1 || tags) && (flags & TRANSPORT_PUSH_CURRENT)) + return error ("Cannot give --current together with --tags or a refspec."); if (tags) add_refspec("refs/tags/*"); diff --git a/http-push.c b/http-push.c index 00e83dc..9c93e91 100644 --- a/http-push.c +++ b/http-push.c @@ -14,7 +14,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] [--current] [--dry-run] [--force] [--verbose] <remote> [<head>...]\n"; #ifndef XML_STATUS_OK enum XML_Status { @@ -75,7 +75,7 @@ static int aborted; static signed char remote_dir_exists[256]; static int push_verbosely; -static int push_all = MATCH_REFS_NONE; +static int match_flags = MATCH_REFS_NONE; static int force_all; static int dry_run; @@ -1802,7 +1802,11 @@ int main(int argc, char **argv) if (*arg == '-') { if (!strcmp(arg, "--all")) { - push_all = MATCH_REFS_ALL; + match_flags |= MATCH_REFS_ALL; + continue; + } + if (!strcmp(arg, "--current")) { + match_flags |= MATCH_REFS_HEAD_ONLY; continue; } if (!strcmp(arg, "--force")) { @@ -1904,7 +1908,17 @@ int main(int argc, char **argv) fetch_indices(); /* Get a list of all local and remote heads to validate refspecs */ - local_refs = get_local_heads(); + if (match_flags && MATCH_REFS_HEAD_ONLY) { + local_refs = get_current_head(); + if (!local_refs) { + fprintf(stderr, "--current specified with no current branch.\n"); + rc = -1; + goto cleanup; + } + } + else + local_refs = get_local_heads(); + fprintf(stderr, "Fetching remote heads...\n"); get_dav_remote_heads(); run_request_queue(); @@ -1919,7 +1933,7 @@ int main(int argc, char **argv) /* match them up */ if (match_refs(local_refs, &remote_refs, - nr_refspec, (const char **) refspec, push_all)) { + nr_refspec, (const char **) refspec, match_flags)) { rc = -1; goto cleanup; } @@ -2005,7 +2019,8 @@ int main(int argc, char **argv) old_sha1_hex = NULL; commit_argv[1] = "--objects"; commit_argv[2] = new_sha1_hex; - if (!push_all && !is_null_sha1(ref->old_sha1)) { + if (!(match_flags & MATCH_REFS_ALL) + && !is_null_sha1(ref->old_sha1)) { old_sha1_hex = xmalloc(42); sprintf(old_sha1_hex, "^%s", sha1_to_hex(ref->old_sha1)); diff --git a/remote.c b/remote.c index c3ada2d..b5bf9a6 100644 --- a/remote.c +++ b/remote.c @@ -990,7 +990,7 @@ static char *guess_ref(const char *name, struct ref *peer) static int match_explicit(struct ref *src, struct ref *dst, struct ref ***dst_tail, - struct refspec *rs) + struct refspec *rs, int head_only) { struct ref *matched_src, *matched_dst; int copy_src; @@ -1007,14 +1007,26 @@ static int match_explicit(struct ref *src, struct ref *dst, copy_src = 1; break; case 0: - /* The source could be in the get_sha1() format + /* + * The source could be in the get_sha1() format, * not a reference name. :refs/other is a * way to delete 'other' ref at the remote end. + * This case however could cause unwanted references + * to be stored in matched_dst->peer_ref for --current. + * In that case, all we can/want handle is HEAD. */ - matched_src = try_explicit_object_name(rs->src); + if (head_only) { + assert (!src->next); + if (strcmp(rs->src, "HEAD")) + return 0; + matched_src = src; + copy_src = 1; + } else { + matched_src = try_explicit_object_name(rs->src); + copy_src = 0; + } if (!matched_src) return error("src refspec %s does not match any.", rs->src); - copy_src = 0; break; default: return error("src refspec %s matches more than one.", rs->src); @@ -1068,11 +1080,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 head_only) { int i, errs; for (i = errs = 0; i < rs_nr; i++) - errs += match_explicit(src, dst, dst_tail, &rs[i]); + errs += match_explicit(src, dst, dst_tail, &rs[i], head_only); return errs; } @@ -1118,6 +1130,7 @@ int match_refs(struct ref *src, struct ref **dst, struct refspec *rs; int send_all = flags & MATCH_REFS_ALL; int send_mirror = flags & MATCH_REFS_MIRROR; + int head_only = flags & MATCH_REFS_HEAD_ONLY; int errs; static const char *default_refspec[] = { ":", NULL }; struct ref **dst_tail = tail_ref(dst); @@ -1127,7 +1140,8 @@ int match_refs(struct ref *src, struct ref **dst, refspec = default_refspec; } rs = parse_push_refspec(nr_refspec, (const char **) refspec); - errs = match_explicit_refs(src, *dst, &dst_tail, rs, nr_refspec); + errs = match_explicit_refs(src, *dst, &dst_tail, rs, nr_refspec, + head_only); /* pick the remainder */ for ( ; src; src = src->next) { @@ -1527,6 +1541,18 @@ struct ref *get_local_heads(void) return local_refs; } +struct ref *get_current_head(void) +{ + struct ref *local_refs = NULL, **local_tail = &local_refs; + struct branch *branch = branch_get(NULL); + unsigned char sha1[20]; + if (branch) { + get_sha1(branch->refname, sha1); + one_local_ref(branch->refname, sha1, 0, &local_tail); + } + return local_refs; +} + struct ref *guess_remote_head(const struct ref *head, const struct ref *refs, int all) diff --git a/remote.h b/remote.h index 5db8420..8e5d5b4 100644 --- a/remote.h +++ b/remote.h @@ -137,6 +137,7 @@ enum match_refs_flags { MATCH_REFS_NONE = 0, MATCH_REFS_ALL = (1 << 0), MATCH_REFS_MIRROR = (1 << 1), + MATCH_REFS_HEAD_ONLY = (1 << 2), }; /* Reporting of tracking info */ @@ -144,6 +145,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs); int format_tracking_info(struct branch *branch, struct strbuf *sb); struct ref *get_local_heads(void); +struct ref *get_current_head(void); /* * Find refs from a list which are likely to be pointed to by the given HEAD * ref. If 'all' is false, returns the most likely ref; otherwise, returns a diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 2d2633f..a480cb2 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -586,4 +586,54 @@ test_expect_success 'push with branches containing #' ' git checkout master ' +test_expect_success 'push --current succeeds on empty repository' ' + git init && + mkdir b.git && + (cd b.git && git init --bare) && + echo a > b && + git add b && + git commit -m a && + git checkout -b branch && + echo bb > b && + git add b && + git commit -m branch && + git checkout master && + git push --current b.git + test $(git rev-parse master) = $(cd b.git && git rev-parse master) +' + +test_expect_success 'push --current always creates current branch' ' + git checkout branch && + git push --current b.git && + test $(git rev-parse branch) = $(cd b.git && git rev-parse branch) +' + +test_expect_success 'push --current does not push other branches' ' + git checkout master && + echo aa > b && + git commit -m master2 b && + git checkout branch && + git push --current b.git 2>&1 | grep "Everything up-to-date" && + test $(git rev-parse master^) = $(cd b.git && git rev-parse master) +' + +test_expect_success 'push --current does update the current branches' ' + echo cc > b && + git commit -m branch2 b && + git checkout master && + git push --current b.git && + test $(git rev-parse master) = $(cd b.git && git rev-parse master) && + test $(git rev-parse branch^) = $(cd b.git && git rev-parse branch) +' + +test_expect_success 'push --current respects configuration' ' + git config remote.bremote.url b.git && + git config remote.bremote.push refs/heads/master:refs/heads/master2 && + git push --current bremote && + test $(git rev-parse master) = $(cd b.git && git rev-parse master2) + git checkout branch && + git push --current bremote 2>&1 | grep "Everything up-to-date" && + test $(git rev-parse branch^) = $(cd b.git && git rev-parse branch) +' + test_done diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index f4a2cf6..a70d13a 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -118,6 +118,38 @@ test_expect_success 'create and delete remote branch' ' test_must_fail git show-ref --verify refs/remotes/origin/dev ' +test_expect_success 'push --current and branch creation' ' + cd "$ROOT_PATH"/test_repo_clone && + git checkout dev && + git push origin HEAD && + : >path4 && + git add path4 && + test_tick && + git commit -m dev && + git checkout -b dev2 && + : >path5 && + git add path5 && + test_tick && + git commit -m dev2 && + git push --current origin && + git fetch && + test $(git rev-parse origin/dev2) = $(git rev-parse dev2) && + test $(git rev-parse origin/dev) = $(git rev-parse dev^) +' + +test_expect_success 'push --current and branch update' ' + cd "$ROOT_PATH"/test_repo_clone && + : >path6 && + git add path6 && + test_tick && + git commit -m dev2 && + git checkout dev && + git push --current origin && + git fetch && + test $(git rev-parse origin/dev) = $(git rev-parse dev) && + test $(git rev-parse origin/dev2) = $(git rev-parse dev2^) +' + test_expect_success 'MKCOL sends directory names with trailing slashes' ' ! grep "\"MKCOL.*[^/] HTTP/[^ ]*\"" < "$HTTPD_ROOT_PATH"/access.log diff --git a/transport.c b/transport.c index de0d587..c7b6aaa 100644 --- a/transport.c +++ b/transport.c @@ -327,6 +327,11 @@ static int rsync_transport_push(struct transport *transport, if (flags & TRANSPORT_PUSH_ALL) { if (for_each_ref(write_one_ref, &temp_dir)) return -1; + } else if (flags & TRANSPORT_PUSH_CURRENT) { + struct branch *branch = branch_get(NULL); + unsigned char sha1[20]; + get_sha1(branch->name, sha1); + write_one_ref(branch->name, sha1, 0, &temp_dir); } else if (write_refs_to_temp_dir(&temp_dir, refspec_nr, refspec)) return -1; @@ -406,6 +411,8 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons argc = 1; if (flags & TRANSPORT_PUSH_ALL) argv[argc++] = "--all"; + if (flags & TRANSPORT_PUSH_CURRENT) + argv[argc++] = "--current"; if (flags & TRANSPORT_PUSH_FORCE) argv[argc++] = "--force"; if (flags & TRANSPORT_PUSH_DRY_RUN) @@ -1001,12 +1008,19 @@ int transport_push(struct transport *transport, { verify_remote_names(refspec_nr, refspec); + if (flags & TRANSPORT_PUSH_CURRENT) { + struct branch *branch = branch_get(NULL); + if (!branch) + return error("Tried to push current branch, but there " + "is no current branch!"); + } + if (transport->push) return transport->push(transport, refspec_nr, refspec, flags); if (transport->push_refs) { struct ref *remote_refs = transport->get_refs_list(transport, 1); - struct ref *local_refs = get_local_heads(); + struct ref *local_refs; int match_flags = MATCH_REFS_NONE; int verbose = flags & TRANSPORT_PUSH_VERBOSE; int porcelain = flags & TRANSPORT_PUSH_PORCELAIN; @@ -1017,6 +1031,12 @@ int transport_push(struct transport *transport, if (flags & TRANSPORT_PUSH_MIRROR) match_flags |= MATCH_REFS_MIRROR; + if (flags & TRANSPORT_PUSH_CURRENT) { + local_refs = get_current_head(); + match_flags |= MATCH_REFS_HEAD_ONLY; + } else + local_refs = get_local_heads(); + if (match_refs(local_refs, &remote_refs, refspec_nr, refspec, match_flags)) { return -1; diff --git a/transport.h b/transport.h index 51b5397..62aa243 100644 --- a/transport.h +++ b/transport.h @@ -36,6 +36,7 @@ struct transport { #define TRANSPORT_PUSH_MIRROR 8 #define TRANSPORT_PUSH_VERBOSE 16 #define TRANSPORT_PUSH_PORCELAIN 32 +#define TRANSPORT_PUSH_CURRENT 64 /* Returns a transport suitable for the url */ struct transport *transport_get(struct remote *, const char *); -- 1.6.4.rc1.10.g26dbf.dirty ^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 3/3] push: add remote.*.pushHeadOnly configuration 2009-07-20 11:58 [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly Paolo Bonzini 2009-07-20 11:58 ` [PATCH 1/3] reintroduce PUSH_DEFAULT_UNSPECIFIED Paolo Bonzini 2009-07-20 11:58 ` [PATCH 2/3] push: add --current Paolo Bonzini @ 2009-07-20 11:58 ` Paolo Bonzini 2009-07-20 20:38 ` [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly Junio C Hamano 3 siblings, 0 replies; 12+ messages in thread From: Paolo Bonzini @ 2009-07-20 11:58 UTC (permalink / raw) To: git; +Cc: gitster This patch adds a remote.*.pushHeadOnly configuration that automatically enables (when possible) the --current option to git push. Signed-off-by: Paolo Bonzini <bonzini@gnu.org> --- Documentation/config.txt | 6 ++++++ builtin-push.c | 2 ++ remote.c | 2 ++ remote.h | 1 + t/t5516-fetch-push.sh | 16 +++++++++++++++- 5 files changed, 26 insertions(+), 1 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index cb6832b..4ab5593 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1359,6 +1359,12 @@ remote.<name>.uploadpack:: The default program to execute on the remote side when fetching. See option \--upload-pack of linkgit:git-fetch-pack[1]. +remote.<name>.pushHeadOnly:: + If true, whenever `git push` is invoked without a refspec and + it will try pushing to this remote, `git push` will automatically + behave as if the `\--current` option was given on the command line. + In other words, only the current branch is pushed to the remote. + remote.<name>.tagopt:: Setting this value to \--no-tags disables automatic tag following when fetching from remote <name> diff --git a/builtin-push.c b/builtin-push.c index 71d94a5..8d5b054 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -109,6 +109,8 @@ static int do_push(const char *repo, int flags) if (remote->mirror) flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); + if (remote->push_head_only && !refspec_nr) + flags |= TRANSPORT_PUSH_CURRENT; if ((flags & TRANSPORT_PUSH_ALL) && refspec) { if (!strcmp(*refspec, "refs/tags/*")) diff --git a/remote.c b/remote.c index b5bf9a6..d46dc0d 100644 --- a/remote.c +++ b/remote.c @@ -379,6 +379,8 @@ static int handle_config(const char *key, const char *value, void *cb) remote->mirror = git_config_bool(key, value); else if (!strcmp(subkey, ".skipdefaultupdate")) remote->skip_default_update = git_config_bool(key, value); + else if (!strcmp(subkey, ".pushheadonly")) + remote->push_head_only = git_config_bool(key, value); else if (!strcmp(subkey, ".url")) { const char *v; diff --git a/remote.h b/remote.h index 8e5d5b4..b1e3e99 100644 --- a/remote.h +++ b/remote.h @@ -36,6 +36,7 @@ struct remote { * 2 to always fetch tags */ int fetch_tags; + int push_head_only; int skip_default_update; int mirror; diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index a480cb2..9d61ba0 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -631,9 +631,23 @@ test_expect_success 'push --current respects configuration' ' git config remote.bremote.push refs/heads/master:refs/heads/master2 && git push --current bremote && test $(git rev-parse master) = $(cd b.git && git rev-parse master2) +' + +test_expect_success 'remote.*.pushHeadOnly respects configuration' ' + echo xx > b && + git commit -mmaster3 b && + git config remote.bremote.pushHeadOnly true && git checkout branch && - git push --current bremote 2>&1 | grep "Everything up-to-date" && + git push bremote && + test $(git rev-parse master^) = $(cd b.git && git rev-parse master) && test $(git rev-parse branch^) = $(cd b.git && git rev-parse branch) ' +test_expect_success 'remote.*.pushHeadOnly works' ' + git config --unset remote.bremote.push && + git push bremote && + test $(git rev-parse master^) = $(cd b.git && git rev-parse master) && + test $(git rev-parse branch) = $(cd b.git && git rev-parse branch) +' + test_done -- 1.6.4.rc1.10.g26dbf.dirty ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly 2009-07-20 11:58 [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly Paolo Bonzini ` (2 preceding siblings ...) 2009-07-20 11:58 ` [PATCH 3/3] push: add remote.*.pushHeadOnly configuration Paolo Bonzini @ 2009-07-20 20:38 ` Junio C Hamano 2009-07-20 22:09 ` Paolo Bonzini 3 siblings, 1 reply; 12+ messages in thread From: Junio C Hamano @ 2009-07-20 20:38 UTC (permalink / raw) To: Paolo Bonzini; +Cc: git, Finn Arne Gangstad Paolo Bonzini <bonzini@gnu.org> writes: > This second series is gets rid of the most annoying part (IMHO) of > push.default = tracking, i.e. the fact that its behavior cannot be > achieved using git's ordinary tools. Having to read this three times made me irritated enough to comment on this part of the cover letter. If some feature X cannot be achieved by combinations of other existing features, is it a bad thing? Why should that be annoying? Maybe it is just the matter of phrasing, but I do not think the above statement helps understanding of the issue. I would certainly understand the justification if it were "This feature improved things somewhat, in that it now allows you to do X, but it did not go far enough. It does not help satisfying wishes Y and Z are similar enough to X and are common enough. Here is an attempt to extend the mechanism in a more generic way to do so". But I do not get a clear sense of what these Y and Z are from the above description, therefore I personally find it hard to judge if Y and Z are so important to support with more code, compared to the support for X we already have. Maybe Finn Arne Gangstad, who originally did push.default, can shed some light on this? Do you agree that Paolo's "annoyance" is justified, and this series makes things saner? I actually do agree that it feels somewhat unbalanced that "git push" with no other arguments can do a "matching" push of _all_ branches without any funny configuration, while the same parameterless push needs many configured remote.*.push refspecs to do a "tracking" push of all branches. And as you say, branch.autosetuppush may make the remote.*.push configuration less painful to set up for the users. So the inbalance may not hurt in practice from the end user's point of view. But more importantly, I think that inbalance is inherent to a certain extent, and it probably is _not_ a bad thing in the first place. If your workflow is to use local branches with the same name as the remote side as your own local integration branches, "matching" push that pushes all matching branches makes a good deal of sense. You keep your local integration branches clean by never integrating premature topics into them, so it is always safe to push them out in one go. If on the other hand your workflow is to fork topics from the remote integration branch(es) (e.g. topicA and fixB both forked from master taken from the remote), both topics will be set to push back to the same master branch on the remote side if you use branch.autosetuppush. In such a workflow, you work on these two topics, and when one of them is done, you want to push that one out, without having to push the other one that is not yet ready (beside, that one won't fast-forward). A "tracking" push that pushes all branches is actively a wrong thing to do in such a workflow. Side note: in that sense, branch.autosetuppush might be a well intentioned but ill conceived concept, and we may want to remove it from 'next'. Of course, even if you are using "matching", you can (temporarily) have an experimental integration on one of the matching branches while the other one is truly ready, and in such a situation you do not want to push out all matching branches (hence you would occasionally need to be more explicit than usual, i.e. "git push origin master"). Also, even if you are forking your topics directly from the remote integration branches and pushing back to where they forked from, you can adopt a discipline not to push out when you have some topics that are not ready. But the point is that the discipline to keep branches that can be pushed out clean is much easier to follow in "matching push" workflow, because it is an integral part of the workflow. If merging a topic to an integration branch is a declaration of doneness of the topic, by definition, your integration branches are all ready to be pushed out at any time. If you fork topics from remote's integration branch and push each of them back individually, "keeping branches that can be pushed out clean" is not even a discipline, but it is a hindrance, as the point of such a workflow is to push things out as they become cooked one-by-one. In such a workflow, you do want "one-by-one, push only the current one I just tested". > ...set the refspecs correctly, push.tracking does not push _all_ tracked > branches, but only the current one (because it implicitly adds only one > refspec, while autosetuppush places them all in the configuration). In short, it is not necessarily bad that "push all matching" is much easier to set up and use than "push all tracking", and I think it is nothing to be annoyed about. ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly 2009-07-20 20:38 ` [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly Junio C Hamano @ 2009-07-20 22:09 ` Paolo Bonzini 0 siblings, 0 replies; 12+ messages in thread From: Paolo Bonzini @ 2009-07-20 22:09 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, Finn Arne Gangstad > Having to read this three times made me irritated enough to comment on > this part of the cover letter. Sorry (you weren't CCed on v1, though, were you?). For me, the inherent problem of push.default is that it is a global setting. As you say below, different remotes may have different characteristics and this can happen within the same repository. One example is an integrator (push.default=matching) that also develops a few topic branches and wants to publish his tree so that people can peek at them --- at the same time, he wants to be free to rebase there. He can then use --mirror to set up the latter remote. Now, instead, we have a person working in a set up like the one Finn described. He is a subsystem integrator, so he publishes some stable branches (push.default=matching again). For development, however, he has to branch off the repositories published by other subsystem integrators; for these he wants push.default=tracking semantics. He then has to define push.default=tracking and configure the other remote manually. Unlike the first user, this one needs to make a global decision and adjust the git configuration whenever this global decision does not fit. In addition, if he wants to disable pushing to some repository, he cannot use push.default=nothing anymore. The problem is that "the exception [the setting of push.default that was desired for one repository] became the rule [the global per-repository push.default setting]", and this happened because there is no way to realize push.default=tracking with the usual set of git tools, namely refspecs and checkout -t. While trying to understand the problem, I tried to see what it would take to implement push.default using the existing git tools, and what extra features would be needed. The resulting design had three new features (per-remote tracking, autosetuppush and pushHeadOnly), and everything else built on top of those (the --push patch series I posted today). I actually liked the design, and decided it was worth pursuing it instead of just turning push.default into a per-remote configuration. It is way bigger than the push.default patches; on the other hand the three new features may be otherwise desirable, and it also includes some nice cleanups. However, because of this I placed too much attention to the separate features and failed to show the big picture and what was missing in the 1.6.3 implementation. (Now, back to your comments). > [...] If on the other hand your workflow is to fork topics from the remote > integration branch(es) (e.g. topicA and fixB both forked from master taken > from the remote), both topics will be set to push back to the same master > branch on the remote side if you use branch.autosetuppush. In such a > workflow, you work on these two topics, and when one of them is done, you > want to push that one out, without having to push the other one that is > not yet ready (beside, that one won't fast-forward). > A "tracking" push that pushes all branches is actively a wrong thing to do > in such a workflow. Agreed. autosetuppush is in fact not meant to be used by the usre, at least the casual user who restricts himself to the "git remote" porcelain and prefers not to deal into .git/config whenever possible. The idea is that the user can use --push=tracking to express his workflow, and this will set up pushHeadOnly and autosetuppush together. The combination of the two options is safe, and provides the behavior that best suites the user's workflow. (Writing this final paragraph is what made me realize me that I was wrong in not showing the big picture in the beginning. Giving hints through the cover letters was not enough). > In short, it is not necessarily bad that "push all matching" is much > easier to set up and use than "push all tracking", and I think it is > nothing to be annoyed about. The problem is, when "the exception becomes the rule", is it still true that "push all matching" is easier to set up? Paolo ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 0/3] add push --current and remote.*.pushHeadOnly @ 2009-07-20 6:36 Paolo Bonzini 2009-07-20 6:36 ` [PATCH 2/3] push: add --current Paolo Bonzini 0 siblings, 1 reply; 12+ messages in thread From: Paolo Bonzini @ 2009-07-20 6:36 UTC (permalink / raw) To: git; +Cc: gitster This second series is gets rid of the most annoying part (IMHO) of push.default = tracking, i.e. the fact that its behavior cannot be achieved using git's ordinary tools. While autosetuppush is enough to set the refspecs correctly, push.tracking does not push _all_ tracked branches, but only the current one (because it implicitly adds only one refspec, while autosetuppush places them all in the configuration). What I introduce here is "git push --current" and a companion remote.*.pushHeadOnly option to make it the default. The difference between "git push HEAD" and "git push --current" is that the latter will still walk the remote.*.push refspecs, but honor only the one matching HEAD. Together with autosetuppush, this more or less achieves the same result as push.tracking, at least for newly created remotes. A subsequent series will handle the transition. v2 integrates changes from Nanako's review. Patch 1 is new and partially reverts bba0fd2 (push: do not give big warning when no preference is configured, 2009-07-18). Patch 2 is the meat of the implementation. Most of it actually touches the transport mechanism, not builtin-push.c (which covers only one detail about how to handle "git push --current" when the remote does not have a corresponding push refspec). Patch 3 adds remote.*.pushHeadOnly. Paolo Bonzini (3): reintroduce PUSH_DEFAULT_UNSPECIFIED push: add --current push: add remote.*.pushHeadOnly configuration Documentation/config.txt | 6 ++++ Documentation/git-push.txt | 18 +++++++++++- builtin-push.c | 17 +++++++++-- cache.h | 1 + environment.c | 2 +- http-push.c | 27 ++++++++++++++---- remote.c | 42 ++++++++++++++++++++++++----- remote.h | 3 ++ t/t5516-fetch-push.sh | 64 ++++++++++++++++++++++++++++++++++++++++++++ transport.c | 22 ++++++++++++++- transport.h | 1 + 11 files changed, 183 insertions(+), 20 deletions(-) ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 2/3] push: add --current 2009-07-20 6:36 [PATCH " Paolo Bonzini @ 2009-07-20 6:36 ` Paolo Bonzini 2009-07-20 7:14 ` Björn Steinbrink ` (2 more replies) 0 siblings, 3 replies; 12+ messages in thread From: Paolo Bonzini @ 2009-07-20 6:36 UTC (permalink / raw) To: git; +Cc: gitster This patch adds the --current option to git-push. The option restricts pushing to the current HEAD, even in the presence of wildcard refspecs in the configuration. This achieves an effect similar to the "tracking" value of push.default, in that git push only pushes a subset of the entire push possibilities (the difference, of course, is that these are implicitly taken from remote.*.merge in the case of push.default = tracking). A secondary effect of --head is that, if there is no push.default specified, I make the default push refspec "HEAD". This conforms to the idea of pushing the current branch only and is in general more intuitive. For example in a normal configuration, "git push --current FOO" would give an error when pushing to an empty destination if this special behavior was not there. The option does not make sense, and is thus disabled, if explicit refspecs are given on the command line. Signed-off-by: Paolo Bonzini <bonzini@gnu.org> --- Documentation/git-push.txt | 18 ++++++++++++++- builtin-push.c | 16 ++++++++++--- http-push.c | 27 ++++++++++++++++++----- remote.c | 40 +++++++++++++++++++++++++++++------ remote.h | 2 + t/t5516-fetch-push.sh | 50 ++++++++++++++++++++++++++++++++++++++++++++ transport.c | 22 ++++++++++++++++++- transport.h | 1 + 8 files changed, 156 insertions(+), 20 deletions(-) diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 2653388..8d03ea7 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -9,8 +9,9 @@ git-push - Update remote refs along with associated objects SYNOPSIS -------- [verse] -'git push' [--all | --mirror | --tags] [--dry-run] [--receive-pack=<git-receive-pack>] - [--repo=<repository>] [-f | --force] [-v | --verbose] +'git push' [--all | --mirror | --tags | --current] [--dry-run] + [--receive-pack=<git-receive-pack>] [--repo=<repository>] + [-f | --force] [-v | --verbose] [<repository> <refspec>...] DESCRIPTION @@ -71,6 +72,19 @@ nor in any Push line of the corresponding remotes file---see below). Instead of naming each ref to push, specifies that all refs under `$GIT_DIR/refs/heads/` be pushed. +--current:: + Independent of the other options, restrict pushing to the current + HEAD. ++ +Refspecs given in the configuration are still used to find the +destination name of the current branch. However, this option cannot +be specified if an explicit refspec is given on the command line, +because it would be useless and possibly confusing. ++ +Additionally, if there is no refspec in the configuration and no +`push.default` configuration either, with this option git will use a +default refspec of `HEAD` rather than `:`. + --mirror:: Instead of naming each ref to push, specifies that all refs under `$GIT_DIR/refs/` (which includes but is not diff --git a/builtin-push.c b/builtin-push.c index e678a9d..71d94a5 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -10,7 +10,7 @@ #include "parse-options.h" static const char * const push_usage[] = { - "git push [--all | --mirror] [--dry-run] [--porcelain] [--tags] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v] [<repository> <refspec>...]", + "git push [--all | --mirror] [--current] [--dry-run] [--porcelain] [--tags] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v] [<repository> <refspec>...]", NULL, }; @@ -64,12 +64,17 @@ static void setup_push_tracking(void) add_refspec(refspec.buf); } -static void setup_default_push_refspecs(void) +static void setup_default_push_refspecs(int flags) { + push_default = PUSH_DEFAULT_UNSPECIFIED; git_config(git_default_config, NULL); + if (push_default == PUSH_DEFAULT_UNSPECIFIED) + push_default = (flags & TRANSPORT_PUSH_CURRENT + ? PUSH_DEFAULT_CURRENT + : PUSH_DEFAULT_MATCHING); + switch (push_default) { default: - case PUSH_DEFAULT_UNSPECIFIED: case PUSH_DEFAULT_MATCHING: add_refspec(":"); break; @@ -127,7 +132,7 @@ static int do_push(const char *repo, int flags) refspec = remote->push_refspec; refspec_nr = remote->push_refspec_nr; } else if (!(flags & TRANSPORT_PUSH_MIRROR)) - setup_default_push_refspecs(); + setup_default_push_refspecs(flags); } errs = 0; if (remote->pushurl_nr) { @@ -175,6 +180,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT( 0 , "mirror", &flags, "mirror all refs", (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)), OPT_BOOLEAN( 0 , "tags", &tags, "push tags"), + OPT_BIT( 0 , "current", &flags, "push current HEAD only", TRANSPORT_PUSH_CURRENT), OPT_BIT( 0 , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE), @@ -186,6 +192,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, push_usage, 0); + if ((argc > 1 || tags) && (flags & TRANSPORT_PUSH_CURRENT)) + return error ("Cannot give --current together with --tags or a refspec."); if (tags) add_refspec("refs/tags/*"); diff --git a/http-push.c b/http-push.c index 00e83dc..9c93e91 100644 --- a/http-push.c +++ b/http-push.c @@ -14,7 +14,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] [--current] [--dry-run] [--force] [--verbose] <remote> [<head>...]\n"; #ifndef XML_STATUS_OK enum XML_Status { @@ -75,7 +75,7 @@ static int aborted; static signed char remote_dir_exists[256]; static int push_verbosely; -static int push_all = MATCH_REFS_NONE; +static int match_flags = MATCH_REFS_NONE; static int force_all; static int dry_run; @@ -1802,7 +1802,11 @@ int main(int argc, char **argv) if (*arg == '-') { if (!strcmp(arg, "--all")) { - push_all = MATCH_REFS_ALL; + match_flags |= MATCH_REFS_ALL; + continue; + } + if (!strcmp(arg, "--current")) { + match_flags |= MATCH_REFS_HEAD_ONLY; continue; } if (!strcmp(arg, "--force")) { @@ -1904,7 +1908,17 @@ int main(int argc, char **argv) fetch_indices(); /* Get a list of all local and remote heads to validate refspecs */ - local_refs = get_local_heads(); + if (match_flags && MATCH_REFS_HEAD_ONLY) { + local_refs = get_current_head(); + if (!local_refs) { + fprintf(stderr, "--current specified with no current branch.\n"); + rc = -1; + goto cleanup; + } + } + else + local_refs = get_local_heads(); + fprintf(stderr, "Fetching remote heads...\n"); get_dav_remote_heads(); run_request_queue(); @@ -1919,7 +1933,7 @@ int main(int argc, char **argv) /* match them up */ if (match_refs(local_refs, &remote_refs, - nr_refspec, (const char **) refspec, push_all)) { + nr_refspec, (const char **) refspec, match_flags)) { rc = -1; goto cleanup; } @@ -2005,7 +2019,8 @@ int main(int argc, char **argv) old_sha1_hex = NULL; commit_argv[1] = "--objects"; commit_argv[2] = new_sha1_hex; - if (!push_all && !is_null_sha1(ref->old_sha1)) { + if (!(match_flags & MATCH_REFS_ALL) + && !is_null_sha1(ref->old_sha1)) { old_sha1_hex = xmalloc(42); sprintf(old_sha1_hex, "^%s", sha1_to_hex(ref->old_sha1)); diff --git a/remote.c b/remote.c index c3ada2d..b5bf9a6 100644 --- a/remote.c +++ b/remote.c @@ -990,7 +990,7 @@ static char *guess_ref(const char *name, struct ref *peer) static int match_explicit(struct ref *src, struct ref *dst, struct ref ***dst_tail, - struct refspec *rs) + struct refspec *rs, int head_only) { struct ref *matched_src, *matched_dst; int copy_src; @@ -1007,14 +1007,26 @@ static int match_explicit(struct ref *src, struct ref *dst, copy_src = 1; break; case 0: - /* The source could be in the get_sha1() format + /* + * The source could be in the get_sha1() format * not a reference name. :refs/other is a * way to delete 'other' ref at the remote end. + * This case however could cause unwanted references + * to be stored in matched_dst->peer_ref for --current. + * In that case, all we can/want handle is HEAD. */ - matched_src = try_explicit_object_name(rs->src); + if (head_only) { + assert (!src->next); + if (strcmp(rs->src, "HEAD")) + return 0; + matched_src = src; + copy_src = 1; + } else { + matched_src = try_explicit_object_name(rs->src); + copy_src = 0; + } if (!matched_src) return error("src refspec %s does not match any.", rs->src); - copy_src = 0; break; default: return error("src refspec %s matches more than one.", rs->src); @@ -1068,11 +1080,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 head_only) { int i, errs; for (i = errs = 0; i < rs_nr; i++) - errs += match_explicit(src, dst, dst_tail, &rs[i]); + errs += match_explicit(src, dst, dst_tail, &rs[i], head_only); return errs; } @@ -1118,6 +1130,7 @@ int match_refs(struct ref *src, struct ref **dst, struct refspec *rs; int send_all = flags & MATCH_REFS_ALL; int send_mirror = flags & MATCH_REFS_MIRROR; + int head_only = flags & MATCH_REFS_HEAD_ONLY; int errs; static const char *default_refspec[] = { ":", NULL }; struct ref **dst_tail = tail_ref(dst); @@ -1127,7 +1140,8 @@ int match_refs(struct ref *src, struct ref **dst, refspec = default_refspec; } rs = parse_push_refspec(nr_refspec, (const char **) refspec); - errs = match_explicit_refs(src, *dst, &dst_tail, rs, nr_refspec); + errs = match_explicit_refs(src, *dst, &dst_tail, rs, nr_refspec, + head_only); /* pick the remainder */ for ( ; src; src = src->next) { @@ -1527,6 +1541,18 @@ struct ref *get_local_heads(void) return local_refs; } +struct ref *get_current_head(void) +{ + struct ref *local_refs = NULL, **local_tail = &local_refs; + struct branch *branch = branch_get(NULL); + unsigned char sha1[20]; + if (branch) { + get_sha1(branch->refname, sha1); + one_local_ref(branch->refname, sha1, 0, &local_tail); + } + return local_refs; +} + struct ref *guess_remote_head(const struct ref *head, const struct ref *refs, int all) diff --git a/remote.h b/remote.h index 5db8420..8e5d5b4 100644 --- a/remote.h +++ b/remote.h @@ -137,6 +137,7 @@ enum match_refs_flags { MATCH_REFS_NONE = 0, MATCH_REFS_ALL = (1 << 0), MATCH_REFS_MIRROR = (1 << 1), + MATCH_REFS_HEAD_ONLY = (1 << 2), }; /* Reporting of tracking info */ @@ -144,6 +145,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs); int format_tracking_info(struct branch *branch, struct strbuf *sb); struct ref *get_local_heads(void); +struct ref *get_current_head(void); /* * Find refs from a list which are likely to be pointed to by the given HEAD * ref. If 'all' is false, returns the most likely ref; otherwise, returns a diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 2d2633f..a480cb2 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -586,4 +586,54 @@ test_expect_success 'push with branches containing #' ' git checkout master ' +test_expect_success 'push --current succeeds on empty repository' ' + git init && + mkdir b.git && + (cd b.git && git init --bare) && + echo a > b && + git add b && + git commit -m a && + git checkout -b branch && + echo bb > b && + git add b && + git commit -m branch && + git checkout master && + git push --current b.git + test $(git rev-parse master) = $(cd b.git && git rev-parse master) +' + +test_expect_success 'push --current always creates current branch' ' + git checkout branch && + git push --current b.git && + test $(git rev-parse branch) = $(cd b.git && git rev-parse branch) +' + +test_expect_success 'push --current does not push other branches' ' + git checkout master && + echo aa > b && + git commit -m master2 b && + git checkout branch && + git push --current b.git 2>&1 | grep "Everything up-to-date" && + test $(git rev-parse master^) = $(cd b.git && git rev-parse master) +' + +test_expect_success 'push --current does update the current branches' ' + echo cc > b && + git commit -m branch2 b && + git checkout master && + git push --current b.git && + test $(git rev-parse master) = $(cd b.git && git rev-parse master) && + test $(git rev-parse branch^) = $(cd b.git && git rev-parse branch) +' + +test_expect_success 'push --current respects configuration' ' + git config remote.bremote.url b.git && + git config remote.bremote.push refs/heads/master:refs/heads/master2 && + git push --current bremote && + test $(git rev-parse master) = $(cd b.git && git rev-parse master2) + git checkout branch && + git push --current bremote 2>&1 | grep "Everything up-to-date" && + test $(git rev-parse branch^) = $(cd b.git && git rev-parse branch) +' + test_done diff --git a/transport.c b/transport.c index de0d587..c7b6aaa 100644 --- a/transport.c +++ b/transport.c @@ -327,6 +327,11 @@ static int rsync_transport_push(struct transport *transport, if (flags & TRANSPORT_PUSH_ALL) { if (for_each_ref(write_one_ref, &temp_dir)) return -1; + } else if (flags & TRANSPORT_PUSH_CURRENT) { + struct branch *branch = branch_get(NULL); + unsigned char sha1[20]; + get_sha1(branch->name, sha1); + write_one_ref(branch->name, sha1, 0, &temp_dir); } else if (write_refs_to_temp_dir(&temp_dir, refspec_nr, refspec)) return -1; @@ -406,6 +411,8 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons argc = 1; if (flags & TRANSPORT_PUSH_ALL) argv[argc++] = "--all"; + if (flags & TRANSPORT_PUSH_CURRENT) + argv[argc++] = "--current"; if (flags & TRANSPORT_PUSH_FORCE) argv[argc++] = "--force"; if (flags & TRANSPORT_PUSH_DRY_RUN) @@ -1001,12 +1008,19 @@ int transport_push(struct transport *transport, { verify_remote_names(refspec_nr, refspec); + if (flags & TRANSPORT_PUSH_CURRENT) { + struct branch *branch = branch_get(NULL); + if (!branch) + return error("Tried to push current branch, but there " + "is no current branch!"); + } + if (transport->push) return transport->push(transport, refspec_nr, refspec, flags); if (transport->push_refs) { struct ref *remote_refs = transport->get_refs_list(transport, 1); - struct ref *local_refs = get_local_heads(); + struct ref *local_refs; int match_flags = MATCH_REFS_NONE; int verbose = flags & TRANSPORT_PUSH_VERBOSE; int porcelain = flags & TRANSPORT_PUSH_PORCELAIN; @@ -1017,6 +1031,12 @@ int transport_push(struct transport *transport, if (flags & TRANSPORT_PUSH_MIRROR) match_flags |= MATCH_REFS_MIRROR; + if (flags & TRANSPORT_PUSH_CURRENT) { + local_refs = get_current_head(); + match_flags |= MATCH_REFS_HEAD_ONLY; + } else + local_refs = get_local_heads(); + if (match_refs(local_refs, &remote_refs, refspec_nr, refspec, match_flags)) { return -1; diff --git a/transport.h b/transport.h index 51b5397..62aa243 100644 --- a/transport.h +++ b/transport.h @@ -36,6 +36,7 @@ struct transport { #define TRANSPORT_PUSH_MIRROR 8 #define TRANSPORT_PUSH_VERBOSE 16 #define TRANSPORT_PUSH_PORCELAIN 32 +#define TRANSPORT_PUSH_CURRENT 64 /* Returns a transport suitable for the url */ struct transport *transport_get(struct remote *, const char *); -- 1.6.2.5 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH 2/3] push: add --current 2009-07-20 6:36 ` [PATCH 2/3] push: add --current Paolo Bonzini @ 2009-07-20 7:14 ` Björn Steinbrink 2009-07-20 9:48 ` Paolo Bonzini 2009-07-20 10:15 ` Tay Ray Chuan 2009-07-20 11:17 ` demerphq 2 siblings, 1 reply; 12+ messages in thread From: Björn Steinbrink @ 2009-07-20 7:14 UTC (permalink / raw) To: Paolo Bonzini; +Cc: git, gitster On 2009.07.20 08:36:59 +0200, Paolo Bonzini wrote: > +--current:: > + Independent of the other options, restrict pushing to the current > + HEAD. To me, this sounds like there are multiple HEADs and this selects the current one to be pushed, which would be wrong. Maybe this could be written as: Restrict pushing to the currently checked out branch head. Which is also in line with the error message that is produced when you're on a detached HEAD. > +Refspecs given in the configuration are still used to find the > +destination name of the current branch. However, this option cannot > +be specified if an explicit refspec is given on the command line, > +because it would be useless and possibly confusing. Hm, this only talks about refspecs in the config, but your patch series is especially about push.default=tracking, which is not a refspec set in the config, but causes a refspec to be generated on the fly. Maybe: With this option, defaults given in the configuration, either as push refspecs for the remote or as a global push default, are still evaluated, but only the currently checked out branch is pushed. This means that this option, unlike the `HEAD` refspec, supports a destination name that differs from the source name. However, this option cannot be specified if an explicit refspec is given on the command line, because it would be useless and possibly confusing. Which also explains a bit how this option differs from using the HEAD refspec. > ++ > +Additionally, if there is no refspec in the configuration and no > +`push.default` configuration either, with this option git will use a > +default refspec of `HEAD` rather than `:`. Hm, this looks kind of funny with my rewrite of the previous paragraph, so: Additionally, if there are no push defaults given in the configuration at all, this option makes git default to `HEAD` instead of `:`. Björn ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 2/3] push: add --current 2009-07-20 7:14 ` Björn Steinbrink @ 2009-07-20 9:48 ` Paolo Bonzini 0 siblings, 0 replies; 12+ messages in thread From: Paolo Bonzini @ 2009-07-20 9:48 UTC (permalink / raw) To: Björn Steinbrink; +Cc: Paolo Bonzini, git, gitster >> +--current:: >> + Independent of the other options, restrict pushing to the current >> + HEAD. > > To me, this sounds like there are multiple HEADs and this selects the > current one to be pushed, which would be wrong. Maybe this could be > written as: > > Restrict pushing to the currently checked out branch head. I wrote that referring to "the branch currently pointed to by [the symref] HEAD". > Hm, this only talks about refspecs in the config, but your patch series > is especially about push.default=tracking, which is not a refspec set in > the config, but causes a refspec to be generated on the fly. Not really, as push.default=tracking and push.default=current would anyway push only the currently checked out branch. "git push --current" would have a visible effect only for push.default=matching (the effect would be the same as push.default=current except that a new branch will not be created remotely). If you have creted all your tracking branches with autosetuppush, then in that case "git push --current" or pushHeadOnly will indeed achieve the same effect as push.default=tracking; however, that would be with real refspecs in the config rather than with one generated on the fly. And since in that case you have a push refspec in the configuration, push.default would not be used. Paolo ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 2/3] push: add --current 2009-07-20 6:36 ` [PATCH 2/3] push: add --current Paolo Bonzini 2009-07-20 7:14 ` Björn Steinbrink @ 2009-07-20 10:15 ` Tay Ray Chuan 2009-07-20 11:50 ` Paolo Bonzini 2009-07-20 11:17 ` demerphq 2 siblings, 1 reply; 12+ messages in thread From: Tay Ray Chuan @ 2009-07-20 10:15 UTC (permalink / raw) To: Paolo Bonzini; +Cc: git, gitster Hi, On Mon, Jul 20, 2009 at 2:36 PM, Paolo Bonzini<bonzini@gnu.org> wrote: > t/t5516-fetch-push.sh | 50 ++++++++++++++++++++++++++++++++++++++++++++ since you're making modifications to pushing over HTTP, you should consider making tests also for HTTP repositories, or else your changes in http-push.c won't be used. -- Cheers, Ray Chuan ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 2/3] push: add --current 2009-07-20 10:15 ` Tay Ray Chuan @ 2009-07-20 11:50 ` Paolo Bonzini 0 siblings, 0 replies; 12+ messages in thread From: Paolo Bonzini @ 2009-07-20 11:50 UTC (permalink / raw) To: Tay Ray Chuan; +Cc: git, gitster On 07/20/2009 12:15 PM, Tay Ray Chuan wrote: > Hi, > > On Mon, Jul 20, 2009 at 2:36 PM, Paolo Bonzini<bonzini@gnu.org> wrote: >> t/t5516-fetch-push.sh | 50 ++++++++++++++++++++++++++++++++++++++++++++ > > since you're making modifications to pushing over HTTP, you should > consider making tests also for HTTP repositories, or else your changes > in http-push.c won't be used. Yeah, I had tested them with a server I already had setup rather than with the testsuite, because the default Apache setup of Fedora is not detected correctly by lib-httpd.sh. Any test I included would then be untested, while http-push.c was tested. :-) However, I'll include the tests in v3 of the patch. Paolo ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 2/3] push: add --current 2009-07-20 6:36 ` [PATCH 2/3] push: add --current Paolo Bonzini 2009-07-20 7:14 ` Björn Steinbrink 2009-07-20 10:15 ` Tay Ray Chuan @ 2009-07-20 11:17 ` demerphq 2 siblings, 0 replies; 12+ messages in thread From: demerphq @ 2009-07-20 11:17 UTC (permalink / raw) To: Paolo Bonzini; +Cc: git, gitster 2009/7/20 Paolo Bonzini <bonzini@gnu.org>: [snip] > A secondary effect of --head is that, if there is no push.default > specified, I make the default push refspec "HEAD". I think you mean something like: A secondary effect of --current is that if there is no push.default configuration setting specified the default push refspec will be "HEAD". The important point being you talk about --current but then mention --head which I am guessing was a previous name for this option. BTW, I like this option and I can imagine it will be a popular alias. One of the more common questions I get asked from people about git is what to do about all the "error" messages when pushing. I usually explain that unless the error message concerns the current branch which you wanted to push that they can be ignored. I look forward to pointing out this option instead. Thanks. :-) Cheers, yves -- perl -Mre=debug -e "/just|another|perl|hacker/" ^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2009-07-20 22:09 UTC | newest] Thread overview: 12+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2009-07-20 11:58 [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly Paolo Bonzini 2009-07-20 11:58 ` [PATCH 1/3] reintroduce PUSH_DEFAULT_UNSPECIFIED Paolo Bonzini 2009-07-20 11:58 ` [PATCH 2/3] push: add --current Paolo Bonzini 2009-07-20 11:58 ` [PATCH 3/3] push: add remote.*.pushHeadOnly configuration Paolo Bonzini 2009-07-20 20:38 ` [PATCH v3 0/3] add push --current and remote.*.pushHeadOnly Junio C Hamano 2009-07-20 22:09 ` Paolo Bonzini -- strict thread matches above, loose matches on Subject: below -- 2009-07-20 6:36 [PATCH " Paolo Bonzini 2009-07-20 6:36 ` [PATCH 2/3] push: add --current Paolo Bonzini 2009-07-20 7:14 ` Björn Steinbrink 2009-07-20 9:48 ` Paolo Bonzini 2009-07-20 10:15 ` Tay Ray Chuan 2009-07-20 11:50 ` Paolo Bonzini 2009-07-20 11:17 ` demerphq
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).