X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=tig.c;h=54d99a721378cee897c49df99e728b61357dba55;hb=33e10c2599e16ff6690e7b0bbdd7a95e7f97c886;hp=9e76ea0c21afb2b3987c94677588685f752b7dd6;hpb=ffccf1b1f837812ee3d33bf0cef5f5036f858ed4;p=tig.git diff --git a/tig.c b/tig.c index 9e76ea0..54d99a7 100644 --- a/tig.c +++ b/tig.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2006-2009 Jonas Fonseca +/* Copyright (c) 2006-2010 Jonas Fonseca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -68,8 +68,8 @@ static void __NORETURN die(const char *err, ...); static void warn(const char *msg, ...); static void report(const char *msg, ...); -static void set_nonblocking_input(bool loading); -static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve); +static size_t utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size); +static inline unsigned char utf8_char_length(const char *string, const char *end); #define ABS(x) ((x) >= 0 ? (x) : -(x)) #define MIN(x, y) ((x) < (y) ? (x) : (y)) @@ -107,6 +107,7 @@ static size_t utf8_length(const char **string, size_t col, int *width, size_t ma #define DATE_SHORT_COLS STRING_SIZE("2006-04-29 ") #define ID_COLS 8 +#define AUTHOR_COLS 19 #define MIN_VIEW_HEIGHT 4 @@ -136,8 +137,9 @@ struct ref_list { struct ref **refs; /* References for this ID. */ }; +static struct ref *get_ref_head(); static struct ref_list *get_ref_list(const char *id); -static void foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data); +static void foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data); static int load_refs(void); enum format_flags { @@ -297,6 +299,9 @@ string_enum_compare(const char *str1, const char *str2, int len) return 0; } +#define enum_equals(entry, str, len) \ + ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len)) + struct enum_map { const char *name; int namelen; @@ -305,6 +310,24 @@ struct enum_map { #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value } +static char * +enum_map_name(const char *name, size_t namelen) +{ + static char buf[SIZEOF_STR]; + int bufpos; + + for (bufpos = 0; bufpos <= namelen; bufpos++) { + buf[bufpos] = tolower(name[bufpos]); + if (buf[bufpos] == '_') + buf[bufpos] = '-'; + } + + buf[bufpos] = 0; + return buf; +} + +#define enum_name(entry) enum_map_name((entry).name, (entry).namelen) + static bool map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name) { @@ -312,8 +335,7 @@ map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char int i; for (i = 0; i < map_size; i++) - if (namelen == map[i].namelen && - !string_enum_compare(name, map[i].name, namelen)) { + if (enum_equals(map[i], name, namelen)) { *value = map[i].value; return TRUE; } @@ -337,41 +359,36 @@ suffixcmp(const char *str, int slen, const char *suffix) } -/* - * What value of "tz" was in effect back then at "time" in the - * local timezone? - */ -static int local_tzoffset(time_t time) -{ - time_t t, t_local; - struct tm tm; - int offset, eastwest; +#define DATE_INFO \ + DATE_(NO), \ + DATE_(DEFAULT), \ + DATE_(RELATIVE), \ + DATE_(SHORT) - t = time; - localtime_r(&t, &tm); - t_local = mktime(&tm); +enum date { +#define DATE_(name) DATE_##name + DATE_INFO +#undef DATE_ +}; - if (t_local < t) { - eastwest = -1; - offset = t - t_local; - } else { - eastwest = 1; - offset = t_local - t; - } - offset /= 60; /* in minutes */ - offset = (offset % 60) + ((offset / 60) * 100); - return offset * eastwest; -} +static const struct enum_map date_map[] = { +#define DATE_(name) ENUM_MAP(#name, DATE_##name) + DATE_INFO +#undef DATE_ +}; -enum date { - DATE_NONE = 0, - DATE_DEFAULT, - DATE_RELATIVE, - DATE_SHORT +struct time { + time_t sec; + int tz; }; -static char * -string_date(const time_t *time, enum date date) +static inline int timecmp(const struct time *t1, const struct time *t2) +{ + return t1->sec - t2->sec; +} + +static const char * +string_date(const struct time *time, enum date date) { static char buf[DATE_COLS + 1]; static const struct enum_map reldate[] = { @@ -386,7 +403,7 @@ string_date(const time_t *time, enum date date) if (date == DATE_RELATIVE) { struct timeval now; - time_t date = *time + local_tzoffset(*time); + time_t date = time->sec + time->tz; time_t seconds; int i; @@ -406,11 +423,65 @@ string_date(const time_t *time, enum date date) } } - gmtime_r(time, &tm); + gmtime_r(&time->sec, &tm); return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL; } +#define AUTHOR_VALUES \ + AUTHOR_(NO), \ + AUTHOR_(FULL), \ + AUTHOR_(ABBREVIATED) + +enum author { +#define AUTHOR_(name) AUTHOR_##name + AUTHOR_VALUES, +#undef AUTHOR_ + AUTHOR_DEFAULT = AUTHOR_FULL +}; + +static const struct enum_map author_map[] = { +#define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name) + AUTHOR_VALUES +#undef AUTHOR_ +}; + +static const char * +get_author_initials(const char *author) +{ + static char initials[AUTHOR_COLS * 6 + 1]; + size_t pos = 0; + const char *end = strchr(author, '\0'); + +#define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-') + + memset(initials, 0, sizeof(initials)); + while (author < end) { + unsigned char bytes; + size_t i; + + while (is_initial_sep(*author)) + author++; + + bytes = utf8_char_length(author, end); + if (bytes < sizeof(initials) - 1 - pos) { + while (bytes--) { + initials[pos++] = *author++; + } + } + + for (i = pos; author < end && !is_initial_sep(*author); author++) { + if (i < sizeof(initials) - 1) + initials[i++] = *author; + } + + initials[i++] = 0; + } + + return initials; +} + + static bool argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd) { @@ -963,8 +1034,7 @@ get_request(const char *name) int i; for (i = 0; i < ARRAY_SIZE(req_info); i++) - if (req_info[i].namelen == namelen && - !string_enum_compare(req_info[i].name, name, namelen)) + if (enum_equals(req_info[i], name, namelen)) return req_info[i].request; return REQ_NONE; @@ -977,7 +1047,7 @@ get_request(const char *name) /* Option and state variables. */ static enum date opt_date = DATE_DEFAULT; -static bool opt_author = TRUE; +static enum author opt_author = AUTHOR_DEFAULT; static bool opt_line_number = FALSE; static bool opt_line_graphics = TRUE; static bool opt_rev_graph = FALSE; @@ -986,17 +1056,15 @@ static int opt_num_interval = 5; static double opt_hscroll = 0.50; static double opt_scale_split_view = 2.0 / 3.0; static int opt_tab_size = 8; -static int opt_author_cols = 19; +static int opt_author_cols = AUTHOR_COLS; 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_head_rev[SIZEOF_REV] = ""; static char opt_remote[SIZEOF_REF] = ""; static char opt_encoding[20] = "UTF-8"; -static bool opt_utf8 = TRUE; -static char opt_codeset[20] = "UTF-8"; -static iconv_t opt_iconv = ICONV_NONE; +static iconv_t opt_iconv_in = ICONV_NONE; +static iconv_t opt_iconv_out = ICONV_NONE; static char opt_search[SIZEOF_STR] = ""; static char opt_cdup[SIZEOF_STR] = ""; static char opt_prefix[SIZEOF_STR] = ""; @@ -1005,8 +1073,8 @@ static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */ static char opt_editor[SIZEOF_STR] = ""; static FILE *opt_tty = NULL; -#define is_initial_commit() (!*opt_head_rev) -#define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev))) +#define is_initial_commit() (!get_ref_head()) +#define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id))) #define mkdate(time) string_date(time, opt_date) @@ -1125,8 +1193,7 @@ get_line_info(const char *name) enum line_type type; for (type = 0; type < ARRAY_SIZE(line_info); type++) - if (namelen == line_info[type].namelen && - !string_enum_compare(line_info[type].name, name, namelen)) + if (enum_equals(line_info[type], name, namelen)) return &line_info[type]; return NULL; @@ -1658,6 +1725,26 @@ static int parse_bool(bool *opt, const char *arg) return OK; } +static int parse_enum_do(unsigned int *opt, const char *arg, + const struct enum_map *map, size_t map_size) +{ + bool is_true; + + assert(map_size > 1); + + if (map_enum_do(map, map_size, (int *) opt, arg)) + return OK; + + if (parse_bool(&is_true, arg) != OK) + return ERR; + + *opt = is_true ? map[1].value : map[0].value; + return OK; +} + +#define parse_enum(opt, arg, map) \ + parse_enum_do(opt, arg, map, ARRAY_SIZE(map)) + static int parse_string(char *opt, const char *arg, size_t optsize) { @@ -1692,22 +1779,10 @@ option_set_command(int argc, const char *argv[]) } if (!strcmp(argv[0], "show-author")) - return parse_bool(&opt_author, argv[2]); + return parse_enum(&opt_author, argv[2], author_map); - if (!strcmp(argv[0], "show-date")) { - bool show_date; - - if (!strcmp(argv[2], "relative")) { - opt_date = DATE_RELATIVE; - return OK; - } else if (!strcmp(argv[2], "short")) { - opt_date = DATE_SHORT; - return OK; - } else if (parse_bool(&show_date, argv[2])) { - opt_date = show_date ? DATE_DEFAULT : DATE_NONE; - } - return ERR; - } + if (!strcmp(argv[0], "show-date")) + return parse_enum(&opt_date, argv[2], date_map); if (!strcmp(argv[0], "show-rev-graph")) return parse_bool(&opt_rev_graph, argv[2]); @@ -2038,19 +2113,11 @@ static struct view views[] = { (view == display[0] || view == display[1]) -enum line_graphic { - LINE_GRAPHIC_VLINE -}; - -static chtype line_graphics[] = { - /* LINE_GRAPHIC_VLINE: */ '|' -}; - static inline void set_view_attr(struct view *view, enum line_type type) { if (!view->curline->selected && view->curtype != type) { - wattrset(view->win, get_line_attr(type)); + (void) wattrset(view->win, get_line_attr(type)); wchgat(view->win, -1, 0, type, NULL); view->curtype = type; } @@ -2060,6 +2127,7 @@ static int draw_chars(struct view *view, enum line_type type, const char *string, int max_len, bool use_tilde) { + static char out_buffer[BUFSIZ * 2]; int len = 0; int col = 0; int trimmed = FALSE; @@ -2068,22 +2136,28 @@ draw_chars(struct view *view, enum line_type type, const char *string, if (max_len <= 0) return 0; - if (opt_utf8) { - len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde); - } else { - col = len = strlen(string); - if (len > max_len) { - if (use_tilde) { - max_len -= 1; + len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size); + + set_view_attr(view, type); + if (len > 0) { + if (opt_iconv_out != ICONV_NONE) { + ICONV_CONST char *inbuf = (ICONV_CONST char *) string; + size_t inlen = len + 1; + + char *outbuf = out_buffer; + size_t outlen = sizeof(out_buffer); + + size_t ret; + + ret = iconv(opt_iconv_out, &inbuf, &inlen, &outbuf, &outlen); + if (ret != (size_t) -1) { + string = out_buffer; + len = sizeof(out_buffer) - outlen; } - col = len = max_len; - trimmed = TRUE; } - } - set_view_attr(view, type); - if (len > 0) waddnstr(view->win, string, len); + } if (trimmed && use_tilde) { set_view_attr(view, LINE_DELIMITER); waddch(view->win, '~'); @@ -2159,9 +2233,9 @@ draw_field(struct view *view, enum line_type type, const char *text, int len, bo } static bool -draw_date(struct view *view, time_t *time) +draw_date(struct view *view, struct time *time) { - const char *date = time ? mkdate(time) : ""; + const char *date = time && time->sec ? mkdate(time) : ""; int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS; return draw_field(view, LINE_DATE, date, cols, FALSE); @@ -2170,25 +2244,11 @@ draw_date(struct view *view, time_t *time) static bool draw_author(struct view *view, const char *author) { - bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author; + bool trim = opt_author_cols == 0 || opt_author_cols > 5; + bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim; - if (!trim) { - static char initials[10]; - size_t pos; - -#define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@') - - memset(initials, 0, sizeof(initials)); - for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) { - while (is_initial_sep(*author)) - author++; - strncpy(&initials[pos], author, sizeof(initials) - 1 - pos); - while (*author && !is_initial_sep(author[1])) - author++; - } - - author = initials; - } + if (abbreviate && author) + author = get_author_initials(author); return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim); } @@ -2221,6 +2281,7 @@ draw_lineno(struct view *view, unsigned int lineno) int digits3 = view->digits < 3 ? 3 : view->digits; int max = MIN(view->width + view->yoffset - view->col, digits3); char *text = NULL; + chtype separator = opt_line_graphics ? ACS_VLINE : '|'; lineno += view->offset + 1; if (lineno == 1 || (lineno % opt_num_interval) == 0) { @@ -2234,7 +2295,7 @@ draw_lineno(struct view *view, unsigned int lineno) view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE); else view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3); - return draw_graphic(view, LINE_DEFAULT, &line_graphics[LINE_GRAPHIC_VLINE], 1); + return draw_graphic(view, LINE_DEFAULT, &separator, 1); } static bool @@ -2442,20 +2503,20 @@ redraw_display(bool clear) } static void -toggle_date_option(enum date *date) +toggle_enum_option_do(unsigned int *opt, const char *help, + const struct enum_map *map, size_t size) { - static const char *help[] = { - "no", - "default", - "relative", - "short" - }; - - opt_date = (opt_date + 1) % ARRAY_SIZE(help); + *opt = (*opt + 1) % size; redraw_display(FALSE); - report("Displaying %s dates", help[opt_date]); + report("Displaying %s %s", enum_name(map[*opt]), help); } +#define toggle_enum_option(opt, help, map) \ + toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map)) + +#define toggle_date() toggle_enum_option(&opt_date, "dates", date_map) +#define toggle_author() toggle_enum_option(&opt_author, "author names", author_map) + static void toggle_view_option(bool *option, const char *help) { @@ -2479,7 +2540,9 @@ open_option_menu(void) if (prompt_menu("Toggle option", menu, &selected)) { if (menu[selected].data == &opt_date) - toggle_date_option(menu[selected].data); + toggle_date(); + else if (menu[selected].data == &opt_author) + toggle_author(); else toggle_view_option(menu[selected].data, menu[selected].text); } @@ -2871,6 +2934,32 @@ free_argv(const char *argv[]) free((void *) argv[argc]); } +static const char * +format_arg(const char *name) +{ + static struct { + const char *name; + size_t namelen; + const char *value; + const char *value_if_empty; + } vars[] = { +#define FORMAT_VAR(name, value, value_if_empty) \ + { name, STRING_SIZE(name), value, value_if_empty } + FORMAT_VAR("%(directory)", opt_path, ""), + FORMAT_VAR("%(file)", opt_file, ""), + FORMAT_VAR("%(ref)", opt_ref, "HEAD"), + FORMAT_VAR("%(head)", ref_head, ""), + FORMAT_VAR("%(commit)", ref_commit, ""), + FORMAT_VAR("%(blob)", ref_blob, ""), + }; + int i; + + for (i = 0; i < ARRAY_SIZE(vars); i++) + if (!strncmp(name, vars[i].name, vars[i].namelen)) + return *vars[i].value ? vars[i].value : vars[i].value_if_empty; + + return NULL; +} static bool format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags) { @@ -2895,27 +2984,13 @@ format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags fl len = strlen(arg); value = ""; - } else if (!prefixcmp(next, "%(directory)")) { - value = opt_path; - - } else if (!prefixcmp(next, "%(file)")) { - value = opt_file; - - } else if (!prefixcmp(next, "%(ref)")) { - value = *opt_ref ? opt_ref : "HEAD"; - - } else if (!prefixcmp(next, "%(head)")) { - value = ref_head; - - } else if (!prefixcmp(next, "%(commit)")) { - value = ref_commit; - - } else if (!prefixcmp(next, "%(blob)")) { - value = ref_blob; - } else { - report("Unknown replacement: `%s`", next); - return FALSE; + value = format_arg(next); + + if (!value) { + report("Unknown replacement: `%s`", next); + return FALSE; + } } if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value)) @@ -2965,7 +3040,6 @@ end_update(struct view *view, bool force) while (!view->ops->read(view, NULL)) if (!force) return; - set_nonblocking_input(FALSE); if (force) kill_io(view->pipe); done_io(view->pipe); @@ -2975,7 +3049,6 @@ end_update(struct view *view, bool force) static void setup_update(struct view *view, const char *vid) { - set_nonblocking_input(TRUE); reset_view(view); string_copy_rev(view->vid, vid); view->pipe = &view->io; @@ -2996,7 +3069,7 @@ prepare_update_file(struct view *view, const char *name) { if (view->pipe) end_update(view, TRUE); - return io_open(&view->io, "%s", name); + return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name); } static bool @@ -3056,7 +3129,7 @@ update_view(struct view *view) } for (; (line = io_get(view->pipe, '\n', can_read)); can_read = FALSE) { - if (opt_iconv != ICONV_NONE) { + if (opt_iconv_in != ICONV_NONE) { ICONV_CONST char *inbuf = line; size_t inlen = strlen(line) + 1; @@ -3065,7 +3138,7 @@ update_view(struct view *view) size_t ret; - ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen); + ret = iconv(opt_iconv_in, &inbuf, &inlen, &outbuf, &outlen); if (ret != (size_t) -1) line = out_buffer; } @@ -3236,11 +3309,9 @@ open_view(struct view *prev, enum request request, enum open_flags flags) do_scroll_view(prev, lines); } - if (prev && view != prev) { - if (split) { - /* "Blur" the previous view. */ - update_view_title(prev); - } + if (prev && view != prev && split && view_is_displayed(prev)) { + /* "Blur" the previous view. */ + update_view_title(prev); } if (view->pipe && view->lines == 0) { @@ -3276,7 +3347,7 @@ open_mergetool(const char *file) } static void -open_editor(bool from_root, const char *file) +open_editor(const char *file) { const char *editor_argv[] = { "vi", file, NULL }; const char *editor; @@ -3292,7 +3363,7 @@ open_editor(bool from_root, const char *file) editor = "vi"; editor_argv[0] = editor; - open_external_viewer(editor_argv, from_root ? opt_cdup : NULL); + open_external_viewer(editor_argv, opt_cdup); } static void @@ -3478,11 +3549,11 @@ view_driver(struct view *view, enum request request) break; case REQ_TOGGLE_DATE: - toggle_date_option(&opt_date); + toggle_date(); break; case REQ_TOGGLE_AUTHOR: - toggle_view_option(&opt_author, "author display"); + toggle_author(); break; case REQ_TOGGLE_REV_GRAPH: @@ -3636,7 +3707,13 @@ get_author(const char *name) } static void -parse_timezone(time_t *time, const char *zone) +parse_timesec(struct time *time, const char *sec) +{ + time->sec = (time_t) atol(sec); +} + +static void +parse_timezone(struct time *time, const char *zone) { long tz; @@ -3648,14 +3725,15 @@ parse_timezone(time_t *time, const char *zone) if (zone[0] == '-') tz = -tz; - *time -= tz; + time->tz = tz; + time->sec -= tz; } /* Parse author lines where the name may be empty: * author 1138474660 +0100 */ static void -parse_author_line(char *ident, const char **author, time_t *time) +parse_author_line(char *ident, const char **author, struct time *time) { char *nameend = strchr(ident, '<'); char *emailend = strchr(ident, '>'); @@ -3677,7 +3755,7 @@ parse_author_line(char *ident, const char **author, time_t *time) char *secs = emailend + 2; char *zone = strchr(secs, ' '); - *time = (time_t) atol(secs); + parse_timesec(time, secs); if (zone && strlen(zone) == STRING_SIZE(" +0700")) parse_timezone(time, zone + 1); @@ -3956,33 +4034,14 @@ static struct view_ops diff_ops = { static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)]; -static char * -help_name(char buf[SIZEOF_STR], const char *name, size_t namelen) -{ - int bufpos; - - for (bufpos = 0; bufpos <= namelen; bufpos++) { - buf[bufpos] = tolower(name[bufpos]); - if (buf[bufpos] == '_') - buf[bufpos] = '-'; - } - - buf[bufpos] = 0; - return buf; -} - -#define help_keymap_name(buf, keymap) \ - help_name(buf, keymap_table[keymap].name, keymap_table[keymap].namelen) - static bool help_open_keymap_title(struct view *view, enum keymap keymap) { - char buf[SIZEOF_STR]; struct line *line; line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings", help_keymap_hidden[keymap] ? '+' : '-', - help_keymap_name(buf, keymap)); + enum_name(keymap_table[keymap])); if (line) line->other = keymap; @@ -4015,7 +4074,7 @@ help_open_keymap(struct view *view, enum keymap keymap) if (add_title && help_open_keymap_title(view, keymap)) return; - add_title = false; + add_title = FALSE; if (group) { add_line_text(view, group, LINE_HELP_GROUP); @@ -4023,8 +4082,7 @@ help_open_keymap(struct view *view, enum keymap keymap) } add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s", key, - help_name(buf, req_info[i].name, req_info[i].namelen), - req_info[i].help); + enum_name(req_info[i]), req_info[i].help); } group = "External commands:"; @@ -4167,7 +4225,7 @@ push_tree_stack_entry(const char *name, unsigned long lineno) struct tree_entry { char id[SIZEOF_REV]; mode_t mode; - time_t time; /* Date from the author ident. */ + struct time time; /* Date from the author ident. */ const char *author; /* Author of the commit. */ char name[1]; }; @@ -4206,7 +4264,7 @@ tree_compare(const void *l1, const void *l2) switch (get_sort_field(tree_sort_state)) { case ORDERBY_DATE: - return sort_order(tree_sort_state, entry1->time - entry2->time); + return sort_order(tree_sort_state, timecmp(&entry1->time, &entry2->time)); case ORDERBY_AUTHOR: return sort_order(tree_sort_state, strcmp(entry1->author, entry2->author)); @@ -4243,7 +4301,7 @@ static bool tree_read_date(struct view *view, char *text, bool *read_date) { static const char *author_name; - static time_t author_time; + static struct time author_time; if (!text && *read_date) { *read_date = FALSE; @@ -4390,7 +4448,7 @@ tree_draw(struct view *view, struct line *line, unsigned int lineno) if (opt_author && draw_author(view, entry->author)) return TRUE; - if (opt_date && draw_date(view, entry->author ? &entry->time : NULL)) + if (opt_date && draw_date(view, &entry->time)) return TRUE; } if (draw_text(view, line->type, entry->name, TRUE)) @@ -4409,7 +4467,7 @@ open_blob_editor() else if (!run_io_append(blob_ops.argv, FORMAT_ALL, fd)) report("Failed to save blob data to file"); else - open_editor(FALSE, file); + open_editor(file); if (fd != -1) unlink(file); } @@ -4435,7 +4493,7 @@ tree_request(struct view *view, enum request request, struct line *line) } else if (!is_head_commit(view->vid)) { open_blob_editor(); } else { - open_editor(TRUE, opt_file); + open_editor(opt_file); } return REQ_NONE; @@ -4633,7 +4691,7 @@ struct blame_commit { char id[SIZEOF_REV]; /* SHA1 ID. */ char title[128]; /* First line of the commit message. */ const char *author; /* Author of the commit. */ - time_t time; /* Date from the author ident. */ + struct time time; /* Date from the author ident. */ char filename[128]; /* Name of file. */ bool has_previous; /* Was a "previous" line detected. */ }; @@ -4820,7 +4878,7 @@ blame_read(struct view *view, char *line) commit->author = get_author(line); } else if (match_blame_header("author-time ", &line)) { - commit->time = (time_t) atol(line); + parse_timesec(&commit->time, line); } else if (match_blame_header("author-tz ", &line)) { parse_timezone(&commit->time, line); @@ -4843,7 +4901,7 @@ static bool blame_draw(struct view *view, struct line *line, unsigned int lineno) { struct blame *blame = line->data; - time_t *time = NULL; + struct time *time = NULL; const char *id = NULL, *author = NULL; char text[SIZEOF_STR]; @@ -5035,10 +5093,12 @@ static struct view_ops blame_ops = { struct branch { const char *author; /* Author of the last commit. */ - time_t time; /* Date of the last activity. */ - struct ref *ref; /* Name and commit ID information. */ + struct time time; /* Date of the last activity. */ + const struct ref *ref; /* Name and commit ID information. */ }; +static const struct ref branch_all; + static const enum sort_field branch_sort_fields[] = { ORDERBY_NAME, ORDERBY_DATE, ORDERBY_AUTHOR }; @@ -5052,7 +5112,7 @@ branch_compare(const void *l1, const void *l2) switch (get_sort_field(branch_sort_state)) { case ORDERBY_DATE: - return sort_order(branch_sort_state, branch1->time - branch2->time); + return sort_order(branch_sort_state, timecmp(&branch1->time, &branch2->time)); case ORDERBY_AUTHOR: return sort_order(branch_sort_state, strcmp(branch1->author, branch2->author)); @@ -5069,19 +5129,21 @@ branch_draw(struct view *view, struct line *line, unsigned int lineno) struct branch *branch = line->data; enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT; - if (opt_date && draw_date(view, branch->author ? &branch->time : NULL)) + if (opt_date && draw_date(view, &branch->time)) return TRUE; if (opt_author && draw_author(view, branch->author)) return TRUE; - draw_text(view, type, branch->ref->name, TRUE); + draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE); return TRUE; } static enum request branch_request(struct view *view, enum request request, struct line *line) { + struct branch *branch = line->data; + switch (request) { case REQ_REFRESH: load_refs(); @@ -5094,7 +5156,21 @@ branch_request(struct view *view, enum request request, struct line *line) return REQ_NONE; case REQ_ENTER: - open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT); + if (branch->ref == &branch_all) { + const char *all_branches_argv[] = { + "git", "log", "--no-color", "--pretty=raw", "--parents", + "--topo-order", "--all", NULL + }; + struct view *main_view = VIEW(REQ_VIEW_MAIN); + + if (!prepare_update(main_view, all_branches_argv, NULL, FORMAT_NONE)) { + report("Failed to load view of all branches"); + return REQ_NONE; + } + open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT); + } else { + open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT); + } return REQ_NONE; default: @@ -5144,7 +5220,7 @@ branch_read(struct view *view, char *line) } static bool -branch_open_visitor(void *data, struct ref *ref) +branch_open_visitor(void *data, const struct ref *ref) { struct view *view = data; struct branch *branch; @@ -5174,6 +5250,7 @@ branch_open(struct view *view) } setup_update(view, view->id); + branch_open_visitor(view, &branch_all); foreach_ref(branch_open_visitor, view); view->p_restore = TRUE; @@ -5287,7 +5364,7 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty char *buf; struct io io = {}; - if (!run_io(&io, argv, NULL, IO_RD)) + if (!run_io(&io, argv, opt_cdup, IO_RD)) return FALSE; add_line_data(view, NULL, type); @@ -5368,7 +5445,7 @@ static const char *status_diff_files_argv[] = { }; static const char *status_list_other_argv[] = { - "git", "ls-files", "-z", "--others", "--exclude-standard", NULL + "git", "ls-files", "-z", "--others", "--exclude-standard", opt_prefix, NULL }; static const char *status_list_no_head_argv[] = { @@ -5685,10 +5762,8 @@ status_update_prepare(struct io *io, enum line_type type) return run_io(io, staged_argv, opt_cdup, IO_WR); case LINE_STAT_UNSTAGED: - return run_io(io, others_argv, opt_cdup, IO_WR); - case LINE_STAT_UNTRACKED: - return run_io(io, others_argv, NULL, IO_WR); + return run_io(io, others_argv, opt_cdup, IO_WR); default: die("line type %d not handled in switch", type); @@ -5814,9 +5889,8 @@ status_revert(struct status *status, enum line_type type, bool has_none) } else { report("Cannot revert changes to multiple files"); } - return FALSE; - } else { + } else if (prompt_yesno("Are you sure you want to revert changes?")) { char mode[10] = "100644"; const char *reset_argv[] = { "git", "update-index", "--cacheinfo", mode, @@ -5826,12 +5900,25 @@ status_revert(struct status *status, enum line_type type, bool has_none) "git", "checkout", "--", status->old.name, NULL }; - if (!prompt_yesno("Are you sure you want to overwrite any changes?")) - return FALSE; - string_format(mode, "%o", status->old.mode); - return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) && - run_io_fg(checkout_argv, opt_cdup); + if (status->status == 'U') { + string_format(mode, "%5o", status->old.mode); + + if (status->old.mode == 0 && status->new.mode == 0) { + reset_argv[2] = "--force-remove"; + reset_argv[3] = status->old.name; + reset_argv[4] = NULL; + } + + if (!run_io_fg(reset_argv, opt_cdup)) + return FALSE; + if (status->old.mode == 0 && status->new.mode == 0) + return TRUE; + } + + return run_io_fg(checkout_argv, opt_cdup); } + + return FALSE; } static enum request @@ -5866,14 +5953,12 @@ status_request(struct view *view, enum request request, struct line *line) return REQ_NONE; } - open_editor(status->status != '?', status->new.name); + open_editor(status->new.name); break; case REQ_VIEW_BLAME: - if (status) { - string_copy(opt_file, status->new.name); + if (status) opt_ref[0] = 0; - } return request; case REQ_ENTER: @@ -5940,6 +6025,8 @@ status_select(struct view *view, struct line *line) } string_format(view->ref, text, key, file); + if (status) + string_copy(opt_file, status->new.name); } static bool @@ -6148,7 +6235,7 @@ stage_request(struct view *view, enum request request, struct line *line) return REQ_NONE; } - open_editor(stage_status.status != '?', stage_status.new.name); + open_editor(stage_status.new.name); break; case REQ_REFRESH: @@ -6215,7 +6302,7 @@ struct commit { char id[SIZEOF_REV]; /* SHA1 ID. */ char title[128]; /* First line of the commit message. */ const char *author; /* Author of the commit. */ - time_t time; /* Date from the author ident. */ + struct time time; /* Date from the author ident. */ struct ref_list *refs; /* Repository references. */ chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */ size_t graph_size; /* The width of the graph array. */ @@ -6342,9 +6429,7 @@ draw_rev_graph(struct rev_graph *graph) struct rev_filler *filler; size_t i; - if (opt_line_graphics) - fillers[DEFAULT].line = line_graphics[LINE_GRAPHIC_VLINE]; - + fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|'; filler = &fillers[DEFAULT]; for (i = 0; i < graph->pos; i++) { @@ -6667,7 +6752,7 @@ static struct view_ops main_ops = { */ static inline int -unicode_width(unsigned long c) +unicode_width(unsigned long c, int tab_size) { if (c >= 0x1100 && (c <= 0x115f /* Hangul Jamo */ @@ -6685,7 +6770,7 @@ unicode_width(unsigned long c) return 2; if (c == '\t') - return opt_tab_size; + return tab_size; return 1; } @@ -6703,6 +6788,14 @@ static const unsigned char utf8_bytes[256] = { 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1, }; +static inline unsigned char +utf8_char_length(const char *string, const char *end) +{ + int c = *(unsigned char *) string; + + return utf8_bytes[c]; +} + /* Decode UTF-8 multi-byte representation into a Unicode character. */ static inline unsigned long utf8_to_unicode(const char *string, size_t length) @@ -6759,7 +6852,7 @@ utf8_to_unicode(const char *string, size_t length) * * Returns the number of bytes to output from string to satisfy max_width. */ static size_t -utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve) +utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size) { const char *string = *start; const char *end = strchr(string, '\0'); @@ -6770,8 +6863,7 @@ utf8_length(const char **start, size_t skip, int *width, size_t max_width, int * *trimmed = 0; while (string < end) { - int c = *(unsigned char *) string; - unsigned char bytes = utf8_bytes[c]; + unsigned char bytes = utf8_char_length(string, end); size_t ucwidth; unsigned long unicode; @@ -6786,7 +6878,7 @@ utf8_length(const char **start, size_t skip, int *width, size_t max_width, int * if (!unicode) break; - ucwidth = unicode_width(unicode); + ucwidth = unicode_width(unicode, tab_size); if (skip > 0) { skip -= ucwidth <= skip ? ucwidth : skip; *start += bytes; @@ -6877,17 +6969,6 @@ report(const char *msg, ...) update_view_title(view); } -/* Controls when nodelay should be in effect when polling user input. */ -static void -set_nonblocking_input(bool loading) -{ - static unsigned int loading_views; - - if ((loading == FALSE && loading_views-- == 1) || - (loading == TRUE && loading_views++ == 0)) - nodelay(status_win, loading); -} - static void init_display(void) { @@ -6927,9 +7008,6 @@ init_display(void) wbkgdset(status_win, get_line_attr(LINE_STATUS)); TABSIZE = opt_tab_size; - if (opt_line_graphics) { - line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE; - } term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM"); if (term && !strcmp(term, "gnome-terminal")) { @@ -6958,6 +7036,7 @@ get_input(int prompt_position) { struct view *view; int i, key, cursor_y, cursor_x; + bool loading = FALSE; if (prompt_position) input_mode = TRUE; @@ -6969,6 +7048,8 @@ get_input(int prompt_position) use_scroll_redrawwin) redrawwin(view->win); view->has_scrolled = FALSE; + if (view->pipe) + loading = TRUE; } /* Update the cursor position. */ @@ -6985,6 +7066,7 @@ get_input(int prompt_position) /* Refresh, accept single keystroke of input */ doupdate(); + nodelay(status_win, loading); key = wgetch(status_win); /* wgetch() with nodelay() enabled returns ERR when @@ -7167,6 +7249,7 @@ static bool prompt_menu(const char *prompt, const struct menu_item *items, int * static struct ref **refs = NULL; static size_t refs_size = 0; +static struct ref *refs_head = NULL; static struct ref_list **ref_lists = NULL; static size_t ref_lists_size = 0; @@ -7195,7 +7278,7 @@ compare_refs(const void *ref1_, const void *ref2_) } static void -foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data) +foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data) { size_t i; @@ -7204,6 +7287,12 @@ foreach_ref(bool (*visitor)(void *data, struct ref *ref), void *data) break; } +static struct ref * +get_ref_head() +{ + return refs_head; +} + static struct ref_list * get_ref_list(const char *id) { @@ -7268,11 +7357,15 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen) } else if (!prefixcmp(name, "refs/heads/")) { namelen -= STRING_SIZE("refs/heads/"); name += STRING_SIZE("refs/heads/"); - head = !strncmp(opt_head, name, namelen); + if (!strncmp(opt_head, name, namelen)) + return OK; } else if (!strcmp(name, "HEAD")) { - string_ncopy(opt_head_rev, id, idlen); - return OK; + head = TRUE; + if (*opt_head) { + namelen = strlen(opt_head); + name = opt_head; + } } /* If we are reloading or it's an annotated tag, replace the @@ -7314,6 +7407,8 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen) ref->tracked = tracked; string_copy_rev(ref->id, id); + if (head) + refs_head = ref; return OK; } @@ -7344,6 +7439,7 @@ load_refs(void) memmove(opt_head, offset, strlen(offset) + 1); } + refs_head = NULL; for (i = 0; i < refs_size; i++) refs[i]->id[0] = 0; @@ -7643,6 +7739,7 @@ parse_options(int argc, const char *argv[]) int main(int argc, const char *argv[]) { + const char *codeset = "UTF-8"; enum request request = parse_options(argc, argv); struct view *view; size_t i; @@ -7651,9 +7748,7 @@ main(int argc, const char *argv[]) signal(SIGPIPE, SIG_IGN); if (setlocale(LC_ALL, "")) { - char *codeset = nl_langinfo(CODESET); - - string_ncopy(opt_codeset, codeset, strlen(codeset)); + codeset = nl_langinfo(CODESET); } if (load_repo_info() == ERR) @@ -7669,12 +7764,15 @@ main(int argc, const char *argv[]) if (!opt_git_dir[0] && request != REQ_VIEW_PAGER) die("Not a git repository"); - if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8")) - opt_utf8 = FALSE; + if (*opt_encoding && strcmp(codeset, "UTF-8")) { + opt_iconv_in = iconv_open("UTF-8", opt_encoding); + if (opt_iconv_in == ICONV_NONE) + die("Failed to initialize character set conversion"); + } - if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) { - opt_iconv = iconv_open(opt_codeset, opt_encoding); - if (opt_iconv == ICONV_NONE) + if (codeset && strcmp(codeset, "UTF-8")) { + opt_iconv_out = iconv_open(codeset, "UTF-8"); + if (opt_iconv_out == ICONV_NONE) die("Failed to initialize character set conversion"); }