Skip to content

Commit 853a369

Browse files
author
Junio C Hamano
committed
[PATCH] Multi-head fetch.
Traditionally, fetch takes these forms: $ git fetch <remote> $ git fetch <remote> <head> $ git fetch <remote> tag <tag> This patch updates it to take $ git fetch <remote> <refspec>... where: - A <refspec> of form "<src>:<dst>" is to fetch the objects needed for the remote ref that matches <src>, and if <dst> is not empty, store it as a local <dst>. - "tag" followed by <next> is just an old way of saying "refs/tags/<next>:refs/tags/<next>"; this mimics the current behaviour of the third form above and means "fetch that tag and store it under the same name". - A single token <refspec> without colon is a shorthand for "<refspec>:" That is, "fetch that ref but do not store anywhere". - when there is no <refspec> specified - if <remote> is the name of a file under $GIT_DIR/remotes/ (i.e. a new-style shorthand), then it is the same as giving the <refspec>s listed on Pull: line in that file. - if <remote> is the name of a file under $GIT_DIR/branches/ (i.e. an old-style shorthand, without trailing path), then it is the same as giving a single <refspec> "<remote-name>:refs/heads/<remote>" on the command line, where <remote-name> is the remote branch name (defaults to HEAD, but can be overridden by .git/branches/<remote> file having the URL fragment notation). That is, "fetch that branch head and store it in refs/heads/<remote>". - otherwise, it is the same as giving a single <refspec> that is "HEAD:". The SHA1 object names of fetched refs are stored in FETCH_HEAD, one name per line, with a comment to describe where it came from. This is later used by "git resolve" and "git octopus". Signed-off-by: Junio C Hamano <junkio@cox.net>
1 parent ac4b0cf commit 853a369

File tree

1 file changed

+147
-42
lines changed

1 file changed

+147
-42
lines changed

git-fetch-script

