git-fetch: auto-following tags.

I added things to ls-remote so that Cogito can auto-follow tags
easily and correctly a while ago, but git-fetch did not use the
facility.  Recently added git-describe command relies on
repository keeping up-to-date set of tags, which made it much
more attractive to automatically follow tags, so we do that as
well.

Signed-off-by: Junio C Hamano <junkio@cox.net>
diff --git a/git-fetch.sh b/git-fetch.sh
index b46b3e5..73e57bd 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -11,6 +11,7 @@
 '
 IFS="$LF"
 
+no_tags=
 tags=
 append=
 force=
@@ -28,6 +29,9 @@
 	-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)
@@ -212,133 +216,166 @@
 	fi
 fi
 
-for ref in $reflist
-do
-    refs="$refs$LF$ref"
+fetch_main () {
+  reflist="$1"
+  refs=
 
-    # These are relative path from $GIT_DIR, typically starting at refs/
-    # but may be HEAD
-    if expr "$ref" : '\.' >/dev/null
-    then
-	not_for_merge=t
-	ref=$(expr "$ref" : '\.\(.*\)')
-    else
-	not_for_merge=
-    fi
-    if expr "$ref" : '\+' >/dev/null
-    then
-	single_force=t
-	ref=$(expr "$ref" : '\+\(.*\)')
-    else
-	single_force=
-    fi
-    remote_name=$(expr "$ref" : '\([^:]*\):')
-    local_name=$(expr "$ref" : '[^:]*:\(.*\)')
+  for ref in $reflist
+  do
+      refs="$refs$LF$ref"
 
-    rref="$rref$LF$remote_name"
+      # These are relative path from $GIT_DIR, typically starting at refs/
+      # but may be HEAD
+      if expr "$ref" : '\.' >/dev/null
+      then
+	  not_for_merge=t
+	  ref=$(expr "$ref" : '\.\(.*\)')
+      else
+	  not_for_merge=
+      fi
+      if expr "$ref" : '\+' >/dev/null
+      then
+	  single_force=t
+	  ref=$(expr "$ref" : '\+\(.*\)')
+      else
+	  single_force=
+      fi
+      remote_name=$(expr "$ref" : '\([^:]*\):')
+      local_name=$(expr "$ref" : '[^:]*:\(.*\)')
 
-    # There are transports that can fetch only one head at a time...
-    case "$remote" in
-    http://* | https://*)
-	if [ -n "$GIT_SSL_NO_VERIFY" ]; then
-	    curl_extra_args="-k"
-	fi
-	remote_name_quoted=$(perl -e '
-	    my $u = $ARGV[0];
-	    $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
-	    print "$u";
-	' "$remote_name")
-	head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") &&
-	expr "$head" : "$_x40\$" >/dev/null ||
-		die "Failed to fetch $remote_name from $remote"
-	echo >&2 Fetching "$remote_name from $remote" using http
-	git-http-fetch -v -a "$head" "$remote/" || exit
-	;;
-    rsync://*)
-	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"
-	test "$rsync_slurped_objects" || {
-	    rsync -av --ignore-existing --exclude info \
-		"$remote/objects/" "$GIT_OBJECT_DIRECTORY/" || exit
+      rref="$rref$LF$remote_name"
 
-	    # 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
-	}
-	;;
-    *)
-	# We will do git native transport with just one call later.
-	continue ;;
-    esac
+      # There are transports that can fetch only one head at a time...
+      case "$remote" in
+      http://* | https://*)
+	  if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+	      curl_extra_args="-k"
+	  fi
+	  remote_name_quoted=$(perl -e '
+	      my $u = $ARGV[0];
+	      $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
+	      print "$u";
+	  ' "$remote_name")
+	  head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") &&
+	  expr "$head" : "$_x40\$" >/dev/null ||
+		  die "Failed to fetch $remote_name from $remote"
+	  echo >&2 Fetching "$remote_name from $remote" using http
+	  git-http-fetch -v -a "$head" "$remote/" || exit
+	  ;;
+      rsync://*)
+	  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"
+	  test "$rsync_slurped_objects" || {
+	      rsync -av --ignore-existing --exclude info \
+		  "$remote/objects/" "$GIT_OBJECT_DIRECTORY/" || exit
 
-    append_fetch_head "$head" "$remote" \
-    	"$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
+	      # 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
+	  }
+	  ;;
+      *)
+	  # We will do git native transport with just one call later.
+	  continue ;;
+      esac
 
-done
+      append_fetch_head "$head" "$remote" \
+	  "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
 
-case "$remote" in
-http://* | https://* | rsync://* )
-    ;; # we are already done.
-*)
-    IFS=" 	$LF"
-    (
-	git-fetch-pack "$remote" $rref || echo failed "$remote"
-    ) |
-    while read sha1 remote_name
-    do
-	case "$sha1" in
-	failed)
-		echo >&2 "Fetch failure: $remote"
-		exit 1 ;;
-	esac
-	found=
-	single_force=
-	for ref in $refs
+  done
+
+  case "$remote" in
+  http://* | https://* | rsync://* )
+      ;; # we are already done.
+  *)
+    ( : subshell because we muck with IFS
+      IFS=" 	$LF"
+      (
+	  git-fetch-pack "$remote" $rref || echo failed "$remote"
+      ) |
+      while read sha1 remote_name
+      do
+	  case "$sha1" in
+	  failed)
+		  echo >&2 "Fetch failure: $remote"
+		  exit 1 ;;
+	  esac
+	  found=
+	  single_force=
+	  for ref in $refs
+	  do
+	      case "$ref" in
+	      +$remote_name:*)
+		  single_force=t
+		  not_for_merge=
+		  found="$ref"
+		  break ;;
+	      .+$remote_name:*)
+		  single_force=t
+		  not_for_merge=t
+		  found="$ref"
+		  break ;;
+	      .$remote_name:*)
+		  not_for_merge=t
+		  found="$ref"
+		  break ;;
+	      $remote_name:*)
+		  not_for_merge=
+		  found="$ref"
+		  break ;;
+	      esac
+	  done
+	  local_name=$(expr "$found" : '[^:]*:\(.*\)')
+	  append_fetch_head "$sha1" "$remote" \
+		  "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
+      done
+    ) || exit ;;
+  esac
+
+}
+
+fetch_main "$reflist"
+
+# automated tag following
+case "$no_tags$tags" in
+'')
+	taglist=$(IFS=" " &&
+    	git-ls-remote --tags "$remote" |
+	sed -ne 's|^\([0-9a-f]*\)[ 	]\(refs/tags/.*\)^{}$|\1 \2|p' |
+	while read sha1 name
 	do
-	    case "$ref" in
-	    +$remote_name:*)
-		single_force=t
-		not_for_merge=
-		found="$ref"
-		break ;;
-	    .+$remote_name:*)
-		single_force=t
-		not_for_merge=t
-		found="$ref"
-		break ;;
-	    .$remote_name:*)
-	        not_for_merge=t
-		found="$ref"
-		break ;;
-	    $remote_name:*)
-	    	not_for_merge=
-		found="$ref"
-		break ;;
-	    esac
-	done
-	local_name=$(expr "$found" : '[^:]*:\(.*\)')
-	append_fetch_head "$sha1" "$remote" \
-		"$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
-    done || exit
-    ;;
+		test -f "$GIT_DIR/$name" && continue
+	  	git-check-ref-format "$name" || {
+			echo >&2 "warning: tag ${name} ignored"
+			continue
+		}
+		git-cat-file -t "$sha1" >/dev/null 2>&1 || continue
+		echo >&2 "Auto-following $name"
+		echo ".${name}:${name}"
+	done)
+	case "$taglist" in
+	'') ;;
+	?*)
+		fetch_main "$taglist" ;;
+	esac
 esac
 
 # If the original head was empty (i.e. no "master" yet), or