Merge branch 'jc/shortstatus'

* jc/shortstatus:
  git commit --dry-run -v: show diff in color when asked
  Documentation/git-commit.txt: describe --dry-run
  wt-status: collect untracked files in a separate "collect" phase
  Make git_status_config() file scope static to builtin-commit.c
  wt-status: move wt_status_colors[] into wt_status structure
  wt-status: move many global settings to wt_status structure
  commit: --dry-run
  status: show worktree status of conflicted paths separately
  wt-status.c: rework the way changes to the index and work tree are summarized
  diff-index: keep the original index intact
  diff-index: report unmerged new entries
This commit is contained in:
Junio C Hamano 2009-08-28 19:38:19 -07:00
commit 433233e0b6
6 changed files with 554 additions and 234 deletions

View File

@ -8,8 +8,8 @@ git-commit - Record changes to the repository
SYNOPSIS SYNOPSIS
-------- --------
[verse] [verse]
'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] 'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run]
[(-c | -C) <commit>] [-F <file> | -m <msg>] [(-c | -C) <commit>] [-F <file> | -m <msg>] [--dry-run]
[--allow-empty] [--no-verify] [-e] [--author=<author>] [--allow-empty] [--no-verify] [-e] [--author=<author>]
[--cleanup=<mode>] [--] [[-i | -o ]<file>...] [--cleanup=<mode>] [--] [[-i | -o ]<file>...]
@ -42,10 +42,9 @@ The content to be added can be specified in several ways:
by one which files should be part of the commit, before finalizing the by one which files should be part of the commit, before finalizing the
operation. Currently, this is done by invoking 'git-add --interactive'. operation. Currently, this is done by invoking 'git-add --interactive'.
The 'git-status' command can be used to obtain a The `--dry-run` option can be used to obtain a
summary of what is included by any of the above for the next summary of what is included by any of the above for the next
commit by giving the same set of parameters you would give to commit by giving the same set of parameters (options and paths).
this command.
If you make a commit and then find a mistake immediately after If you make a commit and then find a mistake immediately after
that, you can recover from it with 'git-reset'. that, you can recover from it with 'git-reset'.
@ -70,6 +69,12 @@ OPTIONS
Like '-C', but with '-c' the editor is invoked, so that Like '-C', but with '-c' the editor is invoked, so that
the user can further edit the commit message. the user can further edit the commit message.
--dry-run::
Do not actually make a commit, but show the list of paths
with updates in the index, paths with changes in the work tree,
and paths that are untracked, similar to the one that is given
in the commit log editor.
-F <file>:: -F <file>::
--file=<file>:: --file=<file>::
Take the commit message from the given file. Use '-' to Take the commit message from the given file. Use '-' to
@ -198,6 +203,11 @@ specified.
--quiet:: --quiet::
Suppress commit summary message. Suppress commit summary message.
--dry-run::
Do not create a commit, but show a list of paths that are
to be committed, paths with local changes that will be left
uncommitted and paths that are untracked.
\--:: \--::
Do not interpret any more arguments as options. Do not interpret any more arguments as options.

View File

