Teach core object handling functions about gitlinks
[gitweb.git] / git-checkout.sh
index 69d0c1c43a9f78908f13d3e0e9c34c44587367dd..deb0a9a3c733ed889158d05b7cae4d174917553d 100755 (executable)
@@ -1,8 +1,9 @@
 #!/bin/sh
 
-USAGE='[-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]'
+USAGE='[-q] [-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]'
 SUBDIRECTORY_OK=Sometimes
 . git-sh-setup
+require_work_tree
 
 old_name=HEAD
 old=$(git-rev-parse --verify $old_name 2>/dev/null)
@@ -11,9 +12,11 @@ new=
 new_name=
 force=
 branch=
+track=
 newbranch=
 newbranch_log=
 merge=
+quiet=
 LF='
 '
 while [ "$#" != "0" ]; do
@@ -31,7 +34,10 @@ while [ "$#" != "0" ]; do
                        die "git checkout: we do not like '$newbranch' as a branch name."
                ;;
        "-l")
-               newbranch_log=1
+               newbranch_log=-l
+               ;;
+       "--track"|"--no-track")
+               track="$arg"
                ;;
        "-f")
                force=1
@@ -39,6 +45,9 @@ while [ "$#" != "0" ]; do
        -m)
                merge=1
                ;;
+       "-q")
+               quiet=1
+               ;;
        --)
                break
                ;;
@@ -53,7 +62,7 @@ while [ "$#" != "0" ]; do
                                exit 1
                        fi
                        new="$rev"
-                       new_name="$arg^0"
+                       new_name="$arg"
                        if git-show-ref --verify --quiet -- "refs/heads/$arg"
                        then
                                branch="$arg"
@@ -80,6 +89,11 @@ while [ "$#" != "0" ]; do
     esac
 done
 
+case "$newbranch,$track" in
+,--*)
+       die "git checkout: --track and --no-track require -b"
+esac
+
 case "$force$merge" in
 11)
        die "git checkout: -f and -m are incompatible"
@@ -134,11 +148,7 @@ fi
 
 # We are switching branches and checking out trees, so
 # we *NEED* to be at the toplevel.
-cdup=$(git-rev-parse --show-cdup)
-if test ! -z "$cdup"
-then
-       cd "$cdup"
-fi
+cd_to_toplevel
 
 [ -z "$new" ] && new=$old && new_name="$old_name"
 
@@ -150,44 +160,38 @@ fi
 # correct, it feels somewhat funny.  More importantly, we do not
 # want "git checkout" nor "git checkout -f" to detach HEAD.
 
-if test -z "$branch$newbranch" && test "$new" != "$old"
-then
-       # NEEDSWORK: we would want to have a command here
-       # that allows us to detach the HEAD atomically.  Perhaps
-       # something like "git update-ref --detach HEAD $new"
-       echo "$new" >"$GIT_DIR/HEAD.new" &&
-       mv "$GIT_DIR/HEAD.new" "$GIT_DIR/HEAD" || die "Cannot detach HEAD"
+detached=
+detach_warn=
 
-       if test -n "$oldbranch"
-       then
-               echo >&2 "warning: you are not on ANY branch anymore.
-If you meant to create a new branch from the commit, you need -b to
-associate a new branch with the wanted checkout.  Example:
-  git checkout -b <new_branch_name> $arg
-"
-       fi
-elif test -z "$oldbranch" && test -n "$branch"
+describe_detached_head () {
+       test -n "$quiet" || {
+               printf >&2 "$1 "
+               GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2"
+       }
+}
+
+if test -z "$branch$newbranch" && test "$new_name" != "$old_name"
 then
-       # Coming back...
-       if test -z "$force"
+       detached="$new"
+       if test -n "$oldbranch" && test -z "$quiet"
        then
