]>
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 | 286 | const char *commit_prefix = "commit "; |
477f2b41 | 287 | int shown = 0; |
70b006b9 | 288 | |
6b9c58f4 | 289 | init_revisions(&rev); |
8e8f9987 | 290 | rev.abbrev = DEFAULT_ABBREV; |
70b006b9 | 291 | argc = setup_revisions(argc, argv, &rev, "HEAD"); |
cd2bdc53 LT |
292 | if (argc > 1) |
293 | die("unrecognized argument: %s", argv[1]); | |
52b70d56 | 294 | |
cd2bdc53 LT |
295 | rev.no_commit_id = 1; |
296 | if (rev.commit_format == CMIT_FMT_ONELINE) | |
297 | commit_prefix = ""; | |
7ae0b0cb | 298 | |
70b006b9 LT |
299 | prepare_revision_walk(&rev); |
300 | setup_pager(); | |
301 | while ((commit = get_revision(&rev)) != NULL) { | |
cd2bdc53 | 302 | if (shown && rev.diff && rev.commit_format != CMIT_FMT_ONELINE) |
477f2b41 | 303 | putchar('\n'); |
f0853837 | 304 | fputs(commit_prefix, stdout); |
cd2bdc53 LT |
305 | if (rev.abbrev_commit && rev.abbrev) |
306 | fputs(find_unique_abbrev(commit->object.sha1, rev.abbrev), | |
f0853837 JH |
307 | stdout); |
308 | else | |
309 | fputs(sha1_to_hex(commit->object.sha1), stdout); | |
7b0c9966 | 310 | if (rev.parents) { |
7ae0b0cb JH |
311 | struct commit_list *parents = commit->parents; |
312 | while (parents) { | |
313 | struct object *o = &(parents->item->object); | |
314 | parents = parents->next; | |
315 | if (o->flags & TMP_MARK) | |
316 | continue; | |
317 | printf(" %s", sha1_to_hex(o->sha1)); | |
318 | o->flags |= TMP_MARK; | |
319 | } | |
320 | /* TMP_MARK is a general purpose flag that can | |
321 | * be used locally, but the user should clean | |
322 | * things up after it is done with them. | |
323 | */ | |
324 | for (parents = commit->parents; | |
325 | parents; | |
326 | parents = parents->next) | |
327 | parents->item->object.flags &= ~TMP_MARK; | |
328 | } | |
cd2bdc53 | 329 | if (rev.commit_format == CMIT_FMT_ONELINE) |
7ae0b0cb JH |
330 | putchar(' '); |
331 | else | |
332 | putchar('\n'); | |
cd2bdc53 LT |
333 | pretty_print_commit(rev.commit_format, commit, ~0, buf, |
334 | LOGSIZE, rev.abbrev); | |
70b006b9 | 335 | printf("%s\n", buf); |
cd2bdc53 LT |
336 | if (rev.diff) |
337 | log_tree_commit(&rev, commit); | |
477f2b41 | 338 | shown = 1; |
f43ba60e LT |
339 | free(commit->buffer); |
340 | commit->buffer = NULL; | |
70b006b9 LT |
341 | } |
342 | free(buf); | |
343 | return 0; | |
344 | } | |
345 | ||
9201c707 | 346 | static void handle_internal_command(int argc, const char **argv, char **envp) |
231af832 LT |
347 | { |
348 | const char *cmd = argv[0]; | |
349 | static struct cmd_struct { | |
350 | const char *cmd; | |
9201c707 | 351 | int (*fn)(int, const char **, char **); |
231af832 LT |
352 | } commands[] = { |
353 | { "version", cmd_version }, | |
354 | { "help", cmd_help }, | |
70b006b9 | 355 | { "log", cmd_log }, |
231af832 LT |
356 | }; |
357 | int i; | |
358 | ||
359 | for (i = 0; i < ARRAY_SIZE(commands); i++) { | |
360 | struct cmd_struct *p = commands+i; | |
361 | if (strcmp(p->cmd, cmd)) | |
362 | continue; | |
363 | exit(p->fn(argc, argv, envp)); | |
364 | } | |
365 | } | |
366 | ||
9201c707 | 367 | int main(int argc, const char **argv, char **envp) |
8e49d503 | 368 | { |
9201c707 | 369 | const char *cmd = argv[0]; |
231af832 | 370 | char *slash = strrchr(cmd, '/'); |
8e49d503 | 371 | char git_command[PATH_MAX + 1]; |
231af832 LT |
372 | const char *exec_path = NULL; |
373 | ||
374 | /* | |
375 | * Take the basename of argv[0] as the command | |
376 | * name, and the dirname as the default exec_path | |
377 | * if it's an absolute path and we don't have | |
378 | * anything better. | |
379 | */ | |
380 | if (slash) { | |
381 | *slash++ = 0; | |
382 | if (*cmd == '/') | |
383 | exec_path = cmd; | |
384 | cmd = slash; | |
385 | } | |
8e49d503 | 386 | |
231af832 LT |
387 | /* |
388 | * "git-xxxx" is the same as "git xxxx", but we obviously: | |
389 | * | |
390 | * - cannot take flags in between the "git" and the "xxxx". | |
391 | * - cannot execute it externally (since it would just do | |
392 | * the same thing over again) | |
393 | * | |
394 | * So we just directly call the internal command handler, and | |
395 | * die if that one cannot handle it. | |
396 | */ | |
397 | if (!strncmp(cmd, "git-", 4)) { | |
398 | cmd += 4; | |
399 | argv[0] = cmd; | |
400 | handle_internal_command(argc, argv, envp); | |
401 | die("cannot handle %s internally", cmd); | |
402 | } | |
8e49d503 | 403 | |
231af832 LT |
404 | /* Default command: "help" */ |
405 | cmd = "help"; | |
8e49d503 | 406 | |
231af832 LT |
407 | /* Look for flags.. */ |
408 | while (argc > 1) { | |
409 | cmd = *++argv; | |
410 | argc--; | |
da6bf70e | 411 | |
231af832 | 412 | if (strncmp(cmd, "--", 2)) |
8e49d503 AE |
413 | break; |
414 | ||
231af832 LT |
415 | cmd += 2; |
416 | ||
417 | /* | |
418 | * For legacy reasons, the "version" and "help" | |
419 | * commands can be written with "--" prepended | |
420 | * to make them look like flags. | |
421 | */ | |
422 | if (!strcmp(cmd, "help")) | |
423 | break; | |
424 | if (!strcmp(cmd, "version")) | |
425 | break; | |
8e49d503 | 426 | |
231af832 LT |
427 | /* |
428 | * Check remaining flags (which by now must be | |
429 | * "--exec-path", but maybe we will accept | |
430 | * other arguments some day) | |
431 | */ | |
432 | if (!strncmp(cmd, "exec-path", 9)) { | |
433 | cmd += 9; | |
434 | if (*cmd == '=') { | |
435 | git_set_exec_path(cmd + 1); | |
436 | continue; | |
8e49d503 | 437 | } |
231af832 | 438 | puts(git_exec_path()); |
8e49d503 AE |
439 | exit(0); |
440 | } | |
a87cd02c | 441 | cmd_usage(0, NULL, NULL); |
97fc6c5f | 442 | } |
231af832 LT |
443 | argv[0] = cmd; |
444 | ||
445 | /* | |
446 | * We search for git commands in the following order: | |
447 | * - git_exec_path() | |
448 | * - the path of the "git" command if we could find it | |
449 | * in $0 | |
450 | * - the regular PATH. | |
451 | */ | |
452 | if (exec_path) | |
453 | prepend_to_path(exec_path, strlen(exec_path)); | |
77cb17e9 MO |
454 | exec_path = git_exec_path(); |
455 | prepend_to_path(exec_path, strlen(exec_path)); | |
8e49d503 | 456 | |
231af832 LT |
457 | /* See if it's an internal command */ |
458 | handle_internal_command(argc, argv, envp); | |
459 | ||
460 | /* .. then try the external ones */ | |
461 | execv_git_cmd(argv); | |
10b15b86 AR |
462 | |
463 | if (errno == ENOENT) | |
a87cd02c | 464 | cmd_usage(0, exec_path, "'%s' is not a git-command", cmd); |
10b15b86 AR |
465 | |
466 | fprintf(stderr, "Failed to run command '%s': %s\n", | |
467 | git_command, strerror(errno)); | |
8e49d503 AE |
468 | |
469 | return 1; | |
470 | } |