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>maint
							parent
							
								
									a876ed83be
								
							
						
					
					
						commit
						8098a178b2
					
				|  | @ -82,6 +82,7 @@ git-ssh-push | |||
| git-ssh-upload | ||||
| git-status | ||||
| git-stripspace | ||||
| git-symbolic-ref | ||||
| git-tag | ||||
| git-tar-tree | ||||
| git-unpack-file | ||||
|  |  | |||
							
								
								
									
										2
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										2
									
								
								Makefile
								
								
								
								
							|  | @ -116,7 +116,7 @@ PROGRAMS = \ | |||
| 	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 | ||||
|  |  | |||
							
								
								
									
										2
									
								
								cache.h
								
								
								
								
							
							
						
						
									
										2
									
								
								cache.h
								
								
								
								
							|  | @ -231,6 +231,8 @@ extern int get_sha1_hex(const char *hex, unsigned char *sha1); | |||
| 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; | ||||
|  |  | |||
|  | @ -402,25 +402,17 @@ static void fsck_object_dir(const char *path) | |||
|  | ||||
| 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(); | ||||
| 	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 */ | ||||
|  | ||||
| 	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)) | ||||
| 	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; | ||||
| } | ||||
|  |  | |||
|  | @ -38,7 +38,8 @@ bisect_start() { | |||
| 	# 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 | ||||
|  | @ -46,7 +47,7 @@ bisect_start() { | |||
| 	refs/heads/*) | ||||
| 		;; | ||||
| 	*) | ||||
| 		die "Bad HEAD - strange symlink" | ||||
| 		die "Bad HEAD - strange symbolic ref" | ||||
| 		;; | ||||
| 	esac | ||||
|  | ||||
|  | @ -135,7 +136,7 @@ bisect_next() { | |||
| 	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" | ||||
| } | ||||
|  | ||||
|  |  | |||
|  | @ -14,7 +14,8 @@ If two arguments, create a new branch <branchname> based off of <start-point>. | |||
|  | ||||
| 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." ;; | ||||
|  | @ -67,7 +68,8 @@ done | |||
|  | ||||
| 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 | | ||||
|  |  | |||
|  | @ -71,7 +71,8 @@ if [ "$?" -eq 0 ]; then | |||
| 		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 | ||||
|  |  | |||
|  | @ -153,15 +153,8 @@ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then | |||
| 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 | ||||
|  | @ -194,6 +187,12 @@ else | |||
| 		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 ] | ||||
|  |  | |||
|  | @ -13,10 +13,13 @@ | |||
| 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" ] | ||||
|  |  | |||
|  | @ -31,7 +31,7 @@ report () { | |||
|   [ "$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" ;; | ||||
|  | @ -39,7 +39,7 @@ esac | |||
|  | ||||
| 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/^://' | | ||||
|  |  | |||
							
								
								
									
										10
									
								
								init-db.c
								
								
								
								
							
							
						
						
									
										10
									
								
								init-db.c
								
								
								
								
							|  | @ -166,6 +166,7 @@ static void create_default_files(const char *git_dir, | |||
| { | ||||
| 	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); | ||||
|  | @ -186,15 +187,14 @@ static void create_default_files(const char *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); | ||||
| } | ||||
|  | ||||
|  |  | |||
							
								
								
									
										77
									
								
								refs.c
								
								
								
								
							
							
						
						
									
										77
									
								
								refs.c
								
								
								
								
							|  | @ -7,6 +7,50 @@ | |||
| /* 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; | ||||
|  | @ -71,6 +115,39 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) | |||
| 	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)) | ||||
|  |  | |||
							
								
								
									
										16
									
								
								setup.c
								
								
								
								
							
							
						
						
									
										16
									
								
								setup.c
								
								
								
								
							|  | @ -76,18 +76,20 @@ const char **get_pathspec(const char *prefix, const char **pathspec) | |||
|  * 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) | ||||
|  |  | |||
|  | @ -349,6 +349,7 @@ int main(int ac, char **av) | |||
| 	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; | ||||
|  | @ -430,11 +431,15 @@ int main(int ac, char **av) | |||
| 	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); | ||||
|  |  | |||
|  | @ -0,0 +1,34 @@ | |||
| #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; | ||||
| } | ||||
|  | @ -20,12 +20,12 @@ test_expect_success setup ' | |||
| 	    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 | ||||
|  | @ -33,7 +33,7 @@ test_expect_success setup ' | |||
| 	    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' | ||||
|  | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano