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"
28 struct diff_state diff
;
32 stage_diff_done(struct line
*line
, struct line
*end
)
35 line
->type
== LINE_DIFF_CHUNK
||
36 line
->type
== LINE_DIFF_HEADER
;
40 stage_diff_write(struct io
*io
, struct line
*line
, struct line
*end
)
43 if (!io_write(io
, line
->data
, strlen(line
->data
)) ||
44 !io_write(io
, "\n", 1))
47 if (stage_diff_done(line
, end
))
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
;
62 const char *prefix
= "";
63 const char *data
= line
->data
;
66 /* Write the complete line. */
68 } else if (line
->type
== write_as_normal
) {
72 } else if (line
->type
== ignore
) {
76 if (data
&& !io_printf(io
, "%s%s\n", prefix
, data
))
80 if (stage_diff_done(line
, end
))
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
))
98 header
.old
.lines
= header
.new.lines
- diff
;
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
);
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
;
119 diff_hdr
= find_prev_line_by_type(view
, chunk
, LINE_DIFF_HEADER
);
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
))
132 if (single
!= NULL
) {
133 if (!stage_apply_line(&io
, diff_hdr
, chunk
, single
, view
->line
+ view
->lines
))
137 if (!stage_diff_write(&io
, diff_hdr
, chunk
) ||
138 !stage_diff_write(&io
, chunk
, view
->line
+ view
->lines
))
144 return chunk
? TRUE
: FALSE
;
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
);
156 if (!stage_apply_chunk(view
, chunk
, single
? line
: NULL
, FALSE
)) {
157 report("Failed to apply chunk");
161 } else if (!stage_status
.status
) {
164 for (line
= view
->line
; view_has_line(view
, line
); line
++)
165 if (line
->type
== stage_line_type
)
168 if (!status_update_files(view
, line
+ 1)) {
169 report("Failed to update files");
173 } else if (!status_update_file(&stage_status
, stage_line_type
)) {
174 report("Failed to update file");
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
);
190 if (!prompt_yesno("Are you sure you want to revert changes?"))
193 if (!stage_apply_chunk(view
, chunk
, NULL
, TRUE
)) {
194 report("Failed to revert chunk");
200 return status_revert(stage_status
.status
? &stage_status
: NULL
,
201 stage_line_type
, FALSE
);
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
];
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
))
220 chunk_line
= strdup(buf
);
225 from
->data
= chunk_line
;
230 if (!add_line_at(view
, after_lineno
++, buf
, LINE_DIFF_CHUNK
, strlen(buf
) + 1, FALSE
))
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
))
240 return view
->line
+ after_lineno
;
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
;
250 if (!chunk_start
|| !parse_chunk_header(&header
, chunk_start
->data
)) {
251 report("Failed to parse chunk header");
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
== '\\')
263 if (*chunk_line
== ' ') {
266 if (last_unchanged_line
< last_changed_line
)
267 last_unchanged_line
= pos
;
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
;
286 if (*chunk_line
== '-') {
288 last_changed_line
= pos
;
289 } else if (*chunk_line
== '+') {
291 last_changed_line
= pos
;
296 stage_insert_chunk(view
, &header
, chunk_start
, NULL
, NULL
);
298 report("Split the chunk in %d", chunks
+ 1);
300 report("The chunk cannot be split");
305 stage_request(struct view
*view
, enum request request
, struct line
*line
)
308 case REQ_STATUS_UPDATE
:
309 if (!stage_update(view
, line
, FALSE
))
313 case REQ_STATUS_REVERT
:
314 if (!stage_revert(view
, line
))
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");
324 if (line
->type
!= LINE_DIFF_DEL
&& line
->type
!= LINE_DIFF_ADD
) {
325 report("Please select a change to stage");
328 if (!stage_update(view
, line
, TRUE
))
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");
339 stage_split_chunk(view
, line
);
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.");
351 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
352 open_editor(stage_status
.new.name
, (line
- view
->line
) + 1);
354 open_editor(stage_status
.new.name
, diff_get_lineno(view
, line
));
359 /* Reload everything(including current branch information) ... */
364 if (stage_line_type
== LINE_STAT_UNTRACKED
) {
365 report("Nothing to blame here");
369 if (stage_status
.new.name
[0]) {
370 string_copy(view
->env
->file
, stage_status
.new.name
);
372 const char *file
= diff_get_pathname(view
, line
);
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)
385 return diff_common_enter(view
, request
, line
);
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
;
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
));
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
;
443 argv
= index_show_argv
;
447 case LINE_STAT_UNSTAGED
:
448 if (stage_status
.status
!= 'U')
449 argv
= files_show_argv
;
451 argv
= files_unmerged_argv
;
454 case LINE_STAT_UNTRACKED
:
456 view
->encoding
= get_path_encoding(stage_status
.old
.name
, default_encoding
);
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");
471 view
->dir
= repo
.cdup
;
472 return begin_update(view
, NULL
, NULL
, flags
);
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
))
486 return pager_read(view
, data
);
489 static struct view_ops stage_ops
= {
492 VIEW_DIFF_LIKE
| VIEW_REFRESH
,
493 sizeof(struct stage_state
),
504 /* vim: set ts=8 sw=8 noexpandtab: */