From 8434c2f1afedb936e0ea8c07ce25733013c2f743 Mon Sep 17 00:00:00 2001
From: Daniel Barkalow <barkalow@iabervon.org>
Date: Sun, 27 Apr 2008 13:39:30 -0400
Subject: Build in clone

Thanks to Johannes Schindelin for various comments and improvements,
including supporting cloning full bundles.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 contrib/examples/git-clone.sh | 523 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 523 insertions(+)
 create mode 100755 contrib/examples/git-clone.sh

(limited to 'contrib/examples/git-clone.sh')

diff --git a/contrib/examples/git-clone.sh b/contrib/examples/git-clone.sh
new file mode 100755
index 0000000000..8c7fc7f631
--- /dev/null
+++ b/contrib/examples/git-clone.sh
@@ -0,0 +1,523 @@
+#!/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" &&
+		find objects -depth -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
-- 
cgit v1.2.1