#define USE_THE_REPOSITORY_VARIABLE #include "cgit.h" #include "html.h" #include "ui-shared.h" #include "ui-tree.h" #include #include /* Mapping of file extensions to languages */ struct lang_extension { const char *ext; const char *lang; }; static const struct lang_extension lang_map[] = {{"c", "C"}, {"h", "C"}, {"js", "JavaScript"}, {"jsx", "JavaScript"}, {"py", "Python"}, {"go", "Go"}, {"rs", "Rust"}, {"rb", "Ruby"}, {"java", "Java"}, {"ts", "TypeScript"}, {"tsx", "TypeScript"}, {"cpp", "C++"}, {"cc", "C++"}, {"cxx", "C++"}, {"cs", "C#"}, {"php", "PHP"}, {"sh", "Shell"}, {"bash", "Shell"}, {"lua", "Lua"}, {"vim", "VimL"}, {"clj", "Clojure"}, {"scala", "Scala"}, {"kt", "Kotlin"}, {"swift", "Swift"}, {"m", "Objective-C"}, {"pl", "Perl"}, {"r", "R"}, {"sql", "SQL"}, {"html", "HTML"}, {"htm", "HTML"}, {"css", "CSS"}, {"scss", "SCSS"}, {"json", "JSON"}, {"yaml", "YAML"}, {"yml", "YAML"}, {"md", "Markdown"}, {"rst", "ReStructuredText"}, {NULL, NULL}}; struct lang_count { const char *lang; int count; }; struct lang_detect_context { struct lang_count counts[100]; int count_nr; int total_files; int max_count; const char *primary_lang; }; static const char *get_lang_from_ext(const char *filename) { const char *ext = strrchr(filename, '.'); if (!ext || ext == filename) return NULL; ext++; /* skip the dot */ for (int i = 0; lang_map[i].ext; i++) { if (!strcasecmp(ext, lang_map[i].ext)) return lang_map[i].lang; } return NULL; } static void count_language(struct lang_detect_context *ctx, const char *lang) { int i, found = 0; if (!lang) return; ctx->total_files++; /* Find or add language in counts */ for (i = 0; i < ctx->count_nr; i++) { if (!strcmp(ctx->counts[i].lang, lang)) { ctx->counts[i].count++; if (ctx->counts[i].count > ctx->max_count) { ctx->max_count = ctx->counts[i].count; ctx->primary_lang = ctx->counts[i].lang; } found = 1; break; } } if (!found && ctx->count_nr < 100) { ctx->counts[ctx->count_nr].lang = lang; ctx->counts[ctx->count_nr].count = 1; if (1 > ctx->max_count) { ctx->max_count = 1; ctx->primary_lang = lang; } ctx->count_nr++; } } static int count_files_cb(const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, void *cbdata) { struct lang_detect_context *ctx = cbdata; const char *lang; /* Skip directories */ if (S_ISDIR(mode)) return READ_TREE_RECURSIVE; /* Skip hidden files */ if (pathname[0] == '.') return 0; lang = get_lang_from_ext(pathname); count_language(ctx, lang); return 0; } void detect_repo_languages(struct cgit_repo *repo) { struct object_id oid; struct commit *commit; struct tree *tree; struct pathspec paths = {.nr = 0}; struct lang_detect_context ctx; if (!repo->defbranch) return; memset(&ctx, 0, sizeof(ctx)); /* Get the commit for the default branch */ if (repo_get_oid(the_repository, repo->defbranch, &oid)) return; commit = lookup_commit_reference(the_repository, &oid); if (!commit) return; if (repo_parse_commit(the_repository, commit)) return; tree = repo_get_commit_tree(the_repository, commit); if (!tree) return; /* Walk the tree and count file extensions */ read_tree(the_repository, tree, &paths, count_files_cb, &ctx); /* Store results in repo */ if (ctx.primary_lang) { repo->primary_lang = xstrdup(ctx.primary_lang); repo->lang_confidence = ctx.total_files > 0 ? (100 * ctx.max_count) / ctx.total_files : 0; } }