16 Commits
v1.2 ... v3.0

Author SHA1 Message Date
8f20a84ef7 bump version 2025-02-18 14:58:24 -06:00
09362e2c7c replace atoi with my own impl to make sure that no errors occour 2025-02-18 14:48:32 -06:00
6025b2d832 Add -e flag to explain what the previous smiley face means. This
requires us to also know what the previous return signal is, so
if XD is given a return code, it passes it through when finishing
execution.
2025-02-18 14:32:40 -06:00
6c099e3648 update man page to reflect passing in return codes 2024-12-22 13:01:45 -05:00
202142a8af fix segfault in certain situations do to missing arguments 2024-12-22 12:57:26 -05:00
6e5d8a1e6a reorder code 2024-12-22 12:56:47 -05:00
d1752c3ac7 bugfix
re enable mwindow size stuff due to divide by zero's happening on
larger repositories
2024-12-22 12:55:39 -05:00
e6029a68e3 version bump
and fix typo
2024-12-21 21:43:01 -05:00
4af00678ca optimize...
make the stage check faster by filtering the status list
2024-12-21 21:36:54 -05:00
cab498199e optimizations :)
instead of initializing libgit2 and then looking for a repo, we
do a naive check to see if one exists and then if so initialize
libgit2
2024-12-21 21:35:07 -05:00
3fb00b615c don't recursively check for -v arg 2024-12-21 21:33:12 -05:00
2dc0d582e6 change libgit2 initialization 2024-12-21 21:32:39 -05:00
2a6385fa36 optimizing...
replace printf with fwrite to make it ever so slightly faster
2024-12-19 13:41:57 -06:00
363f15abd3 add some missing stuff 2024-12-19 13:17:06 -06:00
8089a46d50 optimize further...
make sure to not include useless function calls when errors arent
enabled by wrapping all l calls in a macro
2024-12-19 12:30:55 -06:00
18947be24d optimise
instead of putting each part of the face into an array we just
print it out to the console :)
2024-12-19 12:26:31 -06:00
4 changed files with 256 additions and 71 deletions

View File

@ -2,7 +2,7 @@ include config.mk
# flags and incs
PKGS = $(GITLIB)
CFLAGS = -DVERSION=\"$(VERSION)\" -Wall -pedantic -O3 $(GIT) $(ERR)
CFLAGS = -DVERSION=\"$(VERSION)\" -Wall -pedantic -O3 $(GIT) $(ERR) $(EXPLAIN)
LIBS = `$(PKG_CONFIG) --libs --cflags $(PKGS)`
all: XD

51
XD.1
View File

@ -1,4 +1,4 @@
.Dd December 19, 2024
.Dd Febuary 18, 2025
.Dt XD 1
.Sh NAME
.Nm XD
@ -7,10 +7,16 @@
.Sh SYNOPSIS
.Nm
.Op Fl v
.Op Fl e
.Op number
.Sh DESCRIPTION
.Nm
Displays information using a smiley face like so: :)
to interpret it refer to the following tables:
Displays information using a smiley face.
to interpret XD's output refer to the following tables (or
.Nm
\fB-e\fR):
.Ss Eyes
.TS
tab(;) allbox;
@ -35,8 +41,49 @@ tab(;) allbox;
c;l.
|;no signal provided
);previous signal is 0
O;SIGINT sen't (Ctrl-c)
O;SIGINT sent (Ctrl-c)
P;permission denied
/;command not found
(;previous signal is failure
.TE
.Ss Exit Status:
.Nm
returns the number that was passed in to allow the user to re-run the program
and get the same results. If there's an internal error
.Nm
returns 1. If you wish to find out more infomation about the error enable ERR
in the config.mk.
.Sh OPTIONS
.Ss -v
Print version information to stdout and exit.
.Ss -e
Explain the output instead of putting a smiley face.
.Sh EXAMPLES
Running
.Nm
and passing in the previous return code ($?) results in a smiley face, if you
then follow that by \fBXD -e $?\fR then you will get an explination of what the
face is explaining.
$ \fBXD $?\fR
.br
:)
.br
$ \fBXD -e $?\fR
.br
Not in a git repository.
.br
Return code of 0, no errors.
$ \fBXD $?\fR
.br
;*O
.br
$ \fBXD -e $?\fR
.br
In a git repository.
.br
The're staged changes.
.br
Return code of 130, ctrl-c was pressed, or SIGTERM sent.

263
XD.c
View File

