/* * 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 #include #include /* for WEXITSTATUS - see system(3) */ #include #include #include #include #include #include "cache.h" #include "cvsps_types.h" #include "cvsps.h" #include "util.h" #include "stats.h" #include "cap.h" #include "cvs_direct.h" #include "list_sort.h" RCSID("$Id: cvsps.c,v 4.106 2005/05/26 03:39:29 david Exp $"); #define CVS_LOG_BOUNDARY "----------------------------\n" #define CVS_FILE_BOUNDARY "=============================================================================\n" enum { NEED_RCS_FILE, NEED_WORKING_FILE, NEED_SYMS, NEED_EOS, NEED_START_LOG, NEED_REVISION, NEED_DATE_AUTHOR_STATE, NEED_EOM }; /* true globals */ struct hash_table * file_hash; CvsServerCtx * cvs_direct_ctx; char root_path[PATH_MAX]; char repository_path[PATH_MAX]; const char * tag_flag_descr[] = { "", "**FUNKY**", "**INVALID**", "**INVALID**" }; const char * fnk_descr[] = { "", "FNK_SHOW_SOME", "FNK_SHOW_ALL", "FNK_HIDE_ALL", "FNK_HIDE_SOME" }; /* static globals */ static int ps_counter; static void * ps_tree; static struct hash_table * global_symbols; static char strip_path[PATH_MAX]; static int strip_path_len; static time_t cache_date; static int update_cache; static int ignore_cache; static int do_write_cache; static int statistics; static const char * test_log_file; static struct hash_table * branch_heads; static struct list_head all_patch_sets; static struct list_head collisions; /* settable via options */ static int timestamp_fuzz_factor = 300; static int do_diff; static const char * restrict_author; static int have_restrict_log; static regex_t restrict_log; static int have_restrict_file; static regex_t restrict_file; static time_t restrict_date_start; static time_t restrict_date_end; static const char * restrict_branch; static struct list_head show_patch_set_ranges; static int summary_first; static const char * norc = ""; static const char * patch_set_dir; static const char * restrict_tag_start; static const char * restrict_tag_end; static int restrict_tag_ps_start; static int restrict_tag_ps_end = INT_MAX; static const char * diff_opts; static int bkcvs; static int no_rlog; static int cvs_direct; static int compress; static char compress_arg[8]; static int track_branch_ancestry; static void check_norc(int, char *[]); static int parse_args(int, char *[]); static int parse_rc(); static void load_from_cvs(); static void init_paths(); static CvsFile * build_file_by_name(const char *); static CvsFile * parse_rcs_file(const char *); static CvsFile * parse_working_file(const char *); static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str); static void assign_pre_revision(PatchSetMember *, CvsFileRevision * rev); static void check_print_patch_set(PatchSet *); static void print_patch_set(PatchSet *); static void assign_patchset_id(PatchSet *); static int compare_rev_strings(const char *, const char *); static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2); static int compare_patch_sets_bk(const void *, const void *); static int compare_patch_sets(const void *, const void *); static int compare_patch_sets_bytime_list(struct list_head *, struct list_head *); static int compare_patch_sets_bytime(const PatchSet *, const PatchSet *); static int is_revision_metadata(const char *); static int patch_set_member_regex(PatchSet * ps, regex_t * reg); static int patch_set_affects_branch(PatchSet *, const char *); static void do_cvs_diff(PatchSet *); static PatchSet * create_patch_set(); static PatchSetRange * create_patch_set_range(); static void parse_sym(CvsFile *, char *); static void resolve_global_symbols(); static int revision_affects_branch(CvsFileRevision *, const char *); static int is_vendor_branch(const char *); static void set_psm_initial(PatchSetMember * psm); static int check_rev_funk(PatchSet *, CvsFileRevision *); static CvsFileRevision * rev_follow_branch(CvsFileRevision *, const char *); static int before_tag(CvsFileRevision * rev, const char * tag); static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps); static void handle_collisions(); int main(int argc, char *argv[]) { debuglvl = DEBUG_APPERROR|DEBUG_SYSERROR|DEBUG_APPMSG1; INIT_LIST_HEAD(&show_patch_set_ranges); /* * we want to parse the rc first, so command line can override it * but also, --norc should stop the rc from being processed, so * we look for --norc explicitly first. Note: --norc in the rc * file itself will prevent the cvs rc file from being used. */ check_norc(argc, argv); if (strlen(norc) == 0 && parse_rc() < 0) exit(1); if (parse_args(argc, argv) < 0) exit(1); if (diff_opts && !cvs_direct && do_diff) { debug(DEBUG_APPMSG1, "\nWARNING: diff options are not supported by 'cvs rdiff'"); debug(DEBUG_APPMSG1, " which is usually used to create diffs. 'cvs diff'"); debug(DEBUG_APPMSG1, " will be used instead, but the resulting patches "); debug(DEBUG_APPMSG1, " will need to be applied using the '-p0' option"); debug(DEBUG_APPMSG1, " to patch(1) (in the working directory), "); debug(DEBUG_APPMSG1, " instead of '-p1'\n"); } file_hash = create_hash_table(1023); global_symbols = create_hash_table(111); branch_heads = create_hash_table(1023); INIT_LIST_HEAD(&all_patch_sets); INIT_LIST_HEAD(&collisions); /* this parses some of the CVS/ files, and initializes * the repository_path and other variables */ init_paths(); if (!ignore_cache) { int save_fuzz_factor = timestamp_fuzz_factor; /* the timestamp fuzz should only be in effect when loading from * CVS, not re-fuzzed when loading from cache. This is a hack * working around bad use of global variables */ timestamp_fuzz_factor = 0; if ((cache_date = read_cache()) < 0) update_cache = 1; timestamp_fuzz_factor = save_fuzz_factor; } if (cvs_direct && (do_diff || (update_cache && !test_log_file))) cvs_direct_ctx = open_cvs_server(root_path, compress); if (update_cache) { load_from_cvs(); do_write_cache = 1; } //XXX //handle_collisions(); list_sort(&all_patch_sets, compare_patch_sets_bytime_list); ps_counter = 0; walk_all_patch_sets(assign_patchset_id); handle_collisions(); resolve_global_symbols(); if (do_write_cache) write_cache(cache_date); if (statistics) print_statistics(ps_tree); /* check that the '-r' symbols (if specified) were resolved */ if (restrict_tag_start && restrict_tag_ps_start == 0 && strcmp(restrict_tag_start, "#CVSPS_EPOCH") != 0) { debug(DEBUG_APPERROR, "symbol given with -r: %s: not found", restrict_tag_start); exit(1); } if (restrict_tag_end && restrict_tag_ps_end == INT_MAX) { debug(DEBUG_APPERROR, "symbol given with second -r: %s: not found", restrict_tag_end); exit(1); } walk_all_patch_sets(check_print_patch_set); if (summary_first++) walk_all_patch_sets(check_print_patch_set); if (cvs_direct_ctx) close_cvs_server(cvs_direct_ctx); exit(0); } static void load_from_cvs() { FILE * cvsfp; char buff[BUFSIZ]; int state = NEED_RCS_FILE; CvsFile * file = NULL; PatchSetMember * psm = NULL; char datebuff[20]; char authbuff[AUTH_STR_MAX]; int logbufflen = LOG_STR_MAX + 1; char * logbuff = malloc(logbufflen); int loglen = 0; int have_log = 0; char cmd[BUFSIZ]; char date_str[64]; char use_rep_buff[PATH_MAX]; char * ltype; if (logbuff == NULL) { debug(DEBUG_SYSERROR, "could not malloc %d bytes for logbuff in load_from_cvs", logbufflen); exit(1); } if (!no_rlog && !test_log_file && cvs_check_cap(CAP_HAVE_RLOG)) { ltype = "rlog"; snprintf(use_rep_buff, PATH_MAX, "%s", repository_path); } else { ltype = "log"; use_rep_buff[0] = 0; } if (cache_date > 0) { struct tm * tm = gmtime(&cache_date); strftime(date_str, 64, "%d %b %Y %H:%M:%S %z", tm); /* this command asks for logs using two different date * arguments, separated by ';' (see man rlog). The first * gets all revisions more recent than date, the second * gets a single revision no later than date, which combined * get us all revisions that have occurred since last update * and overlaps what we had before by exactly one revision, * which is necessary to fill in the pre_rev stuff for a * PatchSetMember */ snprintf(cmd, BUFSIZ, "cvs %s %s -q %s -d '%s<;%s' %s", compress_arg, norc, ltype, date_str, date_str, use_rep_buff); } else { date_str[0] = 0; snprintf(cmd, BUFSIZ, "cvs %s %s -q %s %s", compress_arg, norc, ltype, use_rep_buff); } debug(DEBUG_STATUS, "******* USING CMD %s", cmd); cache_date = time(NULL); /* FIXME: this is ugly, need to virtualize the accesses away from here */ if (test_log_file) cvsfp = fopen(test_log_file, "r"); else if (cvs_direct_ctx) cvsfp = cvs_rlog_open(cvs_direct_ctx, repository_path, date_str); else cvsfp = popen(cmd, "r"); if (!cvsfp) { debug(DEBUG_SYSERROR, "can't open cvs pipe using command %s", cmd); exit(1); } for (;;) { char * tst; if (cvs_direct_ctx) tst = cvs_rlog_fgets(buff, BUFSIZ, cvs_direct_ctx); else tst = fgets(buff, BUFSIZ, cvsfp); if (!tst) break; debug(DEBUG_STATUS, "state: %d read line:%s", state, buff); switch(state) { case NEED_RCS_FILE: if (strncmp(buff, "RCS file", 8) == 0) { if ((file = parse_rcs_file(buff)) != NULL) state = NEED_SYMS; else state = NEED_WORKING_FILE; } break; case NEED_WORKING_FILE: if (strncmp(buff, "Working file", 12) == 0) { if ((file = parse_working_file(buff))) state = NEED_SYMS; else state = NEED_RCS_FILE; break; } else { // Working file come just after RCS file. So reset state if it was not found state = NEED_RCS_FILE; } break; case NEED_SYMS: if (strncmp(buff, "symbolic names:", 15) == 0) state = NEED_EOS; break; case NEED_EOS: if (!isspace(buff[0])) { /* see cvsps_types.h for commentary on have_branches */ file->have_branches = 1; state = NEED_START_LOG; } else parse_sym(file, buff); break; case NEED_START_LOG: if (strcmp(buff, CVS_LOG_BOUNDARY) == 0) state = NEED_REVISION; break; case NEED_REVISION: if (strncmp(buff, "revision", 8) == 0) { char new_rev[REV_STR_MAX]; CvsFileRevision * rev; strcpy(new_rev, buff + 9); chop(new_rev); /* * rev may already exist (think cvsps -u), in which * case parse_revision is a hash lookup */ rev = parse_revision(file, new_rev); /* * in the simple case, we are copying rev to psm->pre_rev * (psm refers to last patch set processed at this point) * since generally speaking the log is reverse chronological. * This breaks down slightly when branches are introduced */ assign_pre_revision(psm, rev); /* * if this is a new revision, it will have no post_psm associated. * otherwise we are (probably?) hitting the overlap in cvsps -u */ if (!rev->post_psm) { psm = rev->post_psm = create_patch_set_member(); psm->post_rev = rev; psm->file = file; state = NEED_DATE_AUTHOR_STATE; } else { /* we hit this in cvsps -u mode, we are now up-to-date * w.r.t this particular file. skip all of the rest * of the info (revs and logs) until we hit the next file */ psm = NULL; state = NEED_EOM; } } break; case NEED_DATE_AUTHOR_STATE: if (strncmp(buff, "date:", 5) == 0) { char * p; strncpy(datebuff, buff + 6, 19); datebuff[19] = 0; strcpy(authbuff, "unknown"); p = strstr(buff, "author: "); if (p) { char * op; p += 8; op = strchr(p, ';'); if (op) { strzncpy(authbuff, p, op - p + 1); } } /* read the 'state' tag to see if this is a dead revision */ p = strstr(buff, "state: "); if (p) { char * op; p += 7; op = strchr(p, ';'); if (op) if (strncmp(p, "dead", MIN(4, op - p)) == 0) psm->post_rev->dead = 1; } state = NEED_EOM; } break; case NEED_EOM: if (strcmp(buff, CVS_LOG_BOUNDARY) == 0) { if (psm) { PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm); patch_set_add_member(ps, psm); } logbuff[0] = 0; loglen = 0; have_log = 0; state = NEED_REVISION; } else if (strcmp(buff, CVS_FILE_BOUNDARY) == 0) { if (psm) { PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm); patch_set_add_member(ps, psm); assign_pre_revision(psm, NULL); } logbuff[0] = 0; loglen = 0; have_log = 0; psm = NULL; file = NULL; state = NEED_RCS_FILE; } else { /* other "blahblah: information;" messages can * follow the stuff we pay attention to */ if (have_log || !is_revision_metadata(buff)) { /* If the log buffer is full, try to reallocate more. */ if (loglen < logbufflen) { int len = strlen(buff); if (len >= logbufflen - loglen) { debug(DEBUG_STATUS, "reallocating logbufflen to %d bytes for file %s", logbufflen, file->filename); 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 load_from_cvs", logbufflen); exit(1); } logbuff = newlogbuff; } debug(DEBUG_STATUS, "appending %s to log", buff); memcpy(logbuff + loglen, buff, len); loglen += len; logbuff[loglen] = 0; have_log = 1; } } else { debug(DEBUG_STATUS, "ignoring unhandled info %s", buff); } } break; } } if (state == NEED_SYMS) { debug(DEBUG_APPERROR, "Error: 'symbolic names' not found in log output."); debug(DEBUG_APPERROR, " Perhaps you should try running with --norc"); exit(1); } if (state != NEED_RCS_FILE) { debug(DEBUG_APPERROR, "Error: Log file parsing error. (%d) Use -v to debug", state); exit(1); } if (test_log_file) { fclose(cvsfp); } else if (cvs_direct_ctx) { cvs_rlog_close(cvs_direct_ctx); } else { if (pclose(cvsfp) < 0) { debug(DEBUG_APPERROR, "cvs rlog command exited with error. aborting"); exit(1); } } } static int usage(const char * str1, const char * str2) { if (str1) debug(DEBUG_APPERROR, "\nbad usage: %s %s\n", str1, str2); debug(DEBUG_APPERROR, "Usage: cvsps [-h] [-x] [-u] [-z ] [-g] [-s [,]] "); debug(DEBUG_APPERROR, " [-a ] [-f ] [-d [-d ]] "); debug(DEBUG_APPERROR, " [-b ] [-l ] [-r [-r ]] "); debug(DEBUG_APPERROR, " [-p ] [-v] [-t] [--norc] [--summary-first]"); debug(DEBUG_APPERROR, " [--test-log ] [--bkcvs]"); debug(DEBUG_APPERROR, " [--no-rlog] [--diff-opts