117721Speter/* 2177397Sobrien * Copyright (C) 1986-2008 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$ 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; 92177397Sobrien /* Nonzero if the -b option was seen, meaning that 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", 161177397Sobrien "\t-b\tList 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", 166177397Sobrien "\t-n\tList tags (default).\n", 167175273Sobrien "\t-S\tDo not print name/header if no revisions selected. -d, -r,\n", 168175273Sobrien "\t\t-s, & -w have little effect in conjunction with -b, -h, -R, and\n", 169175273Sobrien "\t\t-t without this option.\n", 170102840Speter "\t-r[revisions]\tA comma-separated list of revisions to print:\n", 17181404Speter "\t rev1:rev2 Between rev1 and rev2, including rev1 and rev2.\n", 172102840Speter "\t rev1::rev2 Between rev1 and rev2, excluding rev1.\n", 17381404Speter "\t rev: rev and following revisions on the same branch.\n", 17481404Speter "\t rev:: After rev on the same branch.\n", 17581404Speter "\t :rev rev and previous revisions on the same branch.\n", 176102840Speter "\t ::rev rev and previous revisions on the same branch.\n", 17781404Speter "\t rev Just rev.\n", 17881404Speter "\t branch All revisions on the branch.\n", 17981404Speter "\t branch. The last revision on the branch.\n", 180102840Speter "\t-d dates\tA semicolon-separated list of dates\n", 181102840Speter "\t \t(D1<D2 for range, D for latest before).\n", 18225839Speter "\t-s states\tOnly list revisions with specified states.\n", 18325839Speter "\t-w[logins]\tOnly list revisions checked in by specified logins.\n", 18432785Speter "(Specify the --help global option for a list of other help options)\n", 18517721Speter NULL 18617721Speter}; 18717721Speter 18866525Speter#ifdef CLIENT_SUPPORT 18966525Speter 19066525Speter/* Helper function for send_arg_list. */ 19166525Speterstatic int send_one PROTO ((Node *, void *)); 19266525Speter 19366525Speterstatic int 19466525Spetersend_one (node, closure) 19566525Speter Node *node; 19666525Speter void *closure; 19766525Speter{ 19866525Speter char *option = (char *) closure; 19966525Speter 20066525Speter send_to_server ("Argument ", 0); 20166525Speter send_to_server (option, 0); 20266525Speter if (strcmp (node->key, "@@MYSELF") == 0) 20366525Speter /* It is a bare -w option. Note that we must send it as 20466525Speter -w rather than messing with getcaller() or something (which on 20566525Speter the client will return garbage). */ 20666525Speter ; 20766525Speter else 20866525Speter send_to_server (node->key, 0); 20966525Speter send_to_server ("\012", 0); 21066525Speter return 0; 21166525Speter} 21266525Speter 21366525Speter/* For each element in ARG, send an argument consisting of OPTION 21466525Speter concatenated with that element. */ 21566525Speterstatic void send_arg_list PROTO ((char *, List *)); 21666525Speter 21766525Speterstatic void 21866525Spetersend_arg_list (option, arg) 21966525Speter char *option; 22066525Speter List *arg; 22166525Speter{ 22266525Speter if (arg == NULL) 22366525Speter return; 22466525Speter walklist (arg, send_one, (void *)option); 22566525Speter} 22666525Speter 22766525Speter#endif 22866525Speter 22917721Speterint 23017721Spetercvslog (argc, argv) 23117721Speter int argc; 23217721Speter char **argv; 23317721Speter{ 23425839Speter int c; 23517721Speter int err = 0; 23617721Speter int local = 0; 23766525Speter struct option_revlist **prl; 23817721Speter 239128266Speter is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0); 24081404Speter 24117721Speter if (argc == -1) 24217721Speter usage (log_usage); 24317721Speter 24425839Speter memset (&log_data, 0, sizeof log_data); 24566525Speter prl = &log_data.revlist; 24617721Speter 24726065Speter optind = 0; 248165111Sobrien while ((c = getopt (argc, argv, "+bd:hlNnSRr::s:tw::")) != -1) 24925839Speter { 25025839Speter switch (c) 25125839Speter { 25225839Speter case 'b': 25325839Speter log_data.default_branch = 1; 25425839Speter break; 25525839Speter case 'd': 25625839Speter log_parse_date (&log_data, optarg); 25725839Speter break; 25825839Speter case 'h': 25925839Speter log_data.header = 1; 26025839Speter break; 26125839Speter case 'l': 26225839Speter local = 1; 26325839Speter break; 26425839Speter case 'N': 26525839Speter log_data.notags = 1; 26625839Speter break; 267165111Sobrien case 'n': 268165111Sobrien log_data.notags = 0; 269165111Sobrien break; 270102840Speter case 'S': 271102840Speter log_data.sup_header = 1; 272102840Speter break; 27325839Speter case 'R': 27425839Speter log_data.nameonly = 1; 27525839Speter break; 27625839Speter case 'r': 27766525Speter *prl = log_parse_revlist (optarg); 27866525Speter prl = &(*prl)->next; 27925839Speter break; 28025839Speter case 's': 28125839Speter log_parse_list (&log_data.statelist, optarg); 28225839Speter break; 28325839Speter case 't': 28425839Speter log_data.long_header = 1; 28525839Speter break; 28625839Speter case 'w': 28725839Speter if (optarg != NULL) 28825839Speter log_parse_list (&log_data.authorlist, optarg); 28925839Speter else 29066525Speter log_parse_list (&log_data.authorlist, "@@MYSELF"); 29125839Speter break; 29225839Speter case '?': 29325839Speter default: 29425839Speter usage (log_usage); 29525839Speter break; 29625839Speter } 29725839Speter } 29881404Speter argc -= optind; 29981404Speter argv += optind; 30025839Speter 30117721Speter wrap_setup (); 30217721Speter 30317721Speter#ifdef CLIENT_SUPPORT 30481404Speter if (current_parsed_root->isremote) 30525839Speter { 30666525Speter struct datelist *p; 30766525Speter struct option_revlist *rp; 30866525Speter char datetmp[MAXDATELEN]; 30925839Speter 31017721Speter /* We're the local client. Fire up the remote server. */ 31117721Speter start_server (); 31281404Speter 31381404Speter if (is_rlog && !supported_request ("rlog")) 31481404Speter error (1, 0, "server does not support rlog"); 31581404Speter 31617721Speter ign_setup (); 31717721Speter 31866525Speter if (log_data.default_branch) 31966525Speter send_arg ("-b"); 32017721Speter 32166525Speter while (log_data.datelist != NULL) 32266525Speter { 32366525Speter p = log_data.datelist; 32466525Speter log_data.datelist = p->next; 325175273Sobrien assert (p->start != NULL && p->end != NULL); 32666525Speter send_to_server ("Argument -d\012", 0); 32766525Speter send_to_server ("Argument ", 0); 32866525Speter date_to_internet (datetmp, p->start); 32966525Speter send_to_server (datetmp, 0); 33066525Speter if (p->inclusive) 33166525Speter send_to_server ("<=", 0); 33266525Speter else 33366525Speter send_to_server ("<", 0); 33466525Speter date_to_internet (datetmp, p->end); 33566525Speter send_to_server (datetmp, 0); 33666525Speter send_to_server ("\012", 0); 337175273Sobrien free (p->start); 338175273Sobrien free (p->end); 33966525Speter free (p); 34066525Speter } 34166525Speter while (log_data.singledatelist != NULL) 34266525Speter { 34366525Speter p = log_data.singledatelist; 34466525Speter log_data.singledatelist = p->next; 345175273Sobrien assert (p->end != NULL); 34666525Speter send_to_server ("Argument -d\012", 0); 34766525Speter send_to_server ("Argument ", 0); 34866525Speter date_to_internet (datetmp, p->end); 34966525Speter send_to_server (datetmp, 0); 35066525Speter send_to_server ("\012", 0); 351175273Sobrien free (p->end); 35266525Speter free (p); 35366525Speter } 35466525Speter 35566525Speter if (log_data.header) 35666525Speter send_arg ("-h"); 35766525Speter if (local) 35866525Speter send_arg("-l"); 35966525Speter if (log_data.notags) 36066525Speter send_arg("-N"); 361102840Speter if (log_data.sup_header) 362102840Speter send_arg("-S"); 36366525Speter if (log_data.nameonly) 36466525Speter send_arg("-R"); 36566525Speter if (log_data.long_header) 36666525Speter send_arg("-t"); 36717721Speter 36866525Speter while (log_data.revlist != NULL) 36966525Speter { 37066525Speter rp = log_data.revlist; 37166525Speter log_data.revlist = rp->next; 37266525Speter send_to_server ("Argument -r", 0); 37366525Speter if (rp->branchhead) 37466525Speter { 37566525Speter if (rp->first != NULL) 37666525Speter send_to_server (rp->first, 0); 37766525Speter send_to_server (".", 1); 37866525Speter } 37966525Speter else 38066525Speter { 38166525Speter if (rp->first != NULL) 38266525Speter send_to_server (rp->first, 0); 38366525Speter send_to_server (":", 1); 38481404Speter if (!rp->inclusive) 38581404Speter send_to_server (":", 1); 38666525Speter if (rp->last != NULL) 38766525Speter send_to_server (rp->last, 0); 38866525Speter } 38966525Speter send_to_server ("\012", 0); 39066525Speter if (rp->first) 39166525Speter free (rp->first); 39266525Speter if (rp->last) 39366525Speter free (rp->last); 39466525Speter free (rp); 39566525Speter } 39666525Speter send_arg_list ("-s", log_data.statelist); 39766525Speter dellist (&log_data.statelist); 39866525Speter send_arg_list ("-w", log_data.authorlist); 39966525Speter dellist (&log_data.authorlist); 400107484Speter send_arg ("--"); 40166525Speter 40281404Speter if (is_rlog) 40381404Speter { 40481404Speter int i; 40581404Speter for (i = 0; i < argc; i++) 40681404Speter send_arg (argv[i]); 40781404Speter send_to_server ("rlog\012", 0); 40881404Speter } 40981404Speter else 41081404Speter { 41181404Speter send_files (argc, argv, local, 0, SEND_NO_CONTENTS); 41281404Speter send_file_names (argc, argv, SEND_EXPAND_WILD); 41381404Speter send_to_server ("log\012", 0); 41481404Speter } 41517721Speter err = get_responses_and_close (); 41617721Speter return err; 41717721Speter } 41817721Speter#endif 41917721Speter 42066525Speter /* OK, now that we know we are local/server, we can resolve @@MYSELF 42166525Speter into our user name. */ 42266525Speter if (findnode (log_data.authorlist, "@@MYSELF") != NULL) 42366525Speter log_parse_list (&log_data.authorlist, getcaller ()); 42466525Speter 42581404Speter if (is_rlog) 42681404Speter { 42781404Speter DBM *db; 42881404Speter int i; 42981404Speter db = open_module (); 43081404Speter for (i = 0; i < argc; i++) 43181404Speter { 43281404Speter err += do_module (db, argv[i], MISC, "Logging", rlog_proc, 433102840Speter (char *) NULL, 0, local, 0, 0, (char *) NULL); 43481404Speter } 43581404Speter close_module (db); 43681404Speter } 43781404Speter else 43881404Speter { 43981404Speter err = rlog_proc (argc + 1, argv - 1, (char *) NULL, 440102840Speter (char *) NULL, (char *) NULL, 0, local, (char *) NULL, 44181404Speter (char *) NULL); 44281404Speter } 44366525Speter 44466525Speter while (log_data.revlist) 44566525Speter { 44666525Speter struct option_revlist *rl = log_data.revlist->next; 44766525Speter if (log_data.revlist->first) 44866525Speter free (log_data.revlist->first); 44966525Speter if (log_data.revlist->last) 45066525Speter free (log_data.revlist->last); 45166525Speter free (log_data.revlist); 45266525Speter log_data.revlist = rl; 45366525Speter } 45466525Speter while (log_data.datelist) 45566525Speter { 45666525Speter struct datelist *nd = log_data.datelist->next; 45766525Speter if (log_data.datelist->start) 45866525Speter free (log_data.datelist->start); 45966525Speter if (log_data.datelist->end) 46066525Speter free (log_data.datelist->end); 46166525Speter free (log_data.datelist); 46266525Speter log_data.datelist = nd; 46366525Speter } 46466525Speter while (log_data.singledatelist) 46566525Speter { 46666525Speter struct datelist *nd = log_data.singledatelist->next; 46766525Speter if (log_data.singledatelist->start) 46866525Speter free (log_data.singledatelist->start); 46966525Speter if (log_data.singledatelist->end) 47066525Speter free (log_data.singledatelist->end); 47166525Speter free (log_data.singledatelist); 47266525Speter log_data.singledatelist = nd; 47366525Speter } 47466525Speter dellist (&log_data.statelist); 47566525Speter dellist (&log_data.authorlist); 47666525Speter 47717721Speter return (err); 47817721Speter} 47917721Speter 48081404Speter 48181404Speterstatic int 48281404Speterrlog_proc (argc, argv, xwhere, mwhere, mfile, shorten, local, mname, msg) 48381404Speter int argc; 48481404Speter char **argv; 48581404Speter char *xwhere; 48681404Speter char *mwhere; 48781404Speter char *mfile; 48881404Speter int shorten; 48981404Speter int local; 49081404Speter char *mname; 49181404Speter char *msg; 49281404Speter{ 49381404Speter /* Begin section which is identical to patch_proc--should this 49481404Speter be abstracted out somehow? */ 49581404Speter char *myargv[2]; 49681404Speter int err = 0; 49781404Speter int which; 49881404Speter char *repository; 49981404Speter char *where; 50081404Speter 50181404Speter if (is_rlog) 50281404Speter { 503128266Speter repository = xmalloc (strlen (current_parsed_root->directory) 504128266Speter + strlen (argv[0]) 50581404Speter + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2); 506128266Speter (void)sprintf (repository, "%s/%s", 507128266Speter current_parsed_root->directory, argv[0]); 508128266Speter where = xmalloc (strlen (argv[0]) 509128266Speter + (mfile == NULL ? 0 : strlen (mfile) + 1) 51081404Speter + 1); 51181404Speter (void) strcpy (where, argv[0]); 51281404Speter 513128266Speter /* If mfile isn't null, we need to set up to do only part of theu 514128266Speter * module. 515128266Speter */ 51681404Speter if (mfile != NULL) 51781404Speter { 51881404Speter char *cp; 51981404Speter char *path; 52081404Speter 521128266Speter /* If the portion of the module is a path, put the dir part on 522128266Speter * repos. 523128266Speter */ 52481404Speter if ((cp = strrchr (mfile, '/')) != NULL) 52581404Speter { 52681404Speter *cp = '\0'; 527128266Speter (void)strcat (repository, "/"); 528128266Speter (void)strcat (repository, mfile); 529128266Speter (void)strcat (where, "/"); 530128266Speter (void)strcat (where, mfile); 53181404Speter mfile = cp + 1; 53281404Speter } 53381404Speter 53481404Speter /* take care of the rest */ 53581404Speter path = xmalloc (strlen (repository) + strlen (mfile) + 5); 536128266Speter (void)sprintf (path, "%s/%s", repository, mfile); 53781404Speter if (isdir (path)) 53881404Speter { 53981404Speter /* directory means repository gets the dir tacked on */ 540128266Speter (void)strcpy (repository, path); 541128266Speter (void)strcat (where, "/"); 542128266Speter (void)strcat (where, mfile); 54381404Speter } 54481404Speter else 54581404Speter { 54681404Speter myargv[0] = argv[0]; 54781404Speter myargv[1] = mfile; 54881404Speter argc = 2; 54981404Speter argv = myargv; 55081404Speter } 55181404Speter free (path); 55281404Speter } 55381404Speter 55481404Speter /* cd to the starting repository */ 555128266Speter if (CVS_CHDIR (repository) < 0) 55681404Speter { 55781404Speter error (0, errno, "cannot chdir to %s", repository); 55881404Speter free (repository); 559128266Speter free (where); 560128266Speter return 1; 56181404Speter } 56281404Speter /* End section which is identical to patch_proc. */ 56381404Speter 56481404Speter which = W_REPOS | W_ATTIC; 56581404Speter } 56681404Speter else 56781404Speter { 568128266Speter repository = NULL; 56981404Speter where = NULL; 57081404Speter which = W_LOCAL | W_REPOS | W_ATTIC; 57181404Speter } 57281404Speter 57381404Speter err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc, 57481404Speter (DIRLEAVEPROC) NULL, (void *) &log_data, 575109655Speter argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ, 576128266Speter where, 1, repository); 577128266Speter 578128266Speter if (!(which & W_LOCAL)) free (repository); 579128266Speter if (where) free (where); 580128266Speter 58181404Speter return err; 58281404Speter} 58381404Speter 58481404Speter 585128266Speter 58625839Speter/* 58725839Speter * Parse a revision list specification. 58825839Speter */ 58925839Speterstatic struct option_revlist * 59025839Speterlog_parse_revlist (argstring) 59125839Speter const char *argstring; 59225839Speter{ 59366525Speter char *orig_copy, *copy; 59425839Speter struct option_revlist *ret, **pr; 59525839Speter 59625839Speter /* Unfortunately, rlog accepts -r without an argument to mean that 59725839Speter latest revision on the default branch, so we must support that 59825839Speter for compatibility. */ 59925839Speter if (argstring == NULL) 60066525Speter argstring = ""; 60125839Speter 60225839Speter ret = NULL; 60325839Speter pr = &ret; 60425839Speter 60525839Speter /* Copy the argument into memory so that we can change it. We 60625839Speter don't want to change the argument because, at least as of this 60766525Speter writing, we will use it if we send the arguments to the server. */ 60866525Speter orig_copy = copy = xstrdup (argstring); 60925839Speter while (copy != NULL) 61025839Speter { 61125839Speter char *comma; 61225839Speter struct option_revlist *r; 61325839Speter 61425839Speter comma = strchr (copy, ','); 61525839Speter if (comma != NULL) 61625839Speter *comma++ = '\0'; 61725839Speter 61825839Speter r = (struct option_revlist *) xmalloc (sizeof *r); 61925839Speter r->next = NULL; 62066525Speter r->first = copy; 62166525Speter r->branchhead = 0; 62266525Speter r->last = strchr (copy, ':'); 62366525Speter if (r->last != NULL) 62481404Speter { 62566525Speter *r->last++ = '\0'; 62681404Speter r->inclusive = (*r->last != ':'); 62781404Speter if (!r->inclusive) 62881404Speter r->last++; 62981404Speter } 63025839Speter else 63125839Speter { 63266525Speter r->last = r->first; 63381404Speter r->inclusive = 1; 63466525Speter if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.') 63566525Speter { 63666525Speter r->branchhead = 1; 63766525Speter r->first[strlen (r->first) - 1] = '\0'; 63866525Speter } 63925839Speter } 64025839Speter 64166525Speter if (*r->first == '\0') 64266525Speter r->first = NULL; 64366525Speter if (*r->last == '\0') 64466525Speter r->last = NULL; 64566525Speter 64666525Speter if (r->first != NULL) 64766525Speter r->first = xstrdup (r->first); 64866525Speter if (r->last != NULL) 64966525Speter r->last = xstrdup (r->last); 65066525Speter 65125839Speter *pr = r; 65225839Speter pr = &r->next; 65325839Speter 65425839Speter copy = comma; 65525839Speter } 65625839Speter 65766525Speter free (orig_copy); 65825839Speter return ret; 65925839Speter} 66025839Speter 66117721Speter/* 66225839Speter * Parse a date specification. 66325839Speter */ 66425839Speterstatic void 66525839Speterlog_parse_date (log_data, argstring) 66625839Speter struct log_data *log_data; 66725839Speter const char *argstring; 66825839Speter{ 66925839Speter char *orig_copy, *copy; 67025839Speter 67125839Speter /* Copy the argument into memory so that we can change it. We 67225839Speter don't want to change the argument because, at least as of this 67325839Speter writing, we will use it if we send the arguments to the server. */ 67466525Speter orig_copy = copy = xstrdup (argstring); 67525839Speter while (copy != NULL) 67625839Speter { 67725839Speter struct datelist *nd, **pd; 67825839Speter char *cpend, *cp, *ds, *de; 67925839Speter 68025839Speter nd = (struct datelist *) xmalloc (sizeof *nd); 68125839Speter 68225839Speter cpend = strchr (copy, ';'); 68325839Speter if (cpend != NULL) 68425839Speter *cpend++ = '\0'; 68525839Speter 68625839Speter pd = &log_data->datelist; 68725839Speter nd->inclusive = 0; 68825839Speter 68925839Speter if ((cp = strchr (copy, '>')) != NULL) 69025839Speter { 69125839Speter *cp++ = '\0'; 69225839Speter if (*cp == '=') 69325839Speter { 69425839Speter ++cp; 69525839Speter nd->inclusive = 1; 69625839Speter } 69725839Speter ds = cp; 69825839Speter de = copy; 69925839Speter } 70025839Speter else if ((cp = strchr (copy, '<')) != NULL) 70125839Speter { 70225839Speter *cp++ = '\0'; 70325839Speter if (*cp == '=') 70425839Speter { 70525839Speter ++cp; 70625839Speter nd->inclusive = 1; 70725839Speter } 70825839Speter ds = copy; 70925839Speter de = cp; 71025839Speter } 71125839Speter else 71225839Speter { 71325839Speter ds = NULL; 71425839Speter de = copy; 71525839Speter pd = &log_data->singledatelist; 71625839Speter } 71725839Speter 71825839Speter if (ds == NULL) 71925839Speter nd->start = NULL; 72025839Speter else if (*ds != '\0') 72125839Speter nd->start = Make_Date (ds); 72225839Speter else 72325839Speter { 72425839Speter /* 1970 was the beginning of time, as far as get_date and 72525839Speter Make_Date are concerned. FIXME: That is true only if time_t 72625839Speter is a POSIX-style time and there is nothing in ANSI that 72725839Speter mandates that. It would be cleaner to set a flag saying 72825839Speter whether or not there is a start date. */ 72925839Speter nd->start = Make_Date ("1/1/1970 UTC"); 73025839Speter } 73125839Speter 73225839Speter if (*de != '\0') 73325839Speter nd->end = Make_Date (de); 73425839Speter else 73525839Speter { 73625839Speter /* We want to set the end date to some time sufficiently far 73725839Speter in the future to pick up all revisions that have been 73825839Speter created since the specified date and the time `cvs log' 73925839Speter completes. FIXME: The date in question only makes sense 74025839Speter if time_t is a POSIX-style time and it is 32 bits 74125839Speter and signed. We should instead be setting a flag saying 74225839Speter whether or not there is an end date. Note that using 74325839Speter something like "next week" would break the testsuite (and, 74425839Speter perhaps less importantly, loses if the clock is set grossly 74525839Speter wrong). */ 74625839Speter nd->end = Make_Date ("2038-01-01"); 74725839Speter } 74825839Speter 74925839Speter nd->next = *pd; 75025839Speter *pd = nd; 75125839Speter 75225839Speter copy = cpend; 75325839Speter } 75425839Speter 75525839Speter free (orig_copy); 75625839Speter} 75725839Speter 75825839Speter/* 75925839Speter * Parse a comma separated list of items, and add each one to *PLIST. 76025839Speter */ 76125839Speterstatic void 76225839Speterlog_parse_list (plist, argstring) 76325839Speter List **plist; 76425839Speter const char *argstring; 76525839Speter{ 76625839Speter while (1) 76725839Speter { 76825839Speter Node *p; 76925839Speter char *cp; 77025839Speter 77125839Speter p = getnode (); 77225839Speter 77325839Speter cp = strchr (argstring, ','); 77425839Speter if (cp == NULL) 77525839Speter p->key = xstrdup (argstring); 77625839Speter else 77725839Speter { 77825839Speter size_t len; 77925839Speter 78025839Speter len = cp - argstring; 78125839Speter p->key = xmalloc (len + 1); 78225839Speter strncpy (p->key, argstring, len); 783107484Speter p->key[len] = '\0'; 78425839Speter } 78525839Speter 78625839Speter if (*plist == NULL) 78725839Speter *plist = getlist (); 78825839Speter if (addnode (*plist, p) != 0) 78925839Speter freenode (p); 79025839Speter 79125839Speter if (cp == NULL) 79225839Speter break; 79325839Speter 79425839Speter argstring = cp + 1; 79525839Speter } 79625839Speter} 79725839Speter 79832785Speterstatic int printlock_proc PROTO ((Node *, void *)); 79932785Speter 80032785Speterstatic int 80132785Speterprintlock_proc (lock, foo) 80232785Speter Node *lock; 80332785Speter void *foo; 80432785Speter{ 80532785Speter cvs_output ("\n\t", 2); 80632785Speter cvs_output (lock->data, 0); 80732785Speter cvs_output (": ", 2); 80832785Speter cvs_output (lock->key, 0); 80932785Speter return 0; 81032785Speter} 81132785Speter 812128266Speter 813128266Speter 81425839Speter/* 81517721Speter * Do an rlog on a file 81617721Speter */ 81717721Speterstatic int 81825839Speterlog_fileproc (callerdat, finfo) 81925839Speter void *callerdat; 82017721Speter struct file_info *finfo; 82117721Speter{ 82225839Speter struct log_data *log_data = (struct log_data *) callerdat; 82317721Speter Node *p; 824175273Sobrien char *baserev; 825102840Speter int selrev = -1; 82617721Speter RCSNode *rcsfile; 82725839Speter char buf[50]; 828128266Speter struct revlist *revlist = NULL; 82925839Speter struct log_data_and_rcs log_data_and_rcs; 83017721Speter 831175273Sobrien rcsfile = finfo->rcs; 832175273Sobrien p = findnode (finfo->entries, finfo->file); 833175273Sobrien if (p != NULL) 83417721Speter { 835175273Sobrien Entnode *e = p->data; 836175273Sobrien baserev = e->version; 837175273Sobrien if (baserev[0] == '-') ++baserev; 838175273Sobrien } 839175273Sobrien else 840175273Sobrien baserev = NULL; 841175273Sobrien 842175273Sobrien if (rcsfile == NULL) 843175273Sobrien { 84417721Speter /* no rcs file. What *do* we know about this file? */ 845175273Sobrien if (baserev != NULL) 84617721Speter { 847175273Sobrien if (baserev[0] == '0' && baserev[1] == '\0') 84817721Speter { 84917721Speter if (!really_quiet) 85017721Speter error (0, 0, "%s has been added, but not committed", 85117721Speter finfo->file); 852128266Speter return 0; 85317721Speter } 85417721Speter } 85517721Speter 85617721Speter if (!really_quiet) 85717721Speter error (0, 0, "nothing known about %s", finfo->file); 85817721Speter 859128266Speter return 1; 86017721Speter } 86117721Speter 862102840Speter if (log_data->sup_header || !log_data->nameonly) 863102840Speter { 864102840Speter 865102840Speter /* We will need all the information in the RCS file. */ 866102840Speter RCS_fully_parse (rcsfile); 867102840Speter 868102840Speter /* Turn any symbolic revisions in the revision list into numeric 869102840Speter revisions. */ 870175273Sobrien revlist = log_expand_revlist (rcsfile, baserev, log_data->revlist, 871102840Speter log_data->default_branch); 872128266Speter if (log_data->sup_header 873128266Speter || (!log_data->header && !log_data->long_header)) 874102840Speter { 875102840Speter log_data_and_rcs.log_data = log_data; 876102840Speter log_data_and_rcs.revlist = revlist; 877102840Speter log_data_and_rcs.rcs = rcsfile; 878102840Speter 879102840Speter /* If any single dates were specified, we need to identify the 880102840Speter revisions they select. Each one selects the single 881102840Speter revision, which is otherwise selected, of that date or 882102840Speter earlier. The log_fix_singledate routine will fill in the 883102840Speter start date for each specific revision. */ 884102840Speter if (log_data->singledatelist != NULL) 885102840Speter walklist (rcsfile->versions, log_fix_singledate, 886128266Speter (void *)&log_data_and_rcs); 887102840Speter 888102840Speter selrev = walklist (rcsfile->versions, log_count_print, 889128266Speter (void *)&log_data_and_rcs); 890128266Speter if (log_data->sup_header && selrev == 0) 891128266Speter { 892128266Speter log_free_revlist (revlist); 893128266Speter return 0; 894128266Speter } 895102840Speter } 896102840Speter 897102840Speter } 898102840Speter 89925839Speter if (log_data->nameonly) 90017721Speter { 90125839Speter cvs_output (rcsfile->path, 0); 90225839Speter cvs_output ("\n", 1); 903128266Speter log_free_revlist (revlist); 90425839Speter return 0; 90517721Speter } 90617721Speter 90725839Speter /* The output here is intended to be exactly compatible with the 90825839Speter output of rlog. I'm not sure whether this code should be here 90925839Speter or in rcs.c; I put it here because it is specific to the log 91025839Speter function, even though it uses information gathered by the 91125839Speter functions in rcs.c. */ 91225839Speter 91325839Speter cvs_output ("\n", 1); 91425839Speter 91525839Speter cvs_output ("RCS file: ", 0); 91625839Speter cvs_output (rcsfile->path, 0); 91725839Speter 91881404Speter if (!is_rlog) 91917721Speter { 92081404Speter cvs_output ("\nWorking file: ", 0); 92181404Speter if (finfo->update_dir[0] != '\0') 92281404Speter { 92381404Speter cvs_output (finfo->update_dir, 0); 92481404Speter cvs_output ("/", 0); 92581404Speter } 92625839Speter cvs_output (finfo->file, 0); 92717721Speter } 92817721Speter 92925839Speter cvs_output ("\nhead:", 0); 93025839Speter if (rcsfile->head != NULL) 93117721Speter { 93225839Speter cvs_output (" ", 1); 93325839Speter cvs_output (rcsfile->head, 0); 93417721Speter } 93525839Speter 93625839Speter cvs_output ("\nbranch:", 0); 93725839Speter if (rcsfile->branch != NULL) 93825839Speter { 93925839Speter cvs_output (" ", 1); 94025839Speter cvs_output (rcsfile->branch, 0); 94125839Speter } 94225839Speter 94325839Speter cvs_output ("\nlocks:", 0); 94432785Speter if (rcsfile->strict_locks) 94532785Speter cvs_output (" strict", 0); 94632785Speter walklist (RCS_getlocks (rcsfile), printlock_proc, NULL); 94725839Speter 94825839Speter cvs_output ("\naccess list:", 0); 94932785Speter if (rcsfile->access != NULL) 95025839Speter { 95132785Speter const char *cp; 95232785Speter 95332785Speter cp = rcsfile->access; 95432785Speter while (*cp != '\0') 95525839Speter { 95625839Speter const char *cp2; 95725839Speter 95825839Speter cvs_output ("\n\t", 2); 95925839Speter cp2 = cp; 960128266Speter while (!isspace ((unsigned char) *cp2) && *cp2 != '\0') 96125839Speter ++cp2; 96225839Speter cvs_output (cp, cp2 - cp); 96325839Speter cp = cp2; 96454427Speter while (isspace ((unsigned char) *cp) && *cp != '\0') 96525839Speter ++cp; 96625839Speter } 96725839Speter } 96825839Speter 969128266Speter if (!log_data->notags) 97025839Speter { 97125839Speter List *syms; 97225839Speter 97325839Speter cvs_output ("\nsymbolic names:", 0); 97425839Speter syms = RCS_symbols (rcsfile); 97525839Speter walklist (syms, log_symbol, NULL); 97625839Speter } 97725839Speter 97825839Speter cvs_output ("\nkeyword substitution: ", 0); 97925839Speter if (rcsfile->expand == NULL) 98025839Speter cvs_output ("kv", 2); 98125839Speter else 98225839Speter cvs_output (rcsfile->expand, 0); 98325839Speter 98425839Speter cvs_output ("\ntotal revisions: ", 0); 98525839Speter sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL)); 98625839Speter cvs_output (buf, 0); 98725839Speter 988102840Speter if (selrev >= 0) 98925839Speter { 99025839Speter cvs_output (";\tselected revisions: ", 0); 991102840Speter sprintf (buf, "%d", selrev); 99225839Speter cvs_output (buf, 0); 99325839Speter } 99425839Speter 99525839Speter cvs_output ("\n", 1); 99625839Speter 997128266Speter if (!log_data->header || log_data->long_header) 99825839Speter { 99925839Speter cvs_output ("description:\n", 0); 100032785Speter if (rcsfile->desc != NULL) 100132785Speter cvs_output (rcsfile->desc, 0); 100225839Speter } 100325839Speter 1004128266Speter if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL) 100525839Speter { 100625839Speter p = findnode (rcsfile->versions, rcsfile->head); 100725839Speter if (p == NULL) 100825839Speter error (1, 0, "can not find head revision in `%s'", 100925839Speter finfo->fullname); 101025839Speter while (p != NULL) 101125839Speter { 1012128266Speter RCSVers *vers = p->data; 101325839Speter 101425839Speter log_version (log_data, revlist, rcsfile, vers, 1); 101525839Speter if (vers->next == NULL) 101625839Speter p = NULL; 101725839Speter else 101825839Speter { 101925839Speter p = findnode (rcsfile->versions, vers->next); 102025839Speter if (p == NULL) 102125839Speter error (1, 0, "can not find next revision `%s' in `%s'", 102225839Speter vers->next, finfo->fullname); 102325839Speter } 102425839Speter } 102525839Speter 102625839Speter log_tree (log_data, revlist, rcsfile, rcsfile->head); 102725839Speter } 102825839Speter 102925839Speter cvs_output("\ 103025839Speter=============================================================================\n", 103125839Speter 0); 103225839Speter 103325839Speter /* Free up the new revlist and restore the old one. */ 103425839Speter log_free_revlist (revlist); 103525839Speter 103625839Speter /* If singledatelist is not NULL, free up the start dates we added 103725839Speter to it. */ 103825839Speter if (log_data->singledatelist != NULL) 103925839Speter { 104025839Speter struct datelist *d; 104125839Speter 104225839Speter for (d = log_data->singledatelist; d != NULL; d = d->next) 104325839Speter { 104425839Speter if (d->start != NULL) 104525839Speter free (d->start); 104625839Speter d->start = NULL; 104725839Speter } 104825839Speter } 104925839Speter 105025839Speter return 0; 105117721Speter} 105217721Speter 1053128266Speter 1054128266Speter 105517721Speter/* 105625839Speter * Fix up a revision list in order to compare it against versions. 105725839Speter * Expand any symbolic revisions. 105825839Speter */ 105925839Speterstatic struct revlist * 1060175273Sobrienlog_expand_revlist (rcs, baserev, revlist, default_branch) 106125839Speter RCSNode *rcs; 1062175273Sobrien char *baserev; 106325839Speter struct option_revlist *revlist; 106425839Speter int default_branch; 106525839Speter{ 106625839Speter struct option_revlist *r; 106725839Speter struct revlist *ret, **pr; 106825839Speter 106925839Speter ret = NULL; 107025839Speter pr = &ret; 107125839Speter for (r = revlist; r != NULL; r = r->next) 107225839Speter { 107325839Speter struct revlist *nr; 107425839Speter 107525839Speter nr = (struct revlist *) xmalloc (sizeof *nr); 107681404Speter nr->inclusive = r->inclusive; 107725839Speter 107825839Speter if (r->first == NULL && r->last == NULL) 107925839Speter { 108025839Speter /* If both first and last are NULL, it means that we want 108125839Speter just the head of the default branch, which is RCS_head. */ 108225839Speter nr->first = RCS_head (rcs); 1083175273Sobrien if (!nr->first) 1084175273Sobrien { 1085175273Sobrien if (!really_quiet) 1086175273Sobrien error (0, 0, "No head revision in archive `%s'.", 1087175273Sobrien rcs->path); 1088175273Sobrien nr->last = NULL; 1089175273Sobrien nr->fields = 0; 1090175273Sobrien } 1091175273Sobrien else 1092175273Sobrien { 1093175273Sobrien nr->last = xstrdup (nr->first); 1094175273Sobrien nr->fields = numdots (nr->first) + 1; 1095175273Sobrien } 109625839Speter } 109725839Speter else if (r->branchhead) 109825839Speter { 109925839Speter char *branch; 110025839Speter 1101175273Sobrien assert (r->first != NULL); 1102175273Sobrien 110325839Speter /* Print just the head of the branch. */ 110454427Speter if (isdigit ((unsigned char) r->first[0])) 110525839Speter nr->first = RCS_getbranch (rcs, r->first, 1); 110625839Speter else 110725839Speter { 110825839Speter branch = RCS_whatbranch (rcs, r->first); 110925839Speter if (branch == NULL) 1110102840Speter nr->first = NULL; 1111102840Speter else 111225839Speter { 1113102840Speter nr->first = RCS_getbranch (rcs, branch, 1); 1114102840Speter free (branch); 111525839Speter } 111625839Speter } 1117175273Sobrien if (!nr->first) 111825839Speter { 1119175273Sobrien if (!really_quiet) 1120175273Sobrien error (0, 0, "warning: no branch `%s' in `%s'", 1121175273Sobrien r->first, rcs->path); 1122102840Speter nr->last = NULL; 1123102840Speter nr->fields = 0; 112425839Speter } 1125102840Speter else 1126102840Speter { 1127102840Speter nr->last = xstrdup (nr->first); 1128102840Speter nr->fields = numdots (nr->first) + 1; 1129102840Speter } 113025839Speter } 113125839Speter else 113225839Speter { 113354427Speter if (r->first == NULL || isdigit ((unsigned char) r->first[0])) 113425839Speter nr->first = xstrdup (r->first); 113525839Speter else 113625839Speter { 1137175273Sobrien if (baserev && strcmp (r->first, TAG_BASE) == 0) 1138175273Sobrien nr->first = xstrdup (baserev); 1139175273Sobrien else if (RCS_nodeisbranch (rcs, r->first)) 114025839Speter nr->first = RCS_whatbranch (rcs, r->first); 114125839Speter else 114225839Speter nr->first = RCS_gettag (rcs, r->first, 1, (int *) NULL); 1143130303Speter if (nr->first == NULL && !really_quiet) 114425839Speter { 114525839Speter error (0, 0, "warning: no revision `%s' in `%s'", 114625839Speter r->first, rcs->path); 114725839Speter } 114825839Speter } 114925839Speter 1150102840Speter if (r->last == r->first || (r->last != NULL && r->first != NULL && 1151102840Speter strcmp (r->last, r->first) == 0)) 115225839Speter nr->last = xstrdup (nr->first); 115354427Speter else if (r->last == NULL || isdigit ((unsigned char) r->last[0])) 115425839Speter nr->last = xstrdup (r->last); 115525839Speter else 115625839Speter { 1157175273Sobrien if (baserev && strcmp (r->last, TAG_BASE) == 0) 1158175273Sobrien nr->last = xstrdup (baserev); 1159175273Sobrien else if (RCS_nodeisbranch (rcs, r->last)) 116025839Speter nr->last = RCS_whatbranch (rcs, r->last); 116125839Speter else 116225839Speter nr->last = RCS_gettag (rcs, r->last, 1, (int *) NULL); 1163130303Speter if (nr->last == NULL && !really_quiet) 116425839Speter { 116525839Speter error (0, 0, "warning: no revision `%s' in `%s'", 116625839Speter r->last, rcs->path); 116725839Speter } 116825839Speter } 116925839Speter 117025839Speter /* Process the revision numbers the same way that rlog 117125839Speter does. This code is a bit cryptic for my tastes, but 117225839Speter keeping the same implementation as rlog ensures a 117325839Speter certain degree of compatibility. */ 1174128266Speter if (r->first == NULL && nr->last != NULL) 117525839Speter { 1176128266Speter nr->fields = numdots (nr->last) + 1; 1177128266Speter if (nr->fields < 2) 1178128266Speter nr->first = xstrdup (".0"); 117925839Speter else 118025839Speter { 1181128266Speter char *cp; 118225839Speter 1183128266Speter nr->first = xstrdup (nr->last); 1184128266Speter cp = strrchr (nr->first, '.'); 1185175273Sobrien assert (cp); 1186128266Speter strcpy (cp + 1, "0"); 118725839Speter } 118825839Speter } 1189128266Speter else if (r->last == NULL && nr->first != NULL) 119025839Speter { 119125839Speter nr->fields = numdots (nr->first) + 1; 119225839Speter nr->last = xstrdup (nr->first); 119325839Speter if (nr->fields < 2) 119425839Speter nr->last[0] = '\0'; 119525839Speter else 119625839Speter { 119725839Speter char *cp; 119825839Speter 119925839Speter cp = strrchr (nr->last, '.'); 1200175273Sobrien assert (cp); 120125839Speter *cp = '\0'; 120225839Speter } 120325839Speter } 1204107484Speter else if (nr->first == NULL || nr->last == NULL) 1205107484Speter nr->fields = 0; 1206107484Speter else if (strcmp (nr->first, nr->last) == 0) 1207107484Speter nr->fields = numdots (nr->last) + 1; 1208107484Speter else 120925839Speter { 1210107484Speter int ord; 1211107484Speter int dots1 = numdots (nr->first); 1212107484Speter int dots2 = numdots (nr->last); 1213107484Speter if (dots1 > dots2 || (dots1 == dots2 && 1214107484Speter version_compare (nr->first, nr->last, dots1 + 1) > 0)) 121525839Speter { 1216107484Speter char *tmp = nr->first; 1217107484Speter nr->first = nr->last; 1218107484Speter nr->last = tmp; 1219107484Speter nr->fields = dots2 + 1; 1220107484Speter dots2 = dots1; 1221107484Speter dots1 = nr->fields - 1; 1222107484Speter } 1223107484Speter else 1224107484Speter nr->fields = dots1 + 1; 1225107484Speter dots1 += (nr->fields & 1); 1226107484Speter ord = version_compare (nr->first, nr->last, dots1); 1227107484Speter if (ord > 0 || (nr->fields > 2 && ord < 0)) 1228107484Speter { 122925839Speter error (0, 0, 123025839Speter "invalid branch or revision pair %s:%s in `%s'", 123125839Speter r->first, r->last, rcs->path); 123225839Speter free (nr->first); 1233102840Speter nr->first = NULL; 123425839Speter free (nr->last); 1235102840Speter nr->last = NULL; 1236102840Speter nr->fields = 0; 123725839Speter } 1238107484Speter else 123925839Speter { 1240107484Speter if (nr->fields <= dots2 && (nr->fields & 1)) 1241107484Speter { 1242107484Speter char *p = xmalloc (strlen (nr->first) + 3); 1243107484Speter strcpy (p, nr->first); 1244107484Speter strcat (p, ".0"); 1245107484Speter free (nr->first); 1246107484Speter nr->first = p; 1247107484Speter ++nr->fields; 1248107484Speter } 1249107484Speter while (nr->fields <= dots2) 1250107484Speter { 1251107484Speter char *p; 1252107484Speter int i; 125325839Speter 1254107484Speter nr->next = NULL; 1255107484Speter *pr = nr; 1256107484Speter nr = (struct revlist *) xmalloc (sizeof *nr); 1257107484Speter nr->inclusive = 1; 1258107484Speter nr->first = xstrdup ((*pr)->last); 1259107484Speter nr->last = xstrdup ((*pr)->last); 1260107484Speter nr->fields = (*pr)->fields; 1261107484Speter p = (*pr)->last; 1262107484Speter for (i = 0; i < nr->fields; i++) 1263107484Speter p = strchr (p, '.') + 1; 1264107484Speter p[-1] = '\0'; 1265107484Speter p = strchr (nr->first + (p - (*pr)->last), '.'); 1266107484Speter if (p != NULL) 1267107484Speter { 1268107484Speter *++p = '0'; 1269107484Speter *++p = '\0'; 1270107484Speter nr->fields += 2; 1271107484Speter } 1272107484Speter else 1273107484Speter ++nr->fields; 1274107484Speter pr = &(*pr)->next; 1275107484Speter } 127625839Speter } 127725839Speter } 127825839Speter } 127925839Speter 128025839Speter nr->next = NULL; 128125839Speter *pr = nr; 128225839Speter pr = &nr->next; 128325839Speter } 128425839Speter 128525839Speter /* If the default branch was requested, add a revlist entry for 128625839Speter it. This is how rlog handles this option. */ 128725839Speter if (default_branch 128825839Speter && (rcs->head != NULL || rcs->branch != NULL)) 128925839Speter { 129025839Speter struct revlist *nr; 129125839Speter 129225839Speter nr = (struct revlist *) xmalloc (sizeof *nr); 129325839Speter if (rcs->branch != NULL) 129425839Speter nr->first = xstrdup (rcs->branch); 129525839Speter else 129625839Speter { 129725839Speter char *cp; 129825839Speter 129925839Speter nr->first = xstrdup (rcs->head); 1300175273Sobrien assert (nr->first); 130125839Speter cp = strrchr (nr->first, '.'); 1302175273Sobrien assert (cp); 130325839Speter *cp = '\0'; 130425839Speter } 130525839Speter nr->last = xstrdup (nr->first); 130625839Speter nr->fields = numdots (nr->first) + 1; 130781404Speter nr->inclusive = 1; 130825839Speter 130925839Speter nr->next = NULL; 131025839Speter *pr = nr; 131125839Speter } 131225839Speter 131325839Speter return ret; 131425839Speter} 131525839Speter 131625839Speter/* 131725839Speter * Free a revlist created by log_expand_revlist. 131825839Speter */ 131925839Speterstatic void 132025839Speterlog_free_revlist (revlist) 132125839Speter struct revlist *revlist; 132225839Speter{ 132325839Speter struct revlist *r; 132425839Speter 132525839Speter r = revlist; 132625839Speter while (r != NULL) 132725839Speter { 132825839Speter struct revlist *next; 132925839Speter 133025839Speter if (r->first != NULL) 133125839Speter free (r->first); 133225839Speter if (r->last != NULL) 133325839Speter free (r->last); 133425839Speter next = r->next; 133525839Speter free (r); 133625839Speter r = next; 133725839Speter } 133825839Speter} 133925839Speter 134025839Speter/* 134125839Speter * Return nonzero if a revision should be printed, based on the 134225839Speter * options provided. 134325839Speter */ 134425839Speterstatic int 134525839Speterlog_version_requested (log_data, revlist, rcs, vnode) 134625839Speter struct log_data *log_data; 134725839Speter struct revlist *revlist; 134825839Speter RCSNode *rcs; 134925839Speter RCSVers *vnode; 135025839Speter{ 135125839Speter /* Handle the list of states from the -s option. */ 135225839Speter if (log_data->statelist != NULL 135325839Speter && findnode (log_data->statelist, vnode->state) == NULL) 135425839Speter { 135525839Speter return 0; 135625839Speter } 135725839Speter 135825839Speter /* Handle the list of authors from the -w option. */ 135925839Speter if (log_data->authorlist != NULL) 136025839Speter { 136125839Speter if (vnode->author != NULL 136225839Speter && findnode (log_data->authorlist, vnode->author) == NULL) 136325839Speter { 136425839Speter return 0; 136525839Speter } 136625839Speter } 136725839Speter 136825839Speter /* rlog considers all the -d options together when it decides 136925839Speter whether to print a revision, so we must be compatible. */ 137025839Speter if (log_data->datelist != NULL || log_data->singledatelist != NULL) 137125839Speter { 137225839Speter struct datelist *d; 137325839Speter 137425839Speter for (d = log_data->datelist; d != NULL; d = d->next) 137525839Speter { 137625839Speter int cmp; 137725839Speter 137825839Speter cmp = RCS_datecmp (vnode->date, d->start); 137925839Speter if (cmp > 0 || (cmp == 0 && d->inclusive)) 138025839Speter { 138125839Speter cmp = RCS_datecmp (vnode->date, d->end); 138225839Speter if (cmp < 0 || (cmp == 0 && d->inclusive)) 138325839Speter break; 138425839Speter } 138525839Speter } 138625839Speter 138725839Speter if (d == NULL) 138825839Speter { 138925839Speter /* Look through the list of specific dates. We want to 139025839Speter select the revision with the exact date found in the 139125839Speter start field. The commit code ensures that it is 139225839Speter impossible to check in multiple revisions of a single 139325839Speter file in a single second, so checking the date this way 139425839Speter should never select more than one revision. */ 139525839Speter for (d = log_data->singledatelist; d != NULL; d = d->next) 139625839Speter { 139725839Speter if (d->start != NULL 139825839Speter && RCS_datecmp (vnode->date, d->start) == 0) 139925839Speter { 140025839Speter break; 140125839Speter } 140225839Speter } 140325839Speter 140425839Speter if (d == NULL) 140525839Speter return 0; 140625839Speter } 140725839Speter } 140825839Speter 140925839Speter /* If the -r or -b options were used, REVLIST will be non NULL, 141025839Speter and we print the union of the specified revisions. */ 141125839Speter if (revlist != NULL) 141225839Speter { 141325839Speter char *v; 141425839Speter int vfields; 141525839Speter struct revlist *r; 141625839Speter 141725839Speter /* This code is taken from rlog. */ 141825839Speter v = vnode->version; 141925839Speter vfields = numdots (v) + 1; 142025839Speter for (r = revlist; r != NULL; r = r->next) 142125839Speter { 142281404Speter if (vfields == r->fields + (r->fields & 1) && 1423102840Speter (r->inclusive ? version_compare (v, r->first, r->fields) >= 0 : 1424102840Speter version_compare (v, r->first, r->fields) > 0) 1425102840Speter && version_compare (v, r->last, r->fields) <= 0) 142625839Speter { 142725839Speter return 1; 142825839Speter } 142925839Speter } 143025839Speter 143125839Speter /* If we get here, then the -b and/or the -r option was used, 143225839Speter but did not match this revision, so we reject it. */ 143325839Speter 143425839Speter return 0; 143525839Speter } 143625839Speter 143725839Speter /* By default, we print all revisions. */ 143825839Speter return 1; 143925839Speter} 144025839Speter 1441128266Speter 1442128266Speter 144325839Speter/* 144425839Speter * Output a single symbol. This is called via walklist. 144525839Speter */ 144625839Speter/*ARGSUSED*/ 144725839Speterstatic int 144825839Speterlog_symbol (p, closure) 144925839Speter Node *p; 145025839Speter void *closure; 145125839Speter{ 145225839Speter cvs_output ("\n\t", 2); 145325839Speter cvs_output (p->key, 0); 145425839Speter cvs_output (": ", 2); 145525839Speter cvs_output (p->data, 0); 145625839Speter return 0; 145725839Speter} 145825839Speter 1459128266Speter 1460128266Speter 146125839Speter/* 146225839Speter * Count the number of entries on a list. This is called via walklist. 146325839Speter */ 146425839Speter/*ARGSUSED*/ 146525839Speterstatic int 146625839Speterlog_count (p, closure) 146725839Speter Node *p; 146825839Speter void *closure; 146925839Speter{ 147025839Speter return 1; 147125839Speter} 147225839Speter 1473128266Speter 1474128266Speter 147525839Speter/* 147625839Speter * Sort out a single date specification by narrowing down the date 147725839Speter * until we find the specific selected revision. 147825839Speter */ 147925839Speterstatic int 148025839Speterlog_fix_singledate (p, closure) 148125839Speter Node *p; 148225839Speter void *closure; 148325839Speter{ 148425839Speter struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure; 148525839Speter Node *pv; 148625839Speter RCSVers *vnode; 148725839Speter struct datelist *holdsingle, *holddate; 148825839Speter int requested; 148925839Speter 149025839Speter pv = findnode (data->rcs->versions, p->key); 149125839Speter if (pv == NULL) 149225839Speter error (1, 0, "missing version `%s' in RCS file `%s'", 149325839Speter p->key, data->rcs->path); 1494128266Speter vnode = pv->data; 149525839Speter 149625839Speter /* We are only interested if this revision passes any other tests. 149725839Speter Temporarily clear log_data->singledatelist to avoid confusing 149825839Speter log_version_requested. We also clear log_data->datelist, 149925839Speter because rlog considers all the -d options together. We don't 150025839Speter want to reject a revision because it does not match a date pair 150125839Speter if we are going to select it on the basis of the singledate. */ 150225839Speter holdsingle = data->log_data->singledatelist; 150325839Speter data->log_data->singledatelist = NULL; 150425839Speter holddate = data->log_data->datelist; 150525839Speter data->log_data->datelist = NULL; 150625839Speter requested = log_version_requested (data->log_data, data->revlist, 150725839Speter data->rcs, vnode); 150825839Speter data->log_data->singledatelist = holdsingle; 150925839Speter data->log_data->datelist = holddate; 151025839Speter 151125839Speter if (requested) 151225839Speter { 151325839Speter struct datelist *d; 151425839Speter 151525839Speter /* For each single date, if this revision is before the 151625839Speter specified date, but is closer than the previously selected 151725839Speter revision, select it instead. */ 151825839Speter for (d = data->log_data->singledatelist; d != NULL; d = d->next) 151925839Speter { 152025839Speter if (RCS_datecmp (vnode->date, d->end) <= 0 152125839Speter && (d->start == NULL 152225839Speter || RCS_datecmp (vnode->date, d->start) > 0)) 152325839Speter { 152425839Speter if (d->start != NULL) 152525839Speter free (d->start); 152625839Speter d->start = xstrdup (vnode->date); 152725839Speter } 152825839Speter } 152925839Speter } 153025839Speter 153125839Speter return 0; 153225839Speter} 153325839Speter 1534128266Speter 1535128266Speter 153625839Speter/* 153725839Speter * Count the number of revisions we are going to print. 153825839Speter */ 153925839Speterstatic int 154025839Speterlog_count_print (p, closure) 154125839Speter Node *p; 154225839Speter void *closure; 154325839Speter{ 154425839Speter struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure; 154525839Speter Node *pv; 154625839Speter 154725839Speter pv = findnode (data->rcs->versions, p->key); 154825839Speter if (pv == NULL) 154925839Speter error (1, 0, "missing version `%s' in RCS file `%s'", 155025839Speter p->key, data->rcs->path); 155125839Speter if (log_version_requested (data->log_data, data->revlist, data->rcs, 1552128266Speter pv->data)) 155325839Speter return 1; 155425839Speter else 155525839Speter return 0; 155625839Speter} 155725839Speter 155825839Speter/* 155925839Speter * Print the list of changes, not including the trunk, in reverse 156025839Speter * order for each branch. 156125839Speter */ 156225839Speterstatic void 156325839Speterlog_tree (log_data, revlist, rcs, ver) 156425839Speter struct log_data *log_data; 156525839Speter struct revlist *revlist; 156625839Speter RCSNode *rcs; 156725839Speter const char *ver; 156825839Speter{ 156925839Speter Node *p; 157025839Speter RCSVers *vnode; 157125839Speter 157225839Speter p = findnode (rcs->versions, ver); 157325839Speter if (p == NULL) 157425839Speter error (1, 0, "missing version `%s' in RCS file `%s'", 157525839Speter ver, rcs->path); 1576128266Speter vnode = p->data; 157725839Speter if (vnode->next != NULL) 157825839Speter log_tree (log_data, revlist, rcs, vnode->next); 157925839Speter if (vnode->branches != NULL) 158025839Speter { 158125839Speter Node *head, *branch; 158225839Speter 158325839Speter /* We need to do the branches in reverse order. This breaks 158425839Speter the List abstraction, but so does most of the branch 158525839Speter manipulation in rcs.c. */ 158625839Speter head = vnode->branches->list; 158725839Speter for (branch = head->prev; branch != head; branch = branch->prev) 158825839Speter { 158925839Speter log_abranch (log_data, revlist, rcs, branch->key); 159025839Speter log_tree (log_data, revlist, rcs, branch->key); 159125839Speter } 159225839Speter } 159325839Speter} 159425839Speter 159525839Speter/* 159625839Speter * Log the changes for a branch, in reverse order. 159725839Speter */ 159825839Speterstatic void 159925839Speterlog_abranch (log_data, revlist, rcs, ver) 160025839Speter struct log_data *log_data; 160125839Speter struct revlist *revlist; 160225839Speter RCSNode *rcs; 160325839Speter const char *ver; 160425839Speter{ 160525839Speter Node *p; 160625839Speter RCSVers *vnode; 160725839Speter 160825839Speter p = findnode (rcs->versions, ver); 160925839Speter if (p == NULL) 161025839Speter error (1, 0, "missing version `%s' in RCS file `%s'", 161125839Speter ver, rcs->path); 1612128266Speter vnode = p->data; 161325839Speter if (vnode->next != NULL) 161425839Speter log_abranch (log_data, revlist, rcs, vnode->next); 161525839Speter log_version (log_data, revlist, rcs, vnode, 0); 161625839Speter} 161725839Speter 161825839Speter/* 161925839Speter * Print the log output for a single version. 162025839Speter */ 162125839Speterstatic void 162225839Speterlog_version (log_data, revlist, rcs, ver, trunk) 162325839Speter struct log_data *log_data; 162425839Speter struct revlist *revlist; 162525839Speter RCSNode *rcs; 162625839Speter RCSVers *ver; 162725839Speter int trunk; 162825839Speter{ 162925839Speter Node *p; 163025839Speter int year, mon, mday, hour, min, sec; 163125839Speter char buf[100]; 163225839Speter Node *padd, *pdel; 163325839Speter 163425839Speter if (! log_version_requested (log_data, revlist, rcs, ver)) 163525839Speter return; 163625839Speter 163725839Speter cvs_output ("----------------------------\nrevision ", 0); 163825839Speter cvs_output (ver->version, 0); 163925839Speter 164032785Speter p = findnode (RCS_getlocks (rcs), ver->version); 164125839Speter if (p != NULL) 164225839Speter { 164325839Speter cvs_output ("\tlocked by: ", 0); 164425839Speter cvs_output (p->data, 0); 164525839Speter cvs_output (";", 1); 164625839Speter } 164725839Speter 164825839Speter cvs_output ("\ndate: ", 0); 164925839Speter (void) sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min, 165025839Speter &sec); 165125839Speter if (year < 1900) 165225839Speter year += 1900; 1653131688Sdes sprintf (buf, "%04d%c%02d%c%02d %02d:%02d:%02d", 1654131688Sdes year, datesep, mon, datesep, mday, hour, min, sec); 165525839Speter cvs_output (buf, 0); 165625839Speter 165725839Speter cvs_output ("; author: ", 0); 165825839Speter cvs_output (ver->author, 0); 165925839Speter 166025839Speter cvs_output ("; state: ", 0); 166125839Speter cvs_output (ver->state, 0); 166225839Speter cvs_output (";", 1); 166325839Speter 166425839Speter if (! trunk) 166525839Speter { 166625839Speter padd = findnode (ver->other, ";add"); 166725839Speter pdel = findnode (ver->other, ";delete"); 166825839Speter } 166925839Speter else if (ver->next == NULL) 167025839Speter { 167125839Speter padd = NULL; 167225839Speter pdel = NULL; 167325839Speter } 167425839Speter else 167525839Speter { 167625839Speter Node *nextp; 167725839Speter RCSVers *nextver; 167825839Speter 167925839Speter nextp = findnode (rcs->versions, ver->next); 168025839Speter if (nextp == NULL) 168125839Speter error (1, 0, "missing version `%s' in `%s'", ver->next, 168225839Speter rcs->path); 1683128266Speter nextver = nextp->data; 168425839Speter pdel = findnode (nextver->other, ";add"); 168525839Speter padd = findnode (nextver->other, ";delete"); 168625839Speter } 168725839Speter 168825839Speter if (padd != NULL) 168925839Speter { 1690175273Sobrien assert (pdel); 169125839Speter cvs_output (" lines: +", 0); 169225839Speter cvs_output (padd->data, 0); 169325839Speter cvs_output (" -", 2); 169425839Speter cvs_output (pdel->data, 0); 169525839Speter } 169625839Speter 169725839Speter if (ver->branches != NULL) 169825839Speter { 169925839Speter cvs_output ("\nbranches:", 0); 170025839Speter walklist (ver->branches, log_branch, (void *) NULL); 170125839Speter } 170225839Speter 170325839Speter cvs_output ("\n", 1); 170425839Speter 170525839Speter p = findnode (ver->other, "log"); 170632785Speter /* The p->date == NULL case is the normal one for an empty log 170732785Speter message (rcs-14 in sanity.sh). I don't think the case where 170832785Speter p->data is "" can happen (getrcskey in rcs.c checks for an 170932785Speter empty string and set the value to NULL in that case). My guess 171032785Speter would be the p == NULL case would mean an RCS file which was 171132785Speter missing the "log" keyword (which is illegal according to 171232785Speter rcsfile.5). */ 1713128266Speter if (p == NULL || p->data == NULL || *(char *)p->data == '\0') 171425839Speter cvs_output ("*** empty log message ***\n", 0); 171525839Speter else 171625839Speter { 171725839Speter /* FIXME: Technically, the log message could contain a null 171825839Speter byte. */ 171925839Speter cvs_output (p->data, 0); 1720128266Speter if (((char *)p->data)[strlen (p->data) - 1] != '\n') 172125839Speter cvs_output ("\n", 1); 172225839Speter } 172325839Speter} 172425839Speter 172525839Speter/* 172625839Speter * Output a branch version. This is called via walklist. 172725839Speter */ 172825839Speter/*ARGSUSED*/ 172925839Speterstatic int 173025839Speterlog_branch (p, closure) 173125839Speter Node *p; 173225839Speter void *closure; 173325839Speter{ 173425839Speter cvs_output (" ", 2); 173525839Speter if ((numdots (p->key) & 1) == 0) 173625839Speter cvs_output (p->key, 0); 173725839Speter else 173825839Speter { 173925839Speter char *f, *cp; 174025839Speter 174125839Speter f = xstrdup (p->key); 174225839Speter cp = strrchr (f, '.'); 174325839Speter *cp = '\0'; 174425839Speter cvs_output (f, 0); 174525839Speter free (f); 174625839Speter } 174725839Speter cvs_output (";", 1); 174825839Speter return 0; 174925839Speter} 175025839Speter 175125839Speter/* 175217721Speter * Print a warm fuzzy message 175317721Speter */ 175417721Speter/* ARGSUSED */ 175517721Speterstatic Dtype 175625839Speterlog_dirproc (callerdat, dir, repository, update_dir, entries) 175725839Speter void *callerdat; 1758128266Speter const char *dir; 1759128266Speter const char *repository; 1760128266Speter const char *update_dir; 176125839Speter List *entries; 176217721Speter{ 176317721Speter if (!isdir (dir)) 176417721Speter return (R_SKIP_ALL); 176517721Speter 176617721Speter if (!quiet) 176717721Speter error (0, 0, "Logging %s", update_dir); 176817721Speter return (R_PROCESS); 176917721Speter} 177025839Speter 177125839Speter/* 177225839Speter * Compare versions. This is taken from RCS compartial. 177325839Speter */ 177425839Speterstatic int 177525839Speterversion_compare (v1, v2, len) 177625839Speter const char *v1; 177725839Speter const char *v2; 177825839Speter int len; 177925839Speter{ 178025839Speter while (1) 178125839Speter { 178225839Speter int d1, d2, r; 178325839Speter 178425839Speter if (*v1 == '\0') 178525839Speter return 1; 178625839Speter if (*v2 == '\0') 178725839Speter return -1; 178825839Speter 178925839Speter while (*v1 == '0') 179025839Speter ++v1; 179154427Speter for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1) 179225839Speter ; 179325839Speter 179425839Speter while (*v2 == '0') 179525839Speter ++v2; 179654427Speter for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2) 179725839Speter ; 179825839Speter 179925839Speter if (d1 != d2) 180025839Speter return d1 < d2 ? -1 : 1; 180125839Speter 180225839Speter r = memcmp (v1, v2, d1); 180325839Speter if (r != 0) 180425839Speter return r; 180525839Speter 180625839Speter --len; 180725839Speter if (len == 0) 180825839Speter return 0; 180925839Speter 181025839Speter v1 += d1; 181125839Speter v2 += d1; 181225839Speter 181325839Speter if (*v1 == '.') 181425839Speter ++v1; 181525839Speter if (*v2 == '.') 181625839Speter ++v2; 181725839Speter } 181825839Speter} 1819