]> git.proxmox.com Git - libgit2.git/blobdiff - examples/status.c
New upstream version 1.4.3+dfsg.1
[libgit2.git] / examples / status.c
index 689098415e3891a0550ae30586681982fd9d31ac..e659efb059b15a912ac3062ede4ce8e7732ec946 100644 (file)
@@ -1,33 +1,32 @@
 /*
- * Copyright (C) the libgit2 contributors. All rights reserved.
+ * libgit2 "status" example - shows how to use the status APIs
  *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
+ * 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 <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
+#include "common.h"
 
-/*
+/**
  * This example demonstrates the use of the libgit2 status APIs,
  * particularly the `git_status_list` object, to roughly simulate the
  * output of running `git status`.  It serves as a simple example of
  * using those APIs to get basic status information.
  *
  * This does not have:
+ *
  * - Robust error handling
  * - Colorized or paginated output formatting
  *
  * This does have:
+ *
  * - Examples of translating command line arguments to the status
  *   options settings to mimic `git status` results.
  * - A sample status formatter that matches the default "long" format
@@ -35,34 +34,97 @@ enum {
  * - A sample status formatter that matches the "short" format
  */
 
-static void check(int error, const char *message, const char *extra)
+enum {
+       FORMAT_DEFAULT   = 0,
+       FORMAT_LONG      = 1,
+       FORMAT_SHORT     = 2,
+       FORMAT_PORCELAIN = 3
+};
+
+#define MAX_PATHSPEC 8
+
+struct status_opts {
+       git_status_options statusopt;
+       char *repodir;
+       char *pathspec[MAX_PATHSPEC];
+       int npaths;
+       int format;
+       int zterm;
+       int showbranch;
+       int showsubmod;
+       int repeat;
+};
+
+static void parse_opts(struct status_opts *o, int argc, char *argv[]);
+static void show_branch(git_repository *repo, int format);
+static void print_long(git_status_list *status);
+static void print_short(git_repository *repo, git_status_list *status);
+static int print_submod(git_submodule *sm, const char *name, void *payload);
+
+int lg2_status(git_repository *repo, int argc, char *argv[])
 {
-       const git_error *lg2err;
-       const char *lg2msg = "", *lg2spacer = "";
+       git_status_list *status;
+       struct status_opts o = { GIT_STATUS_OPTIONS_INIT, "." };
+
+       o.statusopt.show  = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+       o.statusopt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+               GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
+               GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
+
+       parse_opts(&o, argc, argv);
 
-       if (!error)
-               return;
+       if (git_repository_is_bare(repo))
+               fatal("Cannot report status on bare repository",
+                       git_repository_path(repo));
 
-       if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) {
-               lg2msg = lg2err->message;
-               lg2spacer = " - ";
+show_status:
+       if (o.repeat)
+               printf("\033[H\033[2J");
+
+       /**
+        * Run status on the repository
+        *
+        * We use `git_status_list_new()` to generate a list of status
+        * information which lets us iterate over it at our
+        * convenience and extract the data we want to show out of
+        * each entry.
+        *
+        * You can use `git_status_foreach()` or
+        * `git_status_foreach_ext()` if you'd prefer to execute a
+        * callback for each entry. The latter gives you more control
+        * about what results are presented.
+        */
+       check_lg2(git_status_list_new(&status, repo, &o.statusopt),
+               "Could not get status", NULL);
+
+       if (o.showbranch)
+               show_branch(repo, o.format);
+
+       if (o.showsubmod) {
+               int submod_count = 0;
+               check_lg2(git_submodule_foreach(repo, print_submod, &submod_count),
+                       "Cannot iterate submodules", o.repodir);
        }
 
-       if (extra)
-               fprintf(stderr, "%s '%s' [%d]%s%s\n",
-                       message, extra, error, lg2spacer, lg2msg);
+       if (o.format == FORMAT_LONG)
+               print_long(status);
        else
-               fprintf(stderr, "%s [%d]%s%s\n",
-                       message, error, lg2spacer, lg2msg);
+               print_short(repo, status);
 
-       exit(1);
-}
+       git_status_list_free(status);
 
