mirror of
https://github.com/git/git
synced 2024-08-25 10:45:59 +00:00
![Mike Hommey](/assets/img/avatar_default.png)
A typical remote helper will return a `list` of refs containing a symbolic ref HEAD, pointing to, e.g. refs/heads/master. In the case of a clone, all the refs are being requested through `fetch` or `import`, including the symbolic ref. While this works properly, in some cases of a fetch, like `git fetch url` or `git fetch origin HEAD`, or any fetch command involving a symbolic ref without also fetching the corresponding ref it points to, the fetch command fails with: fatal: bad object 0000000000000000000000000000000000000000 error: <remote> did not send all necessary objects (in the case the remote helper returned '?' values to the `list` command). This is because there is only one ref given to fetch(), and it's not further resolved to something at the end of fetch_with_import(). While this can be somehow handled in the remote helper itself, by adding a refspec for the symbolic ref, and storing an explicit ref in a private namespace, and then handling the `import` for that symbolic ref specifically, very few existing remote helpers are actually doing that. So, instead of requesting the exact list of wanted refs to remote helpers, treat symbolic refs differently and request the ref they point to instead. Then, resolve the symbolic refs values based on the pointed ref. This assumes there is no more than one level of indirection (a symbolic ref doesn't point to another symbolic ref). Signed-off-by: Mike Hommey <mh@glandium.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
148 lines
2.9 KiB
Bash
Executable file
148 lines
2.9 KiB
Bash
Executable file
#!/bin/sh
|
|
# Copyright (c) 2012 Felipe Contreras
|
|
|
|
# The first argument can be a url when the fetch/push command was a url
|
|
# instead of a configured remote. In this case, use a generic alias.
|
|
if test "$1" = "testgit::$2"; then
|
|
alias=_
|
|
else
|
|
alias=$1
|
|
fi
|
|
url=$2
|
|
|
|
dir="$GIT_DIR/testgit/$alias"
|
|
prefix="refs/testgit/$alias"
|
|
|
|
default_refspec="refs/heads/*:${prefix}/heads/*"
|
|
|
|
refspec="${GIT_REMOTE_TESTGIT_REFSPEC-$default_refspec}"
|
|
|
|
test -z "$refspec" && prefix="refs"
|
|
|
|
GIT_DIR="$url/.git"
|
|
export GIT_DIR
|
|
|
|
force=
|
|
|
|
mkdir -p "$dir"
|
|
|
|
if test -z "$GIT_REMOTE_TESTGIT_NO_MARKS"
|
|
then
|
|
gitmarks="$dir/git.marks"
|
|
testgitmarks="$dir/testgit.marks"
|
|
test -e "$gitmarks" || >"$gitmarks"
|
|
test -e "$testgitmarks" || >"$testgitmarks"
|
|
fi
|
|
|
|
while read line
|
|
do
|
|
case $line in
|
|
capabilities)
|
|
echo 'import'
|
|
echo 'export'
|
|
test -n "$refspec" && echo "refspec $refspec"
|
|
if test -n "$gitmarks"
|
|
then
|
|
echo "*import-marks $gitmarks"
|
|
echo "*export-marks $gitmarks"
|
|
fi
|
|
test -n "$GIT_REMOTE_TESTGIT_SIGNED_TAGS" && echo "signed-tags"
|
|
test -n "$GIT_REMOTE_TESTGIT_NO_PRIVATE_UPDATE" && echo "no-private-update"
|
|
echo 'option'
|
|
echo
|
|
;;
|
|
list)
|
|
git for-each-ref --format='? %(refname)' 'refs/heads/'
|
|
head=$(git symbolic-ref HEAD)
|
|
echo "@$head HEAD"
|
|
echo
|
|
;;
|
|
import*)
|
|
# read all import lines
|
|
while true
|
|
do
|
|
ref="${line#* }"
|
|
refs="$refs $ref"
|
|
read line
|
|
test "${line%% *}" != "import" && break
|
|
done
|
|
|
|
if test -n "$gitmarks"
|
|
then
|
|
echo "feature import-marks=$gitmarks"
|
|
echo "feature export-marks=$gitmarks"
|
|
fi
|
|
|
|
if test -n "$GIT_REMOTE_TESTGIT_FAILURE"
|
|
then
|
|
echo "feature done"
|
|
exit 1
|
|
fi
|
|
|
|
echo "feature done"
|
|
git fast-export \
|
|
${testgitmarks:+"--import-marks=$testgitmarks"} \
|
|
${testgitmarks:+"--export-marks=$testgitmarks"} \
|
|
$refs |
|
|
sed -e "s#refs/heads/#${prefix}/heads/#g"
|
|
echo "done"
|
|
;;
|
|
export)
|
|
if test -n "$GIT_REMOTE_TESTGIT_FAILURE"
|
|
then
|
|
# consume input so fast-export doesn't get SIGPIPE;
|
|
# git would also notice that case, but we want
|
|
# to make sure we are exercising the later
|
|
# error checks
|
|
while read line; do
|
|
test "done" = "$line" && break
|
|
done
|
|
exit 1
|
|
fi
|
|
|
|
before=$(git for-each-ref --format=' %(refname) %(objectname) ')
|
|
|
|
git fast-import \
|
|
${force:+--force} \
|
|
${testgitmarks:+"--import-marks=$testgitmarks"} \
|
|
${testgitmarks:+"--export-marks=$testgitmarks"} \
|
|
--quiet
|
|
|
|
# figure out which refs were updated
|
|
git for-each-ref --format='%(refname) %(objectname)' |
|
|
while read ref a
|
|
do
|
|
case "$before" in
|
|
*" $ref $a "*)
|
|
continue ;; # unchanged
|
|
esac
|
|
if test -z "$GIT_REMOTE_TESTGIT_PUSH_ERROR"
|
|
then
|
|
echo "ok $ref"
|
|
else
|
|
echo "error $ref $GIT_REMOTE_TESTGIT_PUSH_ERROR"
|
|
fi
|
|
done
|
|
|
|
echo
|
|
;;
|
|
option\ *)
|
|
read cmd opt val <<-EOF
|
|
$line
|
|
EOF
|
|
case $opt in
|
|
force)
|
|
test $val = "true" && force="true" || force=
|
|
echo "ok"
|
|
;;
|
|
*)
|
|
echo "unsupported"
|
|
;;
|
|
esac
|
|
;;
|
|
'')
|
|
exit
|
|
;;
|
|
esac
|
|
done
|