/* * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. * See COPYING file for license information */ #include #include #include #include #include #include #include #include #include #include #include "cache.h" #include "cvsps_types.h" #include "cvsps.h" #include "util.h" #define CACHE_DESCR_BOUNDARY "-=-END CVSPS DESCR-=-\n" /* change this when making the on-disk cache-format invalid */ static int cache_version = 1; /* the tree walk API pretty much requries use of globals :-( */ static FILE * cache_fp; static int ps_counter; static void write_patch_set_to_cache(PatchSet *); static void parse_cache_revision(PatchSetMember *, const char *); static void dump_patch_set(FILE *, PatchSet *); static FILE *cache_open(char const *mode) { char *prefix; char fname[PATH_MAX]; char root[PATH_MAX]; char repository[PATH_MAX]; FILE * fp; /* Get the prefix */ prefix = get_cvsps_dir(); if (!prefix) return NULL; /* Generate the full path */ strcpy(root, root_path); strcpy(repository, repository_path); strrep(root, '/', '#'); strrep(repository, '/', '#'); snprintf(fname, PATH_MAX, "%s/%s#%s", prefix, root, repository); if (!(fp = fopen(fname, mode)) && *mode == 'r') { if ((fp = fopen("CVS/cvsps.cache", mode))) { fprintf(stderr, "\n"); fprintf(stderr, "****WARNING**** Obsolete CVS/cvsps.cache file found.\n"); fprintf(stderr, " New file will be re-written in ~/%s/\n", CVSPS_PREFIX); fprintf(stderr, " Old file will be ignored.\n"); fprintf(stderr, " Please manually remove the old file.\n"); fprintf(stderr, " Continuing in 5 seconds.\n"); sleep(5); fclose(fp); fp = NULL; } } return fp; } /* ************ Reading ************ */ enum { CACHE_NEED_FILE, CACHE_NEED_BRANCHES, CACHE_NEED_SYMBOLS, CACHE_NEED_REV, CACHE_NEED_PS, CACHE_NEED_PS_DATE, CACHE_NEED_PS_AUTHOR, CACHE_NEED_PS_TAG, CACHE_NEED_PS_TAG_FLAGS, CACHE_NEED_PS_BRANCH, CACHE_NEED_PS_BRANCH_ADD, CACHE_NEED_PS_DESCR, CACHE_NEED_PS_EOD, CACHE_NEED_PS_MEMBERS, CACHE_NEED_PS_EOM }; time_t read_cache() { FILE * fp; char buff[BUFSIZ]; int state = CACHE_NEED_FILE; CvsFile * f = NULL; PatchSet * ps = NULL; char datebuff[20] = ""; char authbuff[AUTH_STR_MAX] = ""; char tagbuff[LOG_STR_MAX] = ""; int tag_flags = 0; char branchbuff[LOG_STR_MAX] = ""; int branch_add = 0; int logbufflen = LOG_STR_MAX + 1; char * logbuff = malloc(logbufflen); time_t cache_date = -1; int read_version; if (logbuff == NULL) { debug(DEBUG_SYSERROR, "could not malloc %d bytes for logbuff in read_cache", logbufflen); exit(1); } logbuff[0] = 0; if (!(fp = cache_open("r"))) goto out; /* first line is cache version format "cache version: %d\n" */ if (!fgets(buff, BUFSIZ, fp) || strncmp(buff, "cache version:", 14)) { debug(DEBUG_APPERROR, "bad cvsps.cache file"); goto out_close; } if ((read_version = atoi(buff + 15)) != cache_version) { debug(DEBUG_APPERROR, "bad cvsps.cache version %d, expecting %d. ignoring cache", read_version, cache_version); goto out_close; } /* second line is date cache was created, format "cache date: %d\n" */ if (!fgets(buff, BUFSIZ, fp) || strncmp(buff, "cache date:", 11)) { debug(DEBUG_APPERROR, "bad cvsps.cache file"); goto out_close; } cache_date = atoi(buff + 12); debug(DEBUG_STATUS, "read cache_date %d", (int)cache_date); while (fgets(buff, BUFSIZ, fp)) { int len = strlen(buff); switch(state) { case CACHE_NEED_FILE: if (strncmp(buff, "file:", 5) == 0) { len -= 6; f = create_cvsfile(); f->filename = xstrdup(buff + 6); f->filename[len-1] = 0; /* Remove the \n at the end of line */ debug(DEBUG_STATUS, "read cache filename '%s'", f->filename); put_hash_object_ex(file_hash, f->filename, f, HT_NO_KEYCOPY, NULL, NULL); state = CACHE_NEED_BRANCHES; } else { state = CACHE_NEED_PS; } break; case CACHE_NEED_BRANCHES: if (buff[0] != '\n') { char * tag; tag = strchr(buff, ':'); if (tag) { *tag = 0; tag += 2; buff[len - 1] = 0; cvs_file_add_branch(f, buff, tag); } } else { f->have_branches = 1; state = CACHE_NEED_SYMBOLS; } break; case CACHE_NEED_SYMBOLS: if (buff[0] != '\n') { char * rev; rev = strchr(buff, ':'); if (rev) { *rev = 0; rev += 2; buff[len - 1] = 0; cvs_file_add_symbol(f, rev, buff); } } else { state = CACHE_NEED_REV; } break; case CACHE_NEED_REV: if (isdigit(buff[0])) { char * p = strchr(buff, ' '); if (p) { CvsFileRevision * rev; *p++ = 0; buff[len-1] = 0; rev = cvs_file_add_revision(f, buff); if (strcmp(rev->branch, p) != 0) { debug(DEBUG_APPERROR, "branch mismatch for %s:%s %s != %s", rev->file->filename, rev->rev, rev->branch, p); } } } else { state = CACHE_NEED_FILE; } break; case CACHE_NEED_PS: if (strncmp(buff, "patchset:", 9) == 0) state = CACHE_NEED_PS_DATE; break; case CACHE_NEED_PS_DATE: if (strncmp(buff, "date:", 5) == 0) { /* remove prefix "date: " and LF from len */ len -= 6; strzncpy(datebuff, buff + 6, MIN(len, sizeof(datebuff))); state = CACHE_NEED_PS_AUTHOR; } break; case CACHE_NEED_PS_AUTHOR: if (strncmp(buff, "author:", 7) == 0) { /* remove prefix "author: " and LF from len */ len -= 8; strzncpy(authbuff, buff + 8, MIN(len, AUTH_STR_MAX)); state = CACHE_NEED_PS_TAG; } break; case CACHE_NEED_PS_TAG: if (strncmp(buff, "tag:", 4) == 0) { /* remove prefix "tag: " and LF from len */ len -= 5; strzncpy(tagbuff, buff + 5, MIN(len, LOG_STR_MAX)); state = CACHE_NEED_PS_TAG_FLAGS; } break; case CACHE_NEED_PS_TAG_FLAGS: if (strncmp(buff, "tag_flags:", 10) == 0) { /* remove prefix "tag_flags: " and LF from len */ len -= 11; tag_flags = atoi(buff + 11); state = CACHE_NEED_PS_BRANCH; } break; case CACHE_NEED_PS_BRANCH: if (strncmp(buff, "branch:", 7) == 0) { /* remove prefix "branch: " and LF from len */ len -= 8; strzncpy(branchbuff, buff + 8, MIN(len, LOG_STR_MAX)); state = CACHE_NEED_PS_BRANCH_ADD; } break; case CACHE_NEED_PS_BRANCH_ADD: if (strncmp(buff, "branch_add:", 11) == 0) { /* remove prefix "branch_add: " and LF from len */ len -= 12; branch_add = atoi(buff + 12); state = CACHE_NEED_PS_DESCR; } break; case CACHE_NEED_PS_DESCR: if (strncmp(buff, "descr:", 6) == 0) state = CACHE_NEED_PS_EOD; break; case CACHE_NEED_PS_EOD: if (strcmp(buff, CACHE_DESCR_BOUNDARY) == 0) { debug(DEBUG_STATUS, "patch set %s %s %s %s", datebuff, authbuff, logbuff, branchbuff); ps = get_patch_set(datebuff, logbuff, authbuff, branchbuff, NULL); /* the tag and tag_flags will be assigned by the resolve_global_symbols code * ps->tag = (strlen(tagbuff)) ? get_string(tagbuff) : NULL; * ps->tag_flags = tag_flags; */ ps->branch_add = branch_add; state = CACHE_NEED_PS_MEMBERS; } else { /* Make sure we have enough in the buffer */ int len = strlen(buff); if (strlen(logbuff) + len >= LOG_STR_MAX) { logbufflen += (len >= LOG_STR_MAX ? (len+1) : LOG_STR_MAX); char * newlogbuff = realloc(logbuff, logbufflen); if (newlogbuff == NULL) { debug(DEBUG_SYSERROR, "could not realloc %d bytes for logbuff in read_cache", logbufflen); exit(1); } logbuff = newlogbuff; } strcat(logbuff, buff); } break; case CACHE_NEED_PS_MEMBERS: if (strncmp(buff, "members:", 8) == 0) state = CACHE_NEED_PS_EOM; break; case CACHE_NEED_PS_EOM: if (buff[0] == '\n') { datebuff[0] = 0; authbuff[0] = 0; tagbuff[0] = 0; tag_flags = 0; branchbuff[0] = 0; branch_add = 0; logbuff[0] = 0; state = CACHE_NEED_PS; } else { PatchSetMember * psm = create_patch_set_member(); parse_cache_revision(psm, buff); patch_set_add_member(ps, psm); } break; } } out_close: fclose(fp); out: free(logbuff); return cache_date; } enum { CR_FILENAME, CR_PRE_REV, CR_POST_REV, CR_DEAD, CR_BRANCH_POINT }; static void parse_cache_revision(PatchSetMember * psm, const char * buff) { /* The format used to generate is: * "file:%s; pre_rev:%s; post_rev:%s; dead:%d; branch_point:%d\n" */ char filename[PATH_MAX]; char pre[REV_STR_MAX]; char post[REV_STR_MAX]; int dead = 0; int bp = 0; int state = CR_FILENAME; const char *sep; char * p; char * c; for (p = buff, sep = buff; /* just ensure sep is non-NULL */ (sep != NULL) && (c = strchr(p, ':')); p = sep + 1) { size_t len; sep = strchr(c, ';'); c++; if (sep != NULL) len = sep - c; else /* last field in the cache line */ len = strlen(c); switch(state) { case CR_FILENAME: memcpy(filename, c, len); filename[len] = '\0'; break; case CR_PRE_REV: memcpy(pre, c, len); pre[len] = '\0'; break; case CR_POST_REV: memcpy(post, c, len); post[len] = '\0'; break; case CR_DEAD: dead = atoi(c); break; case CR_BRANCH_POINT: bp = atoi(c); break; } state++; } psm->file = (CvsFile*)get_hash_object(file_hash, filename); if (!psm->file) { debug(DEBUG_APPERROR, "file '%s' not found in hash", filename); exit(1); } psm->pre_rev = file_get_revision(psm->file, pre); psm->post_rev = file_get_revision(psm->file, post); psm->post_rev->dead = dead; psm->post_rev->post_psm = psm; if (!bp) { if (psm->pre_rev) psm->pre_rev->pre_psm = psm; } else { list_add(&psm->post_rev->link, &psm->pre_rev->branch_children); } } /************ Writing ************/ void write_cache(time_t cache_date) { struct hash_entry * file_iter; ps_counter = 0; if ((cache_fp = cache_open("w")) == NULL) { debug(DEBUG_SYSERROR, "can't open cvsps.cache for write"); return; } fprintf(cache_fp, "cache version: %d\n", cache_version); fprintf(cache_fp, "cache date: %d\n", (int)cache_date); reset_hash_iterator(file_hash); while ((file_iter = next_hash_entry(file_hash))) { CvsFile * file = (CvsFile*)file_iter->he_obj; struct hash_entry * rev_iter; fprintf(cache_fp, "file: %s\n", file->filename); reset_hash_iterator(file->branches); while ((rev_iter = next_hash_entry(file->branches))) { char * rev = (char *)rev_iter->he_key; char * tag = (char *)rev_iter->he_obj; fprintf(cache_fp, "%s: %s\n", rev, tag); } fprintf(cache_fp, "\n"); reset_hash_iterator(file->symbols); while ((rev_iter = next_hash_entry(file->symbols))) { char * tag = (char *)rev_iter->he_key; CvsFileRevision * rev = (CvsFileRevision*)rev_iter->he_obj; if (rev->present) fprintf(cache_fp, "%s: %s\n", tag, rev->rev); } fprintf(cache_fp, "\n"); reset_hash_iterator(file->revisions); while ((rev_iter = next_hash_entry(file->revisions))) { CvsFileRevision * rev = (CvsFileRevision*)rev_iter->he_obj; if (rev->present) fprintf(cache_fp, "%s %s\n", rev->rev, rev->branch); } fprintf(cache_fp, "\n"); } fprintf(cache_fp, "\n"); walk_all_patch_sets(write_patch_set_to_cache); fclose(cache_fp); cache_fp = NULL; } static void write_patch_set_to_cache(PatchSet * ps) { dump_patch_set(cache_fp, ps); } static void dump_patch_set(FILE * fp, PatchSet * ps) { struct list_head * next = ps->members.next; ps_counter++; fprintf(fp, "patchset: %d\n", ps_counter); fprintf(fp, "date: %d\n", (int)ps->date); fprintf(fp, "author: %s\n", ps->author); fprintf(fp, "tag: %s\n", ps->tag ? ps->tag : ""); fprintf(fp, "tag_flags: %d\n", ps->tag_flags); fprintf(fp, "branch: %s\n", ps->branch); fprintf(fp, "branch_add: %d\n", ps->branch_add); fprintf(fp, "descr:\n%s", ps->descr); /* descr is guaranteed to end with LF */ fprintf(fp, CACHE_DESCR_BOUNDARY); fprintf(fp, "members:\n"); while (next != &ps->members) { PatchSetMember * psm = list_entry(next, PatchSetMember, link); int bp = 1; /* this actually deduces if this revision is a branch point... */ if (!psm->pre_rev || (psm->pre_rev->pre_psm && psm->pre_rev->pre_psm == psm)) bp = 0; fflush(fp); fprintf(fp, "file:%s; pre_rev:%s; post_rev:%s; dead:%d; branch_point:%d\n", psm->file->filename, psm->pre_rev ? psm->pre_rev->rev : "INITIAL", psm->post_rev->rev, psm->post_rev->dead, bp); next = next->next; } fprintf(fp, "\n"); } /* where's arithmetic?... */