Skip to content

Commit

Permalink
Add user patch framework (#1037)
Browse files Browse the repository at this point in the history
* Add user patch framework

* Add git status patch

* Add namefirst-gitstatus compatibility

* Add patch targets

* Fix gitstatus colors and patch order
  • Loading branch information
luukvbaal authored Jun 2, 2021
1 parent 6f86213 commit 93e7995
Show file tree
Hide file tree
Showing 6 changed files with 766 additions and 0 deletions.
33 changes: 33 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ O_NOSSN := 0 # enable session support
O_NOUG := 0 # disable user, group name in status bar
O_NOX11 := 0 # disable X11 integration

# User patches
O_GITSTATUS := 0 # add git status to detail view
O_NAMEFIRST := 0 # print file name first, add uid and guid to detail view

ifeq ($(strip $(O_GITSTATUS)),1)
LDLIBS += -lgit2
endif

# convert targets to flags for backwards compatibility
ifneq ($(filter debug,$(MAKECMDGOALS)),)
O_DEBUG := 1
Expand Down Expand Up @@ -141,10 +149,15 @@ DESKTOPFILE = misc/desktop/nnn.desktop
LOGOSVG = misc/logo/logo.svg
LOGO64X64 = misc/logo/logo-64x64.png

GITSTATUS = misc/patches/gitstatus
NAMEFIRST = misc/patches/namefirst

all: $(BIN)

$(BIN): $(SRC) $(HEADERS)
@$(MAKE) --silent prepatch
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS)
@$(MAKE) --silent postpatch

# targets for backwards compatibility
debug: $(BIN)
Expand Down Expand Up @@ -232,6 +245,26 @@ upload-local: sign static
clean:
$(RM) -f $(BIN) nnn-$(VERSION).tar.gz *.sig $(BIN)-static $(BIN)-static-$(VERSION).x86_64.tar.gz $(BIN)-icons-static $(BIN)-icons-static-$(VERSION).x86_64.tar.gz $(BIN)-nerd-static $(BIN)-nerd-static-$(VERSION).x86_64.tar.gz

prepatch:
ifeq ($(strip $(O_NAMEFIRST)),1)
patch --forward --strip=1 --input=$(NAMEFIRST)/mainline.diff
ifeq ($(strip $(O_GITSTATUS)),1)
patch --forward --strip=1 --input=$(GITSTATUS)/namefirst.diff
endif
else ifeq ($(strip $(O_GITSTATUS)),1)
patch --forward --strip=1 --input=$(GITSTATUS)/mainline.diff
endif

postpatch:
ifeq ($(strip $(O_NAMEFIRST)),1)
ifeq ($(strip $(O_GITSTATUS)),1)
patch --reverse --strip=1 --input=$(GITSTATUS)/namefirst.diff
endif
patch --reverse --strip=1 --input=$(NAMEFIRST)/mainline.diff
else ifeq ($(strip $(O_GITSTATUS)),1)
patch --reverse --strip=1 --input=$(GITSTATUS)/mainline.diff
endif

skip: ;

.PHONY: all install uninstall strip static dist sign upload-local clean install-desktop uninstall-desktop
33 changes: 33 additions & 0 deletions misc/haiku/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ O_NOSSN := 0 # enable session support
O_NOUG := 0 # disable user, group name in status bar
O_NOX11 := 0 # disable X11 integration

# User patches
O_GITSTATUS := 0 # add git status to detail view
O_NAMEFIRST := 0 # print file name first, add uid and guid to detail view

ifeq ($(strip $(O_GITSTATUS)),1)
LDLIBS += -lgit2
endif

# convert targets to flags for backwards compatibility
ifneq ($(filter debug,$(MAKECMDGOALS)),)
O_DEBUG := 1
Expand Down Expand Up @@ -143,6 +151,9 @@ HEADERS = src/nnn.h
BIN = nnn
OBJS := nnn.o $(OBJS_HAIKU)

GITSTATUS = misc/patches/gitstatus
NAMEFIRST = misc/patches/namefirst

all: $(BIN)