-static void fail(const char *message)
-{
-       check(-1, message, NULL);
+       if (o.repeat) {
+               sleep(o.repeat);
+               goto show_status;
+       }
+
+       return 0;
 }
 
+/**
+ * If the user asked for the branch, let's show the short name of the
+ * branch.
+ */
 static void show_branch(git_repository *repo, int format)
 {
        int error = 0;
@@ -71,14 +133,12 @@ static void show_branch(git_repository *repo, int format)
 
        error = git_repository_head(&head, repo);
 
-       if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND)
+       if (error == GIT_EUNBORNBRANCH || 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/");
+               branch = git_reference_shorthand(head);
        } else
-               check(error, "failed to get current branch", NULL);
+               check_lg2(error, "failed to get current branch", NULL);
 
        if (format == FORMAT_LONG)
                printf("# On branch %s\n",
@@ -89,7 +149,11 @@ static void show_branch(git_repository *repo, int format)
        git_reference_free(head);
 }
 
-static void print_long(git_repository *repo, git_status_list *status)
+/**
+ * This function print out an output similar to git's status command
+ * in long form, including the command-line hints.
+ */
+static void print_long(git_status_list *status)
 {
        size_t i, maxi = git_status_list_entrycount(status);
        const git_status_entry *s;
@@ -97,9 +161,7 @@ static void print_long(git_repository *repo, git_status_list *status)
        int changed_in_workdir = 0, rm_in_workdir = 0;
        const char *old_path, *new_path;
 
-       (void)repo;
-
-       /* print index changes */
+       /** Print index changes. */
 
        for (i = 0; i < maxi; ++i) {
                char *istatus = NULL;
@@ -148,16 +210,22 @@ static void print_long(git_repository *repo, git_status_list *status)
        }
        header = 0;
 
-       /* print workdir changes to tracked files */
+       /** Print workdir changes to tracked files. */
 
        for (i = 0; i < maxi; ++i) {
                char *wstatus = NULL;
 
                s = git_status_byindex(status, i);
 
+               /**
+                * With `GIT_STATUS_OPT_INCLUDE_UNMODIFIED` (not used in this example)
+                * `index_to_workdir` may not be `NULL` even if there are
+                * no differences, in which case it will be a `GIT_DELTA_UNMODIFIED`.
+                */
                if (s->status == GIT_STATUS_CURRENT || s->index_to_workdir == NULL)
                        continue;
 
+               /** Print out the output since we know the file has some changes */
                if (s->status & GIT_STATUS_WT_MODIFIED)
                        wstatus = "modified: ";
                if (s->status & GIT_STATUS_WT_DELETED)
@@ -191,9 +259,8 @@ static void print_long(git_repository *repo, git_status_list *status)
                changed_in_workdir = 1;
                printf("#\n");
        }
-       header = 0;
 
-       /* print untracked files */
+       /** Print untracked files. */
 
        header = 0;
 
@@ -215,7 +282,7 @@ static void print_long(git_repository *repo, git_status_list *status)
 
        header = 0;
 
-       /* print ignored files */
+       /** Print ignored files. */
 
        for (i = 0; i < maxi; ++i) {
                s = git_status_byindex(status, i);
@@ -237,6 +304,10 @@ static void print_long(git_repository *repo, git_status_list *status)
                printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
 }
 
+/**
+ * This version of the output prefixes each path with two status
+ * columns and shows submodule status information.
+ */
 static void print_short(git_repository *repo, git_status_list *status)
 {
        size_t i, maxi = git_status_list_entrycount(status);
@@ -287,16 +358,17 @@ static void print_short(git_repository *repo, git_status_list *status)
                if (istatus == '?' && wstatus == '?')
                        continue;
 
+               /**
+                * A commit in a tree is how submodules are stored, so
+                * let's go take a look at its status.
+                */
                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 (!git_submodule_status(&smstatus, repo, s->index_to_workdir->new_file.path,
+                                                 GIT_SUBMODULE_IGNORE_UNSPECIFIED)) {
                                if (smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED)
                                        extra = " (new commits)";
                                else if (smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED)
@@ -308,6 +380,10 @@ static void print_short(git_repository *repo, git_status_list *status)
                        }
                }
 
+               /**
+                * Now that we have all the information, format the output.
+                */
+
                if (s->head_to_index) {
                        a = s->head_to_index->old_file.path;
                        b = s->head_to_index->new_file.path;
@@ -341,103 +417,82 @@ static void print_short(git_repository *repo, git_status_list *status)
        }
 }
 
