Subject: [PATCH] git-fetch: Split fetch and merge logic

From: Santi Béjar <[email-masked]>
Date: Wed, 07 Mar 2007 13:25:28 +0100

It makes git-parse-remote.sh, git-fetch--tool and almost all git-fetch
independent of the merge logic.

git-fetch fetches the branches from the remote and saves this
information in .git/FETCH_FETCHED (a temporary file), and at the end it
generates the file .git/FETCH_HEAD.

The current merge behaviour is unchanged.

Signed-off-by: Santi Béjar <[email-masked]>
---
Hi *,

 this time the net effect is to add 10 lines :(

 This applies to next.

 Regards,

 Santi

 builtin-fetch--tool.c |   49 ++++++++++--------------------
 git-fetch.sh          |   78 +++++++++++++++++++++++++++++++++++++++++-------
 git-parse-remote.sh   |   65 ++++++++++++-----------------------------
 3 files changed, 101 insertions(+), 91 deletions(-)

diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c
index e9d6764..2cee058 100644
--- a/builtin-fetch--tool.c
+++ b/builtin-fetch--tool.c
@@ -138,8 +138,7 @@ static int update_local_ref(const char *name,
 
 static int append_fetch_head(FILE *fp,
 			     const char *head, const char *remote,
-			     const char *remote_name, const char *remote_nick,
-			     const char *local_name, int not_for_merge,
+			     const char *remote_name, const char *local_name,
 			     int verbose, int force)
 {
 	struct commit *commit;
@@ -151,8 +150,6 @@ static int append_fetch_head(FILE *fp,
 	if (get_sha1(head, sha1))
 		return error("Not a valid object name: %s", head);
 	commit = lookup_commit_reference(sha1);
-	if (!commit)
-		not_for_merge = 1;
 
 	if (!strcmp(remote_name, "HEAD")) {
 		kind = "";
@@ -189,9 +186,9 @@ static int append_fetch_head(FILE *fp,
 		note_len += sprintf(note + note_len, "'%s' of ", what);
 	}
 	note_len += sprintf(note + note_len, "%.*s", remote_len, remote);
-	fprintf(fp, "%s\t%s\t%s\n",
+	fprintf(fp, "%s\t%s:%s\t%s\n",
 		sha1_to_hex(commit ? commit->object.sha1 : sha1),
-		not_for_merge ? "not-for-merge" : "",
+		remote_name, local_name,
 		note);
 	return update_local_ref(local_name, head, note, verbose, force);
 }
@@ -211,14 +208,14 @@ static void remove_keep_on_signal(int signo)
 }
 
 static char *find_local_name(const char *remote_name, const char *refs,
-			     int *force_p, int *not_for_merge_p)
+			     int *force_p)
 {
 	const char *ref = refs;
 	int len = strlen(remote_name);
 
 	while (ref) {
 		const char *next;
-		int single_force, not_for_merge;
+		int single_force;
 
 		while (*ref == '\n')
 			ref++;
@@ -226,19 +223,11 @@ static char *find_local_name(const char *remote_name, const char *refs,
 			break;
 		next = strchr(ref, '\n');
 
-		single_force = not_for_merge = 0;
+		single_force = 0;
 		if (*ref == '+') {
 			single_force = 1;
 			ref++;
 		}
-		if (*ref == '.') {
-			not_for_merge = 1;
-			ref++;
-			if (*ref == '+') {
-				single_force = 1;
-				ref++;
-			}
-		}
 		if (!strncmp(remote_name, ref, len) && ref[len] == ':') {
 			const char *local_part = ref + len + 1;
 			char *ret;
@@ -252,7 +241,6 @@ static char *find_local_name(const char *remote_name, const char *refs,
 			memcpy(ret, local_part, retlen);
 			ret[retlen] = 0;
 			*force_p = single_force;
-			*not_for_merge_p = not_for_merge;
 			return ret;
 		}
 		ref = next;
@@ -262,7 +250,6 @@ static char *find_local_name(const char *remote_name, const char *refs,
 
 static int fetch_native_store(FILE *fp,
 			      const char *remote,
-			      const char *remote_nick,
 			      const char *refs,
 			      int verbose, int force)
 {
@@ -276,7 +263,7 @@ static int fetch_native_store(FILE *fp,
 		int len;
 		char *cp;
 		char *local_name;
-		int single_force, not_for_merge;
+		int single_force;
 
 		for (cp = buffer; *cp && !isspace(*cp); cp++)
 			;
@@ -298,12 +285,11 @@ static int fetch_native_store(FILE *fp,
 		}
 
 		local_name = find_local_name(cp, refs,
-					     &single_force, &not_for_merge);
+					     &single_force);
 		if (!local_name)
 			continue;
 		err |= append_fetch_head(fp,
-					 buffer, remote, cp, remote_nick,
-					 local_name, not_for_merge,
+					 buffer, remote, cp, local_name,
 					 verbose, force || single_force);
 	}
 	return err;
@@ -336,8 +322,6 @@ static int parse_reflist(const char *reflist)
 			break;
 		for (next = ref; *next && !isspace(*next); next++)
 			;
-		if (*ref == '.')
-			ref++;
 		if (*ref == '+')
 			ref++;
 		colon = strchr(ref, ':');
@@ -460,12 +444,11 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
 		int result;
 		FILE *fp;
 
-		if (argc != 8)
-			return error("append-fetch-head takes 6 args");
-		fp = fopen(git_path("FETCH_HEAD"), "a");
+		if (argc != 6)
+			return error("append-fetch-head takes 4 args");
+		fp = fopen(git_path("FETCH_FETCHED"), "a");
 		result = append_fetch_head(fp, argv[2], argv[3],
 					   argv[4], argv[5],
-					   argv[6], !!argv[7][0],
 					   verbose, force);
 		fclose(fp);
 		return result;
@@ -474,10 +457,10 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
 		int result;
 		FILE *fp;
 
-		if (argc != 5)
-			return error("fetch-native-store takes 3 args");
-		fp = fopen(git_path("FETCH_HEAD"), "a");
-		result = fetch_native_store(fp, argv[2], argv[3], argv[4],
+		if (argc != 4)
+			return error("fetch-native-store takes 2 args");
+		fp = fopen(git_path("FETCH_FETCHED"), "a");
+		result = fetch_native_store(fp, argv[2], argv[3],
 					    verbose, force);
 		fclose(fp);
 		return result;
diff --git a/git-fetch.sh b/git-fetch.sh
index 9d45dd2..58cbef1 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -79,9 +79,9 @@ do
 	shift
 done
 
+origin=$(get_default_remote)
 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 ;;
@@ -103,6 +103,8 @@ if test "" = "$append"
 then
 	: >"$GIT_DIR/FETCH_HEAD"
 fi
+: >"$GIT_DIR/FETCH_FETCHED"
+trap 'rm -f "$GIT_DIR"/FETCH_FETCHED' 0 1 2 3 15
 
 # Global that is reused later
 ls_remote_result=$(git ls-remote $exec "$remote") ||
@@ -145,7 +147,7 @@ then
 		  git-show-ref --exclude-existing=refs/tags/ |
 	          while read sha1 name
 		  do
-			echo ".${name}:${name}"
+			echo "${name}:${name}"
 		  done` || exit
 	if test "$#" -gt 1
 	then
@@ -182,7 +184,7 @@ fetch_native () {
 	test -n "$force" && flags="$flags -f"
 	GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \
 		git-fetch--tool $flags native-store \
-			"$remote" "$remote_nick" "$refs"
+			"$remote" "$refs"
       )
     ) || exit
 
@@ -199,13 +201,6 @@ fetch_dumb () {
 
       # 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
@@ -283,7 +278,7 @@ fetch_dumb () {
       esac
 
       append_fetch_head "$head" "$remote" \
-	  "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" || exit
+	  "$remote_name" "$remote_nick" "$local_name" || exit
 
   done
 
@@ -316,7 +311,7 @@ case "$no_tags$tags" in
 		do
 			git-cat-file -t "$sha1" >/dev/null 2>&1 || continue
 			echo >&2 "Auto-following $name"
-			echo ".${name}:${name}"
+			echo "${name}:${name}"
 		done)
 	esac
 	case "$taglist" in
@@ -344,3 +339,62 @@ case "$orig_head" in
 	fi
 	;;
 esac
+# Generate $GIT_DIR/FETCH_HEAD
+case ",$#,$remote_nick," in
+,1,$origin,)
+	# Fetch default: merge the branches in branch.*.merge that match
+	#                the fetched ones or the first one
+	curr_branch=$(git-symbolic-ref -q HEAD | sed -e 's|^refs/heads/||')
+	merge_branches=$(git-config \
+		--get-all "branch.${curr_branch}.merge" | sort -u)
+	fetch_branches=$(get_remote_default_refs_for_fetch -n $remote_nick |
+		sed -e 's/:.*$//g' -e 's/^+//' | sort -u)
+	test -n "$merge_branches" && test -n "$fetch_branches" &&
+	merge_branches=$(printf '%s\n%s' "$merge_branches" "$fetch_branches" |
+		sort | uniq -d)
+
+	[ -z "$merge_branches" ] ||
+	test "$(get_data_source $remote_nick)" = branches && merge_first=yes;;
+,1,$remote,)
+	# Remote is a URL, merge the default branch HEAD
+	merge_branches=HEAD;;
+,1,*)
+	# Remote is a shorthand diferent from the default,
+	# merge the first fetched branch
+	merge_first=yes ;;
+*)
+	# Merge the branches given in the command line
+	merge_branches=$(for ref in $(get_remote_refs_for_fetch "$@") ; do
+		expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:"
+		echo "$(expr "z$ref" : 'z\([^:]*\):')"; done);;
+esac
+
+if test "$merge_first" = "yes" ; then
+    merge_branches=
+    # Do not merge the first branch if it was specified with a glob
+    test "$(get_remote_default_refs_for_fetch -t $remote_nick)" != "explicit" &&
+    merge_first=
+else
+    merge_branches=$(canon_refs_list_for_fetch $merge_branches | sed 's/:.*$//g')
+fi
+
+while IFS='	' read hash ref note ; do
+	# 2.6.11-tree tag would not be happy to be fed to resolve.
+	if git-cat-file commit "$hash" >/dev/null 2>&1
+	then
+		hashc=$(git-rev-parse --verify "$hash^0") || exit
+		remote_branch=$(expr "z$ref" : 'z\([^:]*\):')
+		for merge_branch in $merge_branches ; do
+			[ "$merge_branch" = "$remote_branch" ] &&
+			echo "$hashc		$note" && continue 2
+		done
+	else
+		echo "$hash	not-for-merge	$note" && continue
+	fi
+	if ! test "$merge_first" || test "$merge_first" = "done" ; then
+		echo "$hash	not-for-merge	$note"
+	else
+		echo "$hash		$note"
+		merge_first=done
+	fi
+done < "$GIT_DIR"/FETCH_FETCHED >> "$GIT_DIR/FETCH_HEAD"
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
index c46131f..99e7184 100755
--- a/git-parse-remote.sh
+++ b/git-parse-remote.sh
@@ -70,8 +70,7 @@ get_remote_default_refs_for_push () {
 	esac
 }
 
-# Called from canon_refs_list_for_fetch -d "$remote", which
-# is called from get_remote_default_refs_for_fetch to grok
+# Called from get_remote_default_refs_for_fetch to grok
 # refspecs that are retrieved from the configuration, but not
 # from get_remote_refs_for_fetch when it deals with refspecs
 # supplied on the command line.  $ls_remote_result has the list
@@ -87,30 +86,6 @@ expand_refs_wildcard () {
 
 # Subroutine to canonicalize remote:local notation.
 canon_refs_list_for_fetch () {
-	# If called from get_remote_default_refs_for_fetch
-	# leave the branches in branch.${curr_branch}.merge alone,
-	# or the first one otherwise; add prefix . to the rest
-	# to prevent the secondary branches to be merged by default.
-	merge_branches=
-	curr_branch=
-	if test "$1" = "-d"
-	then
-		shift ; remote="$1" ; shift
-		set $(expand_refs_wildcard "$remote" "$@")
-		is_explicit="$1"
-		shift
-		if test "$remote" = "$(get_default_remote)"
-		then
-			curr_branch=$(git-symbolic-ref -q HEAD | \
-			    sed -e 's|^refs/heads/||')
-			merge_branches=$(git-config \
-			    --get-all "branch.${curr_branch}.merge")
-		fi
-		if test -z "$merge_branches" && test $is_explicit != explicit
-		then
-			merge_branches=..this.will.never.match.any.ref..
-		fi
-	fi
 	for ref
 	do
 		force=
@@ -123,18 +98,6 @@ canon_refs_list_for_fetch () {
 		expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:"
 		remote=$(expr "z$ref" : 'z\([^:]*\):')
 		local=$(expr "z$ref" : 'z[^:]*:\(.*\)')
-		dot_prefix=.
-		if test -z "$merge_branches"
-		then
-			merge_branches=$remote
-			dot_prefix=
-		else
-			for merge_branch in $merge_branches
-			do
-			    [ "$remote" = "$merge_branch" ] &&
-			    dot_prefix= && break
-			done
-		fi
 		case "$remote" in
 		'' | HEAD ) remote=HEAD ;;
 		refs/heads/* | refs/tags/* | refs/remotes/*) ;;
@@ -153,32 +116,42 @@ canon_refs_list_for_fetch () {
 		   git-check-ref-format "$local_ref_name" ||
 		   die "* refusing to create funny ref '$local_ref_name' locally"
 		fi
-		echo "${dot_prefix}${force}${remote}:${local}"
+		echo "${force}${remote}:${local}"
 	done
 }
 
 # Returns list of src: (no store), or src:dst (store)
 get_remote_default_refs_for_fetch () {
+	test "$1" = -t && type=yes && shift
+	test "$1" = -n && canon=no && shift
 	data_source=$(get_data_source "$1")
 	case "$data_source" in
 	'')
-		echo "HEAD:" ;;
+		set explicit "HEAD:" ;;
 	config)
-		canon_refs_list_for_fetch -d "$1" \
-			$(git-config --get-all "remote.$1.fetch") ;;
+		set $(expand_refs_wildcard "$1" \
+			$(git-config --get-all "remote.$1.fetch")) ;;
 	branches)
 		remote_branch=$(sed -ne '/#/s/.*#//p' "$GIT_DIR/branches/$1")
 		case "$remote_branch" in '') remote_branch=master ;; esac
-		echo "refs/heads/${remote_branch}:refs/heads/$1"
+		set explicit "refs/heads/${remote_branch}:refs/heads/$1"
 		;;
 	remotes)
-		canon_refs_list_for_fetch -d "$1" $(sed -ne '/^Pull: */{
-						s///p
-					}' "$GIT_DIR/remotes/$1")
+		set $(expand_refs_wildcard "$1" $(sed -ne '/^Pull: */s///p'\
+					"$GIT_DIR/remotes/$1"))
 		;;
 	*)
 		die "internal error: get-remote-default-ref-for-push $1" ;;
 	esac
+	if test "$type" = yes ; then
+		echo $1
+	elif test "$canon" = no ; then
+		shift
+		for ref ; do echo "$ref" ; done
+	else
+		shift
+		canon_refs_list_for_fetch "$@"
+	fi
 }
 
 get_remote_refs_for_push () {
-- 
1.5.0.3.897.g91a70-dirty