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>
This commit is contained in:
Junio C Hamano 2005-09-30 14:26:57 -07:00 committed by Junio C Hamano
parent a876ed83be
commit 8098a178b2
16 changed files with 175 additions and 56 deletions

1
.gitignore vendored
View File

@ -82,6 +82,7 @@ git-ssh-push
git-ssh-upload git-ssh-upload
git-status git-status
git-stripspace git-stripspace
git-symbolic-ref
git-tag git-tag
git-tar-tree git-tar-tree
git-unpack-file git-unpack-file

View File

@ -116,7 +116,7 @@ PROGRAMS = \
git-ssh-upload 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-unpack-objects git-update-index git-update-server-info \
git-upload-pack git-verify-pack git-write-tree \ git-upload-pack git-verify-pack git-write-tree \
git-update-ref \ git-update-ref git-symbolic-ref \
$(SIMPLE_PROGRAMS) $(SIMPLE_PROGRAMS)
# Backward compatibility -- to be removed after 1.0 # Backward compatibility -- to be removed after 1.0

View File

@ -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 char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
extern int read_ref(const char *filename, unsigned char *sha1); extern int read_ref(const char *filename, unsigned char *sha1);
extern const char *resolve_ref(const char *path, unsigned char *sha1, int); 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 */ /* General helper functions */
extern void usage(const char *err) NORETURN; extern void usage(const char *err) NORETURN;

View File

@ -402,25 +402,17 @@ static void fsck_object_dir(const char *path)
static int fsck_head_link(void) static int fsck_head_link(void)
{ {
int fd, count;
char hex[40];
unsigned char sha1[20]; unsigned char sha1[20];
static char path[PATH_MAX], link[PATH_MAX]; const char *git_HEAD = strdup(git_path("HEAD"));
const char *git_dir = get_git_dir(); 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 (!git_refs_heads_master)
if (readlink(path, link, sizeof(link)) < 0) return error("HEAD is not a symbolic ref");
return error("HEAD is not a symlink"); if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11))
if (strncmp("refs/heads/", link, 11)) return error("HEAD points to something strange (%s)",
return error("HEAD points to something strange (%s)", link); git_refs_heads_master + pfxlen);
fd = open(path, O_RDONLY); if (!memcmp(null_sha1, sha1, 20))
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))
return error("HEAD: not a valid git pointer"); return error("HEAD: not a valid git pointer");
return 0; return 0;
} }

View File

@ -38,7 +38,8 @@ bisect_start() {
# Verify HEAD. If we were bisecting before this, reset to the # Verify HEAD. If we were bisecting before this, reset to the
# top-of-line master first! # 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 case "$head" in
refs/heads/bisect*) refs/heads/bisect*)
git checkout master || exit git checkout master || exit
@ -46,7 +47,7 @@ bisect_start() {
refs/heads/*) refs/heads/*)
;; ;;
*) *)
die "Bad HEAD - strange symlink" die "Bad HEAD - strange symbolic ref"
;; ;;
esac esac
@ -135,7 +136,7 @@ bisect_next() {
echo "$rev" > "$GIT_DIR/refs/heads/new-bisect" echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
git checkout new-bisect || exit git checkout new-bisect || exit
mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" && 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" git-show-branch "$rev"
} }

View File

@ -14,7 +14,8 @@ If two arguments, create a new branch <branchname> based off of <start-point>.
delete_branch () { delete_branch () {
option="$1" branch_name="$2" 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 case ",$headref," in
",$branch_name,") ",$branch_name,")
die "Cannot delete the branch you are on." ;; die "Cannot delete the branch you are on." ;;
@ -67,7 +68,8 @@ done
case "$#" in case "$#" in
0) 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 | git-rev-parse --symbolic --all |
sed -ne 's|^refs/heads/||p' | sed -ne 's|^refs/heads/||p' |
sort | sort |

View File

@ -71,7 +71,8 @@ if [ "$?" -eq 0 ]; then
echo $new > "$GIT_DIR/refs/heads/$newbranch" echo $new > "$GIT_DIR/refs/heads/$newbranch"
branch="$newbranch" branch="$newbranch"
fi 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" rm -f "$GIT_DIR/MERGE_HEAD"
else else
exit 1 exit 1

View File

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

View File

@ -13,10 +13,13 @@
unset CDPATH unset CDPATH
die() { die() {
echo "$@" >&2 echo >&2 "$@"
exit 1 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_DIR/refs" ] &&
[ -d "$GIT_OBJECT_DIRECTORY/00" ] [ -d "$GIT_OBJECT_DIRECTORY/00" ]

View File

@ -31,7 +31,7 @@ report () {
[ "$header" ] [ "$header" ]
} }
branch=`readlink "$GIT_DIR/HEAD"` branch=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD)
case "$branch" in case "$branch" in
refs/heads/master) ;; refs/heads/master) ;;
*) echo "# On branch $branch" ;; *) echo "# On branch $branch" ;;
@ -39,7 +39,7 @@ esac
git-update-index --refresh >/dev/null 2>&1 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 then
git-diff-index -M --cached HEAD | git-diff-index -M --cached HEAD |
sed 's/^://' | sed 's/^://' |

View File

@ -166,6 +166,7 @@ static void create_default_files(const char *git_dir,
{ {
unsigned len = strlen(git_dir); unsigned len = strlen(git_dir);
static char path[PATH_MAX]; static char path[PATH_MAX];
unsigned char sha1[20];
if (len > sizeof(path)-50) if (len > sizeof(path)-50)
die("insane git directory %s", git_dir); 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" * Create the default symlink from ".git/HEAD" to the "master"
* branch * branch, if it does not exist yet.
*/ */
strcpy(path + len, "HEAD"); strcpy(path + len, "HEAD");
if (symlink("refs/heads/master", path) < 0) { if (read_ref(path, sha1) < 0) {
if (errno != EEXIST) { if (create_symref(path, "refs/heads/master") < 0)
perror(path);
exit(1); exit(1);
}
} }
path[len] = 0;
copy_templates(path, len, template_path); copy_templates(path, len, template_path);
} }