@ -51,7 +51,7 @@ static const char *template_file;
static char *edit_message, *use_message; static char *edit_message, *use_message;
static char *author_name, *author_email, *author_date; static char *author_name, *author_email, *author_date;
static int all, edit_flag, also, interactive, only, amend, signoff; static int all, edit_flag, also, interactive, only, amend, signoff;
static int quiet, verbose, no_verify, allow_empty; static int quiet, verbose, no_verify, allow_empty, dry_run;
static char *untracked_files_arg; static char *untracked_files_arg;
/* /*
* The default commit message cleanup mode will remove the lines * The default commit message cleanup mode will remove the lines
@ -103,6 +103,7 @@ static struct option builtin_commit_options[] = {
OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"), OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"),
OPT_BOOLEAN('o', "only", &only, "commit only specified files"), OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
{ 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" }, { 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_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
@ -217,12 +218,15 @@ static void create_base_index(void)
exit(128); /* We've already reported the error, finish dying */ exit(128); /* We've already reported the error, finish dying */
} }
static char *prepare_index(int argc, const char **argv, const char *prefix) static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
{ {
int fd; int fd;
struct string_list partial; struct string_list partial;
const char **pathspec = NULL; const char **pathspec = NULL;
int refresh_flags = REFRESH_QUIET;
if (is_status)
refresh_flags |= REFRESH_UNMERGED;
if (interactive) { if (interactive) {
if (interactive_add(argc, argv, prefix) != 0) if (interactive_add(argc, argv, prefix) != 0)
die("interactive add failed"); die("interactive add failed");
@ -253,7 +257,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
if (all || (also && pathspec && *pathspec)) { if (all || (also && pathspec && *pathspec)) {
int fd = hold_locked_index(&index_lock, 1); int fd = hold_locked_index(&index_lock, 1);
add_files_to_cache(also ? prefix : NULL, pathspec, 0); add_files_to_cache(also ? prefix : NULL, pathspec, 0);
refresh_cache(REFRESH_QUIET); refresh_cache(refresh_flags);
if (write_cache(fd, active_cache, active_nr) || if (write_cache(fd, active_cache, active_nr) ||
close_lock_file(&index_lock)) close_lock_file(&index_lock))
die("unable to write new_index file"); die("unable to write new_index file");
@ -272,7 +276,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
*/ */
if (!pathspec || !*pathspec) { if (!pathspec || !*pathspec) {
fd = hold_locked_index(&index_lock, 1); fd = hold_locked_index(&index_lock, 1);
refresh_cache(REFRESH_QUIET); refresh_cache(refresh_flags);
if (write_cache(fd, active_cache, active_nr) || if (write_cache(fd, active_cache, active_nr) ||
commit_locked_index(&index_lock)) commit_locked_index(&index_lock))
die("unable to write new_index file"); die("unable to write new_index file");
@ -339,27 +343,24 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
return false_lock.filename; return false_lock.filename;
} }
static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn) static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
struct wt_status *s)
{ {
struct wt_status s; if (s->relative_paths)
s->prefix = prefix;
wt_status_prepare(&s);
if (wt_status_relative_paths)
s.prefix = prefix;
if (amend) { if (amend) {
s.amend = 1; s->amend = 1;
s.reference = "HEAD^1"; s->reference = "HEAD^1";
} }
s.verbose = verbose; s->verbose = verbose;
s.untracked = (show_untracked_files == SHOW_ALL_UNTRACKED_FILES); s->index_file = index_file;
s.index_file = index_file; s->fp = fp;
s.fp = fp; s->nowarn = nowarn;
s.nowarn = nowarn;
wt_status_print(&s); wt_status_print(s);
return s.commitable; return s->commitable;
} }
static int is_a_merge(const unsigned char *sha1) static int is_a_merge(const unsigned char *sha1)
@ -413,7 +414,8 @@ static void determine_author_info(void)
author_date = date; author_date = date;
} }
static int prepare_to_commit(const char *index_file, const char *prefix) static int prepare_to_commit(const char *index_file, const char *prefix,
struct wt_status *s)
{ {
struct stat statbuf; struct stat statbuf;
int commitable, saved_color_setting; int commitable, saved_color_setting;
@ -555,10 +557,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
if (ident_shown) if (ident_shown)
fprintf(fp, "#\n"); fprintf(fp, "#\n");
saved_color_setting = wt_status_use_color; saved_color_setting = s->use_color;
wt_status_use_color = 0; s->use_color = 0;
commitable = run_status(fp, index_file, prefix, 1); commitable = run_status(fp, index_file, prefix, 1, s);
wt_status_use_color = saved_color_setting; s->use_color = saved_color_setting;
} else { } else {
unsigned char sha1[20]; unsigned char sha1[20];
const char *parent = "HEAD"; const char *parent = "HEAD";
@ -579,7 +581,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
if (!commitable && !in_merge && !allow_empty && if (!commitable && !in_merge && !allow_empty &&
!(amend && is_a_merge(head_sha1))) { !(amend && is_a_merge(head_sha1))) {
run_status(stdout, index_file, prefix, 0); run_status(stdout, index_file, prefix, 0, s);
return 0; return 0;
} }
@ -691,7 +693,8 @@ static const char *find_author_by_nickname(const char *name)
static int parse_and_validate_options(int argc, const char *argv[], static int parse_and_validate_options(int argc, const char *argv[],
const char * const usage[], const char * const usage[],
const char *prefix) const char *prefix,
struct wt_status *s)
{ {
int f = 0; int f = 0;
@ -794,11 +797,11 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (!untracked_files_arg) if (!untracked_files_arg)
; /* default already initialized */ ; /* default already initialized */
else if (!strcmp(untracked_files_arg, "no")) else if (!strcmp(untracked_files_arg, "no"))
show_untracked_files = SHOW_NO_UNTRACKED_FILES; s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
else if (!strcmp(untracked_files_arg, "normal")) else if (!strcmp(untracked_files_arg, "normal"))
show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
else if (!strcmp(untracked_files_arg, "all")) else if (!strcmp(untracked_files_arg, "all"))
show_untracked_files = SHOW_ALL_UNTRACKED_FILES; s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
else else
die("Invalid untracked files mode '%s'", untracked_files_arg); die("Invalid untracked files mode '%s'", untracked_files_arg);
@ -810,30 +813,95 @@ static int parse_and_validate_options(int argc, const char *argv[],
return argc; return argc;
} }
int cmd_status(int argc, const char **argv, const char *prefix) static int dry_run_commit(int argc, const char **argv, const char *prefix,
struct wt_status *s)
{ {
const char *index_file;
int commitable; int commitable;
const char *index_file;
git_config(git_status_config, NULL); index_file = prepare_index(argc, argv, prefix, 1);
commitable = run_status(stdout, index_file, prefix, 0, s);
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);
commitable = run_status(stdout, index_file, prefix, 0);
rollback_index_files(); rollback_index_files();
return commitable ? 0 : 1; return commitable ? 0 : 1;
} }
static int parse_status_slot(const char *var, int offset)
{
if (!strcasecmp(var+offset, "header"))
return WT_STATUS_HEADER;
if (!strcasecmp(var+offset, "updated")
|| !strcasecmp(var+offset, "added"))
return WT_STATUS_UPDATED;
if (!strcasecmp(var+offset, "changed"))
return WT_STATUS_CHANGED;
if (!strcasecmp(var+offset, "untracked"))
return WT_STATUS_UNTRACKED;
if (!strcasecmp(var+offset, "nobranch"))
return WT_STATUS_NOBRANCH;
if (!strcasecmp(var+offset, "unmerged"))
return WT_STATUS_UNMERGED;
die("bad config variable '%s'", var);
}
static int git_status_config(const char *k, const char *v, void *cb)
{
struct wt_status *s = cb;
if (!strcmp(k, "status.submodulesummary")) {
int is_bool;
s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
if (is_bool && s->submodule_summary)
s->submodule_summary = -1;
return 0;
}
if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
s->use_color = git_config_colorbool(k, v, -1);
return 0;
}
if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
int slot = parse_status_slot(k, 13);
if (!v)
return config_error_nonbool(k);
color_parse(v, k, s->color_palette[slot]);
return 0;
}
if (!strcmp(k, "status.relativepaths")) {
s->relative_paths = git_config_bool(k, v);
return 0;
}
if (!strcmp(k, "status.showuntrackedfiles")) {
if (!v)
return config_error_nonbool(k);
else if (!strcmp(v, "no"))
s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
else if (!strcmp(v, "normal"))
s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
else if (!strcmp(v, "all"))
s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
else
return error("Invalid untracked files mode '%s'", v);
return 0;
}
return git_diff_ui_config(k, v, NULL);
}
int cmd_status(int argc, const char **argv, const char *prefix)
{
struct wt_status s;
wt_status_prepare(&s);
git_config(git_status_config, &s);
if (s.use_color == -1)
s.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, &s);
return dry_run_commit(argc, argv, prefix, &s);
}
static void print_summary(const char *prefix, const unsigned char *sha1) static void print_summary(const char *prefix, const unsigned char *sha1)
{ {
struct rev_info rev; struct rev_info rev;
@ -883,10 +951,12 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
static 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)
{ {
struct wt_status *s = cb;
if (!strcmp(k, "commit.template")) if (!strcmp(k, "commit.template"))
return git_config_string(&template_file, k, v); return git_config_string(&template_file, k, v);
return git_status_config(k, v, cb); return git_status_config(k, v, s);
} }
int cmd_commit(int argc, const char **argv, const char *prefix) int cmd_commit(int argc, const char **argv, const char *prefix)
@ -899,19 +969,26 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
struct commit_list *parents = NULL, **pptr = &parents; struct commit_list *parents = NULL, **pptr = &parents;
struct stat statbuf; struct stat statbuf;
int allow_fast_forward = 1; int allow_fast_forward = 1;
struct wt_status s;
git_config(git_commit_config, NULL); wt_status_prepare(&s);
git_config(git_commit_config, &s);
if (wt_status_use_color == -1) if (s.use_color == -1)
wt_status_use_color = git_use_color_default; s.use_color = git_use_color_default;
argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix); argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
prefix, &s);
index_file = prepare_index(argc, argv, prefix); if (dry_run) {
if (diff_use_color_default == -1)
diff_use_color_default = git_use_color_default;
return dry_run_commit(argc, argv, prefix, &s);
}
index_file = prepare_index(argc, argv, prefix, 0);
/* Set up everything for writing the commit object. This includes /* Set up everything for writing the commit object. This includes
running hooks, writing the trees, and interacting with the user. */ running hooks, writing the trees, and interacting with the user. */
if (!prepare_to_commit(index_file, prefix)) { if (!prepare_to_commit(index_file, prefix, &s)) {
rollback_index_files(); rollback_index_files();
return 1; return 1;
} }

