| #!/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() { | |
| 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 'err=$?; 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 |