@ -1,36 +1,94 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef ERR
#include <ctype.h>
#if defined(ERR) || defined(EXPLAIN)
#include <stdarg.h>
#endif
#ifdef GIT
#include <sys/stat.h>
#include <dirent.h>
#include <git2.h>
#endif
enum face { EYES, NOSE, MOUTH };
#define P(X) fwrite(X, 1, 1, stdout)
#if defined(ERR) || defined(EXPLAIN)
void
l(const char *fmt, ...)
{
#ifdef ERR
va_list ap;
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
fputc(' ', stderr);
perror(NULL);
} else {
fputc('\n', stderr);
}
#endif
if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
fputc(' ', stderr);
perror(NULL);
} else {
fputc('\n', stderr);
}
}
#endif
#ifdef ERR
#define L(...) l(__VA_ARGS__)
#else
#define L(...)
#endif
#ifdef EXPLAIN
static int explain = 0;
#define E(...) if (explain) { \
l(__VA_ARGS__); \
} else
#else
#define E(...)
#endif
#ifdef GIT
/**
* @brief search all parent directories for a git repo
*
* @return absolute path to git repo
*/
char
*find_git_repo()
{
char path[PATH_MAX] = ".", *rpath;
struct stat s;
int i, c;
/* find the number of jumps to the root of the fs */
rpath = realpath(path, NULL);
for (i = c = 0; i < strlen(rpath); i++) {
if (rpath[i] == '/') {
c++;
}
}
free(rpath);
/* start searching */
for (i = c; i > 0; i--) {
strcat(path, "/.git");
/* if there seems to be a git directory return the directory it was found in */
if (stat(path, &s) == 0 && S_ISDIR(s.st_mode)) {
return realpath(path, NULL);
}
/* reset contents of gpath, and go up a directory */
memset(&path[strlen(path) - 4], '.', 2);
memset(&path[strlen(path) - 2], 0, 2);
}
return NULL;
}
/**
* @brief open git repo if one is available at the current path
*
@ -39,27 +97,38 @@ l(const char *fmt, ...)
git_repository
*init_git()
{
git_buf buf = GIT_BUF_INIT_CONST(NULL, 0);
char *buf;
git_repository *repo;
/* check for a repo before loading libgit2 */
if ((buf = find_git_repo()) == NULL) {
return NULL;
}
/* disable a bunch of git options to hopefully speed things up */
git_libgit2_opts(GIT_OPT_ENABLE_CACHING, 0);
git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, "");
git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, "");
git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, "");
git_libgit2_opts(GIT_OPT_SET_TEMPLATE_PATH, "");
git_libgit2_opts(GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, 1);
/* initialize the git library and repository */
if (git_libgit2_init() < 0) {
l("Failed to initalize libgit2, proceeding without git functionality enabled.");
L("Failed to initalize libgit2, proceeding without git functionality enabled.");
return NULL;
}
if (git_repository_discover(&buf, ".", 0, NULL) < 0) {
l("Failed to discover git repo: %s", git_error_last()->message);
return NULL;
}
if (git_repository_open(&repo, buf.ptr) < 0) {
l("Failed to open git repo: %s", git_error_last()->message);
git_buf_dispose(&buf);
if (git_repository_open(&repo, buf) < 0) {
L("Failed to open git repo: %s", git_error_last()->message);
free(buf);
return NULL;
}
/* get rid of object containing git repo path and return the repo */
git_buf_dispose(&buf);
free(buf);
return repo;
}
@ -77,10 +146,9 @@ has_stashes(git_repository *repo)
e = git_reference_lookup(&stash, repo, "refs/stash");
if (e == GIT_ENOTFOUND) {
git_error_clear();
return 0;
} else if (e < GIT_OK) {
l("Error looking up stash reference: %s", git_error_last()->message);
} else if (e < 0) {
L("Error looking up stash reference: %s", git_error_last()->message);
return 0;
} else {
e = 1;
@ -108,7 +176,7 @@ has_untracked(git_repository *repo)
GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
if (git_status_list_new(&list, repo, &opts) < 0) {
l("Error checking for untracked changes: %s", git_error_last()->message);
L("Error checking for untracked changes: %s", git_error_last()->message);
return 0;
}
@ -121,15 +189,25 @@ has_untracked(git_repository *repo)
return r;
}
/**
* @brief check for staged changes
*
* @param repo git repository object
* @return 1 if any staged changes found 0 otherwise
*/
int
has_staged(git_repository *repo)
{
git_status_entry entry;
git_status_list *list = NULL;
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
int i, c, r = 0;
if (git_status_list_new(&list, repo, NULL) < 0) {
l("Error checking for staged changes: %s", git_error_last()->message);
opts.show = GIT_STATUS_SHOW_INDEX_ONLY;
opts.flags = GIT_STATUS_INDEX_NEW;
if (git_status_list_new(&list, repo, &opts) < 0) {
L("Error checking for staged changes: %s", git_error_last()->message);
return 0;
}
@ -138,9 +216,9 @@ has_staged(git_repository *repo)
for (i = 0; i < c; i++) {
entry = *git_status_byindex(list, i);
if (entry.status & GIT_STATUS_INDEX_NEW
|| entry.status & GIT_STATUS_INDEX_DELETED
|| entry.status & GIT_STATUS_INDEX_MODIFIED) {
if (entry.status & (GIT_STATUS_INDEX_NEW
| GIT_STATUS_INDEX_DELETED
| GIT_STATUS_INDEX_MODIFIED)) {
r = 1;
break;
}
@ -152,71 +230,122 @@ has_staged(git_repository *repo)
}
#endif
inline unsigned
numcat(unsigned x, unsigned y)
{
unsigned pow = 10;
while(y >= pow) {
pow *= 10;
}
return (x * pow) + y;
}
int
str_to_int(char *str)
{
int res = -1;
char *c;
for (c = str; (*c != '\0') && isdigit(*c); c++) {
if (res == -1) {
res = *c - '0';
} else {
res = numcat(res, *c - '0');
}
}
return res;
}
int
main(int argc, char *argv[])
{
int i, code = -1;
char face[] = { ':', 0, '|' };
int code = -1;
/* print version information */
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0) {
printf("XD v%s\n", VERSION);
return 0;
}
if (argc > 1 && strcmp(argv[1], "-v") == 0) {
printf("XD [number] v%s\n", VERSION);
return 0;
#ifdef EXPLAIN
} else if (argc > 1 && strcmp(argv[1], "-e") == 0) {
explain = 1;
#endif
}
#ifdef GIT
git_repository *repo;
int git_ok = 0;
if ((repo = init_git())) {
git_ok = 1;
}
if (git_ok) {
/* change the eyes depending on the current git repo's status */
if (has_stashes(repo)) {
face[EYES] = '8'; /* goggle eyes if we have some stashed changes */
E("The current git repo has stashed changes.")
P("8"); /* goggle eyes if we have some stashed changes */
} else if (git_repository_is_empty(repo)) {
face[EYES] = 'B'; /* sunglasses if we're in a new repo with no HEAD */
E("This is a new git repo.")
P("B"); /* sunglasses if we're in a new repo with no HEAD */
} else {
face[EYES] = ';'; /* wink when we're in a git repo */
E("In a git repository.")
P(";"); /* wink when we're in a git repo */
}
/* change the nose depending on the current git repo's status */
if (has_staged(repo)) {
face[NOSE] = '*'; /* change to broken nose for staged changes */
E("The're staged changes.")
P("*"); /* change to broken nose for staged changes */
} else if (has_untracked(repo)) {
face[NOSE] = '^'; /* add a little nose when there are untracked changes in the repo */
E("There are untracked changes in the repository.")
P("^"); /* add a little nose when there are untracked changes in the repo */
} else if (git_repository_head_detached(repo)) {
face[NOSE] = '-'; /* add a minus nose when the HEAD is detached */
E("The HEAD is detached.")
P("-"); /* add a minus nose when the HEAD is detached */
}
}
git_repository_free(repo);
git_libgit2_shutdown();
} else
#endif
if (1) {
E("Not in a git repository.")
P(":");
}
/* get exit code from user args */
if (argv[1]) {
code = atoi(argv[1]);
if (argc > 1 && argv[argc - 1]) {
code = str_to_int(argv[argc - 1]);
if (code < 0) {
L("Return code, %d, not valid", code);
exit(1);
}
}
/* change mouth based on exit code */
if (code >= 0) {
switch (code) {
case 0: face[MOUTH] = ')'; break; /* all good */
case 130: face[MOUTH] = 'O'; break; /* Ctrl-c pressed (SIGTERM) */
case 126: face[MOUTH] = 'P'; break; /* permission denied */
case 127: face[MOUTH] = '/'; break; /* command not found */
default: face[MOUTH] = '('; break; /* all other codes (usually the program saying it has failed) */
case 0: /* all good */
E("Return code of %d, no errors.", code)
P(")");
break;
case 130: /* Ctrl-c pressed (SIGTERM) */
E("Return code of %d, ctrl-c was pressed, or SIGTERM sent.", code)
P("O");
break;
case 126: /* permission denied */
E("Return code of %d, permission denied.", code)
P("P");
break;
case 127: /* command not found */
E("Return code of %d, command not found.", code)
P("/");
break;
default: /* all other codes (usually the program saying it has failed) */
E("Return code of %d, probably an error with the program.", code)
P("(");
break;
}
} else {
face[MOUTH] = '|'; /* no code info */
E("No code information passed in.")
P("|"); /* no code info */
}
#ifdef GIT
git_repository_free(repo);
git_libgit2_shutdown();
#endif
printf("%c%c%c", face[EYES], face[NOSE], face[MOUTH]);
return code;
}

View File

@ -1,4 +1,4 @@
VERSION = 1.2
VERSION = 3.0
PKG_CONFIG = pkg-config
@ -16,6 +16,10 @@ ERR =
# uncomment to enable errors
# ERR = -DERR
EXPLAIN =
# comment to disable explinations
EXPLAIN = -DEXPLAIN
# add compilation details to VERSION variable
ifneq ($(GIT),)
VERSION := $(VERSION)"\\nlibgit2 "`$(PKG_CONFIG) --modversion $(GITLIB)`
@ -25,5 +29,10 @@ ifeq ($(ERR),)
else
VERSION := $(VERSION)"\\nerrors enabled"
endif
ifeq ($(EXPLAIN),)
VERSION := $(VERSION)"\\nexplinations disabled"
else
VERSION := $(VERSION)"\\nexplinations enabled"
endif
CC = cc