View File

@ -309,22 +309,6 @@ static int show_modified(struct rev_info *revs,
return 0; return 0;
} }
/*
* This turns all merge entries into "stage 3". That guarantees that
* when we read in the new tree (into "stage 1"), we won't lose sight
* of the fact that we had unmerged entries.
*/
static void mark_merge_entries(void)
{
int i;
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
if (!ce_stage(ce))
continue;
ce->ce_flags |= CE_STAGEMASK;
}
}
/* /*
* This gets a mix of an existing index and a tree, one pathname entry * This gets a mix of an existing index and a tree, one pathname entry
* at a time. The index entry may be a single stage-0 one, but it could * at a time. The index entry may be a single stage-0 one, but it could
@ -350,8 +334,8 @@ static void do_oneway_diff(struct unpack_trees_options *o,
match_missing = !revs->ignore_merges; match_missing = !revs->ignore_merges;
if (cached && idx && ce_stage(idx)) { if (cached && idx && ce_stage(idx)) {
if (tree) diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode,
diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, idx->sha1); idx->sha1);
return; return;
} }
@ -437,8 +421,6 @@ int run_diff_index(struct rev_info *revs, int cached)
struct unpack_trees_options opts; struct unpack_trees_options opts;
struct tree_desc t; struct tree_desc t;
mark_merge_entries();
ent = revs->pending.objects[0].item; ent = revs->pending.objects[0].item;
tree_name = revs->pending.objects[0].name; tree_name = revs->pending.objects[0].name;
tree = parse_tree_indirect(ent->sha1); tree = parse_tree_indirect(ent->sha1);

58
t/t7060-wtstatus.sh Executable file
View File

@ -0,0 +1,58 @@
#!/bin/sh
test_description='basic work tree status reporting'
. ./test-lib.sh
test_expect_success setup '
test_commit A &&
test_commit B oneside added &&
git checkout A^0 &&
test_commit C oneside created
'
test_expect_success 'A/A conflict' '
git checkout B^0 &&
test_must_fail git merge C
'
test_expect_success 'Report path with conflict' '
git diff --cached --name-status >actual &&
echo "U oneside" >expect &&
test_cmp expect actual
'
test_expect_success 'Report new path with conflict' '
git diff --cached --name-status HEAD^ >actual &&
echo "U oneside" >expect &&
test_cmp expect actual
'
cat >expect <<EOF
# On branch side
# Unmerged paths:
# (use "git reset HEAD <file>..." to unstage)
# (use "git add <file>..." to mark resolution)
#
# deleted by us: foo
#
no changes added to commit (use "git add" and/or "git commit -a")
EOF
test_expect_success 'M/D conflict does not segfault' '
mkdir mdconflict &&
(
cd mdconflict &&
git init &&
test_commit initial foo "" &&
test_commit modify foo foo &&
git checkout -b side HEAD^ &&
git rm foo &&
git commit -m delete &&
test_must_fail git merge master &&
test_must_fail git status > ../actual
) &&
test_cmp expect actual
'
test_done

View File

@ -1,6 +1,5 @@
#include "cache.h" #include "cache.h"
#include "wt-status.h" #include "wt-status.h"
#include "color.h"
#include "object.h" #include "object.h"
#include "dir.h" #include "dir.h"
#include "commit.h" #include "commit.h"
@ -11,38 +10,18 @@
#include "run-command.h" #include "run-command.h"
#include "remote.h" #include "remote.h"
int wt_status_relative_paths = 1; static char default_wt_status_colors[][COLOR_MAXLEN] = {
int wt_status_use_color = -1;
static int wt_status_submodule_summary;
static char wt_status_colors[][COLOR_MAXLEN] = {
GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */ GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
GIT_COLOR_GREEN, /* WT_STATUS_UPDATED */ GIT_COLOR_GREEN, /* WT_STATUS_UPDATED */
GIT_COLOR_RED, /* WT_STATUS_CHANGED */ GIT_COLOR_RED, /* WT_STATUS_CHANGED */
GIT_COLOR_RED, /* WT_STATUS_UNTRACKED */ GIT_COLOR_RED, /* WT_STATUS_UNTRACKED */
GIT_COLOR_RED, /* WT_STATUS_NOBRANCH */ GIT_COLOR_RED, /* WT_STATUS_NOBRANCH */
GIT_COLOR_RED, /* WT_STATUS_UNMERGED */
}; };
enum untracked_status_type show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; static const char *color(int slot, struct wt_status *s)
static int parse_status_slot(const char *var, int offset)
{ {
if (!strcasecmp(var+offset, "header")) return s->use_color > 0 ? s->color_palette[slot] : "";
return WT_STATUS_HEADER;
if (!strcasecmp(var+offset, "updated")
|| !strcasecmp(var+offset, "added"))
return WT_STATUS_UPDATED;
if (!strcasecmp(var+offset, "changed"))
return WT_STATUS_CHANGED;
if (!strcasecmp(var+offset, "untracked"))
return WT_STATUS_UNTRACKED;
if (!strcasecmp(var+offset, "nobranch"))
return WT_STATUS_NOBRANCH;
die("bad config variable '%s'", var);
}
static const char *color(int slot)
{
return wt_status_use_color > 0 ? wt_status_colors[slot] : "";
} }
void wt_status_prepare(struct wt_status *s) void wt_status_prepare(struct wt_status *s)
@ -51,16 +30,35 @@ void wt_status_prepare(struct wt_status *s)
const char *head; const char *head;
memset(s, 0, sizeof(*s)); memset(s, 0, sizeof(*s));
memcpy(s->color_palette, default_wt_status_colors,
sizeof(default_wt_status_colors));
s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
s->use_color = -1;
s->relative_paths = 1;
head = resolve_ref("HEAD", sha1, 0, NULL); head = resolve_ref("HEAD", sha1, 0, NULL);
s->branch = head ? xstrdup(head) : NULL; s->branch = head ? xstrdup(head) : NULL;
s->reference = "HEAD"; s->reference = "HEAD";
s->fp = stdout; s->fp = stdout;
s->index_file = get_index_file(); s->index_file = get_index_file();
s->change.strdup_strings = 1;
s->untracked.strdup_strings = 1;
}
static void wt_status_print_unmerged_header(struct wt_status *s)
{
const char *c = color(WT_STATUS_HEADER, s);
color_fprintf_ln(s->fp, c, "# Unmerged paths:");
if (!s->is_initial)
color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference);
else
color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)");
color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to mark resolution)");
color_fprintf_ln(s->fp, c, "#");
} }
static void wt_status_print_cached_header(struct wt_status *s) static void wt_status_print_cached_header(struct wt_status *s)
{ {
const char *c = color(WT_STATUS_HEADER); const char *c = color(WT_STATUS_HEADER, s);
color_fprintf_ln(s->fp, c, "# Changes to be committed:"); color_fprintf_ln(s->fp, c, "# Changes to be committed:");
if (!s->is_initial) { if (!s->is_initial) {
color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference);
@ -73,7 +71,7 @@ static void wt_status_print_cached_header(struct wt_status *s)
static void wt_status_print_dirty_header(struct wt_status *s, static void wt_status_print_dirty_header(struct wt_status *s,
int has_deleted) int has_deleted)
{ {
const char *c = color(WT_STATUS_HEADER); const char *c = color(WT_STATUS_HEADER, s);
color_fprintf_ln(s->fp, c, "# Changed but not updated:"); color_fprintf_ln(s->fp, c, "# Changed but not updated:");
if (!has_deleted) if (!has_deleted)
color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to update what will be committed)"); color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to update what will be committed)");
@ -85,7 +83,7 @@ static void wt_status_print_dirty_header(struct wt_status *s,
static void wt_status_print_untracked_header(struct wt_status *s) static void wt_status_print_untracked_header(struct wt_status *s)
{ {
const char *c = color(WT_STATUS_HEADER); const char *c = color(WT_STATUS_HEADER, s);
color_fprintf_ln(s->fp, c, "# Untracked files:"); color_fprintf_ln(s->fp, c, "# Untracked files:");
color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to include in what will be committed)"); color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to include in what will be committed)");
color_fprintf_ln(s->fp, c, "#"); color_fprintf_ln(s->fp, c, "#");
@ -93,23 +91,63 @@ static void wt_status_print_untracked_header(struct wt_status *s)
static void wt_status_print_trailer(struct wt_status *s) static void wt_status_print_trailer(struct wt_status *s)
{ {
color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
} }
#define quote_path quote_path_relative #define quote_path quote_path_relative
static void wt_status_print_filepair(struct wt_status *s, static void wt_status_print_unmerged_data(struct wt_status *s,
int t, struct diff_filepair *p) struct string_list_item *it)
{ {
const char *c = color(t); const char *c = color(WT_STATUS_UNMERGED, s);
struct wt_status_change_data *d = it->util;
struct strbuf onebuf = STRBUF_INIT;
const char *one, *how = "bug";
one = quote_path(it->string, -1, &onebuf, s->prefix);
color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
switch (d->stagemask) {
case 1: how = "both deleted:"; break;
case 2: how = "added by us:"; break;
case 3: how = "deleted by them:"; break;
case 4: how = "added by them:"; break;
case 5: how = "deleted by us:"; break;
case 6: how = "both added:"; break;
case 7: how = "both modified:"; break;
}
color_fprintf(s->fp, c, "%-20s%s\n", how, one);
strbuf_release(&onebuf);
}
static void wt_status_print_change_data(struct wt_status *s,
int change_type,
struct string_list_item *it)
{
struct wt_status_change_data *d = it->util;
const char *c = color(change_type, s);
int status = status;
char *one_name;
char *two_name;
const char *one, *two; const char *one, *two;
struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT; struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT;
one = quote_path(p->one->path, -1, &onebuf, s->prefix); one_name = two_name = it->string;
two = quote_path(p->two->path, -1, &twobuf, s->prefix); switch (change_type) {
case WT_STATUS_UPDATED:
status = d->index_status;
if (d->head_path)
one_name = d->head_path;
break;
case WT_STATUS_CHANGED:
status = d->worktree_status;
break;
}
color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); one = quote_path(one_name, -1, &onebuf, s->prefix);
switch (p->status) { two = quote_path(two_name, -1, &twobuf, s->prefix);
color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
switch (status) {
case DIFF_STATUS_ADDED: case DIFF_STATUS_ADDED:
color_fprintf(s->fp, c, "new file: %s", one); color_fprintf(s->fp, c, "new file: %s", one);
break; break;
@ -135,64 +173,114 @@ static void wt_status_print_filepair(struct wt_status *s,
color_fprintf(s->fp, c, "unmerged: %s", one); color_fprintf(s->fp, c, "unmerged: %s", one);
break; break;
default: default:
die("bug: unhandled diff status %c", p->status); die("bug: unhandled diff status %c", status);
} }
fprintf(s->fp, "\n"); fprintf(s->fp, "\n");
strbuf_release(&onebuf); strbuf_release(&onebuf);
strbuf_release(&twobuf); strbuf_release(&twobuf);
} }
static void wt_status_print_updated_cb(struct diff_queue_struct *q, static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
struct diff_options *options, struct diff_options *options,
void *data) void *data)
{ {
struct wt_status *s = data; struct wt_status *s = data;
int shown_header = 0;
int i; int i;
if (!q->nr)
return;
s->workdir_dirty = 1;
for (i = 0; i < q->nr; i++) { for (i = 0; i < q->nr; i++) {
if (q->queue[i]->status == 'U') struct diff_filepair *p;
continue; struct string_list_item *it;
if (!shown_header) { struct wt_status_change_data *d;
wt_status_print_cached_header(s);
s->commitable = 1; p = q->queue[i];
shown_header = 1; it = string_list_insert(p->one->path, &s->change);
d = it->util;
if (!d) {
d = xcalloc(1, sizeof(*d));
it->util = d;
} }
wt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]); if (!d->worktree_status)
d->worktree_status = p->status;
} }
if (shown_header)
wt_status_print_trailer(s);
} }
static void wt_status_print_changed_cb(struct diff_queue_struct *q, static int unmerged_mask(const char *path)
struct diff_options *options, {
void *data) int pos, mask;
struct cache_entry *ce;
pos = cache_name_pos(path, strlen(path));
if (0 <= pos)
return 0;
mask = 0;
pos = -pos-1;
while (pos < active_nr) {
ce = active_cache[pos++];
if (strcmp(ce->name, path) || !ce_stage(ce))
break;
mask |= (1 << (ce_stage(ce) - 1));
}
return mask;
}
static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
struct diff_options *options,
void *data)
{ {
struct wt_status *s = data; struct wt_status *s = data;
int i; int i;
if (q->nr) {
int has_deleted = 0; for (i = 0; i < q->nr; i++) {
s->workdir_dirty = 1; struct diff_filepair *p;
for (i = 0; i < q->nr; i++) struct string_list_item *it;
if (q->queue[i]->status == DIFF_STATUS_DELETED) { struct wt_status_change_data *d;
has_deleted = 1;
break; p = q->queue[i];
} it = string_list_insert(p->two->path, &s->change);
wt_status_print_dirty_header(s, has_deleted); d = it->util;
if (!d) {
d = xcalloc(1, sizeof(*d));
it->util = d;
}
if (!d->index_status)
d->index_status = p->status;
switch (p->status) {
case DIFF_STATUS_COPIED:
case DIFF_STATUS_RENAMED:
d->head_path = xstrdup(p->one->path);
break;
case DIFF_STATUS_UNMERGED:
d->stagemask = unmerged_mask(p->two->path);
break;
}
} }
for (i = 0; i < q->nr; i++)
wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]);
if (q->nr)
wt_status_print_trailer(s);
} }
static void wt_status_print_updated(struct wt_status *s) static void wt_status_collect_changes_worktree(struct wt_status *s)
{ {
struct rev_info rev; struct rev_info rev;
init_revisions(&rev, NULL);
setup_revisions(0, NULL, &rev, NULL);
rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = wt_status_collect_changed_cb;
rev.diffopt.format_callback_data = s;
run_diff_files(&rev, 0);
}
static void wt_status_collect_changes_index(struct wt_status *s)
{
struct rev_info rev;
init_revisions(&rev, NULL); init_revisions(&rev, NULL);
setup_revisions(0, NULL, &rev, setup_revisions(0, NULL, &rev,
s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference); s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference);
rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = wt_status_print_updated_cb; rev.diffopt.format_callback = wt_status_collect_updated_cb;
rev.diffopt.format_callback_data = s; rev.diffopt.format_callback_data = s;
rev.diffopt.detect_rename = 1; rev.diffopt.detect_rename = 1;
rev.diffopt.rename_limit = 200; rev.diffopt.rename_limit = 200;
@ -200,15 +288,155 @@ static void wt_status_print_updated(struct wt_status *s)
run_diff_index(&rev, 1); run_diff_index(&rev, 1);
} }
static void wt_status_collect_changes_initial(struct wt_status *s)
{
int i;
for (i = 0; i < active_nr; i++) {
struct string_list_item *it;
struct wt_status_change_data *d;
struct cache_entry *ce = active_cache[i];
it = string_list_insert(ce->name, &s->change);
d = it->util;
if (!d) {
d = xcalloc(1, sizeof(*d));
it->util = d;
}
if (ce_stage(ce)) {
d->index_status = DIFF_STATUS_UNMERGED;
d->stagemask |= (1 << (ce_stage(ce) - 1));
}
else
d->index_status = DIFF_STATUS_ADDED;
}
}
static void wt_status_collect_untracked(struct wt_status *s)
{
int i;
struct dir_struct dir;
if (!s->show_untracked_files)
return;
memset(&dir, 0, sizeof(dir));
if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
dir.flags |=
DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
setup_standard_excludes(&dir);
fill_directory(&dir, NULL);
for(i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
if (!cache_name_is_other(ent->name, ent->len))
continue;
s->workdir_untracked = 1;
string_list_insert(ent->name, &s->untracked);
}
}
void wt_status_collect(struct wt_status *s)
{
wt_status_collect_changes_worktree(s);
if (s->is_initial)
wt_status_collect_changes_initial(s);
else
wt_status_collect_changes_index(s);
wt_status_collect_untracked(s);
}
static void wt_status_print_unmerged(struct wt_status *s)
{
int shown_header = 0;
int i;
for (i = 0; i < s->change.nr; i++) {
struct wt_status_change_data *d;
struct string_list_item *it;
it = &(s->change.items[i]);
d = it->util;
if (!d->stagemask)
continue;
if (!shown_header) {
wt_status_print_unmerged_header(s);
shown_header = 1;
}
wt_status_print_unmerged_data(s, it);
}
if (shown_header)
wt_status_print_trailer(s);
}
static void wt_status_print_updated(struct wt_status *s)
{
int shown_header = 0;
int i;
for (i = 0; i < s->change.nr; i++) {
struct wt_status_change_data *d;
struct string_list_item *it;
it = &(s->change.items[i]);
d = it->util;
if (!d->index_status ||
d->index_status == DIFF_STATUS_UNMERGED)
continue;
if (!shown_header) {
wt_status_print_cached_header(s);
s->commitable = 1;
shown_header = 1;
}
wt_status_print_change_data(s, WT_STATUS_UPDATED, it);
}
if (shown_header)
wt_status_print_trailer(s);
}
/*
* -1 : has delete
* 0 : no change
* 1 : some change but no delete
*/
static int wt_status_check_worktree_changes(struct wt_status *s)
{
int i;
int changes = 0;
for (i = 0; i < s->change.nr; i++) {
struct wt_status_change_data *d;
d = s->change.items[i].util;
if (!d->worktree_status ||
d->worktree_status == DIFF_STATUS_UNMERGED)
continue;
changes = 1;
if (d->worktree_status == DIFF_STATUS_DELETED)
return -1;
}
return changes;
}
static void wt_status_print_changed(struct wt_status *s) static void wt_status_print_changed(struct wt_status *s)
{ {
struct rev_info rev; int i;
init_revisions(&rev, ""); int worktree_changes = wt_status_check_worktree_changes(s);
setup_revisions(0, NULL, &rev, NULL);
rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; if (!worktree_changes)
rev.diffopt.format_callback = wt_status_print_changed_cb; return;
rev.diffopt.format_callback_data = s;
run_diff_files(&rev, 0); wt_status_print_dirty_header(s, worktree_changes < 0);
for (i = 0; i < s->change.nr; i++) {
struct wt_status_change_data *d;
struct string_list_item *it;
it = &(s->change.items[i]);
d = it->util;
if (!d->worktree_status ||
d->worktree_status == DIFF_STATUS_UNMERGED)
continue;
wt_status_print_change_data(s, WT_STATUS_CHANGED, it);
}
wt_status_print_trailer(s);
} }
static void wt_status_print_submodule_summary(struct wt_status *s) static void wt_status_print_submodule_summary(struct wt_status *s)
@ -228,7 +456,7 @@ static void wt_status_print_submodule_summary(struct wt_status *s)
NULL NULL
}; };
sprintf(summary_limit, "%d", wt_status_submodule_summary); sprintf(summary_limit, "%d", s->submodule_summary);
snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file); snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file);
memset(&sm_summary, 0, sizeof(sm_summary)); memset(&sm_summary, 0, sizeof(sm_summary));
@ -243,32 +471,20 @@ static void wt_status_print_submodule_summary(struct wt_status *s)
static void wt_status_print_untracked(struct wt_status *s) static void wt_status_print_untracked(struct wt_status *s)
{ {
struct dir_struct dir;
int i; int i;
int shown_header = 0;
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;
memset(&dir, 0, sizeof(dir)); if (!s->untracked.nr)
return;
if (!s->untracked) wt_status_print_untracked_header(s);
dir.flags |= for (i = 0; i < s->untracked.nr; i++) {
DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; struct string_list_item *it;
setup_standard_excludes(&dir); it = &(s->untracked.items[i]);
color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
fill_directory(&dir, NULL); color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), "%s",
for(i = 0; i < dir.nr; i++) { quote_path(it->string, strlen(it->string),
struct dir_entry *ent = dir.entries[i]; &buf, s->prefix));
if (!cache_name_is_other(ent->name, ent->len))
continue;
if (!shown_header) {
s->workdir_untracked = 1;
wt_status_print_untracked_header(s);
shown_header = 1;
}
color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%s",
quote_path(ent->name, ent->len,
&buf, s->prefix));
} }
strbuf_release(&buf); strbuf_release(&buf);
} }
@ -310,15 +526,15 @@ static void wt_status_print_tracking(struct wt_status *s)
return; return;
for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1) for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
"# %.*s", (int)(ep - cp), cp); "# %.*s", (int)(ep - cp), cp);
color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
} }
void wt_status_print(struct wt_status *s) void wt_status_print(struct wt_status *s)
{ {
unsigned char sha1[20]; unsigned char sha1[20];
const char *branch_color = color(WT_STATUS_HEADER); const char *branch_color = color(WT_STATUS_HEADER, s);
s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
if (s->branch) { if (s->branch) {
@ -328,26 +544,29 @@ void wt_status_print(struct wt_status *s)
branch_name += 11; branch_name += 11;
else if (!strcmp(branch_name, "HEAD")) { else if (!strcmp(branch_name, "HEAD")) {
branch_name = ""; branch_name = "";
branch_color = color(WT_STATUS_NOBRANCH); branch_color = color(WT_STATUS_NOBRANCH, s);
on_what = "Not currently on any branch."; on_what = "Not currently on any branch.";
} }
color_fprintf(s->fp, color(WT_STATUS_HEADER), "# "); color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name); color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
if (!s->is_initial) if (!s->is_initial)
wt_status_print_tracking(s); wt_status_print_tracking(s);
} }
wt_status_collect(s);
if (s->is_initial) { if (s->is_initial) {
color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# Initial commit"); color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
} }
wt_status_print_unmerged(s);
wt_status_print_updated(s); wt_status_print_updated(s);
wt_status_print_changed(s); wt_status_print_changed(s);
if (wt_status_submodule_summary) if (s->submodule_summary)
wt_status_print_submodule_summary(s); wt_status_print_submodule_summary(s);
if (show_untracked_files) if (s->show_untracked_files)
wt_status_print_untracked(s); wt_status_print_untracked(s);
else if (s->commitable) else if (s->commitable)
fprintf(s->fp, "# Untracked files not listed (use -u option to show untracked files)\n"); fprintf(s->fp, "# Untracked files not listed (use -u option to show untracked files)\n");
@ -361,53 +580,13 @@ void wt_status_print(struct wt_status *s)
; /* nothing */ ; /* nothing */
else if (s->workdir_dirty) else if (s->workdir_dirty)
printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
else if (s->workdir_untracked) else if (s->untracked.nr)
printf("nothing added to commit but untracked files present (use \"git add\" to track)\n"); printf("nothing added to commit but untracked files present (use \"git add\" to track)\n");
else if (s->is_initial) else if (s->is_initial)
printf("nothing to commit (create/copy files and use \"git add\" to track)\n"); printf("nothing to commit (create/copy files and use \"git add\" to track)\n");
else if (!show_untracked_files) else if (!s->show_untracked_files)
printf("nothing to commit (use -u to show untracked files)\n"); printf("nothing to commit (use -u to show untracked files)\n");
else else
printf("nothing to commit (working directory clean)\n"); printf("nothing to commit (working directory clean)\n");
} }
} }
int git_status_config(const char *k, const char *v, void *cb)
{
if (!strcmp(k, "status.submodulesummary")) {
int is_bool;
wt_status_submodule_summary = git_config_bool_or_int(k, v, &is_bool);
if (is_bool && wt_status_submodule_summary)
wt_status_submodule_summary = -1;
return 0;
}
if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
wt_status_use_color = git_config_colorbool(k, v, -1);
return 0;
}
if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
int slot = parse_status_slot(k, 13);
if (!v)
return config_error_nonbool(k);
color_parse(v, k, wt_status_colors[slot]);
return 0;
}
if (!strcmp(k, "status.relativepaths")) {
wt_status_relative_paths = git_config_bool(k, v);
return 0;
}
if (!strcmp(k, "status.showuntrackedfiles")) {
if (!v)
return config_error_nonbool(k);
else if (!strcmp(v, "no"))
show_untracked_files = SHOW_NO_UNTRACKED_FILES;
else if (!strcmp(v, "normal"))
show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
else if (!strcmp(v, "all"))
show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
else
return error("Invalid untracked files mode '%s'", v);
return 0;
}
return git_diff_ui_config(k, v, cb);
}