-               mb=$(git merge-base --all $old $new) &&
-               case "$LF$mb$LF" in
-               *"$LF$old$LF"*) : ;;
-               *)      false ;;
-               esac || {
-                       echo >&2 \
-"You are not on a branch and switching to $new_name branch may lose
-your changes.  Use 'git checkout -f $new_name' if you want to."
-                       exit 1;
-               }
+               detach_warn="Note: moving to \"$new_name\" which isn't a local branch
+If you want to create a new branch from this checkout, you may do so
+(now or later) by using -b with the checkout command again. Example:
+  git checkout -b <new_branch_name>"
        fi
+elif test -z "$oldbranch" && test "$new" != "$old"
+then
+       describe_detached_head 'Previous HEAD position was' "$old"
 fi
 
 if [ "X$old" = X ]
 then
-       echo >&2 "warning: You appear to be on a branch yet to be born."
-       echo >&2 "warning: Forcing checkout of $new_name."
+       if test -z "$quiet"
+       then
+               echo >&2 "warning: You appear to be on a branch yet to be born."
+               echo >&2 "warning: Forcing checkout of $new_name."
+       fi
        force=1
 fi
 
@@ -206,16 +210,12 @@ else
        # Match the index to the working tree, and do a three-way.
        git diff-files --name-only | git update-index --remove --stdin &&
        work=`git write-tree` &&
-       git read-tree --reset -u $new &&
-       git read-tree -m -u --aggressive --exclude-per-directory=.gitignore $old $new $work ||
-       exit
+       git read-tree --reset -u $new || exit
 
-       if result=`git write-tree 2>/dev/null`
-       then
-           echo >&2 "Trivially automerged."
-       else
-           git merge-index -o git-merge-one-file -a
-       fi
+       eval GITHEAD_$new='${new_name:-${branch:-$new}}' &&
+       eval GITHEAD_$work=local &&
+       export GITHEAD_$new GITHEAD_$work &&
+       git merge-recursive $old -- $new $work
 
        # Do not register the cleanly merged paths in the index yet.
        # this is not a real merge before committing, but just carrying
@@ -236,9 +236,9 @@ else
        exit 0
     )
     saved_err=$?
-    if test "$saved_err" = 0
+    if test "$saved_err" = 0 && test -z "$quiet"
     then
-       test "$new" = "$old" || git diff-index --name-status "$new"
+       git diff-index --name-status "$new"
     fi
     (exit $saved_err)
 fi
@@ -251,15 +251,39 @@ fi
 #
 if [ "$?" -eq 0 ]; then
        if [ "$newbranch" ]; then
-               if [ "$newbranch_log" ]; then
-                       mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$newbranch")
-                       touch "$GIT_DIR/logs/refs/heads/$newbranch"
-               fi
-               git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit
+               git-branch $track $newbranch_log "$newbranch" "$new_name" || exit
                branch="$newbranch"
        fi
-       [ "$branch" ] &&
-       GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch"
+       if test -n "$branch"
+       then
+               GIT_DIR="$GIT_DIR" git-symbolic-ref -m "checkout: moving to $branch" HEAD "refs/heads/$branch"
+               if test -n "$quiet"
+               then
+                       true    # nothing
+               elif test "refs/heads/$branch" = "$oldbranch"
+               then
+                       echo >&2 "Already on branch \"$branch\""
+               else
+                       echo >&2 "Switched to${newbranch:+ a new} branch \"$branch\""
+               fi
+       elif test -n "$detached"
+       then
+               # NEEDSWORK: we would want a command to detach the HEAD
+               # atomically, instead of this handcrafted command sequence.
+               # Perhaps:
+               #       git update-ref --detach HEAD $new
+               # or something like that...
+               #
+               git-rev-parse HEAD >"$GIT_DIR/HEAD.new" &&
+               mv "$GIT_DIR/HEAD.new" "$GIT_DIR/HEAD" &&
+               git-update-ref -m "checkout: moving to $arg" HEAD "$detached" ||
+                       die "Cannot detach HEAD"
+               if test -n "$detach_warn"
+               then
+                       echo >&2 "$detach_warn"
+               fi
+               describe_detached_head 'HEAD is now at' HEAD
+       fi
        rm -f "$GIT_DIR/MERGE_HEAD"
 else
        exit 1