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 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
63 !io_write(io
, "\n", 1))
66 if (stage_diff_done(line
, end
))
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
;
81 const char *prefix
= "";
82 const char *data
= line
->data
;
85 /* Write the complete line. */
87 } else if (line
->type
== write_as_normal
) {
91 } else if (line
->type
== ignore
) {
95 if (data
&& !io_printf(io
, "%s%s\n", prefix
, data
))
99 if (stage_diff_done(line
, end
))
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
))
117 header
.old
.lines
= header
.new.lines
- diff
;
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
);
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
;
138 diff_hdr
= find_prev_line_by_type(view
, chunk
, LINE_DIFF_HEADER
);
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
))
151 if (single
!= NULL
) {
152 if (!stage_apply_line(&io
, diff_hdr
, chunk
, single
, view
->line
+ view
->lines
))
156 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
157 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
161 return io_done(&io
) && chunk
;
165 stage_update_files(struct view
*view
, enum line_type type
)
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");
184 line
= find_next_line_by_type(view
, view
->line
, type
);
185 return line
&& status_update_files(view
, line
+ 1);
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
);
197 if (!stage_apply_chunk(view
, chunk
, single
? line
: NULL
, false)) {
198 report("Failed to apply chunk");
202 } else if (!stage_status
.status
) {
203 if (!stage_update_files(view
, stage_line_type
)) {
204 report("Failed to update files");
208 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
209 report("Failed to update file");
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
);
225 if (!prompt_yesno("Are you sure you want to revert changes?"))
228 if (!stage_apply_chunk(view
, chunk
, NULL
, true)) {
229 report("Failed to revert chunk");
235 return status_revert(stage_status
.status
? &stage_status
: NULL
,
236 stage_line_type
, false);
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
];
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
))
255 chunk_line
= strdup(buf
);
260 from
->data
= chunk_line
;
265 if (!add_line_at(view
, after_lineno
++, buf
, LINE_DIFF_CHUNK
, strlen(buf
) + 1, false))
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))
275 return view
->line
+ after_lineno
;
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
;
285 if (!chunk_start
|| !parse_chunk_header(&header
, chunk_start
->data
)) {
286 report("Failed to parse chunk header");
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
== '\\')
298 if (*chunk_line
== ' ') {
301 if (last_unchanged_line
< last_changed_line
)
302 last_unchanged_line
= pos
;
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
;
321 if (*chunk_line
== '-') {
323 last_changed_line
= pos
;
324 } else if (*chunk_line
== '+') {
326 last_changed_line
= pos
;
331 stage_insert_chunk(view
, &header
, chunk_start
, NULL
, NULL
);
333 report("Split the chunk in %d", chunks
+ 1);
335 report("The chunk cannot be split");
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
);
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
)
361 for (; pos
<= line
; pos
++)
369 stage_request(struct view
*view
, enum request request
, struct line
*line
)
372 case REQ_STATUS_UPDATE
:
373 if (!stage_update(view
, line
, false))
377 case REQ_STATUS_REVERT
:
378 if (!stage_revert(view
, line
))
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");
388 if (line
->type
!= LINE_DIFF_DEL
&& line
->type
!= LINE_DIFF_ADD
) {
389 report("Please select a change to stage");
392 if (stage_chunk_is_wrapped(view
, line
)) {
393 report("Staging is not supported for wrapped lines");
396 if (!stage_update(view
, line
, true))
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");
407 stage_split_chunk(view
, line
);
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.");
419 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
420 open_editor(stage_status
.new.name
, (line
- view
->line
) + 1);
422 open_editor(stage_status
.new.name
, diff_get_lineno(view
, line
));
427 /* Reload everything(including current branch information) ... */
432 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
433 report("Nothing to blame here");
437 if (stage_status
.new.name
[0]) {
438 string_copy(view
->env
->file
, stage_status
.new.name
);
440 const char *file
= diff_get_pathname(view
, line
);
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
--;
453 return diff_common_enter(view
, request
, line
);
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
;
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"
476 diff_common_select(view
, line
, changes_msg
);
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
));
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
;
519 argv
= index_show_argv
;
523 case LINE_STAT_UNSTAGED
:
524 watch_register(&view
->watch
, WATCH_INDEX_UNSTAGED
);
525 if (stage_status
.status
!= 'U')
526 argv
= files_show_argv
;
528 argv
= files_unmerged_argv
;
531 case LINE_STAT_UNTRACKED
:
533 view
->encoding
= get_path_encoding(stage_status
.old
.name
, default_encoding
);
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");
546 if (stage_line_type
!= LINE_STAT_UNTRACKED
)
547 diff_save_line(view
, &state
->diff
, flags
);
550 view
->dir
= repo
.cdup
;
551 return begin_update(view
, NULL
, NULL
, flags
);
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);
568 diff_restore_line(view
, &state
->diff
);
570 if (buf
&& diff_common_read(view
, buf
->data
, &state
->diff
))
573 return pager_read(view
, buf
);
576 static struct view_ops stage_ops
= {
579 VIEW_DIFF_LIKE
| VIEW_REFRESH
| VIEW_FLEX_WIDTH
,
580 sizeof(struct stage_state
),
588 view_column_bit(LINE_NUMBER
) | view_column_bit(TEXT
),
589 pager_get_column_data
,
594 /* vim: set ts=8 sw=8 noexpandtab: */