root/git/git.c
/*DEFINITIONS
This source file includes following definitions.- cleanup
- setup_filters
- switch_object
- setup_git
- page_setup
- check_gerr
- check_ptr
- make_query
- make_query_simple
- make_snapshot_name
- make_tag_summary
- commit_to_diff
- do_query
- env_setup
- do_archive
- main
1 #include <stdio.h> 2 #include <string.h> 3 #include <time.h> 4 #include <unistd.h> 5 #include <sys/wait.h> 6 #include <sys/stat.h> 7 #include <git2.h> 8 9 #ifndef MAX_CACHE 10 # define MAX_CACHE 67108864 /* 64 MiB */ 11 #endif 12 #ifndef MAX_SINGLE_CACHE 13 # define MAX_SINGLE_CACHE 1048576 /* 1 MiB */ 14 #endif 15 #ifndef CACHE_CODE 16 # define CACHE_CODE "4D00A18" 17 #endif 18 #ifndef TMP_DIR 19 # define TMP_DIR "/tmp" 20 #endif 21 22 #include "html.h" 23 #include "git.h" 24 #include "ui-log.h" 25 #include "ui-commit.h" 26 #include "ui-tree.h" 27 #include "ui-refs.h" 28 29 FILE *capture; 30 int has_page = 0; 31 int binary_xfer = 0; 32 int git_init = 0; 33 char rname[MINB] = "", 34 title[MINB] = ""; 35 36 struct q query = {0}; 37 38 git_repository *grepo = NULL; 39 const git_oid *gbase = NULL; 40 41 struct cache cstate = {0}; 42 43 44 void 45 cleanup(int e) 46 { 47 if (!binary_xfer) { 48 html("<div class=\"git-ag-footer\">\n\ 49 <hr>\n\ 50 <p>Generated %s%s</p>\n\ 51 </div>\n\ 52 </div>\n", 53 check_sptr(readable_time_static(time(NULL), 54 MY_LONG_DATE_FMT)), 55 query.can_compress ? " (gzip)" : ""); 56 html_page_end(); 57 } 58 59 if (git_init) 60 git_libgit2_shutdown(); 61 62 fclose(stdout); 63 cache_end(e); 64 65 exit(e); 66 } 67 68 69 static void 70 setup_filters(void) 71 { 72 if (!T_IS(T_ALREADY_COMPRESSED)) 73 query.can_compress = gzip_init() == 0; 74 75 if (cache_init(&query, sizeof(struct q)) == 1) { 76 /* we are replaying a cache entry */ 77 binary_xfer = 1; 78 79 cleanup(0); 80 } 81 } 82 83 84 static void 85 switch_object(const char *name) 86 { 87 git_object *tobject; 88 89 check_sgerr(git_revparse_single(&tobject, grepo, name)); 90 gbase = git_object_id(tobject); 91 git_object_free(tobject); 92 } 93 94 95 static void 96 setup_git(void) 97 { 98 if (git_init) 99 return; 100 101 git_libgit2_init(); 102 103 check_sgerr(chdir(query.self.rpath)); 104 git_repository_open_bare(&grepo, "./"); 105 106 if (!grepo) 107 check_sgerr(git_repository_open(&grepo, "./")); 108 109 if (query.trev[0] != '\0') 110 switch_object(query.trev); 111 else 112 switch_object("HEAD"); 113 114 git_init = 1; 115 } 116 117 118 void 119 page_setup(int mini) 120 { 121 git_object *head; 122 char new_clone_url[MINB] = {0}; 123 char description[MINB] = {0}; 124 char descfile[MINB] = {0}; 125 char idh[9] = {0}; 126 char *cp; 127 FILE *df; 128 int ishead = 0; 129 130 if (has_page || binary_xfer) 131 return; 132 133 has_page = 1; 134 135 http_header_html(); 136 137 if (mini) { 138 html("\n"); 139 html_page_begin(title); 140 html("<div class=\"git-main\">\n"); 141 142 return; 143 } 144 145 setup_filters(); 146 http_header_end(query.can_compress); 147 148 setup_git(); 149 150 check_sgerr(git_oid_nfmt(idh, 8, gbase)); 151 check_sgerr(git_revparse_single(&head, grepo, "HEAD")); 152 ishead = git_oid_cmp(gbase, git_object_id(head)) == 0; 153 154 snprintf(new_clone_url, MINB, "%s%s", clone_url, query.repn); 155 snprintf(descfile, MINB, "%s/description", query.self.rpath); 156 157 if ((df = fopen(descfile, "r"))) { 158 fgets(description, MINB, df); 159 fclose(df); 160 if ((cp = strrchr(description, '\n'))) 161 *cp = '\0'; 162 } 163 164 html_page_begin(title); 165 html("<div class=\"git-main\">\n\ 166 <div class=\"git-header\">\n\ 167 <table>\n\ 168 <tr><td><h1>%s</h1> <span class=\"git-desc\">%s</span></td></tr>\n\ 169 <tr><td><code class=\"git-clone-cmd\">git clone %s</code></td></tr>\n\ 170 <tr><td class=\"git-nav\">%s | %s | %s | %s</td></tr>\n\ 171 </table>\n\ 172 <hr>\n\ 173 <span class=\"git-query-bar\"><code>%s<-%s%s%s: %s/<b>%s</b></code></span>\n\ 174 <hr>\n\ 175 </div>\n", rname, my_html_encode(description), 176 my_html_make_link(new_clone_url, my_html_encode(new_clone_url)), 177 my_html_make_link(make_query_simple(T_LOG, NULL, NULL), 178 "Log"), 179 my_html_make_link(make_query_simple(T_COMMIT, NULL, NULL), 180 "Commit"), 181 my_html_make_link(make_query_simple(T_TREE, NULL, NULL), 182 "Tree"), 183 my_html_make_link(make_query_simple(T_REFS, NULL, NULL), 184 "Refs"), 185 my_html_make_link(make_query_simple(0, git_object_id(head), NULL), 186 "HEAD"), 187 ishead ? "" : "<span class=\"git-qb-nothead\">", 188 idh, 189 ishead ? "" : "</span>", 190 my_html_make_link(make_query_simple(0, NULL, "/"), 191 "root"), 192 query.path); 193 } 194 195 196 int 197 check_gerr(int e, const char *op) 198 { 199 if (e >= 0) return e; 200 201 page_setup(1); 202 html_print_err("error %d, operation %s", e, op); 203 cleanup(1); 204 205 return e; 206 } 207 208 209 void * 210 check_ptr(void *p, const char *op) 211 { 212 if (!p) 213 check_gerr(-1, op); 214 215 return p; 216 } 217 218 219 char * 220 make_query(struct q *dest) 221 { 222 char *qstr = check_sptr(my_malloc(MINB)); 223 224 snprintf(qstr, MINB, "?r=%s&path=%s&op=%c&id=%s", 225 dest->repn, dest->path, dest->tool, dest->trev); 226 227 return qstr; 228 } 229 230 231 char * 232 make_query_simple(int t, const git_oid *id, const char *path) 233 { 234 struct q dest = query; 235 char *p2, *pw = path 236 ? my_malloc(html_catsz("rr", path, dest.path)) 237 : NULL; 238 char idh[9] = {0}; 239 240 if (path) { 241 (void)check_sptr(pw); 242 if (*path != '/') 243 sprintf(pw, "%s/%s", dest.path, path); 244 else 245 sprintf(pw, "%s", path); 246 p2 = check_sptr(norm_path(pw, 0)); 247 my_free(pw); 248 dest.path[0] = '\0'; 249 strncat(dest.path, p2, MINL); 250 my_free(p2); 251 } 252 253 if (id) { 254 check_sgerr(git_oid_nfmt(idh, 8, id)); 255 snprintf(dest.trev, MINB, "%s", idh); 256 } 257 if (t) dest.tool = t; 258 259 return make_query(&dest); 260 } 261 262 263 char * 264 make_snapshot_name(const char *ext) 265 { 266 char *snapname = check_sptr(my_malloc(MINB)); 267 char cidbuf[MINB]; 268 269 git_oid_tostr(cidbuf, MINB, gbase); 270 snprintf(snapname, MINB, "%s-%s.%s", rname, cidbuf, ext); 271 272 return snapname; 273 } 274 275 276 char * 277 make_tag_summary(git_commit *commit) 278 { 279 const git_oid *cid = git_commit_id(commit); 280 git_reference_iterator *iter; 281 git_reference *ref; 282 git_object *obj; 283 char tagbuf[MINB]; 284 char *fgs = my_html_encode((char *)git_commit_summary(commit)); 285 size_t tsz = (strlen(fgs) + MINB); 286 char *t = check_sptr(my_malloc(tsz + 1)); 287 *t = '\0'; 288 289 strncat(t, fgs, tsz); 290 291 check_sgerr(git_revparse_single(&obj, grepo, "HEAD")); 292 if (git_oid_cmp(git_object_id(obj), cid) == 0) 293 strncat(t, 294 " <span class=\"git-tag git-tag-head\">HEAD</span>", tsz); 295 git_object_free(obj); 296 297 check_sgerr(git_reference_iterator_glob_new( 298 &iter, grepo, "refs/heads/*")); 299 while (!git_reference_next(&ref, iter)) { 300 if (git_oid_cmp(git_reference_target(ref), cid) == 0) { 301 snprintf(tagbuf, MINB, 302 " <span class=\"git-tag git-tag-branch\">\ 303 %s</span>", (char *)my_html_encode(git_reference_shorthand(ref))); 304 strncat(t, tagbuf, tsz); 305 } 306 git_reference_free(ref); 307 } 308 git_reference_iterator_free(iter); 309 310 my_free(fgs); 311 312 return t; 313 } 314 315 316 git_diff * 317 commit_to_diff(git_commit *commit, git_commit *parent) 318 { 319 git_diff *diff; 320 git_tree *commit_tree, *parent_tree = NULL; 321 git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; 322 323 check_sgerr(git_commit_tree(&commit_tree, commit)); 324 if (parent) { 325 git_commit_tree(&parent_tree, parent); 326 } 327 328 check_sgerr(git_diff_tree_to_tree( 329 &diff, grepo, parent_tree, commit_tree, NULL)); 330 331 opts.flags = GIT_DIFF_FIND_RENAMES 332 | GIT_DIFF_FIND_COPIES 333 | GIT_DIFF_FIND_FOR_UNTRACKED; 334 335 check_sgerr(git_diff_find_similar(diff, &opts)); 336 337 git_tree_free(commit_tree); 338 if (parent_tree) git_tree_free(parent_tree); 339 340 return diff; 341 } 342 343 344 static void 345 do_query(const char *name, const char *value) 346 { 347 char *pw; 348 349 if (strcasecmp("id", name) == 0 && value) 350 strncat(query.trev, value, MINL); 351 else if (strcasecmp("path", name) == 0 && value) 352 strncat(query.path, value, MINL); 353 else if (strcasecmp("op", name) == 0 && value) 354 query.tool = *value; 355 else if (!repo && strcasecmp("r", name) == 0 && value) { 356 pw = check_sptr(norm_path(value, 1)); 357 strncat(query.repn, pw, MINL); 358 my_free(pw); 359 } 360 } 361 362 363 static void 364 env_setup(void) 365 { 366 char *cp, *cp2, *cp3, *name, *value; 367 368 /* XXX: called before git repo open */ 369 370 if (repo) strncat(query.self.rpath, repo, MINL); 371 372 if(!(cp = getenv("QUERY_STRING"))) goto query_skip; 373 if(!(cp = strtok_r(cp, "&", &cp2))) goto query_skip; 374 375 do { 376 if ((name = strtok_r(cp, "=", &cp3))) 377 value = strtok_r(NULL, "=", &cp3); 378 do_query(name, value); 379 } while ((cp = strtok_r(NULL, "&", &cp2)) != NULL); 380 381 query_skip: 382 if (!repo) 383 if (query.repn[0] != '\0') 384 snprintf(query.self.rpath, MINB, 385 "%s%s", repo_dir, query.repn); 386 else 387 check_gerr(-1, "null repository"); 388 389 cp = NULL; 390 #define try_p(...) \ 391 if (!cp) { \ 392 do {__VA_ARGS__} while(0); \ 393 cp3 = cp = realpath(query.self.rpath, NULL); } 394 try_p( /* default */ ); 395 try_p( 396 cp2 = strstr(query.self.rpath, ".git"); 397 if (cp2) *cp2 = '\0'; ); 398 try_p( 399 strncat(query.self.rpath, ".git", MINL); ); 400 if (!cp) { 401 snprintf(title, MINB, "(git) error"); 402 check_gerr(-1, "realpath"); 403 404 return; 405 } 406 407 cp2 = strrchr(cp, '/'); 408 cp = cp2 && *(cp2 + 1) != '\0' ? cp2 + 1 : cp; 409 410 strncat(rname, cp, MINL); 411 snprintf(title, MINB, "(git) %s", rname); 412 413 free(cp3); 414 415 if (repo) snprintf(query.repn, MINB, "/%s", rname); 416 417 if (!(query.tool && strchr("lctbrpzZ", query.tool))) 418 query.tool = T_LOG; 419 420 cp = check_sptr(norm_path(query.path, 0)); 421 memset(query.path, '\0', MINB); 422 strncat(query.path, cp, MINL); 423 } 424 425 426 static void 427 do_archive(char *fmt) 428 { 429 pid_t p; 430 char cmdbuf[MINB], cidbuf[MINB]; 431 432 binary_xfer = 1; 433 http_header_attachment(1, make_snapshot_name(fmt)); 434 http_header_end(query.can_compress); 435 436 git_oid_tostr(cidbuf, MINB, gbase); 437 fflush(stdout); 438 p = check_sgerr(fork()); 439 if (p == 0) { 440 snprintf(cmdbuf, MINB, "--format=%s", fmt); 441 execlp("git", "git", "archive", cmdbuf, cidbuf, (char *) NULL); 442 } else waitpid(p, NULL, 0); 443 } 444 445 446 int main(int argc, char **argv) 447 { 448 struct stat sb; 449 450 /* XXX: just write to stdout, we'll adjust it later */ 451 capture = stdout; 452 453 env_setup(); 454 455 strncat(query.self.mpath, argv[0], MINL); 456 if (stat(query.self.mpath, &sb) >= 0) 457 query.self.mtime = sb.st_mtim; 458 if (stat(query.self.rpath, &sb) >= 0) 459 query.self.rtime = sb.st_mtim; 460 461 if (T_IS(T_USES_PAGE)) 462 page_setup(0); 463 else 464 setup_filters(); 465 466 setup_git(); /* XXX: if we haven't already... (see page_setup) */ 467 468 switch (query.tool) { 469 case T_LOG: dump_commits(); break; 470 case T_PATCH: 471 case T_COMMIT: dump_commit(); break; 472 case T_BLOB: 473 case T_TREE: dump_tree(); break; 474 case T_REFS: dump_refs(); break; 475 case T_TARGZ: do_archive("tgz"); break; 476 case T_ZIP: do_archive("zip"); break; 477 } 478 479 cleanup(0); 480 return 0; /* never achieved */ 481 }
/*