-int main(int argc, char *argv[])
+static int print_submod(git_submodule *sm, const char *name, void *payload)
 {
-       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];
+       int *count = payload;
+       (void)name;
 
-       opt.show  = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
-       opt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
-               GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
-               GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
+       if (*count == 0)
+               printf("# Submodules\n");
+       (*count)++;
+
+       printf("# - submodule '%s' at %s\n",
+               git_submodule_name(sm), git_submodule_path(sm));
+
+       return 0;
+}
+
+/**
+ * Parse options that git's status command supports.
+ */
+static void parse_opts(struct status_opts *o, int argc, char *argv[])
+{
+       struct args_info args = ARGS_INFO_INIT;
+
+       for (args.pos = 1; args.pos < argc; ++args.pos) {
+               char *a = argv[args.pos];
 
-       for (i = 1; i < argc; ++i) {
-               if (argv[i][0] != '-') {
-                       if (npaths < MAX_PATHSPEC)
-                               pathspec[npaths++] = argv[i];
+               if (a[0] != '-') {
+                       if (o->npaths < MAX_PATHSPEC)
+                               o->pathspec[o->npaths++] = a;
                        else
-                               fail("Example only supports a limited pathspec");
+                               fatal("Example only supports a limited pathspec", NULL);
                }
-               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(a, "-s") || !strcmp(a, "--short"))
+                       o->format = FORMAT_SHORT;
+               else if (!strcmp(a, "--long"))
+                       o->format = FORMAT_LONG;
+               else if (!strcmp(a, "--porcelain"))
+                       o->format = FORMAT_PORCELAIN;
+               else if (!strcmp(a, "-b") || !strcmp(a, "--branch"))
+                       o->showbranch = 1;
+               else if (!strcmp(a, "-z")) {
+                       o->zterm = 1;
+                       if (o->format == FORMAT_DEFAULT)
+                               o->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 |
+               else if (!strcmp(a, "--ignored"))
+                       o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
+               else if (!strcmp(a, "-uno") ||
+                                !strcmp(a, "--untracked-files=no"))
+                       o->statusopt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+               else if (!strcmp(a, "-unormal") ||
+                                !strcmp(a, "--untracked-files=normal"))
+                       o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+               else if (!strcmp(a, "-uall") ||
+                                !strcmp(a, "--untracked-files=all"))
+                       o->statusopt.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 if (!strcmp(a, "--ignore-submodules=all"))
+                       o->statusopt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
+               else if (!strncmp(a, "--git-dir=", strlen("--git-dir=")))
+                       o->repodir = a + strlen("--git-dir=");
+               else if (!strcmp(a, "--repeat"))
+                       o->repeat = 10;
+               else if (match_int_arg(&o->repeat, &args, "--repeat", 0))
+                       /* okay */;
+               else if (!strcmp(a, "--list-submodules"))
+                       o->showsubmod = 1;
                else
-                       check(-1, "Unsupported option", argv[i]);
+                       check_lg2(-1, "Unsupported option", a);
        }
 
-       if (format == FORMAT_DEFAULT)
-               format = FORMAT_LONG;
-       if (format == FORMAT_LONG)
-               showbranch = 1;
-       if (npaths > 0) {
-               opt.pathspec.strings = pathspec;
-               opt.pathspec.count   = npaths;
+       if (o->format == FORMAT_DEFAULT)
+               o->format = FORMAT_LONG;
+       if (o->format == FORMAT_LONG)
+               o->showbranch = 1;
+       if (o->npaths > 0) {
+               o->statusopt.pathspec.strings = o->pathspec;
+               o->statusopt.pathspec.count   = o->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;
 }
-