Migrate to Travis container-based infrastructure
[tig.git] / src / tree.c
blob0a808508c7491249cbf807c1d7045106639c910c
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.
14 #include "tig/util.h"
15 #include "tig/repo.h"
16 #include "tig/io.h"
17 #include "tig/parse.h"
18 #include "tig/options.h"
19 #include "tig/display.h"
20 #include "tig/view.h"
21 #include "tig/draw.h"
22 #include "tig/blob.h"
24 /* The top of the path stack. */
25 static struct view_history tree_view_history = { sizeof(char *) };
27 static void
28 pop_tree_stack_entry(struct position *position)
30 char *path_position = NULL;
32 pop_view_history_state(&tree_view_history, position, &path_position);
33 path_position[0] = 0;
36 static void
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);
43 if (!state)
44 return;
46 if (!string_format_from(view->env->directory, &pathlen, "%s/", name)) {
47 pop_tree_stack_entry(NULL);
48 return;
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)))
70 struct tree_entry {
71 char id[SIZEOF_REV];
72 char commit[SIZEOF_REV];
73 mode_t mode;
74 struct time time; /* Date from the author ident. */
75 const struct ident *author; /* Author of the commit. */
76 unsigned long size;
77 char name[1];
80 struct tree_state {
81 char commit[SIZEOF_REV];
82 const struct ident *author;
83 struct time author_time;
84 bool read_date;
87 static const char *
88 tree_path(const struct line *line)
90 return ((struct tree_entry *) line->data)->name;
93 static int
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));
101 static bool
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)
107 return false;
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;
117 return true;
121 static struct line *
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);
129 if (!line)
130 return NULL;
132 strncpy(entry->name, path, strlen(path));
133 if (mode)
134 entry->mode = strtoul(mode, NULL, 8);
135 if (id)
136 string_copy_rev(entry->id, id);
137 entry->size = size;
139 return line;
142 static bool
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;
149 return true;
151 } else if (!text) {
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
158 if (!view->lines) {
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");
162 return true;
165 if (!begin_update(view, repo.cdup, log_file, OPEN_EXTRA)) {
166 report("Failed to load tree data");
167 return true;
170 state->read_date = true;
171 return false;
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 == ':') {
181 char *pos;
182 size_t annotated = 1;
183 size_t i;
185 pos = strrchr(text, '\t');
186 if (!pos)
187 return true;
188 text = pos + 1;
189 if (*view->env->directory && !strncmp(text, view->env->directory, strlen(view->env->directory)))
190 text += strlen(view->env->directory);
191 pos = strchr(text, '/');
192 if (pos)
193 *pos = 0;
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))
201 continue;
203 string_copy_rev(entry->commit, state->commit);
204 entry->author = state->author;
205 entry->time = state->author_time;
206 line->dirty = 1;
207 view_column_info_update(view, line);
208 break;
211 if (annotated == view->lines)
212 io_kill(view->pipe);
214 return true;
217 static bool
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;
223 enum line_type type;
224 char *path;
225 size_t size;
227 if (state->read_date || !buf)
228 return tree_read_date(view, buf, state);
230 if (buf->size <= SIZEOF_TREE_ATTR)
231 return false;
232 if (view->lines == 0 &&
233 !tree_entry(view, LINE_HEADER, view->env->directory, NULL, NULL, 0))
234 return false;
236 size = parse_size(buf->data + SIZEOF_TREE_ATTR);
237 path = strchr(buf->data + SIZEOF_TREE_ATTR, '\t');
238 if (!path)
239 return false;
240 path++;
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))
254 return false;
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);
259 if (!entry)
260 return false;
261 data = entry->data;
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)
267 continue;
269 memmove(line + 1, line, (entry - line) * sizeof(*entry));
271 line->data = data;
272 line->type = type;
273 line->dirty = line->cleareol = 1;
274 for (line++; line <= entry; line++) {
275 line->dirty = line->cleareol = 1;
276 line->lineno++;
278 return true;
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);
285 return true;
288 static bool
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);
295 return true;
298 return view_column_draw(view, line, lineno);
301 void
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];
306 int fd;
308 if (!name)
309 name = "unknown";
311 if (!string_format(file, "%s/tigblob.XXXXXX.%s", get_temp_dir(), name)) {
312 report("Temporary file name is too long");
313 return;
316 fd = mkstemps(file, strlen(name) + 1);
318 if (fd == -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");
322 else
323 open_editor(file, lineno);
324 if (fd != -1)
325 unlink(file);
328 static enum request
329 tree_request(struct view *view, enum request request, struct line *line)
331 enum open_flags flags;
332 struct tree_entry *entry = line->data;
334 switch (request) {
335 case REQ_VIEW_BLAME:
336 if (line->type != LINE_FILE) {
337 report("Blame only supported for files");
338 return REQ_NONE;
341 string_copy(view->env->ref, view->vid);
342 return request;
344 case REQ_EDIT:
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);
349 } else {
350 open_editor(view->env->file, 0);
352 return REQ_NONE;
354 case REQ_PARENT:
355 case REQ_BACK:
356 if (!*view->env->directory) {
357 /* quit view if at top of tree */
358 return REQ_VIEW_CLOSE;
360 /* fake 'cd ..' */
361 pop_tree_stack_entry(&view->pos);
362 reload_view(view);
363 break;
365 case REQ_ENTER:
366 break;
368 default:
369 return request;
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) {
377 case LINE_DIRECTORY:
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);
383 } else {
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. */
391 reload_view(view);
392 break;
394 case LINE_FILE:
395 flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
396 open_blob_view(view, flags);
397 break;
399 default:
400 return REQ_NONE;
403 return REQ_NONE;
406 static void
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);
413 return;
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;
419 return;
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);
430 static bool
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");
439 return false;
442 if (view->lines == 0 && repo.prefix[0]) {
443 char *pos = repo.prefix;
445 while (pos && *pos) {
446 char *end = strchr(pos, '/');
448 if (end)
449 *end = 0;
450 push_tree_stack_entry(view, pos, &view->pos);
451 pos = end;
452 if (end) {
453 *end = '/';
454 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 = {
466 "file",
467 argv_env.commit,
468 VIEW_SEND_CHILD_ENTER | VIEW_SORTABLE,
469 sizeof(struct tree_state),
470 tree_open,
471 tree_read,
472 tree_draw,
473 tree_request,
474 view_column_grep,
475 tree_select,
476 NULL,
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,
484 DEFINE_VIEW(tree);
486 /* vim: set ts=8 sw=8 noexpandtab: */