Commit cf300bb9e50c65e4140f7e204243b34f2898fa95

Russell Belfer 2013-06-20T11:39:31

Initial implementation of status example

diff --git a/examples/Makefile b/examples/Makefile
index c5d5555..140cc4d 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -3,7 +3,7 @@
 CC = gcc
 CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers
 LFLAGS = -L../build -lgit2 -lz
-APPS = general showindex diff rev-list cat-file
+APPS = general showindex diff rev-list cat-file status
 
 all: $(APPS)
 
diff --git a/examples/status.c b/examples/status.c
new file mode 100644
index 0000000..2378c78
--- /dev/null
+++ b/examples/status.c
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2011-2012 the libgit2 contributors
+ *
+ * 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 <git2.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+enum {
+	FORMAT_DEFAULT   = 0,
+	FORMAT_LONG      = 1,
+	FORMAT_SHORT     = 2,
+	FORMAT_PORCELAIN = 3,
+};
+#define MAX_PATHSPEC 8
+
+/*
+ * This example demonstrates the use of `git_status_foreach()` to roughly
+ * simulate the output of running `git status`.  It should serve as a simple
+ * example of how to get basic status information.
+ *
+ * This does not have:
+ * - Robust error handling
+ * - Any real command line parsing
+ * - Colorized or paginated output formatting
+ *
+ */
+
+static void check(int error, const char *message, const char *extra)
+{
+	const git_error *lg2err;
+	const char *lg2msg = "", *lg2spacer = "";
+
+	if (!error)
+		return;
+
+	if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) {
+		lg2msg = lg2err->message;
+		lg2spacer = " - ";
+	}
+
+	if (extra)
+		fprintf(stderr, "%s '%s' [%d]%s%s\n",
+			message, extra, error, lg2spacer, lg2msg);
+	else
+		fprintf(stderr, "%s [%d]%s%s\n",
+			message, error, lg2spacer, lg2msg);
+
+	exit(1);
+}
+
+static void fail(const char *message)
+{
+	check(-1, message, NULL);
+}
+
+static void show_branch(git_repository *repo, int format)
+{
+	int error = 0;
+	const char *branch = NULL;
+	git_reference *head = NULL;
+
+	error = git_repository_head(&head, repo);
+
+	if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND)
+		branch = NULL;
+	else if (!error) {
+		branch = git_reference_name(head);
+		if (!strncmp(branch, "refs/heads/", strlen("refs/heads/")))
+			branch += strlen("refs/heads/");
+	} else
+		check(error, "failed to get current branch", NULL);
+
+	if (format == FORMAT_LONG)
+		printf("# %s\n", branch ? branch : "Not currently on any branch.");
+	else
+		printf("## %s\n", branch ? branch : "HEAD (no branch)");
+
+	git_reference_free(head);
+}
+
+static void print_long(git_repository *repo, git_status_list *status)
+{
+	(void)repo;
+	(void)status;
+}
+
+static void print_short(git_repository *repo, git_status_list *status)
+{
+	size_t i, maxi = git_status_list_entrycount(status);
+	const git_status_entry *s;
+	char istatus, wstatus;
+	const char *extra, *a, *b, *c;
+
+	for (i = 0; i < maxi; ++i) {
+		s = git_status_byindex(status, i);
+
+		if (s->status == GIT_STATUS_CURRENT)
+			continue;
+
+		a = b = c = NULL;
+		istatus = wstatus = ' ';
+		extra = "";
+
+		if (s->status & GIT_STATUS_INDEX_NEW)
+			istatus = 'A';
+		if (s->status & GIT_STATUS_INDEX_MODIFIED)
+			istatus = 'M';
+		if (s->status & GIT_STATUS_INDEX_DELETED)
+			istatus = 'D';
+		if (s->status & GIT_STATUS_INDEX_RENAMED)
+			istatus = 'R';
+		if (s->status & GIT_STATUS_INDEX_TYPECHANGE)
+			istatus = 'T';
+
+		if (s->status & GIT_STATUS_WT_NEW) {
+			if (istatus == ' ')
+				istatus = '?';
+			wstatus = '?';
+		}
+		if (s->status & GIT_STATUS_WT_MODIFIED)
+			wstatus = 'M';
+		if (s->status & GIT_STATUS_WT_DELETED)
+			wstatus = 'D';
+		if (s->status & GIT_STATUS_WT_RENAMED)
+			wstatus = 'R';
+		if (s->status & GIT_STATUS_WT_TYPECHANGE)
+			wstatus = 'T';
+
+		if (s->status & GIT_STATUS_IGNORED) {
+			istatus = '!';
+			wstatus = '!';
+		}
+
+		if (istatus == '?' && wstatus == '?')
+			continue;
+
+		if (s->index_to_workdir &&
+			s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT)
+		{
+			git_submodule *sm = NULL;
+			unsigned int smstatus = 0;
+
+			if (!git_submodule_lookup(
+					&sm, repo, s->index_to_workdir->new_file.path) &&
+				!git_submodule_status(&smstatus, sm))
+			{
+				if (smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED)
+					extra = " (new commits)";
+				else if (smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED)
+					extra = " (modified content)";
+				else if (smstatus & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED)
+					extra = " (modified content)";
+				else if (smstatus & GIT_SUBMODULE_STATUS_WD_UNTRACKED)
+					extra = " (untracked content)";
+			}
+		}
+
+		if (s->head_to_index) {
+			a = s->head_to_index->old_file.path;
+			b = s->head_to_index->new_file.path;
+		}
+		if (s->index_to_workdir) {
+			if (!a)
+				a = s->index_to_workdir->old_file.path;
+			if (!b)
+				b = s->index_to_workdir->old_file.path;
+			c = s->index_to_workdir->new_file.path;
+		}
+
+		if (istatus == 'R') {
+			if (wstatus == 'R')
+				printf("%c%c %s %s %s%s\n", istatus, wstatus, a, b, c, extra);
+			else
+				printf("%c%c %s %s%s\n", istatus, wstatus, a, b, extra);
+		} else {
+			if (wstatus == 'R')
+				printf("%c%c %s %s%s\n", istatus, wstatus, a, c, extra);
+			else
+				printf("%c%c %s%s\n", istatus, wstatus, a, extra);
+		}
+	}
+
+	for (i = 0; i < maxi; ++i) {
+		s = git_status_byindex(status, i);
+
+		if (s->status == GIT_STATUS_WT_NEW)
+			printf("?? %s\n", s->index_to_workdir->old_file.path);
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	git_repository *repo = NULL;
+	int i, npaths = 0, format = FORMAT_DEFAULT, zterm = 0, showbranch = 0;
+	git_status_options opt = GIT_STATUS_OPTIONS_INIT;
+	git_status_list *status;
+	char *repodir = ".", *pathspec[MAX_PATHSPEC];
+
+	opt.show  = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+	opt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+		GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
+
+	for (i = 1; i < argc; ++i) {
+		if (argv[i][0] != '-') {
+			if (npaths < MAX_PATHSPEC)
+				pathspec[npaths++] = argv[i];
+			else
+				fail("Example only supports a limited pathspec");
+		}
+		else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--short"))
+			format = FORMAT_SHORT;
+		else if (!strcmp(argv[i], "--long"))
+			format = FORMAT_LONG;
+		else if (!strcmp(argv[i], "--porcelain"))
+			format = FORMAT_PORCELAIN;
+		else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--branch"))
+			showbranch = 1;
+		else if (!strcmp(argv[i], "-z")) {
+			zterm = 1;
+			if (format == FORMAT_DEFAULT)
+				format = FORMAT_PORCELAIN;
+		}
+		else if (!strcmp(argv[i], "--ignored"))
+			opt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
+		else if (!strcmp(argv[i], "-uno") ||
+				 !strcmp(argv[i], "--untracked-files=no"))
+			opt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+		else if (!strcmp(argv[i], "-unormal") ||
+				 !strcmp(argv[i], "--untracked-files=normal"))
+			opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+		else if (!strcmp(argv[i], "-uall") ||
+				 !strcmp(argv[i], "--untracked-files=all"))
+			opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+				GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+		else if (!strcmp(argv[i], "--ignore-submodules=all"))
+			opt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
+		else if (!strncmp(argv[i], "--git-dir=", strlen("--git-dir=")))
+			repodir = argv[i] + strlen("--git-dir=");
+		else
+			check(-1, "Unsupported option", argv[i]);
+	}
+
+	if (format == FORMAT_DEFAULT)
+		format = FORMAT_LONG;
+	if (format == FORMAT_LONG)
+		showbranch = 1;
+	if (npaths > 0) {
+		opt.pathspec.strings = pathspec;
+		opt.pathspec.count   = npaths;
+	}
+
+	/*
+	 * Try to open the repository at the given path (or at the current
+	 * directory if none was given).
+	 */
+	check(git_repository_open_ext(&repo, repodir, 0, NULL),
+		  "Could not open repository", repodir);
+
+	if (git_repository_is_bare(repo))
+		fail("Cannot report status on bare repository");
+
+	/*
+	 * Run status on the repository
+	 *
+	 * Because we want to simluate a full "git status" run and want to
+	 * support some command line options, we use `git_status_foreach_ext()`
+	 * instead of just the plain status call.  This allows (a) iterating
+	 * over the index and then the workdir and (b) extra flags that control
+	 * which files are included.  If you just want simple status (e.g. to
+	 * enumerate files that are modified) then you probably don't need the
+	 * extended API.
+	 */
+	check(git_status_list_new(&status, repo, &opt),
+		  "Could not get status", NULL);
+
+	if (showbranch)
+		show_branch(repo, format);
+
+	if (format == FORMAT_LONG)
+		print_long(repo, status);
+	else
+		print_short(repo, status);
+
+	git_status_list_free(status);
+	git_repository_free(repo);
+
+	return 0;
+}
+