e3c6f240fd
When we are fetching from a repository that is on a local filesystem, first check if we have all the objects that we are going to fetch available locally, by not just checking the tips of what we are fetching, but with a full reachability analysis to our existing refs. In such a case, we do not have to run git-fetch-pack which would send many needless objects. This is especially true when the other repository is an alternate of the current repository (e.g. perhaps the repository was created by running "git clone -l -s" from there). The useless objects transferred used to be discarded when they were expanded by git-unpack-objects called from git-fetch-pack, but recent git-fetch-pack prefers to keep the data it receives from the other end without exploding them into loose objects, resulting in a pack full of duplicated data when fetching from your own alternate. This also uses fetch--tool pick-rref on dumb transport side to remove a shell loop to do the same. Signed-off-by: Junio C Hamano <junkio@cox.net>
371 lines
8.8 KiB
Bash
Executable File
371 lines
8.8 KiB
Bash
Executable File
#!/bin/sh
|
|
#
|
|
|
|
USAGE='<fetch-options> <repository> <refspec>...'
|
|
SUBDIRECTORY_OK=Yes
|
|
. git-sh-setup
|
|
set_reflog_action "fetch $*"
|
|
cd_to_toplevel ;# probably unnecessary...
|
|
|
|
. git-parse-remote
|
|
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
|
|
_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
|
|
|
|
LF='
|
|
'
|
|
IFS="$LF"
|
|
|
|
no_tags=
|
|
tags=
|
|
append=
|
|
force=
|
|
verbose=
|
|
update_head_ok=
|
|
exec=
|
|
keep=
|
|
shallow_depth=
|
|
no_progress=
|
|
test -t 1 || no_progress=--no-progress
|
|
quiet=
|
|
while case "$#" in 0) break ;; esac
|
|
do
|
|
case "$1" in
|
|
-a|--a|--ap|--app|--appe|--appen|--append)
|
|
append=t
|
|
;;
|
|
--upl|--uplo|--uploa|--upload|--upload-|--upload-p|\
|
|
--upload-pa|--upload-pac|--upload-pack)
|
|
shift
|
|
exec="--upload-pack=$1"
|
|
;;
|
|
--upl=*|--uplo=*|--uploa=*|--upload=*|\
|
|
--upload-=*|--upload-p=*|--upload-pa=*|--upload-pac=*|--upload-pack=*)
|
|
exec=--upload-pack=$(expr "z$1" : 'z-[^=]*=\(.*\)')
|
|
shift
|
|
;;
|
|
-f|--f|--fo|--for|--forc|--force)
|
|
force=t
|
|
;;
|
|
-t|--t|--ta|--tag|--tags)
|
|
tags=t
|
|
;;
|
|
-n|--n|--no|--no-|--no-t|--no-ta|--no-tag|--no-tags)
|
|
no_tags=t
|
|
;;
|
|
-u|--u|--up|--upd|--upda|--updat|--update|--update-|--update-h|\
|
|
--update-he|--update-hea|--update-head|--update-head-|\
|
|
--update-head-o|--update-head-ok)
|
|
update_head_ok=t
|
|
;;
|
|
-q|--q|--qu|--qui|--quie|--quiet)
|
|
quiet=--quiet
|
|
;;
|
|
-v|--verbose)
|
|
verbose=Yes
|
|
;;
|
|
-k|--k|--ke|--kee|--keep)
|
|
keep='-k -k'
|
|
;;
|
|
--depth=*)
|
|
shallow_depth="--depth=`expr "z$1" : 'z-[^=]*=\(.*\)'`"
|
|
;;
|
|
--depth)
|
|
shift
|
|
shallow_depth="--depth=$1"
|
|
;;
|
|
-*)
|
|
usage
|
|
;;
|
|
*)
|
|
break
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
case "$#" in
|
|
0)
|
|
origin=$(get_default_remote)
|
|
test -n "$(get_remote_url ${origin})" ||
|
|
die "Where do you want to fetch from today?"
|
|
set x $origin ; shift ;;
|
|
esac
|
|
|
|
if test -z "$exec"
|
|
then
|
|
# No command line override and we have configuration for the remote.
|
|
exec="--upload-pack=$(get_uploadpack $1)"
|
|
fi
|
|
|
|
remote_nick="$1"
|
|
remote=$(get_remote_url "$@")
|
|
refs=
|
|
rref=
|
|
rsync_slurped_objects=
|
|
|
|
if test "" = "$append"
|
|
then
|
|
: >"$GIT_DIR/FETCH_HEAD"
|
|
fi
|
|
|
|
# Global that is reused later
|
|
ls_remote_result=$(git ls-remote $exec "$remote") ||
|
|
die "Cannot get the repository state from $remote"
|
|
|
|
append_fetch_head () {
|
|
flags=
|
|
test -n "$verbose" && flags="$flags$LF-v"
|
|
test -n "$force$single_force" && flags="$flags$LF-f"
|
|
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \
|
|
git-fetch--tool $flags append-fetch-head "$@"
|
|
}
|
|
|
|
# updating the current HEAD with git-fetch in a bare
|
|
# repository is always fine.
|
|
if test -z "$update_head_ok" && test $(is_bare_repository) = false
|
|
then
|
|
orig_head=$(git-rev-parse --verify HEAD 2>/dev/null)
|
|
fi
|
|
|
|
# Allow --notags from remote.$1.tagopt
|
|
case "$tags$no_tags" in
|
|
'')
|
|
case "$(git-config --get "remote.$1.tagopt")" in
|
|
--no-tags)
|
|
no_tags=t ;;
|
|
esac
|
|
esac
|
|
|
|
# If --tags (and later --heads or --all) is specified, then we are
|
|
# not talking about defaults stored in Pull: line of remotes or
|
|
# branches file, and just fetch those and refspecs explicitly given.
|
|
# Otherwise we do what we always did.
|
|
|
|
reflist=$(get_remote_refs_for_fetch "$@")
|
|
if test "$tags"
|
|
then
|
|
taglist=`IFS=' ' &&
|
|
echo "$ls_remote_result" |
|
|
git-show-ref --exclude-existing=refs/tags/ |
|
|
while read sha1 name
|
|
do
|
|
echo ".${name}:${name}"
|
|
done` || exit
|
|
if test "$#" -gt 1
|
|
then
|
|
# remote URL plus explicit refspecs; we need to merge them.
|
|
reflist="$reflist$LF$taglist"
|
|
else
|
|
# No explicit refspecs; fetch tags only.
|
|
reflist=$taglist
|
|
fi
|
|
fi
|
|
|
|
fetch_all_at_once () {
|
|
|
|
eval=$(echo "$1" | git-fetch--tool parse-reflist "-")
|
|
eval "$eval"
|
|
|
|
( : subshell because we muck with IFS
|
|
IFS=" $LF"
|
|
(
|
|
if test "$remote" = . ; then
|
|
git-show-ref $rref || echo failed "$remote"
|
|
elif test -f "$remote" ; then
|
|
test -n "$shallow_depth" &&
|
|
die "shallow clone with bundle is not supported"
|
|
git-bundle unbundle "$remote" $rref ||
|
|
echo failed "$remote"
|
|
else
|
|
if test -d "$remote" &&
|
|
|
|
# The remote might be our alternate. With
|
|
# this optimization we will bypass fetch-pack
|
|
# altogether, which means we cannot be doing
|
|
# the shallow stuff at all.
|
|
test ! -f "$GIT_DIR/shallow" &&
|
|
test -z "$shallow_depth" &&
|
|
|
|
# See if all of what we are going to fetch are
|
|
# connected to our repository's tips, in which
|
|
# case we do not have to do any fetch.
|
|
theirs=$(git-fetch--tool -s pick-rref \
|
|
"$rref" "$ls_remote_result") &&
|
|
|
|
# This will barf when $theirs reach an object that
|
|
# we do not have in our repository. Otherwise,
|
|
# we already have everything the fetch would bring in.
|
|
git-rev-list --objects $theirs --not --all \
|
|
>/dev/null 2>/dev/null
|
|
then
|
|
git-fetch--tool pick-rref "$rref" "$ls_remote_result"
|
|
else
|
|
git-fetch-pack --thin $exec $keep $shallow_depth \
|
|
$quiet $no_progress "$remote" $rref ||
|
|
echo failed "$remote"
|
|
fi
|
|
fi
|
|
) |
|
|
(
|
|
flags=
|
|
test -n "$verbose" && flags="$flags -v"
|
|
test -n "$force" && flags="$flags -f"
|
|
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \
|
|
git-fetch--tool $flags native-store \
|
|
"$remote" "$remote_nick" "$refs"
|
|
)
|
|
) || exit
|
|
|
|
}
|
|
|
|
fetch_per_ref () {
|
|
reflist="$1"
|
|
refs=
|
|
rref=
|
|
|
|
for ref in $reflist
|
|
do
|
|
refs="$refs$LF$ref"
|
|
|
|
# These are relative path from $GIT_DIR, typically starting at refs/
|
|
# but may be HEAD
|
|
if expr "z$ref" : 'z\.' >/dev/null
|
|
then
|
|
not_for_merge=t
|
|
ref=$(expr "z$ref" : 'z\.\(.*\)')
|
|
else
|
|
not_for_merge=
|
|
fi
|
|
if expr "z$ref" : 'z+' >/dev/null
|
|
then
|
|
single_force=t
|
|
ref=$(expr "z$ref" : 'z+\(.*\)')
|
|
else
|
|
single_force=
|
|
fi
|
|
remote_name=$(expr "z$ref" : 'z\([^:]*\):')
|
|
local_name=$(expr "z$ref" : 'z[^:]*:\(.*\)')
|
|
|
|
rref="$rref$LF$remote_name"
|
|
|
|
# There are transports that can fetch only one head at a time...
|
|
case "$remote" in
|
|
http://* | https://* | ftp://*)
|
|
test -n "$shallow_depth" &&
|
|
die "shallow clone with http not supported"
|
|
proto=`expr "$remote" : '\([^:]*\):'`
|
|
if [ -n "$GIT_SSL_NO_VERIFY" ]; then
|
|
curl_extra_args="-k"
|
|
fi
|
|
if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
|
|
"`git-config --bool http.noEPSV`" = true ]; then
|
|
noepsv_opt="--disable-epsv"
|
|
fi
|
|
|
|
# Find $remote_name from ls-remote output.
|
|
head=$(git-fetch--tool -s pick-rref \
|
|
"$remote_name" "$ls_remote_result")
|
|
expr "z$head" : "z$_x40\$" >/dev/null ||
|
|
die "No such ref $remote_name at $remote"
|
|
echo >&2 "Fetching $remote_name from $remote using $proto"
|
|
case "$quiet" in '') v=-v ;; *) v= ;; esac
|
|
git-http-fetch $v -a "$head" "$remote" || exit
|
|
;;
|
|
rsync://*)
|
|
test -n "$shallow_depth" &&
|
|
die "shallow clone with rsync not supported"
|
|
TMP_HEAD="$GIT_DIR/TMP_HEAD"
|
|
rsync -L -q "$remote/$remote_name" "$TMP_HEAD" || exit 1
|
|
head=$(git-rev-parse --verify TMP_HEAD)
|
|
rm -f "$TMP_HEAD"
|
|
case "$quiet" in '') v=-v ;; *) v= ;; esac
|
|
test "$rsync_slurped_objects" || {
|
|
rsync -a $v --ignore-existing --exclude info \
|
|
"$remote/objects/" "$GIT_OBJECT_DIRECTORY/" || 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 "$remote/objects/info/alternates" \
|
|
"$GIT_DIR/TMP_ALT" 2>/dev/null ||
|
|
rm -f "$GIT_DIR/TMP_ALT"
|
|
if test -f "$GIT_DIR/TMP_ALT"
|
|
then
|
|
resolve_alternates "$remote" <"$GIT_DIR/TMP_ALT" |
|
|
while read alt
|
|
do
|
|
case "$alt" in 'bad alternate: '*) die "$alt";; esac
|
|
echo >&2 "Getting alternate: $alt"
|
|
rsync -av --ignore-existing --exclude info \
|
|
"$alt" "$GIT_OBJECT_DIRECTORY/" || exit
|
|
done
|
|
rm -f "$GIT_DIR/TMP_ALT"
|
|
fi
|
|
rsync_slurped_objects=t
|
|
}
|
|
;;
|
|
esac
|
|
|
|
append_fetch_head "$head" "$remote" \
|
|
"$remote_name" "$remote_nick" "$local_name" "$not_for_merge" || exit
|
|
|
|
done
|
|
|
|
}
|
|
|
|
fetch_main () {
|
|
case "$remote" in
|
|
http://* | https://* | ftp://* | rsync://* )
|
|
fetch_per_ref "$@"
|
|
;;
|
|
*)
|
|
fetch_all_at_once "$@"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
fetch_main "$reflist" || exit
|
|
|
|
# automated tag following
|
|
case "$no_tags$tags" in
|
|
'')
|
|
case "$reflist" in
|
|
*:refs/*)
|
|
# effective only when we are following remote branch
|
|
# using local tracking branch.
|
|
taglist=$(IFS=' ' &&
|
|
echo "$ls_remote_result" |
|
|
git-show-ref --exclude-existing=refs/tags/ |
|
|
while read sha1 name
|
|
do
|
|
git-cat-file -t "$sha1" >/dev/null 2>&1 || continue
|
|
echo >&2 "Auto-following $name"
|
|
echo ".${name}:${name}"
|
|
done)
|
|
esac
|
|
case "$taglist" in
|
|
'') ;;
|
|
?*)
|
|
# do not deepen a shallow tree when following tags
|
|
shallow_depth=
|
|
fetch_main "$taglist" || exit ;;
|
|
esac
|
|
esac
|
|
|
|
# If the original head was empty (i.e. no "master" yet), or
|
|
# if we were told not to worry, we do not have to check.
|
|
case "$orig_head" in
|
|
'')
|
|
;;
|
|
?*)
|
|
curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
|
|
if test "$curr_head" != "$orig_head"
|
|
then
|
|
git-update-ref \
|
|
-m "$GIT_REFLOG_ACTION: Undoing incorrectly fetched HEAD." \
|
|
HEAD "$orig_head"
|
|
die "Cannot fetch into the current branch."
|
|
fi
|
|
;;
|
|
esac
|