Commits

Anonymous committed 8098a17

Add git-symbolic-ref

This adds the counterpart of git-update-ref that lets you read
and create "symbolic refs". By default it uses a symbolic link
to represent ".git/HEAD -> refs/heads/master", but it can be compiled
to use the textfile symbolic ref.

The places that did 'readlink .git/HEAD' and 'ln -s refs/heads/blah
.git/HEAD' have been converted to use new git-symbolic-ref command, so
that they can deal with either implementation.

Signed-off-by: Junio C Hamano <junio@twinsun.com>

  • Participants
  • Parent commits a876ed8

Comments (0)

Files changed (16)

 git-ssh-upload
 git-status
 git-stripspace
+git-symbolic-ref
 git-tag
 git-tar-tree
 git-unpack-file
 	git-ssh-upload git-tar-tree git-unpack-file \
 	git-unpack-objects git-update-index git-update-server-info \
 	git-upload-pack git-verify-pack git-write-tree \
-	git-update-ref \
+	git-update-ref git-symbolic-ref \
 	$(SIMPLE_PROGRAMS)
 
 # Backward compatibility -- to be removed after 1.0
 extern char *sha1_to_hex(const unsigned char *sha1);	/* static buffer result! */
 extern int read_ref(const char *filename, unsigned char *sha1);
 extern const char *resolve_ref(const char *path, unsigned char *sha1, int);
+extern int create_symref(const char *git_HEAD, const char *refs_heads_master);
+extern int validate_symref(const char *git_HEAD);
 
 /* General helper functions */
 extern void usage(const char *err) NORETURN;

File fsck-objects.c

 
 static int fsck_head_link(void)
 {
-	int fd, count;
-	char hex[40];
 	unsigned char sha1[20];
-	static char path[PATH_MAX], link[PATH_MAX];
-	const char *git_dir = get_git_dir();
-
-	snprintf(path, sizeof(path), "%s/HEAD", git_dir);
-	if (readlink(path, link, sizeof(link)) < 0)
-		return error("HEAD is not a symlink");
-	if (strncmp("refs/heads/", link, 11))
-		return error("HEAD points to something strange (%s)", link);
-	fd = open(path, O_RDONLY);
-	if (fd < 0)
-		return error("HEAD: %s", strerror(errno));
-	count = read(fd, hex, sizeof(hex));
-	close(fd);
-	if (count < 0)
-		return error("HEAD: %s", strerror(errno));
-	if (count < 40 || get_sha1_hex(hex, sha1))
+	const char *git_HEAD = strdup(git_path("HEAD"));
+	const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1);
+	int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */
+
+	if (!git_refs_heads_master)
+		return error("HEAD is not a symbolic ref");
+	if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11))
+		return error("HEAD points to something strange (%s)",
+			     git_refs_heads_master + pfxlen);
+	if (!memcmp(null_sha1, sha1, 20))
 		return error("HEAD: not a valid git pointer");
 	return 0;
 }

File git-bisect.sh

 	# Verify HEAD. If we were bisecting before this, reset to the
 	# top-of-line master first!
 	#
