Merge branch 'dl/rebase-i-keep-base'
authorJunio C Hamano <gitster@pobox.com>
Mon, 30 Sep 2019 04:19:31 +0000 (13:19 +0900)
committerJunio C Hamano <gitster@pobox.com>
Mon, 30 Sep 2019 04:19:31 +0000 (13:19 +0900)
"git rebase --keep-base <upstream>" tries to find the original base
of the topic being rebased and rebase on top of that same base,
which is useful when running the "git rebase -i" (and its limited
variant "git rebase -x").

The command also has learned to fast-forward in more cases where it
can instead of replaying to recreate identical commits.

* dl/rebase-i-keep-base:
rebase: teach rebase --keep-base
rebase tests: test linear branch topology
rebase: fast-forward --fork-point in more cases
rebase: fast-forward --onto in more cases
rebase: refactor can_fast_forward into goto tower
t3432: test for --no-ff's interaction with fast-forward
t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests
t3432: test rebase fast-forward behavior
t3431: add rebase --fork-point tests

1  2 
Documentation/git-rebase.txt
builtin/rebase.c
contrib/completion/git-completion.bash
t/t3400-rebase.sh
t/t3404-rebase-interactive.sh
index 7ecf766077ff68eb99ef780d628bdd20fff0558a,3146c1592deaa9790e143694bc9b5a07bb47d254..639a4179d18e4d154f30351aaa4f3b3c79c48c1e
@@@ -8,8 -8,8 +8,8 @@@ git-rebase - Reapply commits on top of 
  SYNOPSIS
  --------
  [verse]
- 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
-       [<upstream> [<branch>]]
+ 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]
+       [--onto <newbase> | --keep-base] [<upstream> [<branch>]]
  'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
        --root [<branch>]
  'git rebase' (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch)
@@@ -217,6 -217,24 +217,24 @@@ As a special case, you may use "A\...B
  merge base of A and B if there is exactly one merge base. You can
  leave out at most one of A and B, in which case it defaults to HEAD.
  
+ --keep-base::
+       Set the starting point at which to create the new commits to the
+       merge base of <upstream> <branch>. Running
+       'git rebase --keep-base <upstream> <branch>' is equivalent to
+       running 'git rebase --onto <upstream>... <upstream>'.
+ +
+ This option is useful in the case where one is developing a feature on
+ top of an upstream branch. While the feature is being worked on, the
+ upstream branch may advance and it may not be the best idea to keep
+ rebasing on top of the upstream but to keep the base commit as-is.
+ +
+ Although both this option and --fork-point find the merge base between
+ <upstream> and <branch>, this option uses the merge base as the _starting
+ point_ on which new commits will be created, whereas --fork-point uses
+ the merge base to determine the _set of commits_ which will be rebased.
+ +
+ See also INCOMPATIBLE OPTIONS below.
  <upstream>::
        Upstream branch to compare against.  May be any valid commit,
        not just an existing branch name. Defaults to the configured
@@@ -369,6 -387,10 +387,10 @@@ ends up being empty, the <upstream> wil
  +
  If either <upstream> or --root is given on the command line, then the
  default is `--no-fork-point`, otherwise the default is `--fork-point`.
+ +
+ If your branch was based on <upstream> but <upstream> was rewound and
+ your branch contains commits which were dropped, this option can be used
+ with `--keep-base` in order to drop those commits from your branch.
  
  --ignore-whitespace::
  --whitespace=<option>::
@@@ -543,6 -565,10 +565,8 @@@ In addition, the following pairs of opt
   * --preserve-merges and --interactive
   * --preserve-merges and --signoff
   * --preserve-merges and --rebase-merges
 - * --rebase-merges and --strategy
 - * --rebase-merges and --strategy-option
+  * --keep-base and --onto
+  * --keep-base and --root
  
  BEHAVIORAL DIFFERENCES
  -----------------------
@@@ -830,8 -856,7 +854,8 @@@ Hard case: The changes are not the same
        This happens if the 'subsystem' rebase had conflicts, or used
        `--interactive` to omit, edit, squash, or fixup commits; or
        if the upstream used one of `commit --amend`, `reset`, or
 -      `filter-branch`.
 +      a full history rewriting command like
 +      https://github.com/newren/git-filter-repo[`filter-repo`].
  
  
  The easy case
