939232e72e
Added: Project Overview TUI Build Cache / Dirty Detection Post-Build Hooks gbuild status
1353 lines
49 KiB
Plaintext
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
|