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:
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);