Commit 7341bf87b111dfa0cf12761389e0e7a118363f42

Vicent Marti 2011-03-16T23:13:28

Refs are now always in-sync on disk

diff --git a/src/refs.c b/src/refs.c
index ea39608..40b80ec 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -59,16 +59,16 @@ static uint32_t reftable_hash(const void *key, int hash_id)
 
 static void reference_free(git_reference *reference);
 static int reference_create(git_reference **ref_out, git_repository *repo, const char *name, git_rtype type);
+static int reference_read(gitfo_buf *file_content, time_t *mtime, const char *repo_path, const char *ref_name);
 
 /* loose refs */
 static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content);
 static int loose_parse_oid(git_reference *ref, gitfo_buf *file_content);
-static int loose_read(gitfo_buf *file_content, const char *name, const char *repo_path);
-static int loose_lookup( git_reference **ref_out, git_repository *repo, const char *name, int skip_symbolic);
+static int loose_lookup(git_reference **ref_out, git_repository *repo, const char *name, int skip_symbolic);
 static int loose_write(git_reference *ref);
+static int loose_update(git_reference *ref);
 
 /* packed refs */
-static int packed_readpack(gitfo_buf *packfile, const char *repo_path);
 static int packed_parse_peel(reference_oid *tag_ref, const char **buffer_out, const char *buffer_end);
 static int packed_parse_oid(reference_oid **ref_out, git_repository *repo, const char **buffer_out, const char *buffer_end);
 static int packed_load(git_repository *repo);
@@ -146,12 +146,73 @@ cleanup:
 	return error;
 }
 
+static int reference_read(gitfo_buf *file_content, time_t *mtime, const char *repo_path, const char *ref_name)
+{
+	struct stat st;
+	char path[GIT_PATH_MAX];
+
+	/* Determine the full path of the file */
+	git__joinpath(path, repo_path, ref_name);
+
+	if (gitfo_stat(path, &st) < 0)
+		return GIT_ENOTFOUND;
+
+	if (S_ISDIR(st.st_mode))
+		return GIT_EOBJCORRUPTED;
+
+	if (mtime)
+		*mtime = st.st_mtime;
+
+	if (file_content)
+		return gitfo_read_file(file_content, path);
+
+	return GIT_SUCCESS;
+}
+
 
 
 
 /*****************************************
  * Internal methods - Loose references
  *****************************************/
+static int loose_update(git_reference *ref)
+{
+	int error;
+	time_t ref_time;
+	gitfo_buf ref_file = GITFO_BUF_INIT;
+
+	if (ref->type & GIT_REF_PACKED)
+		return packed_load(ref->owner);
+
+	error = reference_read(NULL, &ref_time, ref->owner->path_repository, ref->name);
+	if (error < GIT_SUCCESS)
+		goto cleanup;
+
+	if (ref_time == ref->mtime)
+		return GIT_SUCCESS;
+
+	error = reference_read(&ref_file, &ref->mtime, ref->owner->path_repository, ref->name);
+	if (error < GIT_SUCCESS)
+		goto cleanup;
+
+	if (ref->type == GIT_REF_SYMBOLIC)
+		error = loose_parse_symbolic(ref, &ref_file);
+	else if (ref->type == GIT_REF_OID)
+		error = loose_parse_oid(ref, &ref_file);
+	else
+		error = GIT_EINVALIDREFSTATE;
+
+	gitfo_free_buf(&ref_file);
+
+cleanup:
+	if (error != GIT_SUCCESS) {
+		reference_free(ref);
+		git_hashtable_remove(ref->owner->references.loose_cache, ref->name);
+	}
+
+	return error;
+}
+
 static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content)
 {
 	const unsigned int header_len = strlen(GIT_SYMREF);
@@ -172,6 +233,7 @@ static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content)
 
 	refname_start += header_len;
 
+	free(ref_sym->target);
 	ref_sym->target = git__strdup(refname_start);
 	if (ref_sym->target == NULL)
 		return GIT_ENOMEM;
@@ -213,27 +275,6 @@ static int loose_parse_oid(git_reference *ref, gitfo_buf *file_content)
 	return GIT_SUCCESS;
 }
 
