Commit 5774b2b13468aa3c2e7e604dd348357f6842c56a

Edward Thomson 2019-08-11T23:42:45

Merge pull request #5113 from pks-t/pks/stash-perf stash: avoid recomputing tree when committing worktree

diff --git a/examples/common.h b/examples/common.h
index bba8321..2ad897b 100644
--- a/examples/common.h
+++ b/examples/common.h
@@ -70,6 +70,7 @@ extern int lg2_remote(git_repository *repo, int argc, char **argv);
 extern int lg2_rev_list(git_repository *repo, int argc, char **argv);
 extern int lg2_rev_parse(git_repository *repo, int argc, char **argv);
 extern int lg2_show_index(git_repository *repo, int argc, char **argv);
+extern int lg2_stash(git_repository *repo, int argc, char **argv);
 extern int lg2_status(git_repository *repo, int argc, char **argv);
 extern int lg2_tag(git_repository *repo, int argc, char **argv);
 
diff --git a/examples/lg2.c b/examples/lg2.c
index 4ee0110..f1a8843 100644
--- a/examples/lg2.c
+++ b/examples/lg2.c
@@ -31,6 +31,7 @@ struct {
 	{ "rev-list",     lg2_rev_list,     1 },
 	{ "rev-parse",    lg2_rev_parse,    1 },
 	{ "show-index",   lg2_show_index,   0 },
+	{ "stash",        lg2_stash,        1 },
 	{ "status",       lg2_status,       1 },
 	{ "tag",          lg2_tag,          1 },
 };
