#include "strbuf.h"
#include "utf8.h"
#include "parse-options.h"
-#include "path-list.h"
+#include "string-list.h"
+#include "rerere.h"
#include "unpack-trees.h"
static const char * const builtin_commit_usage[] = {
- "git-commit [options] [--] <filepattern>...",
+ "git commit [options] [--] <filepattern>...",
NULL
};
static const char * const builtin_status_usage[] = {
- "git-status [options] [--] <filepattern>...",
+ "git status [options] [--] <filepattern>...",
NULL
};
static char *edit_message, *use_message;
static char *author_name, *author_email, *author_date;
static int all, edit_flag, also, interactive, only, amend, signoff;
-static int quiet, verbose, untracked_files, no_verify, allow_empty;
+static int quiet, verbose, no_verify, allow_empty;
+static char *untracked_files_arg;
/*
* The default commit message cleanup mode will remove the lines
* beginning with # (shell comments) and leading and trailing
static char *cleanup_arg;
static int use_editor = 1, initial_commit, in_merge;
-const char *only_include_assumed;
-struct strbuf message;
+static const char *only_include_assumed;
+static struct strbuf message;
static int opt_parse_m(const struct option *opt, const char *arg, int unset)
{
strbuf_setlen(buf, 0);
else {
strbuf_addstr(buf, arg);
- strbuf_addch(buf, '\n');
- strbuf_addch(buf, '\n');
+ strbuf_addstr(buf, "\n\n");
}
return 0;
}
OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
- OPT_BOOLEAN('u', "untracked-files", &untracked_files, "show all untracked files"),
+ { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
* Take a union of paths in the index and the named tree (typically, "HEAD"),
* and return the paths that match the given pattern in list.
*/
-static int list_paths(struct path_list *list, const char *with_tree,
+static int list_paths(struct string_list *list, const char *with_tree,
const char *prefix, const char **pattern)
{
int i;
continue;
if (!pathspec_match(pattern, m, ce->name, 0))
continue;
- path_list_insert(ce->name, list);
+ string_list_insert(ce->name, list);
}
return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
}
-static void add_remove_files(struct path_list *list)
+static void add_remove_files(struct string_list *list)
{
int i;
for (i = 0; i < list->nr; i++) {
struct stat st;
- struct path_list_item *p = &(list->items[i]);
+ struct string_list_item *p = &(list->items[i]);
- if (!lstat(p->path, &st)) {
- if (add_to_cache(p->path, &st, 0))
+ if (!lstat(p->string, &st)) {
+ if (add_to_cache(p->string, &st, 0))
die("updating files failed");
} else
- remove_file_from_cache(p->path);
+ remove_file_from_cache(p->string);
}
}
static char *prepare_index(int argc, const char **argv, const char *prefix)
{
int fd;
- struct path_list partial;
+ struct string_list partial;
const char **pathspec = NULL;
if (interactive) {
interactive_add(argc, argv, prefix);
- if (read_cache() < 0)
+ if (read_cache_preload(NULL) < 0)
die("index file corrupt");
commit_style = COMMIT_AS_IS;
return get_index_file();
}
- if (read_cache() < 0)
- die("index file corrupt");
-
if (*argv)
pathspec = get_pathspec(prefix, argv);
+ if (read_cache_preload(pathspec) < 0)
+ die("index file corrupt");
+
/*
* Non partial, non as-is commit.
*
die("cannot do a partial commit during a merge.");
memset(&partial, 0, sizeof(partial));
- partial.strdup_paths = 1;
+ partial.strdup_strings = 1;
if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
exit(1);
die("unable to write new_index file");
fd = hold_lock_file_for_update(&false_lock,
- git_path("next-index-%d", getpid()), 1);
+ git_path("next-index-%"PRIuMAX,
+ (uintmax_t) getpid()),
+ LOCK_DIE_ON_ERROR);
create_base_index();
add_remove_files(&partial);
s.reference = "HEAD^1";
}
s.verbose = verbose;
- s.untracked = untracked_files;
+ s.untracked = (show_untracked_files == SHOW_ALL_UNTRACKED_FILES);
s.index_file = index_file;
s.fp = fp;
s.nowarn = nowarn;
return s.commitable;
}
-static int run_hook(const char *index_file, const char *name, ...)
-{
- struct child_process hook;
- const char *argv[10], *env[2];
- char index[PATH_MAX];
- va_list args;
- int i;
-
- va_start(args, name);
- argv[0] = git_path("hooks/%s", name);
- i = 0;
- do {
- if (++i >= ARRAY_SIZE(argv))
- die ("run_hook(): too many arguments");
- argv[i] = va_arg(args, const char *);
- } while (argv[i]);
- va_end(args);
-
- snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
- env[0] = index;
- env[1] = NULL;
-
- if (access(argv[0], X_OK) < 0)
- return 0;
-
- memset(&hook, 0, sizeof(hook));
- hook.argv = argv;
- hook.no_stdin = 1;
- hook.stdout_to_stderr = 1;
- hook.env = env;
-
- return run_command(&hook);
-}
-
static int is_a_merge(const unsigned char *sha1)
{
struct commit *commit = lookup_commit(sha1);
{
struct stat statbuf;
int commitable, saved_color_setting;
- struct strbuf sb;
+ struct strbuf sb = STRBUF_INIT;
char *buffer;
FILE *fp;
const char *hook_arg1 = NULL;
if (!no_verify && run_hook(index_file, "pre-commit", NULL))
return 0;
- strbuf_init(&sb, 0);
if (message.len) {
strbuf_addbuf(&sb, &message);
hook_arg1 = "message";
fp = fopen(git_path(commit_editmsg), "w");
if (fp == NULL)
- die("could not open %s", git_path(commit_editmsg));
+ die("could not open %s: %s",
+ git_path(commit_editmsg), strerror(errno));
if (cleanup_mode != CLEANUP_NONE)
stripspace(&sb, 0);
if (signoff) {
- struct strbuf sob;
+ struct strbuf sob = STRBUF_INIT;
int i;
- strbuf_init(&sob, 0);
strbuf_addstr(&sob, sign_off_header);
strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
getenv("GIT_COMMITTER_EMAIL")));
fprintf(fp,
"\n"
- "# Please enter the commit message for your changes.\n"
- "# (Comment lines starting with '#' will ");
+ "# Please enter the commit message for your changes.");
if (cleanup_mode == CLEANUP_ALL)
- fprintf(fp, "not be included)\n");
+ fprintf(fp,
+ " Lines starting\n"
+ "# with '#' will be ignored, and an empty"
+ " message aborts the commit.\n");
else /* CLEANUP_SPACE, that is. */
- fprintf(fp, "be kept.\n"
- "# You can remove them yourself if you want to)\n");
+ fprintf(fp,
+ " Lines starting\n"
+ "# with '#' will be kept; you may remove them"
+ " yourself if you want to.\n"
+ "# An empty message aborts the commit.\n");
if (only_include_assumed)
fprintf(fp, "# %s\n", only_include_assumed);
if (!commitable && !in_merge && !allow_empty &&
!(amend && is_a_merge(head_sha1))) {
run_status(stdout, index_file, prefix, 0);
- unlink(commit_editmsg);
return 0;
}
char index[PATH_MAX];
const char *env[2] = { index, NULL };
snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
- launch_editor(git_path(commit_editmsg), NULL, env);
+ if (launch_editor(git_path(commit_editmsg), NULL, env)) {
+ fprintf(stderr,
+ "Please supply the message using either -m or -F option.\n");
+ exit(1);
+ }
}
if (!no_verify &&
}
/*
- * Find out if the message starting at position 'start' in the strbuf
- * contains only whitespace and Signed-off-by lines.
+ * Find out if the message in the strbuf contains only whitespace and
+ * Signed-off-by lines.
*/
-static int message_is_empty(struct strbuf *sb, int start)
+static int message_is_empty(struct strbuf *sb)
{
- struct strbuf tmpl;
+ struct strbuf tmpl = STRBUF_INIT;
const char *nl;
- int eol, i;
+ int eol, i, start = 0;
if (cleanup_mode == CLEANUP_NONE && sb->len)
return 0;
/* See if the template is just a prefix of the message. */
- strbuf_init(&tmpl, 0);
if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
if (start + tmpl.len <= sb->len &&
return 1;
}
+static const char *find_author_by_nickname(const char *name)
+{
+ struct rev_info revs;
+ struct commit *commit;
+ struct strbuf buf = STRBUF_INIT;
+ const char *av[20];
+ int ac = 0;
+
+ init_revisions(&revs, NULL);
+ strbuf_addf(&buf, "--author=%s", name);
+ av[++ac] = "--all";
+ av[++ac] = "-i";
+ av[++ac] = buf.buf;
+ av[++ac] = NULL;
+ setup_revisions(ac, av, &revs, NULL);
+ prepare_revision_walk(&revs);
+ commit = get_revision(&revs);
+ if (commit) {
+ strbuf_release(&buf);
+ format_commit_message(commit, "%an <%ae>", &buf, DATE_NORMAL);
+ return strbuf_detach(&buf, NULL);
+ }
+ die("No existing author found with '%s'", name);
+}
+
static int parse_and_validate_options(int argc, const char *argv[],
const char * const usage[],
const char *prefix)
logfile = parse_options_fix_filename(prefix, logfile);
template_file = parse_options_fix_filename(prefix, template_file);
+ if (force_author && !strchr(force_author, '>'))
+ force_author = find_author_by_nickname(force_author);
+
if (logfile || message.len || use_message)
use_editor = 0;
if (edit_flag)
else
die("Invalid cleanup mode %s", cleanup_arg);
+ if (!untracked_files_arg)
+ ; /* default already initialized */
+ else if (!strcmp(untracked_files_arg, "no"))
+ show_untracked_files = SHOW_NO_UNTRACKED_FILES;
+ else if (!strcmp(untracked_files_arg, "normal"))
+ show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
+ else if (!strcmp(untracked_files_arg, "all"))
+ show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
+ else
+ die("Invalid untracked files mode '%s'", untracked_files_arg);
+
if (all && argc > 0)
die("Paths with -a does not make sense.");
else if (interactive && argc > 0)
if (wt_status_use_color == -1)
wt_status_use_color = git_use_color_default;
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
argc = parse_and_validate_options(argc, argv, builtin_status_usage, prefix);
index_file = prepare_index(argc, argv, prefix);
{
struct rev_info rev;
struct commit *commit;
+ static const char *format = "format:%h] %s";
+ unsigned char junk_sha1[20];
+ const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
commit = lookup_commit(sha1);
if (!commit)
rev.verbose_header = 1;
rev.show_root_diff = 1;
- get_commit_format("format:%h: %s", &rev);
+ get_commit_format(format, &rev);
rev.always_show_header = 0;
rev.diffopt.detect_rename = 1;
rev.diffopt.rename_limit = 100;
rev.diffopt.break_opt = 0;
diff_setup_done(&rev.diffopt);
- printf("Created %scommit ", initial_commit ? "initial " : "");
+ printf("[%s%s ",
+ !prefixcmp(head, "refs/heads/") ?
+ head + 11 :
+ !strcmp(head, "HEAD") ?
+ "detached HEAD" :
+ head,
+ initial_commit ? " (root-commit)" : "");
if (!log_tree_commit(&rev, commit)) {
struct strbuf buf = STRBUF_INIT;
- format_commit_message(commit, "%h: %s", &buf);
+ format_commit_message(commit, format + 7, &buf, DATE_NORMAL);
printf("%s\n", buf.buf);
strbuf_release(&buf);
}
}
-int git_commit_config(const char *k, const char *v, void *cb)
+static int git_commit_config(const char *k, const char *v, void *cb)
{
if (!strcmp(k, "commit.template"))
return git_config_string(&template_file, k, v);
return git_status_config(k, v, cb);
}
-static const char commit_utf8_warn[] =
-"Warning: commit message does not conform to UTF-8.\n"
-"You may want to amend it after fixing the message, or set the config\n"
-"variable i18n.commitencoding to the encoding your project uses.\n";
-
-static void add_parent(struct strbuf *sb, const unsigned char *sha1)
-{
- struct object *obj = parse_object(sha1);
- const char *parent = sha1_to_hex(sha1);
- const char *cp;
-
- if (!obj)
- die("Unable to find commit parent %s", parent);
- if (obj->type != OBJ_COMMIT)
- die("Parent %s isn't a proper commit", parent);
-
- for (cp = sb->buf; cp && (cp = strstr(cp, "\nparent ")); cp += 8) {
- if (!memcmp(cp + 8, parent, 40) && cp[48] == '\n') {
- error("duplicate parent %s ignored", parent);
- return;
- }
- }
- strbuf_addf(sb, "parent %s\n", parent);
-}
-
int cmd_commit(int argc, const char **argv, const char *prefix)
{
- int header_len;
- struct strbuf sb;
+ struct strbuf sb = STRBUF_INIT;
const char *index_file, *reflog_msg;
char *nl, *p;
unsigned char commit_sha1[20];
struct ref_lock *ref_lock;
+ struct commit_list *parents = NULL, **pptr = &parents;
+ struct stat statbuf;
+ int allow_fast_forward = 1;
git_config(git_commit_config, NULL);
+ if (wt_status_use_color == -1)
+ wt_status_use_color = git_use_color_default;
+
argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix);
index_file = prepare_index(argc, argv, prefix);
return 1;
}
- /*
- * The commit object
- */
- strbuf_init(&sb, 0);
- strbuf_addf(&sb, "tree %s\n",
- sha1_to_hex(active_cache_tree->sha1));
-
/* Determine parents */
if (initial_commit) {
reflog_msg = "commit (initial)";
die("could not parse HEAD commit");
for (c = commit->parents; c; c = c->next)
- add_parent(&sb, c->item->object.sha1);
+ pptr = &commit_list_insert(c->item, pptr)->next;
} else if (in_merge) {
- struct strbuf m;
+ struct strbuf m = STRBUF_INIT;
FILE *fp;
reflog_msg = "commit (merge)";
- add_parent(&sb, head_sha1);
- strbuf_init(&m, 0);
+ pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
fp = fopen(git_path("MERGE_HEAD"), "r");
if (fp == NULL)
die("could not open %s for reading: %s",
unsigned char sha1[20];
if (get_sha1_hex(m.buf, sha1) < 0)
die("Corrupt MERGE_HEAD file (%s)", m.buf);
- add_parent(&sb, sha1);
+ pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next;
}
fclose(fp);
strbuf_release(&m);
+ if (!stat(git_path("MERGE_MODE"), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0)
+ die("could not read MERGE_MODE: %s",
+ strerror(errno));
+ if (!strcmp(sb.buf, "no-ff"))
+ allow_fast_forward = 0;
+ }
+ if (allow_fast_forward)
+ parents = reduce_heads(parents);
} else {
reflog_msg = "commit";
- strbuf_addf(&sb, "parent %s\n", sha1_to_hex(head_sha1));
+ pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
}
- strbuf_addf(&sb, "author %s\n",
- fmt_ident(author_name, author_email, author_date, IDENT_ERROR_ON_NO_NAME));
- strbuf_addf(&sb, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
- if (!is_encoding_utf8(git_commit_encoding))
- strbuf_addf(&sb, "encoding %s\n", git_commit_encoding);
- strbuf_addch(&sb, '\n');
-
/* Finally, get the commit message */
- header_len = sb.len;
+ strbuf_reset(&sb);
if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
rollback_index_files();
die("could not read commit message");
}
/* Truncate the message just before the diff, if any. */
- p = strstr(sb.buf, "\ndiff --git a/");
- if (p != NULL)
- strbuf_setlen(&sb, p - sb.buf + 1);
+ if (verbose) {
+ p = strstr(sb.buf, "\ndiff --git ");
+ if (p != NULL)
+ strbuf_setlen(&sb, p - sb.buf + 1);
+ }
if (cleanup_mode != CLEANUP_NONE)
stripspace(&sb, cleanup_mode == CLEANUP_ALL);
- if (sb.len < header_len || message_is_empty(&sb, header_len)) {
+ if (message_is_empty(&sb)) {
rollback_index_files();
- die("no commit message? aborting commit.");
+ fprintf(stderr, "Aborting commit due to empty commit message.\n");
+ exit(1);
}
- strbuf_addch(&sb, '\0');
- if (is_encoding_utf8(git_commit_encoding) && !is_utf8(sb.buf))
- fprintf(stderr, commit_utf8_warn);
- if (write_sha1_file(sb.buf, sb.len - 1, commit_type, commit_sha1)) {
+ if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1,
+ fmt_ident(author_name, author_email, author_date,
+ IDENT_ERROR_ON_NO_NAME))) {
rollback_index_files();
die("failed to write commit object");
}
initial_commit ? NULL : head_sha1,
0);
- nl = strchr(sb.buf + header_len, '\n');
+ nl = strchr(sb.buf, '\n');
if (nl)
strbuf_setlen(&sb, nl + 1 - sb.buf);
else
strbuf_addch(&sb, '\n');
- strbuf_remove(&sb, 0, header_len);
strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
unlink(git_path("MERGE_HEAD"));
unlink(git_path("MERGE_MSG"));
+ unlink(git_path("MERGE_MODE"));
unlink(git_path("SQUASH_MSG"));
if (commit_index_files())