Merge branch 'jc/merge-refuse-new-root'
authorJunio C Hamano <gitster@pobox.com>
Fri, 8 Apr 2016 21:29:11 +0000 (14:29 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 8 Apr 2016 21:29:11 +0000 (14:29 -0700)
"git merge" used to allow merging two branches that have no common
base by default, which led to a brand new history of an existing
project created and then get pulled by an unsuspecting maintainer,
which allowed an unnecessary parallel history merged into the
existing project. The command has been taught not to allow this by
default, with an escape hatch "--allow-unrelated-histories" option
to be used in a rare event that merges histories of two projects
that started their lives independently.

* jc/merge-refuse-new-root:
merge: refuse to create too cool a merge by default

1  2 
builtin/merge.c
t/t5500-fetch-pack.sh
t/t9400-git-cvsserver-server.sh
diff --combined builtin/merge.c
index 30b7bd54fab38beed4ef1b48a4aacda0fb018987,e3db41b77a4ecafb2e2d02562b1d460ac68bd551..41467e427770d02f40df5e8d5ca0a27ab483c4af
@@@ -64,6 -64,7 +64,7 @@@ static int option_renormalize
  static int verbosity;
  static int allow_rerere_auto;
  static int abort_current_merge;
+ static int allow_unrelated_histories;
  static int show_progress = -1;
  static int default_to_upstream = 1;
  static const char *sign_commit;
@@@ -221,6 -222,8 +222,8 @@@ static struct option builtin_merge_opti
        OPT__VERBOSITY(&verbosity),
        OPT_BOOL(0, "abort", &abort_current_merge,
                N_("abort the current in-progress merge")),
+       OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories,
+                N_("allow merging unrelated histories")),
        OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1),
        { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
          N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
@@@ -1187,7 -1190,6 +1190,7 @@@ int cmd_merge(int argc, const char **ar
        else
                head_commit = lookup_commit_or_die(head_sha1, "HEAD");
  
 +      init_diff_ui_defaults();
        git_config(git_merge_config, NULL);
  
        if (branch_mergeoptions)
                        builtin_merge_options);
  
        if (!head_commit) {
 -              struct commit *remote_head;
                /*
                 * If the merged head is a valid one there is no reason
                 * to forbid "git merge" into a branch yet to be born.
                 * We do the same for "git pull".
                 */
 +              unsigned char *remote_head_sha1;
                if (squash)
                        die(_("Squash commit into empty head not supported yet"));
                if (fast_forward == FF_NO)
                            "an empty head"));
                remoteheads = collect_parents(head_commit, &head_subsumed,
                                              argc, argv, NULL);
 -              remote_head = remoteheads->item;
 -              if (!remote_head)
 +              if (!remoteheads)
                        die(_("%s - not something we can merge"), argv[0]);
                if (remoteheads->next)
                        die(_("Can merge only exactly one commit into empty head"));
 -              read_empty(remote_head->object.oid.hash, 0);
 -              update_ref("initial pull", "HEAD", remote_head->object.oid.hash,
 +              remote_head_sha1 = remoteheads->item->object.oid.hash;
 +              read_empty(remote_head_sha1, 0);
 +              update_ref("initial pull", "HEAD", remote_head_sha1,
                           NULL, 0, UPDATE_REFS_DIE_ON_ERR);
                goto done;
        }
        update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.oid.hash,
                   NULL, 0, UPDATE_REFS_DIE_ON_ERR);
  
