]>
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; | |
477f2b41 JH |
347 | if (!full_diff && rev.prune_data) |
348 | diff_tree_setup_paths(rev.prune_data, &opt.diffopt); | |
52b70d56 JH |
349 | diff_setup_done(&opt.diffopt); |
350 | } | |
7ae0b0cb | 351 | |
70b006b9 LT |
352 | prepare_revision_walk(&rev); |
353 | setup_pager(); | |
354 | while ((commit = get_revision(&rev)) != NULL) { | |
d5335242 | 355 | if (shown && do_diff && commit_format != CMIT_FMT_ONELINE) |
477f2b41 | 356 | putchar('\n'); |
f0853837 JH |
357 | fputs(commit_prefix, stdout); |
358 | if (abbrev_commit && abbrev) | |
359 | fputs(find_unique_abbrev(commit->object.sha1, abbrev), | |
360 | stdout); | |
361 | else | |
362 | fputs(sha1_to_hex(commit->object.sha1), stdout); | |
7b0c9966 | 363 | if (rev.parents) { |
7ae0b0cb JH |
364 | struct commit_list *parents = commit->parents; |
365 | while (parents) { | |
366 | struct object *o = &(parents->item->object); | |
367 | parents = parents->next; | |
368 | if (o->flags & TMP_MARK) | |
369 | continue; | |
370 | printf(" %s", sha1_to_hex(o->sha1)); | |
371 | o->flags |= TMP_MARK; | |
372 | } | |
373 | /* TMP_MARK is a general purpose flag that can | |
374 | * be used locally, but the user should clean | |
375 | * things up after it is done with them. | |
376 | */ | |
377 | for (parents = commit->parents; | |
378 | parents; | |
379 | parents = parents->next) | |
380 | parents->item->object.flags &= ~TMP_MARK; | |
381 | } | |
382 | if (commit_format == CMIT_FMT_ONELINE) | |
383 | putchar(' '); | |
384 | else | |
385 | putchar('\n'); | |
386 | pretty_print_commit(commit_format, commit, ~0, buf, | |
387 | LOGSIZE, abbrev); | |
70b006b9 | 388 | printf("%s\n", buf); |
6f4780f9 JH |
389 | if (do_diff) { |
390 | printf("---\n"); | |
52b70d56 | 391 | log_tree_commit(&opt, commit); |
6f4780f9 | 392 | } |
477f2b41 | 393 | shown = 1; |
f43ba60e LT |
394 | free(commit->buffer); |
395 | commit->buffer = NULL; | |
70b006b9 LT |
396 | } |
397 | free(buf); | |
398 | return 0; | |
399 | } | |
400 | ||
9201c707 | 401 | static void handle_internal_command(int argc, const char **argv, char **envp) |
231af832 LT |
402 | { |
403 | const char *cmd = argv[0]; | |
404 | static struct cmd_struct { | |
405 | const char *cmd; | |
9201c707 | 406 | int (*fn)(int, const char **, char **); |
231af832 LT |
407 | } commands[] = { |
408 | { "version", cmd_version }, | |
409 | { "help", cmd_help }, | |
70b006b9 | 410 | { "log", cmd_log }, |
231af832 LT |
411 | }; |
412 | int i; | |
413 | ||
1cd95087 LT |
414 | /* Turn "git cmd --help" into "git help cmd" */ |
415 | if (argc > 1 && !strcmp(argv[1], "--help")) { | |
416 | argv[1] = argv[0]; | |
417 | argv[0] = cmd = "help"; | |
418 | } | |
419 | ||
231af832 LT |
420 | for (i = 0; i < ARRAY_SIZE(commands); i++) { |
421 | struct cmd_struct *p = commands+i; | |
422 | if (strcmp(p->cmd, cmd)) | |
423 | continue; | |
424 | exit(p->fn(argc, argv, envp)); | |
425 | } | |
426 | } | |
427 | ||
9201c707 | 428 | int main(int argc, const char **argv, char **envp) |
8e49d503 | 429 | { |
9201c707 | 430 | const char *cmd = argv[0]; |
231af832 | 431 | char *slash = strrchr(cmd, '/'); |
8e49d503 | 432 | char git_command[PATH_MAX + 1]; |
231af832 LT |
433 | const char *exec_path = NULL; |
434 | ||
435 | /* | |
436 | * Take the basename of argv[0] as the command | |
437 | * name, and the dirname as the default exec_path | |
438 | * if it's an absolute path and we don't have | |
439 | * anything better. | |
440 | */ | |
441 | if (slash) { | |
442 | *slash++ = 0; | |
443 | if (*cmd == '/') | |
444 | exec_path = cmd; | |
445 | cmd = slash; | |
446 | } | |
8e49d503 | 447 | |
231af832 LT |
448 | /* |
449 | * "git-xxxx" is the same as "git xxxx", but we obviously: | |
450 | * | |
451 | * - cannot take flags in between the "git" and the "xxxx". | |
452 | * - cannot execute it externally (since it would just do | |
453 | * the same thing over again) | |
454 | * | |
455 | * So we just directly call the internal command handler, and | |
456 | * die if that one cannot handle it. | |
457 | */ | |
458 | if (!strncmp(cmd, "git-", 4)) { | |
459 | cmd += 4; | |
460 | argv[0] = cmd; | |
461 | handle_internal_command(argc, argv, envp); | |
462 | die("cannot handle %s internally", cmd); | |
463 | } | |
8e49d503 | 464 | |
231af832 LT |
465 | /* Default command: "help" */ |
466 | cmd = "help"; | |
8e49d503 | 467 | |
231af832 LT |
468 | /* Look for flags.. */ |
469 | while (argc > 1) { | |
470 | cmd = *++argv; | |
471 | argc--; | |
da6bf70e | 472 | |
231af832 | 473 | if (strncmp(cmd, "--", 2)) |
8e49d503 AE |
474 | break; |
475 | ||
231af832 LT |
476 | cmd += 2; |
477 | ||
478 | /* | |
479 | * For legacy reasons, the "version" and "help" | |
480 | * commands can be written with "--" prepended | |
481 | * to make them look like flags. | |
482 | */ | |
483 | if (!strcmp(cmd, "help")) | |
484 | break; | |
485 | if (!strcmp(cmd, "version")) | |
486 | break; | |
8e49d503 | 487 | |
231af832 LT |
488 | /* |
489 | * Check remaining flags (which by now must be | |
490 | * "--exec-path", but maybe we will accept | |
491 | * other arguments some day) | |
492 | */ | |
493 | if (!strncmp(cmd, "exec-path", 9)) { | |
494 | cmd += 9; | |
495 | if (*cmd == '=') { | |
496 | git_set_exec_path(cmd + 1); | |
497 | continue; | |
8e49d503 | 498 | } |
231af832 | 499 | puts(git_exec_path()); |
8e49d503 AE |
500 | exit(0); |
501 | } | |
a87cd02c | 502 | cmd_usage(0, NULL, NULL); |
97fc6c5f | 503 | } |
231af832 LT |
504 | argv[0] = cmd; |
505 | ||
506 | /* | |
507 | * We search for git commands in the following order: | |
508 | * - git_exec_path() | |
509 | * - the path of the "git" command if we could find it | |
510 | * in $0 | |
511 | * - the regular PATH. | |
512 | */ | |
513 | if (exec_path) | |
514 | prepend_to_path(exec_path, strlen(exec_path)); | |
77cb17e9 MO |
515 | exec_path = git_exec_path(); |
516 | prepend_to_path(exec_path, strlen(exec_path)); | |
8e49d503 | 517 | |
231af832 LT |
518 | /* See if it's an internal command */ |
519 | handle_internal_command(argc, argv, envp); | |
520 | ||
521 | /* .. then try the external ones */ | |
522 | execv_git_cmd(argv); | |
10b15b86 AR |
523 | |
524 | if (errno == ENOENT) | |
a87cd02c | 525 | cmd_usage(0, exec_path, "'%s' is not a git-command", cmd); |
10b15b86 AR |
526 | |
527 | fprintf(stderr, "Failed to run command '%s': %s\n", | |
528 | git_command, strerror(errno)); | |
8e49d503 AE |
529 | |
530 | return 1; | |
531 | } |