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