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.
16 #include "tig/options.h"
17 #include "tig/parse.h"
18 #include "tig/display.h"
19 #include "tig/prompt.h"
23 #include "tig/pager.h"
25 #include "tig/status.h"
27 #include "tig/stage.h"
29 static struct status stage_status
;
30 static enum line_type stage_line_type
;
33 open_stage_view(struct view
*prev
, struct status
*status
, enum line_type type
, enum open_flags flags
)
36 stage_line_type
= type
;
38 stage_status
= *status
;
40 memset(&stage_status
, 0, sizeof(stage_status
));
43 open_view(prev
, &stage_view
, flags
);
47 struct diff_state diff
;
51 stage_diff_done(struct line
*line
, struct line
*end
)
54 line
->type
== LINE_DIFF_CHUNK
||
55 line
->type
== LINE_DIFF_HEADER
;
59 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
62 const char *text
= box_text(line
);
64 if (!io_write(io
, text
, strlen(text
)) ||
65 !io_write(io
, "\n", 1))
68 if (stage_diff_done(line
, end
))
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
;
83 const char *prefix
= "";
84 const char *data
= box_text(line
);
87 /* Write the complete line. */
89 } else if (line
->type
== write_as_normal
) {
93 } else if (line
->type
== ignore
) {
97 if (data
&& !io_printf(io
, "%s%s\n", prefix
, data
))
101 if (stage_diff_done(line
, end
))
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
)))
119 header
.old
.lines
= header
.new.lines
- diff
;
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
);
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
;
140 diff_hdr
= find_prev_line_by_type(view
, chunk
, LINE_DIFF_HEADER
);
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
))
153 if (single
!= NULL
) {
154 if (!stage_apply_line(&io
, diff_hdr
, chunk
, single
, view
->line
+ view
->lines
))
158 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
159 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
163 return io_done(&io
) && chunk
;
167 stage_update_files(struct view
*view
, enum line_type type
)
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");
186 line
= find_next_line_by_type(view
, view
->line
, type
);
187 return line
&& status_update_files(view
, line
+ 1);
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
);
199 if (!stage_apply_chunk(view
, chunk
, single
? line
: NULL
, false)) {
200 report("Failed to apply chunk");
204 } else if (!stage_status
.status
) {
205 if (!stage_update_files(view
, stage_line_type
)) {
206 report("Failed to update files");
210 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
211 report("Failed to update file");
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
);
227 if (!prompt_yesno("Are you sure you want to revert changes?"))
230 if (!stage_apply_chunk(view
, chunk
, NULL
, true)) {
231 report("Failed to revert chunk");
237 return status_revert(stage_status
.status
? &stage_status
: NULL
,
238 stage_line_type
, false);
243 stage_insert_chunk(struct view
*view
, struct chunk_header
*header
,
244 struct line
*from
, struct line
*to
, struct line
*last_unchanged_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
;
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
))
264 // Next diff chunk line
265 if (!add_line_text_at(view
, after_lineno
++, "", LINE_DIFF_CHUNK
, 1))
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))
276 return view
->line
+ after_lineno
;
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
;
286 if (!chunk_start
|| !parse_chunk_header(&header
, box_text(chunk_start
))) {
287 report("Failed to parse chunk header");
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
== '\\')
299 if (*chunk_line
== ' ') {
302 if (last_unchanged_line
< last_changed_line
)
303 last_unchanged_line
= pos
;
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
;
322 if (*chunk_line
== '-') {
324 last_changed_line
= pos
;
325 } else if (*chunk_line
== '+') {
327 last_changed_line
= pos
;
332 stage_insert_chunk(view
, &header
, chunk_start
, NULL
, NULL
);
334 report("Split the chunk in %d", chunks
+ 1);
336 report("The chunk cannot be split");
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
);
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
)
362 for (; pos
<= line
; pos
++)
370 stage_request(struct view
*view
, enum request request
, struct line
*line
)
373 case REQ_STATUS_UPDATE
:
374 if (!stage_update(view
, line
, false))
378 case REQ_STATUS_REVERT
:
379 if (!stage_revert(view
, line
))
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");
389 if (line
->type
!= LINE_DIFF_DEL
&& line
->type
!= LINE_DIFF_ADD
) {
390 report("Please select a change to stage");
393 if (stage_chunk_is_wrapped(view
, line
)) {
394 report("Staging is not supported for wrapped lines");
397 if (!stage_update(view
, line
, true))
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");
408 stage_split_chunk(view
, line
);
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.");
420 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
421 open_editor(stage_status
.new.name
, (line
- view
->line
) + 1);
423 open_editor(stage_status
.new.name
, diff_get_lineno(view
, line
));
428 /* Reload everything(including current branch information) ... */
433 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
434 report("Nothing to blame here");
438 if (stage_status
.new.name
[0]) {
439 string_copy(view
->env
->file
, stage_status
.new.name
);
441 const char *file
= diff_get_pathname(view
, line
);
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
--;
454 return diff_common_enter(view
, request
, line
);
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
;
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"
477 diff_common_select(view
, line
, changes_msg
);
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
));
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
;
520 argv
= index_show_argv
;
524 case LINE_STAT_UNSTAGED
:
525 watch_register(&view
->watch
, WATCH_INDEX_UNSTAGED
);
526 if (stage_status
.status
!= 'U')
527 argv
= files_show_argv
;
529 argv
= files_unmerged_argv
;
532 case LINE_STAT_UNTRACKED
:
534 view
->encoding
= get_path_encoding(stage_status
.old
.name
, default_encoding
);
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");
547 if (stage_line_type
!= LINE_STAT_UNTRACKED
)
548 diff_save_line(view
, &state
->diff
, flags
);
551 view
->dir
= repo
.cdup
;
552 return begin_update(view
, NULL
, NULL
, flags
);
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);
569 diff_restore_line(view
, &state
->diff
);
571 if (buf
&& diff_common_read(view
, buf
->data
, &state
->diff
))
574 return pager_read(view
, buf
, force_stop
);
577 static struct view_ops stage_ops
= {
580 VIEW_DIFF_LIKE
| VIEW_REFRESH
| VIEW_FLEX_WIDTH
,
581 sizeof(struct stage_state
),
589 view_column_bit(LINE_NUMBER
) | view_column_bit(TEXT
),
590 pager_get_column_data
,
595 /* vim: set ts=8 sw=8 noexpandtab: */