Files
gbuild/changelog.txt
Schmidt Peter 939232e72e Added features
Added: 
Project Overview TUI 
Build Cache / Dirty Detection
Post-Build Hooks
gbuild status
2026-05-23 21:03:54 +02:00

1353 lines
49 KiB
Plaintext

diff --git a/src/logger.c b/src/logger.c
index de2eead..997a975 100644
--- a/src/logger.c
+++ b/src/logger.c
@@ -71,9 +71,9 @@ typedef enum { LVL_INFO, LVL_OK, LVL_WARN, LVL_ERROR } LogLevel;
static const char *level_tag(LogLevel lv)
{
switch (lv) {
- case LVL_INFO: return "[INFO ]";
- case LVL_OK: return "[OK ]";
- case LVL_WARN: return "[WARN ]";
+ case LVL_INFO: return "[INFO]";
+ case LVL_OK: return "[OK]";
+ case LVL_WARN: return "[WARN]";
case LVL_ERROR: return "[ERROR]";
}
return "[ ]";
diff --git a/bin/gbuild b/bin/gbuild
deleted file mode 100755
index dc9ad62..0000000
Binary files a/bin/gbuild and /dev/null differ
diff --git a/bin/gconfig b/bin/gconfig
deleted file mode 100755
index 2b1ee61..0000000
Binary files a/bin/gconfig and /dev/null differ
diff --git a/dist/gbuild-1.0.0-1.x86_64.rpm b/dist/gbuild-1.0.0-1.x86_64.rpm
deleted file mode 100644
index 2ac5ced..0000000
Binary files a/dist/gbuild-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild-1.0.0-x86_64-bin.tar.gz b/dist/gbuild-1.0.0-x86_64-bin.tar.gz
deleted file mode 100644
index 8e410d7..0000000
Binary files a/dist/gbuild-1.0.0-x86_64-bin.tar.gz and /dev/null differ
diff --git a/dist/gbuild-1.0.0.tar.gz b/dist/gbuild-1.0.0.tar.gz
deleted file mode 100644
index 4fea580..0000000
Binary files a/dist/gbuild-1.0.0.tar.gz and /dev/null differ
diff --git a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm
deleted file mode 100644
index c0bcf34..0000000
Binary files a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild_1.0.0_amd64.deb b/dist/gbuild_1.0.0_amd64.deb
deleted file mode 100644
index 58b5e08..0000000
Binary files a/dist/gbuild_1.0.0_amd64.deb and /dev/null differ
diff --git a/bin/gbuild b/bin/gbuild
deleted file mode 100755
index dc9ad62..0000000
Binary files a/bin/gbuild and /dev/null differ
diff --git a/bin/gconfig b/bin/gconfig
deleted file mode 100755
index 2b1ee61..0000000
Binary files a/bin/gconfig and /dev/null differ
diff --git a/changelog.txt b/changelog.txt
index 2367a82..374e581 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -15,3 +15,31 @@ index de2eead..997a975 100644
case LVL_ERROR: return "[ERROR]";
}
return "[ ]";
+diff --git a/bin/gbuild b/bin/gbuild
+deleted file mode 100755
+index dc9ad62..0000000
+Binary files a/bin/gbuild and /dev/null differ
+diff --git a/bin/gconfig b/bin/gconfig
+deleted file mode 100755
+index 2b1ee61..0000000
+Binary files a/bin/gconfig and /dev/null differ
+diff --git a/dist/gbuild-1.0.0-1.x86_64.rpm b/dist/gbuild-1.0.0-1.x86_64.rpm
+deleted file mode 100644
+index 2ac5ced..0000000
+Binary files a/dist/gbuild-1.0.0-1.x86_64.rpm and /dev/null differ
+diff --git a/dist/gbuild-1.0.0-x86_64-bin.tar.gz b/dist/gbuild-1.0.0-x86_64-bin.tar.gz
+deleted file mode 100644
+index 8e410d7..0000000
+Binary files a/dist/gbuild-1.0.0-x86_64-bin.tar.gz and /dev/null differ
+diff --git a/dist/gbuild-1.0.0.tar.gz b/dist/gbuild-1.0.0.tar.gz
+deleted file mode 100644
+index 4fea580..0000000
+Binary files a/dist/gbuild-1.0.0.tar.gz and /dev/null differ
+diff --git a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm
+deleted file mode 100644
+index c0bcf34..0000000
+Binary files a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm and /dev/null differ
+diff --git a/dist/gbuild_1.0.0_amd64.deb b/dist/gbuild_1.0.0_amd64.deb
+deleted file mode 100644
+index 58b5e08..0000000
+Binary files a/dist/gbuild_1.0.0_amd64.deb and /dev/null differ
diff --git a/dist/gbuild-1.0.0-1.x86_64.rpm b/dist/gbuild-1.0.0-1.x86_64.rpm
deleted file mode 100644
index 2ac5ced..0000000
Binary files a/dist/gbuild-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild-1.0.0-x86_64-bin.tar.gz b/dist/gbuild-1.0.0-x86_64-bin.tar.gz
deleted file mode 100644
index 8e410d7..0000000
Binary files a/dist/gbuild-1.0.0-x86_64-bin.tar.gz and /dev/null differ
diff --git a/dist/gbuild-1.0.0.tar.gz b/dist/gbuild-1.0.0.tar.gz
deleted file mode 100644
index 4fea580..0000000
Binary files a/dist/gbuild-1.0.0.tar.gz and /dev/null differ
diff --git a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm
deleted file mode 100644
index c0bcf34..0000000
Binary files a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild_1.0.0_amd64.deb b/dist/gbuild_1.0.0_amd64.deb
deleted file mode 100644
index 58b5e08..0000000
Binary files a/dist/gbuild_1.0.0_amd64.deb and /dev/null differ
diff --git a/src/tui.c b/src/tui.c
index b07ea65..a2465df 100644
--- a/src/tui.c
+++ b/src/tui.c
@@ -247,10 +247,10 @@ void tui_log_browser(const char *log_dir)
if (has_colors()) {
start_color();
use_default_colors();
- init_pair(1, COLOR_BLACK, COLOR_CYAN); /* selected row */
- init_pair(2, COLOR_CYAN, -1); /* border */
+ init_pair(1, COLOR_BLACK, COLOR_WHITE); /* selected row */
+ init_pair(2, COLOR_WHITE, -1); /* border */
init_pair(3, COLOR_WHITE, -1); /* normal row */
- init_pair(4, COLOR_YELLOW,-1); /* preview text */
+ init_pair(4, COLOR_WHITE,-1); /* preview text */
init_pair(5, COLOR_RED, -1); /* status/warn */
}
@@ -282,7 +282,7 @@ void tui_log_browser(const char *log_dir)
/* ---- hint bar ---- */
if (has_colors()) attron(COLOR_PAIR(2));
mvprintw(rows - 2, 0,
- " \u2191\u2193 navigate Enter open in less d delete q quit "
+ " J - DOWN K - UP navigate Enter - open in less d - delete q - quit "
" ");
if (has_colors()) attroff(COLOR_PAIR(2));
diff --git a/src/tui.c b/src/tui.c
index a2465df..103babc 100644
--- a/src/tui.c
+++ b/src/tui.c
@@ -282,7 +282,7 @@ void tui_log_browser(const char *log_dir)
/* ---- hint bar ---- */
if (has_colors()) attron(COLOR_PAIR(2));
mvprintw(rows - 2, 0,
- " J - DOWN K - UP navigate Enter - open in less d - delete q - quit "
+ " K - UP J - DOWN to navigate Enter - open in less d - delete q - quit (Press J to start) "
" ");
if (has_colors()) attroff(COLOR_PAIR(2));
@@ -389,10 +389,10 @@ void tui_log_browser(const char *log_dir)
if (has_colors()) {
start_color();
use_default_colors();
- init_pair(1, COLOR_BLACK, COLOR_CYAN);
- init_pair(2, COLOR_CYAN, -1);
+ init_pair(1, COLOR_BLACK, COLOR_WHITE);
+ init_pair(2, COLOR_WHITE, -1);
init_pair(3, COLOR_WHITE, -1);
- init_pair(4, COLOR_YELLOW,-1);
+ init_pair(4, COLOR_WHITE,-1);
init_pair(5, COLOR_RED, -1);
}
clear();
diff --git a/bin/gbuild b/bin/gbuild
index 3585f82..09371a5 100755
Binary files a/bin/gbuild and b/bin/gbuild differ
diff --git a/dist/gbuild-1.0.0-1.x86_64.rpm b/dist/gbuild-1.0.0-1.x86_64.rpm
deleted file mode 100644
index 2343555..0000000
Binary files a/dist/gbuild-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild-1.0.0-x86_64-bin.tar.gz b/dist/gbuild-1.0.0-x86_64-bin.tar.gz
deleted file mode 100644
index cdbc281..0000000
Binary files a/dist/gbuild-1.0.0-x86_64-bin.tar.gz and /dev/null differ
diff --git a/dist/gbuild-1.0.0.tar.gz b/dist/gbuild-1.0.0.tar.gz
deleted file mode 100644
index c8fea5c..0000000
Binary files a/dist/gbuild-1.0.0.tar.gz and /dev/null differ
diff --git a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm
deleted file mode 100644
index 04797e8..0000000
Binary files a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild_1.0.0_amd64.deb b/dist/gbuild_1.0.0_amd64.deb
deleted file mode 100644
index a0b5475..0000000
Binary files a/dist/gbuild_1.0.0_amd64.deb and /dev/null differ
diff --git a/src/tui.c b/src/tui.c
index 103babc..1442086 100644
--- a/src/tui.c
+++ b/src/tui.c
@@ -189,7 +189,7 @@ static int load_log_entries(const char *log_dir,
* Returns the number of lines read.
* Caller owns the memory and should free each pointer.
*/
-#define PREVIEW_MAX 128
+#define PREVIEW_MAX 200
static int read_tail(const char *path, char **lines, int max_lines)
{
@@ -279,11 +279,6 @@ void tui_log_browser(const char *log_dir)
attroff(A_BOLD);
clrtoeol();
- /* ---- hint bar ---- */
- if (has_colors()) attron(COLOR_PAIR(2));
- mvprintw(rows - 2, 0,
- " K - UP J - DOWN to navigate Enter - open in less d - delete q - quit (Press J to start) "
- " ");
if (has_colors()) attroff(COLOR_PAIR(2));
/* ---- vertical divider ---- */
@@ -351,7 +346,13 @@ void tui_log_browser(const char *log_dir)
mvwprintw(wright, 2, 2, "(no log selected)");
}
wrefresh(wright);
- refresh();
+
+ /* ---- hint bar ---- */
+ if (has_colors()) attron(COLOR_PAIR(2));
+ mvprintw(rows - 2, 0,
+ " K - UP J - DOWN to navigate Enter - open in less d - delete q - quit (Press J to start) "
+ " ");
+ refresh();
/* ---- input ---- */
int ch = getch();
diff --git a/bin/gbuild b/bin/gbuild
index 09371a5..e95c8a0 100755
Binary files a/bin/gbuild and b/bin/gbuild differ
diff --git a/dist/gbuild-1.0.0-1.x86_64.rpm b/dist/gbuild-1.0.0-1.x86_64.rpm
index 6df2d36..611a7ef 100644
Binary files a/dist/gbuild-1.0.0-1.x86_64.rpm and b/dist/gbuild-1.0.0-1.x86_64.rpm differ
diff --git a/dist/gbuild-1.0.0-x86_64-bin.tar.gz b/dist/gbuild-1.0.0-x86_64-bin.tar.gz
index 60a5227..a8e0d0e 100644
Binary files a/dist/gbuild-1.0.0-x86_64-bin.tar.gz and b/dist/gbuild-1.0.0-x86_64-bin.tar.gz differ
diff --git a/dist/gbuild-1.0.0.tar.gz b/dist/gbuild-1.0.0.tar.gz
index 3a83082..6010827 100644
Binary files a/dist/gbuild-1.0.0.tar.gz and b/dist/gbuild-1.0.0.tar.gz differ
diff --git a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm
index 813ee98..ba7fe6d 100644
Binary files a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm and b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm differ
diff --git a/dist/gbuild_1.0.0_amd64.deb b/dist/gbuild_1.0.0_amd64.deb
index 6f52b7e..a365173 100644
Binary files a/dist/gbuild_1.0.0_amd64.deb and b/dist/gbuild_1.0.0_amd64.deb differ
diff --git a/src/tui.c b/src/tui.c
index b5df72f..c5e660a 100644
--- a/src/tui.c
+++ b/src/tui.c
@@ -61,7 +61,7 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
/* header hint */
attron(A_DIM);
mvprintw(win_y - 2, win_x,
- "Select make target K - UP J - DOWN to navigate Enter - select \u00b7 q - cancel");
+ "Select make target \u2191\u2193 navigate \u00b7 Enter select \u00b7 q cancel");
attroff(A_DIM);
refresh();
@@ -189,7 +189,7 @@ static int load_log_entries(const char *log_dir,
* Returns the number of lines read.
* Caller owns the memory and should free each pointer.
*/
-#define PREVIEW_MAX 200
+#define PREVIEW_MAX 128
static int read_tail(const char *path, char **lines, int max_lines)
{
@@ -247,24 +247,24 @@ void tui_log_browser(const char *log_dir)
if (has_colors()) {
start_color();
use_default_colors();
- init_pair(1, COLOR_BLACK, COLOR_WHITE); /* selected row */
- init_pair(2, COLOR_WHITE, -1); /* border */
- init_pair(3, COLOR_WHITE, -1); /* normal row */
- init_pair(4, COLOR_WHITE,-1); /* preview text */
- init_pair(5, COLOR_RED, -1); /* status/warn */
+ init_pair(1, COLOR_BLACK, COLOR_WHITE);
+ init_pair(2, COLOR_WHITE, -1);
+ init_pair(3, COLOR_WHITE, -1);
+ init_pair(4, COLOR_WHITE, -1);
+ init_pair(5, COLOR_RED, -1);
}
int rows, cols;
getmaxyx(stdscr, rows, cols);
- /* layout: left pane = 40% width, right pane = rest */
int left_w = cols * 40 / 100;
if (left_w < 30) left_w = 30;
- int right_w = cols - left_w - 1; /* -1 for divider */
- int pane_h = rows - 3; /* -3 for title + hint bar */
+ int right_w = cols - left_w - 1;
+ int pane_h = rows - 3;
WINDOW *wleft = newwin(pane_h, left_w, 1, 0);
WINDOW *wright = newwin(pane_h, right_w, 1, left_w + 1);
+ keypad(wleft, TRUE);
int cur = 0, scroll = 0;
int done = 0;
@@ -279,6 +279,11 @@ void tui_log_browser(const char *log_dir)
attroff(A_BOLD);
clrtoeol();
+ /* ---- hint bar ---- */
+ if (has_colors()) attron(COLOR_PAIR(2));
+ mvprintw(rows - 2, 0,
+ " K - UP J - DOWN to navigate Enter - open in less d - delete q - quit "
+ " ");
if (has_colors()) attroff(COLOR_PAIR(2));
/* ---- vertical divider ---- */
@@ -299,7 +304,6 @@ void tui_log_browser(const char *log_dir)
} else {
for (int i = 0; i < visible && (i + scroll) < count; i++) {
int idx = i + scroll;
- /* trim the .log extension for display */
char disp[256];
strncpy(disp, entries[idx].name, sizeof(disp) - 1);
char *dot = strrchr(disp, '.');
@@ -318,7 +322,7 @@ void tui_log_browser(const char *log_dir)
}
}
}
- wrefresh(wleft);
+ /* NOTE: no wrefresh(wleft) here — flush everything together below */
/* ---- right pane: preview ---- */
werase(wright);
@@ -345,17 +349,17 @@ void tui_log_browser(const char *log_dir)
} else {
mvwprintw(wright, 2, 2, "(no log selected)");
}
- wrefresh(wright);
- refresh();
- /* ---- hint bar ---- */
- if (has_colors()) attron(COLOR_PAIR(2));
- mvprintw(rows - 2, 0,
- " K - UP J - DOWN to navigate Enter - open in less d - delete q - quit (Press J to start) "
- " ");
-
+ /* Flush all layers atomically: stdscr first (background), then the two
+ * panes on top. doupdate() does one physical write so nothing
+ * overwrites anything else. */
+ wnoutrefresh(stdscr);
+ wnoutrefresh(wleft);
+ wnoutrefresh(wright);
+ doupdate();
+
/* ---- input ---- */
- int ch = getch();
+ int ch = wgetch(wleft);
switch (ch) {
case KEY_UP:
case 'k':
@@ -381,7 +385,6 @@ void tui_log_browser(const char *log_dir)
snprintf(cmd, sizeof(cmd),
"less \"%s\"", entries[cur].path);
system(cmd);
- /* re-init after less exits */
initscr();
noecho();
cbreak();
@@ -391,24 +394,24 @@ void tui_log_browser(const char *log_dir)
start_color();
use_default_colors();
init_pair(1, COLOR_BLACK, COLOR_WHITE);
- init_pair(2, COLOR_WHITE, -1);
+ init_pair(2, COLOR_WHITE, -1);
init_pair(3, COLOR_WHITE, -1);
- init_pair(4, COLOR_WHITE,-1);
+ init_pair(4, COLOR_WHITE, -1);
init_pair(5, COLOR_RED, -1);
}
+ touchwin(wleft);
+ touchwin(wright);
clear();
}
break;
case 'd':
if (count > 0 && cur < count) {
- /* confirm */
mvprintw(rows - 1, 0,
"Delete %s? [y/N] ", entries[cur].name);
refresh();
int confirm = getch();
if (confirm == 'y' || confirm == 'Y') {
remove(entries[cur].path);
- /* reload list */
count = load_log_entries(log_dir, entries, MAX_LOGS);
if (cur >= count) cur = count > 0 ? count - 1 : 0;
if (scroll > cur) scroll = cur;
@@ -417,7 +420,7 @@ void tui_log_browser(const char *log_dir)
}
break;
case 'q':
- case 27: /* ESC */
+ case 27:
done = 1;
break;
}
diff --git a/Makefile b/Makefile
index 2c21f21..b2e3f5c 100644
--- a/Makefile
+++ b/Makefile
@@ -30,6 +30,7 @@ COMMON_SRCS = src/config.c
GBUILD_SRCS = \
src/gbuild.c \
src/git_ops.c \
+ src/index.c \
src/logger.c \
src/make_ops.c \
src/tui.c \
diff --git a/README.md b/README.md
index 0a8ef78..13e5301 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# gbuild
+# gbuild — C rewrite
A C rewrite of the original [gbuild](https://spdlab.hu/gbuild) bash tool.
Clone, pull, pick a target, build — all from one command.
@@ -18,7 +18,7 @@ Clone, pull, pick a target, build — all from one command.
## Dependencies
-| Library | Purpose |
+| Library | Purpose |
|----------|------------------------------|
| ncurses | TUI for picker & log browser |
| git | Clone / pull |
@@ -29,14 +29,9 @@ Clone, pull, pick a target, build — all from one command.
## Build
-### Linux
-
```sh
-# Install these packages with your package manager
-libncurses-dev make
-
-# Clone this repository
-git clone https://github.com/jokerz/gbuild.git
+# Install ncurses dev headers if needed
+sudo apt install libncurses-dev
# Build both binaries into bin/
make
@@ -70,7 +65,7 @@ gbuild myproject
[git]
GIT_URL = http://localhost:3000
-GIT_USER = Username
+GIT_USER =
[auth]
# Use token-based OR password-based auth; leave the other blank
diff --git a/include/git_ops.h b/include/git_ops.h
index bf22f63..bd5ae79 100644
--- a/include/git_ops.h
+++ b/include/git_ops.h
@@ -16,3 +16,7 @@ int git_clone(const char *base_url, const char *user,
int git_pull(const char *repo_path, Logger *log);
#endif /* GIT_OPS_H */
+
+/* Read the current HEAD hash of repo_path into out (at least 41 bytes).
+ * Returns 0 on success, -1 on failure. */
+int git_head_hash(const char *repo_path, char *out, size_t n);
diff --git a/include/tui.h b/include/tui.h
index a2c035f..ca6cd1d 100644
--- a/include/tui.h
+++ b/include/tui.h
@@ -13,4 +13,17 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size
* Keys: ↑↓ navigate Enter open in less d delete q quit */
void tui_log_browser(const char *log_dir);
+#include "index.h"
+
+/* Full-screen project overview.
+ * Lists all projects in idx with last-build status, timestamp and HEAD hash.
+ * On Enter, fills selected_project and returns the row index.
+ * Returns -1 if the user quit without selecting.
+ * Keys: ↑↓ navigate Enter build l log-browser q/ESC quit */
+int tui_project_overview(ProjectIndex *idx,
+ const char *clone_dir,
+ const char *log_dir,
+ char *selected_project,
+ size_t sel_size);
+
#endif /* TUI_H */
diff --git a/src/config.c b/src/config.c
index a89ba18..46b8b5f 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1,12 +1,3 @@
-/*
-
-This file is licensed under the MIT license.
-
-Copyright (c) Schmidt Peter Daniel 2026
-
-
-*/
-
#include "config.h"
#include <stdio.h>
diff --git a/src/gbuild.c b/src/gbuild.c
index 9bc8b8a..ff8514e 100644
--- a/src/gbuild.c
+++ b/src/gbuild.c
@@ -1,5 +1,6 @@
#include "config.h"
#include "git_ops.h"
+#include "index.h"
#include "logger.h"
#include "make_ops.h"
#include "tui.h"
@@ -20,20 +21,126 @@ static void print_usage(const char *prog)
"gbuild %s — A build tool for your Linux distro\n\n"
"Usage:\n"
" %s [options] <project>\n"
+ " %s (no args — opens project overview TUI)\n"
" %s --logs\n\n"
"Options:\n"
- " --url <url> Override Git base URL (default: ~/.gconfig)\n"
- " --user <name> Override Git username (default: ~/.gconfig)\n"
- " --target <tgt> Run target directly, skip interactive picker\n"
+ " --url <url> Override Git base URL (default: ~/.gconfig)\n"
+ " --user <name> Override Git username (default: ~/.gconfig)\n"
+ " --target <tgt> Run target directly, skip interactive picker\n"
" --logs Open the interactive log browser\n"
+ " --no-tui With no <project>, print usage instead of opening TUI\n"
" -h, --help Print this help and exit\n\n"
"Examples:\n"
+ " gbuild (overview TUI — pick and build)\n"
" gbuild myproject\n"
" gbuild --target clean myproject\n"
" gbuild --url http://10.0.0.5:3000 --user alice myproject\n"
" gbuild --logs\n\n"
"Configuration is read from ~/.gconfig. Run 'gconfig init' to set it up.\n",
- VERSION, prog, prog);
+ VERSION, prog, prog, prog);
+}
+
+/* ----------------------------------------------------------------- build one project */
+
+static int build_project(const char *project,
+ const char *arg_target,
+ GConfig *cfg,
+ const char *clone_dir,
+ const char *log_dir,
+ ProjectIndex *idx)
+{
+ /* ---- open logger ---- */
+ Logger log;
+ memset(&log, 0, sizeof(log));
+ if (cfg->log_enabled) {
+ if (logger_open(&log, log_dir, project) == 0)
+ logger_info(&log, "Logging to: %s", log.path);
+ else
+ fprintf(stderr, "[WARN ] Could not open log file in %s\n", log_dir);
+ }
+
+ logger_info(&log, "gbuild started for project: %s", project);
+
+ /* ---- clone or pull ---- */
+ char repo_path[CFG_MAX * 2];
+ snprintf(repo_path, sizeof(repo_path), "%s/%s", clone_dir, project);
+
+ if (git_repo_exists(clone_dir, project)) {
+ logger_info(&log, "Repo already cloned — pulling latest changes ...");
+ int rc = git_pull(repo_path, &log);
+ if (rc != 0) {
+ logger_error(&log, "git pull failed (exit %d).", rc);
+ logger_close(&log);
+ return 1;
+ }
+ logger_ok(&log, "Repository updated.");
+ } else {
+ logger_info(&log, "Cloning %s/%s/%s ...",
+ cfg->git_url, cfg->git_user, project);
+ int rc = git_clone(cfg->git_url, cfg->git_user,
+ cfg->git_token, cfg->git_password,
+ project, clone_dir, &log);
+ if (rc != 0) {
+ logger_error(&log, "git clone failed (exit %d).", rc);
+ logger_close(&log);
+ return 1;
+ }
+ logger_ok(&log, "Repository cloned.");
+ }
+
+ /* ---- record HEAD hash ---- */
+ ProjectRecord *rec = index_upsert(idx, project);
+ if (rec)
+ git_head_hash(repo_path, rec->last_head_hash, IDX_HASH_LEN);
+
+ /* ---- resolve make target ---- */
+ char target[TARGET_NAME];
+ memset(target, 0, sizeof(target));
+
+ if (arg_target) {
+ strncpy(target, arg_target, sizeof(target) - 1);
+ logger_info(&log, "Selected target: %s", target);
+ } else if (cfg->default_target[0]) {
+ strncpy(target, cfg->default_target, sizeof(target) - 1);
+ logger_info(&log, "Using default target: %s", target);
+ } else {
+ MakeTargets targets;
+ if (make_parse_targets(repo_path, &targets) != 0) {
+ logger_warn(&log,
+ "No targets found in Makefile — running 'make' with no target.");
+ target[0] = '\0';
+ } else {
+ if (tui_pick_target(&targets, target, sizeof(target)) < 0) {
+ logger_warn(&log, "No target selected — aborting.");
+ logger_close(&log);
+ return 0;
+ }
+ }
+ logger_info(&log, "Selected target: %s",
+ target[0] ? target : "(default)");
+ }
+
+ /* ---- build ---- */
+ logger_info(&log, "Building '%s' target '%s' ...",
+ project, target[0] ? target : "(default)");
+
+ int rc = make_build(repo_path, target, &log);
+
+ /* ---- update index ---- */
+ if (rec) {
+ rec->last_build_rc = rc;
+ rec->last_build_ts = time(NULL);
+ }
+
+ if (rc != 0) {
+ logger_error(&log, "Build failed (exit %d).", rc);
+ logger_close(&log);
+ return 1;
+ }
+
+ logger_ok(&log, "gbuild complete.");
+ logger_close(&log);
+ return 0;
}
/* ----------------------------------------------------------------- main */
@@ -46,6 +153,7 @@ int main(int argc, char *argv[])
const char *arg_target = NULL;
const char *arg_project = NULL;
int flag_logs = 0;
+ int flag_no_tui = 0;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
@@ -62,6 +170,8 @@ int main(int argc, char *argv[])
arg_target = argv[i];
} else if (!strcmp(argv[i], "--logs")) {
flag_logs = 1;
+ } else if (!strcmp(argv[i], "--no-tui")) {
+ flag_no_tui = 1;
} else if (argv[i][0] == '-') {
fprintf(stderr, "error: unknown flag '%s'\n", argv[i]);
return 1;
@@ -86,109 +196,54 @@ int main(int argc, char *argv[])
config_defaults(&cfg);
}
- /* apply overrides */
if (arg_url) strncpy(cfg.git_url, arg_url, CFG_MAX - 1);
if (arg_user) strncpy(cfg.git_user, arg_user, sizeof(cfg.git_user) - 1);
- /* expand paths that might contain ~ */
char clone_dir[CFG_MAX], log_dir[CFG_MAX];
expand_tilde(cfg.clone_dir, clone_dir, sizeof(clone_dir));
expand_tilde(cfg.log_dir, log_dir, sizeof(log_dir));
+ /* ---- load / scan project index ---- */
+ ProjectIndex idx;
+ char idx_path[512];
+ index_get_path(idx_path, sizeof(idx_path));
+ index_load(&idx, idx_path); /* ok if file doesn't exist yet */
+ index_scan(&idx, clone_dir); /* pick up any new repos on disk */
+
/* ---- log browser mode ---- */
if (flag_logs) {
tui_log_browser(log_dir);
return 0;
}
- /* ---- need a project name ---- */
+ /* ---- no project given: open overview TUI or print usage ---- */
if (!arg_project) {
- fprintf(stderr, "error: no project specified.\n\n");
- print_usage(argv[0]);
- return 1;
- }
-
- /* ---- open logger ---- */
- Logger log;
- memset(&log, 0, sizeof(log));
- if (cfg.log_enabled) {
- if (logger_open(&log, log_dir, arg_project) == 0)
- logger_info(&log, "Logging to: %s", log.path);
- else
- fprintf(stderr, "[WARN ] Could not open log file in %s\n", log_dir);
- }
-
- logger_info(&log, "gbuild started for project: %s", arg_project);
-
- /* ---- clone or pull ---- */
- char repo_path[CFG_MAX * 2];
- snprintf(repo_path, sizeof(repo_path), "%s/%s", clone_dir, arg_project);
-
- if (git_repo_exists(clone_dir, arg_project)) {
- logger_info(&log, "Repo already cloned — pulling latest changes ...");
- int rc = git_pull(repo_path, &log);
- if (rc != 0) {
- logger_error(&log, "git pull failed (exit %d).", rc);
- logger_close(&log);
- return 1;
+ if (flag_no_tui || idx.count == 0) {
+ if (idx.count == 0 && !flag_no_tui)
+ fprintf(stderr,
+ "No projects found in %s.\n"
+ "Run 'gbuild <project>' to clone and build one first.\n",
+ clone_dir);
+ else
+ print_usage(argv[0]);
+ return (idx.count == 0) ? 1 : 0;
}
- logger_ok(&log, "Repository updated.");
- } else {
- logger_info(&log, "Cloning %s/%s/%s ...",
- cfg.git_url, cfg.git_user, arg_project);
- int rc = git_clone(cfg.git_url, cfg.git_user,
- cfg.git_token, cfg.git_password,
- arg_project, clone_dir, &log);
- if (rc != 0) {
- logger_error(&log, "git clone failed (exit %d).", rc);
- logger_close(&log);
- return 1;
- }
- logger_ok(&log, "Repository cloned.");
- }
- /* ---- resolve make target ---- */
- char target[TARGET_NAME];
- memset(target, 0, sizeof(target));
+ static char chosen[IDX_NAME_LEN];
+ memset(chosen, 0, sizeof(chosen));
+ if (tui_project_overview(&idx, clone_dir, log_dir,
+ chosen, sizeof(chosen)) < 0)
+ return 0; /* user quit without picking */
- if (arg_target) {
- /* explicit --target flag */
- strncpy(target, arg_target, sizeof(target) - 1);
- logger_info(&log, "Selected target: %s", target);
- } else if (cfg.default_target[0]) {
- /* default from config */
- strncpy(target, cfg.default_target, sizeof(target) - 1);
- logger_info(&log, "Using default target: %s", target);
- } else {
- /* interactive TUI picker */
- MakeTargets targets;
- if (make_parse_targets(repo_path, &targets) != 0) {
- logger_warn(&log,
- "No targets found in Makefile — running 'make' with no target.");
- target[0] = '\0';
- } else {
- if (tui_pick_target(&targets, target, sizeof(target)) < 0) {
- logger_warn(&log, "No target selected — aborting.");
- logger_close(&log);
- return 0;
- }
- }
- logger_info(&log, "Selected target: %s",
- target[0] ? target : "(default)");
+ arg_project = chosen;
}
/* ---- build ---- */
- logger_info(&log, "gbuild: building '%s' target '%s' ...",
- arg_project, target[0] ? target : "(default)");
+ int rc = build_project(arg_project, arg_target,
+ &cfg, clone_dir, log_dir, &idx);
- int rc = make_build(repo_path, target, &log);
- if (rc != 0) {
- logger_error(&log, "Build failed (exit %d).", rc);
- logger_close(&log);
- return 1;
- }
+ /* ---- persist updated index ---- */
+ index_save(&idx, idx_path);
- logger_ok(&log, "gbuild complete.");
- logger_close(&log);
- return 0;
+ return rc;
}
diff --git a/src/git_ops.c b/src/git_ops.c
index 69588eb..3f995f1 100644
--- a/src/git_ops.c
+++ b/src/git_ops.c
@@ -1,13 +1,6 @@
-/*
-
-This file is licensed under the MIT license.
-Copyright (c) Schmidt Peter Daniel 2026.
-
-
-*/
-
#include "git_ops.h"
#include "config.h"
+#include "index.h"
#include <stdio.h>
#include <stdlib.h>
@@ -131,3 +124,27 @@ int git_pull(const char *repo_path, Logger *log)
"git -C \"%s\" pull 2>&1", repo_path);
return run_stream(cmd, log);
}
+
+int git_head_hash(const char *repo_path, char *out, size_t n)
+{
+ char cmd[IDX_PATH_LEN + 64];
+ snprintf(cmd, sizeof(cmd),
+ "git -C \"%s\" rev-parse HEAD 2>/dev/null", repo_path);
+
+ FILE *p = popen(cmd, "r");
+ if (!p) return -1;
+
+ char buf[64] = {0};
+ int ok = (fgets(buf, sizeof(buf), p) != NULL);
+ pclose(p);
+
+ if (!ok || buf[0] == '\0') return -1;
+
+ /* strip newline */
+ size_t l = strlen(buf);
+ if (l > 0 && buf[l-1] == '\n') buf[--l] = '\0';
+
+ strncpy(out, buf, n - 1);
+ out[n - 1] = '\0';
+ return 0;
+}
diff --git a/src/logger.c b/src/logger.c
index 997a975..de2eead 100644
--- a/src/logger.c
+++ b/src/logger.c
@@ -71,9 +71,9 @@ typedef enum { LVL_INFO, LVL_OK, LVL_WARN, LVL_ERROR } LogLevel;
static const char *level_tag(LogLevel lv)
{
switch (lv) {
- case LVL_INFO: return "[INFO]";
- case LVL_OK: return "[OK]";
- case LVL_WARN: return "[WARN]";
+ case LVL_INFO: return "[INFO ]";
+ case LVL_OK: return "[OK ]";
+ case LVL_WARN: return "[WARN ]";
case LVL_ERROR: return "[ERROR]";
}
return "[ ]";
diff --git a/src/tui.c b/src/tui.c
index c5e660a..605fba6 100644
--- a/src/tui.c
+++ b/src/tui.c
@@ -10,7 +10,7 @@
#include <errno.h>
/* ================================================================
- * SECTION 1 — TARGET PICKER
+ * SECTION 1 - TARGET PICKER
* ================================================================ */
#define PICKER_MIN_W 40
@@ -29,18 +29,15 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
if (has_colors()) {
start_color();
use_default_colors();
- init_pair(1, COLOR_BLACK, COLOR_WHITE); /* selected row */
- init_pair(2, COLOR_WHITE, -1); /* border colour */
- init_pair(3, COLOR_WHITE, -1); /* normal row */
+ init_pair(1, COLOR_BLACK, COLOR_WHITE); /* selected row */
}
int rows, cols;
getmaxyx(stdscr, rows, cols);
- int list_h = targets->count + 2; /* +2 for border */
+ int list_h = targets->count + 2;
int list_w = PICKER_MIN_W;
- /* find longest target name and widen accordingly */
for (int i = 0; i < targets->count; i++) {
int l = (int)strlen(targets->names[i]) + 4;
if (l > list_w) list_w = l;
@@ -51,17 +48,16 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
int win_y = (rows - list_h) / 2;
int win_x = (cols - list_w) / 2;
- /* instruction line above the box */
int cur = 0;
int scroll_offset = 0;
- int visible = list_h - 2; /* rows inside border */
+ int visible = list_h - 2;
WINDOW *win = newwin(list_h, list_w, win_y, win_x);
+ keypad(win, TRUE);
- /* header hint */
attron(A_DIM);
mvprintw(win_y - 2, win_x,
- "Select make target \u2191\u2193 navigate \u00b7 Enter select \u00b7 q cancel");
+ "Select make target J/K navigate | Enter select | q cancel");
attroff(A_DIM);
refresh();
@@ -70,9 +66,7 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
while (!done) {
werase(win);
- if (has_colors()) wattron(win, COLOR_PAIR(2));
box(win, 0, 0);
- if (has_colors()) wattroff(win, COLOR_PAIR(2));
for (int i = 0; i < visible && (i + scroll_offset) < targets->count; i++) {
int idx = i + scroll_offset;
@@ -84,10 +78,8 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
if (has_colors()) wattroff(win, COLOR_PAIR(1) | A_BOLD);
else wattroff(win, A_REVERSE);
} else {
- if (has_colors()) wattron(win, COLOR_PAIR(3));
mvwprintw(win, i + 1, 1, " %-*s",
list_w - 4, targets->names[idx]);
- if (has_colors()) wattroff(win, COLOR_PAIR(3));
}
}
@@ -119,7 +111,7 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
done = 1;
break;
case 'q':
- case 27: /* ESC */
+ case 27:
done = 1;
break;
}
@@ -131,20 +123,19 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
}
/* ================================================================
- * SECTION 2 — LOG BROWSER
+ * SECTION 2 - LOG BROWSER
* ================================================================ */
#define MAX_LOGS 512
typedef struct {
- char name[256]; /* filename only */
- char path[768]; /* full path */
+ char name[256];
+ char path[768];
time_t mtime;
} LogEntry;
static int log_entry_cmp(const void *a, const void *b)
{
- /* newest first */
const LogEntry *la = (const LogEntry *)a;
const LogEntry *lb = (const LogEntry *)b;
if (lb->mtime > la->mtime) return 1;
@@ -162,7 +153,6 @@ static int load_log_entries(const char *log_dir,
struct dirent *de;
while ((de = readdir(d)) && count < max) {
if (de->d_name[0] == '.') continue;
- /* only .log files */
const char *dot = strrchr(de->d_name, '.');
if (!dot || strcmp(dot, ".log")) continue;
@@ -184,11 +174,6 @@ static int load_log_entries(const char *log_dir,
return count;
}
-/*
- * Read up to `max_lines` trailing lines from `path` into `lines`.
- * Returns the number of lines read.
- * Caller owns the memory and should free each pointer.
- */
#define PREVIEW_MAX 128
static int read_tail(const char *path, char **lines, int max_lines)
@@ -196,17 +181,14 @@ static int read_tail(const char *path, char **lines, int max_lines)
FILE *f = fopen(path, "r");
if (!f) return 0;
- /* Circular buffer approach */
char **buf = calloc(max_lines, sizeof(char *));
if (!buf) { fclose(f); return 0; }
char tmp[1024];
int idx = 0, total = 0;
while (fgets(tmp, sizeof(tmp), f)) {
- /* strip newline */
size_t l = strlen(tmp);
if (l > 0 && tmp[l-1] == '\n') tmp[l-1] = '\0';
-
free(buf[idx % max_lines]);
buf[idx % max_lines] = strdup(tmp);
idx++;
@@ -214,13 +196,12 @@ static int read_tail(const char *path, char **lines, int max_lines)
}
fclose(f);
- int have = (total < max_lines) ? total : max_lines;
+ int have = (total < max_lines) ? total : max_lines;
int start = (total >= max_lines) ? (idx % max_lines) : 0;
for (int i = 0; i < have; i++)
lines[i] = buf[(start + i) % max_lines];
- /* free slots that weren't returned */
for (int i = have; i < max_lines; i++) {
free(buf[(start + i) % max_lines]);
buf[(start + i) % max_lines] = NULL;
@@ -247,11 +228,7 @@ void tui_log_browser(const char *log_dir)
if (has_colors()) {
start_color();
use_default_colors();
- init_pair(1, COLOR_BLACK, COLOR_WHITE);
- init_pair(2, COLOR_WHITE, -1);
- init_pair(3, COLOR_WHITE, -1);
- init_pair(4, COLOR_WHITE, -1);
- init_pair(5, COLOR_RED, -1);
+ init_pair(1, COLOR_BLACK, COLOR_WHITE); /* selected row */
}
int rows, cols;
@@ -280,11 +257,11 @@ void tui_log_browser(const char *log_dir)
clrtoeol();
/* ---- hint bar ---- */
- if (has_colors()) attron(COLOR_PAIR(2));
+ attron(A_REVERSE);
mvprintw(rows - 2, 0,
- " K - UP J - DOWN to navigate Enter - open in less d - delete q - quit "
+ " J/K navigate Enter open in less d delete q quit "
" ");
- if (has_colors()) attroff(COLOR_PAIR(2));
+ attroff(A_REVERSE);
/* ---- vertical divider ---- */
for (int y = 1; y < rows - 2; y++)
@@ -292,9 +269,7 @@ void tui_log_browser(const char *log_dir)
/* ---- left pane: file list ---- */
werase(wleft);
- if (has_colors()) wattron(wleft, COLOR_PAIR(2));
box(wleft, 0, 0);
- if (has_colors()) wattroff(wleft, COLOR_PAIR(2));
int visible = pane_h - 2;
if (visible < 1) visible = 1;
@@ -322,20 +297,17 @@ void tui_log_browser(const char *log_dir)
}
}
}
- /* NOTE: no wrefresh(wleft) here — flush everything together below */
+ /* NOTE: no wrefresh(wleft) here - flush everything together below */
/* ---- right pane: preview ---- */
werase(wright);
- if (has_colors()) wattron(wright, COLOR_PAIR(2));
box(wright, 0, 0);
- if (has_colors()) wattroff(wright, COLOR_PAIR(2));
if (count > 0 && cur < count) {
int preview_lines = pane_h - 2;
char **lines = calloc(preview_lines, sizeof(char *));
if (lines) {
int n = read_tail(entries[cur].path, lines, preview_lines);
- if (has_colors()) wattron(wright, COLOR_PAIR(4));
for (int i = 0; i < n; i++) {
if (lines[i]) {
mvwprintw(wright, i + 1, 1, "%-*.*s",
@@ -343,16 +315,13 @@ void tui_log_browser(const char *log_dir)
free(lines[i]);
}
}
- if (has_colors()) wattroff(wright, COLOR_PAIR(4));
free(lines);
}
} else {
mvwprintw(wright, 2, 2, "(no log selected)");
}
- /* Flush all layers atomically: stdscr first (background), then the two
- * panes on top. doupdate() does one physical write so nothing
- * overwrites anything else. */
+ /* Atomic flush - stdscr first, then both panes on top */
wnoutrefresh(stdscr);
wnoutrefresh(wleft);
wnoutrefresh(wright);
@@ -394,10 +363,6 @@ void tui_log_browser(const char *log_dir)
start_color();
use_default_colors();
init_pair(1, COLOR_BLACK, COLOR_WHITE);
- init_pair(2, COLOR_WHITE, -1);
- init_pair(3, COLOR_WHITE, -1);
- init_pair(4, COLOR_WHITE, -1);
- init_pair(5, COLOR_RED, -1);
}
touchwin(wleft);
touchwin(wright);
@@ -431,3 +396,202 @@ void tui_log_browser(const char *log_dir)
endwin();
free(entries);
}
+
+/* ================================================================
+ * SECTION 3 - PROJECT OVERVIEW
+ * ================================================================ */
+
+/*
+ * Column layout:
+ * ST PROJECT NAME LAST BUILD RESULT HEAD HASH
+ *
+ * Status symbols:
+ * * last build passed
+ * X last build failed
+ * - never built
+ *
+ * Keys:
+ * J/K navigate Enter build l logs q/ESC quit
+ */
+
+#include "index.h"
+#include "git_ops.h"
+
+#define COL_STATUS_W 3
+#define COL_NAME_W 24
+#define COL_TIME_W 18
+#define COL_RC_W 8
+#define COL_HASH_W 10
+
+static void fmt_ts(time_t ts, char *out, size_t n)
+{
+ if (ts == 0) {
+ strncpy(out, "(never)", n - 1);
+ out[n-1] = '\0';
+ return;
+ }
+ struct tm *t = localtime(&ts);
+ strftime(out, n, "%d %b %H:%M", t);
+}
+
+int tui_project_overview(ProjectIndex *idx,
+ const char *clone_dir,
+ const char *log_dir,
+ char *selected_project,
+ size_t sel_size)
+{
+ (void)clone_dir;
+
+ if (!idx || idx->count == 0) return -1;
+
+ initscr();
+ noecho();
+ cbreak();
+ keypad(stdscr, TRUE);
+ curs_set(0);
+
+ if (has_colors()) {
+ start_color();
+ use_default_colors();
+ init_pair(1, COLOR_BLACK, COLOR_WHITE); /* selected row */
+ }
+
+ int rows, cols;
+ getmaxyx(stdscr, rows, cols);
+
+ int cur = 0, scroll = 0;
+ int done = 0;
+ int result = -1;
+
+ while (!done) {
+ getmaxyx(stdscr, rows, cols);
+ int visible = rows - 5;
+ if (visible < 1) visible = 1;
+
+ erase();
+
+ /* ---- title bar ---- */
+ attron(A_BOLD);
+ mvprintw(0, 2, "gbuild 1.0.0 - project overview");
+ attroff(A_BOLD);
+
+ /* ---- column header ---- */
+ attron(A_BOLD);
+ mvprintw(2, 2, " %-*s %-*s %-*s %-*s",
+ COL_NAME_W, "PROJECT",
+ COL_TIME_W, "LAST BUILD",
+ COL_RC_W, "RESULT",
+ COL_HASH_W, "HEAD");
+ attroff(A_BOLD);
+
+ /* separator */
+ mvhline(3, 0, ACS_HLINE, cols);
+
+ /* ---- project rows ---- */
+ for (int i = 0; i < visible && (i + scroll) < idx->count; i++) {
+ int idx_i = i + scroll;
+ ProjectRecord *r = &idx->projects[idx_i];
+
+ char ts_str[32];
+ fmt_ts(r->last_build_ts, ts_str, sizeof(ts_str));
+
+ char shorthash[12] = "--------";
+ if (r->last_head_hash[0])
+ strncpy(shorthash, r->last_head_hash, 8);
+ shorthash[8] = '\0';
+
+ char rc_str[16];
+ if (r->last_build_rc == -1) strncpy(rc_str, "-", sizeof(rc_str) - 1);
+ else if (r->last_build_rc == 0) strncpy(rc_str, "PASS", sizeof(rc_str) - 1);
+ else snprintf(rc_str, sizeof(rc_str), "FAIL(%d)", r->last_build_rc);
+
+ const char *sym = (r->last_build_rc == 0) ? "*" :
+ (r->last_build_rc > 0) ? "X" : "-";
+
+ int y = 4 + i;
+
+ if (idx_i == cur) {
+ /* fill the whole row first */
+ if (has_colors()) attron(COLOR_PAIR(1) | A_BOLD);
+ else attron(A_REVERSE);
+ mvprintw(y, 0, "%*s", cols, "");
+ mvprintw(y, 2, "%s %-*s %-*s %-*s %-*s",
+ sym,
+ COL_NAME_W, r->name,
+ COL_TIME_W, ts_str,
+ COL_RC_W, rc_str,
+ COL_HASH_W, shorthash);
+ if (has_colors()) attroff(COLOR_PAIR(1) | A_BOLD);
+ else attroff(A_REVERSE);
+ } else {
+ mvprintw(y, 2, "%s %-*s %-*s %-*s %-*s",
+ sym,
+ COL_NAME_W, r->name,
+ COL_TIME_W, ts_str,
+ COL_RC_W, rc_str,
+ COL_HASH_W, shorthash);
+ }
+ }
+
+ /* ---- hint bar ---- */
+ attron(A_REVERSE);
+ mvprintw(rows - 1, 0,
+ " J/K navigate Enter build l logs q quit"
+ " ");
+ attroff(A_REVERSE);
+
+ wnoutrefresh(stdscr);
+ doupdate();
+
+ /* ---- input ---- */
+ int ch = getch();
+ switch (ch) {
+ case KEY_UP:
+ case 'k':
+ if (cur > 0) {
+ cur--;
+ if (cur < scroll) scroll = cur;
+ }
+ break;
+ case KEY_DOWN:
+ case 'j':
+ if (cur < idx->count - 1) {
+ cur++;
+ if (cur >= scroll + visible)
+ scroll = cur - visible + 1;
+ }
+ break;
+ case '\n':
+ case '\r':
+ case KEY_ENTER:
+ strncpy(selected_project,
+ idx->projects[cur].name, sel_size - 1);
+ selected_project[sel_size - 1] = '\0';
+ result = cur;
+ done = 1;
+ break;
+ case 'l':
+ endwin();
+ tui_log_browser(log_dir);
+ initscr();
+ noecho();
+ cbreak();
+ keypad(stdscr, TRUE);
+ curs_set(0);
+ if (has_colors()) {
+ start_color();
+ use_default_colors();
+ init_pair(1, COLOR_BLACK, COLOR_WHITE);
+ }
+ clear();
+ break;
+ case 'q':
+ case 27:
+ done = 1;
+ break;
+ }
+ }
+
+ endwin();
+ return result;
+}
diff --git a/bin/gbuild b/bin/gbuild
deleted file mode 100755
index 23d9fe9..0000000
Binary files a/bin/gbuild and /dev/null differ
diff --git a/bin/gconfig b/bin/gconfig
deleted file mode 100755
index 2b1ee61..0000000
Binary files a/bin/gconfig and /dev/null differ
diff --git a/dist/gbuild-1.0.0-1.x86_64.rpm b/dist/gbuild-1.0.0-1.x86_64.rpm
deleted file mode 100644
index f0d9432..0000000
Binary files a/dist/gbuild-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild-1.0.0-x86_64-bin.tar.gz b/dist/gbuild-1.0.0-x86_64-bin.tar.gz
deleted file mode 100644
index 40bbc22..0000000
Binary files a/dist/gbuild-1.0.0-x86_64-bin.tar.gz and /dev/null differ
diff --git a/dist/gbuild-1.0.0.tar.gz b/dist/gbuild-1.0.0.tar.gz
deleted file mode 100644
index 3cc865a..0000000
Binary files a/dist/gbuild-1.0.0.tar.gz and /dev/null differ
diff --git a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm
deleted file mode 100644
index 4903e10..0000000
Binary files a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild_1.0.0_amd64.deb b/dist/gbuild_1.0.0_amd64.deb
deleted file mode 100644
index 0a722ed..0000000
Binary files a/dist/gbuild_1.0.0_amd64.deb and /dev/null differ