]>
Commit | Line | Data |
---|---|---|
8e49d503 | 1 | #include <stdio.h> |
7dbc2c04 JH |
2 | #include <sys/types.h> |
3 | #include <sys/stat.h> | |
4 | #include <dirent.h> | |
8e49d503 AE |
5 | #include <unistd.h> |
6 | #include <stdlib.h> | |
7 | #include <string.h> | |
8 | #include <errno.h> | |
9 | #include <limits.h> | |
10 | #include <stdarg.h> | |
ea77e675 | 11 | #include <sys/ioctl.h> |
4050c0df | 12 | #include "git-compat-util.h" |
77cb17e9 | 13 | #include "exec_cmd.h" |
8e49d503 | 14 | |
70b006b9 LT |
15 | #include "cache.h" |
16 | #include "commit.h" | |
17 | #include "revision.h" | |
18 | ||
8e49d503 AE |
19 | #ifndef PATH_MAX |
20 | # define PATH_MAX 4096 | |
21 | #endif | |
22 | ||
23 | static const char git_usage[] = | |
24 | "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]"; | |
25 | ||
8e49d503 AE |
26 | /* most gui terms set COLUMNS (although some don't export it) */ |
27 | static int term_columns(void) | |
28 | { | |
29 | char *col_string = getenv("COLUMNS"); | |
30 | int n_cols = 0; | |
31 | ||
32 | if (col_string && (n_cols = atoi(col_string)) > 0) | |
33 | return n_cols; | |
34 | ||
ea77e675 LT |
35 | #ifdef TIOCGWINSZ |
36 | { | |
37 | struct winsize ws; | |
38 | if (!ioctl(1, TIOCGWINSZ, &ws)) { | |
39 | if (ws.ws_col) | |
40 | return ws.ws_col; | |
41 | } | |
42 | } | |
43 | #endif | |
44 | ||
8e49d503 AE |
45 | return 80; |
46 | } | |
47 | ||
7dbc2c04 JH |
48 | static void oom(void) |
49 | { | |
50 | fprintf(stderr, "git: out of memory\n"); | |
51 | exit(1); | |
52 | } | |
53 | ||
8e49d503 AE |
54 | static inline void mput_char(char c, unsigned int num) |
55 | { | |
56 | while(num--) | |
57 | putchar(c); | |
58 | } | |
59 | ||
7dbc2c04 JH |
60 | static struct cmdname { |
61 | size_t len; | |
62 | char name[1]; | |
63 | } **cmdname; | |
64 | static int cmdname_alloc, cmdname_cnt; | |
65 | ||
66 | static void add_cmdname(const char *name, int len) | |
67 | { | |
68 | struct cmdname *ent; | |
69 | if (cmdname_alloc <= cmdname_cnt) { | |
70 | cmdname_alloc = cmdname_alloc + 200; | |
71 | cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname)); | |
72 | if (!cmdname) | |
73 | oom(); | |
74 | } | |
75 | ent = malloc(sizeof(*ent) + len); | |
76 | if (!ent) | |
77 | oom(); | |
78 | ent->len = len; | |
f9039f30 JH |
79 | memcpy(ent->name, name, len); |
80 | ent->name[len] = 0; | |
7dbc2c04 JH |
81 | cmdname[cmdname_cnt++] = ent; |
82 | } | |
83 | ||
84 | static int cmdname_compare(const void *a_, const void *b_) | |
85 | { | |
86 | struct cmdname *a = *(struct cmdname **)a_; | |
87 | struct cmdname *b = *(struct cmdname **)b_; | |
88 | return strcmp(a->name, b->name); | |
89 | } | |
90 | ||
91 | static void pretty_print_string_list(struct cmdname **cmdname, int longest) | |
8e49d503 | 92 | { |
112d0baf | 93 | int cols = 1, rows; |
8e49d503 AE |
94 | int space = longest + 1; /* min 1 SP between words */ |
95 | int max_cols = term_columns() - 1; /* don't print *on* the edge */ | |
112d0baf | 96 | int i, j; |
8e49d503 AE |
97 | |
98 | if (space < max_cols) | |
99 | cols = max_cols / space; | |
112d0baf | 100 | rows = (cmdname_cnt + cols - 1) / cols; |
8e49d503 | 101 | |
7dbc2c04 JH |
102 | qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare); |
103 | ||
112d0baf | 104 | for (i = 0; i < rows; i++) { |
8e49d503 AE |
105 | printf(" "); |
106 | ||
112d0baf LT |
107 | for (j = 0; j < cols; j++) { |
108 | int n = j * rows + i; | |
109 | int size = space; | |
110 | if (n >= cmdname_cnt) | |
111 | break; | |
112 | if (j == cols-1 || n + rows >= cmdname_cnt) | |
113 | size = 1; | |
114 | printf("%-*s", size, cmdname[n]->name); | |
8e49d503 AE |
115 | } |
116 | putchar('\n'); | |
117 | } | |
118 | } | |
119 | ||
120 | static void list_commands(const char *exec_path, const char *pattern) | |
121 | { | |
7dbc2c04 JH |
122 | unsigned int longest = 0; |
123 | char path[PATH_MAX]; | |
124 | int dirlen; | |
125 | DIR *dir = opendir(exec_path); | |
126 | struct dirent *de; | |
127 | ||
128 | if (!dir) { | |
129 | fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno)); | |
8e49d503 AE |
130 | exit(1); |
131 | } | |
132 | ||
7dbc2c04 JH |
133 | dirlen = strlen(exec_path); |
134 | if (PATH_MAX - 20 < dirlen) { | |
135 | fprintf(stderr, "git: insanely long exec-path '%s'\n", | |
136 | exec_path); | |
8e49d503 AE |
137 | exit(1); |
138 | } | |
139 | ||
7dbc2c04 JH |
140 | memcpy(path, exec_path, dirlen); |
141 | path[dirlen++] = '/'; | |
142 | ||
143 | while ((de = readdir(dir)) != NULL) { | |
144 | struct stat st; | |
145 | int entlen; | |
8e49d503 | 146 | |
7dbc2c04 JH |
147 | if (strncmp(de->d_name, "git-", 4)) |
148 | continue; | |
149 | strcpy(path+dirlen, de->d_name); | |
150 | if (stat(path, &st) || /* stat, not lstat */ | |
151 | !S_ISREG(st.st_mode) || | |
152 | !(st.st_mode & S_IXUSR)) | |
8e49d503 AE |
153 | continue; |
154 | ||
7dbc2c04 | 155 | entlen = strlen(de->d_name); |
f9039f30 JH |
156 | if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe")) |
157 | entlen -= 4; | |
8e49d503 | 158 | |
7dbc2c04 JH |
159 | if (longest < entlen) |
160 | longest = entlen; | |
161 | ||
162 | add_cmdname(de->d_name + 4, entlen-4); | |
8e49d503 | 163 | } |
7dbc2c04 | 164 | closedir(dir); |
8e49d503 AE |
165 | |
166 | printf("git commands available in '%s'\n", exec_path); | |
167 | printf("----------------------------"); | |
168 | mput_char('-', strlen(exec_path)); | |
169 | putchar('\n'); | |
7dbc2c04 | 170 | pretty_print_string_list(cmdname, longest - 4); |
8e49d503 AE |
171 | putchar('\n'); |
172 | } | |
173 | ||
174 | #ifdef __GNUC__ | |
4050c0df | 175 | static void cmd_usage(const char *exec_path, const char *fmt, ...) |
8e49d503 AE |
176 | __attribute__((__format__(__printf__, 2, 3), __noreturn__)); |
177 | #endif | |
4050c0df | 178 | static void cmd_usage(const char *exec_path, const char *fmt, ...) |
8e49d503 AE |
179 | { |
180 | if (fmt) { | |
181 | va_list ap; | |
182 | ||
183 | va_start(ap, fmt); | |
184 | printf("git: "); | |
185 | vprintf(fmt, ap); | |
186 | va_end(ap); | |
187 | putchar('\n'); | |
188 | } | |
189 | else | |
190 | puts(git_usage); | |
191 | ||
192 | putchar('\n'); | |
193 | ||
194 | if(exec_path) | |
195 | list_commands(exec_path, "git-*"); | |
196 | ||
197 | exit(1); | |
198 | } | |
199 | ||
200 | static void prepend_to_path(const char *dir, int len) | |
201 | { | |
202 | char *path, *old_path = getenv("PATH"); | |
203 | int path_len = len; | |
204 | ||
205 | if (!old_path) | |
7dbc2c04 | 206 | old_path = "/usr/local/bin:/usr/bin:/bin"; |
8e49d503 AE |
207 | |
208 | path_len = len + strlen(old_path) + 1; | |
209 | ||
210 | path = malloc(path_len + 1); | |
8e49d503 AE |
211 | |
212 | memcpy(path, dir, len); | |
213 | path[len] = ':'; | |
214 | memcpy(path + len + 1, old_path, path_len - len); | |
215 | ||
216 | setenv("PATH", path, 1); | |
217 | } | |
218 | ||
9201c707 | 219 | static void show_man_page(const char *git_cmd) |
97fc6c5f | 220 | { |
9201c707 | 221 | const char *page; |
97fc6c5f AE |
222 | |
223 | if (!strncmp(git_cmd, "git", 3)) | |
224 | page = git_cmd; | |
225 | else { | |
226 | int page_len = strlen(git_cmd) + 4; | |
9201c707 JH |
227 | char *p = malloc(page_len + 1); |
228 | strcpy(p, "git-"); | |
229 | strcpy(p + 4, git_cmd); | |
230 | p[page_len] = 0; | |
231 | page = p; | |
97fc6c5f AE |
232 | } |
233 | ||
7dbc2c04 | 234 | execlp("man", "man", page, NULL); |
97fc6c5f AE |
235 | } |
236 | ||
9201c707 | 237 | static int cmd_version(int argc, const char **argv, char **envp) |
231af832 LT |
238 | { |
239 | printf("git version %s\n", GIT_VERSION); | |
240 | return 0; | |
241 | } | |
242 | ||
9201c707 | 243 | static int cmd_help(int argc, const char **argv, char **envp) |
231af832 | 244 | { |
9201c707 | 245 | const char *help_cmd = argv[1]; |
231af832 LT |
246 | if (!help_cmd) |
247 | cmd_usage(git_exec_path(), NULL); | |
248 | show_man_page(help_cmd); | |
249 | return 0; | |
250 | } | |
251 | ||
70b006b9 LT |
252 | #define LOGSIZE (65536) |
253 | ||
9201c707 | 254 | static int cmd_log(int argc, const char **argv, char **envp) |
70b006b9 LT |
255 | { |
256 | struct rev_info rev; | |
257 | struct commit *commit; | |
258 | char *buf = xmalloc(LOGSIZE); | |
7ae0b0cb JH |
259 | static enum cmit_fmt commit_format = CMIT_FMT_DEFAULT; |
260 | int abbrev = DEFAULT_ABBREV; | |
261 | int show_parents = 0; | |
262 | const char *commit_prefix = "commit "; | |
70b006b9 LT |
263 | |
264 | argc = setup_revisions(argc, argv, &rev, "HEAD"); | |
7ae0b0cb | 265 | while (1 < argc) { |
9201c707 | 266 | const char *arg = argv[1]; |
64bc6e3d | 267 | if (!strncmp(arg, "--pretty", 8)) { |
7ae0b0cb JH |
268 | commit_format = get_commit_format(arg + 8); |
269 | if (commit_format == CMIT_FMT_ONELINE) | |
270 | commit_prefix = ""; | |
271 | } | |
272 | else if (!strcmp(arg, "--parents")) { | |
273 | show_parents = 1; | |
274 | } | |
275 | else if (!strcmp(arg, "--no-abbrev")) { | |
276 | abbrev = 0; | |
277 | } | |
278 | else if (!strncmp(arg, "--abbrev=", 9)) { | |
279 | abbrev = strtoul(arg + 9, NULL, 10); | |
280 | if (abbrev && abbrev < MINIMUM_ABBREV) | |
281 | abbrev = MINIMUM_ABBREV; | |
282 | else if (40 < abbrev) | |
283 | abbrev = 40; | |
284 | } | |
285 | else | |
286 | die("unrecognized argument: %s", arg); | |
287 | argc--; argv++; | |
288 | } | |
289 | ||
70b006b9 LT |
290 | prepare_revision_walk(&rev); |
291 | setup_pager(); | |
292 | while ((commit = get_revision(&rev)) != NULL) { | |
7ae0b0cb JH |
293 | printf("%s%s", commit_prefix, |
294 | sha1_to_hex(commit->object.sha1)); | |
295 | if (show_parents) { | |
296 | struct commit_list *parents = commit->parents; | |
297 | while (parents) { | |
298 | struct object *o = &(parents->item->object); | |
299 | parents = parents->next; | |
300 | if (o->flags & TMP_MARK) | |
301 | continue; | |
302 | printf(" %s", sha1_to_hex(o->sha1)); | |
303 | o->flags |= TMP_MARK; | |
304 | } | |
305 | /* TMP_MARK is a general purpose flag that can | |
306 | * be used locally, but the user should clean | |
307 | * things up after it is done with them. | |
308 | */ | |
309 | for (parents = commit->parents; | |
310 | parents; | |
311 | parents = parents->next) | |
312 | parents->item->object.flags &= ~TMP_MARK; | |
313 | } | |
314 | if (commit_format == CMIT_FMT_ONELINE) | |
315 | putchar(' '); | |
316 | else | |
317 | putchar('\n'); | |
318 | pretty_print_commit(commit_format, commit, ~0, buf, | |
319 | LOGSIZE, abbrev); | |
70b006b9 LT |
320 | printf("%s\n", buf); |
321 | } | |
322 | free(buf); | |
323 | return 0; | |
324 | } | |
325 | ||
231af832 LT |
326 | #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) |
327 | ||
9201c707 | 328 | static void handle_internal_command(int argc, const char **argv, char **envp) |
231af832 LT |
329 | { |
330 | const char *cmd = argv[0]; | |
331 | static struct cmd_struct { | |
332 | const char *cmd; | |
9201c707 | 333 | int (*fn)(int, const char **, char **); |
231af832 LT |
334 | } commands[] = { |
335 | { "version", cmd_version }, | |
336 | { "help", cmd_help }, | |
70b006b9 | 337 | { "log", cmd_log }, |
231af832 LT |
338 | }; |
339 | int i; | |
340 | ||
341 | for (i = 0; i < ARRAY_SIZE(commands); i++) { | |
342 | struct cmd_struct *p = commands+i; | |
343 | if (strcmp(p->cmd, cmd)) | |
344 | continue; | |
345 | exit(p->fn(argc, argv, envp)); | |
346 | } | |
347 | } | |
348 | ||
9201c707 | 349 | int main(int argc, const char **argv, char **envp) |
8e49d503 | 350 | { |
9201c707 | 351 | const char *cmd = argv[0]; |
231af832 | 352 | char *slash = strrchr(cmd, '/'); |
8e49d503 | 353 | char git_command[PATH_MAX + 1]; |
231af832 LT |
354 | const char *exec_path = NULL; |
355 | ||
356 | /* | |
357 | * Take the basename of argv[0] as the command | |
358 | * name, and the dirname as the default exec_path | |
359 | * if it's an absolute path and we don't have | |
360 | * anything better. | |
361 | */ | |
362 | if (slash) { | |
363 | *slash++ = 0; | |
364 | if (*cmd == '/') | |
365 | exec_path = cmd; | |
366 | cmd = slash; | |
367 | } | |
8e49d503 | 368 | |
231af832 LT |
369 | /* |
370 | * "git-xxxx" is the same as "git xxxx", but we obviously: | |
371 | * | |
372 | * - cannot take flags in between the "git" and the "xxxx". | |
373 | * - cannot execute it externally (since it would just do | |
374 | * the same thing over again) | |
375 | * | |
376 | * So we just directly call the internal command handler, and | |
377 | * die if that one cannot handle it. | |
378 | */ | |
379 | if (!strncmp(cmd, "git-", 4)) { | |
380 | cmd += 4; | |
381 | argv[0] = cmd; | |
382 | handle_internal_command(argc, argv, envp); | |
383 | die("cannot handle %s internally", cmd); | |
384 | } | |
8e49d503 | 385 | |
231af832 LT |
386 | /* Default command: "help" */ |
387 | cmd = "help"; | |
8e49d503 | 388 | |
231af832 LT |
389 | /* Look for flags.. */ |
390 | while (argc > 1) { | |
391 | cmd = *++argv; | |
392 | argc--; | |
da6bf70e | 393 | |
231af832 | 394 | if (strncmp(cmd, "--", 2)) |
8e49d503 AE |
395 | break; |
396 | ||
231af832 LT |
397 | cmd += 2; |
398 | ||
399 | /* | |
400 | * For legacy reasons, the "version" and "help" | |
401 | * commands can be written with "--" prepended | |
402 | * to make them look like flags. | |
403 | */ | |
404 | if (!strcmp(cmd, "help")) | |
405 | break; | |
406 | if (!strcmp(cmd, "version")) | |
407 | break; | |
8e49d503 | 408 | |
231af832 LT |
409 | /* |
410 | * Check remaining flags (which by now must be | |
411 | * "--exec-path", but maybe we will accept | |
412 | * other arguments some day) | |
413 | */ | |
414 | if (!strncmp(cmd, "exec-path", 9)) { | |
415 | cmd += 9; | |
416 | if (*cmd == '=') { | |
417 | git_set_exec_path(cmd + 1); | |
418 | continue; | |
8e49d503 | 419 | } |
231af832 | 420 | puts(git_exec_path()); |
8e49d503 AE |
421 | exit(0); |
422 | } | |
231af832 | 423 | cmd_usage(NULL, NULL); |
97fc6c5f | 424 | } |
231af832 LT |
425 | argv[0] = cmd; |
426 | ||
427 | /* | |
428 | * We search for git commands in the following order: | |
429 | * - git_exec_path() | |
430 | * - the path of the "git" command if we could find it | |
431 | * in $0 | |
432 | * - the regular PATH. | |
433 | */ | |
434 | if (exec_path) | |
435 | prepend_to_path(exec_path, strlen(exec_path)); | |
77cb17e9 MO |
436 | exec_path = git_exec_path(); |
437 | prepend_to_path(exec_path, strlen(exec_path)); | |
8e49d503 | 438 | |
231af832 LT |
439 | /* See if it's an internal command */ |
440 | handle_internal_command(argc, argv, envp); | |
441 | ||
442 | /* .. then try the external ones */ | |
443 | execv_git_cmd(argv); | |
10b15b86 AR |
444 | |
445 | if (errno == ENOENT) | |
231af832 | 446 | cmd_usage(exec_path, "'%s' is not a git-command", cmd); |
10b15b86 AR |
447 | |
448 | fprintf(stderr, "Failed to run command '%s': %s\n", | |
449 | git_command, strerror(errno)); | |
8e49d503 AE |
450 | |
451 | return 1; | |
452 | } |