Commit d73043a27aaae4f7608de1a85e426bf89ea7fba3

Edward Thomson 2018-03-19T20:10:31

reader: a generic way to read files from repos Similar to the `git_iterator` interface, the `git_reader` interface will allow us to read file contents from an arbitrary repository-backed data source (trees, index, or working directory).

diff --git a/src/reader.c b/src/reader.c
new file mode 100644
index 0000000..754b90a
--- /dev/null
+++ b/src/reader.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "reader.h"
+
+#include "fileops.h"
+#include "blob.h"
+
+#include "git2/tree.h"
+#include "git2/blob.h"
+#include "git2/index.h"
+#include "git2/repository.h"
+
+/* tree reader */
+
+typedef struct {
+	git_reader reader;
+	git_tree *tree;
+} tree_reader;
+
+static int tree_reader_read(
+	git_buf *out,
+	git_reader *_reader,
+	const char *filename)
+{
+	tree_reader *reader = (tree_reader *)_reader;
+	git_tree_entry *tree_entry = NULL;
+	git_blob *blob = NULL;
+	int error;
+
+	if ((error = git_tree_entry_bypath(&tree_entry, reader->tree, filename)) < 0 ||
+	    (error = git_blob_lookup(&blob, git_tree_owner(reader->tree), git_tree_entry_id(tree_entry))) < 0 ||
+	    (error = git_buf_set(out, git_blob_rawcontent(blob), git_blob_rawsize(blob))) < 0)
+		goto done;
+
+done:
+	git_blob_free(blob);
+	git_tree_entry_free(tree_entry);
+	return error;
+}
+
+static void tree_reader_free(git_reader *_reader)
+{
+	GIT_UNUSED(_reader);
+}
+
+int git_reader_for_tree(git_reader **out, git_tree *tree)
+{
+	tree_reader *reader;
+
+	assert(out && tree);
+
+	reader = git__calloc(1, sizeof(tree_reader));
+	GITERR_CHECK_ALLOC(reader);
+
+	reader->reader.read = tree_reader_read;
+	reader->reader.free = tree_reader_free;
+	reader->tree = tree;
+
+	*out = (git_reader *)reader;
+	return 0;
+}
+
+/* workdir reader */
+
+typedef struct {
+	git_reader reader;
+	git_repository *repo;
+} workdir_reader;
+
+static int workdir_reader_read(
+	git_buf *out,
+	git_reader *_reader,
+	const char *filename)
+{
+	workdir_reader *reader = (workdir_reader *)_reader;
+	git_buf path = GIT_BUF_INIT;
+	int error;
+
+	if ((error = git_buf_joinpath(&path,
+		git_repository_workdir(reader->repo), filename)) < 0)
+		goto done;
+
+	/* TODO: should we read the filtered data? */
+	error = git_futils_readbuffer(out, path.ptr);
+
+done:
+	git_buf_dispose(&path);
+	return error;
+}
+
+static void workdir_reader_free(git_reader *_reader)
+{
+	GIT_UNUSED(_reader);
+}
+
+int git_reader_for_workdir(git_reader **out, git_repository *repo)
+{
+	workdir_reader *reader;
+
+	assert(out && repo);
+
+	reader = git__calloc(1, sizeof(workdir_reader));
+	GITERR_CHECK_ALLOC(reader);
+
+	reader->reader.read = workdir_reader_read;
+	reader->reader.free = workdir_reader_free;
+	reader->repo = repo;
+
+	*out = (git_reader *)reader;
+	return 0;
+}
+
+/* index reader */
+
+typedef struct {
+	git_reader reader;
+	git_repository *repo;
+	git_index *index;
+} index_reader;
+
+static int index_reader_read(
+	git_buf *out,
+	git_reader *_reader,
+	const char *filename)
+{
+	index_reader *reader = (index_reader *)_reader;
+	const git_index_entry *entry;
+	git_blob *blob;
+	int error;
+
+	if ((entry = git_index_get_bypath(reader->index, filename, 0)) == NULL)
+		return GIT_ENOTFOUND;
+
+	if ((error = git_blob_lookup(&blob, reader->repo, &entry->id)) < 0)
+		goto done;
+
+	error = git_blob__getbuf(out, blob);
+
+done:
+	git_blob_free(blob);
+	return error;
+}
+
+static void index_reader_free(git_reader *_reader)
+{
+	GIT_UNUSED(_reader);
+}
+
+int git_reader_for_index(
+	git_reader **out,
+	git_repository *repo,
+	git_index *index)
+{
+	index_reader *reader;
+	int error;
+
+	assert(out && repo);
+
+	reader = git__calloc(1, sizeof(index_reader));
+	GITERR_CHECK_ALLOC(reader);
+
+	reader->reader.read = index_reader_read;
+	reader->reader.free = index_reader_free;
+	reader->repo = repo;
+
+	if (index) {
+		reader->index = index;
+	} else {
+		error = git_repository_index__weakptr(&reader->index, repo);
+
+		if (error < 0) {
+			git__free(reader);
+			return error;
+		}
+	}
+
+	*out = (git_reader *)reader;
+	return 0;
+}
+
+/* generic */
+
+int git_reader_read(git_buf *out, git_reader *reader, const char *filename)
+{
+	assert(out && reader && filename);
+
+	return reader->read(out, reader, filename);
+}
+
+void git_reader_free(git_reader *reader)
+{
+	if (!reader)
+		return;
+
+	reader->free(reader);
+	git__free(reader);
+}
diff --git a/src/reader.h b/src/reader.h
new file mode 100644
index 0000000..18c7f59
--- /dev/null
+++ b/src/reader.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_reader_h__
+#define INCLUDE_reader_h__
+
+#include "common.h"
+
+typedef struct git_reader git_reader;
+
+/*
+ * The `git_reader` structure is a generic interface for reading the
+ * contents of a file by its name, and implementations are provided
+ * for reading out of a tree, the index, and the working directory.
+ *
+ * Note that the reader implementation is meant to have a short
+ * lifecycle and does not increase the refcount of the object that
+ * it's reading.  Callers should ensure that they do not use a
+ * reader after disposing the underlying object that it reads.
+ */
+struct git_reader {
+	int (*read)(git_buf *out, git_reader *reader, const char *filename);
+	void (*free)(git_reader *reader);
+};
+
+/**
+ * Create a `git_reader` that will allow random access to the given
+ * tree.  Paths requested via `git_reader_read` will be rooted at this
+ * tree, callers are not expected to recurse through tree lookups.  Thus,
+ * you can request to read `/src/foo.c` and the tree provided to this
+ * function will be searched to find another tree named `src`, which
+ * will then be opened to find `foo.c`.
+ *
+ * @param out The reader for the given tree
+ * @param tree The tree object to read
+ * @return 0 on success, or an error code < 0
+ */
+extern int git_reader_for_tree(
+	git_reader **out,
+	git_tree *tree);
+
+/**
+ * Create a `git_reader` that will allow random access to the given
+ * index, or the repository's index.
+ *
+ * @param out The reader for the given index
+ * @param repo The repository containing the index
+ * @param index The index to read, or NULL to use the repository's index
+ * @return 0 on success, or an error code < 0
+ */
+extern int git_reader_for_index(
+	git_reader **out,
+	git_repository *repo,
+	git_index *index);
+
+/**
+ * Create a `git_reader` that will allow random access to the given
+ * repository's working directory.  Note that the contents are read
+ * in repository format, meaning any workdir -> odb filters are
+ * applied.
+ *
+ * If `validate_index` is set to true, reads of files will hash the
+ * on-disk contents and ensure that the resulting object ID matches
+ * the repository's index.  This ensures that the working directory
+ * is unmodified from the index contents.
+ *
+ * @param out The reader for the given working directory
+ * @param repo The repository containing the working directory
+ * @param validate_index If true, the working directory contents will
+ *        be compared to the index contents during read to ensure that
+ *        the working directory is unmodified.
+ * @return 0 on success, or an error code < 0
+ */
+extern int git_reader_for_workdir(
+	git_reader **out,
+	git_repository *repo);
+
+/**
+ * Read the given filename from the reader and populate the given buffer
+ * with the contents and the given oid with the object ID.
+ *
+ * @param out The buffer to populate with the file contents
+ * @param out_id The oid to populate with the object ID
+ * @param reader The reader to read
+ * @param filename The filename to read from the reader
+ */
+extern int git_reader_read(
+	git_buf *out,
+	git_reader *reader,
+	const char *filename);
+
+/**
+ * Free the given reader and any associated objects.
+ *
+ * @param reader The reader to free
+ */
+extern void git_reader_free(git_reader *reader);
+
+#endif