Commit 883cb642cb1b58a5ede7ebf107aa7fee9025d622

Edward Thomson 2015-06-20T14:05:02

Merge pull request #3236 from libgit2/cmn/index-checksum Use the checksum to check whether an index has been modified

diff --git a/include/git2/index.h b/include/git2/index.h
index 49bbe16..7caf3ed 100644
--- a/include/git2/index.h
+++ b/include/git2/index.h
@@ -274,6 +274,18 @@ GIT_EXTERN(int) git_index_write(git_index *index);
 GIT_EXTERN(const char *) git_index_path(const git_index *index);
 
 /**
+ * Get the checksum of the index
+ *
+ * This checksum is the SHA-1 hash over the index file (except the
+ * last 20 bytes which are the checksum itself). In cases where the
+ * index does not exist on-disk, it will be zeroed out.
+ *
+ * @param index an existing index object
+ * @return a pointer to the checksum of the index
+ */
+GIT_EXTERN(const git_oid *) git_index_checksum(git_index *index);
+
+/**
  * Read a tree into the index file with stats
  *
  * The current index contents will be replaced by the specified tree.
diff --git a/src/index.c b/src/index.c
index a931f04..1fb3c48 100644
--- a/src/index.c
+++ b/src/index.c
@@ -116,7 +116,7 @@ static int read_header(struct index_header *dest, const void *buffer);
 
 static int parse_index(git_index *index, const char *buffer, size_t buffer_size);
 static bool is_index_extended(git_index *index);
-static int write_index(git_index *index, git_filebuf *file);
+static int write_index(git_oid *checksum, git_index *index, git_filebuf *file);
 
 static void index_entry_free(git_index_entry *entry);
 static void index_entry_reuc_free(git_index_reuc_entry *reuc);
@@ -598,6 +598,38 @@ int git_index_caps(const git_index *index)
 			(index->no_symlinks ? GIT_INDEXCAP_NO_SYMLINKS : 0));
 }
 
+const git_oid *git_index_checksum(git_index *index)
+{
+	return &index->checksum;
+}
+
+/**
+ * Returns 1 for changed, 0 for not changed and <0 for errors
+ */
+static int compare_checksum(git_index *index)
+{
+	int fd, error;
+	ssize_t bytes_read;
+	git_oid checksum = {{ 0 }};
+
+	if ((fd = p_open(index->index_file_path, O_RDONLY)) < 0)
+		return fd;
+
+	if ((error = p_lseek(fd, -20, SEEK_END)) < 0) {
+		p_close(fd);
+		giterr_set(GITERR_OS, "failed to seek to end of file");
+		return -1;
+	}
+
+	bytes_read = p_read(fd, &checksum, GIT_OID_RAWSZ);
+	p_close(fd);
+
+	if (bytes_read < 0)
+		return -1;
+
+	return !!git_oid_cmp(&checksum, &index->checksum);
+}
+
 int git_index_read(git_index *index, int force)
 {
 	int error = 0, updated;
@@ -616,8 +648,8 @@ int git_index_read(git_index *index, int force)
 		return 0;
 	}
 
