{"thread":{"id":"5514","subject":"[PATCH] git-commit.sh: convert run_status to a C builtin","startedAt":"2006-09-08T08:05:34Z","lastAt":"2006-09-08T08:05:34Z","messageCount":1,"participants":["Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"26566","messageId":"20060908080534.GB3771@coredump.intra.peff.net","threadId":"5514","inReplyTo":null,"subject":"[PATCH] git-commit.sh: convert run_status to a C builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2006-09-08T08:05:34Z","receivedAt":"2006-09-08T08:05:34Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"This creates a new git-runstatus which should do roughly the same thing\nas the run_status function from git-commit.sh. Except for color support,\nthe main focus has been to keep the output identical, so that it can be\nverified as correct and then used as a C platform for other improvements to\nthe status printing code.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\nThis is a resend with:\n - formatting cleanups\n - s/status/wt_status/\n - avoid letting colored sections cross newlines\n\n .gitignore          |    1 \n Makefile            |    3 -\n builtin-runstatus.c |   34 ++++++\n builtin.h           |    1 \n dir.c               |    7 +\n dir.h               |    1 \n git-commit.sh       |  106 +-------------------\n git.c               |    1 \n wt-status.c         |  271 +++++++++++++++++++++++++++++++++++++++++++++++++++\n wt-status.h         |   24 +++++\n 10 files changed, 349 insertions(+), 100 deletions(-)\n\ndiff --git a/.gitignore b/.gitignore\nindex 78cb671..f014ad3 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -94,6 +94,7 @@ git-rev-list\n git-rev-parse\n git-revert\n git-rm\n+git-runstatus\n git-send-email\n git-send-pack\n git-sh-setup\ndiff --git a/Makefile b/Makefile\nindex 78748cb..a9314ac 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -252,7 +252,7 @@ LIB_OBJS = \\\n \tfetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \\\n \twrite_or_die.o trace.o \\\n \talloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \\\n-\tcolor.o\n+\tcolor.o wt-status.o\n \n BUILTIN_OBJS = \\\n \tbuiltin-add.o \\\n@@ -286,6 +286,7 @@ BUILTIN_OBJS = \\\n \tbuiltin-rev-list.o \\\n \tbuiltin-rev-parse.o \\\n \tbuiltin-rm.o \\\n+\tbuiltin-runstatus.o \\\n \tbuiltin-show-branch.o \\\n \tbuiltin-stripspace.o \\\n \tbuiltin-symbolic-ref.o \\\ndiff --git a/builtin-runstatus.c b/builtin-runstatus.c\nnew file mode 100644\nindex 0000000..7979d61\n--- /dev/null\n+++ b/builtin-runstatus.c\n@@ -0,0 +1,34 @@\n+#include \"wt-status.h\"\n+#include \"cache.h\"\n+\n+extern int wt_status_use_color;\n+\n+static const char runstatus_usage[] =\n+\"git-runstatus [--color|--nocolor] [--amend] [--verbose]\";\n+\n+int cmd_runstatus(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct wt_status s;\n+\tint i;\n+\n+\tgit_config(git_status_config);\n+\twt_status_prepare(&s);\n+\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--color\"))\n+\t\t\twt_status_use_color = 1;\n+\t\telse if (!strcmp(argv[i], \"--nocolor\"))\n+\t\t\twt_status_use_color = 0;\n+\t\telse if (!strcmp(argv[i], \"--amend\")) {\n+\t\t\ts.amend = 1;\n+\t\t\ts.reference = \"HEAD^1\";\n+\t\t}\n+\t\telse if (!strcmp(argv[i], \"--verbose\"))\n+\t\t\ts.verbose = 1;\n+\t\telse\n+\t\t\tusage(runstatus_usage);\n+\t}\n+\n+\twt_status_print(&s);\n+\treturn s.commitable ? 0 : 1;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex 25431d7..53a896c 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -47,6 +47,7 @@ extern int cmd_repo_config(int argc, con\n extern int cmd_rev_list(int argc, const char **argv, const char *prefix);\n extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);\n extern int cmd_rm(int argc, const char **argv, const char *prefix);\n+extern int cmd_runstatus(int argc, const char **argv, const char *prefix);\n extern int cmd_show_branch(int argc, const char **argv, const char *prefix);\n extern int cmd_show(int argc, const char **argv, const char *prefix);\n extern int cmd_stripspace(int argc, const char **argv, const char *prefix);\ndiff --git a/dir.c b/dir.c\nindex 5a40d8f..e2f472b 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -397,3 +397,10 @@ int read_directory(struct dir_struct *di\n \tqsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);\n \treturn dir->nr;\n }\n+\n+int\n+file_exists(const char *f)\n+{\n+  struct stat sb;\n+  return stat(f, &sb) == 0;\n+}\ndiff --git a/dir.h b/dir.h\nindex 56a1b7f..313f8ab 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -47,5 +47,6 @@ extern int excluded(struct dir_struct *,\n extern void add_excludes_from_file(struct dir_struct *, const char *fname);\n extern void add_exclude(const char *string, const char *base,\n \t\t\tint baselen, struct exclude_list *which);\n+extern int file_exists(const char *);\n \n #endif\ndiff --git a/git-commit.sh b/git-commit.sh\nindex 4cf3fab..10c269a 100755\n--- a/git-commit.sh\n+++ b/git-commit.sh\n@@ -60,26 +60,6 @@ #\n }\n \n run_status () {\n-    (\n-\t# We always show status for the whole tree.\n-\tcd \"$TOP\"\n-\n-\tIS_INITIAL=\"$initial_commit\"\n-\tREFERENCE=HEAD\n-\tcase \"$amend\" in\n-\tt)\n-\t\t# If we are amending the initial commit, there\n-\t\t# is no HEAD^1.\n-\t\tif git-rev-parse --verify \"HEAD^1\" >/dev/null 2>&1\n-\t\tthen\n-\t\t\tREFERENCE=\"HEAD^1\"\n-\t\t\tIS_INITIAL=\n-\t\telse\n-\t\t\tIS_INITIAL=t\n-\t\tfi\n-\t\t;;\n-\tesac\n-\n \t# If TMP_INDEX is defined, that means we are doing\n \t# \"--only\" partial commit, and that index file is used\n \t# to build the tree for the commit.  Otherwise, if\n@@ -96,85 +76,13 @@ run_status () {\n \t    export GIT_INDEX_FILE\n \tfi\n \n-\tcase \"$branch\" in\n-\trefs/heads/master) ;;\n-\t*)  echo \"# On branch $branch\" ;;\n-\tesac\n-\n-\tif test -z \"$IS_INITIAL\"\n-\tthen\n-\t    git-diff-index -M --cached --name-status \\\n-\t\t--diff-filter=MDTCRA $REFERENCE |\n-\t    sed -e '\n-\t\t    s/\\\\/\\\\\\\\/g\n-\t\t    s/ /\\\\ /g\n-\t    ' |\n-\t    report \"Updated but not checked in\" \"will commit\"\n-\t    committable=\"$?\"\n-\telse\n-\t    echo '#\n-# Initial commit\n-#'\n-\t    git-ls-files |\n-\t    sed -e '\n-\t\t    s/\\\\/\\\\\\\\/g\n-\t\t    s/ /\\\\ /g\n-\t\t    s/^/A /\n-\t    ' |\n-\t    report \"Updated but not checked in\" \"will commit\"\n-\n-\t    committable=\"$?\"\n-\tfi\n-\n-\tgit-diff-files  --name-status |\n-\tsed -e '\n-\t\ts/\\\\/\\\\\\\\/g\n-\t\ts/ /\\\\ /g\n-\t' |\n-\treport \"Changed but not updated\" \\\n-\t    \"use git-update-index to mark for commit\"\n-\n-        option=\"\"\n-        if test -z \"$untracked_files\"; then\n-            option=\"--directory --no-empty-directory\"\n-        fi\n-\thdr_shown=\n-\tif test -f \"$GIT_DIR/info/exclude\"\n-\tthen\n-\t    git-ls-files --others $option \\\n-\t\t--exclude-from=\"$GIT_DIR/info/exclude\" \\\n-\t\t--exclude-per-directory=.gitignore\n-\telse\n-\t    git-ls-files --others $option \\\n-\t\t--exclude-per-directory=.gitignore\n-\tfi |\n-\twhile read line; do\n-\t    if [ -z \"$hdr_shown\" ]; then\n-\t\techo '#'\n-\t\techo '# Untracked files:'\n-\t\techo '#   (use \"git add\" to add to commit)'\n-\t\techo '#'\n-\t\thdr_shown=1\n-\t    fi\n-\t    echo \"#\t$line\"\n-\tdone\n-\n-\tif test -n \"$verbose\" -a -z \"$IS_INITIAL\"\n-\tthen\n-\t    git-diff-index --cached -M -p --diff-filter=MDTCRA $REFERENCE\n-\tfi\n-\tcase \"$committable\" in\n-\t0)\n-\t\tcase \"$amend\" in\n-\t\tt)\n-\t\t\techo \"# No changes\" ;;\n-\t\t*)\n-\t\t\techo \"nothing to commit\" ;;\n-\t\tesac\n-\t\texit 1 ;;\n-\tesac\n-\texit 0\n-    )\n+  case \"$status_only\" in\n+    t) color= ;;\n+    *) color=--nocolor ;;\n+  esac\n+  git-runstatus ${color} \\\n+                ${verbose:+--verbose} \\\n+                ${amend:+--amend}\n }\n \n trap '\ndiff --git a/git.c b/git.c\nindex 335f405..495b39a 100644\n--- a/git.c\n+++ b/git.c\n@@ -252,6 +252,7 @@ static void handle_internal_command(int \n \t\t{ \"rev-list\", cmd_rev_list, RUN_SETUP },\n \t\t{ \"rev-parse\", cmd_rev_parse, RUN_SETUP },\n \t\t{ \"rm\", cmd_rm, RUN_SETUP },\n+\t\t{ \"runstatus\", cmd_runstatus, RUN_SETUP },\n \t\t{ \"show-branch\", cmd_show_branch, RUN_SETUP },\n \t\t{ \"show\", cmd_show, RUN_SETUP | USE_PAGER },\n \t\t{ \"stripspace\", cmd_stripspace },\ndiff --git a/wt-status.c b/wt-status.c\nnew file mode 100644\nindex 0000000..ec2c728\n--- /dev/null\n+++ b/wt-status.c\n@@ -0,0 +1,271 @@\n+#include \"wt-status.h\"\n+#include \"color.h\"\n+#include \"cache.h\"\n+#include \"object.h\"\n+#include \"dir.h\"\n+#include \"commit.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"diffcore.h\"\n+\n+int wt_status_use_color = 0;\n+static char wt_status_colors[][COLOR_MAXLEN] = {\n+\t\"\",         /* WT_STATUS_HEADER: normal */\n+\t\"\\033[32m\", /* WT_STATUS_UPDATED: green */\n+\t\"\\033[31m\", /* WT_STATUS_CHANGED: red */\n+\t\"\\033[31m\", /* WT_STATUS_UNTRACKED: red */\n+};\n+\n+static int parse_status_slot(const char *var, int offset)\n+{\n+\tif (!strcasecmp(var+offset, \"header\"))\n+\t\treturn WT_STATUS_HEADER;\n+\tif (!strcasecmp(var+offset, \"updated\"))\n+\t\treturn WT_STATUS_UPDATED;\n+\tif (!strcasecmp(var+offset, \"changed\"))\n+\t\treturn WT_STATUS_CHANGED;\n+\tif (!strcasecmp(var+offset, \"untracked\"))\n+\t\treturn WT_STATUS_UNTRACKED;\n+\tdie(\"bad config variable '%s'\", var);\n+}\n+\n+static const char* color(int slot)\n+{\n+\treturn wt_status_use_color ? wt_status_colors[slot] : \"\";\n+}\n+\n+void wt_status_prepare(struct wt_status *s)\n+{\n+\tunsigned char sha1[20];\n+\tconst char *head;\n+\n+\ts->is_initial = get_sha1(\"HEAD\", sha1) ? 1 : 0;\n+\n+\thead = resolve_ref(git_path(\"HEAD\"), sha1, 0);\n+\ts->branch = head ?\n+\t\t    strdup(head + strlen(get_git_dir()) + 1) :\n+\t\t    NULL;\n+\n+\ts->reference = \"HEAD\";\n+\ts->amend = 0;\n+\ts->verbose = 0;\n+\ts->commitable = 0;\n+}\n+\n+static void wt_status_print_header(const char *main, const char *sub)\n+{\n+\tconst char *c = color(WT_STATUS_HEADER);\n+\tcolor_printf_ln(c, \"# %s:\", main);\n+\tcolor_printf_ln(c, \"#   (%s)\", sub);\n+\tcolor_printf_ln(c, \"#\");\n+}\n+\n+static void wt_status_print_trailer(void)\n+{\n+\tcolor_printf_ln(color(WT_STATUS_HEADER), \"#\");\n+}\n+\n+static void wt_status_print_filepair(int t, struct diff_filepair *p)\n+{\n+\tconst char *c = color(t);\n+\tcolor_printf(color(WT_STATUS_HEADER), \"#\\t\");\n+\tswitch (p->status) {\n+\tcase DIFF_STATUS_ADDED:\n+\t\tcolor_printf(c, \"new file: %s\", p->one->path); break;\n+\tcase DIFF_STATUS_COPIED:\n+\t\tcolor_printf(c, \"copied: %s -> %s\",\n+\t\t\t\tp->one->path, p->two->path);\n+\t\tbreak;\n+\tcase DIFF_STATUS_DELETED:\n+\t\tcolor_printf_ln(c, \"deleted: %s\", p->one->path); break;\n+\tcase DIFF_STATUS_MODIFIED:\n+\t\tcolor_printf(c, \"modified: %s\", p->one->path); break;\n+\tcase DIFF_STATUS_RENAMED:\n+\t\tcolor_printf(c, \"renamed: %s -> %s\",\n+\t\t\t\tp->one->path, p->two->path);\n+\t\tbreak;\n+\tcase DIFF_STATUS_TYPE_CHANGED:\n+\t\tcolor_printf(c, \"typechange: %s\", p->one->path); break;\n+\tcase DIFF_STATUS_UNKNOWN:\n+\t\tcolor_printf(c, \"unknown: %s\", p->one->path); break;\n+\tcase DIFF_STATUS_UNMERGED:\n+\t\tcolor_printf(c, \"unmerged: %s\", p->one->path); break;\n+\tdefault:\n+\t\tdie(\"bug: unhandled diff status %c\", p->status);\n+\t}\n+\tprintf(\"\\n\");\n+}\n+\n+static void wt_status_print_updated_cb(struct diff_queue_struct *q,\n+\t\tstruct diff_options *options,\n+\t\tvoid *data)\n+{\n+\tstruct wt_status *s = data;\n+\tint shown_header = 0;\n+\tint i;\n+\tif (q->nr) {\n+\t}\n+\tfor (i = 0; i < q->nr; i++) {\n+\t\tif (q->queue[i]->status == 'U')\n+\t\t\tcontinue;\n+\t\tif (!shown_header) {\n+\t\t\twt_status_print_header(\"Updated but not checked in\",\n+\t\t\t\t\t\"will commit\");\n+\t\t\ts->commitable = 1;\n+\t\t\tshown_header = 1;\n+\t\t}\n+\t\twt_status_print_filepair(WT_STATUS_UPDATED, q->queue[i]);\n+\t}\n+\tif (shown_header)\n+\t\twt_status_print_trailer();\n+}\n+\n+static void wt_status_print_changed_cb(struct diff_queue_struct *q,\n+                        struct diff_options *options,\n+                        void *data)\n+{\n+\tint i;\n+\tif (q->nr)\n+\t\twt_status_print_header(\"Changed but not updated\",\n+\t\t\t\t\"use git-update-index to mark for commit\");\n+\tfor (i = 0; i < q->nr; i++)\n+\t\twt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]);\n+\tif (q->nr)\n+\t\twt_status_print_trailer();\n+}\n+\n+void wt_status_print_initial(struct wt_status *s)\n+{\n+\tint i;\n+\tread_cache();\n+\tif (active_nr) {\n+\t\ts->commitable = 1;\n+\t\twt_status_print_header(\"Updated but not checked in\",\n+\t\t\t\t\"will commit\");\n+\t}\n+\tfor (i = 0; i < active_nr; i++) {\n+\t\tcolor_printf(color(WT_STATUS_HEADER), \"#\\t\");\n+\t\tcolor_printf_ln(color(WT_STATUS_UPDATED), \"new file: %s\",\n+\t\t\t\tactive_cache[i]->name);\n+\t}\n+\tif (active_nr)\n+\t\twt_status_print_trailer();\n+}\n+\n+static void wt_status_print_updated(struct wt_status *s)\n+{\n+\tstruct rev_info rev;\n+\tconst char *argv[] = { NULL, NULL, NULL };\n+\targv[1] = s->reference;\n+\tinit_revisions(&rev, NULL);\n+\tsetup_revisions(2, argv, &rev, NULL);\n+\trev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;\n+\trev.diffopt.format_callback = wt_status_print_updated_cb;\n+\trev.diffopt.format_callback_data = s;\n+\trev.diffopt.detect_rename = 1;\n+\trun_diff_index(&rev, 1);\n+}\n+\n+static void wt_status_print_changed(struct wt_status *s)\n+{\n+\tstruct rev_info rev;\n+\tconst char *argv[] = { NULL, NULL };\n+\tinit_revisions(&rev, \"\");\n+\tsetup_revisions(1, argv, &rev, NULL);\n+\trev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;\n+\trev.diffopt.format_callback = wt_status_print_changed_cb;\n+\trev.diffopt.format_callback_data = s;\n+\trun_diff_files(&rev, 0);\n+}\n+\n+static void wt_status_print_untracked(const struct wt_status *s)\n+{\n+\tstruct dir_struct dir;\n+\tconst char *x;\n+\tint i;\n+\tint shown_header = 0;\n+\n+\tmemset(&dir, 0, sizeof(dir));\n+\n+\tdir.exclude_per_dir = \".gitignore\";\n+\tx = git_path(\"info/exclude\");\n+\tif (file_exists(x))\n+\t\tadd_excludes_from_file(&dir, x);\n+\n+\tread_directory(&dir, \".\", \"\", 0);\n+\tfor(i = 0; i < dir.nr; i++) {\n+\t\t/* check for matching entry, which is unmerged; lifted from\n+\t\t * builtin-ls-files:show_other_files */\n+\t\tstruct dir_entry *ent = dir.entries[i];\n+\t\tint pos = cache_name_pos(ent->name, ent->len);\n+\t\tstruct cache_entry *ce;\n+\t\tif (0 <= pos)\n+\t\t\tdie(\"bug in wt_status_print_untracked\");\n+\t\tpos = -pos - 1;\n+\t\tif (pos < active_nr) {\n+\t\t\tce = active_cache[pos];\n+\t\t\tif (ce_namelen(ce) == ent->len &&\n+\t\t\t    !memcmp(ce->name, ent->name, ent->len))\n+\t\t\t\tcontinue;\n+\t\t}\n+\t\tif (!shown_header) {\n+\t\t\twt_status_print_header(\"Untracked files\",\n+\t\t\t\t\"use \\\"git add\\\" to add to commit\");\n+\t\t\tshown_header = 1;\n+\t\t}\n+\t\tcolor_printf(color(WT_STATUS_HEADER), \"#\\t\");\n+\t\tcolor_printf_ln(color(WT_STATUS_UNTRACKED), \"%.*s\",\n+\t\t\t\tent->len, ent->name);\n+\t}\n+}\n+\n+static void wt_status_print_verbose(struct wt_status *s)\n+{\n+\tstruct rev_info rev;\n+\tconst char *argv[] = { NULL, NULL, NULL };\n+\targv[1] = s->reference;\n+\tinit_revisions(&rev, NULL);\n+\tsetup_revisions(2, argv, &rev, NULL);\n+\trev.diffopt.output_format |= DIFF_FORMAT_PATCH;\n+\trev.diffopt.detect_rename = 1;\n+\trun_diff_index(&rev, 1);\n+}\n+\n+void wt_status_print(struct wt_status *s)\n+{\n+\tif (s->branch && strcmp(s->branch, \"refs/heads/master\"))\n+\t\tcolor_printf_ln(color(WT_STATUS_HEADER),\n+\t\t\t\"# On branch %s\", s->branch);\n+\n+\tif (s->is_initial) {\n+\t\tcolor_printf_ln(color(WT_STATUS_HEADER), \"#\");\n+\t\tcolor_printf_ln(color(WT_STATUS_HEADER), \"# Initial commit\");\n+\t\tcolor_printf_ln(color(WT_STATUS_HEADER), \"#\");\n+\t\twt_status_print_initial(s);\n+\t}\n+\telse {\n+\t\twt_status_print_updated(s);\n+\t\tdiscard_cache();\n+\t}\n+\n+\twt_status_print_changed(s);\n+\twt_status_print_untracked(s);\n+\n+\tif (s->verbose && !s->is_initial)\n+\t\twt_status_print_verbose(s);\n+\tif (!s->commitable)\n+\t\tprintf(\"%s\\n\", s->amend ? \"# No changes\" : \"nothing to commit\");\n+}\n+\n+int git_status_config(const char *k, const char *v)\n+{\n+\tif (!strcmp(k, \"status.color\")) {\n+\t\twt_status_use_color = git_config_colorbool(k, v);\n+\t\treturn 0;\n+\t}\n+\tif (!strncmp(k, \"status.color.\", 13)) {\n+\t\tint slot = parse_status_slot(k, 13);\n+\t\tcolor_parse(v, k, wt_status_colors[slot]);\n+\t}\n+\treturn git_default_config(k, v);\n+}\ndiff --git a/wt-status.h b/wt-status.h\nnew file mode 100644\nindex 0000000..75d3cfe\n--- /dev/null\n+++ b/wt-status.h\n@@ -0,0 +1,24 @@\n+#ifndef STATUS_H\n+#define STATUS_H\n+\n+enum color_wt_status {\n+\tWT_STATUS_HEADER,\n+\tWT_STATUS_UPDATED,\n+\tWT_STATUS_CHANGED,\n+\tWT_STATUS_UNTRACKED,\n+};\n+\n+struct wt_status {\n+\tint is_initial;\n+\tchar *branch;\n+\tconst char *reference;\n+\tint commitable;\n+\tint verbose;\n+\tint amend;\n+};\n+\n+int git_status_config(const char *var, const char *value);\n+void wt_status_prepare(struct wt_status *s);\n+void wt_status_print(struct wt_status *s);\n+\n+#endif /* STATUS_H */\n-- \n1.4.2.g5290b\n"}]}