-	head=$(readlink $GIT_DIR/HEAD) || die "Bad HEAD - I need a symlink"
+	head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
+	die "Bad HEAD - I need a symbolic ref"
 	case "$head" in
 	refs/heads/bisect*)
 		git checkout master || exit
 	refs/heads/*)
 		;;
 	*)
-		die "Bad HEAD - strange symlink"
+		die "Bad HEAD - strange symbolic ref"
 		;;
 	esac
 
 	echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
 	git checkout new-bisect || exit
 	mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
-	ln -sf refs/heads/bisect "$GIT_DIR/HEAD"
+	GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect
 	git-show-branch "$rev"
 }
 

File git-branch.sh

 
 delete_branch () {
     option="$1" branch_name="$2"
-    headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||')
+    headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD |
+    	       sed -e 's|^refs/heads/||')
     case ",$headref," in
     ",$branch_name,")
 	die "Cannot delete the branch you are on." ;;
 
 case "$#" in
 0)
-	headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||')
+	headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD |
+		  sed -e 's|^refs/heads/||')
 	git-rev-parse --symbolic --all |
 	sed -ne 's|^refs/heads/||p' |
 	sort |

File git-checkout.sh

 		echo $new > "$GIT_DIR/refs/heads/$newbranch"
 		branch="$newbranch"
 	fi
-	[ "$branch" ] && ln -sf "refs/heads/$branch" "$GIT_DIR/HEAD"
+	[ "$branch" ] &&
+	GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch"
 	rm -f "$GIT_DIR/MERGE_HEAD"
 else
 	exit 1

File git-commit.sh

 fi >>.editmsg
 
 PARENTS="-p HEAD"
-if [ ! -r "$GIT_DIR/HEAD" ]; then
-	if [ -z "$(git-ls-files)" ]; then
-		echo Nothing to commit 1>&2
-		exit 1
-	fi
-	PARENTS=""
-	current=
-else
-	current=$(git-rev-parse --verify HEAD)
+if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
+then
 	if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
 		PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
 	fi
 		export GIT_AUTHOR_EMAIL
 		export GIT_AUTHOR_DATE
 	fi
+else
+	if [ -z "$(git-ls-files)" ]; then
+		echo Nothing to commit 1>&2
+		exit 1
+	fi
+	PARENTS=""
 fi
 git-status >>.editmsg
 if [ "$?" != "0" -a ! -f $GIT_DIR/MERGE_HEAD ]

File git-sh-setup.sh

 unset CDPATH
 
 die() {
-	echo "$@" >&2
+	echo >&2 "$@"
 	exit 1
 }
 
-[ -h "$GIT_DIR/HEAD" ] &&
+case "$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD 2>/dev/null)" in
+refs/*)	: ;;
+*)	false ;;
+esac &&
 [ -d "$GIT_DIR/refs" ] &&
 [ -d "$GIT_OBJECT_DIRECTORY/00" ]

File git-status.sh

   [ "$header" ]
 }
 
-branch=`readlink "$GIT_DIR/HEAD"`
+branch=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD)
 case "$branch" in
 refs/heads/master) ;;
 *)	echo "# On branch $branch" ;;
 
 git-update-index --refresh >/dev/null 2>&1
 
-if test -f "$GIT_DIR/HEAD"
+if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
 then
 	git-diff-index -M --cached HEAD |
 	sed 's/^://' |
 {
 	unsigned len = strlen(git_dir);
 	static char path[PATH_MAX];
+	unsigned char sha1[20];
 
 	if (len > sizeof(path)-50)
 		die("insane git directory %s", git_dir);
 
 	/*
 	 * Create the default symlink from ".git/HEAD" to the "master"
-	 * branch
+	 * branch, if it does not exist yet.
 	 */
 	strcpy(path + len, "HEAD");
-	if (symlink("refs/heads/master", path) < 0) {
-		if (errno != EEXIST) {
-			perror(path);
+	if (read_ref(path, sha1) < 0) {
+		if (create_symref(path, "refs/heads/master") < 0)
 			exit(1);
-		}
 	}
+	path[len] = 0;
 	copy_templates(path, len, template_path);
 }
 
 /* We allow "recursive" symbolic refs. Only within reason, though */
 #define MAXDEPTH 5
 
+#ifndef USE_SYMLINK_HEAD
+#define USE_SYMLINK_HEAD 1
+#endif
+
+int validate_symref(const char *path)
+{
+	struct stat st;
+	char *buf, buffer[256];
+	int len, fd;
+
+	if (lstat(path, &st) < 0)
+		return -1;
+
+	/* Make sure it is a "refs/.." symlink */
+	if (S_ISLNK(st.st_mode)) {
+		len = readlink(path, buffer, sizeof(buffer)-1);
+		if (len >= 5 && !memcmp("refs/", buffer, 5))
+			return 0;
+		return -1;
+	}
+
+	/*
+	 * Anything else, just open it and try to see if it is a symbolic ref.
+	 */
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -1;
+	len = read(fd, buffer, sizeof(buffer)-1);
+	close(fd);
+
+	/*
+	 * Is it a symbolic ref?
+	 */
+	if (len < 4 || memcmp("ref:", buffer, 4))
+		return -1;
+	buf = buffer + 4;
+	len -= 4;
+	while (len && isspace(*buf))
+		buf++, len--;
+	if (len >= 5 && !memcmp("refs/", buffer, 5))
+		return 0;
+	return -1;
+}
+
 const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
 {
 	int depth = MAXDEPTH, len;
 	return path;
 }
 
