-/* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
+/* Copyright (c) 2006-2007 Jonas Fonseca <fonseca@diku.dk>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* GNU General Public License for more details.
*/
-#ifndef VERSION
-#define VERSION "unknown-version"
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef TIG_VERSION
+#define TIG_VERSION "unknown-version"
#endif
#ifndef DEBUG
#define COLOR_DEFAULT (-1)
#define ICONV_NONE ((iconv_t) -1)
+#ifndef ICONV_CONST
+#define ICONV_CONST /* nothing */
+#endif
/* The format and size of the date column in the main view. */
#define DATE_FORMAT "%Y-%m-%d %H:%M"
#define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
+#ifndef GIT_CONFIG
+#define GIT_CONFIG "git config"
+#endif
+
#define TIG_LS_REMOTE \
"git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
#define TIG_HELP_CMD ""
#define TIG_PAGER_CMD ""
#define TIG_STATUS_CMD ""
+#define TIG_STAGE_CMD ""
/* Some ascii-shorthands fitted into the ncurses namespace. */
#define KEY_TAB '\t'
REQ_(VIEW_HELP, "Show help page"), \
REQ_(VIEW_PAGER, "Show pager view"), \
REQ_(VIEW_STATUS, "Show status view"), \
+ REQ_(VIEW_STAGE, "Show stage view"), \
\
REQ_GROUP("View manipulation") \
REQ_(ENTER, "Enter current line and scroll"), \
REQ_(TOGGLE_LINENO, "Toggle line numbers"), \
REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
REQ_(STATUS_UPDATE, "Update file status"), \
- REQ_(EDIT, "Open in editor")
+ REQ_(EDIT, "Open in editor"), \
+ REQ_(CHERRY_PICK, "Cherry-pick commit to current branch")
/* User action requests. */
*/
static const char usage[] =
-"tig " VERSION " (" __DATE__ ")\n"
+"tig " TIG_VERSION " (" __DATE__ ")\n"
"\n"
"Usage: tig [options]\n"
" or: tig [options] [--] [git log options]\n"
}
if (check_option(opt, 'v', "version", OPT_NONE)) {
- printf("tig version %s\n", VERSION);
+ printf("tig version %s\n", TIG_VERSION);
return FALSE;
}
LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
-LINE(STAT_SECTION, "", COLOR_DEFAULT, COLOR_BLUE, A_BOLD), \
+LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
-LINE(STAT_STAGED, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
-LINE(STAT_UNSTAGED,"", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(STAT_STAGED, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
+LINE(STAT_UNSTAGED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
enum line_type {
{ 'p', REQ_VIEW_PAGER },
{ 'h', REQ_VIEW_HELP },
{ 'S', REQ_VIEW_STATUS },
+ { 'c', REQ_VIEW_STAGE },
/* View manipulation */
{ 'q', REQ_VIEW_CLOSE },
{ ':', REQ_PROMPT },
{ 'u', REQ_STATUS_UPDATE },
{ 'e', REQ_EDIT },
+ { 'C', REQ_CHERRY_PICK },
/* Using the ncurses SIGWINCH handler. */
{ KEY_RESIZE, REQ_SCREEN_RESIZE },
KEYMAP_(BLOB), \
KEYMAP_(PAGER), \
KEYMAP_(HELP), \
- KEYMAP_(STATUS)
+ KEYMAP_(STATUS), \
+ KEYMAP_(STAGE)
enum keymap {
#define KEYMAP_(name) KEYMAP_##name
static struct view_ops blob_ops;
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}
VIEW_(HELP, "help", &help_ops, ""),
VIEW_(PAGER, "pager", &pager_ops, "stdin"),
VIEW_(STATUS, "status", &status_ops, ""),
+ VIEW_(STAGE, "stage", &stage_ops, ""),
};
#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
/* When running random commands, initially show the
* command in the title. However, it maybe later be
* overwritten if a commit line is selected. */
- string_copy(view->ref, view->cmd);
+ if (view == VIEW(REQ_VIEW_PAGER))
+ string_copy(view->ref, view->cmd);
+ else
+ view->ref[0] = 0;
} else if (view == VIEW(REQ_VIEW_TREE)) {
const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
line[linelen - 1] = 0;
if (opt_iconv != ICONV_NONE) {
- char *inbuf = line;
+ ICONV_CONST char *inbuf = line;
size_t inlen = linelen;
char *outbuf = out_buffer;
}
static void
-open_editor(struct view *view, char *file)
+open_editor(struct view *view, bool from_root, char *file)
{
char cmd[SIZEOF_STR];
char file_sq[SIZEOF_STR];
char *editor;
+ char *prefix = from_root ? opt_cdup : "";
editor = getenv("GIT_EDITOR");
if (!editor && *opt_editor)
editor = "vi";
if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
- string_format(cmd, "%s %s", editor, file_sq)) {
+ string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
def_prog_mode(); /* save current tty modes */
endwin(); /* restore original tty modes */
system(cmd);
{
int i;
+ if (request == REQ_NONE) {
+ doupdate();
+ return TRUE;
+ }
+
if (view && view->lines) {
request = view->ops->request(view, request, &view->line[view->lineno]);
if (request == REQ_NONE)
open_view(view, request, OPEN_DEFAULT);
break;
+ case REQ_VIEW_STAGE:
+ if (!VIEW(REQ_VIEW_STAGE)->lines) {
+ report("No stage content, press %s to open the status view and choose file",
+ get_key(REQ_VIEW_STATUS));
+ break;
+ }
+ open_view(view, request, OPEN_DEFAULT);
+ break;
+
case REQ_VIEW_MAIN:
case REQ_VIEW_DIFF:
case REQ_VIEW_LOG:
if ((view == VIEW(REQ_VIEW_DIFF) &&
view->parent == VIEW(REQ_VIEW_MAIN)) ||
- (view == VIEW(REQ_VIEW_DIFF) &&
+ (view == VIEW(REQ_VIEW_STAGE) &&
view->parent == VIEW(REQ_VIEW_STATUS)) ||
(view == VIEW(REQ_VIEW_BLOB) &&
view->parent == VIEW(REQ_VIEW_TREE))) {
break;
case REQ_SHOW_VERSION:
- report("tig-%s (built %s)", VERSION, __DATE__);
+ report("tig-%s (built %s)", TIG_VERSION, __DATE__);
return TRUE;
case REQ_SCREEN_RESIZE:
report("Nothing to edit");
break;
+ case REQ_CHERRY_PICK:
+ report("Nothing to cherry-pick");
+ break;
+
case REQ_ENTER:
report("Nothing to enter");
break;
- case REQ_NONE:
- doupdate();
- return TRUE;
case REQ_VIEW_CLOSE:
/* XXX: Mark closed views by letting view->parent point to the
* Tree backend
*/
+struct tree_stack_entry {
+ struct tree_stack_entry *prev; /* Entry below this in the stack */
+ unsigned long lineno; /* Line number to restore */
+ char *name; /* Position of name in opt_path */
+};
+
+/* The top of the path stack. */
+static struct tree_stack_entry *tree_stack = NULL;
+unsigned long tree_lineno = 0;
+
+static void
+pop_tree_stack_entry(void)
+{
+ struct tree_stack_entry *entry = tree_stack;
+
+ tree_lineno = entry->lineno;
+ entry->name[0] = 0;
+ tree_stack = entry->prev;
+ free(entry);
+}
+
+static void
+push_tree_stack_entry(char *name, unsigned long lineno)
+{
+ struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
+ size_t pathlen = strlen(opt_path);
+
+ if (!entry)
+ return;
+
+ entry->prev = tree_stack;
+ entry->name = opt_path + pathlen;
+ tree_stack = entry;
+
+ if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
+ pop_tree_stack_entry();
+ return;
+ }
+
+ /* Move the current line to the first tree entry. */
+ tree_lineno = 1;
+ entry->lineno = lineno;
+}
+
/* Parse output from git-ls-tree(1):
*
* 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
if (!add_line_text(view, text, type))
return FALSE;
- /* Move the current line to the first tree entry. */
- if (first_read)
- view->lineno++;
+ if (tree_lineno > view->lineno) {
+ view->lineno = tree_lineno;
+ tree_lineno = 0;
+ }
return TRUE;
}
if (request != REQ_ENTER)
return request;
+ /* Cleanup the stack if the tree view is at a different tree. */
+ while (!*opt_path && tree_stack)
+ pop_tree_stack_entry();
+
switch (line->type) {
case LINE_TREE_DIR:
/* Depending on whether it is a subdir or parent (updir?) link
* mangle the path buffer. */
if (line == &view->line[1] && *opt_path) {
- size_t path_len = strlen(opt_path);
- char *dirsep = opt_path + path_len - 1;
-
- while (dirsep > opt_path && dirsep[-1] != '/')
- dirsep--;
-
- dirsep[0] = 0;
+ pop_tree_stack_entry();
} else {
- size_t pathlen = strlen(opt_path);
- size_t origlen = pathlen;
char *data = line->data;
char *basename = data + SIZEOF_TREE_ATTR;
- if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
- opt_path[origlen] = 0;
- return TRUE;
- }
+ push_tree_stack_entry(basename, view->lineno);
}
/* Trees and subtrees share the same ID, so they are not not
}
open_view(view, request, flags);
+ if (request == REQ_VIEW_TREE) {
+ view->lineno = tree_lineno;
+ }
return REQ_NONE;
}
static bool
blob_read(struct view *view, char *line)
{
- return add_line_text(view, line, LINE_DEFAULT);
+ return add_line_text(view, line, LINE_DEFAULT) != NULL;
}
static struct view_ops blob_ops = {
char name[SIZEOF_STR];
};
+static struct status stage_status;
+static enum line_type stage_line_type;
+
/* Get fields from the diff line:
* :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
*/
struct stat statbuf;
char exclude[SIZEOF_STR];
char cmd[SIZEOF_STR];
+ unsigned long prev_lineno = view->lineno;
size_t i;
+
for (i = 0; i < view->lines; i++)
free(view->line[i].data);
free(view->line);
- view->lines = view->line_size = 0;
+ view->lines = view->line_size = view->lineno = 0;
view->line = NULL;
if (!realloc_lines(view, view->line_size + 6))
!status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
return FALSE;
+ /* If all went well restore the previous line number to stay in
+ * the context. */
+ if (prev_lineno < view->lines)
+ view->lineno = prev_lineno;
+ else
+ view->lineno = view->lines - 1;
+
return TRUE;
}
die("w00t");
}
- open_view(view, REQ_VIEW_DIFF, OPEN_RELOAD | OPEN_SPLIT);
- if (view_is_displayed(VIEW(REQ_VIEW_DIFF))) {
- string_format(VIEW(REQ_VIEW_DIFF)->ref, info, status->name);
+ open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
+ if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
+ if (status) {
+ stage_status = *status;
+ } else {
+ memset(&stage_status, 0, sizeof(stage_status));
+ }
+
+ stage_line_type = line->type;
+ string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
}
return REQ_NONE;
if (!status)
return request;
- open_editor(view, status->name);
+ open_editor(view, status->status != '?', status->name);
break;
case REQ_ENTER:
};
+static bool
+stage_diff_line(FILE *pipe, struct line *line)
+{
+ char *buf = line->data;
+ size_t bufsize = strlen(buf);
+ size_t written = 0;
+
+ while (!ferror(pipe) && written < bufsize) {
+ written += fwrite(buf + written, 1, bufsize - written, pipe);
+ }
+
+ fputc('\n', pipe);
+
+ return written == bufsize;
+}
+
+static struct line *
+stage_diff_hdr(struct view *view, struct line *line)
+{
+ int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
+ struct line *diff_hdr;
+
+ if (line->type == LINE_DIFF_CHUNK)
+ diff_hdr = line - 1;
+ else
+ diff_hdr = view->line + 1;
+
+ while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
+ if (diff_hdr->type == LINE_DIFF_HEADER)
+ return diff_hdr;
+
+ diff_hdr += diff_hdr_dir;
+ }
+
+ return NULL;
+}
+
+static bool
+stage_update_chunk(struct view *view, struct line *line)
+{
+ char cmd[SIZEOF_STR];
+ size_t cmdsize = 0;
+ struct line *diff_hdr, *diff_chunk, *diff_end;
+ FILE *pipe;
+
+ diff_hdr = stage_diff_hdr(view, line);
+ if (!diff_hdr)
+ return FALSE;
+
+ if (opt_cdup[0] &&
+ !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
+ return FALSE;
+
+ if (!string_format_from(cmd, &cmdsize,
+ "git apply --cached %s - && "
+ "git update-index -q --unmerged --refresh 2>/dev/null",
+ stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
+ return FALSE;
+
+ pipe = popen(cmd, "w");
+ if (!pipe)
+ return FALSE;
+
+ diff_end = view->line + view->lines;
+ if (line->type != LINE_DIFF_CHUNK) {
+ diff_chunk = diff_hdr;
+
+ } else {
+ for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
+ if (diff_chunk->type == LINE_DIFF_CHUNK ||
+ diff_chunk->type == LINE_DIFF_HEADER)
+ diff_end = diff_chunk;
+
+ diff_chunk = line;
+
+ while (diff_hdr->type != LINE_DIFF_CHUNK) {
+ switch (diff_hdr->type) {
+ case LINE_DIFF_HEADER:
+ case LINE_DIFF_INDEX:
+ case LINE_DIFF_ADD:
+ case LINE_DIFF_DEL:
+ break;
+
+ default:
+ diff_hdr++;
+ continue;
+ }
+
+ if (!stage_diff_line(pipe, diff_hdr++)) {
+ pclose(pipe);
+ return FALSE;
+ }
+ }
+ }
+
+ while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
+ diff_chunk++;
+
+ pclose(pipe);
+
+ if (diff_chunk != diff_end)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+stage_update(struct view *view, struct line *line)
+{
+ if (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)) {
+ report("Failed to update file");
+ return;
+ }
+
+ open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
+
+ view = VIEW(REQ_VIEW_STATUS);
+ if (view_is_displayed(view))
+ status_enter(view, &view->line[view->lineno]);
+}
+
+static enum request
+stage_request(struct view *view, enum request request, struct line *line)
+{
+ switch (request) {
+ case REQ_STATUS_UPDATE:
+ stage_update(view, line);
+ break;
+
+ case REQ_EDIT:
+ if (!stage_status.name[0])
+ return request;
+
+ open_editor(view, stage_status.status != '?', stage_status.name);
+ break;
+
+ case REQ_ENTER:
+ pager_request(view, request, line);
+ break;
+
+ default:
+ return request;
+ }
+
+ return REQ_NONE;
+}
+
+static struct view_ops stage_ops = {
+ "line",
+ NULL,
+ pager_read,
+ pager_draw,
+ stage_request,
+ pager_grep,
+ pager_select,
+};
+
+
/*
* Revision graph
*/
return TRUE;
}
+static void
+cherry_pick_commit(struct commit *commit)
+{
+ char cmd[SIZEOF_STR];
+ char *cherry_pick = getenv("TIG_CHERRY_PICK");
+
+ if (!cherry_pick)
+ cherry_pick = "git cherry-pick";
+
+ if (string_format(cmd, "%s %s", cherry_pick, commit->id)) {
+ def_prog_mode(); /* save current tty modes */
+ endwin(); /* restore original tty modes */
+ system(cmd);
+ fprintf(stderr, "Press Enter to continue");
+ getc(stdin);
+ reset_prog_mode();
+ redraw_display();
+ }
+}
+
static enum request
main_request(struct view *view, enum request request, struct line *line)
{
if (request == REQ_ENTER)
open_view(view, REQ_VIEW_DIFF, flags);
+ else if (request == REQ_CHERRY_PICK)
+ cherry_pick_commit(line->data);
else
return request;
static int
load_repo_config(void)
{
- return read_properties(popen("git repo-config --list", "r"),
+ return read_properties(popen(GIT_CONFIG " --list", "r"),
"=", read_repo_config_option);
}
if (load_repo_info() == ERR)
die("Failed to load repo info.");
- /* Require a git repository unless when running in pager mode. */
- if (!opt_git_dir[0])
- die("Not a git repository");
-
if (load_options() == ERR)
die("Failed to load user config.");
if (!parse_options(argc, argv))
return 0;
+ /* Require a git repository unless when running in pager mode. */
+ if (!opt_git_dir[0])
+ die("Not a git repository");
+
if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
opt_iconv = iconv_open(opt_codeset, opt_encoding);
if (opt_iconv == ICONV_NONE)