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"
22 #include "tig/pager.h"
24 #include "tig/status.h"
26 DEFINE_ALLOCATOR(realloc_ints
, int, 32)
29 struct diff_state diff
;
35 stage_diff_done(struct line
*line
, struct line
*end
)
38 line
->type
== LINE_DIFF_CHUNK
||
39 line
->type
== LINE_DIFF_HEADER
;
43 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
46 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
47 !io_write(io
, "\n", 1))
50 if (stage_diff_done(line
, end
))
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
;
65 const char *prefix
= "";
66 const char *data
= line
->data
;
69 /* Write the complete line. */
71 } else if (line
->type
== write_as_normal
) {
75 } else if (line
->type
== ignore
) {
79 if (data
&& !io_printf(io
, "%s%s\n", prefix
, data
))
83 if (stage_diff_done(line
, end
))
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
))
101 header
.old
.lines
= header
.new.lines
- diff
;
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
);
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
;
122 diff_hdr
= find_prev_line_by_type(view
, chunk
, LINE_DIFF_HEADER
);
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
))
135 if (single
!= NULL
) {
136 if (!stage_apply_line(&io
, diff_hdr
, chunk
, single
, view
->line
+ view
->lines
))
140 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
141 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
147 return chunk
? TRUE
: FALSE
;
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
);
159 if (!stage_apply_chunk(view
, chunk
, single
? line
: NULL
, FALSE
)) {
160 report("Failed to apply chunk");
164 } else if (!stage_status
.status
) {
167 for (line
= view
->line
; view_has_line(view
, line
); line
++)
168 if (line
->type
== stage_line_type
)
171 if (!status_update_files(view
, line
+ 1)) {
172 report("Failed to update files");
176 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
177 report("Failed to update file");
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
);
193 if (!prompt_yesno("Are you sure you want to revert changes?"))
196 if (!stage_apply_chunk(view
, chunk
, NULL
, TRUE
)) {
197 report("Failed to revert chunk");
203 return status_revert(stage_status
.status
? &stage_status
: NULL
,
204 stage_line_type
, FALSE
);
210 stage_next(struct view
*view
, struct line
*line
)
212 struct stage_state
*state
= view
->private;
215 if (!state
->chunks
) {
216 for (line
= view
->line
; view_has_line(view
, line
); line
++) {
217 if (line
->type
!= LINE_DIFF_CHUNK
)
220 if (!realloc_ints(&state
->chunk
, state
->chunks
, 1)) {
221 report("Allocation failure");
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
);
237 report("No next chunk found");
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_request(struct view
*view
, enum request request
, struct line
*line
)
343 case REQ_STATUS_UPDATE
:
344 if (!stage_update(view
, line
, FALSE
))
348 case REQ_STATUS_REVERT
:
349 if (!stage_revert(view
, line
))
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");
359 if (line
->type
!= LINE_DIFF_DEL
&& line
->type
!= LINE_DIFF_ADD
) {
360 report("Please select a change to stage");
363 if (!stage_update(view
, line
, TRUE
))
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
));
373 stage_next(view
, line
);
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");
382 stage_split_chunk(view
, line
);
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.");
394 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
395 open_editor(stage_status
.new.name
, (line
- view
->line
) + 1);
397 open_editor(stage_status
.new.name
, diff_get_lineno(view
, line
));
402 /* Reload everything(including current branch information) ... */
407 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
408 report("Nothing to blame here");
412 if (stage_status
.new.name
[0]) {
413 string_copy(view
->env
->file
, stage_status
.new.name
);
415 const char *file
= diff_get_pathname(view
, line
);
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)
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
))
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
;
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
));
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
;
492 argv
= index_show_argv
;
496 case LINE_STAT_UNSTAGED
:
497 if (stage_status
.status
!= 'U')
498 argv
= files_show_argv
;
500 argv
= files_unmerged_argv
;
503 case LINE_STAT_UNTRACKED
:
505 view
->encoding
= get_path_encoding(stage_status
.old
.name
, default_encoding
);
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");
520 view
->dir
= repo
.cdup
;
521 return begin_update(view
, NULL
, NULL
, flags
);
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
))
535 return pager_read(view
, data
);
538 struct view_ops stage_ops
= {
542 VIEW_DIFF_LIKE
| VIEW_REFRESH
,
543 sizeof(struct stage_state
),
552 /* vim: set ts=8 sw=8 noexpandtab: */