Remove input field from struct run_request
[tig.git] / src / stage.c
blob14652d2d009349aa1b522b5daa1254e1c31efa96
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/view.h"
20 #include "tig/draw.h"
21 #include "tig/git.h"
22 #include "tig/pager.h"
23 #include "tig/diff.h"
24 #include "tig/status.h"
26 DEFINE_ALLOCATOR(realloc_ints, int, 32)
28 struct stage_state {
29 struct diff_state diff;
30 size_t chunks;
31 int *chunk;
34 static inline bool
35 stage_diff_done(struct line *line, struct line *end)
37 return line >= end ||
38 line->type == LINE_DIFF_CHUNK ||
39 line->type == LINE_DIFF_HEADER;
42 static bool
43 stage_diff_write(struct io *io, struct line *line, struct line *end)
45 while (line < end) {
46 if (!io_write(io, line->data, strlen(line->data)) ||
47 !io_write(io, "\n", 1))
48 return FALSE;
49 line++;
50 if (stage_diff_done(line, end))
51 break;
54 return TRUE;
57 static bool
58 stage_diff_single_write(struct io *io, bool staged,
59 struct line *line, struct line *single, struct line *end)
61 enum line_type write_as_normal = staged ? LINE_DIFF_ADD : LINE_DIFF_DEL;
62 enum line_type ignore = staged ? LINE_DIFF_DEL : LINE_DIFF_ADD;
64 while (line < end) {
65 const char *prefix = "";
66 const char *data = line->data;
68 if (line == single) {
69 /* Write the complete line. */
71 } else if (line->type == write_as_normal) {
72 prefix = " ";
73 data = data + 1;
75 } else if (line->type == ignore) {
76 data = NULL;
79 if (data && !io_printf(io, "%s%s\n", prefix, data))
80 return FALSE;
82 line++;
83 if (stage_diff_done(line, end))
84 break;
87 return TRUE;
90 static bool
91 stage_apply_line(struct io *io, struct line *diff_hdr, struct line *chunk, struct line *single, struct line *end)
93 struct chunk_header header;
94 bool staged = stage_line_type == LINE_STAT_STAGED;
95 int diff = single->type == LINE_DIFF_DEL ? -1 : 1;
97 if (!parse_chunk_header(&header, chunk->data))
98 return FALSE;
100 if (staged)
101 header.old.lines = header.new.lines - diff;
102 else
103 header.new.lines = header.old.lines + diff;
105 return stage_diff_write(io, diff_hdr, chunk) &&
106 io_printf(io, "@@ -%lu,%lu +%lu,%lu @@\n",
107 header.old.position, header.old.lines,
108 header.new.position, header.new.lines) &&
109 stage_diff_single_write(io, staged, chunk + 1, single, end);
112 static bool
113 stage_apply_chunk(struct view *view, struct line *chunk, struct line *single, bool revert)
115 const char *apply_argv[SIZEOF_ARG] = {
116 "git", "apply", "--whitespace=nowarn", NULL
118 struct line *diff_hdr;
119 struct io io;
120 int argc = 3;
122 diff_hdr = find_prev_line_by_type(view, chunk, LINE_DIFF_HEADER);
123 if (!diff_hdr)
124 return FALSE;
126 if (!revert)
127 apply_argv[argc++] = "--cached";
128 if (revert || stage_line_type == LINE_STAT_STAGED)
129 apply_argv[argc++] = "-R";
130 apply_argv[argc++] = "-";
131 apply_argv[argc++] = NULL;
132 if (!io_run(&io, IO_WR, repo.cdup, opt_env, apply_argv))
133 return FALSE;
135 if (single != NULL) {
136 if (!stage_apply_line(&io, diff_hdr, chunk, single, view->line + view->lines))
137 chunk = NULL;
139 } else {
140 if (!stage_diff_write(&io, diff_hdr, chunk) ||
141 !stage_diff_write(&io, chunk, view->line + view->lines))
142 chunk = NULL;
145 io_done(&io);
147 return chunk ? TRUE : FALSE;
150 static bool
151 stage_update(struct view *view, struct line *line, bool single)
153 struct line *chunk = NULL;
155 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
156 chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
158 if (chunk) {
159 if (!stage_apply_chunk(view, chunk, single ? line : NULL, FALSE)) {
160 report("Failed to apply chunk");
161 return FALSE;
164 } else if (!stage_status.status) {
165 view = view->parent;
167 for (line = view->line; view_has_line(view, line); line++)
168 if (line->type == stage_line_type)
169 break;
171 if (!status_update_files(view, line + 1)) {
172 report("Failed to update files");
173 return FALSE;
176 } else if (!status_update_file(&stage_status, stage_line_type)) {
177 report("Failed to update file");
178 return FALSE;
181 return TRUE;
184 static bool
185 stage_revert(struct view *view, struct line *line)
187 struct line *chunk = NULL;
189 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
190 chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
192 if (chunk) {
193 if (!prompt_yesno("Are you sure you want to revert changes?"))
194 return FALSE;
196 if (!stage_apply_chunk(view, chunk, NULL, TRUE)) {
197 report("Failed to revert chunk");
198 return FALSE;
200 return TRUE;
202 } else {
203 return status_revert(stage_status.status ? &stage_status : NULL,
204 stage_line_type, FALSE);
209 static void
210 stage_next(struct view *view, struct line *line)
212 struct stage_state *state = view->private;
213 int i;
215 if (!state->chunks) {
216 for (line = view->line; view_has_line(view, line); line++) {
217 if (line->type != LINE_DIFF_CHUNK)
218 continue;
220 if (!realloc_ints(&state->chunk, state->chunks, 1)) {
221 report("Allocation failure");
222 return;
225 state->chunk[state->chunks++] = line - view->line;
229 for (i = 0; i < state->chunks; i++) {
230 if (state->chunk[i] > view->pos.lineno) {
231 do_scroll_view(view, state->chunk[i] - view->pos.lineno);
232 report("Chunk %d of %zd", i + 1, state->chunks);
233 return;
237 report("No next chunk found");
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 enum request
340 stage_request(struct view *view, enum request request, struct line *line)
342 switch (request) {
343 case REQ_STATUS_UPDATE:
344 if (!stage_update(view, line, FALSE))
345 return REQ_NONE;
346 break;
348 case REQ_STATUS_REVERT:
349 if (!stage_revert(view, line))
350 return REQ_NONE;
351 break;
353 case REQ_STAGE_UPDATE_LINE:
354 if (stage_line_type == LINE_STAT_UNTRACKED ||
355 stage_status.status == 'A') {
356 report("Staging single lines is not supported for new files");
357 return REQ_NONE;
359 if (line->type != LINE_DIFF_DEL && line->type != LINE_DIFF_ADD) {
360 report("Please select a change to stage");
361 return REQ_NONE;
363 if (!stage_update(view, line, TRUE))
364 return REQ_NONE;
365 break;
367 case REQ_STAGE_NEXT:
368 if (stage_line_type == LINE_STAT_UNTRACKED) {
369 report("File is untracked; press %s to add",
370 get_view_key(view, REQ_STATUS_UPDATE));
371 return REQ_NONE;
373 stage_next(view, line);
374 return REQ_NONE;
376 case REQ_STAGE_SPLIT_CHUNK:
377 if (stage_line_type == LINE_STAT_UNTRACKED ||
378 !(line = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK))) {
379 report("No chunks to split in sight");
380 return REQ_NONE;
382 stage_split_chunk(view, line);
383 return REQ_NONE;
385 case REQ_EDIT:
386 if (!stage_status.new.name[0])
387 return diff_common_edit(view, request, line);
389 if (stage_status.status == 'D') {
390 report("File has been deleted.");
391 return REQ_NONE;
394 if (stage_line_type == LINE_STAT_UNTRACKED) {
395 open_editor(stage_status.new.name, (line - view->line) + 1);
396 } else {
397 open_editor(stage_status.new.name, diff_get_lineno(view, line));
399 break;
401 case REQ_REFRESH:
402 /* Reload everything(including current branch information) ... */
403 load_refs(TRUE);
404 break;
406 case REQ_VIEW_BLAME:
407 if (stage_line_type == LINE_STAT_UNTRACKED) {
408 report("Nothing to blame here");
409 return REQ_NONE;
412 if (stage_status.new.name[0]) {
413 string_copy(view->env->file, stage_status.new.name);
414 } else {
415 const char *file = diff_get_pathname(view, line);
417 if (file)
418 string_copy(view->env->file, file);
421 view->env->ref[0] = 0;
422 view->env->lineno = diff_get_lineno(view, line);
423 if (view->env->lineno > 0)
424 view->env->lineno--;
425 return request;
427 case REQ_ENTER:
428 return diff_common_enter(view, request, line);
430 case REQ_DIFF_CONTEXT_UP:
431 case REQ_DIFF_CONTEXT_DOWN:
432 if (!update_diff_context(request))
433 return REQ_NONE;
434 break;
436 default:
437 return request;
440 refresh_view(view->parent);
442 /* Check whether the staged entry still exists, and close the
443 * stage view if it doesn't. */
444 if (!status_exists(view->parent, &stage_status, stage_line_type)) {
445 status_restore(view->parent);
446 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;
479 if (!stage_line_type) {
480 report("No stage content, press %s to open the status view and choose file",
481 get_view_key(view, REQ_VIEW_STATUS));
482 return FALSE;
485 view->encoding = NULL;
487 switch (stage_line_type) {
488 case LINE_STAT_STAGED:
489 if (is_initial_commit()) {
490 argv = no_head_diff_argv;
491 } else {
492 argv = index_show_argv;
494 break;
496 case LINE_STAT_UNSTAGED:
497 if (stage_status.status != 'U')
498 argv = files_show_argv;
499 else
500 argv = files_unmerged_argv;
501 break;
503 case LINE_STAT_UNTRACKED:
504 argv = file_argv;
505 view->encoding = get_path_encoding(stage_status.old.name, default_encoding);
506 break;
508 case LINE_STAT_HEAD:
509 default:
510 die("line type %d not handled in switch", stage_line_type);
513 if (!status_stage_info(view->ref, stage_line_type, &stage_status)
514 || !argv_copy(&view->argv, argv)) {
515 report("Failed to open staged view");
516 return FALSE;
519 view->vid[0] = 0;
520 view->dir = repo.cdup;
521 return begin_update(view, NULL, NULL, flags);
524 static bool
525 stage_read(struct view *view, char *data)
527 struct stage_state *state = view->private;
529 if (stage_line_type == LINE_STAT_UNTRACKED)
530 return pager_common_read(view, data, LINE_DEFAULT);
532 if (data && diff_common_read(view, data, &state->diff))
533 return TRUE;
535 return pager_read(view, data);
538 struct view_ops stage_ops = {
539 "line",
540 { "stage" },
541 argv_env.status,
542 VIEW_DIFF_LIKE | VIEW_REFRESH,
543 sizeof(struct stage_state),
544 stage_open,
545 stage_read,
546 diff_common_draw,
547 stage_request,
548 pager_grep,
549 pager_select,
552 /* vim: set ts=8 sw=8 noexpandtab: */