5274ba6907
When cloning a remote repository which's HEAD refers to a nonexistent ref, git-clone cloned all existing refs, but failed to write the configuration for 'remote'. Now it detects the dangling remote HEAD, refuses to checkout any local branch since HEAD refers to nowhere, but properly writes the configuration for 'remote', so that subsequent 'git fetch's don't fail. The problem was reported by Daniel Jacobowitz through http://bugs.debian.org/466581 Signed-off-by: Gerrit Pape <pape@smarden.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
496 lines
12 KiB
Bash
Executable File
496 lines
12 KiB
Bash
Executable File
#!/bin/sh
|
|
#
|
|
# Copyright (c) 2005, Linus Torvalds
|
|
# Copyright (c) 2005, Junio C Hamano
|
|
#
|
|
# Clone a repository into a different directory that does not yet exist.
|
|
|
|
# See git-sh-setup why.
|
|
unset CDPATH
|
|
|
|
OPTIONS_SPEC="\
|
|
git-clone [options] [--] <repo> [<dir>]
|
|
--
|
|
n,no-checkout don't create a checkout
|
|
bare create a bare repository
|
|
naked create a bare repository
|
|
l,local to clone from a local repository
|
|
no-hardlinks don't use local hardlinks, always copy
|
|
s,shared setup as a shared repository
|
|
template= path to the template directory
|
|
q,quiet be quiet
|
|
reference= reference repository
|
|
o,origin= use <name> instead of 'origin' to track upstream
|
|
u,upload-pack= path to git-upload-pack on the remote
|
|
depth= create a shallow clone of that depth
|
|
|
|
use-separate-remote compatibility, do not use
|
|
no-separate-remote compatibility, do not use"
|
|
|
|
die() {
|
|
echo >&2 "$@"
|
|
exit 1
|
|
}
|
|
|
|
usage() {
|
|
exec "$0" -h
|
|
}
|
|
|
|
eval "$(echo "$OPTIONS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
|
|
|
|
get_repo_base() {
|
|
(
|
|
cd "`/bin/pwd`" &&
|
|
cd "$1" || cd "$1.git" &&
|
|
{
|
|
cd .git
|
|
pwd
|
|
}
|
|
) 2>/dev/null
|
|
}
|
|
|
|
if [ -n "$GIT_SSL_NO_VERIFY" -o \
|
|
"`git config --bool http.sslVerify`" = false ]; then
|
|
curl_extra_args="-k"
|
|
fi
|
|
|
|
http_fetch () {
|
|
# $1 = Remote, $2 = Local
|
|
curl -nsfL $curl_extra_args "$1" >"$2"
|
|
curl_exit_status=$?
|
|
case $curl_exit_status in
|
|
126|127) exit ;;
|
|
*) return $curl_exit_status ;;
|
|
esac
|
|
}
|
|
|
|
clone_dumb_http () {
|
|
# $1 - remote, $2 - local
|
|
cd "$2" &&
|
|
clone_tmp="$GIT_DIR/clone-tmp" &&
|
|
mkdir -p "$clone_tmp" || exit 1
|
|
if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
|
|
"`git config --bool http.noEPSV`" = true ]; then
|
|
curl_extra_args="${curl_extra_args} --disable-epsv"
|
|
fi
|
|
http_fetch "$1/info/refs" "$clone_tmp/refs" ||
|
|
die "Cannot get remote repository information.
|
|
Perhaps git-update-server-info needs to be run there?"
|
|
test "z$quiet" = z && v=-v || v=
|
|
while read sha1 refname
|
|
do
|
|
name=`expr "z$refname" : 'zrefs/\(.*\)'` &&
|
|
case "$name" in
|
|
*^*) continue;;
|
|
esac
|
|
case "$bare,$name" in
|
|
yes,* | ,heads/* | ,tags/*) ;;
|
|
*) continue ;;
|
|
esac
|
|
if test -n "$use_separate_remote" &&
|
|
branch_name=`expr "z$name" : 'zheads/\(.*\)'`
|
|
then
|
|
tname="remotes/$origin/$branch_name"
|
|
else
|
|
tname=$name
|
|
fi
|
|
git-http-fetch $v -a -w "$tname" "$sha1" "$1" || exit 1
|
|
done <"$clone_tmp/refs"
|
|
rm -fr "$clone_tmp"
|
|
http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" ||
|
|
rm -f "$GIT_DIR/REMOTE_HEAD"
|
|
if test -f "$GIT_DIR/REMOTE_HEAD"; then
|
|
head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
|
|
case "$head_sha1" in
|
|
'ref: refs/'*)
|
|
;;
|
|
*)
|
|
git-http-fetch $v -a "$head_sha1" "$1" ||
|
|
rm -f "$GIT_DIR/REMOTE_HEAD"
|
|
;;
|
|
esac
|
|
fi
|
|
}
|
|
|
|
quiet=
|
|
local=no
|
|
use_local_hardlink=yes
|
|
local_shared=no
|
|
unset template
|
|
no_checkout=
|
|
upload_pack=
|
|
bare=
|
|
reference=
|
|
origin=
|
|
origin_override=
|
|
use_separate_remote=t
|
|
depth=
|
|
no_progress=
|
|
local_explicitly_asked_for=
|
|
test -t 1 || no_progress=--no-progress
|
|
|
|
while test $# != 0
|
|
do
|
|
case "$1" in
|
|
-n|--no-checkout)
|
|
no_checkout=yes ;;
|
|
--naked|--bare)
|
|
bare=yes ;;
|
|
-l|--local)
|
|
local_explicitly_asked_for=yes
|
|
use_local_hardlink=yes
|
|
;;
|
|
--no-hardlinks)
|
|
use_local_hardlink=no ;;
|
|
-s|--shared)
|
|
local_shared=yes ;;
|
|
--template)
|
|
shift; template="--template=$1" ;;
|
|
-q|--quiet)
|
|
quiet=-q ;;
|
|
--use-separate-remote|--no-separate-remote)
|
|
die "clones are always made with separate-remote layout" ;;
|
|
--reference)
|
|
shift; reference="$1" ;;
|
|
-o|--origin)
|
|
shift;
|
|
case "$1" in
|
|
'')
|
|
usage ;;
|
|
*/*)
|
|
die "'$1' is not suitable for an origin name"
|
|
esac
|
|
git check-ref-format "heads/$1" ||
|
|
die "'$1' is not suitable for a branch name"
|
|
test -z "$origin_override" ||
|
|
die "Do not give more than one --origin options."
|
|
origin_override=yes
|
|
origin="$1"
|
|
;;
|
|
-u|--upload-pack)
|
|
shift
|
|
upload_pack="--upload-pack=$1" ;;
|
|
--depth)
|
|
shift
|
|
depth="--depth=$1" ;;
|
|
--)
|
|
shift
|
|
break ;;
|
|
*)
|
|
usage ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
repo="$1"
|
|
test -n "$repo" ||
|
|
die 'you must specify a repository to clone.'
|
|
|
|
# --bare implies --no-checkout and --no-separate-remote
|
|
if test yes = "$bare"
|
|
then
|
|
if test yes = "$origin_override"
|
|
then
|
|
die '--bare and --origin $origin options are incompatible.'
|
|
fi
|
|
no_checkout=yes
|
|
use_separate_remote=
|
|
fi
|
|
|
|
if test -z "$origin"
|
|
then
|
|
origin=origin
|
|
fi
|
|
|
|
# Turn the source into an absolute path if
|
|
# it is local
|
|
if base=$(get_repo_base "$repo"); then
|
|
repo="$base"
|
|
if test -z "$depth"
|
|
then
|
|
local=yes
|
|
fi
|
|
fi
|
|
|
|
dir="$2"
|
|
# Try using "humanish" part of source repo if user didn't specify one
|
|
[ -z "$dir" ] && dir=$(echo "$repo" | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
|
|
[ -e "$dir" ] && die "destination directory '$dir' already exists."
|
|
[ yes = "$bare" ] && unset GIT_WORK_TREE
|
|
[ -n "$GIT_WORK_TREE" ] && [ -e "$GIT_WORK_TREE" ] &&
|
|
die "working tree '$GIT_WORK_TREE' already exists."
|
|
D=
|
|
W=
|
|
cleanup() {
|
|
err=$?
|
|
test -z "$D" && rm -rf "$dir"
|
|
test -z "$W" && test -n "$GIT_WORK_TREE" && rm -rf "$GIT_WORK_TREE"
|
|
cd ..
|
|
test -n "$D" && rm -rf "$D"
|
|
test -n "$W" && rm -rf "$W"
|
|
exit $err
|
|
}
|
|
trap cleanup 0
|
|
mkdir -p "$dir" && D=$(cd "$dir" && pwd) || usage
|
|
test -n "$GIT_WORK_TREE" && mkdir -p "$GIT_WORK_TREE" &&
|
|
W=$(cd "$GIT_WORK_TREE" && pwd) && GIT_WORK_TREE="$W" && export GIT_WORK_TREE
|
|
if test yes = "$bare" || test -n "$GIT_WORK_TREE"; then
|
|
GIT_DIR="$D"
|
|
else
|
|
GIT_DIR="$D/.git"
|
|
fi &&
|
|
export GIT_DIR &&
|
|
GIT_CONFIG="$GIT_DIR/config" git-init $quiet ${template+"$template"} || usage
|
|
|
|
if test -n "$bare"
|
|
then
|
|
GIT_CONFIG="$GIT_DIR/config" git config core.bare true
|
|
fi
|
|
|
|
if test -n "$reference"
|
|
then
|
|
ref_git=
|
|
if test -d "$reference"
|
|
then
|
|
if test -d "$reference/.git/objects"
|
|
then
|
|
ref_git="$reference/.git"
|
|
elif test -d "$reference/objects"
|
|
then
|
|
ref_git="$reference"
|
|
fi
|
|
fi
|
|
if test -n "$ref_git"
|
|
then
|
|
ref_git=$(cd "$ref_git" && pwd)
|
|
echo "$ref_git/objects" >"$GIT_DIR/objects/info/alternates"
|
|
(
|
|
GIT_DIR="$ref_git" git for-each-ref \
|
|
--format='%(objectname) %(*objectname)'
|
|
) |
|
|
while read a b
|
|
do
|
|
test -z "$a" ||
|
|
git update-ref "refs/reference-tmp/$a" "$a"
|
|
test -z "$b" ||
|
|
git update-ref "refs/reference-tmp/$b" "$b"
|
|
done
|
|
else
|
|
die "reference repository '$reference' is not a local directory."
|
|
fi
|
|
fi
|
|
|
|
rm -f "$GIT_DIR/CLONE_HEAD"
|
|
|
|
# We do local magic only when the user tells us to.
|
|
case "$local" in
|
|
yes)
|
|
( cd "$repo/objects" ) ||
|
|
die "cannot chdir to local '$repo/objects'."
|
|
|
|
if test "$local_shared" = yes
|
|
then
|
|
mkdir -p "$GIT_DIR/objects/info"
|
|
echo "$repo/objects" >>"$GIT_DIR/objects/info/alternates"
|
|
else
|
|
l= &&
|
|
if test "$use_local_hardlink" = yes
|
|
then
|
|
# See if we can hardlink and drop "l" if not.
|
|
sample_file=$(cd "$repo" && \
|
|
find objects -type f -print | sed -e 1q)
|
|
# objects directory should not be empty because
|
|
# we are cloning!
|
|
test -f "$repo/$sample_file" ||
|
|
die "fatal: cannot clone empty repository"
|
|
if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null
|
|
then
|
|
rm -f "$GIT_DIR/objects/sample"
|
|
l=l
|
|
elif test -n "$local_explicitly_asked_for"
|
|
then
|
|
echo >&2 "Warning: -l asked but cannot hardlink to $repo"
|
|
fi
|
|
fi &&
|
|
cd "$repo" &&
|
|
find objects -depth -print | cpio -pumd$l "$GIT_DIR/" || exit 1
|
|
fi
|
|
git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
|
|
;;
|
|
*)
|
|
case "$repo" in
|
|
rsync://*)
|
|
case "$depth" in
|
|
"") ;;
|
|
*) die "shallow over rsync not supported" ;;
|
|
esac
|
|
rsync $quiet -av --ignore-existing \
|
|
--exclude info "$repo/objects/" "$GIT_DIR/objects/" ||
|
|
exit
|
|
# Look at objects/info/alternates for rsync -- http will
|
|
# support it natively and git native ones will do it on the
|
|
# remote end. Not having that file is not a crime.
|
|
rsync -q "$repo/objects/info/alternates" \
|
|
"$GIT_DIR/TMP_ALT" 2>/dev/null ||
|
|
rm -f "$GIT_DIR/TMP_ALT"
|
|
if test -f "$GIT_DIR/TMP_ALT"
|
|
then
|
|
( cd "$D" &&
|
|
. git-parse-remote &&
|
|
resolve_alternates "$repo" <"$GIT_DIR/TMP_ALT" ) |
|
|
while read alt
|
|
do
|
|
case "$alt" in 'bad alternate: '*) die "$alt";; esac
|
|
case "$quiet" in
|
|
'') echo >&2 "Getting alternate: $alt" ;;
|
|
esac
|
|
rsync $quiet -av --ignore-existing \
|
|
--exclude info "$alt" "$GIT_DIR/objects" || exit
|
|
done
|
|
rm -f "$GIT_DIR/TMP_ALT"
|
|
fi
|
|
git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
|
|
;;
|
|
https://*|http://*|ftp://*)
|
|
case "$depth" in
|
|
"") ;;
|
|
*) die "shallow over http or ftp not supported" ;;
|
|
esac
|
|
if test -z "@@NO_CURL@@"
|
|
then
|
|
clone_dumb_http "$repo" "$D"
|
|
else
|
|
die "http transport not supported, rebuild Git with curl support"
|
|
fi
|
|
;;
|
|
*)
|
|
case "$upload_pack" in
|
|
'') git-fetch-pack --all -k $quiet $depth $no_progress "$repo";;
|
|
*) git-fetch-pack --all -k $quiet "$upload_pack" $depth $no_progress "$repo" ;;
|
|
esac >"$GIT_DIR/CLONE_HEAD" ||
|
|
die "fetch-pack from '$repo' failed."
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
test -d "$GIT_DIR/refs/reference-tmp" && rm -fr "$GIT_DIR/refs/reference-tmp"
|
|
|
|
if test -f "$GIT_DIR/CLONE_HEAD"
|
|
then
|
|
# Read git-fetch-pack -k output and store the remote branches.
|
|
if [ -n "$use_separate_remote" ]
|
|
then
|
|
branch_top="remotes/$origin"
|
|
else
|
|
branch_top="heads"
|
|
fi
|
|
tag_top="tags"
|
|
while read sha1 name
|
|
do
|
|
case "$name" in
|
|
*'^{}')
|
|
continue ;;
|
|
HEAD)
|
|
destname="REMOTE_HEAD" ;;
|
|
refs/heads/*)
|
|
destname="refs/$branch_top/${name#refs/heads/}" ;;
|
|
refs/tags/*)
|
|
destname="refs/$tag_top/${name#refs/tags/}" ;;
|
|
*)
|
|
continue ;;
|
|
esac
|
|
git update-ref -m "clone: from $repo" "$destname" "$sha1" ""
|
|
done < "$GIT_DIR/CLONE_HEAD"
|
|
fi
|
|
|
|
if test -n "$W"; then
|
|
cd "$W" || exit
|
|
else
|
|
cd "$D" || exit
|
|
fi
|
|
|
|
if test -z "$bare"
|
|
then
|
|
# a non-bare repository is always in separate-remote layout
|
|
remote_top="refs/remotes/$origin"
|
|
head_sha1=
|
|
test ! -r "$GIT_DIR/REMOTE_HEAD" || head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
|
|
case "$head_sha1" in
|
|
'ref: refs/'*)
|
|
# Uh-oh, the remote told us (http transport done against
|
|
# new style repository with a symref HEAD).
|
|
# Ideally we should skip the guesswork but for now
|
|
# opt for minimum change.
|
|
head_sha1=`expr "z$head_sha1" : 'zref: refs/heads/\(.*\)'`
|
|
head_sha1=`cat "$GIT_DIR/$remote_top/$head_sha1"`
|
|
;;
|
|
esac
|
|
|
|
# The name under $remote_top the remote HEAD seems to point at.
|
|
head_points_at=$(
|
|
(
|
|
test -f "$GIT_DIR/$remote_top/master" && echo "master"
|
|
cd "$GIT_DIR/$remote_top" &&
|
|
find . -type f -print | sed -e 's/^\.\///'
|
|
) | (
|
|
done=f
|
|
while read name
|
|
do
|
|
test t = $done && continue
|
|
branch_tip=`cat "$GIT_DIR/$remote_top/$name"`
|
|
if test "$head_sha1" = "$branch_tip"
|
|
then
|
|
echo "$name"
|
|
done=t
|
|
fi
|
|
done
|
|
)
|
|
)
|
|
|
|
# Upstream URL
|
|
git config remote."$origin".url "$repo" &&
|
|
|
|
# Set up the mappings to track the remote branches.
|
|
git config remote."$origin".fetch \
|
|
"+refs/heads/*:$remote_top/*" '^$' &&
|
|
|
|
# Write out remote.$origin config, and update our "$head_points_at".
|
|
case "$head_points_at" in
|
|
?*)
|
|
# Local default branch
|
|
git symbolic-ref HEAD "refs/heads/$head_points_at" &&
|
|
|
|
# Tracking branch for the primary branch at the remote.
|
|
git update-ref HEAD "$head_sha1" &&
|
|
|
|
rm -f "refs/remotes/$origin/HEAD"
|
|
git symbolic-ref "refs/remotes/$origin/HEAD" \
|
|
"refs/remotes/$origin/$head_points_at" &&
|
|
|
|
git config branch."$head_points_at".remote "$origin" &&
|
|
git config branch."$head_points_at".merge "refs/heads/$head_points_at"
|
|
;;
|
|
'')
|
|
if test -z "$head_sha1"
|
|
then
|
|
# Source had nonexistent ref in HEAD
|
|
echo >&2 "Warning: Remote HEAD refers to nonexistent ref, unable to checkout."
|
|
no_checkout=t
|
|
else
|
|
# Source had detached HEAD pointing nowhere
|
|
git update-ref --no-deref HEAD "$head_sha1" &&
|
|
rm -f "refs/remotes/$origin/HEAD"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
case "$no_checkout" in
|
|
'')
|
|
test "z$quiet" = z -a "z$no_progress" = z && v=-v || v=
|
|
git read-tree -m -u $v HEAD HEAD
|
|
esac
|
|
fi
|
|
rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD"
|
|
|
|
trap - 0
|