77
refs.c
View File

@ -7,6 +7,50 @@
/* We allow "recursive" symbolic refs. Only within reason, though */ /* We allow "recursive" symbolic refs. Only within reason, though */
#define MAXDEPTH 5 #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) const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
{ {
int depth = MAXDEPTH, len; int depth = MAXDEPTH, len;
@ -71,6 +115,39 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
return path; 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) int read_ref(const char *filename, unsigned char *sha1)
{ {
if (resolve_ref(filename, sha1, 1)) if (resolve_ref(filename, sha1, 1))

16
setup.c
View File

@ -76,18 +76,20 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
* Test it it looks like we're at the top * Test it it looks like we're at the top
* level git directory. We want to see a * 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 * - either a .git/objects/ directory _or_ the proper
* GIT_OBJECT_DIRECTORY environment variable * 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) static int is_toplevel_directory(void)
{ {
struct stat st; if (access(".git/refs/", X_OK) ||
access(getenv(DB_ENVIRONMENT) ?
return !lstat(".git/HEAD", &st) && getenv(DB_ENVIRONMENT) : ".git/objects/", X_OK) ||
S_ISLNK(st.st_mode) && validate_symref(".git/HEAD"))
!access(".git/refs/", X_OK) && return 0;
(getenv(DB_ENVIRONMENT) || !access(".git/objects/", X_OK)); return 1;
} }
const char *setup_git_directory(void) const char *setup_git_directory(void)

View File

@ -349,6 +349,7 @@ int main(int ac, char **av)
int all_heads = 0, all_tags = 0; int all_heads = 0, all_tags = 0;
int all_mask, all_revs, shown_merge_point; int all_mask, all_revs, shown_merge_point;
char head_path[128]; char head_path[128];
const char *head_path_p;
int head_path_len; int head_path_len;
unsigned char head_sha1[20]; unsigned char head_sha1[20];
int merge_base = 0; int merge_base = 0;
@ -430,11 +431,15 @@ int main(int ac, char **av)
if (0 <= extra) if (0 <= extra)
join_revs(&list, &seen, num_rev, extra); join_revs(&list, &seen, num_rev, extra);
head_path_len = readlink(".git/HEAD", head_path, sizeof(head_path)-1); head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
if ((head_path_len < 0) || get_sha1("HEAD", head_sha1)) 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; head_path[0] = 0;
else }
head_path[head_path_len] = 0;
if (merge_base) if (merge_base)
return show_merge_base(seen, num_rev); return show_merge_base(seen, num_rev);

34
symbolic-ref.c Normal file
View File

@ -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;
}

View File

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