Added features

Added: 
Project Overview TUI 
Build Cache / Dirty Detection
Post-Build Hooks
gbuild status
This commit is contained in:
Schmidt Peter
2026-05-23 21:03:54 +02:00
committed by GitHub
parent 351ce1b06e
commit 939232e72e
18 changed files with 2768 additions and 272 deletions
+35
View File
@@ -0,0 +1,35 @@
#ifndef CACHE_H
#define CACHE_H
/*
* Build cache — per-project dirty detection.
*
* A small dotfile (.gbuild_cache) is kept inside each cloned repo dir.
* It stores the git HEAD hash that was current at the last *successful*
* build. On the next run gbuild compares the live HEAD to the cached
* one; if they match the build is skipped unless --force was passed.
*
* Format of .gbuild_cache (plain text, one line):
* <40-char sha1>\n
*/
#include <stddef.h>
/* Maximum length of a stored hash (SHA-1 hex + NUL). */
#define CACHE_HASH_LEN 64
/*
* Read the cached hash for the repo at repo_path into out (>= CACHE_HASH_LEN
* bytes). Returns 0 on success, -1 if the file does not exist or is
* unreadable (out is set to an empty string in that case).
*/
int cache_read(const char *repo_path, char *out, size_t n);
/*
* Write hash as the new cached HEAD for the repo at repo_path.
* Creates or overwrites .gbuild_cache inside repo_path.
* Returns 0 on success, -1 on error.
*/
int cache_write(const char *repo_path, const char *hash);
#endif /* CACHE_H */
+28
View File
@@ -6,6 +6,18 @@
#define CFG_MAX 512
/*
* Per-project hook override loaded from a [project:<name>] section.
* Up to CFG_MAX_PROJECT_OVERRIDES overrides are supported.
*/
#define CFG_MAX_PROJECT_OVERRIDES 64
#define CFG_PROJECT_NAME_LEN 128
typedef struct {
char name[CFG_PROJECT_NAME_LEN]; /* project name, e.g. "myproject" */
char post_build_hook[CFG_MAX]; /* hook command for this project */
} GProjectOverride;
typedef struct {
char git_url[CFG_MAX];
char git_user[256];
@@ -15,8 +27,24 @@ typedef struct {
char clone_dir[CFG_MAX];
bool log_enabled;
char log_dir[CFG_MAX];
/* Post-build hook run after every successful make invocation.
* The hook is executed with the repo directory as its CWD.
* An empty string means "no hook". */
char post_build_hook[CFG_MAX];
/* Per-project hook overrides from [project:<name>] sections. */
GProjectOverride project_overrides[CFG_MAX_PROJECT_OVERRIDES];
int project_override_count;
} GConfig;
/*
* Return the effective post-build hook for a given project name.
* Checks [project:<name>] overrides first; falls back to the global hook.
* Returns a pointer into cfg — do not free.
*/
const char *config_hook_for(const GConfig *cfg, const char *project);
void config_defaults(GConfig *cfg);
int config_load(GConfig *cfg, const char *path);
int config_save(const GConfig *cfg, const char *path);
+16
View File
@@ -15,4 +15,20 @@ int git_clone(const char *base_url, const char *user,
/* Pull latest in repo_path */
int git_pull(const char *repo_path, Logger *log);
/* 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);
/* Write the current branch name into out.
* Returns 0 on success, -1 on failure (detached HEAD writes "(detached)"). */
int git_current_branch(const char *repo_path, char *out, size_t n);
/* Returns 1 if the working tree has uncommitted changes, 0 if clean,
* -1 on error. Does NOT stage anything. */
int git_is_dirty(const char *repo_path);
/* Silently fetches from origin then returns the number of commits
* the local branch is behind its upstream, or -1 on error. */
int git_behind_count(const char *repo_path);
#endif /* GIT_OPS_H */
+18
View File
@@ -0,0 +1,18 @@
#ifndef HOOKS_H
#define HOOKS_H
#include "logger.h"
/*
* Run cmd in a shell with working directory set to working_dir.
* stdout and stderr from the hook are streamed through log.
*
* Returns the hook's exit status (0 = success, >0 = hook failure),
* or -1 if the shell could not be launched.
*
* The caller is responsible for distinguishing hook failure from build
* failure — this function never touches build state.
*/
int hook_run(const char *cmd, const char *working_dir, Logger *log);
#endif /* HOOKS_H */
+82
View File
@@ -0,0 +1,82 @@
#ifndef INDEX_H
#define INDEX_H
#include <time.h>
#include <stddef.h>
#define IDX_MAX_PROJECTS 256
#define IDX_NAME_LEN 128
#define IDX_HASH_LEN 64
#define IDX_PATH_LEN 512
/*
* Per-project record stored in ~/.gbuild_index.
*
* File format (INI-style, one section per project):
*
* [myproject]
* last_build_rc = 0
* last_build_ts = 1716000000
* last_head_hash = abc123def456
*/
typedef struct {
char name[IDX_NAME_LEN];
int last_build_rc; /* exit code of last make_build(); -1 = never built */
time_t last_build_ts; /* unix timestamp of last build attempt */
char last_head_hash[IDX_HASH_LEN]; /* git HEAD hash at last build */
} ProjectRecord;
typedef struct {
ProjectRecord projects[IDX_MAX_PROJECTS];
int count;
} ProjectIndex;
/* Load ~/.gbuild_index into idx. Returns 0 on success, -1 if not found
* (idx is still initialised to an empty index). */
int index_load(ProjectIndex *idx, const char *path);
/* Persist idx to path, creating or overwriting the file. */
int index_save(const ProjectIndex *idx, const char *path);
/* Find a record by name. Returns a pointer into idx->projects, or NULL. */
ProjectRecord *index_find(ProjectIndex *idx, const char *name);
/* Find or create a record by name. Returns NULL only if the index is full. */
ProjectRecord *index_upsert(ProjectIndex *idx, const char *name);
/* Walk clone_dir, find every subdir that contains .git, and upsert each one
* into idx. Does NOT overwrite existing build metadata — only adds new
* entries for projects that are not yet tracked.
* Returns the number of new entries added. */
int index_scan(ProjectIndex *idx, const char *clone_dir);
/* Canonical path for the index file (~/.gbuild_index). */
void index_get_path(char *out, size_t n);
/* ---------------------------------------------------------------- status scan */
/*
* Per-project status gathered by project_scan_all().
* All string fields are NUL-terminated; numeric fields are -1 when unknown.
*/
typedef struct {
char name[IDX_NAME_LEN];
char branch[128]; /* current branch / "(detached:HASH)" */
int behind; /* commits behind upstream; -1 = no upstream/error */
int dirty; /* 1 = dirty, 0 = clean, -1 = error */
int last_build_rc; /* from index; -1 = never built */
time_t last_build_ts; /* from index; 0 = never built */
} StatusResult;
/*
* Scan every subdirectory of clone_dir that contains .git.
* For each one, fetch the three git metrics and join them with build
* metadata from idx. Results are written into results[0..max-1].
* Worker threads run the git queries in parallel (pool of SCAN_THREADS).
* Returns the number of projects found (may be 0, capped at max).
*/
#define SCAN_THREADS 6
int project_scan_all(const char *clone_dir, const ProjectIndex *idx,
StatusResult *results, int max);
#endif /* INDEX_H */
+13
View File
@@ -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 */