summaryrefslogtreecommitdiffstats
path: root/XD.c
diff options
context:
space:
mode:
authorSquibid <me@zacharyscheiman.com>2024-12-19 00:59:22 -0600
committerSquibid <me@zacharyscheiman.com>2024-12-19 01:03:54 -0600
commitb89a69ed5068ce164a31b60a763e5a1547080f38 (patch)
tree163d124d9cc9a2e1ae1d47413c461da564a02cdd /XD.c
downloadXD-b89a69ed5068ce164a31b60a763e5a1547080f38.tar.gz
XD-b89a69ed5068ce164a31b60a763e5a1547080f38.tar.bz2
XD-b89a69ed5068ce164a31b60a763e5a1547080f38.zip
initial commit
Diffstat (limited to '')
-rw-r--r--XD.c186
1 files changed, 186 insertions, 0 deletions
diff --git a/XD.c b/XD.c
new file mode 100644
index 0000000..c07ad6c
--- /dev/null
+++ b/XD.c
@@ -0,0 +1,186 @@
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef ERR
+#include <stdarg.h>
+#include <string.h>
+#endif
+
+#ifdef GIT
+#include <git2.h>
+#include <git2/common.h>
+#include <git2/errors.h>
+#include <git2/global.h>
+#include <git2/refs.h>
+#include <git2/repository.h>
+#include <git2/status.h>
+#endif
+
+enum face { EYES, NOSE, MOUTH };
+
+void
+l(const char *fmt, ...)
+{
+ #ifdef ERR
+ va_list 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
+}
+
+#ifdef GIT
+/**
+ * @brief open git repo if one is available at the current path
+ *
+ * @return a pointer to the git repo object
+ */
+git_repository
+*init_git()
+{
+ git_buf buf = GIT_BUF_INIT_CONST(NULL, 0);
+ git_repository *repo;
+
+ if (git_libgit2_init() < 0) {
+ 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);
+ return NULL;
+ }
+
+ /* get rid of object containing git repo path and return the repo */
+ git_buf_dispose(&buf);
+ return repo;
+}
+
+/**
+ * @brief check for any existing stashes in the provided git repo
+ *
+ * @param repo git repo to check for existing stashes
+ * @return 1 if stashes found 0 otherwise
+ */
+int
+has_stashes(git_repository *repo)
+{
+ git_reference *stash = NULL;
+ int e;
+
+ 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);
+ return 0;
+ } else {
+ e = 1;
+ }
+
+ git_reference_free(stash);
+ return e;
+}
+
+/**
+ * @brief check for any untracked changes in the current git repo
+ *
+ * @param repo git repository object
+ * @return 1 if any untracked changes have been found 0 otherwise
+ */
+int
+has_untracked(git_repository *repo)
+{
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ git_status_list *list = NULL;
+ int r = 0;
+
+ opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
+
+ if (git_status_list_new(&list, repo, &opts) < 0) {
+ l("Error checking for untacked changes: %s", git_error_last()->message);
+ return 0;
+ }
+
+ /* if any changes are found return 1 */
+ if (git_status_list_entrycount(list) > 0) {
+ r = 1;
+ }
+
+ git_status_list_free(list);
+ return r;
+}
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ int code = -1;
+ char face[] = { ':', 0, '|' };
+
+ #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 */
+ } else if (git_repository_is_empty(repo)) {
+ face[EYES] = 'B'; /* sunglasses if we're in a new repo with no HEAD */
+ } else {
+ face[EYES] = ';'; /* wink when we're in a git repo */
+ }
+
+ /* change the nose depending on the current git repo's status */
+ if (has_untracked(repo)) {
+ face[NOSE] = '^'; /* 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 */
+ }
+ }
+ #endif
+
+ /* get exit code from user args */
+ code = atoi(argv[1] ? argv[1] : "-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) */
+ }
+ } else {
+ face[MOUTH] = '|'; /* no code info */
+ }
+
+ #ifdef GIT
+ git_repository_free(repo);
+ git_libgit2_shutdown();
+ #endif
+
+ printf("%c%c%c", face[EYES], face[NOSE], face[MOUTH]);
+}