#!/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 setglobal OPTIONS_SPEC = '"\ git-clone [options] [--] [] -- 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 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'" proc die { echo >&2 @Argv> !2 "$@" exit 1 } proc usage { exec $0 -h } eval $[echo $OPTIONS_SPEC | git rev-parse --parseopt -- @Argv || echo exit $Status] proc get_repo_base { shell { cd $[/bin/pwd] && cd $1 || cd "$1.git" && do { cd .git pwd } } 2>/dev/null } if test -n $GIT_SSL_NO_VERIFY -o \ $[git config --bool http.sslVerify] = false { setglobal curl_extra_args = '"-k'" } proc http_fetch { # $1 = Remote, $2 = Local curl -nsfL $curl_extra_args $1 >$2 setglobal curl_exit_status = $Status match $curl_exit_status { with 126|127 exit with * return $curl_exit_status } } proc clone_dumb_http { # $1 - remote, $2 - local cd $2 && setglobal clone_tmp = ""$GIT_DIR/clone-tmp"" && mkdir -p $clone_tmp || exit 1 if test -n $GIT_CURL_FTP_NO_EPSV -o \ $[git config --bool http.noEPSV] = true { setglobal curl_extra_args = ""$(curl_extra_args) --disable-epsv"" } 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 && setglobal v = '-v' || setglobal v = '' while read sha1 refname { setglobal name = $[expr "z$refname" : 'zrefs/\(.*\)] && match $name { with *^* continue } match "$bare,$name" { with yes,* | ,heads/* | ,tags/* with * continue } if test -n $use_separate_remote && setglobal branch_name = $[expr "z$name" : 'zheads/\(.*\)] { setglobal tname = ""remotes/$origin/$branch_name"" } else { setglobal tname = $name } git-http-fetch $v -a -w $tname $sha1 $1 || exit 1 } <"$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" { setglobal head_sha1 = $[cat "$GIT_DIR/REMOTE_HEAD] match $head_sha1 { with 'ref: refs/'* with * git-http-fetch $v -a $head_sha1 $1 || rm -f "$GIT_DIR/REMOTE_HEAD" } } } setglobal quiet = '' setglobal local = 'no' setglobal use_local_hardlink = 'yes' setglobal local_shared = 'no' unset template setglobal no_checkout = '' setglobal upload_pack = '' setglobal bare = '' setglobal reference = '' setglobal origin = '' setglobal origin_override = '' setglobal use_separate_remote = 't' setglobal depth = '' setglobal no_progress = '' setglobal local_explicitly_asked_for = '' test -t 1 || setglobal no_progress = '--no-progress' while test $# != 0 { match $1 { with -n|--no-checkout setglobal no_checkout = 'yes' with --naked|--bare setglobal bare = 'yes' with -l|--local setglobal local_explicitly_asked_for = 'yes' setglobal use_local_hardlink = 'yes' with --no-hardlinks setglobal use_local_hardlink = 'no' with -s|--shared setglobal local_shared = 'yes' with --template shift; setglobal template = ""--template=$1"" with -q|--quiet setglobal quiet = '-q' with --use-separate-remote|--no-separate-remote die "clones are always made with separate-remote layout" with --reference shift; setglobal reference = $1 with -o|--origin shift; match $1 { with '' usage with */* die "'$1' is not suitable for an origin name" } 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." setglobal origin_override = 'yes' setglobal origin = $1 with -u|--upload-pack shift setglobal upload_pack = ""--upload-pack=$1"" with --depth shift setglobal depth = ""--depth=$1"" with -- shift break with * usage } shift } setglobal 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 { if test yes = $origin_override { die '--bare and --origin $origin options are incompatible.' } setglobal no_checkout = 'yes' setglobal use_separate_remote = '' } if test -z $origin { setglobal origin = 'origin' } # Turn the source into an absolute path if # it is local if setglobal base = $[get_repo_base $repo] { setglobal repo = $base if test -z $depth { setglobal local = 'yes' } } elif test -f $repo { match $repo { with /* with * setglobal repo = ""$PWD/$repo"" } } # Decide the directory name of the new repository if test -n $2 { setglobal dir = $2 test $Argc = 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 { # Cloning from a bundle setglobal dir = $[echo $repo | sed -e 's|/*\.bundle$||' -e 's|.*/||g] } else { setglobal dir = $[echo $repo | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g] } } test -e $dir && die "destination directory '$dir' already exists." test yes = $bare && unset GIT_WORK_TREE test -n $GIT_WORK_TREE && test -e $GIT_WORK_TREE && die "working tree '$GIT_WORK_TREE' already exists." setglobal D = '' setglobal W = '' proc 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 && setglobal D = $[cd $dir && pwd] || usage test -n $GIT_WORK_TREE && mkdir -p $GIT_WORK_TREE && setglobal W = $[cd $GIT_WORK_TREE && pwd] && setglobal GIT_WORK_TREE = $W && export GIT_WORK_TREE if test yes = $bare || test -n $GIT_WORK_TREE { setglobal GIT_DIR = $D } else { setglobal GIT_DIR = ""$D/.git"" } && export GIT_DIR && env GIT_CONFIG="$GIT_DIR/config" git-init $quiet $(template+"$template") || usage if test -n $bare { env GIT_CONFIG="$GIT_DIR/config" git config core.bare true } if test -n $reference { setglobal ref_git = '' if test -d $reference { if test -d "$reference/.git/objects" { setglobal ref_git = ""$reference/.git"" } elif test -d "$reference/objects" { setglobal ref_git = $reference } } if test -n $ref_git { setglobal ref_git = $[cd $ref_git && pwd] echo "$ref_git/objects" >"$GIT_DIR/objects/info/alternates" shell { env GIT_DIR=$ref_git git for-each-ref \ --format='%(objectname) %(*objectname)' } | while read a b { test -z $a || git update-ref "refs/reference-tmp/$a" $a test -z $b || git update-ref "refs/reference-tmp/$b" $b } } else { die "reference repository '$reference' is not a local directory." } } rm -f "$GIT_DIR/CLONE_HEAD" # We do local magic only when the user tells us to. match $local { with yes shell { cd "$repo/objects" } || die "cannot chdir to local '$repo/objects'." if test $local_shared = yes { mkdir -p "$GIT_DIR/objects/info" echo "$repo/objects" >>"$GIT_DIR/objects/info/alternates" } else { setglobal cpio_quiet_flag = ''"" cpio --help !2 > !1 | grep -- --quiet >/dev/null && \ setglobal cpio_quiet_flag = '--quiet' setglobal l = '' && if test $use_local_hardlink = yes { # See if we can hardlink and drop "l" if not. setglobal 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 { rm -f "$GIT_DIR/objects/sample" setglobal l = 'l' } elif test -n $local_explicitly_asked_for { echo >&2 "Warning: -l asked but cannot hardlink to $repo> !2 "Warning: -l asked but cannot hardlink to $repo" } } && cd $repo && # Create dirs using umask and permissions and destination find objects -type d -print | shell {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 } git-ls-remote $repo >"$GIT_DIR/CLONE_HEAD" || exit 1 with * match $repo { with rsync://* match $depth { with "" with * die "shallow over rsync not supported" } 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" { shell { cd $D && source git-parse-remote && resolve_alternates $repo <"$GIT_DIR/TMP_ALT" } | while read alt { match $alt { with 'bad alternate: '* die $alt } match $quiet { with '' echo >&2 "Getting alternate: $alt> !2 "Getting alternate: $alt" } rsync $quiet -av --ignore-existing \ --exclude info $alt "$GIT_DIR/objects" || exit } rm -f "$GIT_DIR/TMP_ALT" } git-ls-remote $repo >"$GIT_DIR/CLONE_HEAD" || exit 1 with https://*|http://*|ftp://* match $depth { with "" with * die "shallow over http or ftp not supported" } if test -z "@@NO_CURL@@" { clone_dumb_http $repo $D } else { die "http transport not supported, rebuild Git with curl support" } with * if test -f $repo { git bundle unbundle $repo > "$GIT_DIR/CLONE_HEAD" || die "unbundle from '$repo' failed." } else { match $upload_pack { with '' git-fetch-pack --all -k $quiet $depth $no_progress $repo with * git-fetch-pack --all -k \ $quiet $upload_pack $depth $no_progress $repo } >"$GIT_DIR/CLONE_HEAD" || die "fetch-pack from '$repo' failed." } } } test -d "$GIT_DIR/refs/reference-tmp" && rm -fr "$GIT_DIR/refs/reference-tmp" if test -f "$GIT_DIR/CLONE_HEAD" { # Read git-fetch-pack -k output and store the remote branches. if test -n $use_separate_remote { setglobal branch_top = ""remotes/$origin"" } else { setglobal branch_top = '"heads'" } setglobal tag_top = '"tags'" while read sha1 name { match $name { with *'^{}' continue with HEAD setglobal destname = '"REMOTE_HEAD'" with refs/heads/* setglobal destname = ""refs/$branch_top/$(name#refs/heads/)"" with refs/tags/* setglobal destname = ""refs/$tag_top/$(name#refs/tags/)"" with * continue } git update-ref -m "clone: from $repo" $destname $sha1 "" } < "$GIT_DIR/CLONE_HEAD" } if test -n $W { cd $W || exit } else { cd $D || exit } if test -z $bare { # a non-bare repository is always in separate-remote layout setglobal remote_top = ""refs/remotes/$origin"" setglobal head_sha1 = '' test ! -r "$GIT_DIR/REMOTE_HEAD" || setglobal head_sha1 = $[cat "$GIT_DIR/REMOTE_HEAD] match $head_sha1 { with '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. setglobal head_sha1 = $[expr "z$head_sha1" : 'zref: refs/heads/\(.*\)] setglobal head_sha1 = $[cat "$GIT_DIR/$remote_top/$head_sha1] } # The name under $remote_top the remote HEAD seems to point at. setglobal head_points_at = $[ shell { test -f "$GIT_DIR/$remote_top/master" && echo "master" cd "$GIT_DIR/$remote_top" && find . -type f -print | sed -e 's/^\.\///' } | shell { setglobal done = 'f' while read name { test t = $done && continue setglobal branch_tip = $[cat "$GIT_DIR/$remote_top/$name] if test $head_sha1 = $branch_tip { echo $name setglobal done = 't' } } }] # 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". match $head_points_at { with ?* # 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" with '' if test -z $head_sha1 { # Source had nonexistent ref in HEAD echo >&2 "Warning: Remote HEAD refers to nonexistent ref, unable to checkout.> !2 "Warning: Remote HEAD refers to nonexistent ref, unable to checkout." setglobal no_checkout = 't' } else { # Source had detached HEAD pointing nowhere git update-ref --no-deref HEAD $head_sha1 && rm -f "refs/remotes/$origin/HEAD" } } match $no_checkout { with '' test "z$quiet" = z && test "z$no_progress" = z && setglobal v = '-v' || setglobal v = '' git read-tree -m -u $v HEAD HEAD } } rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD" trap - 0