-static int loose_read(gitfo_buf *file_content, const char *name, const char *repo_path)
-{
-	int error = GIT_SUCCESS;
-	char ref_path[GIT_PATH_MAX];
-
-	/* Determine the full path of the ref */
-	git__joinpath(ref_path, repo_path, name);
-
-	/* Does it even exist ? */
-	if (gitfo_exists(ref_path) < GIT_SUCCESS)
-		return GIT_ENOTFOUND;
-
-	/* A ref can not be a directory */
-	if (!gitfo_isdir(ref_path))
-		return GIT_ENOTFOUND;
-
-	if (file_content != NULL)
-		error = gitfo_read_file(file_content, ref_path);
-
-	return error;
-}
 
 static git_rtype loose_guess_rtype(const char *full_path)
 {
@@ -262,10 +303,11 @@ static int loose_lookup(
 	int error = GIT_SUCCESS;
 	gitfo_buf ref_file = GITFO_BUF_INIT;
 	git_reference *ref = NULL;
+	time_t ref_time;
 
 	*ref_out = NULL;
 
-	error = loose_read(&ref_file, name, repo->path_repository);
+	error = reference_read(&ref_file, &ref_time, repo->path_repository, name);
 	if (error < GIT_SUCCESS)
 		goto cleanup;
 
@@ -289,6 +331,7 @@ static int loose_lookup(
 	if (error < GIT_SUCCESS)
 		goto cleanup;
 
+	ref->mtime = ref_time;
 	*ref_out = ref;
 	return GIT_SUCCESS;
 
@@ -304,6 +347,7 @@ static int loose_write(git_reference *ref)
 	char ref_path[GIT_PATH_MAX];
 	int error, contents_size;
 	char *ref_contents = NULL;
+	struct stat st;
 
 	assert((ref->type & GIT_REF_PACKED) == 0);
 
@@ -349,6 +393,9 @@ static int loose_write(git_reference *ref)
 
 	error = git_filebuf_commit(&file);
 
+	if (gitfo_stat(ref_path, &st) == GIT_SUCCESS)
+		ref->mtime = st.st_mtime;
+
 	free(ref_contents);
 	return error;
 
@@ -366,19 +413,6 @@ unlock:
 /*****************************************
  * Internal methods - Packed references
  *****************************************/
-static int packed_readpack(gitfo_buf *packfile, const char *repo_path)
-{
-	char ref_path[GIT_PATH_MAX];
-
-	/* Determine the full path of the file */
-	git__joinpath(ref_path, repo_path, GIT_PACKEDREFS_FILE);
-
-	/* Does it even exist ? */
-	if (gitfo_exists(ref_path) < GIT_SUCCESS)
-		return GIT_ENOTFOUND;
-
-	return gitfo_read_file(packfile, ref_path);
-}
 
 static int packed_parse_peel(
 		reference_oid *tag_ref,
@@ -483,19 +517,40 @@ static int packed_load(git_repository *repo)
 	git_refcache *ref_cache = &repo->references;
 
 	/* already loaded */
-	if (repo->references.packfile != NULL)
-		return GIT_SUCCESS;
+	if (repo->references.packfile != NULL) {
+		time_t packed_time;
 
-	repo->references.packfile = git_hashtable_alloc(
-		default_table_size, 
-		reftable_hash,
-		(git_hash_keyeq_ptr)strcmp);
+		/* check if we can read the time of the index;
+		 * if we can read it and it matches the time of the
+		 * index we had previously loaded, we don't need to do
+		 * anything else.
+		 *
+		 * if we cannot load the time (e.g. the packfile
+		 * has disappeared) or the time is different, we
+		 * have to reload the packfile */
 
-	if (repo->references.packfile == NULL)
-		return GIT_ENOMEM;
+		if (!reference_read(NULL, &packed_time, repo->path_repository, GIT_PACKEDREFS_FILE) &&
+			packed_time == ref_cache->packfile_time)
+			return GIT_SUCCESS;
 
-	/* read the packfile from disk */
-	error = packed_readpack(&packfile, repo->path_repository);
+		git_hashtable_clear(repo->references.packfile);
+	} else {
+		ref_cache->packfile = git_hashtable_alloc(
+			default_table_size, 
+			reftable_hash,
+			(git_hash_keyeq_ptr)strcmp);
+
+		if (ref_cache->packfile == NULL)
+			return GIT_ENOMEM;
+	}
+
+	/* read the packfile from disk;
+	 * store its modification time to check for future reloads */
+	error = reference_read(
+			&packfile,
+			&ref_cache->packfile_time,
+			repo->path_repository,
+			GIT_PACKEDREFS_FILE);
 
 	/* there is no packfile on disk; that's ok */
 	if (error == GIT_ENOTFOUND)
@@ -536,7 +591,12 @@ static int packed_load(git_repository *repo)
 		}
 	}
 
+	gitfo_free_buf(&packfile);
+	return GIT_SUCCESS;
+
 cleanup:
+	git_hashtable_free(ref_cache->packfile);
+	ref_cache->packfile = NULL;
 	gitfo_free_buf(&packfile);
 	return error;
 }
@@ -834,8 +894,14 @@ cleanup:
 
 		/* when and only when the packfile has been properly written,
 		 * we can go ahead and remove the loose refs */
-		if (error == GIT_SUCCESS)
+		if (error == GIT_SUCCESS) {
+			struct stat st;
+
 			error = packed_remove_loose(repo, &packing_list);
+
+			if (gitfo_stat(pack_file_path, &st) == GIT_SUCCESS)
+				repo->references.packfile_time = st.st_mtime;
+		}
 	}
 	else git_filebuf_cleanup(&pack_file);
 
