From: Junio C Hamano <junkio@cox.net>
To: git@vger.kernel.org
Subject: [PATCH] Add git-symbolic-ref
Date: Wed, 28 Sep 2005 21:24:08 -0700 [thread overview]
Message-ID: <7vek78pklz.fsf@assigned-by-dhcp.cox.net> (raw)
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. In that case, the
.git/HEAD file would typically read "ref: refs/heads/master".
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.
I suspect that I ended up wasting my git day of this week
working on this, given that Cygwin seems to be capable of
emulating symlinks using .lnk files. On the other hand, even
Linux folks may be on vfat or similar fs that does not have
symlinks so who knows. On yet another hand, a project can have
symlinks stored in the repository, and in order to check things
out the filesystem needs to have symlinks anyway, so maybe this
is a big wasted effort. Mumble mumble mumble...
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
Makefile | 2 +-
cache.h | 1 +
fsck-objects.c | 28 ++++++++++------------------
git-bisect.sh | 7 ++++---
git-branch.sh | 6 ++++--
git-checkout.sh | 3 ++-
git-commit.sh | 17 ++++++++---------
git-sh-setup.sh | 4 ++--
git-status.sh | 4 ++--
init-db.c | 10 +++++-----
refs.c | 46 ++++++++++++++++++++++++++++++++++++++++++++--
setup.c | 16 ++++++++++------
show-branch.c | 13 +++++++++----
symbolic-ref.c | 34 ++++++++++++++++++++++++++++++++++
t/t5400-send-pack.sh | 6 +++---
15 files changed, 139 insertions(+), 58 deletions(-)
create mode 100644 symbolic-ref.c
15de68abcb00ede12ac4b67c3f0ecac7cfffd0b4
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/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 in 0.99.8
diff --git a/cache.h b/cache.h
--- a/cache.h
+++ b/cache.h
@@ -231,6 +231,7 @@ extern int get_sha1_hex(const char *hex,
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);
+extern int create_symref(const char *git_HEAD, const char *refs_heads_master);
/* General helper functions */
extern void usage(const char *err) NORETURN;
diff --git a/fsck-objects.c b/fsck-objects.c
--- a/fsck-objects.c
+++ b/fsck-objects.c
@@ -402,25 +402,17 @@ static void fsck_object_dir(const char *
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);
+ 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;
}
diff --git a/git-bisect.sh b/git-bisect.sh
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -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"
}
diff --git a/git-branch.sh b/git-branch.sh
--- a/git-branch.sh
+++ b/git-branch.sh
@@ -14,7 +14,8 @@ If two arguments, create a new branch <b
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 |
diff --git a/git-checkout.sh b/git-checkout.sh
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -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
diff --git a/git-commit.sh b/git-commit.sh
--- a/git-commit.sh
+++ b/git-commit.sh
@@ -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 ]
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -13,10 +13,10 @@
unset CDPATH
die() {
- echo "$@" >&2
+ echo >&2 "$@"
exit 1
}
-[ -h "$GIT_DIR/HEAD" ] &&
+GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD >/dev/null 2>&1 &&
[ -d "$GIT_DIR/refs" ] &&
[ -d "$GIT_OBJECT_DIRECTORY/00" ]
diff --git a/git-status.sh b/git-status.sh
--- a/git-status.sh
+++ b/git-status.sh
@@ -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/^://' |
diff --git a/init-db.c b/init-db.c
--- a/init-db.c
+++ b/init-db.c
@@ -166,6 +166,7 @@ static void create_default_files(const c
{
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 c
/*
* 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);
}
diff --git a/refs.c b/refs.c
--- a/refs.c
+++ b/refs.c
@@ -9,6 +9,10 @@ const unsigned char null_sha1[20] = { 0,
/* We allow "recursive" symbolic refs. Only within reason, though */
#define MAXDEPTH 5
+#ifndef USE_SYMLINK_HEAD
+#define USE_SYMLINK_HEAD 1
+#endif
+
const char *resolve_ref(const char *path, unsigned char *sha1)
{
int depth = MAXDEPTH, len;
@@ -22,9 +26,14 @@ const char *resolve_ref(const char *path
if (--depth < 0)
return NULL;
- /* Special case: non-existing file */
+ /* Special case: non-existing file.
+ * But if this is the first round, i.e. the user
+ * gave us ".git/NO_SUCH_SYMLINK" which does not even
+ * exist, we should barf. ".git/HEAD" symlink which
+ * points at "refs/heads/master" not yet born is OK.
+ */
if (lstat(path, &st) < 0) {
- if (errno != ENOENT)
+ if (errno != ENOENT || depth == MAXDEPTH - 1)
return NULL;
memset(sha1, 0, 20);
return path;
@@ -67,6 +76,39 @@ const char *resolve_ref(const char *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)
{
if (resolve_ref(filename, sha1) && memcmp(sha1, null_sha1, 20))
diff --git a/setup.c b/setup.c
--- a/setup.c
+++ b/setup.c
@@ -76,18 +76,22 @@ const char **get_pathspec(const char *pr
* 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;
+ unsigned char sha1[20];
- 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) ||
+ (resolve_ref(".git/HEAD", sha1) == NULL))
+ return 0;
+ return 1;
}
const char *setup_git_directory(void)
diff --git a/show-branch.c b/show-branch.c
--- a/show-branch.c
+++ b/show-branch.c
@@ -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);
+ 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);
diff --git a/symbolic-ref.c b/symbolic-ref.c
new file mode 100644
--- /dev/null
+++ b/symbolic-ref.c
@@ -0,0 +1,34 @@
+#include "cache.h"
+
+static const char git_symbolic_ref_usage[] =
+"git-symbolic-ref name [ref]";
+
+static void 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);
+ 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;
+}
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
--- a/t/t5400-send-pack.sh
+++ b/t/t5400-send-pack.sh
@@ -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'
reply other threads:[~2005-09-29 4:24 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: http://vger.kernel.org/majordomo-info.html
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=7vek78pklz.fsf@assigned-by-dhcp.cox.net \
--to=junkio@cox.net \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://80x24.org/mirrors/git.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).