@@@ -869,7 -894,7 +893,7 @@@ NOTE: While an "easy case recovery" som
        --interactive` will be **resurrected**!
  
  The idea is to manually tell 'git rebase' "where the old 'subsystem'
- ended and your 'topic' began", that is, what the old merge-base
+ ended and your 'topic' began", that is, what the old merge base
  between them was.  You will have to find a way to name the last commit
  of the old 'subsystem', for example:
  
diff --combined builtin/rebase.c
index e8319d594639dcd85bb47b5f1ece1a4fdd5c1a4c,27d5c4e19d1c16491060aff4a05500917fb28be1..f730b15a787e8fd119d0b4a1a9e87fd0b04d571e
@@@ -29,8 -29,8 +29,8 @@@
  #include "rebase-interactive.h"
  
  static char const * const builtin_rebase_usage[] = {
-       N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
-               "[<upstream>] [<branch>]"),
+       N_("git rebase [-i] [options] [--exec <cmd>] "
+               "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"),
        N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
                "--root [<branch>]"),
        N_("git rebase --continue | --abort | --skip | --edit-todo"),
@@@ -62,7 -62,7 +62,7 @@@ struct rebase_options 
        const char *onto_name;
        const char *revisions;
        const char *switch_to;
 -      int root;
 +      int root, root_with_onto;
        struct object_id *squash_onto;
        struct commit *restrict_revision;
        int dont_finish_rebase;
@@@ -374,7 -374,6 +374,7 @@@ static int run_rebase_interactive(struc
        flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
        flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
        flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
 +      flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
        flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
  
        switch (command) {
@@@ -1261,24 -1260,44 +1261,44 @@@ static int is_linear_history(struct com
        return 1;
  }
  
- static int can_fast_forward(struct commit *onto, struct object_id *head_oid,
-                           struct object_id *merge_base)
+ static int can_fast_forward(struct commit *onto, struct commit *upstream,
+                           struct commit *restrict_revision,
+                           struct object_id *head_oid, struct object_id *merge_base)
  {
        struct commit *head = lookup_commit(the_repository, head_oid);
-       struct commit_list *merge_bases;
-       int res;
+       struct commit_list *merge_bases = NULL;
+       int res = 0;
  
        if (!head)
-               return 0;
+               goto done;
  
        merge_bases = get_merge_bases(onto, head);
-       if (merge_bases && !merge_bases->next) {
-               oidcpy(merge_base, &merge_bases->item->object.oid);
-               res = oideq(merge_base, &onto->object.oid);
-       } else {
+       if (!merge_bases || merge_bases->next) {
                oidcpy(merge_base, &null_oid);
-               res = 0;
+               goto done;
        }
+       oidcpy(merge_base, &merge_bases->item->object.oid);
+       if (!oideq(merge_base, &onto->object.oid))
+               goto done;
+       if (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base))
+               goto done;
+       if (!upstream)
+               goto done;
+       free_commit_list(merge_bases);
+       merge_bases = get_merge_bases(upstream, head);
+       if (!merge_bases || merge_bases->next)
+               goto done;
+       if (!oideq(&onto->object.oid, &merge_bases->item->object.oid))
+               goto done;
+       res = 1;
+ done:
        free_commit_list(merge_bases);
        return res && is_linear_history(onto, head);
  }
@@@ -1377,6 -1396,7 +1397,7 @@@ int cmd_rebase(int argc, const char **a
        struct rebase_options options = REBASE_OPTIONS_INIT;
        const char *branch_name;
        int ret, flags, total_argc, in_progress = 0;
+       int keep_base = 0;
        int ok_to_skip_pre_rebase = 0;
        struct strbuf msg = STRBUF_INIT;
        struct strbuf revisions = STRBUF_INIT;
                OPT_STRING(0, "onto", &options.onto_name,
                           N_("revision"),
                           N_("rebase onto given branch instead of upstream")),
+               OPT_BOOL(0, "keep-base", &keep_base,
+                        N_("use the merge-base of upstream and branch as the current base")),
                OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase,
                         N_("allow pre-rebase hook to run")),
                OPT_NEGBIT('q', "quiet", &options.flags,
                warning(_("git rebase --preserve-merges is deprecated. "
                          "Use --rebase-merges instead."));
  
+       if (keep_base) {
+               if (options.onto_name)
+                       die(_("cannot combine '--keep-base' with '--onto'"));
+               if (options.root)
+                       die(_("cannot combine '--keep-base' with '--root'"));
+       }
        if (action != ACTION_NONE && !in_progress)
                die(_("No rebase in progress?"));
        setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
                              "'--reschedule-failed-exec'"));
        }
  
 -      if (options.rebase_merges) {
 -              if (strategy_options.nr)
 -                      die(_("cannot combine '--rebase-merges' with "
 -                            "'--strategy-option'"));
 -              if (options.strategy)
 -                      die(_("cannot combine '--rebase-merges' with "
 -                            "'--strategy'"));
 -      }
 -
        if (!options.root) {
                if (argc < 1) {
                        struct branch *branch;
                        options.squash_onto = &squash_onto;
                        options.onto_name = squash_onto_name =
                                xstrdup(oid_to_hex(&squash_onto));
 -              }
 +              } else
 +                      options.root_with_onto = 1;
 +
                options.upstream_name = NULL;
                options.upstream = NULL;
                if (argc > 1)
        }
  
        /* Make sure the branch to rebase onto is valid. */
-       if (!options.onto_name)
+       if (keep_base) {
+               strbuf_reset(&buf);
+               strbuf_addstr(&buf, options.upstream_name);
+               strbuf_addstr(&buf, "...");
+               options.onto_name = xstrdup(buf.buf);
+       } else if (!options.onto_name)
                options.onto_name = options.upstream_name;
        if (strstr(options.onto_name, "...")) {
-               if (get_oid_mb(options.onto_name, &merge_base) < 0)
-                       die(_("'%s': need exactly one merge base"),
-                           options.onto_name);
+               if (get_oid_mb(options.onto_name, &merge_base) < 0) {
+                       if (keep_base)
+                               die(_("'%s': need exactly one merge base with branch"),
+                                   options.upstream_name);
+                       else
+                               die(_("'%s': need exactly one merge base"),
+                                   options.onto_name);
+               }
                options.onto = lookup_commit_or_die(&merge_base,
                                                    options.onto_name);
        } else {
  
        /*
         * Check if we are already based on onto with linear history,
-        * but this should be done only when upstream and onto are the same
-        * and if this is not an interactive rebase.
+        * in which case we could fast-forward without replacing the commits
+        * with new commits recreated by replaying their changes. This
+        * optimization must not be done if this is an interactive rebase.
         */
-       if (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&
-           !is_interactive(&options) && !options.restrict_revision &&
-           options.upstream &&
-           !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {
+       if (can_fast_forward(options.onto, options.upstream, options.restrict_revision,
+                   &options.orig_head, &merge_base) &&
+           !is_interactive(&options)) {
                int flag;
  
                if (!(options.flags & REBASE_FORCE)) {
index 6f7b4f96074393b870ad57c8b867bdfdd09d1fc0,71e7159694376197401f163535d188c8088c8584..59cd3e8e90a382e647c3564bf8943e7de76e8a58
@@@ -340,7 -340,7 +340,7 @@@ __gitcomp (
                        c="$c${4-}"
                        if [[ $c == "$cur_"* ]]; then
                                case $c in
 -                              --*=*|*.) ;;
 +                              --*=|*.) ;;
                                *) c="$c " ;;
                                esac
                                COMPREPLY[i++]="${2-}$c"
                        c="$c${4-}"
                        if [[ $c == "$cur_"* ]]; then
                                case $c in
 -                              --*=*|*.) ;;
 +                              *=|*.) ;;
                                *) c="$c " ;;
                                esac
                                COMPREPLY[i++]="${2-}$c"
@@@ -524,7 -524,7 +524,7 @@@ __git_index_files (
                        # Even when a directory name itself does not contain
                        # any special characters, it will still be quoted if
                        # any of its (stripped) trailing path components do.
 -                      # Because of this we may have seen the same direcory
 +                      # Because of this we may have seen the same directory
                        # both quoted and unquoted.
                        if (p in paths)
                                # We have seen the same directory unquoted,
@@@ -1361,9 -1361,7 +1361,9 @@@ _git_checkout (
        esac
  }
  
 -__git_cherry_pick_inprogress_options="--continue --quit --abort"
 +__git_sequencer_inprogress_options="--continue --quit --abort --skip"
 +
 +__git_cherry_pick_inprogress_options=$__git_sequencer_inprogress_options
  
  _git_cherry_pick ()
  {
@@@ -1401,18 -1399,7 +1401,18 @@@ _git_clean (
  
  _git_clone ()
  {
 +      case "$prev" in
 +      -c|--config)
 +              __git_complete_config_variable_name_and_value
 +              return
 +              ;;
 +      esac
        case "$cur" in
 +      --config=*)
 +              __git_complete_config_variable_name_and_value \
 +                      --cur="${cur##--config=}"
 +              return
 +              ;;
        --*)
                __gitcomp_builtin clone
                return
@@@ -2043,7 -2030,7 +2043,7 @@@ _git_rebase (
                        --autosquash --no-autosquash
                        --fork-point --no-fork-point
                        --autostash --no-autostash
-                       --verify --no-verify
+                       --verify --no-verify --keep-base
                        --keep-empty --root --force-rebase --no-ff
                        --rerere-autoupdate
                        --exec
@@@ -2238,282 -2225,181 +2238,282 @@@ __git_config_vars
  __git_compute_config_vars ()
  {
        test -n "$__git_config_vars" ||
 -      __git_config_vars="$(git help --config-for-completion | sort | uniq)"
 +      __git_config_vars="$(git help --config-for-completion | sort -u)"
  }
  
 -_git_config ()
 +# Completes possible values of various configuration variables.
 +#
 +# Usage: __git_complete_config_variable_value [<option>]...
 +# --varname=<word>: The name of the configuration variable whose value is
 +#                   to be completed.  Defaults to the previous word on the
 +#                   command line.
 +# --cur=<word>: The current value to be completed.  Defaults to the current
 +#               word to be completed.
 +__git_complete_config_variable_value ()
  {
 -      local varname
 +      local varname="$prev" cur_="$cur"
 +
 +      while test $# != 0; do
 +              case "$1" in
 +              --varname=*)    varname="${1##--varname=}" ;;
 +              --cur=*)        cur_="${1##--cur=}" ;;
 +              *)              return 1 ;;
 +              esac
 +              shift
 +      done
  
        if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then
 -              varname="${prev,,}"
 +              varname="${varname,,}"
        else
 -              varname="$(echo "$prev" |tr A-Z a-z)"
 +              varname="$(echo "$varname" |tr A-Z a-z)"
        fi
  
        case "$varname" in
        branch.*.remote|branch.*.pushremote)
 -              __gitcomp_nl "$(__git_remotes)"
 +              __gitcomp_nl "$(__git_remotes)" "" "$cur_"
                return
                ;;
        branch.*.merge)
 -              __git_complete_refs
 +              __git_complete_refs --cur="$cur_"
                return
                ;;
        branch.*.rebase)
 -              __gitcomp "false true merges preserve interactive"
 +              __gitcomp "false true merges preserve interactive" "" "$cur_"
                return
                ;;
        remote.pushdefault)
 -              __gitcomp_nl "$(__git_remotes)"
 +              __gitcomp_nl "$(__git_remotes)" "" "$cur_"
                return
                ;;
        remote.*.fetch)
 -              local remote="${prev#remote.}"
 +              local remote="${varname#remote.}"
                remote="${remote%.fetch}"
 -              if [ -z "$cur" ]; then
 +              if [ -z "$cur_" ]; then
                        __gitcomp_nl "refs/heads/" "" "" ""
                        return
                fi
 -              __gitcomp_nl "$(__git_refs_remotes "$remote")"
 +              __gitcomp_nl "$(__git_refs_remotes "$remote")" "" "$cur_"
                return
                ;;
        remote.*.push)
 -              local remote="${prev#remote.}"
 +              local remote="${varname#remote.}"
                remote="${remote%.push}"
                __gitcomp_nl "$(__git for-each-ref \
 -                      --format='%(refname):%(refname)' refs/heads)"
 +                      --format='%(refname):%(refname)' refs/heads)" "" "$cur_"
                return
                ;;
        pull.twohead|pull.octopus)
                __git_compute_merge_strategies
 -              __gitcomp "$__git_merge_strategies"
 -              return
 -              ;;
 -      color.branch|color.diff|color.interactive|\
 -      color.showbranch|color.status|color.ui)
 -              __gitcomp "always never auto"
 +              __gitcomp "$__git_merge_strategies" "" "$cur_"
                return
                ;;
        color.pager)
 -              __gitcomp "false true"
 +              __gitcomp "false true" "" "$cur_"
                return
                ;;
        color.*.*)
                __gitcomp "
                        normal black red green yellow blue magenta cyan white
                        bold dim ul blink reverse
 -                      "
 +                      " "" "$cur_"
 +              return
 +              ;;
 +      color.*)
 +              __gitcomp "false true always never auto" "" "$cur_"
                return
                ;;
        diff.submodule)
 -              __gitcomp "$__git_diff_submodule_formats"
 +              __gitcomp "$__git_diff_submodule_formats" "" "$cur_"
                return
                ;;
        help.format)
 -              __gitcomp "man info web html"
 +              __gitcomp "man info web html" "" "$cur_"
                return
                ;;
        log.date)
 -              __gitcomp "$__git_log_date_formats"
 +              __gitcomp "$__git_log_date_formats" "" "$cur_"
                return
                ;;
        sendemail.aliasfiletype)
 -              __gitcomp "mutt mailrc pine elm gnus"
 +              __gitcomp "mutt mailrc pine elm gnus" "" "$cur_"
                return
                ;;
        sendemail.confirm)
 -              __gitcomp "$__git_send_email_confirm_options"
 +              __gitcomp "$__git_send_email_confirm_options" "" "$cur_"
                return
                ;;
        sendemail.suppresscc)
 -              __gitcomp "$__git_send_email_suppresscc_options"
 +              __gitcomp "$__git_send_email_suppresscc_options" "" "$cur_"
                return
                ;;
        sendemail.transferencoding)
 -              __gitcomp "7bit 8bit quoted-printable base64"
 -              return
 -              ;;
 -      --get|--get-all|--unset|--unset-all)
 -              __gitcomp_nl "$(__git_config_get_set_variables)"
 +              __gitcomp "7bit 8bit quoted-printable base64" "" "$cur_"
                return
                ;;
        *.*)
                return
                ;;
        esac
 -      case "$cur" in
 -      --*)
 -              __gitcomp_builtin config
 -              return
 -              ;;
 +}
 +
 +# Completes configuration sections, subsections, variable names.
 +#
 +# Usage: __git_complete_config_variable_name [<option>]...
 +# --cur=<word>: The current configuration section/variable name to be
 +#               completed.  Defaults to the current word to be completed.
 +# --sfx=<suffix>: A suffix to be appended to each fully completed
 +#                 configuration variable name (but not to sections or
 +#                 subsections) instead of the default space.
 +__git_complete_config_variable_name ()
 +{
 +      local cur_="$cur" sfx
 +
 +      while test $# != 0; do
 +              case "$1" in
 +              --cur=*)        cur_="${1##--cur=}" ;;
 +              --sfx=*)        sfx="${1##--sfx=}" ;;
 +              *)              return 1 ;;
 +              esac
 +              shift
 +      done
 +
 +      case "$cur_" in
        branch.*.*)
 -              local pfx="${cur%.*}." cur_="${cur##*.}"
 -              __gitcomp "remote pushRemote merge mergeOptions rebase" "$pfx" "$cur_"
 +              local pfx="${cur_%.*}."
 +              cur_="${cur_##*.}"
 +              __gitcomp "remote pushRemote merge mergeOptions rebase" "$pfx" "$cur_" "$sfx"
                return
                ;;
        branch.*)
 -              local pfx="${cur%.*}." cur_="${cur#*.}"
 +              local pfx="${cur%.*}."
 +              cur_="${cur#*.}"
                __gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")"
 -              __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_"
 +              __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "$sfx"
                return
                ;;
        guitool.*.*)
 -              local pfx="${cur%.*}." cur_="${cur##*.}"
 +              local pfx="${cur_%.*}."
 +              cur_="${cur_##*.}"
                __gitcomp "
                        argPrompt cmd confirm needsFile noConsole noRescan
                        prompt revPrompt revUnmerged title
 -                      " "$pfx" "$cur_"
 +                      " "$pfx" "$cur_" "$sfx"
                return
                ;;
        difftool.*.*)
 -              local pfx="${cur%.*}." cur_="${cur##*.}"
 -              __gitcomp "cmd path" "$pfx" "$cur_"
 +              local pfx="${cur_%.*}."
 +              cur_="${cur_##*.}"
 +              __gitcomp "cmd path" "$pfx" "$cur_" "$sfx"
                return
                ;;
        man.*.*)
 -              local pfx="${cur%.*}." cur_="${cur##*.}"
 -              __gitcomp "cmd path" "$pfx" "$cur_"
 +              local pfx="${cur_%.*}."
 +              cur_="${cur_##*.}"
 +              __gitcomp "cmd path" "$pfx" "$cur_" "$sfx"
                return
                ;;
        mergetool.*.*)
 -              local pfx="${cur%.*}." cur_="${cur##*.}"
 -              __gitcomp "cmd path trustExitCode" "$pfx" "$cur_"
 +              local pfx="${cur_%.*}."
 +              cur_="${cur_##*.}"
 +              __gitcomp "cmd path trustExitCode" "$pfx" "$cur_" "$sfx"
                return
                ;;
        pager.*)
 -              local pfx="${cur%.*}." cur_="${cur#*.}"
 +              local pfx="${cur_%.*}."
 +              cur_="${cur_#*.}"
                __git_compute_all_commands
 -              __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_"
 +              __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "$sfx"
                return
                ;;
        remote.*.*)
 -              local pfx="${cur%.*}." cur_="${cur##*.}"
 +              local pfx="${cur_%.*}."
 +              cur_="${cur_##*.}"
                __gitcomp "
                        url proxy fetch push mirror skipDefaultUpdate
                        receivepack uploadpack tagOpt pushurl
 -                      " "$pfx" "$cur_"
 +                      " "$pfx" "$cur_" "$sfx"
                return
                ;;
        remote.*)
 -              local pfx="${cur%.*}." cur_="${cur#*.}"
 +              local pfx="${cur_%.*}."
 +              cur_="${cur_#*.}"
                __gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
 -              __gitcomp_nl_append "pushDefault" "$pfx" "$cur_"
 +              __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "$sfx"
                return
                ;;
        url.*.*)
 -              local pfx="${cur%.*}." cur_="${cur##*.}"
 -              __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_"
 +              local pfx="${cur_%.*}."
 +              cur_="${cur_##*.}"
 +              __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_" "$sfx"
                return
                ;;
        *.*)
                __git_compute_config_vars
 -              __gitcomp "$__git_config_vars"
 +              __gitcomp "$__git_config_vars" "" "$cur_" "$sfx"
                ;;
        *)
                __git_compute_config_vars
 -              __gitcomp "$(echo "$__git_config_vars" | sed 's/\.[^ ]*/./g')"
 +              __gitcomp "$(echo "$__git_config_vars" |
 +                              awk -F . '{
 +                                      sections[$1] = 1
 +                              }
 +                              END {
 +                                      for (s in sections)
 +                                              print s "."
 +                              }
 +                              ')" "" "$cur_"
 +              ;;
 +      esac
 +}
 +
 +# Completes '='-separated configuration sections/variable names and values
 +# for 'git -c section.name=value'.
 +#
 +# Usage: __git_complete_config_variable_name_and_value [<option>]...
 +# --cur=<word>: The current configuration section/variable name/value to be
 +#               completed. Defaults to the current word to be completed.
 +__git_complete_config_variable_name_and_value ()
 +{
 +      local cur_="$cur"
 +
 +      while test $# != 0; do
 +              case "$1" in
 +              --cur=*)        cur_="${1##--cur=}" ;;
 +              *)              return 1 ;;
 +              esac
 +              shift
 +      done
 +
 +      case "$cur_" in
 +      *=*)
 +              __git_complete_config_variable_value \
 +                      --varname="${cur_%%=*}" --cur="${cur_#*=}"
 +              ;;
 +      *)
 +              __git_complete_config_variable_name --cur="$cur_" --sfx='='
 +              ;;
 +      esac
 +}
 +
 +_git_config ()
 +{
 +      case "$prev" in
 +      --get|--get-all|--unset|--unset-all)
 +              __gitcomp_nl "$(__git_config_get_set_variables)"
 +              return
 +              ;;
 +      *.*)
 +              __git_complete_config_variable_value
 +              return
 +              ;;
 +      esac
 +      case "$cur" in
 +      --*)
 +              __gitcomp_builtin config
 +              ;;
 +      *)
 +              __git_complete_config_variable_name
 +              ;;
        esac
  }
  
@@@ -2626,7 -2512,7 +2626,7 @@@ _git_restore (
        esac
  }
  
 -__git_revert_inprogress_options="--continue --quit --abort"
 +__git_revert_inprogress_options=$__git_sequencer_inprogress_options
  
  _git_revert ()
  {
@@@ -3070,11 -2956,7 +3070,11 @@@ __git_main (
                        # Bash filename completion
                        return
                        ;;
 -              -c|--namespace)
 +              -c)
 +                      __git_complete_config_variable_name_and_value
 +                      return
 +                      ;;
 +              --namespace)
                        # we don't support completing these options' arguments
                        return
                        ;;
diff --combined t/t3400-rebase.sh
index 23469cc78937eeec8aabc26c158680c2ca60dd7d,d7c724bea3b28feaa14ec8b48ba9e1ffc0e91b18..ab18ac5f28ac5e2828689d1ca4ca93b0990054c9
@@@ -295,48 -295,12 +295,48 @@@ test_expect_success 'rebase --am and --
                echo two >>init.t &&
                git commit -a -m two &&
                git tag two &&
-               test_must_fail git rebase --onto init HEAD^ &&
+               test_must_fail git rebase -f --onto init HEAD^ &&
                GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&
                grep "show.*$(git rev-parse two)" stderr
        )
  '
  
 +test_expect_success 'rebase --am and .gitattributes' '
 +      test_create_repo attributes &&
 +      (
 +              cd attributes &&
 +              test_commit init &&
 +              git config filter.test.clean "sed -e '\''s/smudged/clean/g'\''" &&
 +              git config filter.test.smudge "sed -e '\''s/clean/smudged/g'\''" &&
 +
 +              test_commit second &&
 +              git checkout -b test HEAD^ &&
 +
 +              echo "*.txt filter=test" >.gitattributes &&
 +              git add .gitattributes &&
 +              test_commit third &&
 +
 +              echo "This text is smudged." >a.txt &&
 +              git add a.txt &&
 +              test_commit fourth &&
 +
 +              git checkout -b removal HEAD^ &&
 +              git rm .gitattributes &&
 +              git add -u &&
 +              test_commit fifth &&
 +              git cherry-pick test &&
 +
 +              git checkout test &&
 +              git rebase master &&
 +              grep "smudged" a.txt &&
 +
 +              git checkout removal &&
 +              git reset --hard &&
 +              git rebase master &&
 +              grep "clean" a.txt
 +      )
 +'
 +
  test_expect_success 'rebase--merge.sh and --show-current-patch' '
        test_create_repo conflict-merge &&
        (
index 04affcc38afc0341a5c77db351d48162cf737b21,3cc9052f10ab83086b1ae96b53deb7f100796a44..29a35840ed03e948fa342a4bb82f093d7fbd8588
@@@ -29,6 -29,9 +29,6 @@@ Initial setup
  
  . "$TEST_DIRECTORY"/lib-rebase.sh
  
 -# WARNING: Modifications to the initial repository can change the SHA ID used
 -# in the expect2 file for the 'stop on conflicting pick' test.
 -
  test_expect_success 'setup' '
        test_commit A file1 &&
        test_commit B file1 &&
@@@ -152,6 -155,8 +152,6 @@@ test_expect_success 'rebase -x with emp
        test_i18ncmp expected actual
  '
  
 -LF='
 -'
  test_expect_success 'rebase -x with newline in command fails' '
        test_when_finished "git rebase --abort ||:" &&
        test_must_fail env git rebase -x "a${LF}b" @ 2>actual &&
@@@ -228,28 -233,25 +228,28 @@@ test_expect_success 'exchange two commi
        set_fake_editor &&
        FAKE_LINES="2 1" git rebase -i HEAD~2 &&
        test H = $(git cat-file commit HEAD^ | sed -ne \$p) &&
 -      test G = $(git cat-file commit HEAD | sed -ne \$p)
 +      test G = $(git cat-file commit HEAD | sed -ne \$p) &&
 +      blob1=$(git rev-parse --short HEAD^:file1) &&
 +      blob2=$(git rev-parse --short HEAD:file1) &&
 +      commit=$(git rev-parse --short HEAD)
  '
  
  test_expect_success 'stop on conflicting pick' '
 -      cat >expect <<-\EOF &&
 +      cat >expect <<-EOF &&
        diff --git a/file1 b/file1
 -      index f70f10e..fd79235 100644
 +      index $blob1..$blob2 100644
        --- a/file1
        +++ b/file1
        @@ -1 +1 @@
        -A
        +G
        EOF
 -      cat >expect2 <<-\EOF &&
 +      cat >expect2 <<-EOF &&
        <<<<<<< HEAD
        D
        =======
        G
 -      >>>>>>> 5d18e54... G
 +      >>>>>>> $commit... G
        EOF
        git tag new-branch1 &&
        set_fake_editor &&
@@@ -1001,7 -1003,7 +1001,7 @@@ test_expect_success 'rebase -i --root t
        git checkout B &&
        set_fake_editor &&
        test_must_fail env FAKE_LINES="2" git rebase -i --root &&
 -      git cat-file commit HEAD | grep "^tree 4b825dc642cb" &&
 +      git cat-file commit HEAD | grep "^tree $EMPTY_TREE" &&
        git rebase --abort
  '
  
@@@ -1056,7 -1058,7 +1056,7 @@@ test_expect_success C_LOCALE_OUTPUT 're
        git reset --hard &&
        git checkout conflict-branch &&
        set_fake_editor &&
-       test_must_fail git rebase --onto HEAD~2 HEAD~ &&
+       test_must_fail git rebase -f --onto HEAD~2 HEAD~ &&
        test_must_fail git rebase --edit-todo &&
        git rebase --abort
  '
@@@ -1159,7 -1161,7 +1159,7 @@@ test_expect_success 'rebase -i error o
        test_expect_code 1 grep  "      emp" error
  '
  
 -test_expect_success 'short SHA-1 setup' '
 +test_expect_success SHA1 'short SHA-1 setup' '
        test_when_finished "git checkout master" &&
        git checkout --orphan collide &&
        git rm -rf . &&
        )
  '
  
 -test_expect_success 'short SHA-1 collide' '
 +test_expect_success SHA1 'short SHA-1 collide' '
        test_when_finished "reset_rebase && git checkout master" &&
        git checkout collide &&
        (
@@@ -1417,6 -1419,7 +1417,6 @@@ test_expect_success 'editor saves as CR
        )
  '
  
 -SQ="'"
  test_expect_success 'rebase -i --gpg-sign=<key-id>' '
        test_when_finished "test_might_fail git rebase --abort" &&
        set_fake_editor &&