+int create_symref(const char *git_HEAD, const char *refs_heads_master)
+{
+#if USE_SYMLINK_HEAD
+	unlink(git_HEAD);
+	return symlink(refs_heads_master, git_HEAD);
+#else
+	const char *lockpath;
+	char ref[1000];
+	int fd, len, written;
+
+	len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
+	if (sizeof(ref) <= len) {
+		error("refname too long: %s", refs_heads_master);
+		return -1;
+	}
+	lockpath = mkpath("%s.lock", git_HEAD);
+	fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);	
+	written = write(fd, ref, len);
+	close(fd);
+	if (written != len) {
+		unlink(lockpath);
+		error("Unable to write to %s", lockpath);
+		return -2;
+	}
+	if (rename(lockpath, git_HEAD) < 0) {
+		unlink(lockpath);
+		error("Unable to create %s", git_HEAD);
+		return -3;
+	}
+	return 0;
+#endif
+}
+
 int read_ref(const char *filename, unsigned char *sha1)
 {
 	if (resolve_ref(filename, sha1, 1))
  * Test it it looks like we're at the top
  * level git directory. We want to see a
  *
- *  - a HEAD symlink and a refs/ directory under ".git"
  *  - either a .git/objects/ directory _or_ the proper
  *    GIT_OBJECT_DIRECTORY environment variable
+ *  - a refs/ directory under ".git"
+ *  - either a HEAD symlink or a HEAD file that is formatted as
+ *    a proper "ref:".
  */
 static int is_toplevel_directory(void)
 {
-	struct stat st;
-
-	return	!lstat(".git/HEAD", &st) &&
-		S_ISLNK(st.st_mode) &&
-		!access(".git/refs/", X_OK) &&
-		(getenv(DB_ENVIRONMENT) || !access(".git/objects/", X_OK));
+	if (access(".git/refs/", X_OK) ||
+	    access(getenv(DB_ENVIRONMENT) ?
+		   getenv(DB_ENVIRONMENT) : ".git/objects/", X_OK) ||
+	    validate_symref(".git/HEAD"))
+		return 0;
+	return 1;
 }
 
 const char *setup_git_directory(void)

File show-branch.c

 	int all_heads = 0, all_tags = 0;
 	int all_mask, all_revs, shown_merge_point;
 	char head_path[128];
+	const char *head_path_p;
 	int head_path_len;
 	unsigned char head_sha1[20];
 	int merge_base = 0;
 	if (0 <= extra)
 		join_revs(&list, &seen, num_rev, extra);
 
-	head_path_len = readlink(".git/HEAD", head_path, sizeof(head_path)-1);
-	if ((head_path_len < 0) || get_sha1("HEAD", head_sha1))
+	head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
+	if (head_path_p) {
+		head_path_len = strlen(head_path_p);
+		memcpy(head_path, head_path_p, head_path_len + 1);
+	}
+	else {
+		head_path_len = 0;
 		head_path[0] = 0;
-	else
-		head_path[head_path_len] = 0;
+	}
 
 	if (merge_base)
 		return show_merge_base(seen, num_rev);

File symbolic-ref.c

+#include "cache.h"
+
+static const char git_symbolic_ref_usage[] =
+"git-symbolic-ref name [ref]";
+
+static int check_symref(const char *HEAD)
+{
+	unsigned char sha1[20];
+	const char *git_HEAD = strdup(git_path("%s", HEAD));
+	const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0);
+	if (git_refs_heads_master) {
+		/* we want to strip the .git/ part */
+		int pfxlen = strlen(git_HEAD) - strlen(HEAD);
+		puts(git_refs_heads_master + pfxlen);
+	}
+	else
+		die("No such ref: %s", HEAD);
+}
+
+int main(int argc, const char **argv)
+{
+	setup_git_directory();
+	switch (argc) {
+	case 2:
+		check_symref(argv[1]);
+		break;
+	case 3:
+		create_symref(strdup(git_path("%s", argv[1])), argv[2]);
+		break;
+	default:
+		usage(git_symbolic_ref_usage);
+	}
+	return 0;
+}

File t/t5400-send-pack.sh

 	    commit=$(echo "Commit #$i" | git-commit-tree $tree -p $parent) &&
 	    parent=$commit || return 1
 	done &&
-	echo "$commit" >.git/HEAD &&
+	git-update-ref HEAD "$commit" &&
 	git-clone -l ./. victim &&
 	cd victim &&
 	git-log &&
 	cd .. &&
-	echo $zero >.git/HEAD &&
+	git-update-ref HEAD "$zero" &&
 	parent=$zero &&
 	for i in $cnt
 	do
 	    commit=$(echo "Rebase #$i" | git-commit-tree $tree -p $parent) &&
 	    parent=$commit || return 1
 	done &&
-	echo "$commit" >.git/HEAD &&
+	git-update-ref HEAD "$commit" &&
 	echo Rebase &&
 	git-log'