Commit 29e1789b34d9aa5e486ca12937899a43f2e2407b

Vicent Marti 2011-04-04T12:14:03

Fix the git_tree_write implementation

diff --git a/include/git2/tree.h b/include/git2/tree.h
index bd1f970..60150b3 100644
--- a/include/git2/tree.h
+++ b/include/git2/tree.h
@@ -134,21 +134,27 @@ GIT_EXTERN(const git_oid *) git_tree_entry_id(git_tree_entry *entry);
  * @param object pointer to the converted object
  * @param repo repository where to lookup the pointed object
  * @param entry a tree entry
- * @return a reference to the pointed object in the repository
+ * @return 0 on success; error code otherwise
  */
 GIT_EXTERN(int) git_tree_entry_2object(git_object **object_out, git_repository *repo, git_tree_entry *entry);
 
 /**
- * Create tree by scanning the index file.
- *
- * @param object identity for the tree
- * @param repository where to lookup for object db
- * @param base directory path
- * @param path length for base
- * @param index entry offset to start scan
- * @return number of items added to the tree
+ * Write a tree to the ODB from the index file
+ *
+ * This method will scan the index and write a representation
+ * of its current state back to disk; it recursively creates
+ * tree objects for each of the subtrees stored in the index,
+ * but only returns the OID of the root tree. This is the OID
+ * that can be used e.g. to create a commit.
+ *
+ * The index instance cannot be bare, and needs to be associated
+ * to an existing repository.
+ *
+ * @param oid Pointer where to store the written tree
+ * @param index Index to write
+ * @return 0 on success; error code otherwise
  */
-GIT_EXTERN(int) git_tree_create(git_oid *oid, git_repository *repo, const char *base, int baselen, int entry_no);
+GIT_EXTERN(int) git_tree_create_fromindex(git_oid *oid, git_index *index);
 
 /** @} */
 GIT_END_DECL
