Add uninstall target
[tig.git] / src / stage.c
blobbe4028cdb951e861797bc88224dae25dfe8803ec
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 if (!io_write(io, line->data, strlen(line->data)) ||
63 !io_write(io, "\n", 1))
64 return false;
65 line++;
66 if (stage_diff_done(line, end))
67 break;
70 return true;
73 static bool
74 stage_diff_single_write(struct io *io, bool staged,
75 struct line *line, struct line *single, struct line *end)
77 enum line_type write_as_normal = staged ? LINE_DIFF_ADD : LINE_DIFF_DEL;
78 enum line_type ignore = staged ? LINE_DIFF_DEL : LINE_DIFF_ADD;
80 while (line < end) {
81 const char *prefix = "";
82 const char *data = line->data;
84 if (line == single) {
85 /* Write the complete line. */
87 } else if (line->type == write_as_normal) {
88 prefix = " ";
89 data = data + 1;
91 } else if (line->type == ignore) {
92 data = NULL;
95 if (data && !io_printf(io, "%s%s\n", prefix, data))
96 return false;
98 line++;
99 if (stage_diff_done(line, end))
100 break;
103 return true;
106 static bool
107 stage_apply_line(struct io *io, struct line *diff_hdr, struct line *chunk, struct line *single, struct line *end)
109 struct chunk_header header;
110 bool staged = stage_line_type == LINE_STAT_STAGED;
111 int diff = single->type == LINE_DIFF_DEL ? -1 : 1;
113 if (!parse_chunk_header(&header, chunk->data))
114 return false;
116 if (staged)
117 header.old.lines = header.new.lines - diff;
118 else
119 header.new.lines = header.old.lines + diff;
121 return stage_diff_write(io, diff_hdr, chunk) &&
122 io_printf(io, "@@ -%lu,%lu +%lu,%lu @@\n",
123 header.old.position, header.old.lines,
124 header.new.position, header.new.lines) &&
125 stage_diff_single_write(io, staged, chunk + 1, single, end);
128 static bool
129 stage_apply_chunk(struct view *view, struct line *chunk, struct line *single, bool revert)
131 const char *apply_argv[SIZEOF_ARG] = {
132 "git", "apply", "--whitespace=nowarn", NULL
134 struct line *diff_hdr;
135 struct io io;
136 int argc = 3;
138 diff_hdr = find_prev_line_by_type(view, chunk, LINE_DIFF_HEADER);
139 if (!diff_hdr)
140 return false;
142 if (!revert)
143 apply_argv[argc++] = "--cached";
144 if (revert || stage_line_type == LINE_STAT_STAGED)
145 apply_argv[argc++] = "-R";
146 apply_argv[argc++] = "-";
147 apply_argv[argc++] = NULL;
148 if (!io_run(&io, IO_WR, repo.cdup, NULL, apply_argv))
149 return false;
151 if (single != NULL) {
152 if (!stage_apply_line(&io, diff_hdr, chunk, single, view->line + view->lines))
153 chunk = NULL;
155 } else {
156 if (!stage_diff_write(&io, diff_hdr, chunk) ||
157 !stage_diff_write(&io, chunk, view->line + view->lines))
158 chunk = NULL;
161 return io_done(&io) && chunk;
164 static bool
165 stage_update_files(struct view *view, enum line_type type)
167 struct line *line;
169 if (view->parent != &status_view) {
170 bool updated = false;
172 for (line = view->line; (line = find_next_line_by_type(view, line, LINE_DIFF_CHUNK)); line++) {
173 if (!stage_apply_chunk(view, line, NULL, false)) {
174 report("Failed to apply chunk");
175 return false;
177 updated = true;
180 return updated;
183 view = view->parent;
184 line = find_next_line_by_type(view, view->line, type);
185 return line && status_update_files(view, line + 1);
188 static bool
189 stage_update(struct view *view, struct line *line, bool single)
191 struct line *chunk = NULL;
193 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
194 chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
196 if (chunk) {
197 if (!stage_apply_chunk(view, chunk, single ? line : NULL, false)) {
198 report("Failed to apply chunk");
199 return false;
202 } else if (!stage_status.status) {
203 if (!stage_update_files(view, stage_line_type)) {
204 report("Failed to update files");
205 return false;
208 } else if (!status_update_file(&stage_status, stage_line_type)) {
209 report("Failed to update file");
210 return false;
213 return true;
216 static bool
217 stage_revert(struct view *view, struct line *line)
219 struct line *chunk = NULL;
221 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
222 chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
224 if (chunk) {
225 if (!prompt_yesno("Are you sure you want to revert changes?"))
226 return false;
228 if (!stage_apply_chunk(view, chunk, NULL, true)) {
229 report("Failed to revert chunk");
230 return false;
232 return true;
234 } else {
235 return status_revert(stage_status.status ? &stage_status : NULL,
236 stage_line_type, false);
240 static struct line *
241 stage_insert_chunk(struct view *view, struct chunk_header *header,
242 struct line *from, struct line *to, struct line *last_unchanged_line)
244 char buf[SIZEOF_STR];
245 char *chunk_line;
246 unsigned long from_lineno = last_unchanged_line - view->line;
247 unsigned long to_lineno = to - view->line;
248 unsigned long after_lineno = to_lineno;
250 if (!string_format(buf, "@@ -%lu,%lu +%lu,%lu @@",
251 header->old.position, header->old.lines,
252 header->new.position, header->new.lines))
253 return NULL;
255 chunk_line = strdup(buf);
256 if (!chunk_line)
257 return NULL;
259 free(from->data);
260 from->data = chunk_line;
262 if (!to)
263 return from;
265 if (!add_line_at(view, after_lineno++, buf, LINE_DIFF_CHUNK, strlen(buf) + 1, false))
266 return NULL;
268 while (from_lineno < to_lineno) {
269 struct line *line = &view->line[from_lineno++];
271 if (!add_line_at(view, after_lineno++, line->data, line->type, strlen(line->data) + 1, false))
272 return false;
275 return view->line + after_lineno;
278 static void
279 stage_split_chunk(struct view *view, struct line *chunk_start)
281 struct chunk_header header;
282 struct line *last_changed_line = NULL, *last_unchanged_line = NULL, *pos;
283 int chunks = 0;
285 if (!chunk_start || !parse_chunk_header(&header, chunk_start->data)) {
286 report("Failed to parse chunk header");
287 return;
290 header.old.lines = header.new.lines = 0;
292 for (pos = chunk_start + 1; view_has_line(view, pos); pos++) {
293 const char *chunk_line = pos->data;
295 if (*chunk_line == '@' || *chunk_line == '\\')
296 break;
298 if (*chunk_line == ' ') {
299 header.old.lines++;
300 header.new.lines++;
301 if (last_unchanged_line < last_changed_line)
302 last_unchanged_line = pos;
303 continue;
306 if (last_changed_line && last_changed_line < last_unchanged_line) {
307 unsigned long chunk_start_lineno = pos - view->line;
308 unsigned long diff = pos - last_unchanged_line;
310 pos = stage_insert_chunk(view, &header, chunk_start, pos, last_unchanged_line);
312 header.old.position += header.old.lines - diff;
313 header.new.position += header.new.lines - diff;
314 header.old.lines = header.new.lines = diff;
316 chunk_start = view->line + chunk_start_lineno;
317 last_changed_line = last_unchanged_line = NULL;
318 chunks++;
321 if (*chunk_line == '-') {
322 header.old.lines++;
323 last_changed_line = pos;
324 } else if (*chunk_line == '+') {
325 header.new.lines++;
326 last_changed_line = pos;
330 if (chunks) {
331 stage_insert_chunk(view, &header, chunk_start, NULL, NULL);
332 redraw_view(view);
333 report("Split the chunk in %d", chunks + 1);
334 } else {
335 report("The chunk cannot be split");
339 static bool
340 stage_exists(struct view *view, struct status *status, enum line_type type)
342 struct view *parent = view->parent;
344 if (parent == &status_view)
345 return status_exists(parent, status, type);
347 if (parent == &main_view)
348 return main_status_exists(parent, type);
350 return false;
353 static bool
354 stage_chunk_is_wrapped(struct view *view, struct line *line)
356 struct line *pos = find_prev_line_by_type(view, line, LINE_DIFF_HEADER);
358 if (!opt_wrap_lines || !pos)
359 return false;
361 for (; pos <= line; pos++)
362 if (pos->wrapped)
363 return true;
365 return false;
368 static enum request
369 stage_request(struct view *view, enum request request, struct line *line)
371 switch (request) {
372 case REQ_STATUS_UPDATE:
373 if (!stage_update(view, line, false))
374 return REQ_NONE;
375 break;
377 case REQ_STATUS_REVERT:
378 if (!stage_revert(view, line))
379 return REQ_NONE;
380 break;
382 case REQ_STAGE_UPDATE_LINE:
383 if (stage_line_type == LINE_STAT_UNTRACKED ||
384 stage_status.status == 'A') {
385 report("Staging single lines is not supported for new files");
386 return REQ_NONE;
388 if (line->type != LINE_DIFF_DEL && line->type != LINE_DIFF_ADD) {
389 report("Please select a change to stage");
390 return REQ_NONE;
392 if (stage_chunk_is_wrapped(view, line)) {
393 report("Staging is not supported for wrapped lines");
394 return REQ_NONE;
396 if (!stage_update(view, line, true))
397 return REQ_NONE;
398 break;
401 case REQ_STAGE_SPLIT_CHUNK:
402 if (stage_line_type == LINE_STAT_UNTRACKED ||
403 !(line = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK))) {
404 report("No chunks to split in sight");
405 return REQ_NONE;
407 stage_split_chunk(view, line);
408 return REQ_NONE;
410 case REQ_EDIT:
411 if (!stage_status.new.name[0])
412 return diff_common_edit(view, request, line);
414 if (stage_status.status == 'D') {
415 report("File has been deleted.");
416 return REQ_NONE;
419 if (stage_line_type == LINE_STAT_UNTRACKED) {
420 open_editor(stage_status.new.name, (line - view->line) + 1);
421 } else {
422 open_editor(stage_status.new.name, diff_get_lineno(view, line));
424 break;
426 case REQ_REFRESH:
427 /* Reload everything(including current branch information) ... */
428 load_refs(true);
429 break;
431 case REQ_VIEW_BLAME:
432 if (stage_line_type == LINE_STAT_UNTRACKED) {
433 report("Nothing to blame here");
434 return REQ_NONE;
437 if (stage_status.new.name[0]) {
438 string_copy(view->env->file, stage_status.new.name);
439 } else {
440 const char *file = diff_get_pathname(view, line);
442 if (file)
443 string_ncopy(view->env->file, file, strlen(file));
446 view->env->ref[0] = 0;
447 view->env->goto_lineno = diff_get_lineno(view, line);
448 if (view->env->goto_lineno > 0)
449 view->env->goto_lineno--;
450 return request;
452 case REQ_ENTER:
453 return diff_common_enter(view, request, line);
455 default:
456 return request;
459 /* Check whether the staged entry still exists, and close the
460 * stage view if it doesn't. */
461 if (view->parent && !stage_exists(view, &stage_status, stage_line_type))
462 return REQ_VIEW_CLOSE;
464 refresh_view(view);
466 return REQ_NONE;
469 static void
470 stage_select(struct view *view, struct line *line)
472 const char *changes_msg = stage_line_type == LINE_STAT_STAGED ? "Staged changes"
473 : stage_line_type == LINE_STAT_UNSTAGED ? "Unstaged changes"
474 : NULL;
476 diff_common_select(view, line, changes_msg);
479 static bool
480 stage_open(struct view *view, enum open_flags flags)
482 const char *no_head_diff_argv[] = {
483 GIT_DIFF_STAGED_INITIAL(encoding_arg, diff_context_arg(), ignore_space_arg(),
484 stage_status.new.name)
486 const char *index_show_argv[] = {
487 GIT_DIFF_STAGED(encoding_arg, diff_context_arg(), ignore_space_arg(),
488 stage_status.old.name, stage_status.new.name)
490 const char *files_show_argv[] = {
491 GIT_DIFF_UNSTAGED(encoding_arg, diff_context_arg(), ignore_space_arg(),
492 stage_status.old.name, stage_status.new.name)
494 /* Diffs for unmerged entries are empty when passing the new
495 * path, so leave out the new path. */
496 const char *files_unmerged_argv[] = {
497 "git", "diff-files", encoding_arg, "--root", "--patch-with-stat",
498 diff_context_arg(), ignore_space_arg(), "--",
499 stage_status.old.name, NULL
501 static const char *file_argv[] = { repo.cdup, stage_status.new.name, NULL };
502 const char **argv = NULL;
503 struct stage_state *state = view->private;
505 if (!stage_line_type) {
506 report("No stage content, press %s to open the status view and choose file",
507 get_view_key(view, REQ_VIEW_STATUS));
508 return false;
511 view->encoding = NULL;
513 switch (stage_line_type) {
514 case LINE_STAT_STAGED:
515 watch_register(&view->watch, WATCH_INDEX_STAGED);
516 if (is_initial_commit()) {
517 argv = no_head_diff_argv;
518 } else {
519 argv = index_show_argv;
521 break;
523 case LINE_STAT_UNSTAGED:
524 watch_register(&view->watch, WATCH_INDEX_UNSTAGED);
525 if (stage_status.status != 'U')
526 argv = files_show_argv;
527 else
528 argv = files_unmerged_argv;
529 break;
531 case LINE_STAT_UNTRACKED:
532 argv = file_argv;
533 view->encoding = get_path_encoding(stage_status.old.name, default_encoding);
534 break;
536 default:
537 die("line type %d not handled in switch", stage_line_type);
540 if (!status_stage_info(view->ref, stage_line_type, &stage_status)
541 || !argv_copy(&view->argv, argv)) {
542 report("Failed to open staged view");
543 return false;
546 if (stage_line_type != LINE_STAT_UNTRACKED)
547 diff_save_line(view, &state->diff, flags);
549 view->vid[0] = 0;
550 view->dir = repo.cdup;
551 return begin_update(view, NULL, NULL, flags);
554 static bool
555 stage_read(struct view *view, struct buffer *buf)
557 struct stage_state *state = view->private;
559 if (stage_line_type == LINE_STAT_UNTRACKED)
560 return pager_common_read(view, buf ? buf->data : NULL, LINE_DEFAULT, NULL);
562 if (!buf && !view->lines && view->parent) {
563 maximize_view(view->parent, true);
564 return true;
567 if (!buf)
568 diff_restore_line(view, &state->diff);
570 if (buf && diff_common_read(view, buf->data, &state->diff))
571 return true;
573 return pager_read(view, buf);
576 static struct view_ops stage_ops = {
577 "line",
578 argv_env.status,
579 VIEW_DIFF_LIKE | VIEW_REFRESH | VIEW_FLEX_WIDTH,
580 sizeof(struct stage_state),
581 stage_open,
582 stage_read,
583 view_column_draw,
584 stage_request,
585 view_column_grep,
586 stage_select,
587 NULL,
588 view_column_bit(LINE_NUMBER) | view_column_bit(TEXT),
589 pager_get_column_data,
592 DEFINE_VIEW(stage);
594 /* vim: set ts=8 sw=8 noexpandtab: */