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.
17 #include "tig/parse.h"
18 #include "tig/options.h"
19 #include "tig/display.h"
24 /* The top of the path stack. */
25 static struct view_history tree_view_history
= { sizeof(char *) };
28 pop_tree_stack_entry(struct position
*position
)
30 char *path_position
= NULL
;
32 pop_view_history_state(&tree_view_history
, position
, &path_position
);
37 push_tree_stack_entry(struct view
*view
, const char *name
, struct position
*position
)
39 size_t pathlen
= strlen(view
->env
->directory
);
40 char *path_position
= view
->env
->directory
+ pathlen
;
41 struct view_state
*state
= push_view_history_state(&tree_view_history
, position
, &path_position
);
46 if (!string_format_from(view
->env
->directory
, &pathlen
, "%s/", name
)) {
47 pop_tree_stack_entry(NULL
);
51 clear_position(position
);
54 /* Parse output from git-ls-tree(1):
56 * 100644 blob 95925677ca47beb0b8cce7c0e0011bcc3f61470f 213045 tig.c
59 #define SIZEOF_TREE_ATTR \
60 STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
62 #define SIZEOF_TREE_MODE \
63 STRING_SIZE("100644 ")
65 #define TREE_ID_OFFSET \
66 STRING_SIZE("100644 blob ")
68 #define tree_path_is_parent(path) (!strcmp("..", (path)))
72 char commit
[SIZEOF_REV
];
74 struct time time
; /* Date from the author ident. */
75 const struct ident
*author
; /* Author of the commit. */
81 char commit
[SIZEOF_REV
];
82 const struct ident
*author
;
83 struct time author_time
;
88 tree_path(const struct line
*line
)
90 return ((struct tree_entry
*) line
->data
)->name
;
94 tree_compare_entry(const struct line
*line1
, const struct line
*line2
)
96 if (line1
->type
!= line2
->type
)
97 return line1
->type
== LINE_DIRECTORY
? -1 : 1;
98 return strcmp(tree_path(line1
), tree_path(line2
));
102 tree_get_column_data(struct view
*view
, const struct line
*line
, struct view_column_data
*column_data
)
104 const struct tree_entry
*entry
= line
->data
;
106 if (line
->type
== LINE_HEADER
)
109 column_data
->author
= entry
->author
;
110 column_data
->date
= &entry
->time
;
111 if (line
->type
!= LINE_DIRECTORY
)
112 column_data
->file_size
= &entry
->size
;
113 column_data
->id
= entry
->commit
;
114 column_data
->mode
= &entry
->mode
;
115 column_data
->file_name
= entry
->name
;
122 tree_entry(struct view
*view
, enum line_type type
, const char *path
,
123 const char *mode
, const char *id
, unsigned long size
)
125 bool custom
= type
== LINE_HEADER
|| tree_path_is_parent(path
);
126 struct tree_entry
*entry
;
127 struct line
*line
= add_line_alloc(view
, &entry
, type
, strlen(path
), custom
);
132 strncpy(entry
->name
, path
, strlen(path
));
134 entry
->mode
= strtoul(mode
, NULL
, 8);
136 string_copy_rev(entry
->id
, id
);
143 tree_read_date(struct view
*view
, struct buffer
*buf
, struct tree_state
*state
)
145 char *text
= buf
? buf
->data
: NULL
;
147 if (!text
&& state
->read_date
) {
148 state
->read_date
= FALSE
;
152 /* Find next entry to process */
153 const char *log_file
[] = {
154 "git", "log", encoding_arg
, "--no-color", "--pretty=raw",
155 "--cc", "--raw", view
->ops
->id
, "--", "%(directory)", NULL
159 tree_entry(view
, LINE_HEADER
, view
->env
->directory
, NULL
, NULL
, 0);
160 tree_entry(view
, LINE_DIRECTORY
, "..", "040000", view
->ref
, 0);
161 report("Tree is empty");
165 if (!begin_update(view
, repo
.cdup
, log_file
, OPEN_EXTRA
)) {
166 report("Failed to load tree data");
170 state
->read_date
= TRUE
;
173 } else if (*text
== 'c' && get_line_type(text
) == LINE_COMMIT
) {
174 string_copy_rev_from_commit_line(state
->commit
, text
);
176 } else if (*text
== 'a' && get_line_type(text
) == LINE_AUTHOR
) {
177 parse_author_line(text
+ STRING_SIZE("author "),
178 &state
->author
, &state
->author_time
);
180 } else if (*text
== ':') {
182 size_t annotated
= 1;
185 pos
= strrchr(text
, '\t');
189 if (*view
->env
->directory
&& !strncmp(text
, view
->env
->directory
, strlen(view
->env
->directory
)))
190 text
+= strlen(view
->env
->directory
);
191 pos
= strchr(text
, '/');
195 for (i
= 1; i
< view
->lines
; i
++) {
196 struct line
*line
= &view
->line
[i
];
197 struct tree_entry
*entry
= line
->data
;
199 annotated
+= !!entry
->author
;
200 if (entry
->author
|| strcmp(entry
->name
, text
))
203 string_copy_rev(entry
->commit
, state
->commit
);
204 entry
->author
= state
->author
;
205 entry
->time
= state
->author_time
;
207 view_column_info_update(view
, line
);
211 if (annotated
== view
->lines
)
218 tree_read(struct view
*view
, struct buffer
*buf
)
220 struct tree_state
*state
= view
->private;
221 struct tree_entry
*data
;
222 struct line
*entry
, *line
;
227 if (state
->read_date
|| !buf
)
228 return tree_read_date(view
, buf
, state
);
230 if (buf
->size
<= SIZEOF_TREE_ATTR
)
232 if (view
->lines
== 0 &&
233 !tree_entry(view
, LINE_HEADER
, view
->env
->directory
, NULL
, NULL
, 0))
236 size
= parse_size(buf
->data
+ SIZEOF_TREE_ATTR
);
237 path
= strchr(buf
->data
+ SIZEOF_TREE_ATTR
, '\t');
242 /* Strip the path part ... */
243 if (*view
->env
->directory
) {
244 size_t pathlen
= strlen(path
);
245 size_t striplen
= strlen(view
->env
->directory
);
247 if (pathlen
> striplen
)
248 memmove(path
, path
+ striplen
,
249 pathlen
- striplen
+ 1);
251 /* Insert "link" to parent directory. */
252 if (view
->lines
== 1 &&
253 !tree_entry(view
, LINE_DIRECTORY
, "..", "040000", view
->ref
, 0))
257 type
= buf
->data
[SIZEOF_TREE_MODE
] == 't' ? LINE_DIRECTORY
: LINE_FILE
;
258 entry
= tree_entry(view
, type
, path
, buf
->data
, buf
->data
+ TREE_ID_OFFSET
, size
);
262 view_column_info_update(view
, entry
);
264 /* Skip "Directory ..." and ".." line. */
265 for (line
= &view
->line
[1 + !!*view
->env
->directory
]; line
< entry
; line
++) {
266 if (tree_compare_entry(line
, entry
) <= 0)
269 memmove(line
+ 1, line
, (entry
- line
) * sizeof(*entry
));
273 line
->dirty
= line
->cleareol
= 1;
274 for (line
++; line
<= entry
; line
++) {
275 line
->dirty
= line
->cleareol
= 1;
281 /* Move the current line to the first tree entry. */
282 if (!check_position(&view
->prev_pos
) && !check_position(&view
->pos
))
283 goto_view_line(view
, 0, 1);
289 tree_draw(struct view
*view
, struct line
*line
, unsigned int lineno
)
291 struct tree_entry
*entry
= line
->data
;
293 if (line
->type
== LINE_HEADER
) {
294 draw_formatted(view
, line
->type
, "Directory path /%s", entry
->name
);
298 return view_column_draw(view
, line
, lineno
);
302 open_blob_editor(const char *id
, const char *name
, unsigned int lineno
)
304 const char *blob_argv
[] = { "git", "cat-file", "blob", id
, NULL
};
305 char file
[SIZEOF_STR
];
311 if (!string_format(file
, "%s/tigblob.XXXXXX.%s", get_temp_dir(), name
)) {
312 report("Temporary file name is too long");
316 fd
= mkstemps(file
, strlen(name
) + 1);
319 report("Failed to create temporary file");
320 else if (!io_run_append(blob_argv
, fd
))
321 report("Failed to save blob data to file");
323 open_editor(file
, lineno
);
329 tree_request(struct view
*view
, enum request request
, struct line
*line
)
331 enum open_flags flags
;
332 struct tree_entry
*entry
= line
->data
;
336 if (line
->type
!= LINE_FILE
) {
337 report("Blame only supported for files");
341 string_copy(view
->env
->ref
, view
->vid
);
345 if (line
->type
!= LINE_FILE
) {
346 report("Edit only supported for files");
347 } else if (!is_head_commit(view
->vid
)) {
348 open_blob_editor(entry
->id
, entry
->name
, 0);
350 open_editor(view
->env
->file
, 0);
356 if (!*view
->env
->directory
) {
357 /* quit view if at top of tree */
358 return REQ_VIEW_CLOSE
;
361 pop_tree_stack_entry(&view
->pos
);
372 /* Cleanup the stack if the tree view is at a different tree. */
373 if (!*view
->env
->directory
)
374 reset_view_history(&tree_view_history
);
376 switch (line
->type
) {
378 /* Depending on whether it is a subdirectory or parent link
379 * mangle the path buffer. */
380 if (tree_path_is_parent(entry
->name
) && *view
->env
->directory
) {
381 pop_tree_stack_entry(&view
->pos
);
384 const char *basename
= tree_path(line
);
386 push_tree_stack_entry(view
, basename
, &view
->pos
);
389 /* Trees and subtrees share the same ID, so they are not not
390 * unique like blobs. */
395 flags
= view_is_displayed(view
) ? OPEN_SPLIT
: OPEN_DEFAULT
;
396 open_blob_view(view
, flags
);
407 tree_select(struct view
*view
, struct line
*line
)
409 struct tree_entry
*entry
= line
->data
;
411 if (line
->type
== LINE_HEADER
) {
412 string_format(view
->ref
, "Files in /%s", view
->env
->directory
);
416 if (line
->type
== LINE_DIRECTORY
&& tree_path_is_parent(entry
->name
)) {
417 string_copy(view
->ref
, "Open parent directory");
418 view
->env
->blob
[0] = 0;
422 if (line
->type
== LINE_FILE
) {
423 string_copy_rev(view
->env
->blob
, entry
->id
);
424 string_format(view
->env
->file
, "%s%s", view
->env
->directory
, tree_path(line
));
427 string_copy_rev(view
->ref
, entry
->id
);
431 tree_open(struct view
*view
, enum open_flags flags
)
433 static const char *tree_argv
[] = {
434 "git", "ls-tree", "-l", "%(commit)", "%(directory)", NULL
437 if (string_rev_is_null(view
->env
->commit
)) {
438 report("No tree exists for this commit");
442 if (view
->lines
== 0 && repo
.prefix
[0]) {
443 char *pos
= repo
.prefix
;
445 while (pos
&& *pos
) {
446 char *end
= strchr(pos
, '/');
450 push_tree_stack_entry(view
, pos
, &view
->pos
);
458 } else if (strcmp(view
->vid
, view
->ops
->id
)) {
459 view
->env
->directory
[0] = 0;
462 return begin_update(view
, repo
.cdup
, tree_argv
, flags
);
465 static struct view_ops tree_ops
= {
468 VIEW_SEND_CHILD_ENTER
| VIEW_SORTABLE
,
469 sizeof(struct tree_state
),
477 view_column_bit(AUTHOR
) | view_column_bit(DATE
) |
478 view_column_bit(FILE_NAME
) | view_column_bit(FILE_SIZE
) |
479 view_column_bit(ID
) | view_column_bit(LINE_NUMBER
) |
480 view_column_bit(MODE
),
481 tree_get_column_data
,
486 /* vim: set ts=8 sw=8 noexpandtab: */