Commit | Line | Data |
---|---|---|
8e49d503 AE |
1 | #include <stdio.h> |
2 | #include <unistd.h> | |
3 | #include <stdlib.h> | |
4 | #include <string.h> | |
5 | #include <errno.h> | |
6 | #include <limits.h> | |
7 | #include <stdarg.h> | |
8 | #include <glob.h> | |
9 | ||
10 | #ifndef PATH_MAX | |
11 | # define PATH_MAX 4096 | |
12 | #endif | |
13 | ||
14 | static const char git_usage[] = | |
15 | "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]"; | |
16 | ||
17 | struct string_list { | |
18 | size_t len; | |
19 | char *str; | |
20 | struct string_list *next; | |
21 | }; | |
22 | ||
23 | /* most gui terms set COLUMNS (although some don't export it) */ | |
24 | static int term_columns(void) | |
25 | { | |
26 | char *col_string = getenv("COLUMNS"); | |
27 | int n_cols = 0; | |
28 | ||
29 | if (col_string && (n_cols = atoi(col_string)) > 0) | |
30 | return n_cols; | |
31 | ||
32 | return 80; | |
33 | } | |
34 | ||
35 | static inline void mput_char(char c, unsigned int num) | |
36 | { | |
37 | while(num--) | |
38 | putchar(c); | |
39 | } | |
40 | ||
41 | static void pretty_print_string_list(struct string_list *list, int longest) | |
42 | { | |
43 | int cols = 1; | |
44 | int space = longest + 1; /* min 1 SP between words */ | |
45 | int max_cols = term_columns() - 1; /* don't print *on* the edge */ | |
46 | ||
47 | if (space < max_cols) | |
48 | cols = max_cols / space; | |
49 | ||
50 | while (list) { | |
51 | int c; | |
52 | printf(" "); | |
53 | ||
54 | for (c = cols; c && list; list = list->next) { | |
55 | printf("%s", list->str); | |
56 | ||
57 | if (--c) | |
58 | mput_char(' ', space - list->len); | |
59 | } | |
60 | putchar('\n'); | |
61 | } | |
62 | } | |
63 | ||
64 | static void list_commands(const char *exec_path, const char *pattern) | |
65 | { | |
66 | struct string_list *list = NULL, *tail = NULL; | |
67 | unsigned int longest = 0, i; | |
68 | glob_t gl; | |
69 | ||
70 | if (chdir(exec_path) < 0) { | |
71 | printf("git: '%s': %s\n", exec_path, strerror(errno)); | |
72 | exit(1); | |
73 | } | |
74 | ||
75 | i = glob(pattern, 0, NULL, &gl); | |
76 | switch(i) { | |
77 | case GLOB_NOSPACE: | |
78 | puts("Out of memory when running glob()"); | |
79 | exit(2); | |
80 | case GLOB_ABORTED: | |
81 | printf("'%s': Read error: %s\n", exec_path, strerror(errno)); | |
82 | exit(2); | |
83 | case GLOB_NOMATCH: | |
84 | printf("No git commands available in '%s'.\n", exec_path); | |
85 | printf("Do you need to specify --exec-path or set GIT_EXEC_PATH?\n"); | |
86 | exit(1); | |
87 | } | |
88 | ||
89 | for (i = 0; i < gl.gl_pathc; i++) { | |
90 | int len = strlen(gl.gl_pathv[i] + 4); | |
91 | ||
92 | if (access(gl.gl_pathv[i], X_OK)) | |
93 | continue; | |
94 | ||
95 | if (longest < len) | |
96 | longest = len; | |
97 | ||
98 | if (!tail) | |
99 | tail = list = malloc(sizeof(struct string_list)); | |
100 | else { | |
101 | tail->next = malloc(sizeof(struct string_list)); | |
102 | tail = tail->next; | |
103 | } | |
104 | tail->len = len; | |
105 | tail->str = gl.gl_pathv[i] + 4; | |
106 | tail->next = NULL; | |
107 | } | |
108 | ||
109 | printf("git commands available in '%s'\n", exec_path); | |
110 | printf("----------------------------"); | |
111 | mput_char('-', strlen(exec_path)); | |
112 | putchar('\n'); | |
113 | pretty_print_string_list(list, longest); | |
114 | putchar('\n'); | |
115 | } | |
116 | ||
117 | #ifdef __GNUC__ | |
118 | static void usage(const char *exec_path, const char *fmt, ...) | |
119 | __attribute__((__format__(__printf__, 2, 3), __noreturn__)); | |
120 | #endif | |
121 | static void usage(const char *exec_path, const char *fmt, ...) | |
122 | { | |
123 | if (fmt) { | |
124 | va_list ap; | |
125 | ||
126 | va_start(ap, fmt); | |
127 | printf("git: "); | |
128 | vprintf(fmt, ap); | |
129 | va_end(ap); | |
130 | putchar('\n'); | |
131 | } | |
132 | else | |
133 | puts(git_usage); | |
134 | ||
135 | putchar('\n'); | |
136 | ||
137 | if(exec_path) | |
138 | list_commands(exec_path, "git-*"); | |
139 | ||
140 | exit(1); | |
141 | } | |
142 | ||
143 | static void prepend_to_path(const char *dir, int len) | |
144 | { | |
145 | char *path, *old_path = getenv("PATH"); | |
146 | int path_len = len; | |
147 | ||
148 | if (!old_path) | |
149 | old_path = "/bin:/usr/bin:."; | |
150 | ||
151 | path_len = len + strlen(old_path) + 1; | |
152 | ||
153 | path = malloc(path_len + 1); | |
154 | path[path_len + 1] = '\0'; | |
155 | ||
156 | memcpy(path, dir, len); | |
157 | path[len] = ':'; | |
158 | memcpy(path + len + 1, old_path, path_len - len); | |
159 | ||
160 | setenv("PATH", path, 1); | |
161 | } | |
162 | ||
163 | int main(int argc, char **argv, char **envp) | |
164 | { | |
165 | char git_command[PATH_MAX + 1]; | |
166 | char wd[PATH_MAX + 1]; | |
167 | int i, len, show_help = 0; | |
168 | char *exec_path = getenv("GIT_EXEC_PATH"); | |
169 | ||
170 | getcwd(wd, PATH_MAX); | |
171 | ||
172 | if (!exec_path) | |
173 | exec_path = GIT_EXEC_PATH; | |
174 | ||
175 | for (i = 1; i < argc; i++) { | |
176 | char *arg = argv[i]; | |
177 | ||
178 | if (strncmp(arg, "--", 2)) | |
179 | break; | |
180 | ||
181 | arg += 2; | |
182 | ||
183 | if (!strncmp(arg, "exec-path", 9)) { | |
184 | arg += 9; | |
185 | if (*arg == '=') | |
186 | exec_path = arg + 1; | |
187 | else { | |
188 | puts(exec_path); | |
189 | exit(0); | |
190 | } | |
191 | } | |
192 | else if (!strcmp(arg, "version")) { | |
193 | printf("git version %s\n", GIT_VERSION); | |
194 | exit(0); | |
195 | } | |
196 | else if (!strcmp(arg, "help")) | |
197 | show_help = 1; | |
198 | else if (!show_help) | |
199 | usage(NULL, NULL); | |
200 | } | |
201 | ||
202 | if (i >= argc || show_help) | |
203 | usage(exec_path, NULL); | |
204 | ||
205 | /* allow relative paths, but run with exact */ | |
206 | if (chdir(exec_path)) { | |
207 | printf("git: '%s': %s\n", exec_path, strerror(errno)); | |
208 | exit (1); | |
209 | } | |
210 | ||
211 | getcwd(git_command, sizeof(git_command)); | |
212 | chdir(wd); | |
213 | ||
214 | len = strlen(git_command); | |
215 | prepend_to_path(git_command, len); | |
216 | ||
217 | strncat(&git_command[len], "/git-", sizeof(git_command) - len); | |
218 | len += 5; | |
219 | strncat(&git_command[len], argv[i], sizeof(git_command) - len); | |
220 | ||
221 | if (access(git_command, X_OK)) | |
222 | usage(exec_path, "'%s' is not a git-command", argv[i]); | |
223 | ||
224 | /* execve() can only ever return if it fails */ | |
225 | execve(git_command, &argv[i], envp); | |
226 | printf("Failed to run command '%s': %s\n", git_command, strerror(errno)); | |
227 | ||
228 | return 1; | |
229 | } |