Commit 15f581747c57f1bb5f3c3a173139789ec759c964

Carlos Martín Nieto 2015-03-11T17:55:39

Merge commit 'refs/pull/2879/head' of ssh://github.com/libgit2/libgit2

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0970bfd..71112bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,6 +32,9 @@ v0.22 + 1
 
 * Reference renaming now uses the right id for the old value.
 
+* `git_index_add_frombuffer()` can now create a blob from memory
+   buffer and add it to the index which is attached to a repository.
+
 ### API removals
 
 ### Breaking API changes
diff --git a/include/git2/index.h b/include/git2/index.h
index 4382c2f..1feeb6f 100644
--- a/include/git2/index.h
+++ b/include/git2/index.h
@@ -457,6 +457,38 @@ GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry);
 GIT_EXTERN(int) git_index_add_bypath(git_index *index, const char *path);
 
 /**
+ * Add or update an index entry from a buffer in memory
+ *
+ * This method will create a blob in the repository that owns the
+ * index and then add the index entry to the index.  The `path` of the
+ * entry represents the position of the blob relative to the
+ * repository's root folder.
+ *
+ * If a previous index entry exists that has the same path as the
+ * given 'entry', it will be replaced.  Otherwise, the 'entry' will be
+ * added. The `id` and the `file_size` of the 'entry' are updated with the
+ * real value of the blob.
+ *
+ * This forces the file to be added to the index, not looking
+ * at gitignore rules.  Those rules can be evaluated through
+ * the git_status APIs (in status.h) before calling this.
+ *
+ * If this file currently is the result of a merge conflict, this
+ * file will no longer be marked as conflicting.  The data about
+ * the conflict will be moved to the "resolve undo" (REUC) section.
+ *
+ * @param index an existing index object
+ * @param entry filename to add
+ * @param buffer data to be written into the blob
+ * @param len length of the data
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_add_frombuffer(
+	git_index *index,
+	git_index_entry *entry,
+	const void *buffer, size_t len);
+
+/**
  * Remove an index entry corresponding to a file on disk
  *
  * The file `path` must be relative to the repository's
diff --git a/src/index.c b/src/index.c
index c122cdf..9880e8f 100644
--- a/src/index.c
+++ b/src/index.c
@@ -1082,6 +1082,58 @@ static int index_conflict_to_reuc(git_index *index, const char *path)
 	return ret;
 }
 
+static bool valid_filemode(const int filemode)
+{
+	return (filemode == GIT_FILEMODE_BLOB ||
+		filemode == GIT_FILEMODE_BLOB_EXECUTABLE ||
+		filemode == GIT_FILEMODE_LINK ||
+		filemode == GIT_FILEMODE_COMMIT);
+}
+
+int git_index_add_frombuffer(
+    git_index *index, git_index_entry *source_entry,
+    const void *buffer, size_t len)
+{
+	git_index_entry *entry = NULL;
+	int error = 0;
+	git_oid id;
+
+	assert(index && source_entry->path);
+
+	if (INDEX_OWNER(index) == NULL)
+		return create_index_error(-1,
+			"Could not initialize index entry. "
+			"Index is not backed up by an existing repository.");
+
+	if (!valid_filemode(source_entry->mode)) {
+		giterr_set(GITERR_INDEX, "invalid filemode");
+		return -1;
+	}
+
+	if (index_entry_dup(&entry, INDEX_OWNER(index), source_entry) < 0)
+		return -1;
+
+	error = git_blob_create_frombuffer(&id, INDEX_OWNER(index), buffer, len);
+	if (error < 0) {
+		index_entry_free(entry);
+		return error;
+	}
+
+	git_oid_cpy(&entry->id, &id);
+	entry->file_size = len;
+
+	if ((error = index_insert(index, &entry, 1)) < 0)
+		return error;
+
+	/* Adding implies conflict was resolved, move conflict entries to REUC */
+	if ((error = index_conflict_to_reuc(index, entry->path)) < 0 && error != GIT_ENOTFOUND)
+		return error;
+
+	git_tree_cache_invalidate_path(index->tree, entry->path);
+	return 0;
+}
+
+
 int git_index_add_bypath(git_index *index, const char *path)
 {
 	git_index_entry *entry = NULL;
@@ -1116,14 +1168,6 @@ int git_index_remove_bypath(git_index *index, const char *path)
 	return 0;
 }
 
-static bool valid_filemode(const int filemode)
-{
-	return (filemode == GIT_FILEMODE_BLOB ||
-		filemode == GIT_FILEMODE_BLOB_EXECUTABLE ||
-		filemode == GIT_FILEMODE_LINK ||
-		filemode == GIT_FILEMODE_COMMIT);
-}
-
 
 int git_index_add(git_index *index, const git_index_entry *source_entry)
 {
diff --git a/tests/index/tests.c b/tests/index/tests.c
index 4cf7051..3c8060a 100644
--- a/tests/index/tests.c
+++ b/tests/index/tests.c
@@ -253,6 +253,128 @@ void test_index_tests__add(void)
 	git_repository_free(repo);
 }
 
