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