@@ -870,7 +936,7 @@ int git_reference_lookup(git_reference **ref_out, git_repository *repo, const ch
 	/* First, check has been previously loaded and cached */
 	*ref_out = git_hashtable_lookup(repo->references.loose_cache, normalized_name);
 	if (*ref_out != NULL)
-		return GIT_SUCCESS;
+		return loose_update(*ref_out);
 
 	/* Then check if there is a loose file for that reference.*/
 	error = loose_lookup(ref_out, repo, normalized_name, 0);
@@ -888,12 +954,10 @@ int git_reference_lookup(git_reference **ref_out, git_repository *repo, const ch
 	 * If we cannot find a loose reference, we look into the packfile
 	 * Load the packfile first if it hasn't been loaded 
 	 */
-	if (!repo->references.packfile) {
-		/* load all the packed references */
-		error = packed_load(repo);
-		if (error < GIT_SUCCESS)
-			return error;
-	}
+	/* load all the packed references */
+	error = packed_load(repo);
+	if (error < GIT_SUCCESS)
+		return error;
 
 	/* Look up on the packfile */
 	*ref_out = git_hashtable_lookup(repo->references.packfile, normalized_name);
@@ -1000,6 +1064,9 @@ const git_oid *git_reference_oid(git_reference *ref)
 	if ((ref->type & GIT_REF_OID) == 0)
 		return NULL;
 
+	if (loose_update(ref) < GIT_SUCCESS)
+		return NULL;
+
 	return &((reference_oid *)ref)->oid;
 }
 
@@ -1010,6 +1077,9 @@ const char *git_reference_target(git_reference *ref)
 	if ((ref->type & GIT_REF_SYMBOLIC) == 0)
 		return NULL;
 
+	if (loose_update(ref) < GIT_SUCCESS)
+		return NULL;
+
 	return ((reference_symbolic *)ref)->target;
 }
 
@@ -1293,6 +1363,9 @@ int git_reference_resolve(git_reference **resolved_ref, git_reference *ref)
 
 	assert(resolved_ref && ref);
 	*resolved_ref = NULL;
+
+	if ((error = loose_update(ref)) < GIT_SUCCESS)
+		return error;
 	
 	repo = ref->owner;
 
diff --git a/src/refs.h b/src/refs.h
index a542ac0..bebb1b9 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -23,11 +23,13 @@ struct git_reference {
 	git_repository *owner;
 	char *name;
 	unsigned int type;
+	time_t mtime;
 };
 
 typedef struct {
 	git_hashtable *packfile;
 	git_hashtable *loose_cache;
+	time_t packfile_time;
 } git_refcache;