blame: move functionality to colorize ID to draw_id()
[tig.git] / src / stage.c
blobfa33706741d213b003dc56df8107eaf73c89b1c8
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"
27 struct stage_state {
28 struct diff_state diff;
31 static inline bool
32 stage_diff_done(struct line *line, struct line *end)
34 return line >= end ||
35 line->type == LINE_DIFF_CHUNK ||
36 line->type == LINE_DIFF_HEADER;
39 static bool
40 stage_diff_write(struct io *io, struct line *line, struct line *end)
42 while (line < end) {
43 if (!io_write(io, line->data, strlen(line->data)) ||
44 !io_write(io, "\n", 1))
45 return FALSE;
46 line++;
47 if (stage_diff_done(line, end))
48 break;
51 return TRUE;
54 static bool
55 stage_diff_single_write(struct io *io, bool staged,
56 struct line *line, struct line *single, struct line *end)
58 enum line_type write_as_normal = staged ? LINE_DIFF_ADD : LINE_DIFF_DEL;
59 enum line_type ignore = staged ? LINE_DIFF_DEL : LINE_DIFF_ADD;
61 while (line < end) {
62 const char *prefix = "";
63 const char *data = line->data;
65 if (line == single) {
66 /* Write the complete line. */
68 } else if (line->type == write_as_normal) {
69 prefix = " ";
70 data = data + 1;
72 } else if (line->type == ignore) {
73 data = NULL;
76 if (data && !io_printf(io, "%s%s\n", prefix, data))
77 return FALSE;
79 line++;
80 if (stage_diff_done(line, end))
81 break;
84 return TRUE;
87 static bool
88 stage_apply_line(struct io *io, struct line *diff_hdr, struct line *chunk, struct line *single, struct line *end)
90 struct chunk_header header;
91 bool staged = stage_line_type == LINE_STAT_STAGED;
92 int diff = single->type == LINE_DIFF_DEL ? -1 : 1;
94 if (!parse_chunk_header(&header, chunk->data))
95 return FALSE;
97 if (staged)
98 header.old.lines = header.new.lines - diff;
99 else
100 header.new.lines = header.old.lines + diff;
102 return stage_diff_write(io, diff_hdr, chunk) &&
103 io_printf(io, "@@ -%lu,%lu +%lu,%lu @@\n",
104 header.old.position, header.old.lines,
105 header.new.position, header.new.lines) &&
106 stage_diff_single_write(io, staged, chunk + 1, single, end);
109 static bool
110 stage_apply_chunk(struct view *view, struct line *chunk, struct line *single, bool revert)
112 const char *apply_argv[SIZEOF_ARG] = {
113 "git", "apply", "--whitespace=nowarn", NULL
115 struct line *diff_hdr;
116 struct io io;
117 int argc = 3;
119 diff_hdr = find_prev_line_by_type(view, chunk, LINE_DIFF_HEADER);
120 if (!diff_hdr)
121 return FALSE;
123 if (!revert)
124 apply_argv[argc++] = "--cached";
125 if (revert || stage_line_type == LINE_STAT_STAGED)
126 apply_argv[argc++] = "-R";
127 apply_argv[argc++] = "-";
128 apply_argv[argc++] = NULL;
129 if (!io_run(&io, IO_WR, repo.cdup, opt_env, apply_argv))
130 return FALSE;
132 if (single != NULL) {
133 if (!stage_apply_line(&io, diff_hdr, chunk, single, view->line + view->lines))
134 chunk = NULL;
136 } else {
137 if (!stage_diff_write(&io, diff_hdr, chunk) ||
138 !stage_diff_write(&io, chunk, view->line + view->lines))
139 chunk = NULL;
142 io_done(&io);
144 return chunk ? TRUE : FALSE;
147 static bool
148 stage_update(struct view *view, struct line *line, bool single)
150 struct line *chunk = NULL;
152 if (!is_initial_commit() && stage_line_type != LINE_STAT_UNTRACKED)
153 chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
155 if (chunk) {
156 if (!stage_apply_chunk(view, chunk, single ? line : NULL, FALSE)) {
157 report("Failed to apply chunk");
158 return FALSE;
161 } else if (!stage_status.status) {
162 view = view->parent;
164 for (line = view->line; view_has_line(view, line); line++)
165 if (line->type == stage_line_type)
166 break;
168 if (!status_update_files(view, line + 1)) {
169 report("Failed to update files");
170 return FALSE;
173 } else if (!status_update_file(&stage_status, stage_line_type)) {
174 report("Failed to update file");
175 return FALSE;
178 return TRUE;
181 static bool
182 stage_revert(struct view *view, struct line *line)
184 struct line *chunk = NULL;
186 if (!is_initial_commit() && stage_line_type == LINE_STAT_UNSTAGED)
187 chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
189 if (chunk) {
190 if (!prompt_yesno("Are you sure you want to revert changes?"))
191 return FALSE;
193 if (!stage_apply_chunk(view, chunk, NULL, TRUE)) {
194 report("Failed to revert chunk");
195 return FALSE;
197 return TRUE;
199 } else {
200 return status_revert(stage_status.status ? &stage_status : NULL,
201 stage_line_type, FALSE);
205 static struct line *
206 stage_insert_chunk(struct view *view, struct chunk_header *header,
207 struct line *from, struct line *to, struct line *last_unchanged_line)
209 char buf[SIZEOF_STR];
210 char *chunk_line;
211 unsigned long from_lineno = last_unchanged_line - view->line;
212 unsigned long to_lineno = to - view->line;
213 unsigned long after_lineno = to_lineno;
215 if (!string_format(buf, "@@ -%lu,%lu +%lu,%lu @@",
216 header->old.position, header->old.lines,
217 header->new.position, header->new.lines))
218 return NULL;
220 chunk_line = strdup(buf);
221 if (!chunk_line)
222 return NULL;
224 free(from->data);
225 from->data = chunk_line;
227 if (!to)
228 return from;
230 if (!add_line_at(view, after_lineno++, buf, LINE_DIFF_CHUNK, strlen(buf) + 1, FALSE))
231 return NULL;
233 while (from_lineno < to_lineno) {
234 struct line *line = &view->line[from_lineno++];
236 if (!add_line_at(view, after_lineno++, line->data, line->type, strlen(line->data) + 1, FALSE))
237 return FALSE;
240 return view->line + after_lineno;
243 static void
244 stage_split_chunk(struct view *view, struct line *chunk_start)
246 struct chunk_header header;
247 struct line *last_changed_line = NULL, *last_unchanged_line = NULL, *pos;
248 int chunks = 0;
250 if (!chunk_start || !parse_chunk_header(&header, chunk_start->data)) {
251 report("Failed to parse chunk header");
252 return;
255 header.old.lines = header.new.lines = 0;
257 for (pos = chunk_start + 1; view_has_line(view, pos); pos++) {
258 const char *chunk_line = pos->data;
260 if (*chunk_line == '@' || *chunk_line == '\\')
261 break;
263 if (*chunk_line == ' ') {
264 header.old.lines++;
265 header.new.lines++;
266 if (last_unchanged_line < last_changed_line)
267 last_unchanged_line = pos;
268 continue;
271 if (last_changed_line && last_changed_line < last_unchanged_line) {
272 unsigned long chunk_start_lineno = pos - view->line;
273 unsigned long diff = pos - last_unchanged_line;
275 pos = stage_insert_chunk(view, &header, chunk_start, pos, last_unchanged_line);
277 header.old.position += header.old.lines - diff;
278 header.new.position += header.new.lines - diff;
279 header.old.lines = header.new.lines = diff;
281 chunk_start = view->line + chunk_start_lineno;
282 last_changed_line = last_unchanged_line = NULL;
283 chunks++;
286 if (*chunk_line == '-') {
287 header.old.lines++;
288 last_changed_line = pos;
289 } else if (*chunk_line == '+') {
290 header.new.lines++;
291 last_changed_line = pos;
295 if (chunks) {
296 stage_insert_chunk(view, &header, chunk_start, NULL, NULL);
297 redraw_view(view);
298 report("Split the chunk in %d", chunks + 1);
299 } else {
300 report("The chunk cannot be split");
304 static enum request
305 stage_request(struct view *view, enum request request, struct line *line)
307 switch (request) {
308 case REQ_STATUS_UPDATE:
309 if (!stage_update(view, line, FALSE))
310 return REQ_NONE;
311 break;
313 case REQ_STATUS_REVERT:
314 if (!stage_revert(view, line))
315 return REQ_NONE;
316 break;
318 case REQ_STAGE_UPDATE_LINE:
319 if (stage_line_type == LINE_STAT_UNTRACKED ||
320 stage_status.status == 'A') {
321 report("Staging single lines is not supported for new files");
322 return REQ_NONE;
324 if (line->type != LINE_DIFF_DEL && line->type != LINE_DIFF_ADD) {
325 report("Please select a change to stage");
326 return REQ_NONE;
328 if (!stage_update(view, line, TRUE))
329 return REQ_NONE;
330 break;
333 case REQ_STAGE_SPLIT_CHUNK:
334 if (stage_line_type == LINE_STAT_UNTRACKED ||
335 !(line = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK))) {
336 report("No chunks to split in sight");
337 return REQ_NONE;
339 stage_split_chunk(view, line);
340 return REQ_NONE;
342 case REQ_EDIT:
343 if (!stage_status.new.name[0])
344 return diff_common_edit(view, request, line);
346 if (stage_status.status == 'D') {
347 report("File has been deleted.");
348 return REQ_NONE;
351 if (stage_line_type == LINE_STAT_UNTRACKED) {
352 open_editor(stage_status.new.name, (line - view->line) + 1);
353 } else {
354 open_editor(stage_status.new.name, diff_get_lineno(view, line));
356 break;
358 case REQ_REFRESH:
359 /* Reload everything(including current branch information) ... */
360 load_refs(TRUE);
361 break;
363 case REQ_VIEW_BLAME:
364 if (stage_line_type == LINE_STAT_UNTRACKED) {
365 report("Nothing to blame here");
366 return REQ_NONE;
369 if (stage_status.new.name[0]) {
370 string_copy(view->env->file, stage_status.new.name);
371 } else {
372 const char *file = diff_get_pathname(view, line);
374 if (file)
375 string_ncopy(view->env->file, file, strlen(file));
378 view->env->ref[0] = 0;
379 view->env->lineno = diff_get_lineno(view, line);
380 if (view->env->lineno > 0)
381 view->env->lineno--;
382 return request;
384 case REQ_ENTER:
385 return diff_common_enter(view, request, line);
387 default:
388 return request;
391 refresh_view(view->parent);
393 /* Check whether the staged entry still exists, and close the
394 * stage view if it doesn't. */
395 if (!status_exists(view->parent, &stage_status, stage_line_type)) {
396 status_restore(view->parent);
397 return REQ_VIEW_CLOSE;
400 refresh_view(view);
402 return REQ_NONE;
405 static bool
406 stage_open(struct view *view, enum open_flags flags)
408 const char *no_head_diff_argv[] = {
409 GIT_DIFF_STAGED_INITIAL(encoding_arg, diff_context_arg(), ignore_space_arg(),
410 stage_status.new.name)
412 const char *index_show_argv[] = {
413 GIT_DIFF_STAGED(encoding_arg, diff_context_arg(), ignore_space_arg(),
414 stage_status.old.name, stage_status.new.name)
416 const char *files_show_argv[] = {
417 GIT_DIFF_UNSTAGED(encoding_arg, diff_context_arg(), ignore_space_arg(),
418 stage_status.old.name, stage_status.new.name)
420 /* Diffs for unmerged entries are empty when passing the new
421 * path, so leave out the new path. */
422 const char *files_unmerged_argv[] = {
423 "git", "diff-files", encoding_arg, "--root", "--patch-with-stat",
424 diff_context_arg(), ignore_space_arg(), "--",
425 stage_status.old.name, NULL
427 static const char *file_argv[] = { repo.cdup, stage_status.new.name, NULL };
428 const char **argv = NULL;
430 if (!stage_line_type) {
431 report("No stage content, press %s to open the status view and choose file",
432 get_view_key(view, REQ_VIEW_STATUS));
433 return FALSE;
436 view->encoding = NULL;
438 switch (stage_line_type) {
439 case LINE_STAT_STAGED:
440 if (is_initial_commit()) {
441 argv = no_head_diff_argv;
442 } else {
443 argv = index_show_argv;
445 break;
447 case LINE_STAT_UNSTAGED:
448 if (stage_status.status != 'U')
449 argv = files_show_argv;
450 else
451 argv = files_unmerged_argv;
452 break;
454 case LINE_STAT_UNTRACKED:
455 argv = file_argv;
456 view->encoding = get_path_encoding(stage_status.old.name, default_encoding);
457 break;
459 case LINE_STAT_HEAD:
460 default:
461 die("line type %d not handled in switch", stage_line_type);
464 if (!status_stage_info(view->ref, stage_line_type, &stage_status)
465 || !argv_copy(&view->argv, argv)) {
466 report("Failed to open staged view");
467 return FALSE;
470 view->vid[0] = 0;
471 view->dir = repo.cdup;
472 return begin_update(view, NULL, NULL, flags);
475 static bool
476 stage_read(struct view *view, char *data)
478 struct stage_state *state = view->private;
480 if (stage_line_type == LINE_STAT_UNTRACKED)
481 return pager_common_read(view, data, LINE_DEFAULT);
483 if (data && diff_common_read(view, data, &state->diff))
484 return TRUE;
486 return pager_read(view, data);
489 static struct view_ops stage_ops = {
490 "line",
491 argv_env.status,
492 VIEW_DIFF_LIKE | VIEW_REFRESH,
493 sizeof(struct stage_state),
494 stage_open,
495 stage_read,
496 diff_common_draw,
497 stage_request,
498 pager_grep,
499 pager_select,
502 DEFINE_VIEW(stage);
504 /* vim: set ts=8 sw=8 noexpandtab: */