Preserve the cursor position when changing the diff context
[tig.git] / src / stage.c
blobbfdbbfb4e9434132a1b1f90d10d4317abca2899f
1 /* Copyright (c) 2006-2014 Jonas Fonseca <jonas.fonseca@gmail.com>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #include "tig/repo.h"
15 #include "tig/argv.h"
16 #include "tig/options.h"
17 #include "tig/parse.h"
18 #include "tig/display.h"
19 #include "tig/prompt.h"
20 #include "tig/view.h"
21 #include "tig/draw.h"
22 #include "tig/git.h"
23 #include "tig/pager.h"
24 #include "tig/diff.h"
25 #include "tig/status.h"
26 #include "tig/stage.h"
28 static struct status stage_status;
29 static enum line_type stage_line_type;
31 void
32 open_stage_view(struct view *prev, struct status *status, enum line_type type, enum open_flags flags)
34 if (type) {
35 stage_line_type = type;
36 if (status)
37 stage_status = *status;
38 else
39 memset(&stage_status, 0, sizeof(stage_status));
42 open_view(prev, &stage_view, flags);
45 struct stage_state {
46 struct diff_state diff;
49 static inline bool
50 stage_diff_done(struct line *line, struct line *end)
52 return line >= end ||
53 line->type == LINE_DIFF_CHUNK ||
54 line->type == LINE_DIFF_HEADER;
57 static bool
58 stage_diff_write(struct io *io, struct line *line, struct line *end)
60 while (line < end) {
61 if (!io_write(io, line->data, strlen(line->data)) ||
62 !io_write(io, "\n", 1))
63 return FALSE;
64 line++;
65 if (stage_diff_done(line, end))
66 break;
69 return TRUE;
72 static bool
73 stage_diff_single_write(struct io *io, bool staged,
74 struct line *line, struct line *single, struct line *end)
76 enum line_type write_as_normal = staged ? LINE_DIFF_ADD : LINE_DIFF_DEL;
77 enum line_type ignore = staged ? LINE_DIFF_DEL : LINE_DIFF_ADD;
79 while (line < end) {
80 const char *prefix = "";
81 const char *data = line->data;
83 if (line == single) {
84 /* Write the complete line. */
86 } else if (line->type == write_as_normal) {
87 prefix = " ";
88 data = data + 1;
90 } else if (line->type == ignore) {
91 data = NULL;
94 if (data && !io_printf(io, "%s%s\n", prefix, data))
95 return FALSE;
97 line++;
98 if (stage_diff_done(line, end))
99 break;
102 return TRUE;
105 static bool
106 stage_apply_line(struct io *io, struct line *diff_hdr, struct line *chunk, struct line *single, struct line *end)
108 struct chunk_header header;
109 bool staged = stage_line_type == LINE_STAT_STAGED;
110 int diff = single->type == LINE_DIFF_DEL ? -1 : 1;
112 if (!parse_chunk_header(&header, chunk->data))
113 return FALSE;
115 if (staged)
116 header.old.lines = header.new.lines - diff;
117 else
118 header.new.lines = header.old.lines + diff;
120 return stage_diff_write(io, diff_hdr, chunk) &&
121 io_printf(io, "@@ -%lu,%lu +%lu,%lu @@\n",
122 header.old.position, header.old.lines,
123 header.new.position, header.new.lines) &&
124 stage_diff_single_write(io, staged, chunk + 1, single, end);
127 static bool
128 stage_apply_chunk(struct view *view, struct line *chunk, struct line *single, bool revert)
130 const char *apply_argv[SIZEOF_ARG] = {
131 "git", "apply", "--whitespace=nowarn", NULL
133 struct line *diff_hdr;
134 struct io io;
135 int argc = 3;
137 diff_hdr = find_prev_line_by_type(view, chunk, LINE_DIFF_HEADER);
138 if (!diff_hdr)
139 return FALSE;
141 if (!revert)
142 apply_argv[argc++] = "--cached";
143 if (revert || stage_line_type == LINE_STAT_STAGED)
144 apply_argv[argc++] = "-R";
145 apply_argv[argc++] = "-";
146 apply_argv[argc++] = NULL;
147 if (!io_run(&io, IO_WR, repo.cdup, opt_env, apply_argv))
148 return FALSE;
150 if (single != NULL) {
151 if (!stage_apply_line(&io, diff_hdr, chunk, single, view->line + view->lines))
152 chunk = NULL;
154 } else {
155 if (!stage_diff_write(&io, diff_hdr, chunk) ||
156 !stage_diff_write(&io, chunk, view->line + view->lines))
157 chunk = NULL;
160 io_done(&io);
162 return chunk ? TRUE : FALSE;
165 static bool
166 stage_update_files(struct view *view, enum line_type type)
168 struct line *line;
170 if (view->parent != &status_view) {
171 bool updated = FALSE;
173 for (line = view->line; (line = find_next_line_by_type(view, line, LINE_DIFF_CHUNK)); line++) {
174 if (!stage_apply_chunk(view, line, NULL, FALSE)) {
175 report("Failed to apply chunk");
176 return FALSE;
178 updated = TRUE;
181 return updated;
184 view = view->parent;
185 line = find_next_line_by_type(view, view->line, type);
186 return line && status_update_files(view, line + 1);
189 static bool
190 stage_update(struct view *view, struct line *line, bool single)
192 struct line *chunk = NULL;
194 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
195 chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
197 if (chunk) {
198 if (!stage_apply_chunk(view, chunk, single ? line : NULL, FALSE)) {
199 report("Failed to apply chunk");
200 return FALSE;
203 } else if (!stage_status.status) {
204 if (!stage_update_files(view, stage_line_type)) {
205 report("Failed to update files");
206 return FALSE;
209 } else if (!status_update_file(&stage_status, stage_line_type)) {
210 report("Failed to update file");
211 return FALSE;
214 return TRUE;
217 static bool
218 stage_revert(struct view *view, struct line *line)
220 struct line *chunk = NULL;
222 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
223 chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
225 if (chunk) {
226 if (!prompt_yesno("Are you sure you want to revert changes?"))
227 return FALSE;
229 if (!stage_apply_chunk(view, chunk, NULL, TRUE)) {
230 report("Failed to revert chunk");
231 return FALSE;
233 return TRUE;
235 } else {
236 return status_revert(stage_status.status ? &stage_status : NULL,
237 stage_line_type, FALSE);
241 static struct line *
242 stage_insert_chunk(struct view *view, struct chunk_header *header,
243 struct line *from, struct line *to, struct line *last_unchanged_line)
245 char buf[SIZEOF_STR];
246 char *chunk_line;
247 unsigned long from_lineno = last_unchanged_line - view->line;
248 unsigned long to_lineno = to - view->line;
249 unsigned long after_lineno = to_lineno;
251 if (!string_format(buf, "@@ -%lu,%lu +%lu,%lu @@",
252 header->old.position, header->old.lines,
253 header->new.position, header->new.lines))
254 return NULL;
256 chunk_line = strdup(buf);
257 if (!chunk_line)
258 return NULL;
260 free(from->data);
261 from->data = chunk_line;
263 if (!to)
264 return from;
266 if (!add_line_at(view, after_lineno++, buf, LINE_DIFF_CHUNK, strlen(buf) + 1, FALSE))
267 return NULL;
269 while (from_lineno < to_lineno) {
270 struct line *line = &view->line[from_lineno++];
272 if (!add_line_at(view, after_lineno++, line->data, line->type, strlen(line->data) + 1, FALSE))
273 return FALSE;
276 return view->line + after_lineno;
279 static void
280 stage_split_chunk(struct view *view, struct line *chunk_start)
282 struct chunk_header header;
283 struct line *last_changed_line = NULL, *last_unchanged_line = NULL, *pos;
284 int chunks = 0;
286 if (!chunk_start || !parse_chunk_header(&header, chunk_start->data)) {
287 report("Failed to parse chunk header");
288 return;
291 header.old.lines = header.new.lines = 0;
293 for (pos = chunk_start + 1; view_has_line(view, pos); pos++) {
294 const char *chunk_line = pos->data;
296 if (*chunk_line == '@' || *chunk_line == '\\')
297 break;
299 if (*chunk_line == ' ') {
300 header.old.lines++;
301 header.new.lines++;
302 if (last_unchanged_line < last_changed_line)
303 last_unchanged_line = pos;
304 continue;
307 if (last_changed_line && last_changed_line < last_unchanged_line) {
308 unsigned long chunk_start_lineno = pos - view->line;
309 unsigned long diff = pos - last_unchanged_line;
311 pos = stage_insert_chunk(view, &header, chunk_start, pos, last_unchanged_line);
313 header.old.position += header.old.lines - diff;
314 header.new.position += header.new.lines - diff;
315 header.old.lines = header.new.lines = diff;
317 chunk_start = view->line + chunk_start_lineno;
318 last_changed_line = last_unchanged_line = NULL;
319 chunks++;
322 if (*chunk_line == '-') {
323 header.old.lines++;
324 last_changed_line = pos;
325 } else if (*chunk_line == '+') {
326 header.new.lines++;
327 last_changed_line = pos;
331 if (chunks) {
332 stage_insert_chunk(view, &header, chunk_start, NULL, NULL);
333 redraw_view(view);
334 report("Split the chunk in %d", chunks + 1);
335 } else {
336 report("The chunk cannot be split");
340 static bool
341 stage_exists(struct view *view, struct status *status, enum line_type type)
343 struct view *parent = view->parent;
344 struct line *line;
346 if (parent == &status_view)
347 return status_exists(parent, status, type);
349 line = find_next_line_by_type(parent, parent->line, type);
350 if (line)
351 select_view_line(parent, line - parent->line);
352 return line != NULL;
355 static enum request
356 stage_request(struct view *view, enum request request, struct line *line)
358 switch (request) {
359 case REQ_STATUS_UPDATE:
360 if (!stage_update(view, line, FALSE))
361 return REQ_NONE;
362 break;
364 case REQ_STATUS_REVERT:
365 if (!stage_revert(view, line))
366 return REQ_NONE;
367 break;
369 case REQ_STAGE_UPDATE_LINE:
370 if (stage_line_type == LINE_STAT_UNTRACKED ||
371 stage_status.status == 'A') {
372 report("Staging single lines is not supported for new files");
373 return REQ_NONE;
375 if (line->type != LINE_DIFF_DEL && line->type != LINE_DIFF_ADD) {
376 report("Please select a change to stage");
377 return REQ_NONE;
379 if (!stage_update(view, line, TRUE))
380 return REQ_NONE;
381 break;
384 case REQ_STAGE_SPLIT_CHUNK:
385 if (stage_line_type == LINE_STAT_UNTRACKED ||
386 !(line = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK))) {
387 report("No chunks to split in sight");
388 return REQ_NONE;
390 stage_split_chunk(view, line);
391 return REQ_NONE;
393 case REQ_EDIT:
394 if (!stage_status.new.name[0])
395 return diff_common_edit(view, request, line);
397 if (stage_status.status == 'D') {
398 report("File has been deleted.");
399 return REQ_NONE;
402 if (stage_line_type == LINE_STAT_UNTRACKED) {
403 open_editor(stage_status.new.name, (line - view->line) + 1);
404 } else {
405 open_editor(stage_status.new.name, diff_get_lineno(view, line));
407 break;
409 case REQ_REFRESH:
410 /* Reload everything(including current branch information) ... */
411 load_refs(TRUE);
412 break;
414 case REQ_VIEW_BLAME:
415 if (stage_line_type == LINE_STAT_UNTRACKED) {
416 report("Nothing to blame here");
417 return REQ_NONE;
420 if (stage_status.new.name[0]) {
421 string_copy(view->env->file, stage_status.new.name);
422 } else {
423 const char *file = diff_get_pathname(view, line);
425 if (file)
426 string_ncopy(view->env->file, file, strlen(file));
429 view->env->ref[0] = 0;
430 view->env->lineno = diff_get_lineno(view, line);
431 if (view->env->lineno > 0)
432 view->env->lineno--;
433 return request;
435 case REQ_ENTER:
436 return diff_common_enter(view, request, line);
438 default:
439 return request;
442 refresh_view(view->parent);
444 /* Check whether the staged entry still exists, and close the
445 * stage view if it doesn't. */
446 if (!stage_exists(view, &stage_status, stage_line_type))
447 return REQ_VIEW_CLOSE;
449 refresh_view(view);
451 return REQ_NONE;
454 static bool
455 stage_open(struct view *view, enum open_flags flags)
457 const char *no_head_diff_argv[] = {
458 GIT_DIFF_STAGED_INITIAL(encoding_arg, diff_context_arg(), ignore_space_arg(),
459 stage_status.new.name)
461 const char *index_show_argv[] = {
462 GIT_DIFF_STAGED(encoding_arg, diff_context_arg(), ignore_space_arg(),
463 stage_status.old.name, stage_status.new.name)
465 const char *files_show_argv[] = {
466 GIT_DIFF_UNSTAGED(encoding_arg, diff_context_arg(), ignore_space_arg(),
467 stage_status.old.name, stage_status.new.name)
469 /* Diffs for unmerged entries are empty when passing the new
470 * path, so leave out the new path. */
471 const char *files_unmerged_argv[] = {
472 "git", "diff-files", encoding_arg, "--root", "--patch-with-stat",
473 diff_context_arg(), ignore_space_arg(), "--",
474 stage_status.old.name, NULL
476 static const char *file_argv[] = { repo.cdup, stage_status.new.name, NULL };
477 const char **argv = NULL;
478 struct stage_state *state = view->private;
480 if (!stage_line_type) {
481 report("No stage content, press %s to open the status view and choose file",
482 get_view_key(view, REQ_VIEW_STATUS));
483 return FALSE;
486 view->encoding = NULL;
488 switch (stage_line_type) {
489 case LINE_STAT_STAGED:
490 watch_register(&view->watch, WATCH_INDEX_STAGED);
491 if (is_initial_commit()) {
492 argv = no_head_diff_argv;
493 } else {
494 argv = index_show_argv;
496 break;
498 case LINE_STAT_UNSTAGED:
499 watch_register(&view->watch, WATCH_INDEX_UNSTAGED);
500 if (stage_status.status != 'U')
501 argv = files_show_argv;
502 else
503 argv = files_unmerged_argv;
504 break;
506 case LINE_STAT_UNTRACKED:
507 argv = file_argv;
508 view->encoding = get_path_encoding(stage_status.old.name, default_encoding);
509 break;
511 default:
512 die("line type %d not handled in switch", stage_line_type);
515 if (!status_stage_info(view->ref, stage_line_type, &stage_status)
516 || !argv_copy(&view->argv, argv)) {
517 report("Failed to open staged view");
518 return FALSE;
521 if (stage_line_type != LINE_STAT_UNTRACKED)
522 diff_save_line(view, &state->diff, flags);
524 view->vid[0] = 0;
525 view->dir = repo.cdup;
526 return begin_update(view, NULL, NULL, flags);
529 static bool
530 stage_read(struct view *view, struct buffer *buf)
532 struct stage_state *state = view->private;
534 if (stage_line_type == LINE_STAT_UNTRACKED)
535 return pager_common_read(view, buf ? buf->data : NULL, LINE_DEFAULT, NULL);
537 if (!buf && !view->lines && view->parent) {
538 maximize_view(view->parent, TRUE);
539 return TRUE;
542 if (!buf)
543 diff_restore_line(view, &state->diff);
545 if (buf && diff_common_read(view, buf->data, &state->diff))
546 return TRUE;
548 return pager_read(view, buf);
551 static struct view_ops stage_ops = {
552 "line",
553 argv_env.status,
554 VIEW_DIFF_LIKE | VIEW_REFRESH,
555 sizeof(struct stage_state),
556 stage_open,
557 stage_read,
558 view_column_draw,
559 stage_request,
560 view_column_grep,
561 pager_select,
562 NULL,
563 view_column_bit(LINE_NUMBER) | view_column_bit(TEXT),
564 pager_get_column_data,
567 DEFINE_VIEW(stage);
569 /* vim: set ts=8 sw=8 noexpandtab: */