| 108 | /* ── git log parsing (popen "git log") ────────────────────────────── */ |
| 109 | |
| 110 | static int parse_git_log(const char *repo_path, commit_t **out, int *out_count) { |
| 111 | *out = NULL; |
| 112 | *out_count = 0; |
| 113 | |
| 114 | if (!cbm_validate_shell_path_arg(repo_path)) { |
| 115 | return CBM_NOT_FOUND; |
| 116 | } |
| 117 | |
| 118 | char cmd[CBM_SZ_1K]; |
| 119 | #ifdef _WIN32 |
| 120 | /* cmd.exe does not recognize single quotes, and '/dev/null' is a POSIX path. */ |
| 121 | const char *null_dev = "NUL"; |
| 122 | #else |
| 123 | const char *null_dev = "/dev/null"; |
| 124 | #endif |
| 125 | /* git -C "<path>" works on both cmd.exe and POSIX shells. Double quotes are |
| 126 | * safe here because cbm_validate_shell_arg (above) rejects ", $, `, \ and the |
| 127 | * other shell metacharacters that would otherwise be active inside them. */ |
| 128 | snprintf(cmd, sizeof(cmd), |
| 129 | "git -C \"%s\" log --name-only --pretty=format:COMMIT:%%H:%%ct " |
| 130 | "--since=\"1 year ago\" --max-count=10000 2>%s", |
| 131 | repo_path, null_dev); |
| 132 | |
| 133 | FILE *fp = cbm_popen(cmd, "r"); |
| 134 | if (!fp) { |
| 135 | return CBM_NOT_FOUND; |
| 136 | } |
| 137 | |
| 138 | int cap = CBM_SZ_64; |
| 139 | commit_t *commits = malloc(cap * sizeof(commit_t)); |
| 140 | int count = 0; |
| 141 | commit_t current = {0}; |
| 142 | |
| 143 | char line[CBM_SZ_1K]; |
| 144 | while (fgets(line, sizeof(line), fp)) { |
| 145 | size_t len = strlen(line); |
| 146 | while (len > 0 && (line[len - SKIP_ONE] == '\n' || line[len - SKIP_ONE] == '\r')) { |
| 147 | line[--len] = '\0'; |
| 148 | } |
| 149 | if (len == 0) { |
| 150 | continue; |
| 151 | } |
| 152 | |
| 153 | if (strncmp(line, "COMMIT:", SLEN("COMMIT:")) == 0) { |
| 154 | if (current.count > 0) { |
| 155 | if (count >= cap) { |
| 156 | cap *= PAIR_LEN; |
| 157 | commits = safe_realloc(commits, cap * sizeof(commit_t)); |
| 158 | } |
| 159 | commits[count++] = current; |
| 160 | memset(¤t, 0, sizeof(current)); |
| 161 | } |
| 162 | /* Parse the unix timestamp from "COMMIT:<hash>:<unix_epoch>". |
| 163 | * Older callers / stripped-down git output without %ct land on 0. */ |
| 164 | const char *hash_end = strchr(line + SLEN("COMMIT:"), ':'); |
| 165 | if (hash_end) { |
| 166 | current.timestamp = strtoll(hash_end + 1, NULL, 10); |
| 167 | } |
no test coverage detected