-	updated = git_futils_filestamp_check(&stamp, index->index_file_path);
-	if (updated < 0) {
+	if ((updated = git_futils_filestamp_check(&stamp, index->index_file_path) < 0) ||
+	    ((updated = compare_checksum(index)) < 0)) {
 		giterr_set(
 			GITERR_INDEX,
 			"Failed to read index: '%s' no longer exists",
@@ -647,15 +679,13 @@ int git_index_read(git_index *index, int force)
 }
 
 int git_index__changed_relative_to(
-	git_index *index, const git_futils_filestamp *fs)
+	git_index *index, const git_oid *checksum)
 {
 	/* attempt to update index (ignoring errors) */
 	if (git_index_read(index, false) < 0)
 		giterr_clear();
 
-	return (index->stamp.mtime != fs->mtime ||
-			index->stamp.size != fs->size ||
-			index->stamp.ino != fs->ino);
+	return !!git_oid_cmp(&index->checksum, checksum);
 }
 
 /*
@@ -2092,6 +2122,8 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
 		goto done;
 	}
 
+	git_oid_cpy(&index->checksum, &checksum_calculated);
+
 #undef seek_forward
 
 	/* Entries are stored case-sensitively on disk, so re-sort now if
@@ -2355,7 +2387,7 @@ static int write_tree_extension(git_index *index, git_filebuf *file)
 	return error;
 }
 
-static int write_index(git_index *index, git_filebuf *file)
+static int write_index(git_oid *checksum, git_index *index, git_filebuf *file)
 {
 	git_oid hash_final;
 	struct index_header header;
@@ -2391,6 +2423,7 @@ static int write_index(git_index *index, git_filebuf *file)
 
 	/* get out the hash for all the contents we've appended to the file */
 	git_filebuf_hash(&hash_final, file);
+	git_oid_cpy(checksum, &hash_final);
 
 	/* write it at the end of the file */
 	return git_filebuf_write(file, hash_final.id, GIT_OID_RAWSZ);
@@ -2953,6 +2986,7 @@ int git_indexwriter_init_for_operation(
 int git_indexwriter_commit(git_indexwriter *writer)
 {
 	int error;
+	git_oid checksum = {{ 0 }};
 
 	if (!writer->should_write)
 		return 0;
@@ -2962,7 +2996,7 @@ int git_indexwriter_commit(git_indexwriter *writer)
 
 	git_vector_sort(&writer->index->reuc);
 
-	if ((error = write_index(writer->index, &writer->file)) < 0) {
+	if ((error = write_index(&checksum, writer->index, &writer->file)) < 0) {
 		git_indexwriter_cleanup(writer);
 		return error;
 	}
@@ -2977,6 +3011,7 @@ int git_indexwriter_commit(git_indexwriter *writer)
 	}
 
 	writer->index->on_disk = 1;
+	git_oid_cpy(&writer->index->checksum, &checksum);
 
 	git_index_free(writer->index);
 	writer->index = NULL;
diff --git a/src/index.h b/src/index.h
index 0f6f4e8..9c60b01 100644
--- a/src/index.h
+++ b/src/index.h
@@ -22,6 +22,7 @@ struct git_index {
 
 	char *index_file_path;
 	git_futils_filestamp stamp;
+	git_oid checksum;   /* checksum at the end of the file */
 
 	git_vector entries;
 
@@ -80,7 +81,7 @@ GIT_INLINE(const git_futils_filestamp *) git_index__filestamp(git_index *index)
    return &index->stamp;
 }
 
-extern int git_index__changed_relative_to(git_index *index, const git_futils_filestamp *fs);
+extern int git_index__changed_relative_to(git_index *index, const git_oid *checksum);
 
 /* Copy the current entries vector *and* increment the index refcount.
  * Call `git_index__release_snapshot` when done.
diff --git a/src/submodule.c b/src/submodule.c
index 1139df9..246502e 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -1946,7 +1946,7 @@ static int submodule_cache_refresh(git_submodule_cache *cache, int refresh)
 		update_index = update_head = update_gitmod = true;
 	else {
 		update_index =
-			!idx || git_index__changed_relative_to(idx, &cache->index_stamp);
+			!idx || git_index__changed_relative_to(idx, &cache->index_checksum);
 		update_head =
 			!head || !git_oid_equal(&cache->head_id, git_tree_id(head));
 
@@ -1984,8 +1984,7 @@ static int submodule_cache_refresh(git_submodule_cache *cache, int refresh)
 		if ((error = submodule_cache_refresh_from_index(cache, idx)) < 0)
 			goto cleanup;
 
-		git_futils_filestamp_set(
-			&cache->index_stamp, git_index__filestamp(idx));
+		git_oid_cpy(&cache->index_checksum, git_index_checksum(idx));
 	}
 
 	/* add submodule information from HEAD */
diff --git a/src/submodule.h b/src/submodule.h
index a6182be..7a9bf9c 100644
--- a/src/submodule.h
+++ b/src/submodule.h
@@ -110,7 +110,7 @@ typedef struct {
 
 	/* cache invalidation data */
 	git_oid head_id;
-	git_futils_filestamp index_stamp;
+	git_oid index_checksum;
 	git_buf gitmodules_path;
 	git_futils_filestamp gitmodules_stamp;
 	git_futils_filestamp config_stamp;
diff --git a/tests/index/tests.c b/tests/index/tests.c
index 3c8060a..e1ff12a 100644
--- a/tests/index/tests.c
+++ b/tests/index/tests.c
@@ -103,8 +103,8 @@ void test_index_tests__default_test_index(void)
 		git_index_entry *e = entries[test_entries[i].index];
 
 		cl_assert_equal_s(e->path, test_entries[i].path);
-		cl_assert(e->mtime.seconds == test_entries[i].mtime);
-		cl_assert(e->file_size == test_entries[i].file_size);
+		cl_assert_equal_i(e->mtime.seconds, test_entries[i].mtime);
+		cl_assert_equal_i(e->file_size, test_entries[i].file_size);
    }
 
    git_index_free(index);