ifeq ($(shell uname -s), Haiku)
Expand All @@ -154,7 +165,9 @@ nnn.o: $(SRC) $(HEADERS)
$(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<

$(BIN): $(OBJS)
@$(MAKE) --silent prepatch
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
@$(MAKE) --silent postpatch

# targets for backwards compatibility
debug: $(BIN)
Expand Down Expand Up @@ -207,6 +220,26 @@ upload-local: sign static
clean:
$(RM) -f $(BIN) nnn-$(VERSION).tar.gz *.sig $(BIN)-static $(BIN)-static-$(VERSION).x86_64.tar.gz

prepatch:
ifeq ($(strip $(O_NAMEFIRST)),1)
patch --forward --strip=1 --input=$(NAMEFIRST)/mainline.diff
ifeq ($(strip $(O_GITSTATUS)),1)
patch --forward --strip=1 --input=$(GITSTATUS)/namefirst.diff
endif
else ifeq ($(strip $(O_GITSTATUS)),1)
patch --forward --strip=1 --input=$(GITSTATUS)/mainline.diff
endif

postpatch:
ifeq ($(strip $(O_NAMEFIRST)),1)
ifeq ($(strip $(O_GITSTATUS)),1)
patch --reverse --strip=1 --input=$(GITSTATUS)/namefirst.diff
endif
patch --reverse --strip=1 --input=$(NAMEFIRST)/mainline.diff
else ifeq ($(strip $(O_GITSTATUS)),1)
patch --reverse --strip=1 --input=$(GITSTATUS)/mainline.diff
endif

skip: ;

.PHONY: all install uninstall strip static dist sign upload-local clean
14 changes: 14 additions & 0 deletions misc/patches/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<h1 align="center">User Patch Framework</h1>

This directory contains user submitted patches that were rejected from mainline as they tend to be more subjective in nature. The patches will be adapted on each release when necessary (v4.1 onwards). Each patch can be applied through its respective make variable during compilation. In case inter-patch merge conflicts occur, a compatability patch is provided and will automatically be applied.

## List of patches
| Patch (a-z) | Description | Make variable |
| --- | --- | --- |
| gitstatus | Add git status column to the detail view. Requires [libgit2](https://github.com/libgit2/libgit2). | O_GISTATUS |
| namefirst | Print filenames first in the detail view. Print user/group columns when a directory contains different users/groups. | O_NAMEFIRST |

To apply the patches, use the corresponding make variables, e.g.:

make O_NAMEFIRST=1

233 changes: 233 additions & 0 deletions misc/patches/gitstatus/mainline.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
# Description: Add git status column to detail mode.
#
# Dependencies: libgit2
#
# Authors: @crides, Luuk van Baal
diff --git a/src/nnn.c b/src/nnn.c
index 562622f..96d946a 100644
--- a/src/nnn.c
+++ b/src/nnn.c
@@ -127,6 +127,8 @@
#include "qsort.h"
#endif

+#include <git2.h>
+
/* Macro definitions */
#define VERSION "4.0"
#define GENERAL_INFO "BSD 2-Clause\nhttps://github.com/jarun/nnn"
@@ -245,6 +247,18 @@ typedef unsigned char uchar_t;
typedef unsigned short ushort_t;
typedef unsigned long long ulong_t;

+typedef enum {
+ GIT_COLUMN_STATUS_NONE = 0,
+ GIT_COLUMN_STATUS_UNMOD,
+ GIT_COLUMN_STATUS_NEW,
+ GIT_COLUMN_STATUS_MODIFIED,
+ GIT_COLUMN_STATUS_DELETED,
+ GIT_COLUMN_STATUS_RENAMED,
+ GIT_COLUMN_STATUS_TYPE_CHANGE,
+ GIT_COLUMN_STATUS_IGNORED,
+ GIT_COLUMN_STATUS_CONFLICTED,
+} git_column_status_t;
+
/* STRUCTURES */

/* Directory entry */
@@ -258,6 +272,8 @@ typedef struct entry {
ulong_t blocks : 40; /* 5 bytes (enough for 512 TiB in 512B blocks allocated) */
ulong_t nlen : 16; /* 2 bytes (length of file name) */
ulong_t flags : 8; /* 1 byte (flags specific to the file) */
+ git_column_status_t status_indexed;
+ git_column_status_t status_staged;
};
#ifndef NOUG
uid_t uid; /* 4 bytes */
@@ -362,7 +378,18 @@ typedef struct {
} session_header_t;
#endif

+typedef struct {
+ char *path;
+ git_status_t status;
+} simple_git_status_t;
+
+typedef struct {
+ simple_git_status_t *statuses;
+ size_t len;
+} simple_git_statuses_t;
+
/* GLOBALS */
+simple_git_statuses_t git_statuses;

/* Configuration, contexts */
static settings cfg = {
@@ -809,6 +836,92 @@ static void notify_fifo(bool force);

/* Functions */

+static git_column_status_t git_get_indexed_status(const uint32_t status) {
+ if (status & GIT_STATUS_INDEX_NEW) return GIT_COLUMN_STATUS_NEW;
+ if (status & GIT_STATUS_INDEX_MODIFIED) return GIT_COLUMN_STATUS_MODIFIED;
+ if (status & GIT_STATUS_INDEX_DELETED) return GIT_COLUMN_STATUS_DELETED;
+ if (status & GIT_STATUS_INDEX_RENAMED) return GIT_COLUMN_STATUS_RENAMED;
+ if (status & GIT_STATUS_INDEX_TYPECHANGE) return GIT_COLUMN_STATUS_TYPE_CHANGE;
+ return GIT_COLUMN_STATUS_UNMOD;
+}
+
+static git_column_status_t git_get_staged_status(const uint32_t status) {
+ if (status & GIT_STATUS_WT_NEW) return GIT_COLUMN_STATUS_NEW;
+ if (status & GIT_STATUS_WT_MODIFIED) return GIT_COLUMN_STATUS_MODIFIED;
+ if (status & GIT_STATUS_WT_DELETED) return GIT_COLUMN_STATUS_DELETED;
+ if (status & GIT_STATUS_WT_RENAMED) return GIT_COLUMN_STATUS_RENAMED;
+ if (status & GIT_STATUS_WT_TYPECHANGE) return GIT_COLUMN_STATUS_TYPE_CHANGE;
+ if (status & GIT_STATUS_IGNORED) return GIT_COLUMN_STATUS_IGNORED;
+ if (status & GIT_STATUS_CONFLICTED) return GIT_COLUMN_STATUS_CONFLICTED;
+ return GIT_COLUMN_STATUS_UNMOD;
+}
+
+static void print_gitstatus(git_column_status_t status) {
+ switch (status) {
+ case GIT_COLUMN_STATUS_NONE: break;
+ case GIT_COLUMN_STATUS_UNMOD: addch('-' | 0); break;
+ case GIT_COLUMN_STATUS_NEW: addch('N' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_EXE))); break;
+ case GIT_COLUMN_STATUS_MODIFIED: addch('M' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(4))); break;
+ case GIT_COLUMN_STATUS_DELETED: addch('D' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_UND))); break;
+ case GIT_COLUMN_STATUS_RENAMED: addch('R' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_CHR))); break;
+ case GIT_COLUMN_STATUS_TYPE_CHANGE: addch('T' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_HRD))); break;
+ case GIT_COLUMN_STATUS_IGNORED: addch('I' | 0); break;
+ case GIT_COLUMN_STATUS_CONFLICTED: addch('U' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_UND))); break;
+ }
+}
+
+static bool starts_with(const char *const s, const char *const start) {
+ const size_t start_len = strlen(start);
+
+ return (start_len <= strlen(s)) && (0 == strncmp(s, start, start_len));
+}
+
+static size_t mkpath(const char *dir, const char *name, char *out);
+static simple_git_statuses_t statuses_from_path(const char *path) {
+ simple_git_statuses_t statuses = { .statuses = NULL, .len = 0 };
+ git_buf ret = { .ptr = 0, .asize = 0, .size = 0 };
+ git_repository *repo;
+ git_repository_discover(&ret, path, false, NULL);
+ git_repository_open(&repo, ret.ptr);
+ git_buf_dispose(&ret);
+
+ if (repo) {
+ git_status_list *status_list = NULL;
+ git_status_list_new(&status_list, repo, NULL);
+ statuses.len = git_status_list_entrycount(status_list);
+ statuses.statuses = malloc(statuses.len * sizeof(simple_git_status_t));
+ const char *workdir = git_repository_workdir(repo);
+
+ for (size_t i = 0; i < statuses.len; i ++) {
+ const git_status_entry *status_ent = git_status_byindex(status_list, i);
+ const char *entry_path = status_ent->head_to_index ? status_ent->head_to_index->old_file.path
+ : status_ent->index_to_workdir->old_file.path;
+ char *joined = malloc(PATH_MAX * sizeof(char));
+ mkpath(workdir, entry_path, joined);
+ char *canon_path = realpath(joined, NULL);
+
+ if (canon_path) {
+ statuses.statuses[i].path = canon_path;
+ free(joined);
+ } else
+ statuses.statuses[i].path = joined;
+
+ statuses.statuses[i].status = status_ent->status;
+ }
+
+ git_status_list_free(status_list);
+ git_repository_free(repo);
+ }
+ return statuses;
+}
+
+static void statuses_free(simple_git_statuses_t statuses) {
+ for (size_t i = 0; i < statuses.len; i ++)
+ free(statuses.statuses[i].path);
+
+ free(statuses.statuses);
+}
+
static void sigint_handler(int UNUSED(sig))
{
g_state.interrupt = 1;
@@ -3794,6 +3907,12 @@ static void printent(const struct entry *ent, uint_t namecols, bool sel)

if (attrs)
attroff(attrs);
+
+ if (ent->status_indexed != GIT_COLUMN_STATUS_NONE)
+ addch(' ');
+
+ print_gitstatus(ent->status_indexed);
+ print_gitstatus(ent->status_staged);
}

