{"thread":{"id":"9904","subject":"[PATCH 2/7] Enable wt-status to run against non-standard index file.","startedAt":"2007-09-18T00:06:42Z","lastAt":"2007-09-24T20:27:27Z","messageCount":18,"participants":["Kristian Høgsberg","Johannes Schindelin","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"53377","messageId":"1190074008617-git-send-email-krh@redhat.com","threadId":"9904","inReplyTo":null,"subject":"[PATCH 1/7] Enable wt-status output to a given FILE pointer.","fromName":"Kristian Høgsberg","fromEmail":"krh@redhat.com","sentAt":"2007-09-18T00:06:42Z","receivedAt":"2007-09-18T00:06:42Z","isPatch":true,"sender":{"key":"krh@redhat.com","avatar":"https://gravatar.com/avatar/763dee6f9594ac474f725b137a39565792928e583ddf59b32befc2907409027e?d=mp&s=160"},"body":"Still defaults to stdout, but you can now override wt_status.fp after\ncalling wt_status_prepare().\n\nSigned-off-by: Kristian Høgsberg <krh@redhat.com>\n---\n color.c     |   18 ++++++------\n color.h     |    4 +-\n wt-status.c |   85 ++++++++++++++++++++++++++++++----------------------------\n wt-status.h |    3 ++\n 4 files changed, 58 insertions(+), 52 deletions(-)\n\ndiff --git a/color.c b/color.c\nindex 09d82ee..124ba33 100644\n--- a/color.c\n+++ b/color.c\n@@ -135,39 +135,39 @@ int git_config_colorbool(const char *var, const char *value)\n \treturn git_config_bool(var, value);\n }\n \n-static int color_vprintf(const char *color, const char *fmt,\n+static int color_vfprintf(FILE *fp, const char *color, const char *fmt,\n \t\tva_list args, const char *trail)\n {\n \tint r = 0;\n \n \tif (*color)\n-\t\tr += printf(\"%s\", color);\n-\tr += vprintf(fmt, args);\n+\t\tr += fprintf(fp, \"%s\", color);\n+\tr += vfprintf(fp, fmt, args);\n \tif (*color)\n-\t\tr += printf(\"%s\", COLOR_RESET);\n+\t\tr += fprintf(fp, \"%s\", COLOR_RESET);\n \tif (trail)\n-\t\tr += printf(\"%s\", trail);\n+\t\tr += fprintf(fp, \"%s\", trail);\n \treturn r;\n }\n \n \n \n-int color_printf(const char *color, const char *fmt, ...)\n+int color_fprintf(FILE *fp, const char *color, const char *fmt, ...)\n {\n \tva_list args;\n \tint r;\n \tva_start(args, fmt);\n-\tr = color_vprintf(color, fmt, args, NULL);\n+\tr = color_vfprintf(fp, color, fmt, args, NULL);\n \tva_end(args);\n \treturn r;\n }\n \n-int color_printf_ln(const char *color, const char *fmt, ...)\n+int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)\n {\n \tva_list args;\n \tint r;\n \tva_start(args, fmt);\n-\tr = color_vprintf(color, fmt, args, \"\\n\");\n+\tr = color_vfprintf(fp, color, fmt, args, \"\\n\");\n \tva_end(args);\n \treturn r;\n }\ndiff --git a/color.h b/color.h\nindex 88bb8ff..6809800 100644\n--- a/color.h\n+++ b/color.h\n@@ -6,7 +6,7 @@\n \n int git_config_colorbool(const char *var, const char *value);\n void color_parse(const char *var, const char *value, char *dst);\n-int color_printf(const char *color, const char *fmt, ...);\n-int color_printf_ln(const char *color, const char *fmt, ...);\n+int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);\n+int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);\n \n #endif /* COLOR_H */\ndiff --git a/wt-status.c b/wt-status.c\nindex 10ce6ee..eeb1691 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -52,31 +52,33 @@ void wt_status_prepare(struct wt_status *s)\n \thead = resolve_ref(\"HEAD\", sha1, 0, NULL);\n \ts->branch = head ? xstrdup(head) : NULL;\n \ts->reference = \"HEAD\";\n+\ts->fp = stdout;\n }\n \n-static void wt_status_print_cached_header(const char *reference)\n+static void wt_status_print_cached_header(struct wt_status *s)\n {\n \tconst char *c = color(WT_STATUS_HEADER);\n-\tcolor_printf_ln(c, \"# Changes to be committed:\");\n-\tif (reference) {\n-\t\tcolor_printf_ln(c, \"#   (use \\\"git reset %s <file>...\\\" to unstage)\", reference);\n+\tcolor_fprintf_ln(s->fp, c, \"# Changes to be committed:\");\n+\tif (s->reference) {\n+\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git reset %s <file>...\\\" to unstage)\", s->reference);\n \t} else {\n-\t\tcolor_printf_ln(c, \"#   (use \\\"git rm --cached <file>...\\\" to unstage)\");\n+\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git rm --cached <file>...\\\" to unstage)\");\n \t}\n-\tcolor_printf_ln(c, \"#\");\n+\tcolor_fprintf_ln(s->fp, c, \"#\");\n }\n \n-static void wt_status_print_header(const char *main, const char *sub)\n+static void wt_status_print_header(struct wt_status *s,\n+\t\t\t\t   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+\tcolor_fprintf_ln(s->fp, c, \"# %s:\", main);\n+\tcolor_fprintf_ln(s->fp, c, \"#   (%s)\", sub);\n+\tcolor_fprintf_ln(s->fp, c, \"#\");\n }\n \n-static void wt_status_print_trailer(void)\n+static void wt_status_print_trailer(struct wt_status *s)\n {\n-\tcolor_printf_ln(color(WT_STATUS_HEADER), \"#\");\n+\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER), \"#\");\n }\n \n static const char *quote_crlf(const char *in, char *buf, size_t sz)\n@@ -108,7 +110,8 @@ static const char *quote_crlf(const char *in, char *buf, size_t sz)\n \treturn ret;\n }\n \n-static void wt_status_print_filepair(int t, struct diff_filepair *p)\n+static void wt_status_print_filepair(struct wt_status *s,\n+\t\t\t\t     int t, struct diff_filepair *p)\n {\n \tconst char *c = color(t);\n \tconst char *one, *two;\n@@ -117,36 +120,36 @@ static void wt_status_print_filepair(int t, struct diff_filepair *p)\n \tone = quote_crlf(p->one->path, onebuf, sizeof(onebuf));\n \ttwo = quote_crlf(p->two->path, twobuf, sizeof(twobuf));\n \n-\tcolor_printf(color(WT_STATUS_HEADER), \"#\\t\");\n+\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER), \"#\\t\");\n \tswitch (p->status) {\n \tcase DIFF_STATUS_ADDED:\n-\t\tcolor_printf(c, \"new file:   %s\", one);\n+\t\tcolor_fprintf(s->fp, c, \"new file:   %s\", one);\n \t\tbreak;\n \tcase DIFF_STATUS_COPIED:\n-\t\tcolor_printf(c, \"copied:     %s -> %s\", one, two);\n+\t\tcolor_fprintf(s->fp, c, \"copied:     %s -> %s\", one, two);\n \t\tbreak;\n \tcase DIFF_STATUS_DELETED:\n-\t\tcolor_printf(c, \"deleted:    %s\", one);\n+\t\tcolor_fprintf(s->fp, c, \"deleted:    %s\", one);\n \t\tbreak;\n \tcase DIFF_STATUS_MODIFIED:\n-\t\tcolor_printf(c, \"modified:   %s\", one);\n+\t\tcolor_fprintf(s->fp, c, \"modified:   %s\", one);\n \t\tbreak;\n \tcase DIFF_STATUS_RENAMED:\n-\t\tcolor_printf(c, \"renamed:    %s -> %s\", one, two);\n+\t\tcolor_fprintf(s->fp, c, \"renamed:    %s -> %s\", one, two);\n \t\tbreak;\n \tcase DIFF_STATUS_TYPE_CHANGED:\n-\t\tcolor_printf(c, \"typechange: %s\", one);\n+\t\tcolor_fprintf(s->fp, c, \"typechange: %s\", one);\n \t\tbreak;\n \tcase DIFF_STATUS_UNKNOWN:\n-\t\tcolor_printf(c, \"unknown:    %s\", one);\n+\t\tcolor_fprintf(s->fp, c, \"unknown:    %s\", one);\n \t\tbreak;\n \tcase DIFF_STATUS_UNMERGED:\n-\t\tcolor_printf(c, \"unmerged:   %s\", one);\n+\t\tcolor_fprintf(s->fp, c, \"unmerged:   %s\", one);\n \t\tbreak;\n \tdefault:\n \t\tdie(\"bug: unhandled diff status %c\", p->status);\n \t}\n-\tprintf(\"\\n\");\n+\tfprintf(s->fp, \"\\n\");\n }\n \n static void wt_status_print_updated_cb(struct diff_queue_struct *q,\n@@ -160,14 +163,14 @@ static void wt_status_print_updated_cb(struct diff_queue_struct *q,\n \t\tif (q->queue[i]->status == 'U')\n \t\t\tcontinue;\n \t\tif (!shown_header) {\n-\t\t\twt_status_print_cached_header(s->reference);\n+\t\t\twt_status_print_cached_header(s);\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\twt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]);\n \t}\n \tif (shown_header)\n-\t\twt_status_print_trailer();\n+\t\twt_status_print_trailer(s);\n }\n \n static void wt_status_print_changed_cb(struct diff_queue_struct *q,\n@@ -184,12 +187,12 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q,\n \t\t\t\tmsg = use_add_rm_msg;\n \t\t\t\tbreak;\n \t\t\t}\n-\t\twt_status_print_header(\"Changed but not updated\", msg);\n+\t\twt_status_print_header(s, \"Changed but not updated\", msg);\n \t}\n \tfor (i = 0; i < q->nr; i++)\n-\t\twt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]);\n+\t\twt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]);\n \tif (q->nr)\n-\t\twt_status_print_trailer();\n+\t\twt_status_print_trailer(s);\n }\n \n static void wt_read_cache(struct wt_status *s)\n@@ -206,16 +209,16 @@ static void wt_status_print_initial(struct wt_status *s)\n \twt_read_cache(s);\n \tif (active_nr) {\n \t\ts->commitable = 1;\n-\t\twt_status_print_cached_header(NULL);\n+\t\twt_status_print_cached_header(s);\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\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER), \"#\\t\");\n+\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_UPDATED), \"new file: %s\",\n \t\t\t\tquote_crlf(active_cache[i]->name,\n \t\t\t\t\t   buf, sizeof(buf)));\n \t}\n \tif (active_nr)\n-\t\twt_status_print_trailer();\n+\t\twt_status_print_trailer(s);\n }\n \n static void wt_status_print_updated(struct wt_status *s)\n@@ -282,12 +285,12 @@ static void wt_status_print_untracked(struct wt_status *s)\n \t\t}\n \t\tif (!shown_header) {\n \t\t\ts->workdir_untracked = 1;\n-\t\t\twt_status_print_header(\"Untracked files\",\n+\t\t\twt_status_print_header(s, \"Untracked files\",\n \t\t\t\t\t       use_add_to_include_msg);\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\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER), \"#\\t\");\n+\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), \"%.*s\",\n \t\t\t\tent->len, ent->name);\n \t}\n }\n@@ -317,14 +320,14 @@ void wt_status_print(struct wt_status *s)\n \t\t\tbranch_name = \"\";\n \t\t\ton_what = \"Not currently on any branch.\";\n \t\t}\n-\t\tcolor_printf_ln(color(WT_STATUS_HEADER),\n+\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER),\n \t\t\t\"# %s%s\", on_what, branch_name);\n \t}\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\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER), \"#\");\n+\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER), \"# Initial commit\");\n+\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER), \"#\");\n \t\twt_status_print_initial(s);\n \t}\n \telse {\n@@ -338,7 +341,7 @@ void wt_status_print(struct wt_status *s)\n \t\twt_status_print_verbose(s);\n \tif (!s->commitable) {\n \t\tif (s->amend)\n-\t\t\tprintf(\"# No changes\\n\");\n+\t\t\tfprintf(s->fp, \"# No changes\\n\");\n \t\telse if (s->workdir_dirty)\n \t\t\tprintf(\"no changes added to commit (use \\\"git add\\\" and/or \\\"git commit -a\\\")\\n\");\n \t\telse if (s->workdir_untracked)\ndiff --git a/wt-status.h b/wt-status.h\nindex cfea4ae..4f3a615 100644\n--- a/wt-status.h\n+++ b/wt-status.h\n@@ -1,6 +1,8 @@\n #ifndef STATUS_H\n #define STATUS_H\n \n+#include <stdio.h>\n+\n enum color_wt_status {\n \tWT_STATUS_HEADER,\n \tWT_STATUS_UPDATED,\n@@ -19,6 +21,7 @@ struct wt_status {\n \tint commitable;\n \tint workdir_dirty;\n \tint workdir_untracked;\n+\tFILE *fp;\n };\n \n int git_status_config(const char *var, const char *value);\n-- \n1.5.3.1.993.gbf388-dirty\n"},{"id":"53373","messageId":"1190074014548-git-send-email-krh@redhat.com","threadId":"9904","inReplyTo":"1190074008617-git-send-email-krh@redhat.com","subject":"[PATCH 2/7] Enable wt-status to run against non-standard index file.","fromName":"Kristian Høgsberg","fromEmail":"krh@redhat.com","sentAt":"2007-09-18T00:06:43Z","receivedAt":"2007-09-18T00:06:43Z","isPatch":true,"sender":{"key":"krh@redhat.com","avatar":"https://gravatar.com/avatar/763dee6f9594ac474f725b137a39565792928e583ddf59b32befc2907409027e?d=mp&s=160"},"body":"We still default to get_index_file(), but this can be overridden\nby setting wt_status.index_file after calling wt_status_prepare().\n\nSigned-off-by: Kristian Høgsberg <krh@redhat.com>\n---\n wt-status.c |    3 ++-\n wt-status.h |    1 +\n 2 files changed, 3 insertions(+), 1 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex eeb1691..03b5ec4 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -53,6 +53,7 @@ void wt_status_prepare(struct wt_status *s)\n \ts->branch = head ? xstrdup(head) : NULL;\n \ts->reference = \"HEAD\";\n \ts->fp = stdout;\n+\ts->index_file = get_index_file();\n }\n \n static void wt_status_print_cached_header(struct wt_status *s)\n@@ -198,7 +199,7 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q,\n static void wt_read_cache(struct wt_status *s)\n {\n \tdiscard_cache();\n-\tread_cache();\n+\tread_cache_from(s->index_file);\n }\n \n static void wt_status_print_initial(struct wt_status *s)\ndiff --git a/wt-status.h b/wt-status.h\nindex 4f3a615..7744932 100644\n--- a/wt-status.h\n+++ b/wt-status.h\n@@ -21,6 +21,7 @@ struct wt_status {\n \tint commitable;\n \tint workdir_dirty;\n \tint workdir_untracked;\n+\tconst char *index_file;\n \tFILE *fp;\n };\n \n-- \n1.5.3.1.993.gbf388-dirty\n"},{"id":"53376","messageId":"11900740142347-git-send-email-krh@redhat.com","threadId":"9904","inReplyTo":"1190074014548-git-send-email-krh@redhat.com","subject":"[PATCH 3/7] Introduce entry point for launching add--interactive.","fromName":"Kristian Høgsberg","fromEmail":"krh@redhat.com","sentAt":"2007-09-18T00:06:44Z","receivedAt":"2007-09-18T00:06:44Z","isPatch":true,"sender":{"key":"krh@redhat.com","avatar":"https://gravatar.com/avatar/763dee6f9594ac474f725b137a39565792928e583ddf59b32befc2907409027e?d=mp&s=160"},"body":"This refactors builtin-add.c a little to provide a unique entry point\nfor launching git add --interactive, which will be used by\nbuiltin-commit too.  If we later want to make add --interactive a\nbuiltin or change how it is launched, we just start from this function.\n\nSigned-off-by: Kristian Høgsberg <krh@redhat.com>\n---\n builtin-add.c |   15 ++++++++++-----\n commit.h      |    3 +++\n 2 files changed, 13 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin-add.c b/builtin-add.c\nindex 0d7d0ce..a5cb910 100644\n--- a/builtin-add.c\n+++ b/builtin-add.c\n@@ -12,6 +12,7 @@\n #include \"diffcore.h\"\n #include \"commit.h\"\n #include \"revision.h\"\n+#include \"run-command.h\"\n \n static const char builtin_add_usage[] =\n \"git-add [-n] [-v] [-f] [--interactive | -i] [-u] [--refresh] [--] <filepattern>...\";\n@@ -153,6 +154,13 @@ static int git_add_config(const char *var, const char *value)\n \treturn git_default_config(var, value);\n }\n \n+int interactive_add(void)\n+{\n+\tconst char *argv[2] = { \"add--interactive\", NULL };\n+\n+\treturn run_command_v_opt(argv, RUN_GIT_CMD);\n+}\n+\n static struct lock_file lock_file;\n \n static const char ignore_error[] =\n@@ -172,12 +180,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\t\tadd_interactive++;\n \t}\n \tif (add_interactive) {\n-\t\tconst char *args[] = { \"add--interactive\", NULL };\n-\n-\t\tif (add_interactive != 1 || argc != 2)\n+\t\tif (argc != 2)\n \t\t\tdie(\"add --interactive does not take any parameters\");\n-\t\texecv_git_cmd(args);\n-\t\texit(1);\n+\t\texit(interactive_add());\n \t}\n \n \tgit_config(git_add_config);\ndiff --git a/commit.h b/commit.h\nindex b779de8..da34c23 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -128,4 +128,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,\n \t\tint depth, int shallow_flag, int not_shallow_flag);\n \n int in_merge_bases(struct commit *, struct commit **, int);\n+\n+extern int interactive_add(void);\n+\n #endif /* COMMIT_H */\n-- \n1.5.3.1.993.gbf388-dirty\n"},{"id":"53374","messageId":"11900740153845-git-send-email-krh@redhat.com","threadId":"9904","inReplyTo":"11900740142347-git-send-email-krh@redhat.com","subject":"[PATCH 4/7] Clean up stripspace a bit, use strbuf even more.","fromName":"Kristian Høgsberg","fromEmail":"krh@redhat.com","sentAt":"2007-09-18T00:06:45Z","receivedAt":"2007-09-18T00:06:45Z","isPatch":true,"sender":{"key":"krh@redhat.com","avatar":"https://gravatar.com/avatar/763dee6f9594ac474f725b137a39565792928e583ddf59b32befc2907409027e?d=mp&s=160"},"body":"Signed-off-by: Kristian Høgsberg <krh@redhat.com>\n---\n builtin-stripspace.c |   56 ++++++++++++++++++++++---------------------------\n builtin-tag.c        |    5 +---\n builtin.h            |    1 -\n strbuf.h             |    2 +\n 4 files changed, 28 insertions(+), 36 deletions(-)\n\ndiff --git a/builtin-stripspace.c b/builtin-stripspace.c\nindex c4cf2f0..a5e7518 100644\n--- a/builtin-stripspace.c\n+++ b/builtin-stripspace.c\n@@ -9,17 +9,13 @@\n  */\n static size_t cleanup(char *line, size_t len)\n {\n-\tif (len) {\n-\t\tif (line[len - 1] == '\\n')\n-\t\t\tlen--;\n-\n-\t\twhile (len) {\n-\t\t\tunsigned char c = line[len - 1];\n-\t\t\tif (!isspace(c))\n-\t\t\t\tbreak;\n-\t\t\tlen--;\n-\t\t}\n+\twhile (len) {\n+\t\tunsigned char c = line[len - 1];\n+\t\tif (!isspace(c))\n+\t\t\tbreak;\n+\t\tlen--;\n \t}\n+\n \treturn len;\n }\n \n@@ -35,42 +31,42 @@ static size_t cleanup(char *line, size_t len)\n  * If the input has only empty lines and spaces,\n  * no output will be produced.\n  *\n- * If last line has a newline at the end, it will be removed.\n+ * If last line does not have a newline at the end, one is added.\n  *\n  * Enable skip_comments to skip every line starting with \"#\".\n  */\n-size_t stripspace(char *buffer, size_t length, int skip_comments)\n+void stripspace(struct strbuf *sb, int skip_comments)\n {\n-\tint empties = -1;\n+\tint empties = 0;\n \tsize_t i, j, len, newlen;\n \tchar *eol;\n \n-\tfor (i = j = 0; i < length; i += len, j += newlen) {\n-\t\teol = memchr(buffer + i, '\\n', length - i);\n-\t\tlen = eol ? eol - (buffer + i) + 1 : length - i;\n+\t/* We may have to add a newline. */\n+\tstrbuf_grow(sb, 1);\n \n-\t\tif (skip_comments && len && buffer[i] == '#') {\n+\tfor (i = j = 0; i < sb->len; i += len, j += newlen) {\n+\t\teol = memchr(sb->buf + i, '\\n', sb->len - i);\n+\t\tlen = eol ? eol - (sb->buf + i) + 1 : sb->len - i;\n+\n+\t\tif (skip_comments && len && sb->buf[i] == '#') {\n \t\t\tnewlen = 0;\n \t\t\tcontinue;\n \t\t}\n-\t\tnewlen = cleanup(buffer + i, len);\n+\t\tnewlen = cleanup(sb->buf + i, len);\n \n \t\t/* Not just an empty line? */\n \t\tif (newlen) {\n-\t\t\tif (empties != -1)\n-\t\t\t\tbuffer[j++] = '\\n';\n-\t\t\tif (empties > 0)\n-\t\t\t\tbuffer[j++] = '\\n';\n+\t\t\tif (empties > 0 && j > 0)\n+\t\t\t\tsb->buf[j++] = '\\n';\n \t\t\tempties = 0;\n-\t\t\tmemmove(buffer + j, buffer + i, newlen);\n-\t\t\tcontinue;\n+\t\t\tmemmove(sb->buf + j, sb->buf + i, newlen);\n+\t\t\tsb->buf[newlen + j++] = '\\n';\n+\t\t} else {\n+\t\t\tempties++;\n \t\t}\n-\t\tif (empties < 0)\n-\t\t\tcontinue;\n-\t\tempties++;\n \t}\n \n-\treturn j;\n+\tstrbuf_setlen(sb, j);\n }\n \n int cmd_stripspace(int argc, const char **argv, const char *prefix)\n@@ -86,9 +82,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix)\n \tif (strbuf_read(&buf, 0, 1024) < 0)\n \t\tdie(\"could not read the input\");\n \n-\tstrbuf_setlen(&buf, stripspace(buf.buf, buf.len, strip_comments));\n-\tif (buf.len)\n-\t\tstrbuf_addch(&buf, '\\n');\n+\tstripspace(&buf, strip_comments);\n \n \twrite_or_die(1, buf.buf, buf.len);\n \tstrbuf_release(&buf);\ndiff --git a/builtin-tag.c b/builtin-tag.c\nindex 9f29342..68c9a20 100644\n--- a/builtin-tag.c\n+++ b/builtin-tag.c\n@@ -297,14 +297,11 @@ static void create_tag(const unsigned char *object, const char *tag,\n \t\tfree(path);\n \t}\n \n-\tstrbuf_setlen(buf, stripspace(buf->buf, buf->len, 1));\n+\tstripspace(buf, 1);\n \n \tif (!message && !buf->len)\n \t\tdie(\"no tag message?\");\n \n-\t/* insert the header and add the '\\n' if needed: */\n-\tif (buf->len)\n-\t\tstrbuf_addch(buf, '\\n');\n \tstrbuf_insert(buf, 0, header_buf, header_len);\n \n \tif (sign && do_sign(buf) < 0)\ndiff --git a/builtin.h b/builtin.h\nindex 03ee7bf..d6f2c76 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -7,7 +7,6 @@ extern const char git_version_string[];\n extern const char git_usage_string[];\n \n extern void help_unknown_cmd(const char *cmd);\n-extern size_t stripspace(char *buffer, size_t length, int skip_comments);\n extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);\n extern void prune_packed_objects(int);\n \ndiff --git a/strbuf.h b/strbuf.h\nindex 21fc111..5960637 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -98,4 +98,6 @@ extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);\n \n extern void read_line(struct strbuf *, FILE *, int);\n \n+extern void stripspace(struct strbuf *buf, int skip_comments);\n+\n #endif /* STRBUF_H */\n-- \n1.5.3.1.993.gbf388-dirty\n"},{"id":"53375","messageId":"11900740154136-git-send-email-krh@redhat.com","threadId":"9904","inReplyTo":"11900740153845-git-send-email-krh@redhat.com","subject":"[PATCH 5/7] Add strbuf_read_file().","fromName":"Kristian Høgsberg","fromEmail":"krh@redhat.com","sentAt":"2007-09-18T00:06:46Z","receivedAt":"2007-09-18T00:06:46Z","isPatch":true,"sender":{"key":"krh@redhat.com","avatar":"https://gravatar.com/avatar/763dee6f9594ac474f725b137a39565792928e583ddf59b32befc2907409027e?d=mp&s=160"},"body":"Signed-off-by: Kristian Høgsberg <krh@redhat.com>\n---\n builtin-tag.c |   10 +++-------\n strbuf.c      |   15 +++++++++++++++\n strbuf.h      |    1 +\n 3 files changed, 19 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin-tag.c b/builtin-tag.c\nindex 68c9a20..c97673f 100644\n--- a/builtin-tag.c\n+++ b/builtin-tag.c\n@@ -53,13 +53,9 @@ static void launch_editor(const char *path, struct strbuf *buffer)\n \tif (run_command(&child))\n \t\tdie(\"There was a problem with the editor %s.\", editor);\n \n-\tfd = open(path, O_RDONLY);\n-\tif (fd < 0)\n-\t\tdie(\"could not open '%s': %s\", path, strerror(errno));\n-\tif (strbuf_read(buffer, fd, 0) < 0) {\n-\t\tdie(\"could not read message file '%s': %s\", path, strerror(errno));\n-\t}\n-\tclose(fd);\n+\tif (strbuf_read_file(buffer, path) < 0)\n+\t\tdie(\"could not read message file '%s': %s\",\n+\t\t    path, strerror(errno));\n }\n \n struct tag_filter {\ndiff --git a/strbuf.c b/strbuf.c\nindex d919047..438606d 100644\n--- a/strbuf.c\n+++ b/strbuf.c\n@@ -110,6 +110,21 @@ ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)\n \treturn sb->len - oldlen;\n }\n \n+int strbuf_read_file(struct strbuf *sb, const char *path)\n+{\n+\tint fd, len;\n+\n+\tfd = open(path, O_RDONLY);\n+\tif (fd < 0)\n+\t\treturn -1;\n+\tlen = strbuf_read(sb, fd, 0);\n+\tclose(fd);\n+\tif (len < 0)\n+\t\treturn -1;\n+\n+\treturn len;\n+}\n+\n void read_line(struct strbuf *sb, FILE *fp, int term) {\n \tint ch;\n \tif (feof(fp)) {\ndiff --git a/strbuf.h b/strbuf.h\nindex 5960637..3a523ab 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -95,6 +95,7 @@ extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);\n extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);\n /* XXX: if read fails, any partial read is undone */\n extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);\n+extern int strbuf_read_file(struct strbuf *sb, const char *path);\n \n extern void read_line(struct strbuf *, FILE *, int);\n \n-- \n1.5.3.1.993.gbf388-dirty\n"},{"id":"53378","messageId":"1190074016669-git-send-email-krh@redhat.com","threadId":"9904","inReplyTo":"11900740154136-git-send-email-krh@redhat.com","subject":"[PATCH 6/7] Export rerere() and launch_editor().","fromName":"Kristian Høgsberg","fromEmail":"krh@redhat.com","sentAt":"2007-09-18T00:06:47Z","receivedAt":"2007-09-18T00:06:47Z","isPatch":true,"sender":{"key":"krh@redhat.com","avatar":"https://gravatar.com/avatar/763dee6f9594ac474f725b137a39565792928e583ddf59b32befc2907409027e?d=mp&s=160"},"body":"Signed-off-by: Kristian Høgsberg <krh@redhat.com>\n---\n builtin-rerere.c |   16 ++++++++++++++++\n commit.h         |    1 +\n 2 files changed, 17 insertions(+), 0 deletions(-)\n\ndiff --git a/builtin-rerere.c b/builtin-rerere.c\nindex 826d346..54f3575 100644\n--- a/builtin-rerere.c\n+++ b/builtin-rerere.c\n@@ -395,6 +395,22 @@ static int is_rerere_enabled(void)\n \treturn 1;\n }\n \n+/* Export for builtin-commit. */\n+int rerere(void)\n+{\n+\tstruct path_list merge_rr = { NULL, 0, 0, 1 };\n+\tint fd;\n+\n+\tgit_config(git_rerere_config);\n+\tif (!is_rerere_enabled())\n+\t\treturn 0;\n+\n+\tmerge_rr_path = xstrdup(git_path(\"rr-cache/MERGE_RR\"));\n+\tfd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);\n+\tread_rr(&merge_rr);\n+\treturn do_plain_rerere(&merge_rr, fd);\n+}\n+\n int cmd_rerere(int argc, const char **argv, const char *prefix)\n {\n \tstruct path_list merge_rr = { NULL, 0, 0, 1 };\ndiff --git a/commit.h b/commit.h\nindex da34c23..cc8d890 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -130,5 +130,6 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,\n int in_merge_bases(struct commit *, struct commit **, int);\n \n extern int interactive_add(void);\n+extern int rerere(void);\n \n #endif /* COMMIT_H */\n-- \n1.5.3.1.993.gbf388-dirty\n"},{"id":"53379","messageId":"11900740163661-git-send-email-krh@redhat.com","threadId":"9904","inReplyTo":"1190074016669-git-send-email-krh@redhat.com","subject":"[PATCH 7/7] Implement git commit as a builtin command.","fromName":"Kristian Høgsberg","fromEmail":"krh@redhat.com","sentAt":"2007-09-18T00:06:48Z","receivedAt":"2007-09-18T00:06:48Z","isPatch":true,"sender":{"key":"krh@redhat.com","avatar":"https://gravatar.com/avatar/763dee6f9594ac474f725b137a39565792928e583ddf59b32befc2907409027e?d=mp&s=160"},"body":"Move git-commit.sh to contrib/examples.\n\nSigned-off-by: Kristian Høgsberg <krh@redhat.com>\n---\n Makefile                                        |    9 +-\n builtin-commit.c                                |  740 +++++++++++++++++++++++\n builtin-tag.c                                   |    2 +-\n builtin.h                                       |    3 +-\n git-commit.sh => contrib/examples/git-commit.sh |    0 \n git.c                                           |    3 +-\n strbuf.h                                        |    1 +\n t/t3501-revert-cherry-pick.sh                   |    4 +-\n t/t3901-i18n-patch.sh                           |    8 +-\n t/test-lib.sh                                   |    4 +-\n 10 files changed, 757 insertions(+), 17 deletions(-)\n create mode 100644 builtin-commit.c\n rename git-commit.sh => contrib/examples/git-commit.sh (100%)\n\ndiff --git a/Makefile b/Makefile\nindex 0055eef..df82d92 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -206,7 +206,7 @@ BASIC_LDFLAGS =\n \n SCRIPT_SH = \\\n \tgit-bisect.sh git-checkout.sh \\\n-\tgit-clean.sh git-clone.sh git-commit.sh \\\n+\tgit-clean.sh git-clone.sh \\\n \tgit-fetch.sh \\\n \tgit-ls-remote.sh \\\n \tgit-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \\\n@@ -255,7 +255,7 @@ EXTRA_PROGRAMS =\n BUILT_INS = \\\n \tgit-format-patch$X git-show$X git-whatchanged$X git-cherry$X \\\n \tgit-get-tar-commit-id$X git-init$X git-repo-config$X \\\n-\tgit-fsck-objects$X git-cherry-pick$X \\\n+\tgit-fsck-objects$X git-cherry-pick$X git-status$X\\\n \t$(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))\n \n # what 'all' will build and 'install' will install, in gitexecdir\n@@ -327,6 +327,7 @@ BUILTIN_OBJS = \\\n \tbuiltin-check-attr.o \\\n \tbuiltin-checkout-index.o \\\n \tbuiltin-check-ref-format.o \\\n+\tbuiltin-commit.o \\\n \tbuiltin-commit-tree.o \\\n \tbuiltin-count-objects.o \\\n \tbuiltin-describe.o \\\n@@ -363,7 +364,6 @@ BUILTIN_OBJS = \\\n \tbuiltin-rev-parse.o \\\n \tbuiltin-revert.o \\\n \tbuiltin-rm.o \\\n-\tbuiltin-runstatus.o \\\n \tbuiltin-shortlog.o \\\n \tbuiltin-show-branch.o \\\n \tbuiltin-stripspace.o \\\n@@ -819,9 +819,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl\n \tchmod +x $@+ && \\\n \tmv $@+ $@\n \n-git-status: git-commit\n-\t$(QUIET_GEN)cp $< $@+ && mv $@+ $@\n-\n gitweb/gitweb.cgi: gitweb/gitweb.perl\n \t$(QUIET_GEN)$(RM) $@ $@+ && \\\n \tsed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \\\ndiff --git a/builtin-commit.c b/builtin-commit.c\nnew file mode 100644\nindex 0000000..ee98de9\n--- /dev/null\n+++ b/builtin-commit.c\n@@ -0,0 +1,740 @@\n+/*\n+ * Builtin \"git commit\"\n+ *\n+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>\n+ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds\n+ */\n+\n+#include <sys/types.h>\n+#include <sys/stat.h>\n+#include <unistd.h>\n+\n+#include \"cache.h\"\n+#include \"cache-tree.h\"\n+#include \"builtin.h\"\n+#include \"diff.h\"\n+#include \"diffcore.h\"\n+#include \"commit.h\"\n+#include \"revision.h\"\n+#include \"wt-status.h\"\n+#include \"run-command.h\"\n+#include \"refs.h\"\n+#include \"log-tree.h\"\n+#include \"strbuf.h\"\n+#include \"utf8.h\"\n+\n+static const char builtin_commit_usage[] =\n+\t\"[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]\";\n+\n+static unsigned char head_sha1[20], merge_head_sha1[20];\n+static char *use_message_buffer;\n+static const char commit_editmsg[] = \"COMMIT_EDITMSG\";\n+static struct lock_file lock_file;\n+\n+enum option_type {\n+    OPTION_NONE,\n+    OPTION_STRING,\n+    OPTION_INTEGER,\n+    OPTION_LAST,\n+};\n+\n+struct option {\n+    enum option_type type;\n+    const char *long_name;\n+    char short_name;\n+    void *value;\n+};\n+\n+static int scan_options(const char ***argv, struct option *options)\n+{\n+\tconst char *value, *eq;\n+\tint i;\n+\n+\tif (**argv == NULL)\n+\t\treturn 0;\n+\tif ((**argv)[0] != '-')\n+\t\treturn 0;\n+\tif (!strcmp(**argv, \"--\"))\n+\t\treturn 0;\n+\n+\tvalue = NULL;\n+\tfor (i = 0; options[i].type != OPTION_LAST; i++) {\n+\t\tif ((**argv)[1] == '-') {\n+\t\t\tif (!prefixcmp(options[i].long_name, **argv + 2)) {\n+\t\t\t\tif (options[i].type != OPTION_NONE)\n+\t\t\t\t\tvalue = *++(*argv);\n+\t\t\t\tgoto match;\n+\t\t\t}\n+\n+\t\t\teq = strchr(**argv + 2, '=');\n+\t\t\tif (eq && options[i].type != OPTION_NONE &&\n+\t\t\t    !strncmp(**argv + 2, \n+\t\t\t\t     options[i].long_name, eq - **argv - 2)) {\n+\t\t\t\tvalue = eq + 1;\n+\t\t\t\tgoto match;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif ((**argv)[1] == options[i].short_name) {\n+\t\t\tif ((**argv)[2] == '\\0') {\n+\t\t\t\tif (options[i].type != OPTION_NONE)\n+\t\t\t\t\tvalue = *++(*argv);\n+\t\t\t\tgoto match;\n+\t\t\t}\n+\n+\t\t\tif (options[i].type != OPTION_NONE) {\n+\t\t\t\tvalue = **argv + 2;\n+\t\t\t\tgoto match;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tusage(builtin_commit_usage);\n+\n+ match:\n+\tswitch (options[i].type) {\n+\tcase OPTION_NONE:\n+\t\t*(int *)options[i].value = 1;\n+\t\tbreak;\n+\tcase OPTION_STRING:\n+\t\tif (value == NULL)\n+\t\t\tdie(\"option %s requires a value.\", (*argv)[-1]);\n+\t\t*(const char **)options[i].value = value;\n+\t\tbreak;\n+\tcase OPTION_INTEGER:\n+\t\tif (value == NULL)\n+\t\t\tdie(\"option %s requires a value.\", (*argv)[-1]);\n+\t\t*(int *)options[i].value = atoi(value);\n+\t\tbreak;\n+\tdefault:\n+\t\tassert(0);\n+\t}\n+\n+\t(*argv)++;\n+\n+\treturn 1;\n+}\n+\n+static char *logfile, *force_author, *message, *template_file;\n+static char *edit_message, *use_message;\n+static int all, edit_flag, also, interactive, only, no_verify, amend, signoff;\n+static int quiet, verbose, untracked_files;\n+\n+static int no_edit, initial_commit, in_merge;\n+const char *only_include_assumed;\n+\n+static struct option commit_options[] = {\n+\t{ OPTION_STRING, \"file\", 'F', (void *) &logfile },\n+\t{ OPTION_NONE, \"all\", 'a', &all },\n+\t{ OPTION_STRING, \"author\", 0, (void *) &force_author },\n+\t{ OPTION_NONE, \"edit\", 'e', &edit_flag },\n+\t{ OPTION_NONE, \"include\", 'i', &also },\n+\t{ OPTION_NONE, \"interactive\", 0, &interactive },\n+\t{ OPTION_NONE, \"only\", 'o', &only },\n+\t{ OPTION_STRING, \"message\", 'm', &message },\n+\t{ OPTION_NONE, \"no-verify\", 'n', &no_verify },\n+\t{ OPTION_NONE, \"amend\", 0, &amend },\n+\t{ OPTION_STRING, \"reedit-message\", 'c', &edit_message },\n+\t{ OPTION_STRING, \"reuse-message\", 'C', &use_message },\n+\t{ OPTION_NONE, \"signoff\", 's', &signoff },\n+\t{ OPTION_NONE, \"quiet\", 'q', &quiet },\n+\t{ OPTION_NONE, \"verbose\", 'v', &verbose },\n+\t{ OPTION_NONE, \"untracked-files\", 0, &untracked_files },\n+\t{ OPTION_STRING, \"template\", 't', &template_file },\n+\t{ OPTION_LAST },\n+};\n+\n+/* FIXME: Taken from builtin-add, should be shared. */\n+\n+static void update_callback(struct diff_queue_struct *q,\n+\t\t\t    struct diff_options *opt, void *cbdata)\n+{\n+\tint i, verbose;\n+\n+\tverbose = *((int *)cbdata);\n+\tfor (i = 0; i < q->nr; i++) {\n+\t\tstruct diff_filepair *p = q->queue[i];\n+\t\tconst char *path = p->one->path;\n+\t\tswitch (p->status) {\n+\t\tdefault:\n+\t\t\tdie(\"unexpacted diff status %c\", p->status);\n+\t\tcase DIFF_STATUS_UNMERGED:\n+\t\tcase DIFF_STATUS_MODIFIED:\n+\t\tcase DIFF_STATUS_TYPE_CHANGED:\n+\t\t\tadd_file_to_cache(path, verbose);\n+\t\t\tbreak;\n+\t\tcase DIFF_STATUS_DELETED:\n+\t\t\tremove_file_from_cache(path);\n+\t\t\tcache_tree_invalidate_path(active_cache_tree, path);\n+\t\t\tif (verbose)\n+\t\t\t\tprintf(\"remove '%s'\\n\", path);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+}\n+\n+static void\n+add_files_to_cache(int fd, const char **files, const char *prefix)\n+{\n+\tstruct rev_info rev;\n+\n+\tinit_revisions(&rev, \"\");\n+\tsetup_revisions(0, NULL, &rev, NULL);\n+\trev.prune_data = get_pathspec(prefix, files);\n+\trev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;\n+\trev.diffopt.format_callback = update_callback;\n+\trev.diffopt.format_callback_data = &verbose;\n+\n+\trun_diff_files(&rev, 0);\n+\trefresh_cache(REFRESH_QUIET);\n+\n+\tif (write_cache(fd, active_cache, active_nr) || close(fd))\n+\t\tdie(\"unable to write new index file\");\n+}\n+\n+static char *\n+prepare_index(const char **files, const char *prefix)\n+{\n+\tint fd;\n+\tstruct tree *tree;\n+\tstruct lock_file *next_index_lock;\n+\n+\tfd = hold_locked_index(&lock_file, 1);\n+\tif (read_cache() < 0)\n+\t\tdie(\"index file corrupt\");\n+\n+\tif (all) {\n+\t\tadd_files_to_cache(fd, files, NULL);\n+\t\treturn lock_file.filename;\n+\t} else if (also) {\n+\t\tadd_files_to_cache(fd, files, prefix);\n+\t\treturn lock_file.filename;\n+\t}\n+\n+\tif (interactive)\n+\t\tinteractive_add();\n+\n+\tif (*files == NULL) {\n+\t\t/* Commit index as-is. */\n+\t\trollback_lock_file(&lock_file);\n+\t\treturn get_index_file();\n+\t}\n+\n+\t/*\n+\t * FIXME: Warn on unknown files.  Shell script does\n+\t *\n+\t *   commit_only=`git-ls-files --error-unmatch -- \"$@\"`\n+\t */\n+\n+\t/*\n+\t * FIXME: shell script does\n+\t *\n+\t *   git-read-tree --index-output=\"$TMP_INDEX\" -i -m HEAD\n+\t *\n+\t * which warns about unmerged files in the index.\n+\t */\n+\n+\t/* update the user index file */\n+\tadd_files_to_cache(fd, files, prefix);\n+\n+\tif (!initial_commit) {\n+\t\ttree = parse_tree_indirect(head_sha1);\n+\t\tif (!tree)\n+\t\t\tdie(\"failed to unpack HEAD tree object\");\n+\t\tif (read_tree(tree, 0, NULL))\n+\t\t\tdie(\"failed to read HEAD tree object\");\n+\t}\n+\n+\t/* Uh oh, abusing lock_file to create a garbage collected file */\n+\tnext_index_lock = xmalloc(sizeof(*next_index_lock));\n+\tfd = hold_lock_file_for_update(next_index_lock,\n+\t\t\t\t       git_path(\"next-index-%d\", getpid()), 1);\n+\tadd_files_to_cache(fd, files, prefix);\n+\n+\treturn next_index_lock->filename;\n+}\n+\n+static int run_status(FILE *fp, const char *index_file)\n+{\n+\tstruct wt_status s;\n+\n+\twt_status_prepare(&s);\n+\n+\tif (amend) {\n+\t\ts.amend = 1;\n+\t\ts.reference = \"HEAD^1\";\n+\t}\n+\ts.verbose = verbose;\n+\ts.untracked = untracked_files;\n+\ts.index_file = index_file;\n+\ts.fp = fp;\n+\n+\twt_status_print(&s);\n+\n+\treturn s.commitable;\n+}\n+\n+static const char sign_off_header[] = \"Signed-off-by: \";\n+\n+static int prepare_log_message(const char *index_file)\n+{\n+\tstruct stat statbuf;\n+\tint commitable;\n+\tstruct strbuf sb;\n+\tchar *buffer;\n+\tFILE *fp;\n+\n+\tstrbuf_init(&sb, 0);\n+\tif (message) {\n+\t\tstrbuf_add(&sb, message, strlen(message));\n+\t} else if (logfile && !strcmp(logfile, \"-\")) {\n+\t\tif (isatty(0))\n+\t\t\tfprintf(stderr, \"(reading log message from standard input)\\n\");\n+\t\tif (strbuf_read(&sb, 0, 0) < 0)\n+\t\t\tdie(\"could not read log from standard input\");\n+\t} else if (logfile) {\n+\t\tif (strbuf_read_file(&sb, logfile) < 0)\n+\t\t\tdie(\"could not read log file '%s': %s\",\n+\t\t\t    logfile, strerror(errno));\n+\t} else if (use_message) {\n+\t\tbuffer = strstr(use_message_buffer, \"\\n\\n\");\n+\t\tif (!buffer || buffer[2] == '\\0')\n+\t\t\tdie(\"commit has empty message\");\n+\t\tstrbuf_add(&sb, buffer + 2, strlen(buffer + 2));\n+\t} else if (!stat(git_path(\"MERGE_MSG\"), &statbuf)) {\n+\t\tif (strbuf_read_file(&sb, git_path(\"MERGE_MSG\")) < 0)\n+\t\t\tdie(\"could not read MERGE_MSG: %s\", strerror(errno));\n+\t} else if (!stat(git_path(\"SQUASH_MSG\"), &statbuf)) {\n+\t\tif (strbuf_read_file(&sb, git_path(\"SQUASH_MSG\")) < 0)\n+\t\t\tdie(\"could not read SQUASH_MSG: %s\", strerror(errno));\n+\t} else if (!stat(template_file, &statbuf)) {\n+\t\tif (strbuf_read_file(&sb, template_file) < 0)\n+\t\t\tdie(\"could not read %s: %s\",\n+\t\t\t    template_file, strerror(errno));\n+\t}\n+\n+\tfp = fopen(git_path(commit_editmsg), \"w\");\n+\tif (fp == NULL)\n+\t\tdie(\"could not open %s\\n\", git_path(commit_editmsg));\n+\t\t\n+\tstripspace(&sb, 0);\n+\tif (fwrite(sb.buf, 1, sb.len, fp) < sb.len)\n+\t\tdie(\"could not write commit template: %s\\n\",\n+\t\t    strerror(errno));\n+\n+\tif (signoff) {\n+\t\tconst char *info, *bol;\n+\n+\t\tinfo = git_committer_info(1);\n+\t\tstrbuf_addch(&sb, '\\0');\n+\t\tbol = strrchr(sb.buf + sb.len - 1, '\\n');\n+\t\tif (!bol || prefixcmp(bol, sign_off_header))\n+\t\t\tfprintf(fp, \"\\n\");\n+\t\tfprintf(fp, \"%s%s\\n\", sign_off_header, git_committer_info(1));\n+\t}\n+\n+\tstrbuf_release(&sb);\n+\n+\tif (in_merge && !no_edit) {\n+\t\tfprintf(fp,\n+\t\t\t\"#\\n\"\n+\t\t\t\"# It looks like you may be committing a MERGE.\\n\"\n+\t\t\t\"# If this is not correct, please remove the file\\n\"\n+\t\t\t\"#\t%s\\n\"\n+\t\t\t\"# and try again.\\n\"\n+\t\t\t\"#\\n\",\n+\t\t\tgit_path(\"MERGE_HEAD\"));\n+\t}\n+\n+\tfprintf(fp,\n+\t\t\"\\n\"\n+\t\t\"# Please enter the commit message for your changes.\\n\"\n+\t\t\"# (Comment lines starting with '#' will not be included)\\n\");\n+\tif (only_include_assumed)\n+\t\tfprintf(fp, \"# %s\\n\", only_include_assumed);\n+\n+\tcommitable = run_status(fp, index_file);\n+\n+\tfclose(fp);\n+\n+\treturn commitable;\n+}\n+\n+/* Find out if the message starting at position 'start' in the strbuf\n+ * contains only whitespace and Signed-off-by lines. */\n+static int message_is_empty(struct strbuf *sb, int start)\n+{\n+\tstruct strbuf tmpl;\n+\tconst char *nl;\n+\tint eol, i;\n+\n+\t/* See if the template is just a prefix of the message. */\n+\tstrbuf_init(&tmpl, 0);\n+\tif (template_file && strbuf_read_file(&tmpl, template_file) > 0) {\n+\t\tstripspace(&tmpl, 1);\n+\t\tif (start + tmpl.len <= sb->len &&\n+\t\t    memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)\n+\t\t\tstart += tmpl.len;\n+\t}\n+\tstrbuf_release(&tmpl);\n+\n+\t/* Check if the rest is just whitespace and Signed-of-by's. */\n+\tfor (i = start; i < sb->len; i++) {\n+\t\tnl = memchr(sb->buf + i, '\\n', sb->len - i);\n+\t\tif (nl)\n+\t\t\teol = nl - sb->buf;\n+\t\telse\n+\t\t\teol = sb->len;\n+\n+\t\tif (strlen(sign_off_header) <= eol - i &&\n+\t\t    !prefixcmp(sb->buf + i, sign_off_header)) {\n+\t\t\ti = eol;\n+\t\t\tcontinue;\n+\t\t}\n+\t\twhile (i < eol)\n+\t\t\tif (!isspace(sb->buf[i++]))\n+\t\t\t\treturn 0;\n+\t}\n+\n+\treturn 1;\n+}\n+\n+static void determine_author_info(struct strbuf *sb)\n+{\n+\tchar *p, *eol;\n+\tchar *name = NULL, *email = NULL;\n+\n+\tif (use_message) {\n+\t\tp = strstr(use_message_buffer, \"\\nauthor\");\n+\t\tif (!p)\n+\t\t\tdie(\"invalid commit: %s\\n\", use_message);\n+\t\tp++;\n+\t\teol = strchr(p, '\\n');\n+\t\tif (!eol)\n+\t\t\tdie(\"invalid commit: %s\\n\", use_message);\n+\n+\t\tstrbuf_add(sb, p, eol + 1 - p);\n+\t} else if (force_author) {\n+\t\tconst char *eoname = strstr(force_author, \" <\");\n+\t\tconst char *eomail = strchr(force_author, '>');\n+\n+\t\tif (!eoname || !eomail)\n+\t\t\tdie(\"malformed --author parameter\\n\");\n+\t\tname = xstrndup(force_author, eoname - force_author);\n+\t\temail = xstrndup(eoname + 2, eomail - eoname - 2);\n+\t\tstrbuf_addf(sb, \"author %s\\n\",\n+\t\t\t    fmt_ident(name, email, \n+\t\t\t\t      getenv(\"GIT_AUTHOR_DATE\"), 1));\n+\t\tfree(name);\n+\t\tfree(email);\n+\t} else {\n+\t\tstrbuf_addf(sb, \"author %s\\n\", git_author_info(1));\n+\t}\n+}\n+\n+static void parse_and_validate_options(const char ***argv)\n+{\n+\tint f = 0;\n+\n+\t(*argv)++;\n+\twhile (scan_options(argv, commit_options))\n+\t\t;\n+\n+\tif (logfile || message || use_message)\n+\t\tno_edit = 1;\n+\tif (edit_flag)\n+\t\tno_edit = 0;\n+\n+\tif (get_sha1(\"HEAD\", head_sha1))\n+\t\tinitial_commit = 1;\n+\n+\tif (!get_sha1(\"MERGE_HEAD\", merge_head_sha1))\n+\t\tin_merge = 1;\n+\n+\t/* Sanity check options */\n+\tif (amend && initial_commit)\n+\t\tdie(\"You have nothing to amend.\");\n+\tif (amend && in_merge)\n+\t\tdie(\"You are in the middle of a merger -- cannot amend.\");\n+\n+\tif (use_message)\n+\t\tf++;\n+\tif (edit_message)\n+\t\tf++;\n+\tif (logfile)\n+\t\tf++;\n+\tif (f > 1)\n+\t\tdie(\"Only one of -c/-C/-F can be used.\");\n+\tif (message && f > 0)\n+\t\tdie(\"Option -m cannot be combined with -c/-C/-F.\");\n+\tif (edit_message)\n+\t\tuse_message = edit_message;\n+\tif (amend)\n+\t\tuse_message = \"HEAD\";\n+\tif (use_message) {\n+\t\tunsigned char sha1[20];\n+\t\tstatic char utf8[] = \"UTF-8\";\n+\t\tconst char *out_enc;\n+\t\tchar *enc, *end;\n+\t\tstruct commit *commit;\n+\n+\t\tif (get_sha1(use_message, sha1))\n+\t\t\tdie(\"could not lookup commit %s\", use_message);\n+\t\tcommit = lookup_commit(sha1);\n+\t\tif (!commit || parse_commit(commit))\n+\t\t\tdie(\"could not parse commit %s\", use_message);\n+\n+\t\tenc = strstr(commit->buffer, \"\\nencoding\");\n+\t\tif (enc) {\n+\t\t\tend = strchr(enc + 10, '\\n');\n+\t\t\tenc = xstrndup(enc + 10, end - (enc + 10));\n+\t\t} else {\n+\t\t\tenc = utf8;\n+\t\t}\n+\t\tout_enc = git_commit_encoding ? git_commit_encoding : utf8;\n+\n+\t\tuse_message_buffer =\n+\t\t\treencode_string(commit->buffer, out_enc, enc);\n+\t\tif (enc != utf8)\n+\t\t\tfree(enc);\n+\t}\n+\n+\tif (also && only)\n+\t\tdie(\"Only one of --include/--only can be used.\");\n+\tif (!*argv && (also || (only && !amend)))\n+\t\tdie(\"No paths with --include/--only does not make sense.\");\n+\tif (!*argv && only && amend)\n+\t\tonly_include_assumed = \"Clever... amending the last one with dirty index.\";\n+\tif (*argv && !also && !only) {\n+\t\tonly_include_assumed = \"Explicit paths specified without -i nor -o; assuming --only paths...\";\n+\t\talso = 0;\n+\t}\n+\n+\tif (all && interactive)\n+\t\tdie(\"Cannot use -a, --interactive or -i at the same time.\");\n+\telse if (all && **argv)\n+\t\tdie(\"Paths with -a does not make sense.\");\n+\telse if (interactive && **argv)\n+\t\tdie(\"Paths with --interactive does not make sense.\");\n+}\n+\n+int cmd_status(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char *index_file;\n+\tint commitable;\n+\n+\tgit_config(git_status_config);\n+\n+\tparse_and_validate_options(&argv);\n+\n+\tindex_file = prepare_index(argv, prefix);\n+\n+\tcommitable = run_status(stdout, index_file);\n+\n+\trollback_lock_file(&lock_file);\n+\n+\treturn commitable ? 0 : 1;\n+}\n+\n+static int run_hook(const char *index_file, const char *name, const char *arg)\n+{\n+\tstruct child_process hook;\n+\tconst char *argv[3], *env[2];\n+\tchar index[PATH_MAX];\n+\n+\targv[0] = git_path(\"hooks/%s\", name);\n+\targv[1] = arg;\n+\targv[2] = NULL;\n+\tsnprintf(index, sizeof(index), \"GIT_INDEX_FILE=%s\", index_file);\n+\tenv[0] = index;\n+\tenv[1] = NULL;\n+\n+\tif (access(argv[0], X_OK) < 0)\n+\t\treturn 0;\n+\n+\tmemset(&hook, 0, sizeof(hook));\n+\thook.argv = argv;\n+\thook.no_stdin = 1;\n+\thook.stdout_to_stderr = 1;\n+\thook.env = env;\n+\n+\treturn run_command(&hook);\n+}\n+\n+static void print_summary(const char *prefix, const unsigned char *sha1)\n+{\n+\tstruct rev_info rev;\n+\tstruct commit *commit;\n+\n+\tcommit = lookup_commit(sha1);\n+\tif (!commit)\n+\t\tdie(\"couldn't look up newly created commit\\n\");\n+\tif (!commit || parse_commit(commit))\n+\t\tdie(\"could not parse newly created commit\");\n+\n+\tinit_revisions(&rev, prefix);\n+\tsetup_revisions(0, NULL, &rev, NULL);\n+\n+\trev.abbrev = 0;\n+\trev.diff = 1;\n+\trev.diffopt.output_format =\n+\t\tDIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;\n+\n+\trev.verbose_header = 1;\n+\trev.show_root_diff = 1;\n+\trev.commit_format = get_commit_format(\"format:%h: %s\");\n+\trev.always_show_header = 1;\n+\t\t\n+\tprintf(\"Created %scommit \", initial_commit ? \"initial \" : \"\");\n+\n+\tlog_tree_commit(&rev, commit);\n+}\n+\n+int git_commit_config(const char *k, const char *v)\n+{\n+\tif (!strcmp(k, \"commit.template\")) {\n+\t\ttemplate_file = xstrdup(v);\n+\t\treturn 0;\n+\t}\n+\n+\treturn git_status_config(k, v);\n+}\n+\n+static const char commit_utf8_warn[] =\n+\"Warning: commit message does not conform to UTF-8.\\n\"\n+\"You may want to amend it after fixing the message, or set the config\\n\"\n+\"variable i18n.commitencoding to the encoding your project uses.\\n\";\n+\n+int cmd_commit(int argc, const char **argv, const char *prefix)\n+{\n+\tint header_len, parent_count = 0;\n+\tstruct strbuf sb;\n+\tconst char *index_file, *reflog_msg;\n+\tchar *nl, *header_line;\n+\tunsigned char commit_sha1[20];\n+\tstruct ref_lock *ref_lock;\n+\n+\tgit_config(git_commit_config);\n+\n+\tparse_and_validate_options(&argv);\n+\n+\tindex_file = prepare_index(argv, prefix);\n+\n+\tif (run_hook(index_file, \"pre-commit\", NULL))\n+\t\texit(1);\n+\n+\tif (!prepare_log_message(index_file) && !in_merge) {\n+\t\trun_status(stdout, index_file);\n+\t\tunlink(commit_editmsg);\n+\t\treturn 1;\n+\t}\n+\n+\tstrbuf_init(&sb, 0);\n+\n+\t/* Start building up the commit header */\n+\tread_cache_from(index_file);\n+\tactive_cache_tree = cache_tree();\n+\tif (cache_tree_update(active_cache_tree,\n+\t\t\t      active_cache, active_nr, 0, 0) < 0)\n+\t\tdie(\"Error building trees\");\n+\tstrbuf_addf(&sb, \"tree %s\\n\",\n+\t\t    sha1_to_hex(active_cache_tree->sha1));\n+\n+\t/* Determine parents */\n+\tif (initial_commit) {\n+\t\treflog_msg = \"commit (initial)\"; \n+\t\tparent_count = 0;\n+\t} else if (amend) {\n+\t\tstruct commit_list *c;\n+\t\tstruct commit *commit;\n+\n+\t\treflog_msg = \"commit (amend)\";\n+\t\tcommit = lookup_commit(head_sha1);\n+\t\tif (!commit || parse_commit(commit))\n+\t\t\tdie(\"could not parse HEAD commit\");\n+\n+\t\tfor (c = commit->parents; c; c = c->next)\n+\t\t\tstrbuf_addf(&sb, \"parent %s\\n\",\n+\t\t\t\t      sha1_to_hex(c->item->object.sha1));\n+\t} else if (in_merge) {\n+\t\tstruct strbuf m;\n+\t\tFILE *fp;\n+\n+\t\treflog_msg = \"commit (merge)\";\n+\t\tstrbuf_addf(&sb, \"parent %s\\n\", sha1_to_hex(head_sha1));\n+\t\tstrbuf_init(&m, 0);\n+\t\tfp = fopen(git_path(\"MERGE_HEAD\"), \"r\");\n+\t\tif (fp == NULL)\n+\t\t\tdie(\"could not open %s for reading: %s\",\n+\t\t\t    git_path(\"MERGE_HEAD\"), strerror(errno));\n+\t\twhile (!m.eof) {\n+\t\t\tread_line(&m, fp, '\\n');\n+\t\t\tif (!m.eof)\n+\t\t\t\tstrbuf_addf(&sb, \"parent %s\\n\", m.buf);\n+\t\t}\n+\t\tfclose(fp);\n+\t\tstrbuf_release(&m);\n+\t} else {\n+\t\treflog_msg = \"commit\";\n+\t\tstrbuf_addf(&sb, \"parent %s\\n\", sha1_to_hex(head_sha1));\n+\t}\n+\n+\tdetermine_author_info(&sb);\n+\tstrbuf_addf(&sb, \"committer %s\\n\", git_committer_info(1));\n+\tif (!is_encoding_utf8(git_commit_encoding))\n+\t\tstrbuf_addf(&sb, \"encoding %s\\n\", git_commit_encoding);\n+\tstrbuf_addch(&sb, '\\n');\n+\n+\t/* Get the commit message and validate it */\n+\theader_len = sb.len;\n+\tif (!no_edit) {\n+\t\tfprintf(stderr, \"launching editor, log %s\\n\", logfile);\n+\t\tlaunch_editor(git_path(commit_editmsg), &sb);\n+\t}\n+\telse if (strbuf_read_file(&sb, git_path(commit_editmsg)) < 0)\n+\t\tdie(\"could not read commit message\\n\");\n+\tif (run_hook(index_file, \"commit-msg\", commit_editmsg))\n+\t\texit(1);\n+\tstripspace(&sb, 1);\n+\tif (sb.len < header_len ||\n+\t    message_is_empty(&sb, header_len))\n+\t\tdie(\"* no commit message?  aborting commit.\");\n+\tstrbuf_addch(&sb, '\\0');\n+\tif (is_encoding_utf8(git_commit_encoding) && !is_utf8(sb.buf))\n+\t\tfprintf(stderr, commit_utf8_warn);\n+\n+\tif (write_sha1_file(sb.buf, sb.len - 1, commit_type, commit_sha1))\n+\t\tdie(\"failed to write commit object\");\n+\t\t       \n+\tref_lock = lock_any_ref_for_update(\"HEAD\",\n+\t\t\t\t\t   initial_commit ? NULL : head_sha1,\n+\t\t\t\t\t   0);\n+\n+\tnl = strchr(sb.buf + header_len, '\\n');\n+\theader_line = xstrndup(sb.buf + header_len,\n+\t\t\t       nl - (sb.buf + header_len));\n+\tstrbuf_release(&sb);\n+\tstrbuf_addf(&sb, \"%s: %s\\n\", reflog_msg, header_line);\n+\tstrbuf_addch(&sb, '\\0');\n+\tfree(header_line);\n+\n+\tif (!ref_lock)\n+\t\tdie(\"cannot lock HEAD ref\");\n+\tif (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0)\n+\t\tdie(\"cannot update HEAD ref\");\n+\n+\tunlink(git_path(\"MERGE_HEAD\"));\n+\tunlink(git_path(\"MERGE_MSG\"));\n+\n+\tif (lock_file.filename[0] && commit_locked_index(&lock_file))\n+\t\tdie(\"failed to write new index\");\n+\n+\trerere();\n+\n+\trun_hook(index_file, \"post-commit\", NULL);\n+\n+\tif (!quiet)\n+\t\tprint_summary(prefix, commit_sha1);\n+\n+\treturn 0;\n+}\ndiff --git a/builtin-tag.c b/builtin-tag.c\nindex c97673f..5c4dc4f 100644\n--- a/builtin-tag.c\n+++ b/builtin-tag.c\n@@ -18,7 +18,7 @@ static const char builtin_tag_usage[] =\n \n static char signingkey[1000];\n \n-static void launch_editor(const char *path, struct strbuf *buffer)\n+void launch_editor(const char *path, struct strbuf *buffer)\n {\n \tconst char *editor, *terminal;\n \tstruct child_process child;\ndiff --git a/builtin.h b/builtin.h\nindex d6f2c76..0aea4db 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -23,6 +23,7 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix);\n extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);\n extern int cmd_cherry(int argc, const char **argv, const char *prefix);\n extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);\n+extern int cmd_commit(int argc, const char **argv, const char *prefix);\n extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_count_objects(int argc, const char **argv, const char *prefix);\n extern int cmd_describe(int argc, const char **argv, const char *prefix);\n@@ -64,10 +65,10 @@ 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_revert(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_shortlog(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_show_branch(int argc, const char **argv, const char *prefix);\n+extern int cmd_status(int argc, const char **argv, const char *prefix);\n extern int cmd_stripspace(int argc, const char **argv, const char *prefix);\n extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);\n extern int cmd_tag(int argc, const char **argv, const char *prefix);\ndiff --git a/git-commit.sh b/contrib/examples/git-commit.sh\nsimilarity index 100%\nrename from git-commit.sh\nrename to contrib/examples/git-commit.sh\ndiff --git a/git.c b/git.c\nindex 56ae8cc..f915b47 100644\n--- a/git.c\n+++ b/git.c\n@@ -326,6 +326,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"check-attr\", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"cherry\", cmd_cherry, RUN_SETUP },\n \t\t{ \"cherry-pick\", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },\n+\t\t{ \"commit\", cmd_commit, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"commit-tree\", cmd_commit_tree, RUN_SETUP },\n \t\t{ \"config\", cmd_config },\n \t\t{ \"count-objects\", cmd_count_objects, RUN_SETUP },\n@@ -369,10 +370,10 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"rev-parse\", cmd_rev_parse, RUN_SETUP },\n \t\t{ \"revert\", cmd_revert, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"rm\", cmd_rm, RUN_SETUP | NEED_WORK_TREE },\n-\t\t{ \"runstatus\", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"shortlog\", cmd_shortlog, RUN_SETUP | USE_PAGER },\n \t\t{ \"show-branch\", cmd_show_branch, RUN_SETUP },\n \t\t{ \"show\", cmd_show, RUN_SETUP | USE_PAGER },\n+\t\t{ \"status\", cmd_status, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"stripspace\", cmd_stripspace },\n \t\t{ \"symbolic-ref\", cmd_symbolic_ref, RUN_SETUP },\n \t\t{ \"tag\", cmd_tag, RUN_SETUP },\ndiff --git a/strbuf.h b/strbuf.h\nindex 3a523ab..eba7369 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -100,5 +100,6 @@ extern int strbuf_read_file(struct strbuf *sb, const char *path);\n extern void read_line(struct strbuf *, FILE *, int);\n \n extern void stripspace(struct strbuf *buf, int skip_comments);\n+extern void launch_editor(const char *path, struct strbuf *buffer);\n \n #endif /* STRBUF_H */\ndiff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh\nindex 552af1c..2dbe04f 100755\n--- a/t/t3501-revert-cherry-pick.sh\n+++ b/t/t3501-revert-cherry-pick.sh\n@@ -44,7 +44,7 @@ test_expect_success setup '\n test_expect_success 'cherry-pick after renaming branch' '\n \n \tgit checkout rename2 &&\n-\tEDITOR=: VISUAL=: git cherry-pick added &&\n+\tgit cherry-pick added &&\n \ttest -f opos &&\n \tgrep \"Add extra line at the end\" opos\n \n@@ -53,7 +53,7 @@ test_expect_success 'cherry-pick after renaming branch' '\n test_expect_success 'revert after renaming branch' '\n \n \tgit checkout rename1 &&\n-\tEDITOR=: VISUAL=: git revert added &&\n+\tgit revert added &&\n \ttest -f spoo &&\n \t! grep \"Add extra line at the end\" spoo\n \ndiff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh\nindex 28e9e37..235f372 100755\n--- a/t/t3901-i18n-patch.sh\n+++ b/t/t3901-i18n-patch.sh\n@@ -154,7 +154,7 @@ test_expect_success 'cherry-pick(U/U)' '\n \tgit reset --hard master &&\n \tgit cherry-pick side^ &&\n \tgit cherry-pick side &&\n-\tEDITOR=: VISUAL=: git revert HEAD &&\n+\tgit revert HEAD &&\n \n \tcheck_encoding 3\n '\n@@ -169,7 +169,7 @@ test_expect_success 'cherry-pick(L/L)' '\n \tgit reset --hard master &&\n \tgit cherry-pick side^ &&\n \tgit cherry-pick side &&\n-\tEDITOR=: VISUAL=: git revert HEAD &&\n+\tgit revert HEAD &&\n \n \tcheck_encoding 3 8859\n '\n@@ -184,7 +184,7 @@ test_expect_success 'cherry-pick(U/L)' '\n \tgit reset --hard master &&\n \tgit cherry-pick side^ &&\n \tgit cherry-pick side &&\n-\tEDITOR=: VISUAL=: git revert HEAD &&\n+\tgit revert HEAD &&\n \n \tcheck_encoding 3\n '\n@@ -200,7 +200,7 @@ test_expect_success 'cherry-pick(L/U)' '\n \tgit reset --hard master &&\n \tgit cherry-pick side^ &&\n \tgit cherry-pick side &&\n-\tEDITOR=: VISUAL=: git revert HEAD &&\n+\tgit revert HEAD &&\n \n \tcheck_encoding 3 8859\n '\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex cc1253c..a232bd6 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -9,8 +9,8 @@ LC_ALL=C\n PAGER=cat\n TZ=UTC\n export LANG LC_ALL PAGER TZ\n-EDITOR=:\n-VISUAL=:\n+EDITOR=/bin/true\n+VISUAL=/bin/true\n unset GIT_EDITOR\n unset AUTHOR_DATE\n unset AUTHOR_EMAIL\n-- \n1.5.3.1.993.gbf388-dirty\n"},{"id":"53472","messageId":"Pine.LNX.4.64.0709181411200.28586@racer.site","threadId":"9904","inReplyTo":"11900740153845-git-send-email-krh@redhat.com","subject":"Re: [PATCH 4/7] Clean up stripspace a bit, use strbuf even more.","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-18T13:12:18Z","receivedAt":"2007-09-18T13:12:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nI really like your patch, except for this:\n\nOn Mon, 17 Sep 2007, Kristian Høgsberg wrote:\n\n> diff --git a/builtin.h b/builtin.h\n> index 03ee7bf..d6f2c76 100644\n> --- a/builtin.h\n> +++ b/builtin.h\n> @@ -7,7 +7,6 @@ extern const char git_version_string[];\n>  extern const char git_usage_string[];\n>  \n>  extern void help_unknown_cmd(const char *cmd);\n> -extern size_t stripspace(char *buffer, size_t length, int skip_comments);\n>  extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);\n>  extern void prune_packed_objects(int);\n>  \n> diff --git a/strbuf.h b/strbuf.h\n> index 21fc111..5960637 100644\n> --- a/strbuf.h\n> +++ b/strbuf.h\n> @@ -98,4 +98,6 @@ extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);\n>  \n>  extern void read_line(struct strbuf *, FILE *, int);\n>  \n> +extern void stripspace(struct strbuf *buf, int skip_comments);\n> +\n>  #endif /* STRBUF_H */\n\nIf you do that, you have to move the function \"stripspace\" to strbuf.c, \ntoo...\n\nCiao,\nDscho\n"},{"id":"53473","messageId":"Pine.LNX.4.64.0709181414150.28586@racer.site","threadId":"9904","inReplyTo":"1190074016669-git-send-email-krh@redhat.com","subject":"Re: [PATCH 6/7] Export rerere() and launch_editor().","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-18T13:14:38Z","receivedAt":"2007-09-18T13:14:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nI see rerere() in the patch, but not launch_editor().\n\nCiao,\nDscho\n"},{"id":"53479","messageId":"1190123537.23692.2.camel@hinata.boston.redhat.com","threadId":"9904","inReplyTo":"Pine.LNX.4.64.0709181411200.28586@racer.site","subject":"Re: [PATCH 4/7] Clean up stripspace a bit, use strbuf even more.","fromName":"Kristian Høgsberg","fromEmail":"krh@redhat.com","sentAt":"2007-09-18T13:52:17Z","receivedAt":"2007-09-18T13:52:17Z","isPatch":true,"sender":{"key":"krh@redhat.com","avatar":"https://gravatar.com/avatar/763dee6f9594ac474f725b137a39565792928e583ddf59b32befc2907409027e?d=mp&s=160"},"body":"\nOn Tue, 2007-09-18 at 14:12 +0100, Johannes Schindelin wrote:\n> Hi,\n> \n> I really like your patch, except for this:\n> \n> On Mon, 17 Sep 2007, Kristian Høgsberg wrote:\n> \n> > diff --git a/builtin.h b/builtin.h\n> > index 03ee7bf..d6f2c76 100644\n> > --- a/builtin.h\n> > +++ b/builtin.h\n> > @@ -7,7 +7,6 @@ extern const char git_version_string[];\n> >  extern const char git_usage_string[];\n> >  \n> >  extern void help_unknown_cmd(const char *cmd);\n> > -extern size_t stripspace(char *buffer, size_t length, int skip_comments);\n> >  extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);\n> >  extern void prune_packed_objects(int);\n> >  \n> > diff --git a/strbuf.h b/strbuf.h\n> > index 21fc111..5960637 100644\n> > --- a/strbuf.h\n> > +++ b/strbuf.h\n> > @@ -98,4 +98,6 @@ extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);\n> >  \n> >  extern void read_line(struct strbuf *, FILE *, int);\n> >  \n> > +extern void stripspace(struct strbuf *buf, int skip_comments);\n> > +\n> >  #endif /* STRBUF_H */\n> \n> If you do that, you have to move the function \"stripspace\" to strbuf.c, \n> too...\n\nRight, the alternative is to #include strbuf.h in builtin.h, or maybe\njust add struct strbuf; at the top.  And with Pierres latest patch\nstrbuf is included everywhere, so maybe this is already moot.\n\nKristian\n"},{"id":"53480","messageId":"Pine.LNX.4.64.0709181453220.28586@racer.site","threadId":"9904","inReplyTo":"11900740163661-git-send-email-krh@redhat.com","subject":"Re: [PATCH 7/7] Implement git commit as a builtin command.","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-18T13:58:32Z","receivedAt":"2007-09-18T13:58:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nvery nice!\n\nFour nits, though, and a half:\n\n- it would be nicer to put the option parsing it option.[ch] (you would \n  also need to pass the usage line then, instead of hardwiring it to \n  \"git_commit_usage\"),\n\n- it seems more logical to me to call it \"parse_option()\" than \n  \"scan_options()\", since that is what it does,\n\n- you might want to rename OPTION_NONE to OPTION_BOOLEAN, and maybe even \n  allow \"--no-<option>\" in that case for free,\n\n- wt_status_prepare() could take a parameter \"index_file\", which would \n  default to git_path(\"index\") when passed as NULL, and\n\n- launch_editor() is defined in builtin-tag.c, which is not part of the \n  library, and therefore it would be technically more correct to either \n  move the function to editor.c (my preferred solution), or declare it in \n  builtin.h instead of strbuf.h.\n\nAs you can see, my nits are really minor, which means that I am pretty \nhappy with your work!\n\nThanks,\nDscho\n"},{"id":"53488","messageId":"1190128052.23692.6.camel@hinata.boston.redhat.com","threadId":"9904","inReplyTo":"Pine.LNX.4.64.0709181453220.28586@racer.site","subject":"Re: [PATCH 7/7] Implement git commit as a builtin command.","fromName":"Kristian Høgsberg","fromEmail":"krh@redhat.com","sentAt":"2007-09-18T15:07:32Z","receivedAt":"2007-09-18T15:07:32Z","isPatch":true,"sender":{"key":"krh@redhat.com","avatar":"https://gravatar.com/avatar/763dee6f9594ac474f725b137a39565792928e583ddf59b32befc2907409027e?d=mp&s=160"},"body":"On Tue, 2007-09-18 at 14:58 +0100, Johannes Schindelin wrote:\n> Hi,\n> \n> very nice!\n> \n> Four nits, though, and a half:\n> \n> - it would be nicer to put the option parsing it option.[ch] (you would \n>   also need to pass the usage line then, instead of hardwiring it to \n>   \"git_commit_usage\"),\n\nYes, good point.\n\n> - it seems more logical to me to call it \"parse_option()\" than \n>   \"scan_options()\", since that is what it does,\n\nYup.\n\n> - you might want to rename OPTION_NONE to OPTION_BOOLEAN, and maybe even \n>   allow \"--no-<option>\" in that case for free,\n\nAgree.\n\n> - wt_status_prepare() could take a parameter \"index_file\", which would \n>   default to git_path(\"index\") when passed as NULL, and\n\nYeah, the way I did it, I preserved the API, but that's not really a\nconcern, I guess.\n\n> - launch_editor() is defined in builtin-tag.c, which is not part of the \n>   library, and therefore it would be technically more correct to either \n>   move the function to editor.c (my preferred solution), or declare it in \n>   builtin.h instead of strbuf.h.\n\nYeah, and we should move the stripspace code there too.  Or maybe we\nshould rename that strbuf_stripspace and put it in strbuf.c.\n\n> As you can see, my nits are really minor, which means that I am pretty \n> happy with your work!\n\nGreat, I hope we can get it in soon :)\n\nKristian\n"},{"id":"53613","messageId":"7vr6kunrh7.fsf@gitster.siamese.dyndns.org","threadId":"9904","inReplyTo":"1190074016669-git-send-email-krh@redhat.com","subject":"Re: [PATCH 6/7] Export rerere() and launch_editor().","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-19T23:52:04Z","receivedAt":"2007-09-19T23:52:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Kristian Høgsberg <krh@redhat.com> writes:\n\n> +/* Export for builtin-commit. */\n> +int rerere(void)\n> +{\n> +\tstruct path_list merge_rr = { NULL, 0, 0, 1 };\n> +\tint fd;\n> +\n> +\tgit_config(git_rerere_config);\n> +\tif (!is_rerere_enabled())\n> +\t\treturn 0;\n> +\n> +\tmerge_rr_path = xstrdup(git_path(\"rr-cache/MERGE_RR\"));\n> +\tfd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);\n> +\tread_rr(&merge_rr);\n> +\treturn do_plain_rerere(&merge_rr, fd);\n> +}\n\nIs it just me who sees a suboptimal cut and paste here?\n\nBTW, [1-5/7] look good so far.\n\ndiff --git a/builtin-rerere.c b/builtin-rerere.c\nindex 29d057c..2f51ae0 100644\n--- a/builtin-rerere.c\n+++ b/builtin-rerere.c\n@@ -415,18 +415,39 @@ static int is_rerere_enabled(void)\n \treturn 1;\n }\n \n-int cmd_rerere(int argc, const char **argv, const char *prefix)\n+static int setup_rerere(struct path_list *merge_rr)\n {\n-\tstruct path_list merge_rr = { NULL, 0, 0, 1 };\n-\tint i, fd = -1;\n+\tint fd;\n \n \tgit_config(git_rerere_config);\n \tif (!is_rerere_enabled())\n-\t\treturn 0;\n+\t\treturn -1;\n \n \tmerge_rr_path = xstrdup(git_path(\"rr-cache/MERGE_RR\"));\n \tfd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);\n-\tread_rr(&merge_rr);\n+\tread_rr(merge_rr);\n+\treturn fd;\n+}\n+\n+int rerere(void)\n+{\n+\tstruct path_list merge_rr = { NULL, 0, 0, 1 };\n+\tint fd;\n+\n+\tfd = setup_rerere(&merge_rr);\n+\tif (fd < 0)\n+\t\treturn 0;\n+\treturn do_plain_rerere(&merge_rr, fd);\n+}\n+\n+int cmd_rerere(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct path_list merge_rr = { NULL, 0, 0, 1 };\n+\tint i, fd;\n+\n+\tfd = setup_rerere(&merge_rr);\n+\tif (fd < 0)\n+\t\treturn 0;\n \n \tif (argc < 2)\n \t\treturn do_plain_rerere(&merge_rr, fd);\n"},{"id":"53616","messageId":"7vk5qmm8hq.fsf@gitster.siamese.dyndns.org","threadId":"9904","inReplyTo":"11900740163661-git-send-email-krh@redhat.com","subject":"Re: [PATCH 7/7] Implement git commit as a builtin command.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-20T01:27:29Z","receivedAt":"2007-09-20T01:27:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Kristian Høgsberg <krh@redhat.com> writes:\n\n> diff --git a/builtin-commit.c b/builtin-commit.c\n> new file mode 100644\n> index 0000000..ee98de9\n> --- /dev/null\n> +++ b/builtin-commit.c\n> @@ -0,0 +1,740 @@\n> +/*\n> + * Builtin \"git commit\"\n> + *\n> + * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>\n> + * Based on git-commit.sh by Junio C Hamano and Linus Torvalds\n> + */\n> +\n> +#include <sys/types.h>\n> +#include <sys/stat.h>\n> +#include <unistd.h>\n> +\n\nWith 85023577a8f4b540aa64aa37f6f44578c0c305a3 (simplify\ninclusion of system header files.), we introduced a rule against\nthese lines.  We probably would want a Coding Guideline (aka\n\"Hacking Git\") document somewhere.\n\n> +#include \"cache.h\"\n> +#include \"cache-tree.h\"\n> +#include \"builtin.h\"\n> +#include \"diff.h\"\n> +#include \"diffcore.h\"\n> +#include \"commit.h\"\n> +#include \"revision.h\"\n> +#include \"wt-status.h\"\n> +#include \"run-command.h\"\n> +#include \"refs.h\"\n> +#include \"log-tree.h\"\n> +#include \"strbuf.h\"\n> +#include \"utf8.h\"\n> +\n> +static const char builtin_commit_usage[] =\n> +\t\"[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]\";\n> +\n> +static unsigned char head_sha1[20], merge_head_sha1[20];\n> +static char *use_message_buffer;\n> +static const char commit_editmsg[] = \"COMMIT_EDITMSG\";\n> +static struct lock_file lock_file;\n> +\n> +enum option_type {\n> +    OPTION_NONE,\n> +    OPTION_STRING,\n> +    OPTION_INTEGER,\n> +    OPTION_LAST,\n> +};\n> +\n> +struct option {\n> +    enum option_type type;\n> +    const char *long_name;\n> +    char short_name;\n> +    void *value;\n> +};\n> +\n> +static int scan_options(const char ***argv, struct option *options)\n> +{\n> +\tconst char *value, *eq;\n> +\tint i;\n> +\n> +\tif (**argv == NULL)\n> +\t\treturn 0;\n> +\tif ((**argv)[0] != '-')\n> +\t\treturn 0;\n> +\tif (!strcmp(**argv, \"--\"))\n> +\t\treturn 0;\n> +\n> +\tvalue = NULL;\n> +\tfor (i = 0; options[i].type != OPTION_LAST; i++) {\n> +\t\tif ((**argv)[1] == '-') {\n> +\t\t\tif (!prefixcmp(options[i].long_name, **argv + 2)) {\n> +\t\t\t\tif (options[i].type != OPTION_NONE)\n> +\t\t\t\t\tvalue = *++(*argv);\n> +\t\t\t\tgoto match;\n> +\t\t\t}\n> +\n> +\t\t\teq = strchr(**argv + 2, '=');\n> +\t\t\tif (eq && options[i].type != OPTION_NONE &&\n> +\t\t\t    !strncmp(**argv + 2, \n> +\t\t\t\t     options[i].long_name, eq - **argv - 2)) {\n> +\t\t\t\tvalue = eq + 1;\n> +\t\t\t\tgoto match;\n> +\t\t\t}\n> +\t\t}\n> +\n> +\t\tif ((**argv)[1] == options[i].short_name) {\n> +\t\t\tif ((**argv)[2] == '\\0') {\n> +\t\t\t\tif (options[i].type != OPTION_NONE)\n> +\t\t\t\t\tvalue = *++(*argv);\n> +\t\t\t\tgoto match;\n> +\t\t\t}\n> +\n> +\t\t\tif (options[i].type != OPTION_NONE) {\n> +\t\t\t\tvalue = **argv + 2;\n> +\t\t\t\tgoto match;\n> +\t\t\t}\n> +\t\t}\n> +\t}\n\nHow do you disambiguate between \"--author <me>\" and \"--amend\"?\n\"The order in *options list matters\" is an acceptable answer but\nit needs to be documented.\n\n> +\n> +\tusage(builtin_commit_usage);\n> +\n> + match:\n> +\tswitch (options[i].type) {\n> +\tcase OPTION_NONE:\n> +\t\t*(int *)options[i].value = 1;\n> +\t\tbreak;\n> +\tcase OPTION_STRING:\n> +\t\tif (value == NULL)\n> +\t\t\tdie(\"option %s requires a value.\", (*argv)[-1]);\n> +\t\t*(const char **)options[i].value = value;\n> +\t\tbreak;\n> +\tcase OPTION_INTEGER:\n> +\t\tif (value == NULL)\n> +\t\t\tdie(\"option %s requires a value.\", (*argv)[-1]);\n> +\t\t*(int *)options[i].value = atoi(value);\n> +\t\tbreak;\n> +\tdefault:\n> +\t\tassert(0);\n> +\t}\n> +\n> +\t(*argv)++;\n> +\n> +\treturn 1;\n> +}\n\nI do not particularly like this OPTION_LAST convention, but that\nis a minor detail.  I also suspect in the longer term we might\nbe better off using getopt() or popt(), but that would be a\nlarger project.\n\nIn any case, if you want to use this option parser, you would\nneed to add a new file, perhaps parse_options.c, and move this\npart there, so that other parts of the system can reuse it.\n\nAnd the same comment goes for the launch_editor still in\nbuiltin-tag.c.  We should move it to editor.c or something.\n\n> +\n> +static char *logfile, *force_author, *message, *template_file;\n> +static char *edit_message, *use_message;\n> +static int all, edit_flag, also, interactive, only, no_verify, amend, signoff;\n> +static int quiet, verbose, untracked_files;\n> +\n> +static int no_edit, initial_commit, in_merge;\n> +const char *only_include_assumed;\n> +\n> +static struct option commit_options[] = {\n> +\t{ OPTION_STRING, \"file\", 'F', (void *) &logfile },\n> +\t{ OPTION_NONE, \"all\", 'a', &all },\n> +\t{ OPTION_STRING, \"author\", 0, (void *) &force_author },\n> +\t{ OPTION_NONE, \"edit\", 'e', &edit_flag },\n\nWhy do some get casted to (void*) and others don't?  It doesn't\nseem to have any pattern.  I am puzzled...\n\n> +\t{ OPTION_NONE, \"include\", 'i', &also },\n> +\t{ OPTION_NONE, \"interactive\", 0, &interactive },\n> +\t{ OPTION_NONE, \"only\", 'o', &only },\n> +\t{ OPTION_STRING, \"message\", 'm', &message },\n> +\t{ OPTION_NONE, \"no-verify\", 'n', &no_verify },\n> +\t{ OPTION_NONE, \"amend\", 0, &amend },\n> +\t{ OPTION_STRING, \"reedit-message\", 'c', &edit_message },\n> +\t{ OPTION_STRING, \"reuse-message\", 'C', &use_message },\n> +\t{ OPTION_NONE, \"signoff\", 's', &signoff },\n> +\t{ OPTION_NONE, \"quiet\", 'q', &quiet },\n> +\t{ OPTION_NONE, \"verbose\", 'v', &verbose },\n> +\t{ OPTION_NONE, \"untracked-files\", 0, &untracked_files },\n> +\t{ OPTION_STRING, \"template\", 't', &template_file },\n> +\t{ OPTION_LAST },\n> +};\n> +\n> +/* FIXME: Taken from builtin-add, should be shared. */\n\nYou're darn right.  I thought you have some other patch that\ntouches builtin-add already...\n\n> +\n> +static void update_callback(struct diff_queue_struct *q,\n> +\t\t\t    struct diff_options *opt, void *cbdata)\n> +{\n> +\tint i, verbose;\n> +\n> +\tverbose = *((int *)cbdata);\n> +\tfor (i = 0; i < q->nr; i++) {\n> +\t\tstruct diff_filepair *p = q->queue[i];\n> +\t\tconst char *path = p->one->path;\n> +\t\tswitch (p->status) {\n> +\t\tdefault:\n> +\t\t\tdie(\"unexpacted diff status %c\", p->status);\n> +\t\tcase DIFF_STATUS_UNMERGED:\n> +\t\tcase DIFF_STATUS_MODIFIED:\n> +\t\tcase DIFF_STATUS_TYPE_CHANGED:\n> +\t\t\tadd_file_to_cache(path, verbose);\n> +\t\t\tbreak;\n> +\t\tcase DIFF_STATUS_DELETED:\n> +\t\t\tremove_file_from_cache(path);\n> +\t\t\tcache_tree_invalidate_path(active_cache_tree, path);\n\nI've updated remove_file_from_cache() to invalidate the path in\ncache-tree so this does not hurt but is no longer necessary.  I\nremoved this line in the version I queued in 'pu'.\n\n> +\t\t\tif (verbose)\n> +\t\t\t\tprintf(\"remove '%s'\\n\", path);\n> +\t\t\tbreak;\n> +\t\t}\n> +\t}\n> +}\n> +\n> +static void\n> +add_files_to_cache(int fd, const char **files, const char *prefix)\n> +{\n> +\tstruct rev_info rev;\n> +\n> +\tinit_revisions(&rev, \"\");\n> +\tsetup_revisions(0, NULL, &rev, NULL);\n> +\trev.prune_data = get_pathspec(prefix, files);\n> +\trev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;\n> +\trev.diffopt.format_callback = update_callback;\n> +\trev.diffopt.format_callback_data = &verbose;\n> +\n> +\trun_diff_files(&rev, 0);\n> +\trefresh_cache(REFRESH_QUIET);\n\nYour update_callback() does add_file_to_cache() which picks up\nthe stat information from the filesystem already, so I do not\nsee much point calling refresh_cache() afterwards.  On the other\nhand, refreshing before running diff_files() might make sense,\nas you can avoid a lot of unnecessary work when you are told to\ndo \"git commit .\"\n\nHave you tested this with an unmerged index?\n\n> +\n> +\tif (write_cache(fd, active_cache, active_nr) || close(fd))\n> +\t\tdie(\"unable to write new index file\");\n> +}\n> +\n> +static char *\n> +prepare_index(const char **files, const char *prefix)\n> +{\n> +\tint fd;\n> +\tstruct tree *tree;\n> +\tstruct lock_file *next_index_lock;\n> +\n> +\tfd = hold_locked_index(&lock_file, 1);\n> +\tif (read_cache() < 0)\n> +\t\tdie(\"index file corrupt\");\n> +\n> +\tif (all) {\n> +\t\tadd_files_to_cache(fd, files, NULL);\n> +\t\treturn lock_file.filename;\n\nShould you be passing files, which is used as pathspec to decide\nif your update_callback() should be called, under --all option?\n\n> +\t} else if (also) {\n> +\t\tadd_files_to_cache(fd, files, prefix);\n> +\t\treturn lock_file.filename;\n> +\t}\n> +\n> +\tif (interactive)\n> +\t\tinteractive_add();\n\nDon't you need to\n\n (1) abort if the user aborts out of interactive add with ^C?\n (2) re-read the index after interactive_add() returns?\n\n> +\tif (*files == NULL) {\n> +\t\t/* Commit index as-is. */\n> +\t\trollback_lock_file(&lock_file);\n> +\t\treturn get_index_file();\n> +\t}\n> +\n> +\t/*\n> +\t * FIXME: Warn on unknown files.  Shell script does\n> +\t *\n> +\t *   commit_only=`git-ls-files --error-unmatch -- \"$@\"`\n> +\t */\n\nThis should be much easier to do than from the shell script, as\nyou have active_cache[] (aka \"the_index.cache[]\") in-core.\n\n> +\t/*\n> +\t * FIXME: shell script does\n> +\t *\n> +\t *   git-read-tree --index-output=\"$TMP_INDEX\" -i -m HEAD\n> +\t *\n> +\t * which warns about unmerged files in the index.\n> +\t */\n\nI think \"unmerged warning\" is an unintended side effect.  The\npoint of the command is to have a copy of index, as there is no\nway for shell script to work with more than one index at a time\nwithout using a temporary file.\n\n> +\n> +\t/* update the user index file */\n> +\tadd_files_to_cache(fd, files, prefix);\n> +\n> +\tif (!initial_commit) {\n> +\t\ttree = parse_tree_indirect(head_sha1);\n> +\t\tif (!tree)\n> +\t\t\tdie(\"failed to unpack HEAD tree object\");\n> +\t\tif (read_tree(tree, 0, NULL))\n> +\t\t\tdie(\"failed to read HEAD tree object\");\n> +\t}\n\nHuh?  Doesn't this read_tree() defeat the add_files_to_cache()\nyou did earlier?\n\n> +\n> +\t/* Uh oh, abusing lock_file to create a garbage collected file */\n> +\tnext_index_lock = xmalloc(sizeof(*next_index_lock));\n> +\tfd = hold_lock_file_for_update(next_index_lock,\n> +\t\t\t\t       git_path(\"next-index-%d\", getpid()), 1);\n\nThat's not an abuse, but I wonder what happened to the fd you\ngot at the beginning of the function.\n\n> +\tadd_files_to_cache(fd, files, prefix);\n\nand then this is puzzling to me.\n\nI am starting to suspect that you might be better off if you do\nnot follow the use of temporary index file that was in the shell\nscript version to the letter.  You can use more than one index\nin the core at the same time, now you are built-in.\n\n> +\n> +\treturn next_index_lock->filename;\n> +}\n\n... and if we were to go that route, wt_status structure would\nhave a pointer to the \"struct index_state\" instead of the\nfilename of the index... oops, wt_status_print() will discard\nand re-read the cache from file, so that approach would not work\nright now without fixing wt-status first.  Hmm.\n\n> +static int run_status(FILE *fp, const char *index_file)\n> +{\n> +\tstruct wt_status s;\n> +\n> +\twt_status_prepare(&s);\n> +\n> +\tif (amend) {\n> +\t\ts.amend = 1;\n> +\t\ts.reference = \"HEAD^1\";\n> +\t}\n> +\ts.verbose = verbose;\n> +\ts.untracked = untracked_files;\n> +\ts.index_file = index_file;\n> +\ts.fp = fp;\n> +\n> +\twt_status_print(&s);\n> +\n> +\treturn s.commitable;\n> +}\n\nI did not look at the rest of the patch; it should be either\nobviously correct or outright incorrect --- anybody would notice\nthe breakage immediately.\n\nThe prepare_index() part is the most (and only) \"interesting\"\npart of the puzzle.  I need to look at this again and think\nabout it a bit more.  It needs to:\n\n * save the current index and restore in case the whole\n   \"git-commit\" is aborted in any way (e.g. ^C, empty log\n   message from the editor); This is easy to do from C with\n   hold_locked_index().\n\n * when doing a partial commit,\n   - read HEAD in a temporary index, if not initial commit;\n   - update the temporary index with the given paths;\n   - write out the temporary index as a tree to build a commit;\n\n   - update the real index the same way with the given paths;\n   - if all goes well, commit the updated real index; again,\n     this is easy in C with commit_locked_index();\n\n * when not doing a partial commit,\n   - update the real index with given paths (if \"--also\") or all\n     what diff-files reports (if \"--all\");\n   - write out the real index as a tree to build a commit;\n   - if all goes well, commit the updated real index; again,\n     this is easy in C with commit_locked_index();\n\nA thing to keep in mind is that you can write out the temporary\nindex as a tree from the core without writing it out first to a\ntemporary index file at all.  Perhaps the code should be\nstructured like this.\n\n    - parse options and all the other necessary setup;\n\n    - hold_locked_index() to lock the \"real\" index;\n\n    - prepare_index() is responsibile for doing two things\n\n      1. build, write out and return the tree object to be\n         committed;\n\n      2. leave the_index[] in the state to become the \"real\"\n         index if this commit command is not aborted;\n\n    - take the tree object, put commit log message around it and\n      make a commit object;\n\n    - commit_locked_index() to write out the \"real\" index.\n\nNow, the task #1 for prepare_index() is simpler for \"also\" and\n\"all\" case.  You do the equivalent of \"git-add -u\" or \"git add\n<path>\" and run cache_tree_update() to write out the tree, and\nwhen you are done, both the_index.cache and the_index.cache_tree\nare up-to-date, ready to be written out by the caller at the\nvery end.\n\nFor a partial commit, the task is a bit more convoluted, as\nwriting out the tree needs to be done from outside the_index.  I\nwould say something like this:\n\n\tint prepare_index(unsigned char *sha1) {\n\t\tif (partial commit) {\n                       struct index_state tmp_index;\n\n                       /* \"temporary index\" only in-core */\n                       memset(&tmp_index, 0, sizeof(tmp_index));\n                       read_index(&tmp_index);\n                       add_files_to_index(files, prefix, &tmp_index);\n                       write_index_as_tree(&tmp_index, sha1);\n                       discard_index(&tmp_index);\n\n                       /* update the index the same way */\n                       add_files_to_index(files, prefix, &the_index);\n                       return ok;\n\t\t}\n                /* otherwise */\n                if (files && *files)\n                \tadd_files_to_index(files, prefix, &the_index);\n\t\telse if (all)\n                \tadd_files_to_index(NULL, prefix, &the_index);\n\t\twrite_index_as_tree(&the_index, sha1);\n                return ok;\n\t}\n\nwhere\n\n * add_files_to_index() is similar to your add_files_to_cache()\n   but takes struct index_state; the callback can still diff\n   with the_index.cache (aka active_cache) as that diff is what\n   we are interested in updating anyway;\n\n * write_index_as_tree() would be a new function that takes\n   struct index_state and writes that as a tree.  It would\n   essentially be the builtin-write-tree.c::write_tree()\n   function except the opening and reading the index (or\n   updating the cache-tree) part, something like:\n\nwrite_index_as_tree(struct index_state *istate, unsigned char *sha1)\n{\n       \tif (!cache_tree_fully_valid(istate->cache_tree))\n        \tcache_tree_update(istate->cache_tree,\n                \t\t  istate->cache,\n                                  istate->cache_nr, 0, 0);\n\thashcpy(sha1, istate->cache_tree->sha1);\n}\n"},{"id":"53734","messageId":"1190395088.31494.55.camel@hinata.boston.redhat.com","threadId":"9904","inReplyTo":"7vk5qmm8hq.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 7/7] Implement git commit as a builtin command.","fromName":"Kristian Høgsberg","fromEmail":"krh@redhat.com","sentAt":"2007-09-21T17:18:08Z","receivedAt":"2007-09-21T17:18:08Z","isPatch":true,"sender":{"key":"krh@redhat.com","avatar":"https://gravatar.com/avatar/763dee6f9594ac474f725b137a39565792928e583ddf59b32befc2907409027e?d=mp&s=160"},"body":"On Wed, 2007-09-19 at 18:27 -0700, Junio C Hamano wrote:\n> Kristian Høgsberg <krh@redhat.com> writes:\n> \n> > diff --git a/builtin-commit.c b/builtin-commit.c\n> > new file mode 100644\n> > index 0000000..ee98de9\n> > --- /dev/null\n> > +++ b/builtin-commit.c\n> > @@ -0,0 +1,740 @@\n> > +/*\n> > + * Builtin \"git commit\"\n> > + *\n> > + * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>\n> > + * Based on git-commit.sh by Junio C Hamano and Linus Torvalds\n> > + */\n> > +\n> > +#include <sys/types.h>\n> > +#include <sys/stat.h>\n> > +#include <unistd.h>\n> > +\n> \n> With 85023577a8f4b540aa64aa37f6f44578c0c305a3 (simplify\n> inclusion of system header files.), we introduced a rule against\n> these lines.  We probably would want a Coding Guideline (aka\n> \"Hacking Git\") document somewhere.\n\nOk.  I see you removed them in the pu commit, thanks.\n\n> > +#include \"cache.h\"\n> > +#include \"cache-tree.h\"\n> > +#include \"builtin.h\"\n> > +#include \"diff.h\"\n> > +#include \"diffcore.h\"\n> > +#include \"commit.h\"\n> > +#include \"revision.h\"\n> > +#include \"wt-status.h\"\n> > +#include \"run-command.h\"\n> > +#include \"refs.h\"\n> > +#include \"log-tree.h\"\n> > +#include \"strbuf.h\"\n> > +#include \"utf8.h\"\n> > +\n> > +static const char builtin_commit_usage[] =\n> > +\t\"[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]\";\n> > +\n> > +static unsigned char head_sha1[20], merge_head_sha1[20];\n> > +static char *use_message_buffer;\n> > +static const char commit_editmsg[] = \"COMMIT_EDITMSG\";\n> > +static struct lock_file lock_file;\n> > +\n> > +enum option_type {\n> > +    OPTION_NONE,\n> > +    OPTION_STRING,\n> > +    OPTION_INTEGER,\n> > +    OPTION_LAST,\n> > +};\n> > +\n> > +struct option {\n> > +    enum option_type type;\n> > +    const char *long_name;\n> > +    char short_name;\n> > +    void *value;\n> > +};\n> > +\n> > +static int scan_options(const char ***argv, struct option *options)\n> > +{\n> > +\tconst char *value, *eq;\n> > +\tint i;\n> > +\n> > +\tif (**argv == NULL)\n> > +\t\treturn 0;\n> > +\tif ((**argv)[0] != '-')\n> > +\t\treturn 0;\n> > +\tif (!strcmp(**argv, \"--\"))\n> > +\t\treturn 0;\n> > +\n> > +\tvalue = NULL;\n> > +\tfor (i = 0; options[i].type != OPTION_LAST; i++) {\n> > +\t\tif ((**argv)[1] == '-') {\n> > +\t\t\tif (!prefixcmp(options[i].long_name, **argv + 2)) {\n> > +\t\t\t\tif (options[i].type != OPTION_NONE)\n> > +\t\t\t\t\tvalue = *++(*argv);\n> > +\t\t\t\tgoto match;\n> > +\t\t\t}\n> > +\n> > +\t\t\teq = strchr(**argv + 2, '=');\n> > +\t\t\tif (eq && options[i].type != OPTION_NONE &&\n> > +\t\t\t    !strncmp(**argv + 2, \n> > +\t\t\t\t     options[i].long_name, eq - **argv - 2)) {\n> > +\t\t\t\tvalue = eq + 1;\n> > +\t\t\t\tgoto match;\n> > +\t\t\t}\n> > +\t\t}\n> > +\n> > +\t\tif ((**argv)[1] == options[i].short_name) {\n> > +\t\t\tif ((**argv)[2] == '\\0') {\n> > +\t\t\t\tif (options[i].type != OPTION_NONE)\n> > +\t\t\t\t\tvalue = *++(*argv);\n> > +\t\t\t\tgoto match;\n> > +\t\t\t}\n> > +\n> > +\t\t\tif (options[i].type != OPTION_NONE) {\n> > +\t\t\t\tvalue = **argv + 2;\n> > +\t\t\t\tgoto match;\n> > +\t\t\t}\n> > +\t\t}\n> > +\t}\n> \n> How do you disambiguate between \"--author <me>\" and \"--amend\"?\n> \"The order in *options list matters\" is an acceptable answer but\n> it needs to be documented.\n\nYes, the order matters.  I made sure the C version has the same\nprecedence as the shell script.  I'll add a comment in parse-options.h.\n\n> > +\n> > +\tusage(builtin_commit_usage);\n> > +\n> > + match:\n> > +\tswitch (options[i].type) {\n> > +\tcase OPTION_NONE:\n> > +\t\t*(int *)options[i].value = 1;\n> > +\t\tbreak;\n> > +\tcase OPTION_STRING:\n> > +\t\tif (value == NULL)\n> > +\t\t\tdie(\"option %s requires a value.\", (*argv)[-1]);\n> > +\t\t*(const char **)options[i].value = value;\n> > +\t\tbreak;\n> > +\tcase OPTION_INTEGER:\n> > +\t\tif (value == NULL)\n> > +\t\t\tdie(\"option %s requires a value.\", (*argv)[-1]);\n> > +\t\t*(int *)options[i].value = atoi(value);\n> > +\t\tbreak;\n> > +\tdefault:\n> > +\t\tassert(0);\n> > +\t}\n> > +\n> > +\t(*argv)++;\n> > +\n> > +\treturn 1;\n> > +}\n> \n> I do not particularly like this OPTION_LAST convention, but that\n> is a minor detail.  I also suspect in the longer term we might\n> be better off using getopt() or popt(), but that would be a\n> larger project.\n> \n> In any case, if you want to use this option parser, you would\n> need to add a new file, perhaps parse_options.c, and move this\n> part there, so that other parts of the system can reuse it.\n\nI'll split it out in parse-options.[ch].  I still don't understand why\nusing getopt is better; parse-options.c is a 75 line file and it's\nsimple code.  It does more work that getopt, since for most options it\nautomatically writes back the option argument into a global.  All I have\nto do then is validate that no illegal combination of options was\npassed.\n\n> And the same comment goes for the launch_editor still in\n> builtin-tag.c.  We should move it to editor.c or something.\n\nYeah.\n\n> > +\n> > +static char *logfile, *force_author, *message, *template_file;\n> > +static char *edit_message, *use_message;\n> > +static int all, edit_flag, also, interactive, only, no_verify, amend, signoff;\n> > +static int quiet, verbose, untracked_files;\n> > +\n> > +static int no_edit, initial_commit, in_merge;\n> > +const char *only_include_assumed;\n> > +\n> > +static struct option commit_options[] = {\n> > +\t{ OPTION_STRING, \"file\", 'F', (void *) &logfile },\n> > +\t{ OPTION_NONE, \"all\", 'a', &all },\n> > +\t{ OPTION_STRING, \"author\", 0, (void *) &force_author },\n> > +\t{ OPTION_NONE, \"edit\", 'e', &edit_flag },\n> \n> Why do some get casted to (void*) and others don't?  It doesn't\n> seem to have any pattern.  I am puzzled...\n\nYes, oops, not sure what that was about.\n\n> > +\t{ OPTION_NONE, \"include\", 'i', &also },\n> > +\t{ OPTION_NONE, \"interactive\", 0, &interactive },\n> > +\t{ OPTION_NONE, \"only\", 'o', &only },\n> > +\t{ OPTION_STRING, \"message\", 'm', &message },\n> > +\t{ OPTION_NONE, \"no-verify\", 'n', &no_verify },\n> > +\t{ OPTION_NONE, \"amend\", 0, &amend },\n> > +\t{ OPTION_STRING, \"reedit-message\", 'c', &edit_message },\n> > +\t{ OPTION_STRING, \"reuse-message\", 'C', &use_message },\n> > +\t{ OPTION_NONE, \"signoff\", 's', &signoff },\n> > +\t{ OPTION_NONE, \"quiet\", 'q', &quiet },\n> > +\t{ OPTION_NONE, \"verbose\", 'v', &verbose },\n> > +\t{ OPTION_NONE, \"untracked-files\", 0, &untracked_files },\n> > +\t{ OPTION_STRING, \"template\", 't', &template_file },\n> > +\t{ OPTION_LAST },\n> > +};\n> > +\n> > +/* FIXME: Taken from builtin-add, should be shared. */\n> \n> You're darn right.  I thought you have some other patch that\n> touches builtin-add already...\n\nYeah, I'll fix that.\n\n> > +\n> > +static void update_callback(struct diff_queue_struct *q,\n> > +\t\t\t    struct diff_options *opt, void *cbdata)\n> > +{\n> > +\tint i, verbose;\n> > +\n> > +\tverbose = *((int *)cbdata);\n> > +\tfor (i = 0; i < q->nr; i++) {\n> > +\t\tstruct diff_filepair *p = q->queue[i];\n> > +\t\tconst char *path = p->one->path;\n> > +\t\tswitch (p->status) {\n> > +\t\tdefault:\n> > +\t\t\tdie(\"unexpacted diff status %c\", p->status);\n> > +\t\tcase DIFF_STATUS_UNMERGED:\n> > +\t\tcase DIFF_STATUS_MODIFIED:\n> > +\t\tcase DIFF_STATUS_TYPE_CHANGED:\n> > +\t\t\tadd_file_to_cache(path, verbose);\n> > +\t\t\tbreak;\n> > +\t\tcase DIFF_STATUS_DELETED:\n> > +\t\t\tremove_file_from_cache(path);\n> > +\t\t\tcache_tree_invalidate_path(active_cache_tree, path);\n> \n> I've updated remove_file_from_cache() to invalidate the path in\n> cache-tree so this does not hurt but is no longer necessary.  I\n> removed this line in the version I queued in 'pu'.\n> \n> > +\t\t\tif (verbose)\n> > +\t\t\t\tprintf(\"remove '%s'\\n\", path);\n> > +\t\t\tbreak;\n> > +\t\t}\n> > +\t}\n> > +}\n> > +\n> > +static void\n> > +add_files_to_cache(int fd, const char **files, const char *prefix)\n> > +{\n> > +\tstruct rev_info rev;\n> > +\n> > +\tinit_revisions(&rev, \"\");\n> > +\tsetup_revisions(0, NULL, &rev, NULL);\n> > +\trev.prune_data = get_pathspec(prefix, files);\n> > +\trev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;\n> > +\trev.diffopt.format_callback = update_callback;\n> > +\trev.diffopt.format_callback_data = &verbose;\n> > +\n> > +\trun_diff_files(&rev, 0);\n> > +\trefresh_cache(REFRESH_QUIET);\n> \n> Your update_callback() does add_file_to_cache() which picks up\n> the stat information from the filesystem already, so I do not\n> see much point calling refresh_cache() afterwards.  On the other\n> hand, refreshing before running diff_files() might make sense,\n> as you can avoid a lot of unnecessary work when you are told to\n> do \"git commit .\"\n\nI didn't have refresh_cache() in there originally, there was some test\ncase that didn't pass until I added it.  The git-commit.sh shell script\ncalls git update-index --refresh after the prepare_index() part, which\nis where I got it from.  Will investigate.\n\n> Have you tested this with an unmerged index?\n\nNot yet.\n\n> > +\n> > +\tif (write_cache(fd, active_cache, active_nr) || close(fd))\n> > +\t\tdie(\"unable to write new index file\");\n> > +}\n> > +\n> > +static char *\n> > +prepare_index(const char **files, const char *prefix)\n> > +{\n> > +\tint fd;\n> > +\tstruct tree *tree;\n> > +\tstruct lock_file *next_index_lock;\n> > +\n> > +\tfd = hold_locked_index(&lock_file, 1);\n> > +\tif (read_cache() < 0)\n> > +\t\tdie(\"index file corrupt\");\n> > +\n> > +\tif (all) {\n> > +\t\tadd_files_to_cache(fd, files, NULL);\n> > +\t\treturn lock_file.filename;\n> \n> Should you be passing files, which is used as pathspec to decide\n> if your update_callback() should be called, under --all option?\n\nOh... hmm, if 'all' is set there are no paths, but yes, that is\nconfusing.\n\n> > +\t} else if (also) {\n> > +\t\tadd_files_to_cache(fd, files, prefix);\n> > +\t\treturn lock_file.filename;\n> > +\t}\n> > +\n> > +\tif (interactive)\n> > +\t\tinteractive_add();\n> \n> Don't you need to\n> \n>  (1) abort if the user aborts out of interactive add with ^C?\n>  (2) re-read the index after interactive_add() returns?\n\nUhm, yes, good points.\n\n> > +\tif (*files == NULL) {\n> > +\t\t/* Commit index as-is. */\n> > +\t\trollback_lock_file(&lock_file);\n> > +\t\treturn get_index_file();\n> > +\t}\n> > +\n> > +\t/*\n> > +\t * FIXME: Warn on unknown files.  Shell script does\n> > +\t *\n> > +\t *   commit_only=`git-ls-files --error-unmatch -- \"$@\"`\n> > +\t */\n> \n> This should be much easier to do than from the shell script, as\n> you have active_cache[] (aka \"the_index.cache[]\") in-core.\n> \n> > +\t/*\n> > +\t * FIXME: shell script does\n> > +\t *\n> > +\t *   git-read-tree --index-output=\"$TMP_INDEX\" -i -m HEAD\n> > +\t *\n> > +\t * which warns about unmerged files in the index.\n> > +\t */\n> \n> I think \"unmerged warning\" is an unintended side effect.  The\n> point of the command is to have a copy of index, as there is no\n> way for shell script to work with more than one index at a time\n> without using a temporary file.\n\nYes, we talked about this in IRC a few weeks back, and agreed that just\nread_tree() should be fine.  I'll just remove that FIXME.\n\n> > +\n> > +\t/* update the user index file */\n> > +\tadd_files_to_cache(fd, files, prefix);\n> > +\n> > +\tif (!initial_commit) {\n> > +\t\ttree = parse_tree_indirect(head_sha1);\n> > +\t\tif (!tree)\n> > +\t\t\tdie(\"failed to unpack HEAD tree object\");\n> > +\t\tif (read_tree(tree, 0, NULL))\n> > +\t\t\tdie(\"failed to read HEAD tree object\");\n> > +\t}\n> \n> Huh?  Doesn't this read_tree() defeat the add_files_to_cache()\n> you did earlier?\n\nThis is the case where we add the files on the command line\nto .git/index, but commit from a clean index file corresponding to HEAD\nwith the files from the command line added (partial commit?).  The first\nadd_files_to_cache() updates .git/index, then we do read_tree() to build\na tmp index from HEAD and then we add the files again.  The tmp index is\nwritten to a tmp index file.\n\n> > +\n> > +\t/* Uh oh, abusing lock_file to create a garbage collected file */\n> > +\tnext_index_lock = xmalloc(sizeof(*next_index_lock));\n> > +\tfd = hold_lock_file_for_update(next_index_lock,\n> > +\t\t\t\t       git_path(\"next-index-%d\", getpid()), 1);\n> \n> That's not an abuse, but I wonder what happened to the fd you\n> got at the beginning of the function.\n\nUgh, yeah, that's a bit ugly... it gets closed in add_files_to_cache()\nonce we've written the index.  The patch I have here uses the\nadd_files_to_cache() from builtin-add.c which doesn't close the fd or\nwrite the index.  That now happens in prepare_index(), so it's a little\nclearer what's going on.\n\n> > +\tadd_files_to_cache(fd, files, prefix);\n> \n> and then this is puzzling to me.\n> \n> I am starting to suspect that you might be better off if you do\n> not follow the use of temporary index file that was in the shell\n> script version to the letter.  You can use more than one index\n> in the core at the same time, now you are built-in.\n\nAs described above, we need to add the files to the user index and the\ntemporary index we're committing from.  As for just using an in-memory\nindex, I wanted to do it that way originally, but you have to write it\nto disk after all for the pre-commit hook.  Maybe doing it in-memory is\nbetter after all, and then only write it to disk if we actually have a\npre-commit hook.\n\n> > +\n> > +\treturn next_index_lock->filename;\n> > +}\n> \n> ... and if we were to go that route, wt_status structure would\n> have a pointer to the \"struct index_state\" instead of the\n> filename of the index... oops, wt_status_print() will discard\n> and re-read the cache from file, so that approach would not work\n> right now without fixing wt-status first.  Hmm.\n\nYep, that was the other reason.  I suppose a short term fix for this\nwould be to make run_status() take a struct index_state and write it to\na temp file and run against that.  We can then clean that up to not use\na temp file in a second patch.\n\n> > +static int run_status(FILE *fp, const char *index_file)\n> > +{\n> > +\tstruct wt_status s;\n> > +\n> > +\twt_status_prepare(&s);\n> > +\n> > +\tif (amend) {\n> > +\t\ts.amend = 1;\n> > +\t\ts.reference = \"HEAD^1\";\n> > +\t}\n> > +\ts.verbose = verbose;\n> > +\ts.untracked = untracked_files;\n> > +\ts.index_file = index_file;\n> > +\ts.fp = fp;\n> > +\n> > +\twt_status_print(&s);\n> > +\n> > +\treturn s.commitable;\n> > +}\n> \n> I did not look at the rest of the patch; it should be either\n> obviously correct or outright incorrect --- anybody would notice\n> the breakage immediately.\n> \n> The prepare_index() part is the most (and only) \"interesting\"\n> part of the puzzle.  I need to look at this again and think\n> about it a bit more.  It needs to:\n> \n>  * save the current index and restore in case the whole\n>    \"git-commit\" is aborted in any way (e.g. ^C, empty log\n>    message from the editor); This is easy to do from C with\n>    hold_locked_index().\n\nThis is working in the current version.  I use hold_locked_index() in\nthe beginning of prepare_index() and calls commit_locked_index() to\nwrite the new index at the end of cmd_commit() once the commit is done.\n\n>  * when doing a partial commit,\n>    - read HEAD in a temporary index, if not initial commit;\n>    - update the temporary index with the given paths;\n>    - write out the temporary index as a tree to build a commit;\n> \n>    - update the real index the same way with the given paths;\n>    - if all goes well, commit the updated real index; again,\n>      this is easy in C with commit_locked_index();\n\nprepare_index() does this, that's the case you pointed out above, where\nI'm calling add_files_to_cache() twice.\n\n>  * when not doing a partial commit,\n>    - update the real index with given paths (if \"--also\") or all\n>      what diff-files reports (if \"--all\");\n>    - write out the real index as a tree to build a commit;\n>    - if all goes well, commit the updated real index; again,\n>      this is easy in C with commit_locked_index();\n\nThis is also in prepare_index() - the 'if (all)' and 'if (also)' cases\nin the beginning of the function does that, except commiting the the\nupdated real index, that's done at the end of cmd_commit(), share among\nall cases.\n\n> A thing to keep in mind is that you can write out the temporary\n> index as a tree from the core without writing it out first to a\n> temporary index file at all.  Perhaps the code should be\n> structured like this.\n> \n>     - parse options and all the other necessary setup;\n> \n>     - hold_locked_index() to lock the \"real\" index;\n> \n>     - prepare_index() is responsibile for doing two things\n> \n>       1. build, write out and return the tree object to be\n>          committed;\n> \n>       2. leave the_index[] in the state to become the \"real\"\n>          index if this commit command is not aborted;\n> \n>     - take the tree object, put commit log message around it and\n>       make a commit object;\n> \n>     - commit_locked_index() to write out the \"real\" index.\n> \n> Now, the task #1 for prepare_index() is simpler for \"also\" and\n> \"all\" case.  You do the equivalent of \"git-add -u\" or \"git add\n> <path>\" and run cache_tree_update() to write out the tree, and\n> when you are done, both the_index.cache and the_index.cache_tree\n> are up-to-date, ready to be written out by the caller at the\n> very end.\n\nYes, but isn't that what the code is doing now?  I write out the tree in\ncmd_commit(), not in prepare_index(), but prepare_index() is also used\nin cmd_status().  It seems better to only write the tree if we're going\nto do a commit.  Other than that, the flow of the code follows your\ndescription pretty much step by step.\n\n> For a partial commit, the task is a bit more convoluted, as\n> writing out the tree needs to be done from outside the_index.  I\n> would say something like this:\n> \n> \tint prepare_index(unsigned char *sha1) {\n> \t\tif (partial commit) {\n>                        struct index_state tmp_index;\n> \n>                        /* \"temporary index\" only in-core */\n>                        memset(&tmp_index, 0, sizeof(tmp_index));\n>                        read_index(&tmp_index);\n>                        add_files_to_index(files, prefix, &tmp_index);\n>                        write_index_as_tree(&tmp_index, sha1);\n>                        discard_index(&tmp_index);\n> \n>                        /* update the index the same way */\n>                        add_files_to_index(files, prefix, &the_index);\n>                        return ok;\n> \t\t}\n>                 /* otherwise */\n>                 if (files && *files)\n>                 \tadd_files_to_index(files, prefix, &the_index);\n> \t\telse if (all)\n>                 \tadd_files_to_index(NULL, prefix, &the_index);\n> \t\twrite_index_as_tree(&the_index, sha1);\n>                 return ok;\n> \t}\n> \n> where\n> \n>  * add_files_to_index() is similar to your add_files_to_cache()\n>    but takes struct index_state; the callback can still diff\n>    with the_index.cache (aka active_cache) as that diff is what\n>    we are interested in updating anyway;\n> \n>  * write_index_as_tree() would be a new function that takes\n>    struct index_state and writes that as a tree.  It would\n>    essentially be the builtin-write-tree.c::write_tree()\n>    function except the opening and reading the index (or\n>    updating the cache-tree) part, something like:\n> \n> write_index_as_tree(struct index_state *istate, unsigned char *sha1)\n> {\n>        \tif (!cache_tree_fully_valid(istate->cache_tree))\n>         \tcache_tree_update(istate->cache_tree,\n>                 \t\t  istate->cache,\n>                                   istate->cache_nr, 0, 0);\n> \thashcpy(sha1, istate->cache_tree->sha1);\n> }\n\nThat looks nicer, the add_files_to_index() function is a pretty clean\nsolution.  But again, we can't write the tree in prepare_index(); we\nneed the temporary index for the pre-commit hook, and prepare_index() is\nused in cmd_status().  But we can just return an struct index_state\npointer from prepare_index().\n\nThanks for reviewing,\nKristian\n"},{"id":"53735","messageId":"1190397678.31494.57.camel@hinata.boston.redhat.com","threadId":"9904","inReplyTo":"7vr6kunrh7.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 6/7] Export rerere() and launch_editor().","fromName":"Kristian Høgsberg","fromEmail":"krh@redhat.com","sentAt":"2007-09-21T18:01:18Z","receivedAt":"2007-09-21T18:01:18Z","isPatch":true,"sender":{"key":"krh@redhat.com","avatar":"https://gravatar.com/avatar/763dee6f9594ac474f725b137a39565792928e583ddf59b32befc2907409027e?d=mp&s=160"},"body":"On Wed, 2007-09-19 at 16:52 -0700, Junio C Hamano wrote:\n> Kristian Høgsberg <krh@redhat.com> writes:\n> \n> > +/* Export for builtin-commit. */\n> > +int rerere(void)\n> > +{\n> > +\tstruct path_list merge_rr = { NULL, 0, 0, 1 };\n> > +\tint fd;\n> > +\n> > +\tgit_config(git_rerere_config);\n> > +\tif (!is_rerere_enabled())\n> > +\t\treturn 0;\n> > +\n> > +\tmerge_rr_path = xstrdup(git_path(\"rr-cache/MERGE_RR\"));\n> > +\tfd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);\n> > +\tread_rr(&merge_rr);\n> > +\treturn do_plain_rerere(&merge_rr, fd);\n> > +}\n> \n> Is it just me who sees a suboptimal cut and paste here?\n> \n> BTW, [1-5/7] look good so far.\n> \n> diff --git a/builtin-rerere.c b/builtin-rerere.c\n> index 29d057c..2f51ae0 100644\n> --- a/builtin-rerere.c\n> +++ b/builtin-rerere.c\n> @@ -415,18 +415,39 @@ static int is_rerere_enabled(void)\n>  \treturn 1;\n>  }\n>  \n> -int cmd_rerere(int argc, const char **argv, const char *prefix)\n> +static int setup_rerere(struct path_list *merge_rr)\n>  {\n> -\tstruct path_list merge_rr = { NULL, 0, 0, 1 };\n> -\tint i, fd = -1;\n> +\tint fd;\n>  \n>  \tgit_config(git_rerere_config);\n>  \tif (!is_rerere_enabled())\n> -\t\treturn 0;\n> +\t\treturn -1;\n>  \n>  \tmerge_rr_path = xstrdup(git_path(\"rr-cache/MERGE_RR\"));\n>  \tfd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);\n> -\tread_rr(&merge_rr);\n> +\tread_rr(merge_rr);\n> +\treturn fd;\n> +}\n> +\n> +int rerere(void)\n> +{\n> +\tstruct path_list merge_rr = { NULL, 0, 0, 1 };\n> +\tint fd;\n> +\n> +\tfd = setup_rerere(&merge_rr);\n> +\tif (fd < 0)\n> +\t\treturn 0;\n> +\treturn do_plain_rerere(&merge_rr, fd);\n> +}\n> +\n> +int cmd_rerere(int argc, const char **argv, const char *prefix)\n> +{\n> +\tstruct path_list merge_rr = { NULL, 0, 0, 1 };\n> +\tint i, fd;\n> +\n> +\tfd = setup_rerere(&merge_rr);\n> +\tif (fd < 0)\n> +\t\treturn 0;\n>  \n>  \tif (argc < 2)\n>  \t\treturn do_plain_rerere(&merge_rr, fd);\n\nThat looks better, yes.\n\nKristian\n"},{"id":"53740","messageId":"7vwsujizli.fsf@gitster.siamese.dyndns.org","threadId":"9904","inReplyTo":"1190395088.31494.55.camel@hinata.boston.redhat.com","subject":"Re: [PATCH 7/7] Implement git commit as a builtin command.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-21T19:32:25Z","receivedAt":"2007-09-21T19:32:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Kristian Høgsberg <krh@redhat.com> writes:\n\n>> > +\n>> > +\t/* update the user index file */\n>> > +\tadd_files_to_cache(fd, files, prefix);\n>> > +\n>> > +\tif (!initial_commit) {\n>> > +\t\ttree = parse_tree_indirect(head_sha1);\n>> > +\t\tif (!tree)\n>> > +\t\t\tdie(\"failed to unpack HEAD tree object\");\n>> > +\t\tif (read_tree(tree, 0, NULL))\n>> > +\t\t\tdie(\"failed to read HEAD tree object\");\n>> > +\t}\n>> \n>> Huh?  Doesn't this read_tree() defeat the add_files_to_cache()\n>> you did earlier?\n>\n> This is the case where we add the files on the command line\n> to .git/index, but commit from a clean index file corresponding to HEAD\n> with the files from the command line added (partial commit?).  The first\n> add_files_to_cache() updates .git/index, then we do read_tree() to build\n> a tmp index from HEAD and then we add the files again.  The tmp index is\n> written to a tmp index file.\n\nStill, if you are doing read_tree() that reads into the same\nin-core cache you have just prepared in the add_fiels_to_cache()\nabove, potentially overwriting whatever you did, doesn't it?\nThat was what I was puzzled about...\n\n> ...  As for just using an in-memory\n> index, I wanted to do it that way originally, but you have to write it\n> to disk after all for the pre-commit hook.\n\nAh, I completely forgot about the hook.  Ok, scratch the idea of\nnot using a temporary index file.  The is not much potential for\nperformance gain anyway.\n"},{"id":"53943","messageId":"1190665647.11372.3.camel@hinata.boston.redhat.com","threadId":"9904","inReplyTo":"7vwsujizli.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 7/7] Implement git commit as a builtin command.","fromName":"Kristian Høgsberg","fromEmail":"krh@redhat.com","sentAt":"2007-09-24T20:27:27Z","receivedAt":"2007-09-24T20:27:27Z","isPatch":true,"sender":{"key":"krh@redhat.com","avatar":"https://gravatar.com/avatar/763dee6f9594ac474f725b137a39565792928e583ddf59b32befc2907409027e?d=mp&s=160"},"body":"On Fri, 2007-09-21 at 12:32 -0700, Junio C Hamano wrote:\n> Kristian Høgsberg <krh@redhat.com> writes:\n> \n> >> > +\n> >> > +\t/* update the user index file */\n> >> > +\tadd_files_to_cache(fd, files, prefix);\n> >> > +\n> >> > +\tif (!initial_commit) {\n> >> > +\t\ttree = parse_tree_indirect(head_sha1);\n> >> > +\t\tif (!tree)\n> >> > +\t\t\tdie(\"failed to unpack HEAD tree object\");\n> >> > +\t\tif (read_tree(tree, 0, NULL))\n> >> > +\t\t\tdie(\"failed to read HEAD tree object\");\n> >> > +\t}\n> >> \n> >> Huh?  Doesn't this read_tree() defeat the add_files_to_cache()\n> >> you did earlier?\n> >\n> > This is the case where we add the files on the command line\n> > to .git/index, but commit from a clean index file corresponding to HEAD\n> > with the files from the command line added (partial commit?).  The first\n> > add_files_to_cache() updates .git/index, then we do read_tree() to build\n> > a tmp index from HEAD and then we add the files again.  The tmp index is\n> > written to a tmp index file.\n> \n> Still, if you are doing read_tree() that reads into the same\n> in-core cache you have just prepared in the add_fiels_to_cache()\n> above, potentially overwriting whatever you did, doesn't it?\n> That was what I was puzzled about...\n\nAh, I understand the confusion - add_files_to_cache() will write out the\ncache to the given fd and close it.  That's not clear, and I've moved\nthe write+close part back into prepare_index() in the follow-on patches\nI sent that shares out add_files_to_cache() with builtin-add.c.\n\n> > ...  As for just using an in-memory\n> > index, I wanted to do it that way originally, but you have to write it\n> > to disk after all for the pre-commit hook.\n> \n> Ah, I completely forgot about the hook.  Ok, scratch the idea of\n> not using a temporary index file.  The is not much potential for\n> performance gain anyway.\n\nOk, cool, I'll keep the current structure of the code then.\n\nKristian\n"}]}