2 * Copyright (c) 2018 Stefan Sperling <stsp@openbsd.org>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #include <sys/queue.h>
28 #include "got_error.h"
29 #include "got_object.h"
30 #include "got_reference.h"
31 #include "got_repository.h"
35 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
46 const struct got_error *(*cmd_main)(int, char *[]);
47 void (*cmd_usage)(void);
48 enum tog_view_id view;
52 __dead void usage(void);
53 __dead void usage_log(void);
54 __dead void usage_diff(void);
55 __dead void usage_blame(void);
57 const struct got_error* cmd_log(int, char *[]);
58 const struct got_error* cmd_diff(int, char *[]);
59 const struct got_error* cmd_blame(int, char *[]);
61 struct tog_cmd tog_commands[] = {
62 { "log", cmd_log, usage_log, TOG_VIEW_LOG,
63 "show repository history" },
64 { "diff", cmd_diff, usage_diff, TOG_VIEW_DIFF,
65 "compare files and directories" },
66 { "blame", cmd_blame, usage_blame, TOG_VIEW_BLAME,
67 "show line-by-line file history" },
72 PANEL *tog_main_panel;
73 static struct tog_log_view {
82 fprintf(stderr, "usage: %s log [-c commit] [repository-path]\n",
87 static const struct got_error *
88 draw_commit(struct got_commit_object *commit, struct got_object_id *id,
89 struct got_repository *repo)
91 const struct got_error *err = NULL;
92 char *logmsg0 = NULL, *logmsg = NULL;
93 char *author0 = NULL, *author = NULL;
94 char *newline, *smallerthan;
99 err = got_object_id_str(&id_str, id);
102 logmsg0 = strdup(commit->logmsg);
103 if (logmsg0 == NULL) {
104 err = got_error_from_errno();
108 while (*logmsg == '\n')
110 newline = strchr(logmsg, '\n');
114 author0 = strdup(commit->author);
115 if (author0 == NULL) {
116 err = got_error_from_errno();
120 smallerthan = strchr(author, '<');
124 char *at = strchr(author, '@');
129 if (asprintf(&line, "%.8s %.20s %s", id_str, author, logmsg) == -1) {
130 err = got_error_from_errno();
134 waddstr(tog_log_view.window, line);
136 while (len < COLS - 1) {
137 waddch(tog_log_view.window, ' ');
140 waddch(tog_log_view.window, '\n');
148 struct commit_queue_entry {
149 TAILQ_ENTRY(commit_queue_entry) entry;
150 struct got_object_id *id;
151 struct got_commit_object *commit;
154 static const struct got_error *
155 draw_commits(struct got_object *root_obj, struct got_object_id *root_id,
156 struct got_repository *repo, int selected)
158 const struct got_error *err;
159 struct got_commit_object *root_commit;
160 TAILQ_HEAD(, commit_queue_entry) commits;
161 struct commit_queue_entry *entry;
164 TAILQ_INIT(&commits);
166 err = got_object_commit_open(&root_commit, repo, root_obj);
170 entry = calloc(1, sizeof(*entry));
172 return got_error_from_errno();
173 entry->id = got_object_id_dup(root_id);
174 if (entry->id == NULL) {
175 err = got_error_from_errno();
179 entry->commit = root_commit;
180 TAILQ_INSERT_HEAD(&commits, entry, entry);
182 wclear(tog_log_view.window);
184 while (!TAILQ_EMPTY(&commits) && ncommits < LINES) {
185 struct got_parent_id *pid;
186 struct got_object *obj;
187 struct got_commit_object *pcommit;
188 struct commit_queue_entry *pentry;
190 entry = TAILQ_FIRST(&commits);
192 if (ncommits == selected)
193 wstandout(tog_log_view.window);
194 err = draw_commit(entry->commit, entry->id, repo);
195 if (ncommits == selected)
196 wstandend(tog_log_view.window);
201 if (entry->commit->nparents == 0)
204 /* Follow the first parent (TODO: handle merge commits). */
205 pid = SIMPLEQ_FIRST(&entry->commit->parent_ids);
206 err = got_object_open(&obj, repo, pid->id);
209 if (got_object_get_type(obj) != GOT_OBJ_TYPE_COMMIT) {
210 err = got_error(GOT_ERR_OBJ_TYPE);
214 err = got_object_commit_open(&pcommit, repo, obj);
215 got_object_close(obj);
219 pentry = calloc(1, sizeof(*pentry));
220 if (pentry == NULL) {
221 err = got_error_from_errno();
222 got_object_commit_close(pcommit);
225 pentry->id = got_object_id_dup(pid->id);
226 if (pentry->id == NULL) {
227 err = got_error_from_errno();
228 got_object_commit_close(pcommit);
231 pentry->commit = pcommit;
232 TAILQ_INSERT_TAIL(&commits, pentry, entry);
234 TAILQ_REMOVE(&commits, entry, entry);
235 got_object_commit_close(entry->commit);
240 while (!TAILQ_EMPTY(&commits)) {
241 entry = TAILQ_FIRST(&commits);
242 TAILQ_REMOVE(&commits, entry, entry);
243 got_object_commit_close(entry->commit);
254 static const struct got_error *
255 show_log_view(struct got_object_id *start_id, struct got_repository *repo)
257 const struct got_error *err = NULL;
258 struct got_object *obj;
259 int ch, done = 0, selected = 0;
260 struct got_object_id *id = start_id;
262 if (tog_log_view.window == NULL) {
263 tog_log_view.window = newwin(0, 0, 0, 0);
264 if (tog_log_view.window == NULL)
265 return got_error_from_errno();
266 keypad(tog_log_view.window, TRUE);
268 if (tog_log_view.panel == NULL) {
269 tog_log_view.panel = new_panel(tog_log_view.window);
270 if (tog_log_view.panel == NULL)
271 return got_error_from_errno();
274 err = got_object_open(&obj, repo, id);
277 if (got_object_get_type(obj) != GOT_OBJ_TYPE_COMMIT) {
278 err = got_error(GOT_ERR_OBJ_TYPE);
283 err = draw_commits(obj, id, repo, selected);
287 nodelay(stdscr, FALSE);
288 ch = wgetch(tog_log_view.window);
300 if (selected < LINES - 1)
306 nodelay(stdscr, TRUE);
309 got_object_close(obj);
313 const struct got_error *
314 cmd_log(int argc, char *argv[])
316 const struct got_error *error;
317 struct got_repository *repo;
318 struct got_object_id *id = NULL;
319 char *repo_path = NULL;
320 char *start_commit = NULL;
324 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
328 while ((ch = getopt(argc, argv, "c:")) != -1) {
331 start_commit = optarg;
343 repo_path = getcwd(NULL, 0);
344 if (repo_path == NULL)
345 return got_error_from_errno();
346 } else if (argc == 1) {
347 repo_path = realpath(argv[0], NULL);
348 if (repo_path == NULL)
349 return got_error_from_errno();
353 error = got_repo_open(&repo, repo_path);
358 if (start_commit == NULL) {
359 struct got_reference *head_ref;
360 error = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
363 error = got_ref_resolve(&id, repo, head_ref);
364 got_ref_close(head_ref);
368 struct got_object *obj;
369 error = got_object_open_by_id_str(&obj, repo, start_commit);
371 id = got_object_get_id(obj);
373 error = got_error_from_errno();
378 error = show_log_view(id, repo);
380 got_repo_close(repo);
388 fprintf(stderr, "usage: %s diff [repository-path] object1 object2\n",
393 const struct got_error *
394 cmd_diff(int argc, char *argv[])
396 return got_error(GOT_ERR_NOT_IMPL);
403 fprintf(stderr, "usage: %s blame [repository-path] blob-object\n",
408 const struct got_error *
409 cmd_blame(int argc, char *argv[])
411 return got_error(GOT_ERR_NOT_IMPL);
414 static const struct got_error *
421 intrflush(stdscr, FALSE);
422 keypad(stdscr, TRUE);
424 tog_main_win = newwin(0, 0, 0, 0);
425 if (tog_main_win == NULL)
426 return got_error_from_errno();
427 tog_main_panel = new_panel(tog_main_win);
428 if (tog_main_panel == NULL)
429 return got_error_from_errno();
439 fprintf(stderr, "usage: %s [-h] [command] [arg ...]\n\n"
440 "Available commands:\n", getprogname());
441 for (i = 0; i < nitems(tog_commands); i++) {
442 struct tog_cmd *cmd = &tog_commands[i];
443 fprintf(stderr, " %s: %s\n", cmd->name, cmd->descr);
449 make_argv(const char *arg0, const char *arg1)
452 int argc = (arg1 == NULL ? 1 : 2);
454 argv = calloc(argc, sizeof(char *));
457 argv[0] = strdup(arg0);
461 argv[1] = strdup(arg1);
470 main(int argc, char *argv[])
472 const struct got_error *error = NULL;
473 struct tog_cmd *cmd = NULL;
475 char **cmd_argv = NULL;
477 setlocale(LC_ALL, "");
479 while ((ch = getopt(argc, argv, "h")) != -1) {
496 /* Build an argument vector which runs a default command. */
497 cmd = &tog_commands[0];
498 cmd_argv = make_argv(cmd->name, NULL);
503 /* Did the user specific a command? */
504 for (i = 0; i < nitems(tog_commands); i++) {
505 if (strncmp(tog_commands[i].name, argv[0],
506 strlen(argv[0])) == 0) {
507 cmd = &tog_commands[i];
509 tog_commands[i].cmd_usage();
514 /* Did the user specify a repository? */
515 char *repo_path = realpath(argv[0], NULL);
517 struct got_repository *repo;
518 error = got_repo_open(&repo, repo_path);
520 got_repo_close(repo);
522 error = got_error(GOT_ERR_NOT_GIT_REPO);
525 fprintf(stderr, "%s: unknown command '%s'\n",
526 getprogname(), argv[0]);
529 cmd = &tog_commands[0];
530 cmd_argv = make_argv(cmd->name, repo_path);
536 error = init_curses();
538 fprintf(stderr, "cannot initialize ncurses: %s\n", error->msg);
542 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
549 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);