+void test_index_tests__add_frombuffer(void)
+{
+	git_index *index;
+	git_repository *repo;
+        git_index_entry entry;
+	const git_index_entry *returned_entry;
+
+	git_oid id1;
+	git_blob *blob;
+
+	const char *content = "hey there\n";
+
+	cl_set_cleanup(&cleanup_myrepo, NULL);
+
+	/* Intialize a new repository */
+	cl_git_pass(git_repository_init(&repo, "./myrepo", 0));
+
+	/* Ensure we're the only guy in the room */
+	cl_git_pass(git_repository_index(&index, repo));
+	cl_assert(git_index_entrycount(index) == 0);
+
+	/* Store the expected hash of the file/blob
+	 * This has been generated by executing the following
+	 * $ echo "hey there" | git hash-object --stdin
+	 */
+	cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6"));
+
+	/* Add the new file to the index */
+	memset(&entry, 0x0, sizeof(git_index_entry));
+	entry.mode = GIT_FILEMODE_BLOB;
+	entry.path = "test.txt";
+	cl_git_pass(git_index_add_frombuffer(index, &entry,
+		content, strlen(content)));
+
+	/* Wow... it worked! */
+	cl_assert(git_index_entrycount(index) == 1);
+	returned_entry = git_index_get_byindex(index, 0);
+
+	/* And the built-in hashing mechanism worked as expected */
+	cl_assert_equal_oid(&id1, &returned_entry->id);
+	/* And mode is the one asked */
+	cl_assert_equal_i(GIT_FILEMODE_BLOB, returned_entry->mode);
+
+	/* Test access by path instead of index */
+	cl_assert((returned_entry = git_index_get_bypath(index, "test.txt", 0)) != NULL);
+	cl_assert_equal_oid(&id1, &returned_entry->id);
+
+	/* Test the blob is in the repository */
+	cl_git_pass(git_blob_lookup(&blob, repo, &id1));
+	cl_assert_equal_s(
+		content, git_blob_rawcontent(blob));
+	git_blob_free(blob);
+
+	git_index_free(index);
+	git_repository_free(repo);
+}
+
+void test_index_tests__add_frombuffer_reset_entry(void)
+{
+	git_index *index;
+	git_repository *repo;
+        git_index_entry entry;
+	const git_index_entry *returned_entry;
+	git_filebuf file = GIT_FILEBUF_INIT;
+
+	git_oid id1;
+	git_blob *blob;
+	const char *old_content = "here\n";
+	const char *content = "hey there\n";
+
+	cl_set_cleanup(&cleanup_myrepo, NULL);
+
+	/* Intialize a new repository */
+	cl_git_pass(git_repository_init(&repo, "./myrepo", 0));
+	cl_git_pass(git_repository_index(&index, repo));
+	cl_git_pass(git_futils_mkpath2file("myrepo/test.txt", 0777));
+	cl_git_pass(git_filebuf_open(&file, "myrepo/test.txt", 0, 0666));
+	cl_git_pass(git_filebuf_write(&file, old_content, strlen(old_content)));
+	cl_git_pass(git_filebuf_commit(&file));
+
+	/* Store the expected hash of the file/blob
+	 * This has been generated by executing the following
+	 * $ echo "hey there" | git hash-object --stdin
+	 */
+	cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6"));
+
+	cl_git_pass(git_index_add_bypath(index, "test.txt"));
+
+	/* Add the new file to the index */
+	memset(&entry, 0x0, sizeof(git_index_entry));
+	entry.mode = GIT_FILEMODE_BLOB;
+	entry.path = "test.txt";
+	cl_git_pass(git_index_add_frombuffer(index, &entry,
+		content, strlen(content)));
+
+	/* Wow... it worked! */
+	cl_assert(git_index_entrycount(index) == 1);
+	returned_entry = git_index_get_byindex(index, 0);
+
+	/* And the built-in hashing mechanism worked as expected */
+	cl_assert_equal_oid(&id1, &returned_entry->id);
+	/* And mode is the one asked */
+	cl_assert_equal_i(GIT_FILEMODE_BLOB, returned_entry->mode);
+
+	/* Test access by path instead of index */
+	cl_assert((returned_entry = git_index_get_bypath(index, "test.txt", 0)) != NULL);
+	cl_assert_equal_oid(&id1, &returned_entry->id);
+	cl_assert_equal_i(0, returned_entry->dev);
+	cl_assert_equal_i(0, returned_entry->ino);
+	cl_assert_equal_i(0, returned_entry->uid);
+	cl_assert_equal_i(0, returned_entry->uid);
+	cl_assert_equal_i(10, returned_entry->file_size);
+
+            /* Test the blob is in the repository */
+	cl_git_pass(git_blob_lookup(&blob, repo, &id1));
+	cl_assert_equal_s(content, git_blob_rawcontent(blob));
+	git_blob_free(blob);
+
+	git_index_free(index);
+	git_repository_free(repo);
+}
+
 static void cleanup_1397(void *opaque)
 {
 	GIT_UNUSED(opaque);