c904bf392d
Honour the setgid and umask when re-creating the objects directory at the destination. cpio in copy-pass mode aims to copy file permissions which causes this problem and cannot be disabled. Be explicit by copying the directory structure first, honouring the permissions at the destination, then copy the files with 0444 permissions. This also avoids bugs in some versions of cpio. Signed-off-by: Mark Hills <mark@pogo.org.uk> Signed-off-by: Junio C Hamano <gitster@pobox.com>
527 lines
12 KiB
Bash
Executable File
527 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
|
|
elif test -f "$repo"
|
|
then
|
|
case "$repo" in /*) ;; *) repo="$PWD/$repo" ;; esac
|
|
fi
|
|
|
|
# Decide the directory name of the new repository
|
|
if test -n "$2"
|
|
then
|
|
dir="$2"
|
|
test $# = 2 || die "excess parameter to git-clone"
|
|
else
|
|
# Derive one from the repository name
|
|
# Try using "humanish" part of source repo if user didn't specify one
|
|
if test -f "$repo"
|
|
then
|
|
# Cloning from a bundle
|
|
dir=$(echo "$repo" | sed -e 's|/*\.bundle$||' -e 's|.*/||g')
|
|
else
|
|
dir=$(echo "$repo" |
|
|
sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
|
|
fi
|
|
fi
|
|
|
|
[ -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
|
|
cpio_quiet_flag=""
|
|
cpio --help 2>&1 | grep -- --quiet >/dev/null && \
|
|
cpio_quiet_flag=--quiet
|
|
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" &&
|
|
# Create dirs using umask and permissions and destination
|
|
find objects -type d -print | (cd "$GIT_DIR" && xargs mkdir -p) &&
|
|
# Copy existing 0444 permissions on content
|
|
find objects ! -type d -print | cpio $cpio_quiet_flag -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
|
|
;;
|
|
*)
|
|
if [ -f "$repo" ] ; then
|
|
git bundle unbundle "$repo" > "$GIT_DIR/CLONE_HEAD" ||
|
|
die "unbundle from '$repo' failed."
|
|
else
|
|
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."
|
|
fi
|
|
;;
|
|
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
|