+147-42
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,159 @@
11
#!/bin/sh
22
#
33
. git-sh-setup-script || die "Not a git archive"
4-
. git-parse-remote "$@"
5-
merge_repo="$_remote_repo"
6-
merge_head="$_remote_head"
7-
merge_store="$_remote_store"
8-
9-
TMP_HEAD="$GIT_DIR/TMP_HEAD"
10-
11-
case "$merge_repo" in
12-
http://* | https://*)
13-
if [ -n "$GIT_SSL_NO_VERIFY" ]; then
14-
curl_extra_args="-k"
15-
fi
16-
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' &&
17-
_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" &&
18-
head=$(curl -nsf $curl_extra_args "$merge_repo/$merge_head") &&
19-
expr "$head" : "$_x40\$" >/dev/null || {
20-
echo >&2 "Failed to fetch $merge_head from $merge_repo"
21-
exit 1
22-
}
23-
echo Fetching "$merge_head" using http
24-
git-http-pull -v -a "$head" "$merge_repo/" || exit
25-
;;
26-
rsync://*)
27-
rsync -L "$merge_repo/$merge_head" "$TMP_HEAD" || exit 1
28-
head=$(git-rev-parse TMP_HEAD)
29-
rm -f "$TMP_HEAD"
30-
rsync -avz --ignore-existing "$merge_repo/objects/" "$GIT_OBJECT_DIRECTORY/"
31-
;;
4+
. git-parse-remote-script
5+
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
6+
_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
7+
8+
append=
9+
case "$#" in
10+
0)
11+
die "Where do you want to fetch from?" ;;
3212
*)
33-
head=$(git-fetch-pack "$merge_repo" "$merge_head")
34-
if h=`expr "$head" : '\([^ ][^ ]*\) '`
13+
case "$1" in
14+
-a|--a|--ap|--app|--appe|--appen|--append)
15+
append=t
16+
shift ;;
17+
esac
18+
esac
19+
remote_nick="$1"
20+
remote=$(get_remote_url "$@")
21+
refs=
22+
rref=
23+
rsync_slurped_objects=
24+
25+
if test "" = "$append"
26+
then
27+
: >$GIT_DIR/FETCH_HEAD
28+
fi
29+
30+
append_fetch_head () {
31+
head_="$1"
32+
remote_="$2"
33+
remote_name_="$3"
34+
remote_nick_="$4"
35+
local_name_="$5"
36+
37+
# 2.6.11-tree tag would not be happy to be fed to resolve.
38+
if git-cat-file commit "$head_" >/dev/null 2>&1
39+
then
40+
head_=$(git-rev-parse --verify "$head_^0") || exit
41+
note_="$head_ $remote_name_ from $remote_nick_"
42+
echo "$note_" >>$GIT_DIR/FETCH_HEAD
43+
echo >&2 "* committish: $note_"
44+
else
45+
echo >&2 "* non-commit: $note_"
46+
fi
47+
if test "$local_name_" != ""
48+
then
49+
# We are storing the head locally. Make sure that it is
50+
# a fast forward (aka "reverse push").
51+
fast_forward_local "$local_name_" "$head_" "$remote_" "$remote_name_"
52+
fi
53+
}
54+
55+
fast_forward_local () {
56+
case "$1" in
57+
refs/tags/*)
58+
# Tags need not be pointing at commits so there
59+
# is no way to guarantee "fast-forward" anyway.
60+
echo "$2" >"$GIT_DIR/$1" ;;
61+
refs/heads/*)
62+
# NEEDSWORK: use the same cmpxchg protocol here.
63+
echo "$2" >"$GIT_DIR/$1.lock"
64+
if test -f "$GIT_DIR/$1"
3565
then
36-
head=$h
66+
local=$(git-rev-parse --verify "$1^0") &&
67+
mb=$(git-merge-base "$local" "$2") &&
68+
case "$2,$mb" in
69+
$local,*)
70+
echo >&2 "* $1: same as $4"
71+
echo >&2 " from $3"
72+
;;
73+
*,$local)
74+
echo >&2 "* $1: fast forward to $4"
75+
echo >&2 " from $3"
76+
;;
77+
*)
78+
false
79+
;;
80+
esac || {
81+
mv "$GIT_DIR/$1.lock" "$GIT_DIR/$1.remote"
82+
echo >&2 "* $1: does not fast forward to $4"
83+
echo >&2 " from $3; leaving it in '$1.remote'"
84+
}
85+
else
86+
echo >&2 "* $1: storing $4"
87+
echo >&2 " from $3."
3788
fi
89+
test -f "$GIT_DIR/$1.lock" &&
90+
mv "$GIT_DIR/$1.lock" "$GIT_DIR/$1"
3891
;;
39-
esac || exit 1
92+
esac
93+
}
94+
95+
for ref in $(get_remote_refs_for_fetch "$@")
96+
do
97+
refs="$refs $ref"
4098

41-
git-rev-parse --verify "$head" > /dev/null || exit 1
99+
# These are relative path from $GIT_DIR, typically starting at refs/
100+
# but may be HEAD
101+
remote_name=$(expr "$ref" : '\([^:]*\):')
102+
local_name=$(expr "$ref" : '[^:]*:\(.*\)')
42103

43-
case "$merge_store" in
44-
'')
104+
rref="$rref $remote_name"
105+
106+
# There are transports that can fetch only one head at a time...
107+
case "$remote" in
108+
http://* | https://*)
109+
if [ -n "$GIT_SSL_NO_VERIFY" ]; then
110+
curl_extra_args="-k"
111+
fi
112+
head=$(curl -nsf $curl_extra_args "$remote/$remote_name") &&
113+
expr "$head" : "$_x40\$" >/dev/null ||
114+
die "Failed to fetch $remote_name from $remote"
115+
echo Fetching "$remote_name from $remote" using http
116+
git-http-pull -v -a "$head" "$remote/" || exit
45117
;;
46-
*)
47-
echo "$head" > "$GIT_DIR/$merge_store"
48-
esac &&
118+
rsync://*)
119+
TMP_HEAD="$GIT_DIR/TMP_HEAD"
120+
rsync -L "$remote/$remote_name" "$TMP_HEAD" || exit 1
121+
head=$(git-rev-parse TMP_HEAD)
122+
rm -f "$TMP_HEAD"
123+
test "$rsync_slurped_objects" || {
124+
rsync -avz --ignore-existing "$remote/objects/" \
125+
"$GIT_OBJECT_DIRECTORY/" || exit
126+
rsync_slurped_objects=t
127+
}
128+
;;
129+
*)
130+
# We will do git native transport with just one call later.
131+
continue ;;
132+
esac
133+
134+
append_fetch_head "$head" "$remote" "$remote_name" "$remote_nick" "$local_name"
49135

50-
# FETCH_HEAD is fed to git-resolve-script which will eventually be
51-
# passed to git-commit-tree as one of the parents. Make sure we do
52-
# not give a tag object ID.
136+
done
137+
138+
case "$remote" in
139+
http://* | https://* | rsync://* )
140+
;; # we are already done.
141+
*)
142+
git-fetch-pack "$remote" $rref |
143+
while read sha1 remote_name
144+
do
145+
found=
146+
for ref in $refs
147+
do
148+
case "$ref" in
149+
$remote_name:*)
150+
found="$ref"
151+
break ;;
152+
esac
153+
done
53154

54-
git-rev-parse "$head^0" >"$GIT_DIR/FETCH_HEAD"
155+
local_name=$(expr "$found" : '[^:]*:\(.*\)')
156+
append_fetch_head "$sha1" "$remote" "$remote_name" "$remote_nick" "$local_name"
157+
done
158+
;;
159+
esac

0 commit comments

Comments
 (0)