-       if (remoteheads && !common)
-               ; /* No common ancestors found. We need a real merge. */
-       else if (!remoteheads ||
+       if (remoteheads && !common) {
+               /* No common ancestors found. */
+               if (!allow_unrelated_histories)
+                       die(_("refusing to merge unrelated histories"));
+               /* otherwise, we need a real merge. */
+       } else if (!remoteheads ||
                 (!remoteheads->next && !common->next &&
                  common->item == remoteheads->item)) {
                /*
diff --combined t/t5500-fetch-pack.sh
index 9b9bec468a849cec0344a4b735f7fcdb4f6f888d,256958023277de8b5c1c3924170a0640ffb92972..91a69fc33a590d22cf80e101f9b58b9a0b2d9d48
@@@ -14,7 -14,7 +14,7 @@@ test_description='Testing multi_ack pac
  add () {
        name=$1 &&
        text="$@" &&
 -      branch=`echo $name | sed -e 's/^\(.\).*$/\1/'` &&
 +      branch=$(echo $name | sed -e 's/^\(.\).*$/\1/') &&
        parents="" &&
  
        shift &&
@@@ -50,18 -50,18 +50,18 @@@ pull_to_client () 
                        case "$heads" in *B*)
                            echo $BTIP > .git/refs/heads/B;;
                        esac &&
 -                      git symbolic-ref HEAD refs/heads/`echo $heads \
 -                              | sed -e "s/^\(.\).*$/\1/"` &&
 +                      git symbolic-ref HEAD refs/heads/$(echo $heads \
 +                              | sed -e "s/^\(.\).*$/\1/") &&
  
                        git fsck --full &&
  
                        mv .git/objects/pack/pack-* . &&
 -                      p=`ls -1 pack-*.pack` &&
 +                      p=$(ls -1 pack-*.pack) &&
                        git unpack-objects <$p &&
                        git fsck --full &&
  
 -                      idx=`echo pack-*.idx` &&
 -                      pack_count=`git show-index <$idx | wc -l` &&
 +                      idx=$(echo pack-*.idx) &&
 +                      pack_count=$(git show-index <$idx | wc -l) &&
                        test $pack_count = $count &&
                        rm -f pack-*
                )
@@@ -132,13 -132,13 +132,13 @@@ test_expect_success 'single given branc
  
  test_expect_success 'clone shallow depth 1' '
        git clone --no-single-branch --depth 1 "file://$(pwd)/." shallow0 &&
 -      test "`git --git-dir=shallow0/.git rev-list --count HEAD`" = 1
 +      test "$(git --git-dir=shallow0/.git rev-list --count HEAD)" = 1
  '
  
  test_expect_success 'clone shallow depth 1 with fsck' '
        git config --global fetch.fsckobjects true &&
        git clone --no-single-branch --depth 1 "file://$(pwd)/." shallow0fsck &&
 -      test "`git --git-dir=shallow0fsck/.git rev-list --count HEAD`" = 1 &&
 +      test "$(git --git-dir=shallow0fsck/.git rev-list --count HEAD)" = 1 &&
        git config --global --unset fetch.fsckobjects
  '
  
@@@ -147,7 -147,7 +147,7 @@@ test_expect_success 'clone shallow' 
  '
  
  test_expect_success 'clone shallow depth count' '
 -      test "`git --git-dir=shallow/.git rev-list --count HEAD`" = 2
 +      test "$(git --git-dir=shallow/.git rev-list --count HEAD)" = 2
  '
  
  test_expect_success 'clone shallow object count' '
@@@ -259,7 -259,8 +259,8 @@@ test_expect_success 'clone shallow obje
  test_expect_success 'pull in shallow repo with missing merge base' '
        (
                cd shallow &&
-               test_must_fail git pull --depth 4 .. A
+               git fetch --depth 4 .. A
+               test_must_fail git merge --allow-unrelated-histories FETCH_HEAD
        )
  '
  
@@@ -273,15 -274,16 +274,16 @@@ test_expect_success 'additional simple 
  '
  
  test_expect_success 'clone shallow depth count' '
 -      test "`git --git-dir=shallow/.git rev-list --count HEAD`" = 11
 +      test "$(git --git-dir=shallow/.git rev-list --count HEAD)" = 11
  '
  
  test_expect_success 'clone shallow object count' '
        (
                cd shallow &&
+               git prune &&
                git count-objects -v
        ) > count.shallow &&
-       grep "^count: 55" count.shallow
+       grep "^count: 54" count.shallow
  '
  
  test_expect_success 'fetch --no-shallow on full repo' '
@@@ -531,20 -533,6 +533,20 @@@ test_expect_success 'shallow fetch wit
                git fsck
        )
  '
 +
 +test_expect_success 'fetch-pack can fetch a raw sha1' '
 +      git init hidden &&
 +      (
 +              cd hidden &&
 +              test_commit 1 &&
 +              test_commit 2 &&
 +              git update-ref refs/hidden/one HEAD^ &&
 +              git config transfer.hiderefs refs/hidden &&
 +              git config uploadpack.allowtipsha1inwant true
 +      ) &&
 +      git fetch-pack hidden $(git -C hidden rev-parse refs/hidden/one)
 +'
 +
  check_prot_path () {
        cat >expected <<-EOF &&
        Diag: url=$1
index d708cbf0320abe502d875ae3333eda2501208ca2,bc19811dde8308a31bed705b0d465bc736318a96..432c61d246c938192a2b31f2db35f4f9b320a788
@@@ -25,11 -25,11 +25,11 @@@ perl -e 'use DBI; use DBD::SQLite' >/de
      test_done
  }
  
 -WORKDIR=$(pwd)
 -SERVERDIR=$(pwd)/gitcvs.git
 +WORKDIR=$PWD
 +SERVERDIR=$PWD/gitcvs.git
  git_config="$SERVERDIR/config"
  CVSROOT=":fork:$SERVERDIR"
 -CVSWORK="$(pwd)/cvswork"
 +CVSWORK="$PWD/cvswork"
  CVS_SERVER=git-cvsserver
  export CVSROOT CVS_SERVER
  
@@@ -45,7 -45,8 +45,8 @@@ test_expect_success 'setup' 
    touch secondrootfile &&
    git add secondrootfile &&
    git commit -m "second root") &&
-   git pull secondroot master &&
+   git fetch secondroot master &&
+   git merge --allow-unrelated-histories FETCH_HEAD &&
    git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
    GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
    GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" &&