X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/33d43e78b3c214adc9178b1c3b6e02311247da79..251acae219225cb1b6e5cfef73e1720b6dd2d528:/tig.c diff --git a/tig.c b/tig.c index 39d63e7..c41f2b2 100644 --- a/tig.c +++ b/tig.c @@ -103,6 +103,8 @@ static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bo #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3) +#define NULL_ID "0000000000000000000000000000000000000000" + #ifndef GIT_CONFIG #define GIT_CONFIG "git config" #endif @@ -141,9 +143,11 @@ static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bo struct ref { char *name; /* Ref name; tag or head names are shortened. */ char id[SIZEOF_REV]; /* Commit SHA1 ID */ + unsigned int head:1; /* Is it the current HEAD? */ unsigned int tag:1; /* Is it a tag? */ unsigned int ltag:1; /* If so, is the tag local? */ unsigned int remote:1; /* Is it a remote ref? */ + unsigned int tracked:1; /* Is it the remote for the current HEAD? */ unsigned int next:1; /* For ref lists: are there more refs? */ }; @@ -328,6 +332,7 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src) REQ_(PREVIOUS, "Move to previous"), \ REQ_(VIEW_NEXT, "Move focus to next view"), \ REQ_(REFRESH, "Reload and refresh"), \ + REQ_(MAXIMIZE, "Maximize the current view"), \ REQ_(VIEW_CLOSE, "Close the current view"), \ REQ_(QUIT, "Close all views and quit"), \ \ @@ -442,6 +447,9 @@ static char opt_cmd[SIZEOF_STR] = ""; static char opt_path[SIZEOF_STR] = ""; static char opt_file[SIZEOF_STR] = ""; static char opt_ref[SIZEOF_REF] = ""; +static char opt_head[SIZEOF_REF] = ""; +static char opt_remote[SIZEOF_REF] = ""; +static bool opt_no_head = TRUE; static FILE *opt_pipe = NULL; static char opt_encoding[20] = "UTF-8"; static bool opt_utf8 = TRUE; @@ -461,6 +469,12 @@ parse_options(int argc, char *argv[]) bool seen_dashdash = FALSE; int i; + if (!isatty(STDIN_FILENO)) { + opt_request = REQ_VIEW_PAGER; + opt_pipe = stdin; + return TRUE; + } + if (argc <= 1) return TRUE; @@ -519,7 +533,7 @@ parse_options(int argc, char *argv[]) return FALSE; } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) { - printf(usage); + printf("%s\n", usage); return FALSE; } @@ -529,12 +543,6 @@ parse_options(int argc, char *argv[]) die("command too long"); } - if (!isatty(STDIN_FILENO)) { - opt_request = REQ_VIEW_PAGER; - opt_pipe = stdin; - buf_size = 0; - } - opt_cmd[buf_size] = 0; return TRUE; @@ -577,19 +585,22 @@ LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \ LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \ LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \ LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \ +LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \ LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \ LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \ LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \ LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \ -LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \ -LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \ -LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \ -LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \ +LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ +LINE(MAIN_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \ +LINE(MAIN_TRACKED, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \ +LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \ +LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \ LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \ LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \ +LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \ LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \ LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \ LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \ @@ -646,8 +657,9 @@ get_line_attr(enum line_type type) } static struct line_info * -get_line_info(char *name, int namelen) +get_line_info(char *name) { + size_t namelen = strlen(name); enum line_type type; for (type = 0; type < ARRAY_SIZE(line_info); type++) @@ -722,6 +734,7 @@ static struct keybinding default_keybindings[] = { { KEY_UP, REQ_PREVIOUS }, { KEY_DOWN, REQ_NEXT }, { 'R', REQ_REFRESH }, + { 'M', REQ_MAXIMIZE }, /* Cursor navigation */ { 'k', REQ_MOVE_UP }, @@ -1043,10 +1056,15 @@ option_color_command(int argc, char *argv[]) return ERR; } - info = get_line_info(argv[0], strlen(argv[0])); + info = get_line_info(argv[0]); if (!info) { - config_msg = "Unknown color name"; - return ERR; + if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) { + info = get_line_info("delimiter"); + + } else { + config_msg = "Unknown color name"; + return ERR; + } } if (set_color(&info->fg, argv[1]) == ERR || @@ -1344,6 +1362,7 @@ struct view { struct view_ops *ops; /* View operations */ enum keymap keymap; /* What keymap does this view have */ + bool git_dir; /* Whether the view requires a git directory. */ char cmd[SIZEOF_STR]; /* Command buffer */ char ref[SIZEOF_REF]; /* Hovered commit reference */ @@ -1403,27 +1422,28 @@ static struct view_ops help_ops; static struct view_ops status_ops; static struct view_ops stage_ops; -#define VIEW_STR(name, cmd, env, ref, ops, map) \ - { name, cmd, #env, ref, ops, map} +#define VIEW_STR(name, cmd, env, ref, ops, map, git) \ + { name, cmd, #env, ref, ops, map, git } -#define VIEW_(id, name, ops, ref) \ - VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id) +#define VIEW_(id, name, ops, git, ref) \ + VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git) static struct view views[] = { - VIEW_(MAIN, "main", &main_ops, ref_head), - VIEW_(DIFF, "diff", &pager_ops, ref_commit), - VIEW_(LOG, "log", &pager_ops, ref_head), - VIEW_(TREE, "tree", &tree_ops, ref_commit), - VIEW_(BLOB, "blob", &blob_ops, ref_blob), - VIEW_(BLAME, "blame", &blame_ops, ref_commit), - VIEW_(HELP, "help", &help_ops, ""), - VIEW_(PAGER, "pager", &pager_ops, "stdin"), - VIEW_(STATUS, "status", &status_ops, ""), - VIEW_(STAGE, "stage", &stage_ops, ""), + VIEW_(MAIN, "main", &main_ops, TRUE, ref_head), + VIEW_(DIFF, "diff", &pager_ops, TRUE, ref_commit), + VIEW_(LOG, "log", &pager_ops, TRUE, ref_head), + VIEW_(TREE, "tree", &tree_ops, TRUE, ref_commit), + VIEW_(BLOB, "blob", &blob_ops, TRUE, ref_blob), + VIEW_(BLAME, "blame", &blame_ops, TRUE, ref_commit), + VIEW_(HELP, "help", &help_ops, FALSE, ""), + VIEW_(PAGER, "pager", &pager_ops, FALSE, "stdin"), + VIEW_(STATUS, "status", &status_ops, TRUE, ""), + VIEW_(STAGE, "stage", &stage_ops, TRUE, ""), }; -#define VIEW(req) (&views[(req) - REQ_OFFSET - 1]) +#define VIEW(req) (&views[(req) - REQ_OFFSET - 1]) +#define VIEW_REQ(view) ((view) - views + REQ_OFFSET + 1) #define foreach_view(view, i) \ for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++) @@ -1433,7 +1453,7 @@ static struct view views[] = { static int draw_text(struct view *view, const char *string, int max_len, - bool use_tilde, int tilde_attr) + bool use_tilde, bool selected) { int len = 0; int trimmed = FALSE; @@ -1456,8 +1476,8 @@ draw_text(struct view *view, const char *string, int max_len, waddnstr(view->win, string, len); if (trimmed && use_tilde) { - if (tilde_attr != -1) - wattrset(view->win, tilde_attr); + if (!selected) + wattrset(view->win, get_line_attr(LINE_DELIMITER)); waddch(view->win, '~'); len++; } @@ -1465,6 +1485,43 @@ draw_text(struct view *view, const char *string, int max_len, return len; } +static int +draw_lineno(struct view *view, unsigned int lineno, int max, bool selected) +{ + static char fmt[] = "%1ld"; + char number[10] = " "; + int max_number = MIN(view->digits, STRING_SIZE(number)); + bool showtrimmed = FALSE; + int col; + + lineno += view->offset + 1; + if (lineno == 1 || (lineno % opt_num_interval) == 0) { + if (view->digits <= 9) + fmt[1] = '0' + view->digits; + + if (!string_format(number, fmt, lineno)) + number[0] = 0; + showtrimmed = TRUE; + } + + if (max < max_number) + max_number = max; + + col = draw_text(view, number, max_number, showtrimmed, selected); + if (col < max) { + if (!selected) + wattrset(view->win, A_NORMAL); + waddch(view->win, ACS_VLINE); + col++; + } + if (col < max) { + waddch(view->win, ' '); + col++; + } + + return col; +} + static bool draw_view_line(struct view *view, unsigned int lineno) { @@ -2172,7 +2229,6 @@ update_view(struct view *view) * might have rearranged things. */ redraw_view(view); - } else if (redraw_from >= 0) { /* If this is an incremental update, redraw the previous line * since for commits some members could have changed when @@ -2266,15 +2322,8 @@ open_view(struct view *prev, enum request request, enum open_flags flags) return; } - if (view->ops->open) { - if (!view->ops->open(view)) { - report("Failed to load %s view", view->name); - return; - } - - } else if ((reload || strcmp(view->vid, view->id)) && - !begin_update(view)) { - report("Failed to load %s view", view->name); + if (view->git_dir && !opt_git_dir[0]) { + report("The %s view is disabled in pager view", view->name); return; } @@ -2295,6 +2344,18 @@ open_view(struct view *prev, enum request request, enum open_flags flags) (nviews == 1 && base_view != display[0])) resize_display(); + if (view->ops->open) { + if (!view->ops->open(view)) { + report("Failed to load %s view", view->name); + return; + } + + } else if ((reload || strcmp(view->vid, view->id)) && + !begin_update(view)) { + report("Failed to load %s view", view->name); + return; + } + if (split && prev->lineno - prev->offset >= prev->height) { /* Take the title line into account. */ int lines = prev->lineno - prev->offset - prev->height + 1; @@ -2569,6 +2630,11 @@ view_driver(struct view *view, enum request request) report("Refreshing is not yet supported for the %s view", view->name); break; + case REQ_MAXIMIZE: + if (displayed_views() == 2) + open_view(view, VIEW_REQ(view), OPEN_DEFAULT); + break; + case REQ_TOGGLE_LINENO: opt_line_number = !opt_line_number; redraw_display(); @@ -2674,39 +2740,35 @@ view_driver(struct view *view, enum request request) static bool pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selected) { + static char spaces[] = " "; char *text = line->data; enum line_type type = line->type; - int attr; + int attr = A_NORMAL; + int col = 0; wmove(view->win, lineno, 0); if (selected) { type = LINE_CURSOR; wchgat(view->win, -1, 0, type, NULL); + attr = get_line_attr(type); } - - attr = get_line_attr(type); wattrset(view->win, attr); - if (opt_line_number || opt_tab_size < TABSIZE) { - static char spaces[] = " "; - int col_offset = 0, col = 0; - - if (opt_line_number) { - unsigned long real_lineno = view->offset + lineno + 1; - - if (real_lineno == 1 || - (real_lineno % opt_num_interval) == 0) { - wprintw(view->win, "%.*d", view->digits, real_lineno); + if (opt_line_number) { + col += draw_lineno(view, lineno, view->width, selected); + if (col >= view->width) + return TRUE; + } - } else { - waddnstr(view->win, spaces, - MIN(view->digits, STRING_SIZE(spaces))); - } - waddstr(view->win, ": "); - col_offset = view->digits + 2; - } + if (!selected) { + attr = get_line_attr(type); + wattrset(view->win, attr); + } + if (opt_tab_size < TABSIZE) { + int col_offset = col; + col = 0; while (text && col_offset + col < view->width) { int cols_max = view->width - col_offset - col; char *pos = text; @@ -2728,9 +2790,7 @@ pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selec } } else { - int tilde_attr = get_line_attr(LINE_MAIN_DELIM); - - draw_text(view, text, view->width, TRUE, tilde_attr); + draw_text(view, text, view->width - col, TRUE, selected); } return TRUE; @@ -3159,8 +3219,8 @@ tree_request(struct view *view, enum request request, struct line *line) return REQ_NONE; } - string_copy(opt_ref, ref_commit); - string_ncopy(opt_file, filename, strlen(filename)); + string_copy(opt_ref, view->vid); + string_format(opt_file, "%s%s", opt_path, filename); return request; } if (request == REQ_TREE_PARENT) { @@ -3404,9 +3464,9 @@ parse_blame_commit(struct view *view, char *text, int *blamed) blame = line->data; blame->commit = commit; + blame->header = !group; line->dirty = 1; } - blame->header = 1; return commit; } @@ -3516,7 +3576,6 @@ blame_read(struct view *view, char *line) static bool blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selected) { - int tilde_attr = -1; struct blame *blame = line->data; int col = 0; @@ -3527,7 +3586,6 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selec wchgat(view->win, -1, 0, LINE_CURSOR, NULL); } else { wattrset(view->win, A_NORMAL); - tilde_attr = get_line_attr(LINE_MAIN_DELIM); } if (opt_date) { @@ -3540,8 +3598,8 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selec int timelen; timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &blame->commit->time); - n = draw_text(view, buf, view->width - col, FALSE, tilde_attr); - draw_text(view, " ", view->width - col - n, FALSE, tilde_attr); + n = draw_text(view, buf, view->width - col, FALSE, selected); + draw_text(view, " ", view->width - col - n, FALSE, selected); } col += DATE_COLS; @@ -3556,7 +3614,7 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selec if (!selected) wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR)); if (blame->commit) - draw_text(view, blame->commit->author, max, TRUE, tilde_attr); + draw_text(view, blame->commit->author, max, TRUE, selected); col += AUTHOR_COLS; if (col >= view->width) return TRUE; @@ -3577,44 +3635,14 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selec } { - unsigned long real_lineno = view->offset + lineno + 1; - char number[10] = " "; - int max = MIN(view->digits, STRING_SIZE(number)); - bool showtrimmed = FALSE; - - if (real_lineno == 1 || - (real_lineno % opt_num_interval) == 0) { - char fmt[] = "%1ld"; - - if (view->digits <= 9) - fmt[1] = '0' + view->digits; - - if (!string_format(number, fmt, real_lineno)) - number[0] = 0; - showtrimmed = TRUE; - } - - if (max > view->width - col) - max = view->width - col; if (!selected) wattrset(view->win, get_line_attr(LINE_BLAME_LINENO)); - col += draw_text(view, number, max, showtrimmed, tilde_attr); + col += draw_lineno(view, lineno, view->width - col, selected); if (col >= view->width) return TRUE; } - if (!selected) - wattrset(view->win, A_NORMAL); - - if (col >= view->width) - return TRUE; - waddch(view->win, ACS_VLINE); - col++; - if (col >= view->width) - return TRUE; - waddch(view->win, ' '); - col++; - col += draw_text(view, blame->text, view->width - col, TRUE, tilde_attr); + col += draw_text(view, blame->text, view->width - col, TRUE, selected); return TRUE; } @@ -3632,7 +3660,7 @@ blame_request(struct view *view, enum request request, struct line *line) break; } - if (!strcmp(blame->commit->id, "0000000000000000000000000000000000000000")) { + if (!strcmp(blame->commit->id, NULL_ID)) { char path[SIZEOF_STR]; if (sq_quote(path, 0, view->vid) >= sizeof(path)) @@ -3687,7 +3715,7 @@ blame_select(struct view *view, struct line *line) if (!commit) return; - if (!strcmp(commit->id, "0000000000000000000000000000000000000000")) + if (!strcmp(commit->id, NULL_ID)) string_ncopy(ref_commit, "HEAD", 4); else string_copy_rev(ref_commit, commit->id); @@ -3721,6 +3749,7 @@ struct status { } new; }; +static char status_onbranch[SIZEOF_STR]; static struct status stage_status; static enum line_type stage_line_type; @@ -3758,7 +3787,7 @@ status_get_diff(struct status *file, char *buf, size_t bufsize) } static bool -status_run(struct view *view, const char cmd[], bool diff, enum line_type type) +status_run(struct view *view, const char cmd[], char status, enum line_type type) { struct status *file = NULL; struct status *unmerged = NULL; @@ -3797,8 +3826,10 @@ status_run(struct view *view, const char cmd[], bool diff, enum line_type type) } /* Parse diff info part. */ - if (!diff) { - file->status = '?'; + if (status) { + file->status = status; + if (status == 'A') + string_copy(file->old.rev, NULL_ID); } else if (!file->status) { if (!status_get_diff(file, buf, sepsize)) @@ -3874,6 +3905,8 @@ error_out: #define STATUS_DIFF_FILES_CMD "git diff-files -z" #define STATUS_LIST_OTHER_CMD \ "git ls-files -z --others --exclude-per-directory=.gitignore" +#define STATUS_LIST_NO_HEAD_CMD \ + "git ls-files -z --cached --exclude-per-directory=.gitignore" #define STATUS_DIFF_INDEX_SHOW_CMD \ "git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null" @@ -3881,6 +3914,9 @@ error_out: #define STATUS_DIFF_FILES_SHOW_CMD \ "git diff-files --root --patch-with-stat -C -M -- %s %s 2>/dev/null" +#define STATUS_DIFF_NO_HEAD_SHOW_CMD \ + "git diff --no-color --patch-with-stat /dev/null %s 2>/dev/null" + /* First parse staged info using git-diff-index(1), then parse unstaged * info using git-diff-files(1), and finally untracked files using * git-ls-files(1). */ @@ -3889,8 +3925,10 @@ status_open(struct view *view) { struct stat statbuf; char exclude[SIZEOF_STR]; - char cmd[SIZEOF_STR]; + char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD; + char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD; unsigned long prev_lineno = view->lineno; + char indexstatus = 0; size_t i; for (i = 0; i < view->lines; i++) @@ -3899,35 +3937,66 @@ status_open(struct view *view) view->lines = view->line_alloc = view->line_size = view->lineno = 0; view->line = NULL; - if (!realloc_lines(view, view->line_size + 6)) + if (!realloc_lines(view, view->line_size + 7)) return FALSE; - if (!string_format(exclude, "%s/info/exclude", opt_git_dir)) + add_line_data(view, NULL, LINE_STAT_HEAD); + if (opt_no_head) + string_copy(status_onbranch, "Initial commit"); + else if (!*opt_head) + string_copy(status_onbranch, "Not currently on any branch"); + else if (!string_format(status_onbranch, "On branch %s", opt_head)) return FALSE; - string_copy(cmd, STATUS_LIST_OTHER_CMD); + if (opt_no_head) { + string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD); + indexstatus = 'A'; + } + + if (!string_format(exclude, "%s/info/exclude", opt_git_dir)) + return FALSE; if (stat(exclude, &statbuf) >= 0) { - size_t cmdsize = strlen(cmd); + size_t cmdsize = strlen(othercmd); + + if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") || + sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd)) + return FALSE; - if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") || - sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd)) + cmdsize = strlen(indexcmd); + if (opt_no_head && + (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") || + sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd))) return FALSE; } system("git update-index -q --refresh"); - if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) || - !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) || - !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED)) + if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) || + !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) || + !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED)) return FALSE; /* If all went well restore the previous line number to stay in - * the context. */ + * the context or select a line with something that can be + * updated. */ + if (prev_lineno >= view->lines) + prev_lineno = view->lines - 1; + while (prev_lineno < view->lines && !view->line[prev_lineno].data) + prev_lineno++; + while (prev_lineno > 0 && !view->line[prev_lineno].data) + prev_lineno--; + + /* If the above fails, always skip the "On branch" line. */ if (prev_lineno < view->lines) view->lineno = prev_lineno; else - view->lineno = view->lines - 1; + view->lineno = 1; + + if (view->lineno < view->offset) + view->offset = view->lineno; + else if (view->offset + view->height <= view->lineno) + view->offset = view->lineno - view->height + 1; return TRUE; } @@ -3936,14 +4005,18 @@ static bool status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected) { struct status *status = line->data; - int tilde_attr = get_line_attr(LINE_MAIN_DELIM); + char *text; + int col = 0; wmove(view->win, lineno, 0); if (selected) { wattrset(view->win, get_line_attr(LINE_CURSOR)); wchgat(view->win, -1, 0, LINE_CURSOR, NULL); - tilde_attr = -1; + + } else if (line->type == LINE_STAT_HEAD) { + wattrset(view->win, get_line_attr(LINE_STAT_HEAD)); + wchgat(view->win, -1, 0, LINE_STAT_HEAD, NULL); } else if (!status && line->type != LINE_STAT_NONE) { wattrset(view->win, get_line_attr(LINE_STAT_SECTION)); @@ -3954,8 +4027,6 @@ status_draw(struct view *view, struct line *line, unsigned int lineno, bool sele } if (!status) { - char *text; - switch (line->type) { case LINE_STAT_STAGED: text = "Changes to be committed:"; @@ -3973,22 +4044,23 @@ status_draw(struct view *view, struct line *line, unsigned int lineno, bool sele text = " (no files)"; break; + case LINE_STAT_HEAD: + text = status_onbranch; + break; + default: return FALSE; } + } else { + char buf[] = { status->status, ' ', ' ', ' ', 0 }; - draw_text(view, text, view->width, TRUE, tilde_attr); - return TRUE; + col += draw_text(view, buf, view->width, TRUE, selected); + if (!selected) + wattrset(view->win, A_NORMAL); + text = status->new.name; } - waddch(view->win, status->status); - if (!selected) - wattrset(view->win, A_NORMAL); - wmove(view->win, lineno, 4); - if (view->width < 5) - return TRUE; - - draw_text(view, status->new.name, view->width - 5, TRUE, tilde_attr); + draw_text(view, text, view->width - col, TRUE, selected); return TRUE; } @@ -4024,9 +4096,18 @@ status_enter(struct view *view, struct line *line) switch (line->type) { case LINE_STAT_STAGED: - if (!string_format_from(opt_cmd, &cmdsize, - STATUS_DIFF_INDEX_SHOW_CMD, oldpath, newpath)) - return REQ_QUIT; + if (opt_no_head) { + if (!string_format_from(opt_cmd, &cmdsize, + STATUS_DIFF_NO_HEAD_SHOW_CMD, + newpath)) + return REQ_QUIT; + } else { + if (!string_format_from(opt_cmd, &cmdsize, + STATUS_DIFF_INDEX_SHOW_CMD, + oldpath, newpath)) + return REQ_QUIT; + } + if (status) info = "Staged changes to %s"; else @@ -4047,7 +4128,6 @@ status_enter(struct view *view, struct line *line) if (opt_pipe) return REQ_QUIT; - if (!status) { report("No file to show"); return REQ_NONE; @@ -4057,6 +4137,9 @@ status_enter(struct view *view, struct line *line) info = "Untracked file %s"; break; + case LINE_STAT_HEAD: + return REQ_NONE; + default: die("line type %d not handled in switch", line->type); } @@ -4077,61 +4160,113 @@ status_enter(struct view *view, struct line *line) } -static bool -status_update_file(struct view *view, struct status *status, enum line_type type) +static FILE * +status_update_prepare(enum line_type type) { char cmd[SIZEOF_STR]; - char buf[SIZEOF_STR]; size_t cmdsize = 0; - size_t bufsize = 0; - size_t written = 0; - FILE *pipe; if (opt_cdup[0] && type != LINE_STAT_UNTRACKED && !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup)) - return FALSE; + return NULL; + + switch (type) { + case LINE_STAT_STAGED: + string_add(cmd, cmdsize, "git update-index -z --index-info"); + break; + + case LINE_STAT_UNSTAGED: + case LINE_STAT_UNTRACKED: + string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin"); + break; + + default: + die("line type %d not handled in switch", type); + } + + return popen(cmd, "w"); +} + +static bool +status_update_write(FILE *pipe, struct status *status, enum line_type type) +{ + char buf[SIZEOF_STR]; + size_t bufsize = 0; + size_t written = 0; switch (type) { case LINE_STAT_STAGED: if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c", - status->old.mode, + status->old.mode, status->old.rev, status->old.name, 0)) return FALSE; - - string_add(cmd, cmdsize, "git update-index -z --index-info"); break; case LINE_STAT_UNSTAGED: case LINE_STAT_UNTRACKED: if (!string_format_from(buf, &bufsize, "%s%c", status->new.name, 0)) return FALSE; - - string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin"); break; default: die("line type %d not handled in switch", type); } - pipe = popen(cmd, "w"); - if (!pipe) - return FALSE; - while (!ferror(pipe) && written < bufsize) { written += fwrite(buf + written, 1, bufsize - written, pipe); } + return written == bufsize; +} + +static bool +status_update_file(struct status *status, enum line_type type) +{ + FILE *pipe = status_update_prepare(type); + bool result; + + if (!pipe) + return FALSE; + + result = status_update_write(pipe, status, type); pclose(pipe); + return result; +} + +static bool +status_update_files(struct view *view, struct line *line) +{ + FILE *pipe = status_update_prepare(line->type); + bool result = TRUE; + struct line *pos = view->line + view->lines; + int files = 0; + int file, done; - if (written != bufsize) + if (!pipe) return FALSE; - return TRUE; + for (pos = line; pos < view->line + view->lines && pos->data; pos++) + files++; + + for (file = 0, done = 0; result && file < files; line++, file++) { + int almost_done = file * 100 / files; + + if (almost_done > done) { + done = almost_done; + string_format(view->ref, "updating file %u of %u (%d%% done)", + file, files, done); + update_view_title(view); + } + result = status_update_write(pipe, line->data, line->type); + } + + pclose(pipe); + return result; } -static void +static bool status_update(struct view *view) { struct line *line = &view->line[view->lineno]; @@ -4139,19 +4274,20 @@ status_update(struct view *view) assert(view->lines); if (!line->data) { - while (++line < view->line + view->lines && line->data) { - if (!status_update_file(view, line->data, line->type)) - report("Failed to update file status"); - } - - if (!line[-1].data) { + /* This should work even for the "On branch" line. */ + if (line < view->line + view->lines && !line[1].data) { report("Nothing to update"); - return; + return FALSE; } - } else if (!status_update_file(view, line->data, line->type)) { + if (!status_update_files(view, line + 1)) + report("Failed to update file status"); + + } else if (!status_update_file(line->data, line->type)) { report("Failed to update file status"); } + + return TRUE; } static enum request @@ -4161,7 +4297,8 @@ status_request(struct view *view, enum request request, struct line *line) switch (request) { case REQ_STATUS_UPDATE: - status_update(view); + if (!status_update(view)) + return REQ_NONE; break; case REQ_STATUS_MERGE: @@ -4233,6 +4370,7 @@ status_select(struct view *view, struct line *line) text = "Press %s to stage %s for addition"; break; + case LINE_STAT_HEAD: case LINE_STAT_NONE: text = "Nothing to update"; break; @@ -4349,7 +4487,7 @@ stage_update_chunk(struct view *view, struct line *line) return FALSE; if (!string_format_from(cmd, &cmdsize, - "git apply --cached %s - && " + "git apply --whitespace=nowarn --cached %s - && " "git update-index -q --unmerged --refresh 2>/dev/null", stage_line_type == LINE_STAT_STAGED ? "-R" : "")) return FALSE; @@ -4404,14 +4542,14 @@ stage_update_chunk(struct view *view, struct line *line) static void stage_update(struct view *view, struct line *line) { - if (stage_line_type != LINE_STAT_UNTRACKED && + if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED && (line->type == LINE_DIFF_CHUNK || !stage_status.status)) { if (!stage_update_chunk(view, line)) { report("Failed to apply chunk"); return; } - } else if (!status_update_file(view, &stage_status, stage_line_type)) { + } else if (!status_update_file(&stage_status, stage_line_type)) { report("Failed to update file"); return; } @@ -4685,7 +4823,6 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select enum line_type type; int col = 0; size_t timelen; - int tilde_attr; int space; if (!*commit->author) @@ -4698,19 +4835,17 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select type = LINE_CURSOR; wattrset(view->win, get_line_attr(type)); wchgat(view->win, -1, 0, type, NULL); - tilde_attr = -1; } else { type = LINE_MAIN_COMMIT; wattrset(view->win, get_line_attr(LINE_MAIN_DATE)); - tilde_attr = get_line_attr(LINE_MAIN_DELIM); } if (opt_date) { int n; timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time); - n = draw_text(view, buf, view->width - col, FALSE, tilde_attr); - draw_text(view, " ", view->width - col - n, FALSE, tilde_attr); + n = draw_text(view, buf, view->width - col, FALSE, selected); + draw_text(view, " ", view->width - col - n, FALSE, selected); col += DATE_COLS; wmove(view->win, lineno, col); @@ -4726,7 +4861,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select max_len = view->width - col; if (max_len > AUTHOR_COLS - 1) max_len = AUTHOR_COLS - 1; - draw_text(view, commit->author, max_len, TRUE, tilde_attr); + draw_text(view, commit->author, max_len, TRUE, selected); col += AUTHOR_COLS; if (col >= view->width) return TRUE; @@ -4762,22 +4897,26 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select do { if (type == LINE_CURSOR) ; + else if (commit->refs[i]->head) + wattrset(view->win, get_line_attr(LINE_MAIN_HEAD)); else if (commit->refs[i]->ltag) wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG)); else if (commit->refs[i]->tag) wattrset(view->win, get_line_attr(LINE_MAIN_TAG)); + else if (commit->refs[i]->tracked) + wattrset(view->win, get_line_attr(LINE_MAIN_TRACKED)); else if (commit->refs[i]->remote) wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE)); else wattrset(view->win, get_line_attr(LINE_MAIN_REF)); - col += draw_text(view, "[", view->width - col, TRUE, tilde_attr); + col += draw_text(view, "[", view->width - col, TRUE, selected); col += draw_text(view, commit->refs[i]->name, view->width - col, - TRUE, tilde_attr); - col += draw_text(view, "]", view->width - col, TRUE, tilde_attr); + TRUE, selected); + col += draw_text(view, "]", view->width - col, TRUE, selected); if (type != LINE_CURSOR) wattrset(view->win, A_NORMAL); - col += draw_text(view, " ", view->width - col, TRUE, tilde_attr); + col += draw_text(view, " ", view->width - col, TRUE, selected); if (col >= view->width) return TRUE; } while (commit->refs[i++]->next); @@ -4786,7 +4925,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select if (type != LINE_CURSOR) wattrset(view->win, get_line_attr(type)); - draw_text(view, commit->title, view->width - col, TRUE, tilde_attr); + draw_text(view, commit->title, view->width - col, TRUE, selected); return TRUE; } @@ -5373,7 +5512,9 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen) bool tag = FALSE; bool ltag = FALSE; bool remote = FALSE; + bool tracked = FALSE; bool check_replace = FALSE; + bool head = FALSE; if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) { if (!strcmp(name + namelen - 3, "^{}")) { @@ -5393,12 +5534,15 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen) remote = TRUE; namelen -= STRING_SIZE("refs/remotes/"); name += STRING_SIZE("refs/remotes/"); + tracked = !strcmp(opt_remote, name); } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) { namelen -= STRING_SIZE("refs/heads/"); name += STRING_SIZE("refs/heads/"); + head = !strncmp(opt_head, name, namelen); } else if (!strcmp(name, "HEAD")) { + opt_no_head = FALSE; return OK; } @@ -5423,9 +5567,11 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen) strncpy(ref->name, name, namelen); ref->name[namelen] = 0; + ref->head = head; ref->tag = tag; ref->ltag = ltag; ref->remote = remote; + ref->tracked = tracked; string_copy_rev(ref->id, id); return OK; @@ -5449,6 +5595,28 @@ read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen if (!strcmp(name, "core.editor")) string_ncopy(opt_editor, value, valuelen); + /* branch..remote */ + if (*opt_head && + !strncmp(name, "branch.", 7) && + !strncmp(name + 7, opt_head, strlen(opt_head)) && + !strcmp(name + 7 + strlen(opt_head), ".remote")) + string_ncopy(opt_remote, value, valuelen); + + if (*opt_head && *opt_remote && + !strncmp(name, "branch.", 7) && + !strncmp(name + 7, opt_head, strlen(opt_head)) && + !strcmp(name + 7 + strlen(opt_head), ".merge")) { + size_t from = strlen(opt_remote); + + if (!strncmp(value, "refs/heads/", STRING_SIZE("refs/heads/"))) { + value += STRING_SIZE("refs/heads/"); + valuelen -= STRING_SIZE("refs/heads/"); + } + + if (!string_format_from(opt_remote, &from, "/%s", value)) + opt_remote[0] = 0; + } + return OK; } @@ -5473,20 +5641,36 @@ read_repo_info(char *name, size_t namelen, char *value, size_t valuelen) * Default to true for the unknown case. */ opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE; - } else { + } else if (opt_cdup[0] == ' ') { string_ncopy(opt_cdup, name, namelen); + } else { + if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) { + namelen -= STRING_SIZE("refs/heads/"); + name += STRING_SIZE("refs/heads/"); + string_ncopy(opt_head, name, namelen); + } } return OK; } -/* XXX: The line outputted by "--show-cdup" can be empty so the option - * must be the last one! */ static int load_repo_info(void) { - return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"), - "=", read_repo_info); + int result; + FILE *pipe = popen("git rev-parse --git-dir --is-inside-work-tree " + " --show-cdup --symbolic-full-name HEAD 2>/dev/null", "r"); + + /* XXX: The line outputted by "--show-cdup" can be empty so + * initialize it to something invalid to make it possible to + * detect whether it has been set or not. */ + opt_cdup[0] = ' '; + + result = read_properties(pipe, "=", read_repo_info); + if (opt_cdup[0] == ' ') + opt_cdup[0] = 0; + + return result; } static int @@ -5601,7 +5785,7 @@ main(int argc, char *argv[]) return 0; /* Require a git repository unless when running in pager mode. */ - if (!opt_git_dir[0]) + if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER) die("Not a git repository"); if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))