View File

@ -2,13 +2,16 @@
#define STATUS_H #define STATUS_H
#include <stdio.h> #include <stdio.h>
#include "string-list.h"
#include "color.h"
enum color_wt_status { enum color_wt_status {
WT_STATUS_HEADER, WT_STATUS_HEADER = 0,
WT_STATUS_UPDATED, WT_STATUS_UPDATED,
WT_STATUS_CHANGED, WT_STATUS_CHANGED,
WT_STATUS_UNTRACKED, WT_STATUS_UNTRACKED,
WT_STATUS_NOBRANCH, WT_STATUS_NOBRANCH,
WT_STATUS_UNMERGED,
}; };
enum untracked_status_type { enum untracked_status_type {
@ -16,7 +19,13 @@ enum untracked_status_type {
SHOW_NORMAL_UNTRACKED_FILES, SHOW_NORMAL_UNTRACKED_FILES,
SHOW_ALL_UNTRACKED_FILES SHOW_ALL_UNTRACKED_FILES
}; };
extern enum untracked_status_type show_untracked_files;
struct wt_status_change_data {
int worktree_status;
int index_status;
int stagemask;
char *head_path;
};
struct wt_status { struct wt_status {
int is_initial; int is_initial;
@ -24,8 +33,13 @@ struct wt_status {
const char *reference; const char *reference;
int verbose; int verbose;
int amend; int amend;
int untracked;
int nowarn; int nowarn;
int use_color;
int relative_paths;
int submodule_summary;
enum untracked_status_type show_untracked_files;
char color_palette[WT_STATUS_UNMERGED+1][COLOR_MAXLEN];
/* These are computed during processing of the individual sections */ /* These are computed during processing of the individual sections */
int commitable; int commitable;
int workdir_dirty; int workdir_dirty;
@ -33,12 +47,12 @@ struct wt_status {
const char *index_file; const char *index_file;
FILE *fp; FILE *fp;
const char *prefix; const char *prefix;
struct string_list change;
struct string_list untracked;
}; };
int git_status_config(const char *var, const char *value, void *cb);
extern int wt_status_use_color;
extern int wt_status_relative_paths;
void wt_status_prepare(struct wt_status *s); void wt_status_prepare(struct wt_status *s);
void wt_status_print(struct wt_status *s); void wt_status_print(struct wt_status *s);
void wt_status_collect(struct wt_status *s);
#endif /* STATUS_H */ #endif /* STATUS_H */