Fix the help tests
[tig.git] / src / stage.c
blob8d241e8724d3364b0015fbd64610f62dadc53d34
1 /* Copyright (c) 2006-2015 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/main.h"
27 #include "tig/stage.h"
29 static struct status stage_status;
30 static enum line_type stage_line_type;
32 void
33 open_stage_view(struct view *prev, struct status *status, enum line_type type, enum open_flags flags)
35 if (type) {
36 stage_line_type = type;
37 if (status)
38 stage_status = *status;
39 else
40 memset(&stage_status, 0, sizeof(stage_status));
43 open_view(prev, &stage_view, flags);
46 struct stage_state {
47 struct diff_state diff;
50 static inline bool
51 stage_diff_done(struct line *line, struct line *end)
53 return line >= end ||
54 line->type == LINE_DIFF_CHUNK ||
55 line->type == LINE_DIFF_HEADER;
58 static bool
59 stage_diff_write(struct io *io, struct line *line, struct line *end)
61 while (line < end) {
62 const char *text = box_text(line);
64 if (!io_write(io, text, strlen(text)) ||
65 !io_write(io, "\n", 1))
66 return false;
67 line++;
68 if (stage_diff_done(line, end))
69 break;
72 return true;
75 static bool
76 stage_diff_single_write(struct io *io, bool staged,
77 struct line *line, struct line *single, struct line *end)
79 enum line_type write_as_normal = staged ? LINE_DIFF_ADD : LINE_DIFF_DEL;
80 enum line_type ignore = staged ? LINE_DIFF_DEL : LINE_DIFF_ADD;
82 while (line < end) {
83 const char *prefix = "";
84 const char *data = box_text(line);
86 if (line == single) {
87 /* Write the complete line. */
89 } else if (line->type == write_as_normal) {
90 prefix = " ";
91 data = data + 1;
93 } else if (line->type == ignore) {
94 data = NULL;
97 if (data && !io_printf(io, "%s%s\n", prefix, data))
98 return false;
100 line++;
101 if (stage_diff_done(line, end))
102 break;
105 return true;
108 static bool
109 stage_apply_line(struct io *io, struct line *diff_hdr, struct line *chunk, struct line *single, struct line *end)
111 struct chunk_header header;
112 bool staged = stage_line_type == LINE_STAT_STAGED;
113 int diff = single->type == LINE_DIFF_DEL ? -1 : 1;
115 if (!parse_chunk_header(&header, box_text(chunk)))
116 return false;
118 if (staged)
119 header.old.lines = header.new.lines - diff;
120 else
121 header.new.lines = header.old.lines + diff;
123 return stage_diff_write(io, diff_hdr, chunk) &&
124 io_printf(io, "@@ -%lu,%lu +%lu,%lu @@\n",
125 header.old.position, header.old.lines,
126 header.new.position, header.new.lines) &&
127 stage_diff_single_write(io, staged, chunk + 1, single, end);
130 static bool
131 stage_apply_chunk(struct view *view, struct line *chunk, struct line *single, bool revert)
133 const char *apply_argv[SIZEOF_ARG] = {
134 "git", "apply", "--whitespace=nowarn", NULL
136 struct line *diff_hdr;
137 struct io io;
138 int argc = 3;
140 diff_hdr = find_prev_line_by_type(view, chunk, LINE_DIFF_HEADER);
141 if (!diff_hdr)
142 return false;
144 if (!revert)
145 apply_argv[argc++] = "--cached";
146 if (revert || stage_line_type == LINE_STAT_STAGED)
147 apply_argv[argc++] = "-R";
148 apply_argv[argc++] = "-";
149 apply_argv[argc++] = NULL;
150 if (!io_run(&io, IO_WR, repo.cdup, NULL, apply_argv))
151 return false;
153 if (single != NULL) {
154 if (!stage_apply_line(&io, diff_hdr, chunk, single, view->line + view->lines))
155 chunk = NULL;
157 } else {
158 if (!stage_diff_write(&io, diff_hdr, chunk) ||
159 !stage_diff_write(&io, chunk, view->line + view->lines))
160 chunk = NULL;
163 return io_done(&io) && chunk;
166 static bool
167 stage_update_files(struct view *view, enum line_type type)
169 struct line *line;
171 if (view->parent != &status_view) {
172 bool updated = false;
174 for (line = view->line; (line = find_next_line_by_type(view, line, LINE_DIFF_CHUNK)); line++) {
175 if (!stage_apply_chunk(view, line, NULL, false)) {
176 report("Failed to apply chunk");
177 return false;
179 updated = true;
182 return updated;
185 view = view->parent;
186 line = find_next_line_by_type(view, view->line, type);
187 return line && status_update_files(view, line + 1);
190 static bool
191 stage_update(struct view *view, struct line *line, bool single)
193 struct line *chunk = NULL;
195 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
196 chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
198 if (chunk) {
199 if (!stage_apply_chunk(view, chunk, single ? line : NULL, false)) {
200 report("Failed to apply chunk");
201 return false;
204 } else if (!stage_status.status) {
205 if (!stage_update_files(view, stage_line_type)) {
206 report("Failed to update files");
207 return false;
210 } else if (!status_update_file(&stage_status, stage_line_type)) {
211 report("Failed to update file");
212 return false;
215 return true;
218 static bool
219 stage_revert(struct view *view, struct line *line)
221 struct line *chunk = NULL;
223 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
224 chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
226 if (chunk) {
227 if (!prompt_yesno("Are you sure you want to revert changes?"))
228 return false;
230 if (!stage_apply_chunk(view, chunk, NULL, true)) {
231 report("Failed to revert chunk");
232 return false;
234 return true;
236 } else {
237 return status_revert(stage_status.status ? &stage_status : NULL,
238 stage_line_type, false);
242 static struct line *
243 stage_insert_chunk(struct view *view, struct chunk_header *header,
244 struct line *from, struct line *to, struct line *last_unchanged_line)
246 struct box *box;
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;
250 int i;
252 box = from->data;
253 for (i = 0; i < box->cells; i++)
254 box->cell[i].length = 0;
256 if (!append_line_format(view, from, "@@ -%lu,%lu +%lu,%lu @@",
257 header->old.position, header->old.lines,
258 header->new.position, header->new.lines))
259 return NULL;
261 if (!to)
262 return from;
264 // Next diff chunk line
265 if (!add_line_text_at(view, after_lineno++, "", LINE_DIFF_CHUNK, 1))
266 return NULL;
268 while (from_lineno < to_lineno) {
269 struct line *line = &view->line[from_lineno++];
270 const char *text = box_text(line);
272 if (!add_line_text_at(view, after_lineno++, text, line->type, 1))
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, box_text(chunk_start))) {
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 = box_text(pos);
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;
345 if (parent == &status_view)
346 return status_exists(parent, status, type);
348 if (parent == &main_view)
349 return main_status_exists(parent, type);
351 return false;
354 static bool
355 stage_chunk_is_wrapped(struct view *view, struct line *line)
357 struct line *pos = find_prev_line_by_type(view, line, LINE_DIFF_HEADER);
359 if (!opt_wrap_lines || !pos)
360 return false;
362 for (; pos <= line; pos++)
363 if (pos->wrapped)
364 return true;
366 return false;
369 static enum request
370 stage_request(struct view *view, enum request request, struct line *line)
372 switch (request) {
373 case REQ_STATUS_UPDATE:
374 if (!stage_update(view, line, false))
375 return REQ_NONE;
376 break;
378 case REQ_STATUS_REVERT:
379 if (!stage_revert(view, line))
380 return REQ_NONE;
381 break;
383 case REQ_STAGE_UPDATE_LINE:
384 if (stage_line_type == LINE_STAT_UNTRACKED ||
385 stage_status.status == 'A') {
386 report("Staging single lines is not supported for new files");
387 return REQ_NONE;
389 if (line->type != LINE_DIFF_DEL && line->type != LINE_DIFF_ADD) {
390 report("Please select a change to stage");
391 return REQ_NONE;
393 if (stage_chunk_is_wrapped(view, line)) {
394 report("Staging is not supported for wrapped lines");
395 return REQ_NONE;
397 if (!stage_update(view, line, true))
398 return REQ_NONE;
399 break;
402 case REQ_STAGE_SPLIT_CHUNK:
403 if (stage_line_type == LINE_STAT_UNTRACKED ||
404 !(line = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK))) {
405 report("No chunks to split in sight");
406 return REQ_NONE;
408 stage_split_chunk(view, line);
409 return REQ_NONE;
411 case REQ_EDIT:
412 if (!stage_status.new.name[0])
413 return diff_common_edit(view, request, line);
415 if (stage_status.status == 'D') {
416 report("File has been deleted.");
417 return REQ_NONE;
420 if (stage_line_type == LINE_STAT_UNTRACKED) {
421 open_editor(stage_status.new.name, (line - view->line) + 1);
422 } else {
423 open_editor(stage_status.new.name, diff_get_lineno(view, line));
425 break;
427 case REQ_REFRESH:
428 /* Reload everything(including current branch information) ... */
429 load_refs(true);
430 break;
432 case REQ_VIEW_BLAME:
433 if (stage_line_type == LINE_STAT_UNTRACKED) {
434 report("Nothing to blame here");
435 return REQ_NONE;
438 if (stage_status.new.name[0]) {
439 string_copy(view->env->file, stage_status.new.name);
440 } else {
441 const char *file = diff_get_pathname(view, line);
443 if (file)
444 string_ncopy(view->env->file, file, strlen(file));
447 view->env->ref[0] = 0;
448 view->env->goto_lineno = diff_get_lineno(view, line);
449 if (view->env->goto_lineno > 0)
450 view->env->goto_lineno--;
451 return request;
453 case REQ_ENTER:
454 return diff_common_enter(view, request, line);
456 default:
457 return request;
460 /* Check whether the staged entry still exists, and close the
461 * stage view if it doesn't. */
462 if (view->parent && !stage_exists(view, &stage_status, stage_line_type))
463 return REQ_VIEW_CLOSE;
465 refresh_view(view);
467 return REQ_NONE;
470 static void
471 stage_select(struct view *view, struct line *line)
473 const char *changes_msg = stage_line_type == LINE_STAT_STAGED ? "Staged changes"
474 : stage_line_type == LINE_STAT_UNSTAGED ? "Unstaged changes"
475 : NULL;
477 diff_common_select(view, line, changes_msg);
480 static bool
481 stage_open(struct view *view, enum open_flags flags)
483 const char *no_head_diff_argv[] = {
484 GIT_DIFF_STAGED_INITIAL(encoding_arg, diff_context_arg(), ignore_space_arg(),
485 stage_status.new.name)
487 const char *index_show_argv[] = {
488 GIT_DIFF_STAGED(encoding_arg, diff_context_arg(), ignore_space_arg(),
489 stage_status.old.name, stage_status.new.name)
491 const char *files_show_argv[] = {
492 GIT_DIFF_UNSTAGED(encoding_arg, diff_context_arg(), ignore_space_arg(),
493 stage_status.old.name, stage_status.new.name)
495 /* Diffs for unmerged entries are empty when passing the new
496 * path, so leave out the new path. */
497 const char *files_unmerged_argv[] = {
498 "git", "diff-files", encoding_arg, "--root", "--patch-with-stat",
499 diff_context_arg(), ignore_space_arg(), "--",
500 stage_status.old.name, NULL
502 static const char *file_argv[] = { repo.cdup, stage_status.new.name, NULL };
503 const char **argv = NULL;
504 struct stage_state *state = view->private;
506 if (!stage_line_type) {
507 report("No stage content, press %s to open the status view and choose file",
508 get_view_key(view, REQ_VIEW_STATUS));
509 return false;
512 view->encoding = NULL;
514 switch (stage_line_type) {
515 case LINE_STAT_STAGED:
516 watch_register(&view->watch, WATCH_INDEX_STAGED);
517 if (is_initial_commit()) {
518 argv = no_head_diff_argv;
519 } else {
520 argv = index_show_argv;
522 break;
524 case LINE_STAT_UNSTAGED:
525 watch_register(&view->watch, WATCH_INDEX_UNSTAGED);
526 if (stage_status.status != 'U')
527 argv = files_show_argv;
528 else
529 argv = files_unmerged_argv;
530 break;
532 case LINE_STAT_UNTRACKED:
533 argv = file_argv;
534 view->encoding = get_path_encoding(stage_status.old.name, default_encoding);
535 break;
537 default:
538 die("line type %d not handled in switch", stage_line_type);
541 if (!status_stage_info(view->ref, stage_line_type, &stage_status)
542 || !argv_copy(&view->argv, argv)) {
543 report("Failed to open staged view");
544 return false;
547 if (stage_line_type != LINE_STAT_UNTRACKED)
548 diff_save_line(view, &state->diff, flags);
550 view->vid[0] = 0;
551 view->dir = repo.cdup;
552 return begin_update(view, NULL, NULL, flags);
555 static bool
556 stage_read(struct view *view, struct buffer *buf, bool force_stop)
558 struct stage_state *state = view->private;
560 if (stage_line_type == LINE_STAT_UNTRACKED)
561 return pager_common_read(view, buf ? buf->data : NULL, LINE_DEFAULT, NULL);
563 if (!buf && !view->lines && view->parent) {
564 maximize_view(view->parent, true);
565 return true;
568 if (!buf)
569 diff_restore_line(view, &state->diff);
571 if (buf && diff_common_read(view, buf->data, &state->diff))
572 return true;
574 return pager_read(view, buf, force_stop);
577 static struct view_ops stage_ops = {
578 "line",
579 argv_env.status,
580 VIEW_DIFF_LIKE | VIEW_REFRESH | VIEW_FLEX_WIDTH,
581 sizeof(struct stage_state),
582 stage_open,
583 stage_read,
584 view_column_draw,
585 stage_request,
586 view_column_grep,
587 stage_select,
588 NULL,
589 view_column_bit(LINE_NUMBER) | view_column_bit(TEXT),
590 pager_get_column_data,
593 DEFINE_VIEW(stage);
595 /* vim: set ts=8 sw=8 noexpandtab: */