Commit 4047950f30618c160cd2fdf5da39fb8e26b031d9

nulltoken 2013-08-29T14:19:34

odb: Prevent stream_finalize_write() from overwriting Now that #1785 is merged, git_odb_stream_finalize_write() calculates the object id before invoking the odb backend. This commit gives a chance to the backend to check if it already knows this object.

diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h
index 7b3c6a3..bafeec0 100644
--- a/include/git2/odb_backend.h
+++ b/include/git2/odb_backend.h
@@ -92,6 +92,10 @@ struct git_odb_stream {
 	/**
 	 * Store the contents of the stream as an object with the id
 	 * specified in `oid`.
+	 *
+	 * This method will *not* be invoked by libgit2 if the object pointed at
+	 * by `oid` already exists in any backend. Libgit2 will however take care
+	 * of properly disposing the stream through a call to `free()`.
 	 */
 	int (*finalize_write)(git_odb_stream *stream, const git_oid *oid);
 
diff --git a/src/odb.c b/src/odb.c
index e47715f..dfb2521 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -900,6 +900,10 @@ int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len)
 int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream)
 {
 	git_hash_final(out, stream->hash_ctx);
+
+	if (git_odb_exists(stream->backend->odb, out))
+		return 0;
+
 	return stream->finalize_write(stream, out);
 }
 
diff --git a/src/odb_loose.c b/src/odb_loose.c
index abf46a1..ce63f46 100644
--- a/src/odb_loose.c
+++ b/src/odb_loose.c
@@ -781,13 +781,6 @@ static int loose_backend__stream_fwrite(git_odb_stream *_stream, const git_oid *
 	if (object_file_name(&final_path, backend, oid) < 0 ||
 		object_mkdir(&final_path, backend) < 0)
 		error = -1;
-	/*
-	 * Don't try to add an existing object to the repository. This
-	 * is what git does and allows us to sidestep the fact that
-	 * we're not allowed to overwrite a read-only file on Windows.
-	 */
-	else if (git_path_exists(final_path.ptr) == true)
-		git_filebuf_cleanup(&stream->fbuf);
 	else
 		error = git_filebuf_commit_at(
 			&stream->fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE);
diff --git a/tests-clar/object/blob/fromchunks.c b/tests-clar/object/blob/fromchunks.c
index 9fe62da..03ed4ef 100644
--- a/tests-clar/object/blob/fromchunks.c
+++ b/tests-clar/object/blob/fromchunks.c
@@ -53,6 +53,34 @@ void test_object_blob_fromchunks__can_create_a_blob_from_a_in_memory_chunk_provi
 	git_object_free(blob);
 }
 
+void test_object_blob_fromchunks__doesnot_overwrite_an_already_existing_object(void)
+{
+	git_buf path = GIT_BUF_INIT;
+	git_buf content = GIT_BUF_INIT;
+	git_oid expected_oid, oid;
+	int howmany = 7;
+	
+	cl_git_pass(git_oid_fromstr(&expected_oid, "321cbdf08803c744082332332838df6bd160f8f9"));
+
+	cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany));
+
+	/* Let's replace the content of the blob file storage with something else... */
+	cl_git_pass(git_buf_joinpath(&path, git_repository_path(repo), "objects/32/1cbdf08803c744082332332838df6bd160f8f9"));
+	cl_git_pass(p_unlink(git_buf_cstr(&path)));
+	cl_git_mkfile(git_buf_cstr(&path), "boom");
+
+	/* ...request a creation of the same blob... */
+	howmany = 7;
+	cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany));
+
+	/* ...and ensure the content of the faked blob file hasn't been altered */
+	cl_git_pass(git_futils_readbuffer(&content, git_buf_cstr(&path)));
+	cl_assert(!git__strcmp("boom", git_buf_cstr(&content)));
+
+	git_buf_free(&path);
+	git_buf_free(&content);
+}
+
 #define GITATTR "* text=auto\n" \
 	"*.txt text\n" \
 	"*.data binary\n"