diff --git a/examples/stash.c b/examples/stash.c
new file mode 100644
index 0000000..8142439
--- /dev/null
+++ b/examples/stash.c
@@ -0,0 +1,157 @@
+/*
+ * libgit2 "stash" example - shows how to use the stash API
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include <stdarg.h>
+
+#include "common.h"
+
+enum subcmd {
+	SUBCMD_APPLY,
+	SUBCMD_LIST,
+	SUBCMD_POP,
+	SUBCMD_PUSH
+};
+
+struct opts {
+	enum subcmd cmd;
+	int argc;
+	char **argv;
+};
+
+static void usage(const char *fmt, ...)
+{
+	va_list ap;
+
+	fputs("usage: git stash list\n", stderr);
+	fputs("   or: git stash ( pop | apply )\n", stderr);
+	fputs("   or: git stash [push]\n", stderr);
+	fputs("\n", stderr);
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+
+	exit(1);
+}
+
+static void parse_subcommand(struct opts *opts, int argc, char *argv[])
+{
+	char *arg = (argc < 2) ? "push" : argv[1];
+	enum subcmd cmd;
+
+	if (!strcmp(arg, "apply")) {
+		cmd = SUBCMD_APPLY;
+	} else if (!strcmp(arg, "list")) {
+		cmd = SUBCMD_LIST;
+	} else if (!strcmp(arg, "pop")) {
+		cmd = SUBCMD_POP;
+	} else if (!strcmp(arg, "push")) {
+		cmd = SUBCMD_PUSH;
+	} else {
+		usage("invalid command %s", arg);
+		return;
+	}
+
+	opts->cmd = cmd;
+	opts->argc = (argc < 2) ? argc - 1 : argc - 2;
+	opts->argv = argv;
+}
+
+static int cmd_apply(git_repository *repo, struct opts *opts)
+{
+	if (opts->argc)
+		usage("apply does not accept any parameters");
+
+	check_lg2(git_stash_apply(repo, 0, NULL),
+		  "Unable to apply stash", NULL);
+
+	return 0;
+}
+
+static int list_stash_cb(size_t index, const char *message,
+			 const git_oid *stash_id, void *payload)
+{
+	UNUSED(stash_id);
+	UNUSED(payload);
+	printf("stash@{%"PRIuZ"}: %s\n", index, message);
+	return 0;
+}
+
+static int cmd_list(git_repository *repo, struct opts *opts)
+{
+	if (opts->argc)
+		usage("list does not accept any parameters");
+
+	check_lg2(git_stash_foreach(repo, list_stash_cb, NULL),
+		  "Unable to list stashes", NULL);
+
+	return 0;
+}
+
+static int cmd_push(git_repository *repo, struct opts *opts)
+{
+	git_signature *signature;
+	git_commit *stash;
+	git_oid stashid;
+
+	if (opts->argc)
+		usage("push does not accept any parameters");
+
+	check_lg2(git_signature_default(&signature, repo),
+		  "Unable to get signature", NULL);
+	check_lg2(git_stash_save(&stashid, repo, signature, NULL, GIT_STASH_DEFAULT),
+		  "Unable to save stash", NULL);
+	check_lg2(git_commit_lookup(&stash, repo, &stashid),
+		  "Unable to lookup stash commit", NULL);
+
+	printf("Saved working directory %s\n", git_commit_summary(stash));
+
+	git_signature_free(signature);
+	git_commit_free(stash);
+
+	return 0;
+}
+
+static int cmd_pop(git_repository *repo, struct opts *opts)
+{
+	if (opts->argc)
+		usage("pop does not accept any parameters");
+
+	check_lg2(git_stash_pop(repo, 0, NULL),
+		  "Unable to pop stash", NULL);
+
+	printf("Dropped refs/stash@{0}\n");
+
+	return 0;
+}
+
+int lg2_stash(git_repository *repo, int argc, char *argv[])
+{
+	struct opts opts = { 0 };
+
+	parse_subcommand(&opts, argc, argv);
+
+	switch (opts.cmd) {
+		case SUBCMD_APPLY:
+			return cmd_apply(repo, &opts);
+		case SUBCMD_LIST:
+			return cmd_list(repo, &opts);
+		case SUBCMD_PUSH:
+			return cmd_push(repo, &opts);
+		case SUBCMD_POP:
+			return cmd_pop(repo, &opts);
+	}
+
+	return -1;
+}
diff --git a/src/stash.c b/src/stash.c
index 2d61e68..aa3cecf 100644
--- a/src/stash.c
+++ b/src/stash.c
@@ -398,28 +398,23 @@ static int commit_worktree(
 	git_commit *b_commit,
 	git_commit *u_commit)
 {
-	int error = 0;
-	git_tree *w_tree = NULL, *i_tree = NULL;
-	git_index *i_index = NULL;
-	const git_commit *parents[] = {	NULL, NULL,	NULL };
-	int ignorecase;
+	const git_commit *parents[] = {	NULL, NULL, NULL };
+	git_index *i_index = NULL, *r_index = NULL;
+	git_tree *w_tree = NULL;
+	int error = 0, ignorecase;
 
 	parents[0] = b_commit;
 	parents[1] = i_commit;
 	parents[2] = u_commit;
 
-	if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
-		goto cleanup;
-
-	if ((error = git_index_new(&i_index)) < 0 ||
-		(error = git_repository__configmap_lookup(&ignorecase, repo, GIT_CONFIGMAP_IGNORECASE)) < 0)
+	if ((error = git_repository_index(&r_index, repo) < 0) ||
+	    (error = git_index_new(&i_index)) < 0 ||
+	    (error = git_index__fill(i_index, &r_index->entries) < 0) ||
+	    (error = git_repository__configmap_lookup(&ignorecase, repo, GIT_CONFIGMAP_IGNORECASE)) < 0)
 		goto cleanup;
 
 	git_index__set_ignore_case(i_index, ignorecase);
 
-	if ((error = git_index_read_tree(i_index, i_tree)) < 0)
-		goto cleanup;
-
 	if ((error = build_workdir_tree(&w_tree, repo, i_index, b_commit)) < 0)
 		goto cleanup;
 
@@ -436,9 +431,9 @@ static int commit_worktree(
 		parents);
 
 cleanup:
-	git_tree_free(i_tree);
 	git_tree_free(w_tree);
 	git_index_free(i_index);
+	git_index_free(r_index);
 	return error;
 }