log.c revision 175273
117721Speter/* 2175273Sobrien * Copyright (C) 1986-2005 The Free Software Foundation, Inc. 3175273Sobrien * 4175273Sobrien * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, 5175273Sobrien * and others. 6175273Sobrien * 7175273Sobrien * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk 8175273Sobrien * Portions Copyright (C) 1989-1992, Brian Berliner 917721Speter * 1017721Speter * You may distribute under the terms of the GNU General Public License as 1132785Speter * specified in the README file that comes with the CVS source distribution. 1217721Speter * 1317721Speter * Print Log Information 1417721Speter * 1517721Speter * Prints the RCS "log" (rlog) information for the specified files. With no 1617721Speter * argument, prints the log information for all the files in the directory 1717721Speter * (recursive by default). 18131688Sdes * 19131688Sdes * $FreeBSD: head/contrib/cvs/src/log.c 175273 2008-01-13 06:08:49Z obrien $ 2017721Speter */ 2117721Speter 2217721Speter#include "cvs.h" 23175273Sobrien#include <assert.h> 2417721Speter 2525839Speter/* This structure holds information parsed from the -r option. */ 2617721Speter 2725839Speterstruct option_revlist 2825839Speter{ 2925839Speter /* The next -r option. */ 3025839Speter struct option_revlist *next; 3125839Speter /* The first revision to print. This is NULL if the range is 3225839Speter :rev, or if no revision is given. */ 3325839Speter char *first; 3425839Speter /* The last revision to print. This is NULL if the range is rev:, 3525839Speter or if no revision is given. If there is no colon, first and 3625839Speter last are the same. */ 3725839Speter char *last; 3825839Speter /* Nonzero if there was a trailing `.', which means to print only 3925839Speter the head revision of a branch. */ 4025839Speter int branchhead; 4181404Speter /* Nonzero if first and last are inclusive. */ 4281404Speter int inclusive; 4325839Speter}; 4425839Speter 4525839Speter/* This structure holds information derived from option_revlist given 4625839Speter a particular RCS file. */ 4725839Speter 4825839Speterstruct revlist 4925839Speter{ 5025839Speter /* The next pair. */ 5125839Speter struct revlist *next; 5225839Speter /* The first numeric revision to print. */ 5325839Speter char *first; 5425839Speter /* The last numeric revision to print. */ 5525839Speter char *last; 5625839Speter /* The number of fields in these revisions (one more than 5725839Speter numdots). */ 5825839Speter int fields; 5981404Speter /* Whether first & last are to be included or excluded. */ 6081404Speter int inclusive; 6125839Speter}; 6225839Speter 6325839Speter/* This structure holds information parsed from the -d option. */ 6425839Speter 6525839Speterstruct datelist 6625839Speter{ 6725839Speter /* The next date. */ 6825839Speter struct datelist *next; 6925839Speter /* The starting date. */ 7025839Speter char *start; 7125839Speter /* The ending date. */ 7225839Speter char *end; 7325839Speter /* Nonzero if the range is inclusive rather than exclusive. */ 7425839Speter int inclusive; 7525839Speter}; 7625839Speter 7725839Speter/* This structure is used to pass information through start_recursion. */ 7825839Speterstruct log_data 7925839Speter{ 8025839Speter /* Nonzero if the -R option was given, meaning that only the name 8125839Speter of the RCS file should be printed. */ 8225839Speter int nameonly; 8325839Speter /* Nonzero if the -h option was given, meaning that only header 8425839Speter information should be printed. */ 8525839Speter int header; 8625839Speter /* Nonzero if the -t option was given, meaning that only the 8725839Speter header and the descriptive text should be printed. */ 8825839Speter int long_header; 8925839Speter /* Nonzero if the -N option was seen, meaning that tag information 9025839Speter should not be printed. */ 9125839Speter int notags; 9225839Speter /* Nonzero if the -b option was seen, meaning that only revisions 9325839Speter on the default branch should be printed. */ 9425839Speter int default_branch; 95102840Speter /* Nonzero if the -S option was seen, meaning that the header/name 96102840Speter should be suppressed if no revisions are selected. */ 97102840Speter int sup_header; 9825839Speter /* If not NULL, the value given for the -r option, which lists 9925839Speter sets of revisions to be printed. */ 10025839Speter struct option_revlist *revlist; 10125839Speter /* If not NULL, the date pairs given for the -d option, which 10225839Speter select date ranges to print. */ 10325839Speter struct datelist *datelist; 10425839Speter /* If not NULL, the single dates given for the -d option, which 10525839Speter select specific revisions to print based on a date. */ 10625839Speter struct datelist *singledatelist; 10725839Speter /* If not NULL, the list of states given for the -s option, which 10825839Speter only prints revisions of given states. */ 10925839Speter List *statelist; 11025839Speter /* If not NULL, the list of login names given for the -w option, 11125839Speter which only prints revisions checked in by given users. */ 11225839Speter List *authorlist; 11325839Speter}; 11425839Speter 11525839Speter/* This structure is used to pass information through walklist. */ 11625839Speterstruct log_data_and_rcs 11725839Speter{ 11825839Speter struct log_data *log_data; 11925839Speter struct revlist *revlist; 12025839Speter RCSNode *rcs; 12125839Speter}; 12225839Speter 12381404Speterstatic int rlog_proc PROTO((int argc, char **argv, char *xwhere, 12481404Speter char *mwhere, char *mfile, int shorten, 12581404Speter int local_specified, char *mname, char *msg)); 126128266Speterstatic Dtype log_dirproc PROTO ((void *callerdat, const char *dir, 127128266Speter const char *repository, 128128266Speter const char *update_dir, 129128266Speter List *entries)); 13025839Speterstatic int log_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 13125839Speterstatic struct option_revlist *log_parse_revlist PROTO ((const char *)); 13225839Speterstatic void log_parse_date PROTO ((struct log_data *, const char *)); 13325839Speterstatic void log_parse_list PROTO ((List **, const char *)); 134175273Sobrienstatic struct revlist *log_expand_revlist PROTO ((RCSNode *, char *, 13525839Speter struct option_revlist *, 13625839Speter int)); 13725839Speterstatic void log_free_revlist PROTO ((struct revlist *)); 13825839Speterstatic int log_version_requested PROTO ((struct log_data *, struct revlist *, 13925839Speter RCSNode *, RCSVers *)); 14025839Speterstatic int log_symbol PROTO ((Node *, void *)); 14125839Speterstatic int log_count PROTO ((Node *, void *)); 14225839Speterstatic int log_fix_singledate PROTO ((Node *, void *)); 14325839Speterstatic int log_count_print PROTO ((Node *, void *)); 14425839Speterstatic void log_tree PROTO ((struct log_data *, struct revlist *, 14525839Speter RCSNode *, const char *)); 14625839Speterstatic void log_abranch PROTO ((struct log_data *, struct revlist *, 14725839Speter RCSNode *, const char *)); 14825839Speterstatic void log_version PROTO ((struct log_data *, struct revlist *, 14925839Speter RCSNode *, RCSVers *, int)); 15025839Speterstatic int log_branch PROTO ((Node *, void *)); 15125839Speterstatic int version_compare PROTO ((const char *, const char *, int)); 15225839Speter 15381404Speterstatic struct log_data log_data; 15481404Speterstatic int is_rlog; 15581404Speter 15617721Speterstatic const char *const log_usage[] = 15717721Speter{ 15825839Speter "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n", 15925839Speter " [-w[logins]] [files...]\n", 16017721Speter "\t-l\tLocal directory only, no recursion.\n", 161175273Sobrien "\t-b\tOnly list revisions on the default branch.\n", 162175273Sobrien "\t-h\tOnly print header.\n", 16325839Speter "\t-R\tOnly print name of RCS file.\n", 16425839Speter "\t-t\tOnly print header and descriptive text.\n", 16525839Speter "\t-N\tDo not list tags.\n", 166175273Sobrien "\t-S\tDo not print name/header if no revisions selected. -d, -r,\n", 167175273Sobrien "\t\t-s, & -w have little effect in conjunction with -b, -h, -R, and\n", 168175273Sobrien "\t\t-t without this option.\n", 169102840Speter "\t-r[revisions]\tA comma-separated list of revisions to print:\n", 17081404Speter "\t rev1:rev2 Between rev1 and rev2, including rev1 and rev2.\n", 171102840Speter "\t rev1::rev2 Between rev1 and rev2, excluding rev1.\n", 17281404Speter "\t rev: rev and following revisions on the same branch.\n", 17381404Speter "\t rev:: After rev on the same branch.\n", 17481404Speter "\t :rev rev and previous revisions on the same branch.\n", 175102840Speter "\t ::rev rev and previous revisions on the same branch.\n", 17681404Speter "\t rev Just rev.\n", 17781404Speter "\t branch All revisions on the branch.\n", 17881404Speter "\t branch. The last revision on the branch.\n", 179102840Speter "\t-d dates\tA semicolon-separated list of dates\n", 180102840Speter "\t \t(D1<D2 for range, D for latest before).\n", 18125839Speter "\t-s states\tOnly list revisions with specified states.\n", 18225839Speter "\t-w[logins]\tOnly list revisions checked in by specified logins.\n", 18332785Speter "(Specify the --help global option for a list of other help options)\n", 18417721Speter NULL 18517721Speter}; 18617721Speter 18766525Speter#ifdef CLIENT_SUPPORT 18866525Speter 18966525Speter/* Helper function for send_arg_list. */ 19066525Speterstatic int send_one PROTO ((Node *, void *)); 19166525Speter 19266525Speterstatic int 19366525Spetersend_one (node, closure) 19466525Speter Node *node; 19566525Speter void *closure; 19666525Speter{ 19766525Speter char *option = (char *) closure; 19866525Speter 19966525Speter send_to_server ("Argument ", 0); 20066525Speter send_to_server (option, 0); 20166525Speter if (strcmp (node->key, "@@MYSELF") == 0) 20266525Speter /* It is a bare -w option. Note that we must send it as 20366525Speter -w rather than messing with getcaller() or something (which on 20466525Speter the client will return garbage). */ 20566525Speter ; 20666525Speter else 20766525Speter send_to_server (node->key, 0); 20866525Speter send_to_server ("\012", 0); 20966525Speter return 0; 21066525Speter} 21166525Speter 21266525Speter/* For each element in ARG, send an argument consisting of OPTION 21366525Speter concatenated with that element. */ 21466525Speterstatic void send_arg_list PROTO ((char *, List *)); 21566525Speter 21666525Speterstatic void 21766525Spetersend_arg_list (option, arg) 21866525Speter char *option; 21966525Speter List *arg; 22066525Speter{ 22166525Speter if (arg == NULL) 22266525Speter return; 22366525Speter walklist (arg, send_one, (void *)option); 22466525Speter} 22566525Speter 22666525Speter#endif 22766525Speter 22817721Speterint 22917721Spetercvslog (argc, argv) 23017721Speter int argc; 23117721Speter char **argv; 23217721Speter{ 23325839Speter int c; 23417721Speter int err = 0; 23517721Speter int local = 0; 23666525Speter struct option_revlist **prl; 23717721Speter 238128266Speter is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0); 23981404Speter 24017721Speter if (argc == -1) 24117721Speter usage (log_usage); 24217721Speter 24325839Speter memset (&log_data, 0, sizeof log_data); 24466525Speter prl = &log_data.revlist; 24517721Speter 24626065Speter optind = 0; 247165111Sobrien while ((c = getopt (argc, argv, "+bd:hlNnSRr::s:tw::")) != -1) 24825839Speter { 24925839Speter switch (c) 25025839Speter { 25125839Speter case 'b': 25225839Speter log_data.default_branch = 1; 25325839Speter break; 25425839Speter case 'd': 25525839Speter log_parse_date (&log_data, optarg); 25625839Speter break; 25725839Speter case 'h': 25825839Speter log_data.header = 1; 25925839Speter break; 26025839Speter case 'l': 26125839Speter local = 1; 26225839Speter break; 26325839Speter case 'N': 26425839Speter log_data.notags = 1; 26525839Speter break; 266165111Sobrien case 'n': 267165111Sobrien log_data.notags = 0; 268165111Sobrien break; 269102840Speter case 'S': 270102840Speter log_data.sup_header = 1; 271102840Speter break; 27225839Speter case 'R': 27325839Speter log_data.nameonly = 1; 27425839Speter break; 27525839Speter case 'r': 27666525Speter *prl = log_parse_revlist (optarg); 27766525Speter prl = &(*prl)->next; 27825839Speter break; 27925839Speter case 's': 28025839Speter log_parse_list (&log_data.statelist, optarg); 28125839Speter break; 28225839Speter case 't': 28325839Speter log_data.long_header = 1; 28425839Speter break; 28525839Speter case 'w': 28625839Speter if (optarg != NULL) 28725839Speter log_parse_list (&log_data.authorlist, optarg); 28825839Speter else 28966525Speter log_parse_list (&log_data.authorlist, "@@MYSELF"); 29025839Speter break; 29125839Speter case '?': 29225839Speter default: 29325839Speter usage (log_usage); 29425839Speter break; 29525839Speter } 29625839Speter } 29781404Speter argc -= optind; 29881404Speter argv += optind; 29925839Speter 30017721Speter wrap_setup (); 30117721Speter 30217721Speter#ifdef CLIENT_SUPPORT 30381404Speter if (current_parsed_root->isremote) 30425839Speter { 30566525Speter struct datelist *p; 30666525Speter struct option_revlist *rp; 30766525Speter char datetmp[MAXDATELEN]; 30825839Speter 30917721Speter /* We're the local client. Fire up the remote server. */ 31017721Speter start_server (); 31181404Speter 31281404Speter if (is_rlog && !supported_request ("rlog")) 31381404Speter error (1, 0, "server does not support rlog"); 31481404Speter 31517721Speter ign_setup (); 31617721Speter 31766525Speter if (log_data.default_branch) 31866525Speter send_arg ("-b"); 31917721Speter 32066525Speter while (log_data.datelist != NULL) 32166525Speter { 32266525Speter p = log_data.datelist; 32366525Speter log_data.datelist = p->next; 324175273Sobrien assert (p->start != NULL && p->end != NULL); 32566525Speter send_to_server ("Argument -d\012", 0); 32666525Speter send_to_server ("Argument ", 0); 32766525Speter date_to_internet (datetmp, p->start); 32866525Speter send_to_server (datetmp, 0); 32966525Speter if (p->inclusive) 33066525Speter send_to_server ("<=", 0); 33166525Speter else 33266525Speter send_to_server ("<", 0); 33366525Speter date_to_internet (datetmp, p->end); 33466525Speter send_to_server (datetmp, 0); 33566525Speter send_to_server ("\012", 0); 336175273Sobrien free (p->start); 337175273Sobrien free (p->end); 33866525Speter free (p); 33966525Speter } 34066525Speter while (log_data.singledatelist != NULL) 34166525Speter { 34266525Speter p = log_data.singledatelist; 34366525Speter log_data.singledatelist = p->next; 344175273Sobrien assert (p->end != NULL); 34566525Speter send_to_server ("Argument -d\012", 0); 34666525Speter send_to_server ("Argument ", 0); 34766525Speter date_to_internet (datetmp, p->end); 34866525Speter send_to_server (datetmp, 0); 34966525Speter send_to_server ("\012", 0); 350175273Sobrien free (p->end); 35166525Speter free (p); 35266525Speter } 35366525Speter 35466525Speter if (log_data.header) 35566525Speter send_arg ("-h"); 35666525Speter if (local) 35766525Speter send_arg("-l"); 35866525Speter if (log_data.notags) 35966525Speter send_arg("-N"); 360102840Speter if (log_data.sup_header) 361102840Speter send_arg("-S"); 36266525Speter if (log_data.nameonly) 36366525Speter send_arg("-R"); 36466525Speter if (log_data.long_header) 36566525Speter send_arg("-t"); 36617721Speter 36766525Speter while (log_data.revlist != NULL) 36866525Speter { 36966525Speter rp = log_data.revlist; 37066525Speter log_data.revlist = rp->next; 37166525Speter send_to_server ("Argument -r", 0); 37266525Speter if (rp->branchhead) 37366525Speter { 37466525Speter if (rp->first != NULL) 37566525Speter send_to_server (rp->first, 0); 37666525Speter send_to_server (".", 1); 37766525Speter } 37866525Speter else 37966525Speter { 38066525Speter if (rp->first != NULL) 38166525Speter send_to_server (rp->first, 0); 38266525Speter send_to_server (":", 1); 38381404Speter if (!rp->inclusive) 38481404Speter send_to_server (":", 1); 38566525Speter if (rp->last != NULL) 38666525Speter send_to_server (rp->last, 0); 38766525Speter } 38866525Speter send_to_server ("\012", 0); 38966525Speter if (rp->first) 39066525Speter free (rp->first); 39166525Speter if (rp->last) 39266525Speter free (rp->last); 39366525Speter free (rp); 39466525Speter } 39566525Speter send_arg_list ("-s", log_data.statelist); 39666525Speter dellist (&log_data.statelist); 39766525Speter send_arg_list ("-w", log_data.authorlist); 39866525Speter dellist (&log_data.authorlist); 399107484Speter send_arg ("--"); 40066525Speter 40181404Speter if (is_rlog) 40281404Speter { 40381404Speter int i; 40481404Speter for (i = 0; i < argc; i++) 40581404Speter send_arg (argv[i]); 40681404Speter send_to_server ("rlog\012", 0); 40781404Speter } 40881404Speter else 40981404Speter { 41081404Speter send_files (argc, argv, local, 0, SEND_NO_CONTENTS); 41181404Speter send_file_names (argc, argv, SEND_EXPAND_WILD); 41281404Speter send_to_server ("log\012", 0); 41381404Speter } 41417721Speter err = get_responses_and_close (); 41517721Speter return err; 41617721Speter } 41717721Speter#endif 41817721Speter 41966525Speter /* OK, now that we know we are local/server, we can resolve @@MYSELF 42066525Speter into our user name. */ 42166525Speter if (findnode (log_data.authorlist, "@@MYSELF") != NULL) 42266525Speter log_parse_list (&log_data.authorlist, getcaller ()); 42366525Speter 42481404Speter if (is_rlog) 42581404Speter { 42681404Speter DBM *db; 42781404Speter int i; 42881404Speter db = open_module (); 42981404Speter for (i = 0; i < argc; i++) 43081404Speter { 43181404Speter err += do_module (db, argv[i], MISC, "Logging", rlog_proc, 432102840Speter (char *) NULL, 0, local, 0, 0, (char *) NULL); 43381404Speter } 43481404Speter close_module (db); 43581404Speter } 43681404Speter else 43781404Speter { 43881404Speter err = rlog_proc (argc + 1, argv - 1, (char *) NULL, 439102840Speter (char *) NULL, (char *) NULL, 0, local, (char *) NULL, 44081404Speter (char *) NULL); 44181404Speter } 44266525Speter 44366525Speter while (log_data.revlist) 44466525Speter { 44566525Speter struct option_revlist *rl = log_data.revlist->next; 44666525Speter if (log_data.revlist->first) 44766525Speter free (log_data.revlist->first); 44866525Speter if (log_data.revlist->last) 44966525Speter free (log_data.revlist->last); 45066525Speter free (log_data.revlist); 45166525Speter log_data.revlist = rl; 45266525Speter } 45366525Speter while (log_data.datelist) 45466525Speter { 45566525Speter struct datelist *nd = log_data.datelist->next; 45666525Speter if (log_data.datelist->start) 45766525Speter free (log_data.datelist->start); 45866525Speter if (log_data.datelist->end) 45966525Speter free (log_data.datelist->end); 46066525Speter free (log_data.datelist); 46166525Speter log_data.datelist = nd; 46266525Speter } 46366525Speter while (log_data.singledatelist) 46466525Speter { 46566525Speter struct datelist *nd = log_data.singledatelist->next; 46666525Speter if (log_data.singledatelist->start) 46766525Speter free (log_data.singledatelist->start); 46866525Speter if (log_data.singledatelist->end) 46966525Speter free (log_data.singledatelist->end); 47066525Speter free (log_data.singledatelist); 47166525Speter log_data.singledatelist = nd; 47266525Speter } 47366525Speter dellist (&log_data.statelist); 47466525Speter dellist (&log_data.authorlist); 47566525Speter 47617721Speter return (err); 47717721Speter} 47817721Speter 47981404Speter 48081404Speterstatic int 48181404Speterrlog_proc (argc, argv, xwhere, mwhere, mfile, shorten, local, mname, msg) 48281404Speter int argc; 48381404Speter char **argv; 48481404Speter char *xwhere; 48581404Speter char *mwhere; 48681404Speter char *mfile; 48781404Speter int shorten; 48881404Speter int local; 48981404Speter char *mname; 49081404Speter char *msg; 49181404Speter{ 49281404Speter /* Begin section which is identical to patch_proc--should this 49381404Speter be abstracted out somehow? */ 49481404Speter char *myargv[2]; 49581404Speter int err = 0; 49681404Speter int which; 49781404Speter char *repository; 49881404Speter char *where; 49981404Speter 50081404Speter if (is_rlog) 50181404Speter { 502128266Speter repository = xmalloc (strlen (current_parsed_root->directory) 503128266Speter + strlen (argv[0]) 50481404Speter + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2); 505128266Speter (void)sprintf (repository, "%s/%s", 506128266Speter current_parsed_root->directory, argv[0]); 507128266Speter where = xmalloc (strlen (argv[0]) 508128266Speter + (mfile == NULL ? 0 : strlen (mfile) + 1) 50981404Speter + 1); 51081404Speter (void) strcpy (where, argv[0]); 51181404Speter 512128266Speter /* If mfile isn't null, we need to set up to do only part of theu 513128266Speter * module. 514128266Speter */ 51581404Speter if (mfile != NULL) 51681404Speter { 51781404Speter char *cp; 51881404Speter char *path; 51981404Speter 520128266Speter /* If the portion of the module is a path, put the dir part on 521128266Speter * repos. 522128266Speter */ 52381404Speter if ((cp = strrchr (mfile, '/')) != NULL) 52481404Speter { 52581404Speter *cp = '\0'; 526128266Speter (void)strcat (repository, "/"); 527128266Speter (void)strcat (repository, mfile); 528128266Speter (void)strcat (where, "/"); 529128266Speter (void)strcat (where, mfile); 53081404Speter mfile = cp + 1; 53181404Speter } 53281404Speter 53381404Speter /* take care of the rest */ 53481404Speter path = xmalloc (strlen (repository) + strlen (mfile) + 5); 535128266Speter (void)sprintf (path, "%s/%s", repository, mfile); 53681404Speter if (isdir (path)) 53781404Speter { 53881404Speter /* directory means repository gets the dir tacked on */ 539128266Speter (void)strcpy (repository, path); 540128266Speter (void)strcat (where, "/"); 541128266Speter (void)strcat (where, mfile); 54281404Speter } 54381404Speter else 54481404Speter { 54581404Speter myargv[0] = argv[0]; 54681404Speter myargv[1] = mfile; 54781404Speter argc = 2; 54881404Speter argv = myargv; 54981404Speter } 55081404Speter free (path); 55181404Speter } 55281404Speter 55381404Speter /* cd to the starting repository */ 554128266Speter if (CVS_CHDIR (repository) < 0) 55581404Speter { 55681404Speter error (0, errno, "cannot chdir to %s", repository); 55781404Speter free (repository); 558128266Speter free (where); 559128266Speter return 1; 56081404Speter } 56181404Speter /* End section which is identical to patch_proc. */ 56281404Speter 56381404Speter which = W_REPOS | W_ATTIC; 56481404Speter } 56581404Speter else 56681404Speter { 567128266Speter repository = NULL; 56881404Speter where = NULL; 56981404Speter which = W_LOCAL | W_REPOS | W_ATTIC; 57081404Speter } 57181404Speter 57281404Speter err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc, 57381404Speter (DIRLEAVEPROC) NULL, (void *) &log_data, 574109655Speter argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ, 575128266Speter where, 1, repository); 576128266Speter 577128266Speter if (!(which & W_LOCAL)) free (repository); 578128266Speter if (where) free (where); 579128266Speter 58081404Speter return err; 58181404Speter} 58281404Speter 58381404Speter 584128266Speter 58525839Speter/* 58625839Speter * Parse a revision list specification. 58725839Speter */ 58825839Speterstatic struct option_revlist * 58925839Speterlog_parse_revlist (argstring) 59025839Speter const char *argstring; 59125839Speter{ 59266525Speter char *orig_copy, *copy; 59325839Speter struct option_revlist *ret, **pr; 59425839Speter 59525839Speter /* Unfortunately, rlog accepts -r without an argument to mean that 59625839Speter latest revision on the default branch, so we must support that 59725839Speter for compatibility. */ 59825839Speter if (argstring == NULL) 59966525Speter argstring = ""; 60025839Speter 60125839Speter ret = NULL; 60225839Speter pr = &ret; 60325839Speter 60425839Speter /* Copy the argument into memory so that we can change it. We 60525839Speter don't want to change the argument because, at least as of this 60666525Speter writing, we will use it if we send the arguments to the server. */ 60766525Speter orig_copy = copy = xstrdup (argstring); 60825839Speter while (copy != NULL) 60925839Speter { 61025839Speter char *comma; 61125839Speter struct option_revlist *r; 61225839Speter 61325839Speter comma = strchr (copy, ','); 61425839Speter if (comma != NULL) 61525839Speter *comma++ = '\0'; 61625839Speter 61725839Speter r = (struct option_revlist *) xmalloc (sizeof *r); 61825839Speter r->next = NULL; 61966525Speter r->first = copy; 62066525Speter r->branchhead = 0; 62166525Speter r->last = strchr (copy, ':'); 62266525Speter if (r->last != NULL) 62381404Speter { 62466525Speter *r->last++ = '\0'; 62581404Speter r->inclusive = (*r->last != ':'); 62681404Speter if (!r->inclusive) 62781404Speter r->last++; 62881404Speter } 62925839Speter else 63025839Speter { 63166525Speter r->last = r->first; 63281404Speter r->inclusive = 1; 63366525Speter if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.') 63466525Speter { 63566525Speter r->branchhead = 1; 63666525Speter r->first[strlen (r->first) - 1] = '\0'; 63766525Speter } 63825839Speter } 63925839Speter 64066525Speter if (*r->first == '\0') 64166525Speter r->first = NULL; 64266525Speter if (*r->last == '\0') 64366525Speter r->last = NULL; 64466525Speter 64566525Speter if (r->first != NULL) 64666525Speter r->first = xstrdup (r->first); 64766525Speter if (r->last != NULL) 64866525Speter r->last = xstrdup (r->last); 64966525Speter 65025839Speter *pr = r; 65125839Speter pr = &r->next; 65225839Speter 65325839Speter copy = comma; 65425839Speter } 65525839Speter 65666525Speter free (orig_copy); 65725839Speter return ret; 65825839Speter} 65925839Speter 66017721Speter/* 66125839Speter * Parse a date specification. 66225839Speter */ 66325839Speterstatic void 66425839Speterlog_parse_date (log_data, argstring) 66525839Speter struct log_data *log_data; 66625839Speter const char *argstring; 66725839Speter{ 66825839Speter char *orig_copy, *copy; 66925839Speter 67025839Speter /* Copy the argument into memory so that we can change it. We 67125839Speter don't want to change the argument because, at least as of this 67225839Speter writing, we will use it if we send the arguments to the server. */ 67366525Speter orig_copy = copy = xstrdup (argstring); 67425839Speter while (copy != NULL) 67525839Speter { 67625839Speter struct datelist *nd, **pd; 67725839Speter char *cpend, *cp, *ds, *de; 67825839Speter 67925839Speter nd = (struct datelist *) xmalloc (sizeof *nd); 68025839Speter 68125839Speter cpend = strchr (copy, ';'); 68225839Speter if (cpend != NULL) 68325839Speter *cpend++ = '\0'; 68425839Speter 68525839Speter pd = &log_data->datelist; 68625839Speter nd->inclusive = 0; 68725839Speter 68825839Speter if ((cp = strchr (copy, '>')) != NULL) 68925839Speter { 69025839Speter *cp++ = '\0'; 69125839Speter if (*cp == '=') 69225839Speter { 69325839Speter ++cp; 69425839Speter nd->inclusive = 1; 69525839Speter } 69625839Speter ds = cp; 69725839Speter de = copy; 69825839Speter } 69925839Speter else if ((cp = strchr (copy, '<')) != NULL) 70025839Speter { 70125839Speter *cp++ = '\0'; 70225839Speter if (*cp == '=') 70325839Speter { 70425839Speter ++cp; 70525839Speter nd->inclusive = 1; 70625839Speter } 70725839Speter ds = copy; 70825839Speter de = cp; 70925839Speter } 71025839Speter else 71125839Speter { 71225839Speter ds = NULL; 71325839Speter de = copy; 71425839Speter pd = &log_data->singledatelist; 71525839Speter } 71625839Speter 71725839Speter if (ds == NULL) 71825839Speter nd->start = NULL; 71925839Speter else if (*ds != '\0') 72025839Speter nd->start = Make_Date (ds); 72125839Speter else 72225839Speter { 72325839Speter /* 1970 was the beginning of time, as far as get_date and 72425839Speter Make_Date are concerned. FIXME: That is true only if time_t 72525839Speter is a POSIX-style time and there is nothing in ANSI that 72625839Speter mandates that. It would be cleaner to set a flag saying 72725839Speter whether or not there is a start date. */ 72825839Speter nd->start = Make_Date ("1/1/1970 UTC"); 72925839Speter } 73025839Speter 73125839Speter if (*de != '\0') 73225839Speter nd->end = Make_Date (de); 73325839Speter else 73425839Speter { 73525839Speter /* We want to set the end date to some time sufficiently far 73625839Speter in the future to pick up all revisions that have been 73725839Speter created since the specified date and the time `cvs log' 73825839Speter completes. FIXME: The date in question only makes sense 73925839Speter if time_t is a POSIX-style time and it is 32 bits 74025839Speter and signed. We should instead be setting a flag saying 74125839Speter whether or not there is an end date. Note that using 74225839Speter something like "next week" would break the testsuite (and, 74325839Speter perhaps less importantly, loses if the clock is set grossly 74425839Speter wrong). */ 74525839Speter nd->end = Make_Date ("2038-01-01"); 74625839Speter } 74725839Speter 74825839Speter nd->next = *pd; 74925839Speter *pd = nd; 75025839Speter 75125839Speter copy = cpend; 75225839Speter } 75325839Speter 75425839Speter free (orig_copy); 75525839Speter} 75625839Speter 75725839Speter/* 75825839Speter * Parse a comma separated list of items, and add each one to *PLIST. 75925839Speter */ 76025839Speterstatic void 76125839Speterlog_parse_list (plist, argstring) 76225839Speter List **plist; 76325839Speter const char *argstring; 76425839Speter{ 76525839Speter while (1) 76625839Speter { 76725839Speter Node *p; 76825839Speter char *cp; 76925839Speter 77025839Speter p = getnode (); 77125839Speter 77225839Speter cp = strchr (argstring, ','); 77325839Speter if (cp == NULL) 77425839Speter p->key = xstrdup (argstring); 77525839Speter else 77625839Speter { 77725839Speter size_t len; 77825839Speter 77925839Speter len = cp - argstring; 78025839Speter p->key = xmalloc (len + 1); 78125839Speter strncpy (p->key, argstring, len); 782107484Speter p->key[len] = '\0'; 78325839Speter } 78425839Speter 78525839Speter if (*plist == NULL) 78625839Speter *plist = getlist (); 78725839Speter if (addnode (*plist, p) != 0) 78825839Speter freenode (p); 78925839Speter 79025839Speter if (cp == NULL) 79125839Speter break; 79225839Speter 79325839Speter argstring = cp + 1; 79425839Speter } 79525839Speter} 79625839Speter 79732785Speterstatic int printlock_proc PROTO ((Node *, void *)); 79832785Speter 79932785Speterstatic int 80032785Speterprintlock_proc (lock, foo) 80132785Speter Node *lock; 80232785Speter void *foo; 80332785Speter{ 80432785Speter cvs_output ("\n\t", 2); 80532785Speter cvs_output (lock->data, 0); 80632785Speter cvs_output (": ", 2); 80732785Speter cvs_output (lock->key, 0); 80832785Speter return 0; 80932785Speter} 81032785Speter 811128266Speter 812128266Speter 81325839Speter/* 81417721Speter * Do an rlog on a file 81517721Speter */ 81617721Speterstatic int 81725839Speterlog_fileproc (callerdat, finfo) 81825839Speter void *callerdat; 81917721Speter struct file_info *finfo; 82017721Speter{ 82125839Speter struct log_data *log_data = (struct log_data *) callerdat; 82217721Speter Node *p; 823175273Sobrien char *baserev; 824102840Speter int selrev = -1; 82517721Speter RCSNode *rcsfile; 82625839Speter char buf[50]; 827128266Speter struct revlist *revlist = NULL; 82825839Speter struct log_data_and_rcs log_data_and_rcs; 82917721Speter 830175273Sobrien rcsfile = finfo->rcs; 831175273Sobrien p = findnode (finfo->entries, finfo->file); 832175273Sobrien if (p != NULL) 83317721Speter { 834175273Sobrien Entnode *e = p->data; 835175273Sobrien baserev = e->version; 836175273Sobrien if (baserev[0] == '-') ++baserev; 837175273Sobrien } 838175273Sobrien else 839175273Sobrien baserev = NULL; 840175273Sobrien 841175273Sobrien if (rcsfile == NULL) 842175273Sobrien { 84317721Speter /* no rcs file. What *do* we know about this file? */ 844175273Sobrien if (baserev != NULL) 84517721Speter { 846175273Sobrien if (baserev[0] == '0' && baserev[1] == '\0') 84717721Speter { 84817721Speter if (!really_quiet) 84917721Speter error (0, 0, "%s has been added, but not committed", 85017721Speter finfo->file); 851128266Speter return 0; 85217721Speter } 85317721Speter } 85417721Speter 85517721Speter if (!really_quiet) 85617721Speter error (0, 0, "nothing known about %s", finfo->file); 85717721Speter 858128266Speter return 1; 85917721Speter } 86017721Speter 861102840Speter if (log_data->sup_header || !log_data->nameonly) 862102840Speter { 863102840Speter 864102840Speter /* We will need all the information in the RCS file. */ 865102840Speter RCS_fully_parse (rcsfile); 866102840Speter 867102840Speter /* Turn any symbolic revisions in the revision list into numeric 868102840Speter revisions. */ 869175273Sobrien revlist = log_expand_revlist (rcsfile, baserev, log_data->revlist, 870102840Speter log_data->default_branch); 871128266Speter if (log_data->sup_header 872128266Speter || (!log_data->header && !log_data->long_header)) 873102840Speter { 874102840Speter log_data_and_rcs.log_data = log_data; 875102840Speter log_data_and_rcs.revlist = revlist; 876102840Speter log_data_and_rcs.rcs = rcsfile; 877102840Speter 878102840Speter /* If any single dates were specified, we need to identify the 879102840Speter revisions they select. Each one selects the single 880102840Speter revision, which is otherwise selected, of that date or 881102840Speter earlier. The log_fix_singledate routine will fill in the 882102840Speter start date for each specific revision. */ 883102840Speter if (log_data->singledatelist != NULL) 884102840Speter walklist (rcsfile->versions, log_fix_singledate, 885128266Speter (void *)&log_data_and_rcs); 886102840Speter 887102840Speter selrev = walklist (rcsfile->versions, log_count_print, 888128266Speter (void *)&log_data_and_rcs); 889128266Speter if (log_data->sup_header && selrev == 0) 890128266Speter { 891128266Speter log_free_revlist (revlist); 892128266Speter return 0; 893128266Speter } 894102840Speter } 895102840Speter 896102840Speter } 897102840Speter 89825839Speter if (log_data->nameonly) 89917721Speter { 90025839Speter cvs_output (rcsfile->path, 0); 90125839Speter cvs_output ("\n", 1); 902128266Speter log_free_revlist (revlist); 90325839Speter return 0; 90417721Speter } 90517721Speter 90625839Speter /* The output here is intended to be exactly compatible with the 90725839Speter output of rlog. I'm not sure whether this code should be here 90825839Speter or in rcs.c; I put it here because it is specific to the log 90925839Speter function, even though it uses information gathered by the 91025839Speter functions in rcs.c. */ 91125839Speter 91225839Speter cvs_output ("\n", 1); 91325839Speter 91425839Speter cvs_output ("RCS file: ", 0); 91525839Speter cvs_output (rcsfile->path, 0); 91625839Speter 91781404Speter if (!is_rlog) 91817721Speter { 91981404Speter cvs_output ("\nWorking file: ", 0); 92081404Speter if (finfo->update_dir[0] != '\0') 92181404Speter { 92281404Speter cvs_output (finfo->update_dir, 0); 92381404Speter cvs_output ("/", 0); 92481404Speter } 92525839Speter cvs_output (finfo->file, 0); 92617721Speter } 92717721Speter 92825839Speter cvs_output ("\nhead:", 0); 92925839Speter if (rcsfile->head != NULL) 93017721Speter { 93125839Speter cvs_output (" ", 1); 93225839Speter cvs_output (rcsfile->head, 0); 93317721Speter } 93425839Speter 93525839Speter cvs_output ("\nbranch:", 0); 93625839Speter if (rcsfile->branch != NULL) 93725839Speter { 93825839Speter cvs_output (" ", 1); 93925839Speter cvs_output (rcsfile->branch, 0); 94025839Speter } 94125839Speter 94225839Speter cvs_output ("\nlocks:", 0); 94332785Speter if (rcsfile->strict_locks) 94432785Speter cvs_output (" strict", 0); 94532785Speter walklist (RCS_getlocks (rcsfile), printlock_proc, NULL); 94625839Speter 94725839Speter cvs_output ("\naccess list:", 0); 94832785Speter if (rcsfile->access != NULL) 94925839Speter { 95032785Speter const char *cp; 95132785Speter 95232785Speter cp = rcsfile->access; 95332785Speter while (*cp != '\0') 95425839Speter { 95525839Speter const char *cp2; 95625839Speter 95725839Speter cvs_output ("\n\t", 2); 95825839Speter cp2 = cp; 959128266Speter while (!isspace ((unsigned char) *cp2) && *cp2 != '\0') 96025839Speter ++cp2; 96125839Speter cvs_output (cp, cp2 - cp); 96225839Speter cp = cp2; 96354427Speter while (isspace ((unsigned char) *cp) && *cp != '\0') 96425839Speter ++cp; 96525839Speter } 96625839Speter } 96725839Speter 968128266Speter if (!log_data->notags) 96925839Speter { 97025839Speter List *syms; 97125839Speter 97225839Speter cvs_output ("\nsymbolic names:", 0); 97325839Speter syms = RCS_symbols (rcsfile); 97425839Speter walklist (syms, log_symbol, NULL); 97525839Speter } 97625839Speter 97725839Speter cvs_output ("\nkeyword substitution: ", 0); 97825839Speter if (rcsfile->expand == NULL) 97925839Speter cvs_output ("kv", 2); 98025839Speter else 98125839Speter cvs_output (rcsfile->expand, 0); 98225839Speter 98325839Speter cvs_output ("\ntotal revisions: ", 0); 98425839Speter sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL)); 98525839Speter cvs_output (buf, 0); 98625839Speter 987102840Speter if (selrev >= 0) 98825839Speter { 98925839Speter cvs_output (";\tselected revisions: ", 0); 990102840Speter sprintf (buf, "%d", selrev); 99125839Speter cvs_output (buf, 0); 99225839Speter } 99325839Speter 99425839Speter cvs_output ("\n", 1); 99525839Speter 996128266Speter if (!log_data->header || log_data->long_header) 99725839Speter { 99825839Speter cvs_output ("description:\n", 0); 99932785Speter if (rcsfile->desc != NULL) 100032785Speter cvs_output (rcsfile->desc, 0); 100125839Speter } 100225839Speter 1003128266Speter if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL) 100425839Speter { 100525839Speter p = findnode (rcsfile->versions, rcsfile->head); 100625839Speter if (p == NULL) 100725839Speter error (1, 0, "can not find head revision in `%s'", 100825839Speter finfo->fullname); 100925839Speter while (p != NULL) 101025839Speter { 1011128266Speter RCSVers *vers = p->data; 101225839Speter 101325839Speter log_version (log_data, revlist, rcsfile, vers, 1); 101425839Speter if (vers->next == NULL) 101525839Speter p = NULL; 101625839Speter else 101725839Speter { 101825839Speter p = findnode (rcsfile->versions, vers->next); 101925839Speter if (p == NULL) 102025839Speter error (1, 0, "can not find next revision `%s' in `%s'", 102125839Speter vers->next, finfo->fullname); 102225839Speter } 102325839Speter } 102425839Speter 102525839Speter log_tree (log_data, revlist, rcsfile, rcsfile->head); 102625839Speter } 102725839Speter 102825839Speter cvs_output("\ 102925839Speter=============================================================================\n", 103025839Speter 0); 103125839Speter 103225839Speter /* Free up the new revlist and restore the old one. */ 103325839Speter log_free_revlist (revlist); 103425839Speter 103525839Speter /* If singledatelist is not NULL, free up the start dates we added 103625839Speter to it. */ 103725839Speter if (log_data->singledatelist != NULL) 103825839Speter { 103925839Speter struct datelist *d; 104025839Speter 104125839Speter for (d = log_data->singledatelist; d != NULL; d = d->next) 104225839Speter { 104325839Speter if (d->start != NULL) 104425839Speter free (d->start); 104525839Speter d->start = NULL; 104625839Speter } 104725839Speter } 104825839Speter 104925839Speter return 0; 105017721Speter} 105117721Speter 1052128266Speter 1053128266Speter 105417721Speter/* 105525839Speter * Fix up a revision list in order to compare it against versions. 105625839Speter * Expand any symbolic revisions. 105725839Speter */ 105825839Speterstatic struct revlist * 1059175273Sobrienlog_expand_revlist (rcs, baserev, revlist, default_branch) 106025839Speter RCSNode *rcs; 1061175273Sobrien char *baserev; 106225839Speter struct option_revlist *revlist; 106325839Speter int default_branch; 106425839Speter{ 106525839Speter struct option_revlist *r; 106625839Speter struct revlist *ret, **pr; 106725839Speter 106825839Speter ret = NULL; 106925839Speter pr = &ret; 107025839Speter for (r = revlist; r != NULL; r = r->next) 107125839Speter { 107225839Speter struct revlist *nr; 107325839Speter 107425839Speter nr = (struct revlist *) xmalloc (sizeof *nr); 107581404Speter nr->inclusive = r->inclusive; 107625839Speter 107725839Speter if (r->first == NULL && r->last == NULL) 107825839Speter { 107925839Speter /* If both first and last are NULL, it means that we want 108025839Speter just the head of the default branch, which is RCS_head. */ 108125839Speter nr->first = RCS_head (rcs); 1082175273Sobrien if (!nr->first) 1083175273Sobrien { 1084175273Sobrien if (!really_quiet) 1085175273Sobrien error (0, 0, "No head revision in archive `%s'.", 1086175273Sobrien rcs->path); 1087175273Sobrien nr->last = NULL; 1088175273Sobrien nr->fields = 0; 1089175273Sobrien } 1090175273Sobrien else 1091175273Sobrien { 1092175273Sobrien nr->last = xstrdup (nr->first); 1093175273Sobrien nr->fields = numdots (nr->first) + 1; 1094175273Sobrien } 109525839Speter } 109625839Speter else if (r->branchhead) 109725839Speter { 109825839Speter char *branch; 109925839Speter 1100175273Sobrien assert (r->first != NULL); 1101175273Sobrien 110225839Speter /* Print just the head of the branch. */ 110354427Speter if (isdigit ((unsigned char) r->first[0])) 110425839Speter nr->first = RCS_getbranch (rcs, r->first, 1); 110525839Speter else 110625839Speter { 110725839Speter branch = RCS_whatbranch (rcs, r->first); 110825839Speter if (branch == NULL) 1109102840Speter nr->first = NULL; 1110102840Speter else 111125839Speter { 1112102840Speter nr->first = RCS_getbranch (rcs, branch, 1); 1113102840Speter free (branch); 111425839Speter } 111525839Speter } 1116175273Sobrien if (!nr->first) 111725839Speter { 1118175273Sobrien if (!really_quiet) 1119175273Sobrien error (0, 0, "warning: no branch `%s' in `%s'", 1120175273Sobrien r->first, rcs->path); 1121102840Speter nr->last = NULL; 1122102840Speter nr->fields = 0; 112325839Speter } 1124102840Speter else 1125102840Speter { 1126102840Speter nr->last = xstrdup (nr->first); 1127102840Speter nr->fields = numdots (nr->first) + 1; 1128102840Speter } 112925839Speter } 113025839Speter else 113125839Speter { 113254427Speter if (r->first == NULL || isdigit ((unsigned char) r->first[0])) 113325839Speter nr->first = xstrdup (r->first); 113425839Speter else 113525839Speter { 1136175273Sobrien if (baserev && strcmp (r->first, TAG_BASE) == 0) 1137175273Sobrien nr->first = xstrdup (baserev); 1138175273Sobrien else if (RCS_nodeisbranch (rcs, r->first)) 113925839Speter nr->first = RCS_whatbranch (rcs, r->first); 114025839Speter else 114125839Speter nr->first = RCS_gettag (rcs, r->first, 1, (int *) NULL); 1142130303Speter if (nr->first == NULL && !really_quiet) 114325839Speter { 114425839Speter error (0, 0, "warning: no revision `%s' in `%s'", 114525839Speter r->first, rcs->path); 114625839Speter } 114725839Speter } 114825839Speter 1149102840Speter if (r->last == r->first || (r->last != NULL && r->first != NULL && 1150102840Speter strcmp (r->last, r->first) == 0)) 115125839Speter nr->last = xstrdup (nr->first); 115254427Speter else if (r->last == NULL || isdigit ((unsigned char) r->last[0])) 115325839Speter nr->last = xstrdup (r->last); 115425839Speter else 115525839Speter { 1156175273Sobrien if (baserev && strcmp (r->last, TAG_BASE) == 0) 1157175273Sobrien nr->last = xstrdup (baserev); 1158175273Sobrien else if (RCS_nodeisbranch (rcs, r->last)) 115925839Speter nr->last = RCS_whatbranch (rcs, r->last); 116025839Speter else 116125839Speter nr->last = RCS_gettag (rcs, r->last, 1, (int *) NULL); 1162130303Speter if (nr->last == NULL && !really_quiet) 116325839Speter { 116425839Speter error (0, 0, "warning: no revision `%s' in `%s'", 116525839Speter r->last, rcs->path); 116625839Speter } 116725839Speter } 116825839Speter 116925839Speter /* Process the revision numbers the same way that rlog 117025839Speter does. This code is a bit cryptic for my tastes, but 117125839Speter keeping the same implementation as rlog ensures a 117225839Speter certain degree of compatibility. */ 1173128266Speter if (r->first == NULL && nr->last != NULL) 117425839Speter { 1175128266Speter nr->fields = numdots (nr->last) + 1; 1176128266Speter if (nr->fields < 2) 1177128266Speter nr->first = xstrdup (".0"); 117825839Speter else 117925839Speter { 1180128266Speter char *cp; 118125839Speter 1182128266Speter nr->first = xstrdup (nr->last); 1183128266Speter cp = strrchr (nr->first, '.'); 1184175273Sobrien assert (cp); 1185128266Speter strcpy (cp + 1, "0"); 118625839Speter } 118725839Speter } 1188128266Speter else if (r->last == NULL && nr->first != NULL) 118925839Speter { 119025839Speter nr->fields = numdots (nr->first) + 1; 119125839Speter nr->last = xstrdup (nr->first); 119225839Speter if (nr->fields < 2) 119325839Speter nr->last[0] = '\0'; 119425839Speter else 119525839Speter { 119625839Speter char *cp; 119725839Speter 119825839Speter cp = strrchr (nr->last, '.'); 1199175273Sobrien assert (cp); 120025839Speter *cp = '\0'; 120125839Speter } 120225839Speter } 1203107484Speter else if (nr->first == NULL || nr->last == NULL) 1204107484Speter nr->fields = 0; 1205107484Speter else if (strcmp (nr->first, nr->last) == 0) 1206107484Speter nr->fields = numdots (nr->last) + 1; 1207107484Speter else 120825839Speter { 1209107484Speter int ord; 1210107484Speter int dots1 = numdots (nr->first); 1211107484Speter int dots2 = numdots (nr->last); 1212107484Speter if (dots1 > dots2 || (dots1 == dots2 && 1213107484Speter version_compare (nr->first, nr->last, dots1 + 1) > 0)) 121425839Speter { 1215107484Speter char *tmp = nr->first; 1216107484Speter nr->first = nr->last; 1217107484Speter nr->last = tmp; 1218107484Speter nr->fields = dots2 + 1; 1219107484Speter dots2 = dots1; 1220107484Speter dots1 = nr->fields - 1; 1221107484Speter } 1222107484Speter else 1223107484Speter nr->fields = dots1 + 1; 1224107484Speter dots1 += (nr->fields & 1); 1225107484Speter ord = version_compare (nr->first, nr->last, dots1); 1226107484Speter if (ord > 0 || (nr->fields > 2 && ord < 0)) 1227107484Speter { 122825839Speter error (0, 0, 122925839Speter "invalid branch or revision pair %s:%s in `%s'", 123025839Speter r->first, r->last, rcs->path); 123125839Speter free (nr->first); 1232102840Speter nr->first = NULL; 123325839Speter free (nr->last); 1234102840Speter nr->last = NULL; 1235102840Speter nr->fields = 0; 123625839Speter } 1237107484Speter else 123825839Speter { 1239107484Speter if (nr->fields <= dots2 && (nr->fields & 1)) 1240107484Speter { 1241107484Speter char *p = xmalloc (strlen (nr->first) + 3); 1242107484Speter strcpy (p, nr->first); 1243107484Speter strcat (p, ".0"); 1244107484Speter free (nr->first); 1245107484Speter nr->first = p; 1246107484Speter ++nr->fields; 1247107484Speter } 1248107484Speter while (nr->fields <= dots2) 1249107484Speter { 1250107484Speter char *p; 1251107484Speter int i; 125225839Speter 1253107484Speter nr->next = NULL; 1254107484Speter *pr = nr; 1255107484Speter nr = (struct revlist *) xmalloc (sizeof *nr); 1256107484Speter nr->inclusive = 1; 1257107484Speter nr->first = xstrdup ((*pr)->last); 1258107484Speter nr->last = xstrdup ((*pr)->last); 1259107484Speter nr->fields = (*pr)->fields; 1260107484Speter p = (*pr)->last; 1261107484Speter for (i = 0; i < nr->fields; i++) 1262107484Speter p = strchr (p, '.') + 1; 1263107484Speter p[-1] = '\0'; 1264107484Speter p = strchr (nr->first + (p - (*pr)->last), '.'); 1265107484Speter if (p != NULL) 1266107484Speter { 1267107484Speter *++p = '0'; 1268107484Speter *++p = '\0'; 1269107484Speter nr->fields += 2; 1270107484Speter } 1271107484Speter else 1272107484Speter ++nr->fields; 1273107484Speter pr = &(*pr)->next; 1274107484Speter } 127525839Speter } 127625839Speter } 127725839Speter } 127825839Speter 127925839Speter nr->next = NULL; 128025839Speter *pr = nr; 128125839Speter pr = &nr->next; 128225839Speter } 128325839Speter 128425839Speter /* If the default branch was requested, add a revlist entry for 128525839Speter it. This is how rlog handles this option. */ 128625839Speter if (default_branch 128725839Speter && (rcs->head != NULL || rcs->branch != NULL)) 128825839Speter { 128925839Speter struct revlist *nr; 129025839Speter 129125839Speter nr = (struct revlist *) xmalloc (sizeof *nr); 129225839Speter if (rcs->branch != NULL) 129325839Speter nr->first = xstrdup (rcs->branch); 129425839Speter else 129525839Speter { 129625839Speter char *cp; 129725839Speter 129825839Speter nr->first = xstrdup (rcs->head); 1299175273Sobrien assert (nr->first); 130025839Speter cp = strrchr (nr->first, '.'); 1301175273Sobrien assert (cp); 130225839Speter *cp = '\0'; 130325839Speter } 130425839Speter nr->last = xstrdup (nr->first); 130525839Speter nr->fields = numdots (nr->first) + 1; 130681404Speter nr->inclusive = 1; 130725839Speter 130825839Speter nr->next = NULL; 130925839Speter *pr = nr; 131025839Speter } 131125839Speter 131225839Speter return ret; 131325839Speter} 131425839Speter 131525839Speter/* 131625839Speter * Free a revlist created by log_expand_revlist. 131725839Speter */ 131825839Speterstatic void 131925839Speterlog_free_revlist (revlist) 132025839Speter struct revlist *revlist; 132125839Speter{ 132225839Speter struct revlist *r; 132325839Speter 132425839Speter r = revlist; 132525839Speter while (r != NULL) 132625839Speter { 132725839Speter struct revlist *next; 132825839Speter 132925839Speter if (r->first != NULL) 133025839Speter free (r->first); 133125839Speter if (r->last != NULL) 133225839Speter free (r->last); 133325839Speter next = r->next; 133425839Speter free (r); 133525839Speter r = next; 133625839Speter } 133725839Speter} 133825839Speter 133925839Speter/* 134025839Speter * Return nonzero if a revision should be printed, based on the 134125839Speter * options provided. 134225839Speter */ 134325839Speterstatic int 134425839Speterlog_version_requested (log_data, revlist, rcs, vnode) 134525839Speter struct log_data *log_data; 134625839Speter struct revlist *revlist; 134725839Speter RCSNode *rcs; 134825839Speter RCSVers *vnode; 134925839Speter{ 135025839Speter /* Handle the list of states from the -s option. */ 135125839Speter if (log_data->statelist != NULL 135225839Speter && findnode (log_data->statelist, vnode->state) == NULL) 135325839Speter { 135425839Speter return 0; 135525839Speter } 135625839Speter 135725839Speter /* Handle the list of authors from the -w option. */ 135825839Speter if (log_data->authorlist != NULL) 135925839Speter { 136025839Speter if (vnode->author != NULL 136125839Speter && findnode (log_data->authorlist, vnode->author) == NULL) 136225839Speter { 136325839Speter return 0; 136425839Speter } 136525839Speter } 136625839Speter 136725839Speter /* rlog considers all the -d options together when it decides 136825839Speter whether to print a revision, so we must be compatible. */ 136925839Speter if (log_data->datelist != NULL || log_data->singledatelist != NULL) 137025839Speter { 137125839Speter struct datelist *d; 137225839Speter 137325839Speter for (d = log_data->datelist; d != NULL; d = d->next) 137425839Speter { 137525839Speter int cmp; 137625839Speter 137725839Speter cmp = RCS_datecmp (vnode->date, d->start); 137825839Speter if (cmp > 0 || (cmp == 0 && d->inclusive)) 137925839Speter { 138025839Speter cmp = RCS_datecmp (vnode->date, d->end); 138125839Speter if (cmp < 0 || (cmp == 0 && d->inclusive)) 138225839Speter break; 138325839Speter } 138425839Speter } 138525839Speter 138625839Speter if (d == NULL) 138725839Speter { 138825839Speter /* Look through the list of specific dates. We want to 138925839Speter select the revision with the exact date found in the 139025839Speter start field. The commit code ensures that it is 139125839Speter impossible to check in multiple revisions of a single 139225839Speter file in a single second, so checking the date this way 139325839Speter should never select more than one revision. */ 139425839Speter for (d = log_data->singledatelist; d != NULL; d = d->next) 139525839Speter { 139625839Speter if (d->start != NULL 139725839Speter && RCS_datecmp (vnode->date, d->start) == 0) 139825839Speter { 139925839Speter break; 140025839Speter } 140125839Speter } 140225839Speter 140325839Speter if (d == NULL) 140425839Speter return 0; 140525839Speter } 140625839Speter } 140725839Speter 140825839Speter /* If the -r or -b options were used, REVLIST will be non NULL, 140925839Speter and we print the union of the specified revisions. */ 141025839Speter if (revlist != NULL) 141125839Speter { 141225839Speter char *v; 141325839Speter int vfields; 141425839Speter struct revlist *r; 141525839Speter 141625839Speter /* This code is taken from rlog. */ 141725839Speter v = vnode->version; 141825839Speter vfields = numdots (v) + 1; 141925839Speter for (r = revlist; r != NULL; r = r->next) 142025839Speter { 142181404Speter if (vfields == r->fields + (r->fields & 1) && 1422102840Speter (r->inclusive ? version_compare (v, r->first, r->fields) >= 0 : 1423102840Speter version_compare (v, r->first, r->fields) > 0) 1424102840Speter && version_compare (v, r->last, r->fields) <= 0) 142525839Speter { 142625839Speter return 1; 142725839Speter } 142825839Speter } 142925839Speter 143025839Speter /* If we get here, then the -b and/or the -r option was used, 143125839Speter but did not match this revision, so we reject it. */ 143225839Speter 143325839Speter return 0; 143425839Speter } 143525839Speter 143625839Speter /* By default, we print all revisions. */ 143725839Speter return 1; 143825839Speter} 143925839Speter 1440128266Speter 1441128266Speter 144225839Speter/* 144325839Speter * Output a single symbol. This is called via walklist. 144425839Speter */ 144525839Speter/*ARGSUSED*/ 144625839Speterstatic int 144725839Speterlog_symbol (p, closure) 144825839Speter Node *p; 144925839Speter void *closure; 145025839Speter{ 145125839Speter cvs_output ("\n\t", 2); 145225839Speter cvs_output (p->key, 0); 145325839Speter cvs_output (": ", 2); 145425839Speter cvs_output (p->data, 0); 145525839Speter return 0; 145625839Speter} 145725839Speter 1458128266Speter 1459128266Speter 146025839Speter/* 146125839Speter * Count the number of entries on a list. This is called via walklist. 146225839Speter */ 146325839Speter/*ARGSUSED*/ 146425839Speterstatic int 146525839Speterlog_count (p, closure) 146625839Speter Node *p; 146725839Speter void *closure; 146825839Speter{ 146925839Speter return 1; 147025839Speter} 147125839Speter 1472128266Speter 1473128266Speter 147425839Speter/* 147525839Speter * Sort out a single date specification by narrowing down the date 147625839Speter * until we find the specific selected revision. 147725839Speter */ 147825839Speterstatic int 147925839Speterlog_fix_singledate (p, closure) 148025839Speter Node *p; 148125839Speter void *closure; 148225839Speter{ 148325839Speter struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure; 148425839Speter Node *pv; 148525839Speter RCSVers *vnode; 148625839Speter struct datelist *holdsingle, *holddate; 148725839Speter int requested; 148825839Speter 148925839Speter pv = findnode (data->rcs->versions, p->key); 149025839Speter if (pv == NULL) 149125839Speter error (1, 0, "missing version `%s' in RCS file `%s'", 149225839Speter p->key, data->rcs->path); 1493128266Speter vnode = pv->data; 149425839Speter 149525839Speter /* We are only interested if this revision passes any other tests. 149625839Speter Temporarily clear log_data->singledatelist to avoid confusing 149725839Speter log_version_requested. We also clear log_data->datelist, 149825839Speter because rlog considers all the -d options together. We don't 149925839Speter want to reject a revision because it does not match a date pair 150025839Speter if we are going to select it on the basis of the singledate. */ 150125839Speter holdsingle = data->log_data->singledatelist; 150225839Speter data->log_data->singledatelist = NULL; 150325839Speter holddate = data->log_data->datelist; 150425839Speter data->log_data->datelist = NULL; 150525839Speter requested = log_version_requested (data->log_data, data->revlist, 150625839Speter data->rcs, vnode); 150725839Speter data->log_data->singledatelist = holdsingle; 150825839Speter data->log_data->datelist = holddate; 150925839Speter 151025839Speter if (requested) 151125839Speter { 151225839Speter struct datelist *d; 151325839Speter 151425839Speter /* For each single date, if this revision is before the 151525839Speter specified date, but is closer than the previously selected 151625839Speter revision, select it instead. */ 151725839Speter for (d = data->log_data->singledatelist; d != NULL; d = d->next) 151825839Speter { 151925839Speter if (RCS_datecmp (vnode->date, d->end) <= 0 152025839Speter && (d->start == NULL 152125839Speter || RCS_datecmp (vnode->date, d->start) > 0)) 152225839Speter { 152325839Speter if (d->start != NULL) 152425839Speter free (d->start); 152525839Speter d->start = xstrdup (vnode->date); 152625839Speter } 152725839Speter } 152825839Speter } 152925839Speter 153025839Speter return 0; 153125839Speter} 153225839Speter 1533128266Speter 1534128266Speter 153525839Speter/* 153625839Speter * Count the number of revisions we are going to print. 153725839Speter */ 153825839Speterstatic int 153925839Speterlog_count_print (p, closure) 154025839Speter Node *p; 154125839Speter void *closure; 154225839Speter{ 154325839Speter struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure; 154425839Speter Node *pv; 154525839Speter 154625839Speter pv = findnode (data->rcs->versions, p->key); 154725839Speter if (pv == NULL) 154825839Speter error (1, 0, "missing version `%s' in RCS file `%s'", 154925839Speter p->key, data->rcs->path); 155025839Speter if (log_version_requested (data->log_data, data->revlist, data->rcs, 1551128266Speter pv->data)) 155225839Speter return 1; 155325839Speter else 155425839Speter return 0; 155525839Speter} 155625839Speter 155725839Speter/* 155825839Speter * Print the list of changes, not including the trunk, in reverse 155925839Speter * order for each branch. 156025839Speter */ 156125839Speterstatic void 156225839Speterlog_tree (log_data, revlist, rcs, ver) 156325839Speter struct log_data *log_data; 156425839Speter struct revlist *revlist; 156525839Speter RCSNode *rcs; 156625839Speter const char *ver; 156725839Speter{ 156825839Speter Node *p; 156925839Speter RCSVers *vnode; 157025839Speter 157125839Speter p = findnode (rcs->versions, ver); 157225839Speter if (p == NULL) 157325839Speter error (1, 0, "missing version `%s' in RCS file `%s'", 157425839Speter ver, rcs->path); 1575128266Speter vnode = p->data; 157625839Speter if (vnode->next != NULL) 157725839Speter log_tree (log_data, revlist, rcs, vnode->next); 157825839Speter if (vnode->branches != NULL) 157925839Speter { 158025839Speter Node *head, *branch; 158125839Speter 158225839Speter /* We need to do the branches in reverse order. This breaks 158325839Speter the List abstraction, but so does most of the branch 158425839Speter manipulation in rcs.c. */ 158525839Speter head = vnode->branches->list; 158625839Speter for (branch = head->prev; branch != head; branch = branch->prev) 158725839Speter { 158825839Speter log_abranch (log_data, revlist, rcs, branch->key); 158925839Speter log_tree (log_data, revlist, rcs, branch->key); 159025839Speter } 159125839Speter } 159225839Speter} 159325839Speter 159425839Speter/* 159525839Speter * Log the changes for a branch, in reverse order. 159625839Speter */ 159725839Speterstatic void 159825839Speterlog_abranch (log_data, revlist, rcs, ver) 159925839Speter struct log_data *log_data; 160025839Speter struct revlist *revlist; 160125839Speter RCSNode *rcs; 160225839Speter const char *ver; 160325839Speter{ 160425839Speter Node *p; 160525839Speter RCSVers *vnode; 160625839Speter 160725839Speter p = findnode (rcs->versions, ver); 160825839Speter if (p == NULL) 160925839Speter error (1, 0, "missing version `%s' in RCS file `%s'", 161025839Speter ver, rcs->path); 1611128266Speter vnode = p->data; 161225839Speter if (vnode->next != NULL) 161325839Speter log_abranch (log_data, revlist, rcs, vnode->next); 161425839Speter log_version (log_data, revlist, rcs, vnode, 0); 161525839Speter} 161625839Speter 161725839Speter/* 161825839Speter * Print the log output for a single version. 161925839Speter */ 162025839Speterstatic void 162125839Speterlog_version (log_data, revlist, rcs, ver, trunk) 162225839Speter struct log_data *log_data; 162325839Speter struct revlist *revlist; 162425839Speter RCSNode *rcs; 162525839Speter RCSVers *ver; 162625839Speter int trunk; 162725839Speter{ 162825839Speter Node *p; 162925839Speter int year, mon, mday, hour, min, sec; 163025839Speter char buf[100]; 163125839Speter Node *padd, *pdel; 163225839Speter 163325839Speter if (! log_version_requested (log_data, revlist, rcs, ver)) 163425839Speter return; 163525839Speter 163625839Speter cvs_output ("----------------------------\nrevision ", 0); 163725839Speter cvs_output (ver->version, 0); 163825839Speter 163932785Speter p = findnode (RCS_getlocks (rcs), ver->version); 164025839Speter if (p != NULL) 164125839Speter { 164225839Speter cvs_output ("\tlocked by: ", 0); 164325839Speter cvs_output (p->data, 0); 164425839Speter cvs_output (";", 1); 164525839Speter } 164625839Speter 164725839Speter cvs_output ("\ndate: ", 0); 164825839Speter (void) sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min, 164925839Speter &sec); 165025839Speter if (year < 1900) 165125839Speter year += 1900; 1652131688Sdes sprintf (buf, "%04d%c%02d%c%02d %02d:%02d:%02d", 1653131688Sdes year, datesep, mon, datesep, mday, hour, min, sec); 165425839Speter cvs_output (buf, 0); 165525839Speter 165625839Speter cvs_output ("; author: ", 0); 165725839Speter cvs_output (ver->author, 0); 165825839Speter 165925839Speter cvs_output ("; state: ", 0); 166025839Speter cvs_output (ver->state, 0); 166125839Speter cvs_output (";", 1); 166225839Speter 166325839Speter if (! trunk) 166425839Speter { 166525839Speter padd = findnode (ver->other, ";add"); 166625839Speter pdel = findnode (ver->other, ";delete"); 166725839Speter } 166825839Speter else if (ver->next == NULL) 166925839Speter { 167025839Speter padd = NULL; 167125839Speter pdel = NULL; 167225839Speter } 167325839Speter else 167425839Speter { 167525839Speter Node *nextp; 167625839Speter RCSVers *nextver; 167725839Speter 167825839Speter nextp = findnode (rcs->versions, ver->next); 167925839Speter if (nextp == NULL) 168025839Speter error (1, 0, "missing version `%s' in `%s'", ver->next, 168125839Speter rcs->path); 1682128266Speter nextver = nextp->data; 168325839Speter pdel = findnode (nextver->other, ";add"); 168425839Speter padd = findnode (nextver->other, ";delete"); 168525839Speter } 168625839Speter 168725839Speter if (padd != NULL) 168825839Speter { 1689175273Sobrien assert (pdel); 169025839Speter cvs_output (" lines: +", 0); 169125839Speter cvs_output (padd->data, 0); 169225839Speter cvs_output (" -", 2); 169325839Speter cvs_output (pdel->data, 0); 169425839Speter } 169525839Speter 169625839Speter if (ver->branches != NULL) 169725839Speter { 169825839Speter cvs_output ("\nbranches:", 0); 169925839Speter walklist (ver->branches, log_branch, (void *) NULL); 170025839Speter } 170125839Speter 170225839Speter cvs_output ("\n", 1); 170325839Speter 170425839Speter p = findnode (ver->other, "log"); 170532785Speter /* The p->date == NULL case is the normal one for an empty log 170632785Speter message (rcs-14 in sanity.sh). I don't think the case where 170732785Speter p->data is "" can happen (getrcskey in rcs.c checks for an 170832785Speter empty string and set the value to NULL in that case). My guess 170932785Speter would be the p == NULL case would mean an RCS file which was 171032785Speter missing the "log" keyword (which is illegal according to 171132785Speter rcsfile.5). */ 1712128266Speter if (p == NULL || p->data == NULL || *(char *)p->data == '\0') 171325839Speter cvs_output ("*** empty log message ***\n", 0); 171425839Speter else 171525839Speter { 171625839Speter /* FIXME: Technically, the log message could contain a null 171725839Speter byte. */ 171825839Speter cvs_output (p->data, 0); 1719128266Speter if (((char *)p->data)[strlen (p->data) - 1] != '\n') 172025839Speter cvs_output ("\n", 1); 172125839Speter } 172225839Speter} 172325839Speter 172425839Speter/* 172525839Speter * Output a branch version. This is called via walklist. 172625839Speter */ 172725839Speter/*ARGSUSED*/ 172825839Speterstatic int 172925839Speterlog_branch (p, closure) 173025839Speter Node *p; 173125839Speter void *closure; 173225839Speter{ 173325839Speter cvs_output (" ", 2); 173425839Speter if ((numdots (p->key) & 1) == 0) 173525839Speter cvs_output (p->key, 0); 173625839Speter else 173725839Speter { 173825839Speter char *f, *cp; 173925839Speter 174025839Speter f = xstrdup (p->key); 174125839Speter cp = strrchr (f, '.'); 174225839Speter *cp = '\0'; 174325839Speter cvs_output (f, 0); 174425839Speter free (f); 174525839Speter } 174625839Speter cvs_output (";", 1); 174725839Speter return 0; 174825839Speter} 174925839Speter 175025839Speter/* 175117721Speter * Print a warm fuzzy message 175217721Speter */ 175317721Speter/* ARGSUSED */ 175417721Speterstatic Dtype 175525839Speterlog_dirproc (callerdat, dir, repository, update_dir, entries) 175625839Speter void *callerdat; 1757128266Speter const char *dir; 1758128266Speter const char *repository; 1759128266Speter const char *update_dir; 176025839Speter List *entries; 176117721Speter{ 176217721Speter if (!isdir (dir)) 176317721Speter return (R_SKIP_ALL); 176417721Speter 176517721Speter if (!quiet) 176617721Speter error (0, 0, "Logging %s", update_dir); 176717721Speter return (R_PROCESS); 176817721Speter} 176925839Speter 177025839Speter/* 177125839Speter * Compare versions. This is taken from RCS compartial. 177225839Speter */ 177325839Speterstatic int 177425839Speterversion_compare (v1, v2, len) 177525839Speter const char *v1; 177625839Speter const char *v2; 177725839Speter int len; 177825839Speter{ 177925839Speter while (1) 178025839Speter { 178125839Speter int d1, d2, r; 178225839Speter 178325839Speter if (*v1 == '\0') 178425839Speter return 1; 178525839Speter if (*v2 == '\0') 178625839Speter return -1; 178725839Speter 178825839Speter while (*v1 == '0') 178925839Speter ++v1; 179054427Speter for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1) 179125839Speter ; 179225839Speter 179325839Speter while (*v2 == '0') 179425839Speter ++v2; 179554427Speter for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2) 179625839Speter ; 179725839Speter 179825839Speter if (d1 != d2) 179925839Speter return d1 < d2 ? -1 : 1; 180025839Speter 180125839Speter r = memcmp (v1, v2, d1); 180225839Speter if (r != 0) 180325839Speter return r; 180425839Speter 180525839Speter --len; 180625839Speter if (len == 0) 180725839Speter return 0; 180825839Speter 180925839Speter v1 += d1; 181025839Speter v2 += d1; 181125839Speter 181225839Speter if (*v1 == '.') 181325839Speter ++v1; 181425839Speter if (*v2 == '.') 181525839Speter ++v2; 181625839Speter } 181725839Speter} 1818