Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
0e3a7a84e6 | |||
68634537c0 | |||
529ac333ea | |||
50aa5f157f | |||
8f20a84ef7 | |||
09362e2c7c | |||
6025b2d832 | |||
6c099e3648 | |||
202142a8af | |||
6e5d8a1e6a | |||
d1752c3ac7 | |||
e6029a68e3 | |||
4af00678ca | |||
cab498199e | |||
3fb00b615c | |||
2dc0d582e6 | |||
2a6385fa36 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
*.o
|
*.o
|
||||||
XD
|
XD
|
||||||
|
compile_commands.json
|
||||||
|
2
Makefile
2
Makefile
@ -2,7 +2,7 @@ include config.mk
|
|||||||
|
|
||||||
# flags and incs
|
# flags and incs
|
||||||
PKGS = $(GITLIB)
|
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)`
|
LIBS = `$(PKG_CONFIG) --libs --cflags $(PKGS)`
|
||||||
|
|
||||||
all: XD
|
all: XD
|
||||||
|
52
XD.1
52
XD.1
@ -1,4 +1,4 @@
|
|||||||
.Dd December 19, 2024
|
.Dd Febuary 18, 2025
|
||||||
.Dt XD 1
|
.Dt XD 1
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
.Nm XD
|
.Nm XD
|
||||||
@ -7,10 +7,16 @@
|
|||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl v
|
.Op Fl v
|
||||||
|
.Op Fl e
|
||||||
|
.Op number
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
.Nm
|
.Nm
|
||||||
Displays information using a smiley face like so: :)
|
Displays information using a smiley face like so: :)
|
||||||
to interpret it refer to the following tables:
|
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
|
.Ss Eyes
|
||||||
.TS
|
.TS
|
||||||
tab(;) allbox;
|
tab(;) allbox;
|
||||||
@ -18,6 +24,7 @@ c;l.
|
|||||||
:;default
|
:;default
|
||||||
\;;in a git repo
|
\;;in a git repo
|
||||||
8;in a git repo with stashed changes
|
8;in a git repo with stashed changes
|
||||||
|
X;in a git repo during a merge
|
||||||
B;in a git repo with no commits
|
B;in a git repo with no commits
|
||||||
.TE
|
.TE
|
||||||
.Ss Nose
|
.Ss Nose
|
||||||
@ -35,8 +42,49 @@ tab(;) allbox;
|
|||||||
c;l.
|
c;l.
|
||||||
|;no signal provided
|
|;no signal provided
|
||||||
);previous signal is 0
|
);previous signal is 0
|
||||||
O;SIGINT sen't (Ctrl-c)
|
O;SIGINT sent (Ctrl-c)
|
||||||
P;permission denied
|
P;permission denied
|
||||||
/;command not found
|
/;command not found
|
||||||
(;previous signal is failure
|
(;previous signal is failure
|
||||||
.TE
|
.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.
|
||||||
|
220
XD.c
220
XD.c
@ -1,15 +1,24 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
#ifdef ERR
|
#ifdef ERR
|
||||||
|
#include <errno.h>
|
||||||
|
#endif
|
||||||
|
#if defined(EXPLAIN) || defined(ERR)
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef GIT
|
#ifdef GIT
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <dirent.h>
|
||||||
#include <git2.h>
|
#include <git2.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ERR
|
#define P(X) fwrite(X, 1, 1, stdout)
|
||||||
|
|
||||||
|
#if defined(ERR) || defined(EXPLAIN)
|
||||||
void
|
void
|
||||||
l(const char *fmt, ...)
|
l(const char *fmt, ...)
|
||||||
{
|
{
|
||||||
@ -26,12 +35,66 @@ l(const char *fmt, ...)
|
|||||||
fputc('\n', stderr);
|
fputc('\n', stderr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ERR
|
||||||
#define L(...) l(__VA_ARGS__)
|
#define L(...) l(__VA_ARGS__)
|
||||||
#else
|
#else
|
||||||
#define L(...)
|
#define L(...)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef EXPLAIN
|
||||||
|
static int explain = 0;
|
||||||
|
#define E(...) if (explain) { \
|
||||||
|
l(__VA_ARGS__); \
|
||||||
|
} else
|
||||||
|
#else
|
||||||
|
#define E(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef GIT
|
#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);
|
||||||
|
if (!rpath) {
|
||||||
|
L("realpath: %s", strerror(errno));
|
||||||
|
return 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
|
* @brief open git repo if one is available at the current path
|
||||||
*
|
*
|
||||||
@ -40,27 +103,38 @@ l(const char *fmt, ...)
|
|||||||
git_repository
|
git_repository
|
||||||
*init_git()
|
*init_git()
|
||||||
{
|
{
|
||||||
git_buf buf = GIT_BUF_INIT_CONST(NULL, 0);
|
char *buf;
|
||||||
git_repository *repo;
|
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) {
|
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (git_repository_discover(&buf, ".", 0, NULL) < 0) {
|
if (git_repository_open(&repo, buf) < 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);
|
L("Failed to open git repo: %s", git_error_last()->message);
|
||||||
git_buf_dispose(&buf);
|
free(buf);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* get rid of object containing git repo path and return the repo */
|
/* get rid of object containing git repo path and return the repo */
|
||||||
git_buf_dispose(&buf);
|
free(buf);
|
||||||
return repo;
|
return repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,9 +206,13 @@ has_staged(git_repository *repo)
|
|||||||
{
|
{
|
||||||
git_status_entry entry;
|
git_status_entry entry;
|
||||||
git_status_list *list = NULL;
|
git_status_list *list = NULL;
|
||||||
|
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
|
||||||
int i, c, r = 0;
|
int i, c, r = 0;
|
||||||
|
|
||||||
if (git_status_list_new(&list, repo, NULL) < 0) {
|
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);
|
L("Error checking for staged changes: %s", git_error_last()->message);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -144,9 +222,9 @@ has_staged(git_repository *repo)
|
|||||||
for (i = 0; i < c; i++) {
|
for (i = 0; i < c; i++) {
|
||||||
entry = *git_status_byindex(list, i);
|
entry = *git_status_byindex(list, i);
|
||||||
|
|
||||||
if (entry.status & GIT_STATUS_INDEX_NEW
|
if (entry.status & (GIT_STATUS_INDEX_NEW
|
||||||
|| entry.status & GIT_STATUS_INDEX_DELETED
|
| GIT_STATUS_INDEX_DELETED
|
||||||
|| entry.status & GIT_STATUS_INDEX_MODIFIED) {
|
| GIT_STATUS_INDEX_MODIFIED)) {
|
||||||
r = 1;
|
r = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -158,71 +236,125 @@ has_staged(git_repository *repo)
|
|||||||
}
|
}
|
||||||
#endif
|
#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
|
int
|
||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int i, code = -1;
|
int code = -1;
|
||||||
|
|
||||||
/* print version information */
|
/* print version information */
|
||||||
for (i = 1; i < argc; i++) {
|
if (argc > 1 && strcmp(argv[1], "-v") == 0) {
|
||||||
if (strcmp(argv[i], "-v") == 0) {
|
printf("XD [number] v%s\n", VERSION);
|
||||||
printf("XD v%s\n", VERSION);
|
return 0;
|
||||||
return 0;
|
#ifdef EXPLAIN
|
||||||
}
|
} else if (argc > 1 && strcmp(argv[1], "-e") == 0) {
|
||||||
|
explain = 1;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef GIT
|
#ifdef GIT
|
||||||
git_repository *repo;
|
git_repository *repo;
|
||||||
int git_ok = 0;
|
|
||||||
|
|
||||||
if ((repo = init_git())) {
|
if ((repo = init_git())) {
|
||||||
git_ok = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (git_ok) {
|
|
||||||
/* change the eyes depending on the current git repo's status */
|
/* change the eyes depending on the current git repo's status */
|
||||||
if (has_stashes(repo)) {
|
if (has_stashes(repo)) {
|
||||||
printf("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_state(repo) == GIT_REPOSITORY_STATE_MERGE) {
|
||||||
|
E("The current git repo is in the middle of a merge.")
|
||||||
|
P("X"); /* laughing eyes cause the user is fucked */
|
||||||
} else if (git_repository_is_empty(repo)) {
|
} else if (git_repository_is_empty(repo)) {
|
||||||
printf("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 {
|
} else {
|
||||||
printf(";"); /* 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 */
|
/* change the nose depending on the current git repo's status */
|
||||||
if (has_staged(repo)) {
|
if (has_staged(repo)) {
|
||||||
printf("*"); /* change to broken nose for staged changes */
|
E("They're staged changes.")
|
||||||
|
P("*"); /* change to broken nose for staged changes */
|
||||||
} else if (has_untracked(repo)) {
|
} else if (has_untracked(repo)) {
|
||||||
printf("^"); /* 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)) {
|
} else if (git_repository_head_detached(repo)) {
|
||||||
printf("-"); /* 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
|
} else
|
||||||
#endif
|
#endif
|
||||||
if (1) {
|
if (1) {
|
||||||
printf(":");
|
E("Not in a git repository.")
|
||||||
|
P(":");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* get exit code from user args */
|
/* get exit code from user args */
|
||||||
if (argv[1]) {
|
if (argc > 1 && argv[argc - 1]) {
|
||||||
code = atoi(argv[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 */
|
/* change mouth based on exit code */
|
||||||
if (code >= 0) {
|
if (code >= 0) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 0: printf(")"); break; /* all good */
|
case 0: /* all good */
|
||||||
case 130: printf("O"); break; /* Ctrl-c pressed (SIGTERM) */
|
E("Return code of %d, no errors.", code)
|
||||||
case 126: printf("P"); break; /* permission denied */
|
P(")");
|
||||||
case 127: printf("/"); break; /* command not found */
|
break;
|
||||||
default: printf("("); break; /* all other codes (usually the program saying it has failed) */
|
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 {
|
} else {
|
||||||
printf("|"); /* no code info */
|
E("No code information passed in.")
|
||||||
|
P("|"); /* no code info */
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef GIT
|
return code;
|
||||||
git_repository_free(repo);
|
|
||||||
git_libgit2_shutdown();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
11
config.mk
11
config.mk
@ -1,4 +1,4 @@
|
|||||||
VERSION = 1.2
|
VERSION = 3.2
|
||||||
|
|
||||||
PKG_CONFIG = pkg-config
|
PKG_CONFIG = pkg-config
|
||||||
|
|
||||||
@ -16,6 +16,10 @@ ERR =
|
|||||||
# uncomment to enable errors
|
# uncomment to enable errors
|
||||||
# ERR = -DERR
|
# ERR = -DERR
|
||||||
|
|
||||||
|
EXPLAIN =
|
||||||
|
# comment to disable explinations
|
||||||
|
EXPLAIN = -DEXPLAIN
|
||||||
|
|
||||||
# add compilation details to VERSION variable
|
# add compilation details to VERSION variable
|
||||||
ifneq ($(GIT),)
|
ifneq ($(GIT),)
|
||||||
VERSION := $(VERSION)"\\nlibgit2 "`$(PKG_CONFIG) --modversion $(GITLIB)`
|
VERSION := $(VERSION)"\\nlibgit2 "`$(PKG_CONFIG) --modversion $(GITLIB)`
|
||||||
@ -25,5 +29,10 @@ ifeq ($(ERR),)
|
|||||||
else
|
else
|
||||||
VERSION := $(VERSION)"\\nerrors enabled"
|
VERSION := $(VERSION)"\\nerrors enabled"
|
||||||
endif
|
endif
|
||||||
|
ifeq ($(EXPLAIN),)
|
||||||
|
VERSION := $(VERSION)"\\nexplinations disabled"
|
||||||
|
else
|
||||||
|
VERSION := $(VERSION)"\\nexplinations enabled"
|
||||||
|
endif
|
||||||
|
|
||||||
CC = cc
|
CC = cc
|
||||||
|
Reference in New Issue
Block a user