attrs = 0;
@@ -3809,6 +3928,7 @@ static void printent(const struct entry *ent, uint_t namecols, bool sel)
color_pair = C_MIS;
if (color_pair && fcolors[color_pair])
attrs |= COLOR_PAIR(color_pair);
+
#ifdef ICONS_ENABLED
print_icon(ent, attrs);
#endif
@@ -5088,6 +5208,9 @@ static int dentfill(char *path, struct entry **ppdents)
struct stat sb_path, sb;
DIR *dirp = opendir(path);

+ statuses_free(git_statuses);
+ git_statuses = statuses_from_path(path);
+
ndents = 0;

DPRINTF_S(__func__);
@@ -5277,6 +5400,26 @@ static int dentfill(char *path, struct entry **ppdents)
dentp->gid = sb.st_gid;
#endif

+ if (git_statuses.len) {
+ uint32_t merged_status = 0;
+ char joined[PATH_MAX];
+ mkpath(path, dentp->name, joined);
+ char *real = realpath(joined, NULL);
+ char *canon_path = real ? real : joined;
+
+ for (size_t i = 0; i < git_statuses.len; i ++)
+ if (((dentp->mode & S_IFMT) == S_IFDIR) ? starts_with(git_statuses.statuses[i].path, canon_path) :
+ !xstrcmp(git_statuses.statuses[i].path, canon_path) || starts_with(canon_path, git_statuses.statuses[i].path))
+ merged_status |= git_statuses.statuses[i].status;
+
+ dentp->status_indexed = git_get_indexed_status(merged_status);
+ dentp->status_staged = git_get_staged_status(merged_status);
+ free(real);
+ } else {
+ dentp->status_indexed = GIT_COLUMN_STATUS_NONE;
+ dentp->status_staged = GIT_COLUMN_STATUS_NONE;
+ }
+
dentp->flags = S_ISDIR(sb.st_mode) ? 0 : ((sb.st_nlink > 1) ? HARD_LINK : 0);
if (entflags) {
dentp->flags |= entflags;
@@ -7712,6 +7855,8 @@ static void cleanup(void)
fflush(stdout);
}
#endif
+ statuses_free(git_statuses);
+ git_libgit2_shutdown();
free(selpath);
free(plgpath);
free(cfgpath);
@@ -7907,6 +8052,7 @@ int main(int argc, char *argv[])
return EXIT_FAILURE;

atexit(cleanup);
+ git_libgit2_init();

/* Check if we are in path list mode */
if (!isatty(STDIN_FILENO)) {
Loading

0 comments on commit 93e7995

Please sign in to comment.