From f25950f3475e263ed6c8f0797bb058ba6444f85e Mon Sep 17 00:00:00 2001 From: Christopher Tiwald Date: Tue, 20 Mar 2012 00:31:33 -0400 Subject: push: Provide situational hints for non-fast-forward errors Pushing a non-fast-forward update to a remote repository will result in an error, but the hint text doesn't provide the correct resolution in every case. Give better resolution advice in three push scenarios: 1) If you push your current branch and it triggers a non-fast-forward error, you should merge remote changes with 'git pull' before pushing again. 2) If you push to a shared repository others push to, and your local tracking branches are not kept up to date, the 'matching refs' default will generate non-fast-forward errors on outdated branches. If this is your workflow, the 'matching refs' default is not for you. Consider setting the 'push.default' configuration variable to 'current' or 'upstream' to ensure only your current branch is pushed. 3) If you explicitly specify a ref that is not your current branch or push matching branches with ':', you will generate a non-fast-forward error if any pushed branch tip is out of date. You should checkout the offending branch and merge remote changes before pushing again. Teach transport.c to recognize these scenarios and configure push.c to hint for them. If 'git push's default behavior changes or we discover more scenarios, extension is easy. Standardize on the advice API and add three new advice variables, 'pushNonFFCurrent', 'pushNonFFDefault', and 'pushNonFFMatching'. Setting any of these to 'false' will disable their affiliated advice. Setting 'pushNonFastForward' to false will disable all three, thus preserving the config option for users who already set it, but guaranteeing new users won't disable push advice accidentally. Based-on-patch-by: Junio C Hamano Signed-off-by: Christopher Tiwald Signed-off-by: Junio C Hamano --- Documentation/config.txt | 19 +++++++++++++-- advice.c | 6 +++++ advice.h | 3 +++ builtin/push.c | 60 ++++++++++++++++++++++++++++++++++++++++++++---- cache.h | 8 +++++-- environment.c | 2 +- transport.c | 13 +++++++++-- 7 files changed, 99 insertions(+), 12 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index c081657be7..fb386abc51 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -138,8 +138,23 @@ advice.*:: + -- pushNonFastForward:: - Advice shown when linkgit:git-push[1] refuses - non-fast-forward refs. + Set this variable to 'false' if you want to disable + 'pushNonFFCurrent', 'pushNonFFDefault', and + 'pushNonFFMatching' simultaneously. + pushNonFFCurrent:: + Advice shown when linkgit:git-push[1] fails due to a + non-fast-forward update to the current branch. + pushNonFFDefault:: + Advice to set 'push.default' to 'upstream' or 'current' + when you ran linkgit:git-push[1] and pushed 'matching + refs' by default (i.e. you did not provide an explicit + refspec, and no 'push.default' configuration was set) + and it resulted in a non-fast-forward error. + pushNonFFMatching:: + Advice shown when you ran linkgit:git-push[1] and pushed + 'matching refs' explicitly (i.e. you used ':', or + specified a refspec that isn't your current branch) and + it resulted in a non-fast-forward error. statusHints:: Directions on how to stage/unstage/add shown in the output of linkgit:git-status[1] and the template shown diff --git a/advice.c b/advice.c index 01130e54e7..a492eea24f 100644 --- a/advice.c +++ b/advice.c @@ -1,6 +1,9 @@ #include "cache.h" int advice_push_nonfastforward = 1; +int advice_push_non_ff_current = 1; +int advice_push_non_ff_default = 1; +int advice_push_non_ff_matching = 1; int advice_status_hints = 1; int advice_commit_before_merge = 1; int advice_resolve_conflict = 1; @@ -12,6 +15,9 @@ static struct { int *preference; } advice_config[] = { { "pushnonfastforward", &advice_push_nonfastforward }, + { "pushnonffcurrent", &advice_push_non_ff_current }, + { "pushnonffdefault", &advice_push_non_ff_default }, + { "pushnonffmatching", &advice_push_non_ff_matching }, { "statushints", &advice_status_hints }, { "commitbeforemerge", &advice_commit_before_merge }, { "resolveconflict", &advice_resolve_conflict }, diff --git a/advice.h b/advice.h index 7bda45b83e..f3cdbbf29e 100644 --- a/advice.h +++ b/advice.h @@ -4,6 +4,9 @@ #include "git-compat-util.h" extern int advice_push_nonfastforward; +extern int advice_push_non_ff_current; +extern int advice_push_non_ff_default; +extern int advice_push_non_ff_matching; extern int advice_status_hints; extern int advice_commit_before_merge; extern int advice_resolve_conflict; diff --git a/builtin/push.c b/builtin/push.c index d315475f16..8a14e4bcc1 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -24,6 +24,7 @@ static int progress = -1; static const char **refspec; static int refspec_nr; static int refspec_alloc; +static int default_matching_used; static void add_refspec(const char *ref) { @@ -95,6 +96,9 @@ static void setup_default_push_refspecs(struct remote *remote) { switch (push_default) { default: + case PUSH_DEFAULT_UNSPECIFIED: + default_matching_used = 1; + /* fallthru */ case PUSH_DEFAULT_MATCHING: add_refspec(":"); break; @@ -114,6 +118,45 @@ static void setup_default_push_refspecs(struct remote *remote) } } +static const char message_advice_pull_before_push[] = + N_("Updates were rejected because the tip of your current branch is behind\n" + "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n" + "before pushing again.\n" + "See the 'Note about fast-forwards' in 'git push --help' for details."); + +static const char message_advice_use_upstream[] = + N_("Updates were rejected because a pushed branch tip is behind its remote\n" + "counterpart. If you did not intend to push that branch, you may want to\n" + "specify branches to push or set the 'push.default' configuration\n" + "variable to 'current' or 'upstream' to push only the current branch."); + +static const char message_advice_checkout_pull_push[] = + N_("Updates were rejected because a pushed branch tip is behind its remote\n" + "counterpart. Check out this branch and merge the remote changes\n" + "(e.g. 'git pull') before pushing again.\n" + "See the 'Note about fast-forwards' in 'git push --help' for details."); + +static void advise_pull_before_push(void) +{ + if (!advice_push_non_ff_current || !advice_push_nonfastforward) + return; + advise(_(message_advice_pull_before_push)); +} + +static void advise_use_upstream(void) +{ + if (!advice_push_non_ff_default || !advice_push_nonfastforward) + return; + advise(_(message_advice_use_upstream)); +} + +static void advise_checkout_pull_push(void) +{ + if (!advice_push_non_ff_matching || !advice_push_nonfastforward) + return; + advise(_(message_advice_checkout_pull_push)); +} + static int push_with_options(struct transport *transport, int flags) { int err; @@ -135,14 +178,21 @@ static int push_with_options(struct transport *transport, int flags) error(_("failed to push some refs to '%s'"), transport->url); err |= transport_disconnect(transport); - if (!err) return 0; - if (nonfastforward && advice_push_nonfastforward) { - fprintf(stderr, _("To prevent you from losing history, non-fast-forward updates were rejected\n" - "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n" - "'Note about fast-forwards' section of 'git push --help' for details.\n")); + switch (nonfastforward) { + default: + break; + case NON_FF_HEAD: + advise_pull_before_push(); + break; + case NON_FF_OTHER: + if (default_matching_used) + advise_use_upstream(); + else + advise_checkout_pull_push(); + break; } return 1; diff --git a/cache.h b/cache.h index e5e1aa4e15..427b600267 100644 --- a/cache.h +++ b/cache.h @@ -625,7 +625,8 @@ enum push_default_type { PUSH_DEFAULT_NOTHING = 0, PUSH_DEFAULT_MATCHING, PUSH_DEFAULT_UPSTREAM, - PUSH_DEFAULT_CURRENT + PUSH_DEFAULT_CURRENT, + PUSH_DEFAULT_UNSPECIFIED }; extern enum branch_track git_branch_track; @@ -1008,7 +1009,6 @@ struct ref { char *symref; unsigned int force:1, merge:1, - nonfastforward:1, deletion:1; enum { REF_STATUS_NONE = 0, @@ -1019,6 +1019,10 @@ struct ref { REF_STATUS_REMOTE_REJECT, REF_STATUS_EXPECTING_REPORT } status; + enum { + NON_FF_HEAD = 1, + NON_FF_OTHER + } nonfastforward; char *remote_status; struct ref *peer_ref; /* when renaming */ char name[FLEX_ARRAY]; /* more */ diff --git a/environment.c b/environment.c index c93b8f44df..d7e6c65763 100644 --- a/environment.c +++ b/environment.c @@ -52,7 +52,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 diff --git a/transport.c b/transport.c index 181f8f24d1..7864007c9c 100644 --- a/transport.c +++ b/transport.c @@ -721,6 +721,10 @@ void transport_print_push_status(const char *dest, struct ref *refs, { struct ref *ref; int n = 0; + unsigned char head_sha1[20]; + char *head; + + head = resolve_refdup("HEAD", head_sha1, 1, NULL); if (verbose) { for (ref = refs; ref; ref = ref->next) @@ -738,8 +742,13 @@ void transport_print_push_status(const char *dest, struct ref *refs, ref->status != REF_STATUS_UPTODATE && ref->status != REF_STATUS_OK) n += print_one_push_status(ref, dest, n, porcelain); - if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD) - *nonfastforward = 1; + if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD && + *nonfastforward != NON_FF_HEAD) { + if (!strcmp(head, ref->name)) + *nonfastforward = NON_FF_HEAD; + else + *nonfastforward = NON_FF_OTHER; + } } } -- cgit v1.2.3 From e339aa92ae2db194a3d5738cb3aee6d8b3bf7b10 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 26 Mar 2012 15:51:50 -0400 Subject: clean up struct ref's nonfastforward field Each ref structure contains a "nonfastforward" field which is set during push to show whether the ref rewound history. Originally this was a single bit, but it was changed in f25950f (push: Provide situational hints for non-fast-forward errors) to an enum differentiating a non-ff of the current branch versus another branch. However, we never actually set the member according to the enum values, nor did we ever read it expecting anything but a boolean value. But we did use the side effect of declaring the enum constants to store those values in a totally different integer variable. The code as-is isn't buggy, but the enum declaration inside "struct ref" is somewhat misleading. Let's convert nonfastforward back into a single bit, and then define the NON_FF_* constants closer to where they would be used (they are returned via the "int *nonfastforward" parameter to transport_push, so we can define them there). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- cache.h | 5 +---- transport.h | 2 ++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cache.h b/cache.h index 427b600267..35f30752c6 100644 --- a/cache.h +++ b/cache.h @@ -1009,6 +1009,7 @@ struct ref { char *symref; unsigned int force:1, merge:1, + nonfastforward:1, deletion:1; enum { REF_STATUS_NONE = 0, @@ -1019,10 +1020,6 @@ struct ref { REF_STATUS_REMOTE_REJECT, REF_STATUS_EXPECTING_REPORT } status; - enum { - NON_FF_HEAD = 1, - NON_FF_OTHER - } nonfastforward; char *remote_status; struct ref *peer_ref; /* when renaming */ char name[FLEX_ARRAY]; /* more */ diff --git a/transport.h b/transport.h index ce99ef8b7e..1631a35ea6 100644 --- a/transport.h +++ b/transport.h @@ -138,6 +138,8 @@ int transport_set_option(struct transport *transport, const char *name, void transport_set_verbosity(struct transport *transport, int verbosity, int force_progress); +#define NON_FF_HEAD 1 +#define NON_FF_OTHER 2 int transport_push(struct transport *connection, int refspec_nr, const char **refspec, int flags, int * nonfastforward); -- cgit v1.2.3 From 0aff719f489771c5e52259394d011c51317b118f Mon Sep 17 00:00:00 2001 From: Christopher Tiwald Date: Thu, 12 Apr 2012 13:56:28 -0400 Subject: Fix httpd tests that broke when non-ff push advice changed Signed-off-by: Christopher Tiwald Signed-off-by: Junio C Hamano --- t/lib-httpd.sh | 2 +- t/t5541-http-push.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index f7dc0781d5..094d490893 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -160,6 +160,6 @@ test_http_push_nonff() { ' test_expect_success 'non-fast-forward push shows help message' ' - test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" output + test_i18ngrep "Updates were rejected because" output ' } diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index cc6f081711..57c3e48dda 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -167,7 +167,7 @@ test_expect_success 'push fails for non-fast-forward refs unmatched by remote he ' test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper: our output' ' - test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" \ + test_i18ngrep "Updates were rejected because" \ output ' -- cgit v1.2.3