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.
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"
26 #include "tig/stage.h"
28 static struct status stage_status
;
29 static enum line_type stage_line_type
;
32 open_stage_view(struct view
*prev
, struct status
*status
, enum line_type type
, enum open_flags flags
)
35 stage_line_type
= type
;
37 stage_status
= *status
;
39 memset(&stage_status
, 0, sizeof(stage_status
));
42 open_view(prev
, &stage_view
, flags
);
46 struct diff_state diff
;
50 stage_diff_done(struct line
*line
, struct line
*end
)
53 line
->type
== LINE_DIFF_CHUNK
||
54 line
->type
== LINE_DIFF_HEADER
;
58 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
61 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
62 !io_write(io
, "\n", 1))
65 if (stage_diff_done(line
, end
))
73 stage_diff_single_write(struct io
*io
, bool staged
,
74 struct line
*line
, struct line
*single
, struct line
*end
)
76 enum line_type write_as_normal
= staged
? LINE_DIFF_ADD
: LINE_DIFF_DEL
;
77 enum line_type ignore
= staged
? LINE_DIFF_DEL
: LINE_DIFF_ADD
;
80 const char *prefix
= "";
81 const char *data
= line
->data
;
84 /* Write the complete line. */
86 } else if (line
->type
== write_as_normal
) {
90 } else if (line
->type
== ignore
) {
94 if (data
&& !io_printf(io
, "%s%s\n", prefix
, data
))
98 if (stage_diff_done(line
, end
))
106 stage_apply_line(struct io
*io
, struct line
*diff_hdr
, struct line
*chunk
, struct line
*single
, struct line
*end
)
108 struct chunk_header header
;
109 bool staged
= stage_line_type
== LINE_STAT_STAGED
;
110 int diff
= single
->type
== LINE_DIFF_DEL
? -1 : 1;
112 if (!parse_chunk_header(&header
, chunk
->data
))
116 header
.old
.lines
= header
.new.lines
- diff
;
118 header
.new.lines
= header
.old
.lines
+ diff
;
120 return stage_diff_write(io
, diff_hdr
, chunk
) &&
121 io_printf(io
, "@@ -%lu,%lu +%lu,%lu @@\n",
122 header
.old
.position
, header
.old
.lines
,
123 header
.new.position
, header
.new.lines
) &&
124 stage_diff_single_write(io
, staged
, chunk
+ 1, single
, end
);
128 stage_apply_chunk(struct view
*view
, struct line
*chunk
, struct line
*single
, bool revert
)
130 const char *apply_argv
[SIZEOF_ARG
] = {
131 "git", "apply", "--whitespace=nowarn", NULL
133 struct line
*diff_hdr
;
137 diff_hdr
= find_prev_line_by_type(view
, chunk
, LINE_DIFF_HEADER
);
142 apply_argv
[argc
++] = "--cached";
143 if (revert
|| stage_line_type
== LINE_STAT_STAGED
)
144 apply_argv
[argc
++] = "-R";
145 apply_argv
[argc
++] = "-";
146 apply_argv
[argc
++] = NULL
;
147 if (!io_run(&io
, IO_WR
, repo
.cdup
, opt_env
, apply_argv
))
150 if (single
!= NULL
) {
151 if (!stage_apply_line(&io
, diff_hdr
, chunk
, single
, view
->line
+ view
->lines
))
155 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
156 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
162 return chunk
? TRUE
: FALSE
;
166 stage_update_files(struct view
*view
, enum line_type type
)
170 if (view
->parent
!= &status_view
) {
171 bool updated
= FALSE
;
173 for (line
= view
->line
; (line
= find_next_line_by_type(view
, line
, LINE_DIFF_CHUNK
)); line
++) {
174 if (!stage_apply_chunk(view
, line
, NULL
, FALSE
)) {
175 report("Failed to apply chunk");
185 line
= find_next_line_by_type(view
, view
->line
, type
);
186 return line
&& status_update_files(view
, line
+ 1);
190 stage_update(struct view
*view
, struct line
*line
, bool single
)
192 struct line
*chunk
= NULL
;
194 if (!is_initial_commit() && stage_line_type
!= LINE_STAT_UNTRACKED
)
195 chunk
= find_prev_line_by_type(view
, line
, LINE_DIFF_CHUNK
);
198 if (!stage_apply_chunk(view
, chunk
, single
? line
: NULL
, FALSE
)) {
199 report("Failed to apply chunk");
203 } else if (!stage_status
.status
) {
204 if (!stage_update_files(view
, stage_line_type
)) {
205 report("Failed to update files");
209 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
210 report("Failed to update file");
218 stage_revert(struct view
*view
, struct line
*line
)
220 struct line
*chunk
= NULL
;
222 if (!is_initial_commit() && stage_line_type
== LINE_STAT_UNSTAGED
)
223 chunk
= find_prev_line_by_type(view
, line
, LINE_DIFF_CHUNK
);
226 if (!prompt_yesno("Are you sure you want to revert changes?"))
229 if (!stage_apply_chunk(view
, chunk
, NULL
, TRUE
)) {
230 report("Failed to revert chunk");
236 return status_revert(stage_status
.status
? &stage_status
: NULL
,
237 stage_line_type
, FALSE
);
242 stage_insert_chunk(struct view
*view
, struct chunk_header
*header
,
243 struct line
*from
, struct line
*to
, struct line
*last_unchanged_line
)
245 char buf
[SIZEOF_STR
];
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
;
251 if (!string_format(buf
, "@@ -%lu,%lu +%lu,%lu @@",
252 header
->old
.position
, header
->old
.lines
,
253 header
->new.position
, header
->new.lines
))
256 chunk_line
= strdup(buf
);
261 from
->data
= chunk_line
;
266 if (!add_line_at(view
, after_lineno
++, buf
, LINE_DIFF_CHUNK
, strlen(buf
) + 1, FALSE
))
269 while (from_lineno
< to_lineno
) {
270 struct line
*line
= &view
->line
[from_lineno
++];
272 if (!add_line_at(view
, after_lineno
++, line
->data
, line
->type
, strlen(line
->data
) + 1, FALSE
))
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
, chunk_start
->data
)) {
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
= pos
->data
;
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
;
346 if (parent
== &status_view
)
347 return status_exists(parent
, status
, type
);
349 line
= find_next_line_by_type(parent
, parent
->line
, type
);
351 select_view_line(parent
, line
- parent
->line
);
356 stage_request(struct view
*view
, enum request request
, struct line
*line
)
359 case REQ_STATUS_UPDATE
:
360 if (!stage_update(view
, line
, FALSE
))
364 case REQ_STATUS_REVERT
:
365 if (!stage_revert(view
, line
))
369 case REQ_STAGE_UPDATE_LINE
:
370 if (stage_line_type
== LINE_STAT_UNTRACKED
||
371 stage_status
.status
== 'A') {
372 report("Staging single lines is not supported for new files");
375 if (line
->type
!= LINE_DIFF_DEL
&& line
->type
!= LINE_DIFF_ADD
) {
376 report("Please select a change to stage");
379 if (!stage_update(view
, line
, TRUE
))
384 case REQ_STAGE_SPLIT_CHUNK
:
385 if (stage_line_type
== LINE_STAT_UNTRACKED
||
386 !(line
= find_prev_line_by_type(view
, line
, LINE_DIFF_CHUNK
))) {
387 report("No chunks to split in sight");
390 stage_split_chunk(view
, line
);
394 if (!stage_status
.new.name
[0])
395 return diff_common_edit(view
, request
, line
);
397 if (stage_status
.status
== 'D') {
398 report("File has been deleted.");
402 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
403 open_editor(stage_status
.new.name
, (line
- view
->line
) + 1);
405 open_editor(stage_status
.new.name
, diff_get_lineno(view
, line
));
410 /* Reload everything(including current branch information) ... */
415 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
416 report("Nothing to blame here");
420 if (stage_status
.new.name
[0]) {
421 string_copy(view
->env
->file
, stage_status
.new.name
);
423 const char *file
= diff_get_pathname(view
, line
);
426 string_ncopy(view
->env
->file
, file
, strlen(file
));
429 view
->env
->ref
[0] = 0;
430 view
->env
->goto_lineno
= diff_get_lineno(view
, line
);
431 if (view
->env
->goto_lineno
> 0)
432 view
->env
->goto_lineno
--;
436 return diff_common_enter(view
, request
, line
);
442 refresh_view(view
->parent
);
444 /* Check whether the staged entry still exists, and close the
445 * stage view if it doesn't. */
446 if (!stage_exists(view
, &stage_status
, stage_line_type
))
447 return REQ_VIEW_CLOSE
;
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
;
478 struct stage_state
*state
= view
->private;
480 if (!stage_line_type
) {
481 report("No stage content, press %s to open the status view and choose file",
482 get_view_key(view
, REQ_VIEW_STATUS
));
486 view
->encoding
= NULL
;
488 switch (stage_line_type
) {
489 case LINE_STAT_STAGED
:
490 watch_register(&view
->watch
, WATCH_INDEX_STAGED
);
491 if (is_initial_commit()) {
492 argv
= no_head_diff_argv
;
494 argv
= index_show_argv
;
498 case LINE_STAT_UNSTAGED
:
499 watch_register(&view
->watch
, WATCH_INDEX_UNSTAGED
);
500 if (stage_status
.status
!= 'U')
501 argv
= files_show_argv
;
503 argv
= files_unmerged_argv
;
506 case LINE_STAT_UNTRACKED
:
508 view
->encoding
= get_path_encoding(stage_status
.old
.name
, default_encoding
);
512 die("line type %d not handled in switch", stage_line_type
);
515 if (!status_stage_info(view
->ref
, stage_line_type
, &stage_status
)
516 || !argv_copy(&view
->argv
, argv
)) {
517 report("Failed to open staged view");
521 if (stage_line_type
!= LINE_STAT_UNTRACKED
)
522 diff_save_line(view
, &state
->diff
, flags
);
525 view
->dir
= repo
.cdup
;
526 return begin_update(view
, NULL
, NULL
, flags
);
530 stage_read(struct view
*view
, struct buffer
*buf
)
532 struct stage_state
*state
= view
->private;
534 if (stage_line_type
== LINE_STAT_UNTRACKED
)
535 return pager_common_read(view
, buf
? buf
->data
: NULL
, LINE_DEFAULT
, NULL
);
537 if (!buf
&& !view
->lines
&& view
->parent
) {
538 maximize_view(view
->parent
, TRUE
);
543 diff_restore_line(view
, &state
->diff
);
545 if (buf
&& diff_common_read(view
, buf
->data
, &state
->diff
))
548 return pager_read(view
, buf
);
551 static struct view_ops stage_ops
= {
554 VIEW_DIFF_LIKE
| VIEW_REFRESH
,
555 sizeof(struct stage_state
),
563 view_column_bit(LINE_NUMBER
) | view_column_bit(TEXT
),
564 pager_get_column_data
,
569 /* vim: set ts=8 sw=8 noexpandtab: */