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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
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"