diff --git a/src/tree.c b/src/tree.c
index 299fc69..588b5e9 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -137,7 +137,7 @@ static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buf
 	while (buffer < buffer_end) {
 		git_tree_entry *entry;
 
-		entry = git__malloc(sizeof(git_tree_entry));
+		entry = git__calloc(1, sizeof(git_tree_entry));
 		if (entry == NULL) {
 			error = GIT_ENOMEM;
 			break;
@@ -178,90 +178,98 @@ int git_tree__parse(git_tree *tree, git_odb_object *obj)
 	return tree_parse_buffer(tree, (char *)obj->raw.data, (char *)obj->raw.data + obj->raw.len);
 }
 
-int git_tree_create(git_oid *oid, git_repository *repo, const char *base, int baselen, int entry_no)
+static int write_entry(char *buffer, int mode, const char *path, size_t path_len, const git_oid *oid)
 {
-	unsigned long size, offset;
+	int written;
+	written = sprintf(buffer, "%o %.*s%c", mode, (int)path_len, path, 0);
+	memcpy(buffer + written, &oid->id, GIT_OID_RAWSZ);
+	return written + GIT_OID_RAWSZ;
+}
+
+static int write_index(git_oid *oid, git_index *index, const char *base, int baselen, int entry_no, int maxentries)
+{
+	size_t size, offset;
 	char *buffer;
-	int nr, maxentries;
-	git_index *index;
-	git_odb_stream *stream;
-	int error;	
-	
-	git_index_open_inrepo(&index,repo);
-	maxentries = git_index_entrycount (index);
-	
-	/* FIXME: A matching error code could not be found. Hence used a close error code GIT_EBAREINDEX */
-	if (maxentries <= 0) {
-		return GIT_EBAREINDEX;
-	}
-	
+	int nr, error;
+
 	/* Guess at some random initial size */
-	size = 8192;
+	size = maxentries * 40;
 	buffer = git__malloc(size);
 	if (buffer == NULL)
 		return GIT_ENOMEM;
 		
 	offset = 0;
-	nr = entry_no;
 	
-	do {
+	for (nr = entry_no; nr < maxentries; ++nr) {
 		git_index_entry *entry = git_index_get(index, nr);
+
 		const char *pathname = entry->path, *filename, *dirname;
 		int pathlen = strlen(pathname), entrylen;
-		git_oid *entry_oid;
-		unsigned int mode;
-		unsigned char *sha1;
+
+		unsigned int write_mode;
+		git_oid subtree_oid;
+		git_oid *write_oid;
 		
 		/* Did we hit the end of the directory? Return how many we wrote */
-		if (baselen >= pathlen || memcmp(base, pathname, baselen))
+		if (baselen >= pathlen || memcmp(base, pathname, baselen) != 0)
 			break;
 		
-		entry_oid = &entry->oid;
-		mode = entry->mode;
-		sha1 = entry_oid->id;
-		
 		/* Do we have _further_ subdirectories? */
 		filename = pathname + baselen;
 		dirname = strchr(filename, '/');
+
+		write_oid = &entry->oid;
+		write_mode = entry->mode;
+
 		if (dirname) {
 			int subdir_written;
-			subdir_written = git_tree_create(oid, repo, pathname, dirname-pathname+1, nr);
-			
-			if (subdir_written == GIT_ENOMEM) 
-				return GIT_ENOMEM;
+
+#if 0
+			if (entry->mode != S_IFDIR) {
+				free(buffer);
+				return GIT_EOBJCORRUPTED;
+			}
+#endif
+			subdir_written = write_index(&subtree_oid, index, pathname, dirname - pathname + 1, nr, maxentries);
+
+			if (subdir_written < GIT_SUCCESS) {
+				free(buffer);
+				return subdir_written;
+			}
 			
-			nr += subdir_written;
+			nr = subdir_written - 1;
 			
 			/* Now we need to write out the directory entry into this tree.. */
-			mode = S_IFDIR;
 			pathlen = dirname - pathname;
-			
-			/* ..but the directory entry doesn't count towards the total count */
-			nr--;
-			sha1 = oid->id;
+			write_oid = &subtree_oid;
+			write_mode = S_IFDIR;
 		}
-		
+
 		entrylen = pathlen - baselen;
-		if (offset + entrylen + 100 > size) {
-			size = alloc_nr(offset + entrylen + 100); 
+		if (offset + entrylen + 32 > size) {
+			size = alloc_nr(offset + entrylen + 32);
 			buffer = git__realloc(buffer, size);
 			
 			if (buffer == NULL)
 				return GIT_ENOMEM;
 		}
-		offset += sprintf(buffer + offset, "%o %.*s", mode, entrylen, filename);
-		buffer[offset++] = 0;
-		memcpy(buffer + offset, sha1, GIT_OID_RAWSZ);
-		offset += GIT_OID_RAWSZ;
-		nr++;
-	} while (nr < maxentries);
-	
-	if ((error = git_odb_open_wstream(&stream, repo->db, offset, GIT_OBJ_TREE)) < GIT_SUCCESS)
-		return error;
+
+		offset += write_entry(buffer + offset, write_mode, filename, entrylen, write_oid);
+	}
 	
-	stream->write(stream, buffer, offset);
-	error = stream->finalize_write(oid, stream);
-	stream->free(stream);
+	error = git_odb_write(oid, index->repository->db, buffer, offset, GIT_OBJ_TREE);
+	free(buffer);
+
+	return (error == GIT_SUCCESS) ? nr : error;
+}
+
+int git_tree_create_fromindex(git_oid *oid, git_index *index)
+{
+	int error;
+
+	if (index->repository == NULL)
+		return GIT_EBAREINDEX;
 
-	return nr;
+	error = write_index(oid, index, "", 0, 0, git_index_entrycount(index));
+	return (error < GIT_SUCCESS) ? error : GIT_SUCCESS;
 }