gout

A static git page generator
git clone https://git.bracken.jp/gout.git
Log | Files | Refs | README | LICENSE

commit 3ed1be17c156a40a5b25cda0afe98c951f5117b4
parent 70e3545308c95e2a8ee1a15071c2003b2626bfb1
Author: Chris Bracken <chris@bracken.jp>
Date:   Fri, 20 Feb 2026 21:14:48 +0900

git: add tests for filemode and filetype lookups

Also simplify the implementations.

Diffstat:
MBUILD.gn | 1+
Msrc/git/git.c | 48+++++++++++++-----------------------------------
Asrc/git/git_tests.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/git/internal.h | 3+++
4 files changed, 114 insertions(+), 35 deletions(-)

diff --git a/BUILD.gn b/BUILD.gn @@ -71,6 +71,7 @@ executable("gout_tests") { "src/fs_inmemory.c", "src/git/delta_tests.c", "src/git/commit_tests.c", + "src/git/git_tests.c", "src/gout_index_options_tests.c", "src/gout_options_tests.c", "src/git/repo_tests.c", diff --git a/src/git/git.c b/src/git/git.c @@ -39,8 +39,6 @@ static void libgit2_for_each_file(Git* git, FileCallback cb, void* user_data); /* Internal libgit2 utilities. */ static int oid_for_spec(git_repository* repo, const char* spec, git_oid* oid); static size_t string_count_lines(const char* str, ssize_t str_len); -static char get_filetype(git_filemode_t m); -static char* format_filemode(git_filemode_t m); static bool gitrepo_walk_tree_files(git_repository* repo, git_tree* tree, const char* path, @@ -266,7 +264,7 @@ static size_t string_count_lines(const char* str, ssize_t str_len) { return str[str_len - 1] == '\n' ? lines : lines + 1; } -static char get_filetype(git_filemode_t m) { +char git_get_filetype(git_filemode_t m) { switch (m & S_IFMT) { case S_IFREG: return '-'; @@ -287,38 +285,18 @@ static char get_filetype(git_filemode_t m) { } } -static char* format_filemode(git_filemode_t m) { - char* mode = ecalloc(GOUT_FILEMODE_MAX_SIZE, sizeof(char)); - memset(mode, '-', GOUT_FILEMODE_MAX_SIZE - 1); - mode[GOUT_FILEMODE_MAX_SIZE - 1] = '\0'; +char* git_format_filemode(git_filemode_t m) { + char* mode = estrdup("----------"); - mode[0] = get_filetype(m); - if (m & S_IRUSR) { - mode[1] = 'r'; - } - if (m & S_IWUSR) { - mode[2] = 'w'; - } - if (m & S_IXUSR) { - mode[3] = 'x'; - } - if (m & S_IRGRP) { - mode[4] = 'r'; - } - if (m & S_IWGRP) { - mode[5] = 'w'; - } - if (m & S_IXGRP) { - mode[6] = 'x'; - } - if (m & S_IROTH) { - mode[7] = 'r'; - } - if (m & S_IWOTH) { - mode[8] = 'w'; - } - if (m & S_IXOTH) { - mode[9] = 'x'; + mode[0] = git_get_filetype(m); + static const char chars[] = "rwxrwxrwx"; + static const int masks[] = {S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, + S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH}; + + for (size_t i = 0; i < 9; i++) { + if (m & masks[i]) { + mode[i + 1] = chars[i]; + } } if (m & S_ISUID) { @@ -414,7 +392,7 @@ static bool gitrepo_walk_tree_files(git_repository* repo, size_lines = string_count_lines(content, size_bytes); } - char* filemode = format_filemode(git_tree_entry_filemode(entry)); + char* filemode = git_format_filemode(git_tree_entry_filemode(entry)); GitFile* fileinfo = gitfile_create(kFileTypeFile, filemode, entrypath, entrypath, "", size_bytes, size_lines, content); diff --git a/src/git/git_tests.c b/src/git/git_tests.c @@ -0,0 +1,97 @@ +#include <sys/stat.h> +#include <stdlib.h> +#include "git/internal.h" +#include "utest.h" + +UTEST(git_format_filemode, RegularFile) { + char* mode = git_format_filemode(S_IFREG | 0644); + EXPECT_STREQ("-rw-r--r--", mode); + free(mode); +} + +UTEST(git_format_filemode, ExecutableFile) { + char* mode = git_format_filemode(S_IFREG | 0755); + EXPECT_STREQ("-rwxr-xr-x", mode); + free(mode); +} + +UTEST(git_format_filemode, Directory) { + char* mode = git_format_filemode(S_IFDIR | 0755); + EXPECT_STREQ("drwxr-xr-x", mode); + free(mode); +} + +UTEST(git_format_filemode, SymbolicLink) { + char* mode = git_format_filemode(S_IFLNK | 0777); + EXPECT_STREQ("lrwxrwxrwx", mode); + free(mode); +} + +UTEST(git_format_filemode, Setuid) { + char* mode = git_format_filemode(S_IFREG | S_ISUID | 0755); + EXPECT_STREQ("-rwsr-xr-x", mode); + free(mode); + + mode = git_format_filemode(S_IFREG | S_ISUID | 0644); + EXPECT_STREQ("-rwSr--r--", mode); + free(mode); +} + +UTEST(git_format_filemode, Setgid) { + char* mode = git_format_filemode(S_IFREG | S_ISGID | 0755); + EXPECT_STREQ("-rwxr-sr-x", mode); + free(mode); + + mode = git_format_filemode(S_IFREG | S_ISGID | 0644); + EXPECT_STREQ("-rw-r-Sr--", mode); + free(mode); +} + +UTEST(git_format_filemode, StickyBit) { + /* Sticky bit with execute bit set: 't' */ + char* mode = git_format_filemode(S_IFDIR | S_ISVTX | 0777); + EXPECT_STREQ("drwxrwxrwt", mode); + free(mode); + + mode = git_format_filemode(S_IFDIR | S_ISVTX | 0755); + EXPECT_STREQ("drwxr-xr-t", mode); + free(mode); + + /* Sticky bit without execute bit set: 'T' */ + mode = git_format_filemode(S_IFDIR | S_ISVTX | 0754); + EXPECT_STREQ("drwxr-xr-T", mode); + free(mode); +} + +UTEST(git_format_filemode, OtherTypes) { + /* Block device */ + char* mode = git_format_filemode(S_IFBLK | 0600); + EXPECT_EQ('b', mode[0]); + free(mode); + + /* Character device */ + mode = git_format_filemode(S_IFCHR | 0600); + EXPECT_EQ('c', mode[0]); + free(mode); + + /* FIFO */ + mode = git_format_filemode(S_IFIFO | 0600); + EXPECT_EQ('p', mode[0]); + free(mode); + + /* Socket */ + mode = git_format_filemode(S_IFSOCK | 0600); + EXPECT_EQ('s', mode[0]); + free(mode); +} + +UTEST(git_get_filetype, AllTypes) { + EXPECT_EQ('-', git_get_filetype(S_IFREG)); + EXPECT_EQ('d', git_get_filetype(S_IFDIR)); + EXPECT_EQ('l', git_get_filetype(S_IFLNK)); + EXPECT_EQ('b', git_get_filetype(S_IFBLK)); + EXPECT_EQ('c', git_get_filetype(S_IFCHR)); + EXPECT_EQ('p', git_get_filetype(S_IFIFO)); + EXPECT_EQ('s', git_get_filetype(S_IFSOCK)); + EXPECT_EQ('?', git_get_filetype(0)); /* Unknown */ +} diff --git a/src/git/internal.h b/src/git/internal.h @@ -26,6 +26,9 @@ GitFile* gitfile_create(FileType type, const char* content); void gitfile_free(GitFile* file); +char git_get_filetype(git_filemode_t m); +char* git_format_filemode(git_filemode_t m); + GitReference* gitreference_create(git_repository* repo, git_reference* ref); void gitreference_free(GitReference* ref); int gitreference_compare(const void* r1, const void* r2);