diff options
Diffstat (limited to 'contrib/cvs/src/history.c')
-rw-r--r-- | contrib/cvs/src/history.c | 1666 |
1 files changed, 0 insertions, 1666 deletions
diff --git a/contrib/cvs/src/history.c b/contrib/cvs/src/history.c deleted file mode 100644 index 78fa0fe..0000000 --- a/contrib/cvs/src/history.c +++ /dev/null @@ -1,1666 +0,0 @@ -/* - * Copyright (C) 1994-2005 The Free Software Foundation, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -/* **************** History of Users and Module **************** - * - * LOGGING: Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY". - * - * On For each Tag, Add, Checkout, Commit, Update or Release command, - * one line of text is written to a History log. - * - * X date | user | CurDir | special | rev(s) | argument '\n' - * - * where: [The spaces in the example line above are not in the history file.] - * - * X is a single character showing the type of event: - * T "Tag" cmd. - * O "Checkout" cmd. - * E "Export" cmd. - * F "Release" cmd. - * W "Update" cmd - No User file, Remove from Entries file. - * U "Update" cmd - File was checked out over User file. - * P "Update" cmd - User file was patched. - * G "Update" cmd - File was merged successfully. - * C "Update" cmd - File was merged and shows overlaps. - * M "Commit" cmd - "Modified" file. - * A "Commit" cmd - "Added" file. - * R "Commit" cmd - "Removed" file. - * - * date is a fixed length 8-char hex representation of a Unix time_t. - * [Starting here, variable fields are delimited by '|' chars.] - * - * user is the username of the person who typed the command. - * - * CurDir The directory where the action occurred. This should be the - * absolute path of the directory which is at the same level as - * the "Repository" field (for W,U,P,G,C & M,A,R). - * - * Repository For record types [W,U,P,G,C,M,A,R] this field holds the - * repository read from the administrative data where the - * command was typed. - * T "A" --> New Tag, "D" --> Delete Tag - * Otherwise it is the Tag or Date to modify. - * O,F,E A "" (null field) - * - * rev(s) Revision number or tag. - * T The Tag to apply. - * O,E The Tag or Date, if specified, else "" (null field). - * F "" (null field) - * W The Tag or Date, if specified, else "" (null field). - * U,P The Revision checked out over the User file. - * G,C The Revision(s) involved in merge. - * M,A,R RCS Revision affected. - * - * argument The module (for [TOEF]) or file (for [WUPGCMAR]) affected. - * - * - *** Report categories: "User" and "Since" modifiers apply to all reports. - * [For "sort" ordering see the "sort_order" routine.] - * - * Extract list of record types - * - * -e, -x [TOEFWUPGCMAR] - * - * Extracted records are simply printed, No analysis is performed. - * All "field" modifiers apply. -e chooses all types. - * - * Checked 'O'ut modules - * - * -o, -w - * Checked out modules. 'F' and 'O' records are examined and if - * the last record for a repository/file is an 'O', a line is - * printed. "-w" forces the "working dir" to be used in the - * comparison instead of the repository. - * - * Committed (Modified) files - * - * -c, -l, -w - * All 'M'odified, 'A'dded and 'R'emoved records are examined. - * "Field" modifiers apply. -l forces a sort by file within user - * and shows only the last modifier. -w works as in Checkout. - * - * Warning: Be careful with what you infer from the output of - * "cvs hi -c -l". It means the last time *you* - * changed the file, not the list of files for which - * you were the last changer!!! - * - * Module history for named modules. - * -m module, -l - * - * This is special. If one or more modules are specified, the - * module names are remembered and the files making up the - * modules are remembered. Only records matching exactly those - * files and repositories are shown. Sorting by "module", then - * filename, is implied. If -l ("last modified") is specified, - * then "update" records (types WUPCG), tag and release records - * are ignored and the last (by date) "modified" record. - * - * TAG history - * - * -T All Tag records are displayed. - * - *** Modifiers. - * - * Since ... [All records contain a timestamp, so any report - * category can be limited by date.] - * - * -D date - The "date" is parsed into a Unix "time_t" and - * records with an earlier time stamp are ignored. - * -r rev/tag - A "rev" begins with a digit. A "tag" does not. If - * you use this option, every file is searched for the - * indicated rev/tag. - * -t tag - The "tag" is searched for in the history file and no - * record is displayed before the tag is found. An - * error is printed if the tag is never found. - * -b string - Records are printed only back to the last reference - * to the string in the "module", "file" or - * "repository" fields. - * - * Field Selections [Simple comparisons on existing fields. All field - * selections are repeatable.] - * - * -a - All users. - * -u user - If no user is given and '-a' is not given, only - * records for the user typing the command are shown. - * ==> If -a or -u is not specified, just use "self". - * - * -f filematch - Only records in which the "file" field contains the - * string "filematch" are considered. - * - * -p repository - Only records in which the "repository" string is a - * prefix of the "repos" field are considered. - * - * -n modulename - Only records which contain "modulename" in the - * "module" field are considered. - * - * - * EXAMPLES: ("cvs history", "cvs his" or "cvs hi") - * - *** Checked out files for username. (default self, e.g. "dgg") - * cvs hi [equivalent to: "cvs hi -o -u dgg"] - * cvs hi -u user [equivalent to: "cvs hi -o -u user"] - * cvs hi -o [equivalent to: "cvs hi -o -u dgg"] - * - *** Committed (modified) files from the beginning of the file. - * cvs hi -c [-u user] - * - *** Committed (modified) files since Midnight, January 1, 1990: - * cvs hi -c -D 'Jan 1 1990' [-u user] - * - *** Committed (modified) files since tag "TAG" was stored in the history file: - * cvs hi -c -t TAG [-u user] - * - *** Committed (modified) files since tag "TAG" was placed on the files: - * cvs hi -c -r TAG [-u user] - * - *** Who last committed file/repository X? - * cvs hi -c -l -[fp] X - * - *** Modified files since tag/date/file/repos? - * cvs hi -c {-r TAG | -D Date | -b string} - * - *** Tag history - * cvs hi -T - * - *** History of file/repository/module X. - * cvs hi -[fpn] X - * - *** History of user "user". - * cvs hi -e -u user - * - *** Dump (eXtract) specified record types - * cvs hi -x [TOEFWUPGCMAR] - * - * - * FUTURE: J[Join], I[Import] (Not currently implemented.) - * - */ - -#include "cvs.h" -#include "history.h" -#include "savecwd.h" - -static struct hrec -{ - char *type; /* Type of record (In history record) */ - char *user; /* Username (In history record) */ - char *dir; /* "Compressed" Working dir (In history record) */ - char *repos; /* (Tag is special.) Repository (In history record) */ - char *rev; /* Revision affected (In history record) */ - char *file; /* Filename (In history record) */ - char *end; /* Ptr into repository to copy at end of workdir */ - char *mod; /* The module within which the file is contained */ - time_t date; /* Calculated from date stored in record */ - long idx; /* Index of record, for "stable" sort. */ -} *hrec_head; -static long hrec_idx; - - -static void fill_hrec PROTO((char *line, struct hrec * hr)); -static int accept_hrec PROTO((struct hrec * hr, struct hrec * lr)); -static int select_hrec PROTO((struct hrec * hr)); -static int sort_order PROTO((const PTR l, const PTR r)); -static int within PROTO((char *find, char *string)); -static void expand_modules PROTO((void)); -static void read_hrecs PROTO((char *fname)); -static void report_hrecs PROTO((void)); -static void save_file PROTO((char *dir, char *name, char *module)); -static void save_module PROTO((char *module)); -static void save_user PROTO((char *name)); - -#define USER_INCREMENT 2 -#define FILE_INCREMENT 128 -#define MODULE_INCREMENT 5 -#define HREC_INCREMENT 128 - -static short report_count; - -static short extract; -static short extract_all; -static short v_checkout; -static short modified; -static short tag_report; -static short module_report; -static short working; -static short last_entry; -static short all_users; - -static short user_sort; -static short repos_sort; -static short file_sort; -static short module_sort; - -static short tz_local; -static time_t tz_seconds_east_of_GMT; -static char *tz_name = "+0000"; - -char *logHistory; - -/* -r, -t, or -b options, malloc'd. These are "" if the option in - question is not specified or is overridden by another option. The - main reason for using "" rather than NULL is historical. Together - with since_date, these are a mutually exclusive set; one overrides the - others. */ -static char *since_rev; -static char *since_tag; -static char *backto; -/* -D option, or 0 if not specified. RCS format. */ -static char * since_date; - -static struct hrec *last_since_tag; -static struct hrec *last_backto; - -/* Record types to look for, malloc'd. Probably could be statically - allocated, but only if we wanted to check for duplicates more than - we do. */ -static char *rec_types; - -static size_t hrec_count; -static size_t hrec_max; - -static char **user_list; /* Ptr to array of ptrs to user names */ -static size_t user_max; /* Number of elements allocated */ -static size_t user_count; /* Number of elements used */ - -static struct file_list_str -{ - char *l_file; - char *l_module; -} *file_list; /* Ptr to array file name structs */ -static size_t file_max; /* Number of elements allocated */ -static size_t file_count; /* Number of elements used */ - -static char **mod_list; /* Ptr to array of ptrs to module names */ -static size_t mod_max; /* Number of elements allocated */ -static size_t mod_count; /* Number of elements used */ - -static char *histfile; /* Ptr to the history file name */ - -/* This is pretty unclear. First of all, separating "flags" vs. - "options" (I think the distinction is that "options" take arguments) - is nonstandard, and not something we do elsewhere in CVS. Second of - all, what does "reports" mean? I think it means that you can only - supply one of those options, but "reports" hardly has that meaning in - a self-explanatory way. */ -static const char *const history_usg[] = -{ - "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n", - " Reports:\n", - " -T Produce report on all TAGs\n", - " -c Committed (Modified) files\n", - " -o Checked out modules\n", - " -m <module> Look for specified module (repeatable)\n", - " -x [" ALL_HISTORY_REC_TYPES "] Extract by record type\n", - " -e Everything (same as -x, but all record types)\n", - " Flags:\n", - " -a All users (Default is self)\n", - " -l Last modified (committed or modified report)\n", - " -w Working directory must match\n", - " Options:\n", - " -D <date> Since date (Many formats)\n", - " -b <str> Back to record with str in module/file/repos field\n", - " -f <file> Specified file (same as command line) (repeatable)\n", - " -n <modulename> In module (repeatable)\n", - " -p <repos> In repository (repeatable)\n", - " -r <rev/tag> Since rev or tag (looks inside RCS files!)\n", - " -t <tag> Since tag record placed in history file (by anyone).\n", - " -u <user> For user name (repeatable)\n", - " -z <tz> Output for time zone <tz> (e.g. -z -0700)\n", - NULL}; - -/* Sort routine for qsort: - - If a user is selected at all, sort it first. User-within-file is useless. - - If a module was selected explicitly, sort next on module. - - Then sort by file. "File" is "repository/file" unless "working" is set, - then it is "workdir/file". (Revision order should always track date.) - - Always sort timestamp last. -*/ -static int -sort_order (l, r) - const PTR l; - const PTR r; -{ - int i; - const struct hrec *left = (const struct hrec *) l; - const struct hrec *right = (const struct hrec *) r; - - if (user_sort) /* If Sort by username, compare users */ - { - if ((i = strcmp (left->user, right->user)) != 0) - return (i); - } - if (module_sort) /* If sort by modules, compare module names */ - { - if (left->mod && right->mod) - if ((i = strcmp (left->mod, right->mod)) != 0) - return (i); - } - if (repos_sort) /* If sort by repository, compare them. */ - { - if ((i = strcmp (left->repos, right->repos)) != 0) - return (i); - } - if (file_sort) /* If sort by filename, compare files, NOT dirs. */ - { - if ((i = strcmp (left->file, right->file)) != 0) - return (i); - - if (working) - { - if ((i = strcmp (left->dir, right->dir)) != 0) - return (i); - - if ((i = strcmp (left->end, right->end)) != 0) - return (i); - } - } - - /* - * By default, sort by date, time - * XXX: This fails after 2030 when date slides into sign bit - */ - if ((i = ((long) (left->date) - (long) (right->date))) != 0) - return (i); - - /* For matching dates, keep the sort stable by using record index */ - return (left->idx - right->idx); -} - -int -history (argc, argv) - int argc; - char **argv; -{ - int i, c; - char *fname; - - if (argc == -1) - usage (history_usg); - - since_rev = xstrdup (""); - since_tag = xstrdup (""); - backto = xstrdup (""); - rec_types = xstrdup (""); - optind = 0; - while ((c = getopt (argc, argv, "+Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != -1) - { - switch (c) - { - case 'T': /* Tag list */ - report_count++; - tag_report++; - break; - case 'a': /* For all usernames */ - all_users++; - break; - case 'c': - report_count++; - modified = 1; - break; - case 'e': - report_count++; - extract_all++; - free (rec_types); - rec_types = xstrdup (ALL_HISTORY_REC_TYPES); - break; - case 'l': /* Find Last file record */ - last_entry = 1; - break; - case 'o': - report_count++; - v_checkout = 1; - break; - case 'w': /* Match Working Dir (CurDir) fields */ - working = 1; - break; - case 'X': /* Undocumented debugging flag */ -#ifdef DEBUG - histfile = optarg; -#endif - break; - - case 'D': /* Since specified date */ - if (*since_rev || *since_tag || *backto) - { - error (0, 0, "date overriding rev/tag/backto"); - *since_rev = *since_tag = *backto = '\0'; - } - since_date = Make_Date (optarg); - break; - case 'b': /* Since specified file/Repos */ - if (since_date || *since_rev || *since_tag) - { - error (0, 0, "backto overriding date/rev/tag"); - *since_rev = *since_tag = '\0'; - if (since_date != NULL) - free (since_date); - since_date = NULL; - } - free (backto); - backto = xstrdup (optarg); - break; - case 'f': /* For specified file */ - save_file (NULL, optarg, NULL); - break; - case 'm': /* Full module report */ - if (!module_report++) report_count++; - /* fall through */ - case 'n': /* Look for specified module */ - save_module (optarg); - break; - case 'p': /* For specified directory */ - save_file (optarg, NULL, NULL); - break; - case 'r': /* Since specified Tag/Rev */ - if (since_date || *since_tag || *backto) - { - error (0, 0, "rev overriding date/tag/backto"); - *since_tag = *backto = '\0'; - if (since_date != NULL) - free (since_date); - since_date = NULL; - } - free (since_rev); - since_rev = xstrdup (optarg); - break; - case 't': /* Since specified Tag/Rev */ - if (since_date || *since_rev || *backto) - { - error (0, 0, "tag overriding date/marker/file/repos"); - *since_rev = *backto = '\0'; - if (since_date != NULL) - free (since_date); - since_date = NULL; - } - free (since_tag); - since_tag = xstrdup (optarg); - break; - case 'u': /* For specified username */ - save_user (optarg); - break; - case 'x': - report_count++; - extract++; - { - char *cp; - - for (cp = optarg; *cp; cp++) - if (!strchr (ALL_HISTORY_REC_TYPES, *cp)) - error (1, 0, "%c is not a valid report type", *cp); - } - free (rec_types); - rec_types = xstrdup (optarg); - break; - case 'z': - tz_local = - (optarg[0] == 'l' || optarg[0] == 'L') - && (optarg[1] == 't' || optarg[1] == 'T') - && !optarg[2]; - if (tz_local) - tz_name = optarg; - else - { - /* - * Convert a known time with the given timezone to time_t. - * Use the epoch + 23 hours, so timezones east of GMT work. - */ - static char f[] = "1/1/1970 23:00 %s"; - char *buf = xmalloc (sizeof (f) - 2 + strlen (optarg)); - time_t t; - sprintf (buf, f, optarg); - t = get_date (buf, (struct timeb *) NULL); - free (buf); - if (t == (time_t) -1) - error (0, 0, "%s is not a known time zone", optarg); - else - { - /* - * Convert to seconds east of GMT, removing the - * 23-hour offset mentioned above. - */ - tz_seconds_east_of_GMT = (time_t)23 * 60 * 60 - t; - tz_name = optarg; - } - } - break; - case '?': - default: - usage (history_usg); - break; - } - } - argc -= optind; - argv += optind; - for (i = 0; i < argc; i++) - save_file (NULL, argv[i], NULL); - - - /* ================ Now analyze the arguments a bit */ - if (!report_count) - v_checkout++; - else if (report_count > 1) - error (1, 0, "Only one report type allowed from: \"-Tcomxe\"."); - -#ifdef CLIENT_SUPPORT - if (current_parsed_root->isremote) - { - struct file_list_str *f1; - char **mod; - - /* We're the client side. Fire up the remote server. */ - start_server (); - - ign_setup (); - - if (tag_report) - send_arg("-T"); - if (all_users) - send_arg("-a"); - if (modified) - send_arg("-c"); - if (last_entry) - send_arg("-l"); - if (v_checkout) - send_arg("-o"); - if (working) - send_arg("-w"); - if (histfile) - send_arg("-X"); - if (since_date) - client_senddate (since_date); - if (backto[0] != '\0') - option_with_arg ("-b", backto); - for (f1 = file_list; f1 < &file_list[file_count]; ++f1) - { - if (f1->l_file[0] == '*') - option_with_arg ("-p", f1->l_file + 1); - else - option_with_arg ("-f", f1->l_file); - } - if (module_report) - send_arg("-m"); - for (mod = mod_list; mod < &mod_list[mod_count]; ++mod) - option_with_arg ("-n", *mod); - if (*since_rev) - option_with_arg ("-r", since_rev); - if (*since_tag) - option_with_arg ("-t", since_tag); - for (mod = user_list; mod < &user_list[user_count]; ++mod) - option_with_arg ("-u", *mod); - if (extract_all) - send_arg("-e"); - if (extract) - option_with_arg ("-x", rec_types); - option_with_arg ("-z", tz_name); - - send_to_server ("history\012", 0); - return get_responses_and_close (); - } -#endif - - if (all_users) - save_user (""); - - if (mod_list) - expand_modules (); - - if (tag_report) - { - if (!strchr (rec_types, 'T')) - { - rec_types = xrealloc (rec_types, strlen (rec_types) + 5); - (void) strcat (rec_types, "T"); - } - } - else if (extract || extract_all) - { - if (user_list) - user_sort++; - } - else if (modified) - { - free (rec_types); - rec_types = xstrdup ("MAR"); - /* - * If the user has not specified a date oriented flag ("Since"), sort - * by Repository/file before date. Default is "just" date. - */ - if (last_entry - || (!since_date && !*since_rev && !*since_tag && !*backto)) - { - repos_sort++; - file_sort++; - /* - * If we are not looking for last_modified and the user specified - * one or more users to look at, sort by user before filename. - */ - if (!last_entry && user_list) - user_sort++; - } - } - else if (module_report) - { - free (rec_types); - rec_types = xstrdup (last_entry ? "OMAR" : ALL_HISTORY_REC_TYPES); - module_sort++; - repos_sort++; - file_sort++; - working = 0; /* User's workdir doesn't count here */ - } - else - /* Must be "checkout" or default */ - { - free (rec_types); - rec_types = xstrdup ("OF"); - /* See comments in "modified" above */ - if (!last_entry && user_list) - user_sort++; - if (last_entry - || (!since_date && !*since_rev && !*since_tag && !*backto)) - file_sort++; - } - - /* If no users were specified, use self (-a saves a universal ("") user) */ - if (!user_list) - save_user (getcaller ()); - - /* If we're looking back to a Tag value, must consider "Tag" records */ - if (*since_tag && !strchr (rec_types, 'T')) - { - rec_types = xrealloc (rec_types, strlen (rec_types) + 5); - (void) strcat (rec_types, "T"); - } - - if (histfile) - fname = xstrdup (histfile); - else - { - fname = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM) - + sizeof (CVSROOTADM_HISTORY) + 10); - (void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory, - CVSROOTADM, CVSROOTADM_HISTORY); - } - - read_hrecs (fname); - if(hrec_count>0) - { - qsort ((PTR) hrec_head, hrec_count, - sizeof (struct hrec), sort_order); - } - report_hrecs (); - free (fname); - if (since_date != NULL) - free (since_date); - free (since_rev); - free (since_tag); - free (backto); - free (rec_types); - - return (0); -} - -void -history_write (type, update_dir, revs, name, repository) - int type; - const char *update_dir; - const char *revs; - const char *name; - const char *repository; -{ - char *fname; - char *workdir; - char *username = getcaller (); - int fd; - char *line; - char *slash = "", *cp; - const char *cp2, *repos; - int i; - static char *tilde = ""; - static char *PrCurDir = NULL; - - if (logoff) /* History is turned off by noexec or - * readonlyfs. - */ - return; - if (strchr (logHistory, type) == NULL) - return; - fname = xmalloc (strlen (current_parsed_root->directory) - + sizeof (CVSROOTADM) - + sizeof (CVSROOTADM_HISTORY) + 3); - (void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory, - CVSROOTADM, CVSROOTADM_HISTORY); - - /* turn off history logging if the history file does not exist */ - /* FIXME: This should check for write permissions instead. This way, - * O_CREATE could be added back into the call to open() below and - * there would be no race condition involved in log rotation. - * - * Note that the new method of turning off logging would be either via - * the CVSROOT/config file (probably the quicker method, but would need - * to be added, or at least checked for, too) or by creating a dummy - * history file with 0444 permissions. - */ - if (!isfile (fname)) - { - logoff = 1; - goto out; - } - - if (trace) - fprintf (stderr, "%s-> fopen(%s,a)\n", - CLIENT_SERVER_STR, fname); - if (noexec) - goto out; - - if (!history_lock (current_parsed_root->directory)) - /* history_lock() will already have printed an error on failure. */ - goto out; - - fd = CVS_OPEN (fname, O_WRONLY | O_APPEND | OPEN_BINARY, 0666); - if (fd < 0) - { - if (! really_quiet) - { - error (0, errno, "warning: cannot write to history file %s", - fname); - } - goto out; - } - - repos = Short_Repository (repository); - - if (!PrCurDir) - { - char *pwdir; - - pwdir = get_homedir (); - PrCurDir = CurDir; - if (pwdir != NULL) - { - /* Assumes neither CurDir nor pwdir ends in '/' */ - i = strlen (pwdir); - if (!strncmp (CurDir, pwdir, i)) - { - PrCurDir += i; /* Point to '/' separator */ - tilde = "~"; - } - else - { - /* Try harder to find a "homedir" */ - struct saved_cwd cwd; - char *homedir; - - if (save_cwd (&cwd)) - error_exit (); - - if (CVS_CHDIR (pwdir) < 0 || (homedir = xgetwd ()) == NULL) - homedir = pwdir; - - if (restore_cwd (&cwd, NULL)) - error_exit (); - free_cwd (&cwd); - - i = strlen (homedir); - if (!strncmp (CurDir, homedir, i)) - { - PrCurDir += i; /* Point to '/' separator */ - tilde = "~"; - } - - if (homedir != pwdir) - free (homedir); - } - } - } - - if (type == 'T') - { - repos = update_dir; - update_dir = ""; - } - else if (update_dir && *update_dir) - slash = "/"; - else - update_dir = ""; - - workdir = xmalloc (strlen (tilde) + strlen (PrCurDir) + strlen (slash) - + strlen (update_dir) + 10); - (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir); - - /* - * "workdir" is the directory where the file "name" is. ("^~" == $HOME) - * "repos" is the Repository, relative to $CVSROOT where the RCS file is. - * - * "$workdir/$name" is the working file name. - * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository. - * - * First, note that the history format was intended to save space, not - * to be human readable. - * - * The working file directory ("workdir") and the Repository ("repos") - * usually end with the same one or more directory elements. To avoid - * duplication (and save space), the "workdir" field ends with - * an integer offset into the "repos" field. This offset indicates the - * beginning of the "tail" of "repos", after which all characters are - * duplicates. - * - * In other words, if the "workdir" field has a '*' (a very stupid thing - * to put in a filename) in it, then every thing following the last '*' - * is a hex offset into "repos" of the first character from "repos" to - * append to "workdir" to finish the pathname. - * - * It might be easier to look at an example: - * - * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo - * - * Indicates that the workdir is really "~/work/cvs/examples", saving - * 10 characters, where "~/work*d" would save 6 characters and mean that - * the workdir is really "~/work/examples". It will mean more on - * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term - * - * "workdir" is always an absolute pathname (~/xxx is an absolute path) - * "repos" is always a relative pathname. So we can assume that we will - * never run into the top of "workdir" -- there will always be a '/' or - * a '~' at the head of "workdir" that is not matched by anything in - * "repos". On the other hand, we *can* run off the top of "repos". - * - * Only "compress" if we save characters. - */ - - if (!repos) - repos = ""; - - cp = workdir + strlen (workdir) - 1; - cp2 = repos + strlen (repos) - 1; - for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--) - i++; - - if (i > 2) - { - i = strlen (repos) - i; - (void) sprintf ((cp + 1), "*%x", i); - } - - if (!revs) - revs = ""; - line = xmalloc (strlen (username) + strlen (workdir) + strlen (repos) - + strlen (revs) + strlen (name) + 100); - sprintf (line, "%c%08lx|%s|%s|%s|%s|%s\n", - type, (long) time ((time_t *) NULL), - username, workdir, repos, revs, name); - - /* Lessen some race conditions on non-Posix-compliant hosts. */ - if (lseek (fd, (off_t) 0, SEEK_END) == -1) - error (1, errno, "cannot seek to end of history file: %s", fname); - - if (write (fd, line, strlen (line)) < 0) - error (1, errno, "cannot write to history file: %s", fname); - free (line); - if (close (fd) != 0) - error (1, errno, "cannot close history file: %s", fname); - free (workdir); - out: - clear_history_lock (); - free (fname); -} - -/* - * save_user() adds a user name to the user list to select. Zero-length - * username ("") matches any user. - */ -static void -save_user (name) - char *name; -{ - if (user_count == user_max) - { - user_max = xsum (user_max, USER_INCREMENT); - if (user_count == user_max - || size_overflow_p (xtimes (user_max, sizeof (char *)))) - { - error (0, 0, "save_user: too many users"); - return; - } - user_list = xrealloc (user_list, xtimes (user_max, sizeof (char *))); - } - user_list[user_count++] = xstrdup (name); -} - -/* - * save_file() adds file name and associated module to the file list to select. - * - * If "dir" is null, store a file name as is. - * If "name" is null, store a directory name with a '*' on the front. - * Else, store concatenated "dir/name". - * - * Later, in the "select" stage: - * - if it starts with '*', it is prefix-matched against the repository. - * - if it has a '/' in it, it is matched against the repository/file. - * - else it is matched against the file name. - */ -static void -save_file (dir, name, module) - char *dir; - char *name; - char *module; -{ - char *cp; - struct file_list_str *fl; - - if (file_count == file_max) - { - file_max = xsum (file_max, FILE_INCREMENT); - if (file_count == file_max - || size_overflow_p (xtimes (file_max, sizeof (*fl)))) - { - error (0, 0, "save_file: too many files"); - return; - } - file_list = xrealloc (file_list, xtimes (file_max, sizeof (*fl))); - } - fl = &file_list[file_count++]; - fl->l_file = cp = xmalloc (dir ? strlen (dir) : 0 - + name ? strlen (name) : 0 - + 2); - fl->l_module = module; - - if (dir && *dir) - { - if (name && *name) - { - (void) strcpy (cp, dir); - (void) strcat (cp, "/"); - (void) strcat (cp, name); - } - else - { - *cp++ = '*'; - (void) strcpy (cp, dir); - } - } - else - { - if (name && *name) - { - (void) strcpy (cp, name); - } - else - { - error (0, 0, "save_file: null dir and file name"); - } - } -} - -static void -save_module (module) - char *module; -{ - if (mod_count == mod_max) - { - mod_max = xsum (mod_max, MODULE_INCREMENT); - if (mod_count == mod_max - || size_overflow_p (xtimes (mod_max, sizeof (char *)))) - { - error (0, 0, "save_module: too many modules"); - return; - } - mod_list = xrealloc (mod_list, xtimes (mod_max, sizeof (char *))); - } - mod_list[mod_count++] = xstrdup (module); -} - -static void -expand_modules () -{ -} - -/* fill_hrec - * - * Take a ptr to 7-part history line, ending with a newline, for example: - * - * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo - * - * Split it into 7 parts and drop the parts into a "struct hrec". - * Return a pointer to the character following the newline. - * - */ - -#define NEXT_BAR(here) do { \ - while (isspace(*line)) line++; \ - hr->here = line; \ - while ((c = *line++) && c != '|') ; \ - if (!c) return; line[-1] = '\0'; \ - } while (0) - -static void -fill_hrec (line, hr) - char *line; - struct hrec *hr; -{ - char *cp; - int c; - - hr->type = hr->user = hr->dir = hr->repos = hr->rev = hr->file = - hr->end = hr->mod = NULL; - hr->date = -1; - hr->idx = ++hrec_idx; - - while (isspace ((unsigned char) *line)) - line++; - - hr->type = line++; - hr->date = strtoul (line, &cp, 16); - if (cp == line || *cp != '|') - return; - line = cp + 1; - NEXT_BAR (user); - NEXT_BAR (dir); - if ((cp = strrchr (hr->dir, '*')) != NULL) - { - *cp++ = '\0'; - hr->end = line + strtoul (cp, NULL, 16); - } - else - hr->end = line - 1; /* A handy pointer to '\0' */ - NEXT_BAR (repos); - NEXT_BAR (rev); - if (strchr ("FOET", *(hr->type))) - hr->mod = line; - - NEXT_BAR (file); -} - - -#ifndef STAT_BLOCKSIZE -#if HAVE_STRUCT_STAT_ST_BLKSIZE -#define STAT_BLOCKSIZE(s) (s).st_blksize -#else -#define STAT_BLOCKSIZE(s) (4 * 1024) -#endif -#endif - - -/* read_hrecs's job is to read the history file and fill in all the "hrec" - * (history record) array elements with the ones we need to print. - * - * Logic: - * - Read a block from the file. - * - Walk through the block parsing line into hr records. - * - if the hr isn't used, free its strings, if it is, bump the hrec counter - * - at the end of a block, copy the end of the current block to the start - * of space for the next block, then read in the next block. If we get less - * than the whole block, we're done. - */ -static void -read_hrecs (fname) - char *fname; -{ - unsigned char *cpstart, *cpend, *cp, *nl; - char *hrline; - int i; - int fd; - struct stat st_buf; - - if ((fd = CVS_OPEN (fname, O_RDONLY | OPEN_BINARY)) < 0) - error (1, errno, "cannot open history file: %s", fname); - - if (fstat (fd, &st_buf) < 0) - error (1, errno, "can't stat history file"); - - if (!(st_buf.st_size)) - error (1, 0, "history file is empty"); - - cpstart = xmalloc (2 * STAT_BLOCKSIZE(st_buf)); - cpstart[0] = '\0'; - cp = cpend = cpstart; - - hrec_max = HREC_INCREMENT; - hrec_head = xmalloc (hrec_max * sizeof (struct hrec)); - hrec_idx = 0; - - for (;;) - { - for (nl = cp; nl < cpend && *nl != '\n'; nl++) - if (!isprint(*nl)) *nl = ' '; - - if (nl >= cpend) - { - if (nl - cp >= STAT_BLOCKSIZE(st_buf)) - { - error(1, 0, "history line %ld too long (> %lu)", hrec_idx + 1, - (unsigned long) STAT_BLOCKSIZE(st_buf)); - } - if (nl > cp) - memmove (cpstart, cp, nl - cp); - nl = cpstart + (nl - cp); - cp = cpstart; - i = read (fd, nl, STAT_BLOCKSIZE(st_buf)); - if (i > 0) - { - cpend = nl + i; - *cpend = '\0'; - continue; - } - if (i < 0) - error (1, errno, "error reading history file"); - if (nl == cp) break; - error (0, 0, "warning: no newline at end of history file"); - } - *nl = '\0'; - - if (hrec_count == hrec_max) - { - struct hrec *old_head = hrec_head; - - hrec_max = xsum (hrec_max, HREC_INCREMENT); - if (hrec_count == hrec_max - || size_overflow_p (xtimes (hrec_max, sizeof (struct hrec)))) - error (1, 0, "Too many history records in history file."); - - hrec_head = xrealloc (hrec_head, - xtimes (hrec_max, sizeof (struct hrec))); - if (last_since_tag) - last_since_tag = hrec_head + (last_since_tag - old_head); - if (last_backto) - last_backto = hrec_head + (last_backto - old_head); - } - - /* fill_hrec dates from when history read the entire - history file in one chunk, and then records were pulled out - by pointing to the various parts of this big chunk. This is - why there are ugly hacks here: I don't want to completely - re-write the whole history stuff right now. */ - - hrline = xstrdup ((char *)cp); - fill_hrec (hrline, &hrec_head[hrec_count]); - if (select_hrec (&hrec_head[hrec_count])) - hrec_count++; - else - free(hrline); - - cp = nl + 1; - } - free (cpstart); - close (fd); - - /* Special selection problem: If "since_tag" is set, we have saved every - * record from the 1st occurrence of "since_tag", when we want to save - * records since the *last* occurrence of "since_tag". So what we have - * to do is bump hrec_head forward and reduce hrec_count accordingly. - */ - if (last_since_tag) - { - hrec_count -= (last_since_tag - hrec_head); - hrec_head = last_since_tag; - } - - /* Much the same thing is necessary for the "backto" option. */ - if (last_backto) - { - hrec_count -= (last_backto - hrec_head); - hrec_head = last_backto; - } -} - -/* Utility program for determining whether "find" is inside "string" */ -static int -within (find, string) - char *find, *string; -{ - int c, len; - - if (!find || !string) - return (0); - - c = *find++; - len = strlen (find); - - while (*string) - { - if (!(string = strchr (string, c))) - return (0); - string++; - if (!strncmp (find, string, len)) - return (1); - } - return (0); -} - -/* The purpose of "select_hrec" is to apply the selection criteria based on - * the command arguments and defaults and return a flag indicating whether - * this record should be remembered for printing. - */ -static int -select_hrec (hr) - struct hrec *hr; -{ - char **cpp, *cp, *cp2; - struct file_list_str *fl; - int count; - - /* basic validity checking */ - if (!hr->type || !hr->user || !hr->dir || !hr->repos || !hr->rev || - !hr->file || !hr->end) - { - error (0, 0, "warning: history line %ld invalid", hr->idx); - return (0); - } - - /* "Since" checking: The argument parser guarantees that only one of the - * following four choices is set: - * - * 1. If "since_date" is set, it contains the date specified on the - * command line. hr->date fields earlier than "since_date" are ignored. - * 2. If "since_rev" is set, it contains either an RCS "dotted" revision - * number (which is of limited use) or a symbolic TAG. Each RCS file - * is examined and the date on the specified revision (or the revision - * corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is - * compared against hr->date as in 1. above. - * 3. If "since_tag" is set, matching tag records are saved. The field - * "last_since_tag" is set to the last one of these. Since we don't - * know where the last one will be, all records are saved from the - * first occurrence of the TAG. Later, at the end of "select_hrec" - * records before the last occurrence of "since_tag" are skipped. - * 4. If "backto" is set, all records with a module name or file name - * matching "backto" are saved. In addition, all records with a - * repository field with a *prefix* matching "backto" are saved. - * The field "last_backto" is set to the last one of these. As in - * 3. above, "select_hrec" adjusts to include the last one later on. - */ - if (since_date) - { - char *ourdate = date_from_time_t (hr->date); - count = RCS_datecmp (ourdate, since_date); - free (ourdate); - if (count < 0) - return (0); - } - else if (*since_rev) - { - Vers_TS *vers; - time_t t; - struct file_info finfo; - - memset (&finfo, 0, sizeof finfo); - finfo.file = hr->file; - /* Not used, so don't worry about it. */ - finfo.update_dir = NULL; - finfo.fullname = finfo.file; - finfo.repository = hr->repos; - finfo.entries = NULL; - finfo.rcs = NULL; - - vers = Version_TS (&finfo, (char *) NULL, since_rev, (char *) NULL, - 1, 0); - if (vers->vn_rcs) - { - if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0)) - != (time_t) 0) - { - if (hr->date < t) - { - freevers_ts (&vers); - return (0); - } - } - } - freevers_ts (&vers); - } - else if (*since_tag) - { - if (*(hr->type) == 'T') - { - /* - * A 'T'ag record, the "rev" field holds the tag to be set, - * while the "repos" field holds "D"elete, "A"dd or a rev. - */ - if (within (since_tag, hr->rev)) - { - last_since_tag = hr; - return (1); - } - else - return (0); - } - if (!last_since_tag) - return (0); - } - else if (*backto) - { - if (within (backto, hr->file) || within (backto, hr->mod) || - within (backto, hr->repos)) - last_backto = hr; - else - return (0); - } - - /* User checking: - * - * Run down "user_list", match username ("" matches anything) - * If "" is not there and actual username is not there, return failure. - */ - if (user_list && hr->user) - { - for (cpp = user_list, count = user_count; count; cpp++, count--) - { - if (!**cpp) - break; /* null user == accept */ - if (!strcmp (hr->user, *cpp)) /* found listed user */ - break; - } - if (!count) - return (0); /* Not this user */ - } - - /* Record type checking: - * - * 1. If Record type is not in rec_types field, skip it. - * 2. If mod_list is null, keep everything. Otherwise keep only modules - * on mod_list. - * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list". If - * file_list is null, keep everything. Otherwise, keep only files on - * file_list, matched appropriately. - */ - if (!strchr (rec_types, *(hr->type))) - return (0); - if (!strchr ("TFOE", *(hr->type))) /* Don't bother with "file" if "TFOE" */ - { - if (file_list) /* If file_list is null, accept all */ - { - for (fl = file_list, count = file_count; count; fl++, count--) - { - /* 1. If file_list entry starts with '*', skip the '*' and - * compare it against the repository in the hrec. - * 2. If file_list entry has a '/' in it, compare it against - * the concatenation of the repository and file from hrec. - * 3. Else compare the file_list entry against the hrec file. - */ - char *cmpfile = NULL; - - if (*(cp = fl->l_file) == '*') - { - cp++; - /* if argument to -p is a prefix of repository */ - if (!strncmp (cp, hr->repos, strlen (cp))) - { - hr->mod = fl->l_module; - break; - } - } - else - { - if (strchr (cp, '/')) - { - cmpfile = xmalloc (strlen (hr->repos) - + strlen (hr->file) - + 10); - (void) sprintf (cmpfile, "%s/%s", - hr->repos, hr->file); - cp2 = cmpfile; - } - else - { - cp2 = hr->file; - } - - /* if requested file is found within {repos}/file fields */ - if (within (cp, cp2)) - { - hr->mod = fl->l_module; - if (cmpfile != NULL) - free (cmpfile); - break; - } - if (cmpfile != NULL) - free (cmpfile); - } - } - if (!count) - return (0); /* String specified and no match */ - } - } - if (mod_list) - { - for (cpp = mod_list, count = mod_count; count; cpp++, count--) - { - if (hr->mod && !strcmp (hr->mod, *cpp)) /* found module */ - break; - } - if (!count) - return (0); /* Module specified & this record is not one of them. */ - } - - return (1); /* Select this record unless rejected above. */ -} - -/* The "sort_order" routine (when handed to qsort) has arranged for the - * hrecs files to be in the right order for the report. - * - * Most of the "selections" are done in the select_hrec routine, but some - * selections are more easily done after the qsort by "accept_hrec". - */ -static void -report_hrecs () -{ - struct hrec *hr, *lr; - struct tm *tm; - int i, count, ty; - char *cp; - int user_len, file_len, rev_len, mod_len, repos_len; - - if (*since_tag && !last_since_tag) - { - (void) printf ("No tag found: %s\n", since_tag); - return; - } - else if (*backto && !last_backto) - { - (void) printf ("No module, file or repository with: %s\n", backto); - return; - } - else if (hrec_count < 1) - { - (void) printf ("No records selected.\n"); - return; - } - - user_len = file_len = rev_len = mod_len = repos_len = 0; - - /* Run through lists and find maximum field widths */ - hr = lr = hrec_head; - hr++; - for (count = hrec_count; count--; lr = hr, hr++) - { - char *repos; - - if (!count) - hr = NULL; - if (!accept_hrec (lr, hr)) - continue; - - ty = *(lr->type); - repos = xstrdup (lr->repos); - if ((cp = strrchr (repos, '/')) != NULL) - { - if (lr->mod && !strcmp (++cp, lr->mod)) - { - (void) strcpy (cp, "*"); - } - } - if ((i = strlen (lr->user)) > user_len) - user_len = i; - if ((i = strlen (lr->file)) > file_len) - file_len = i; - if (ty != 'T' && (i = strlen (repos)) > repos_len) - repos_len = i; - if (ty != 'T' && (i = strlen (lr->rev)) > rev_len) - rev_len = i; - if (lr->mod && (i = strlen (lr->mod)) > mod_len) - mod_len = i; - free (repos); - } - - /* Walk through hrec array setting "lr" (Last Record) to each element. - * "hr" points to the record following "lr" -- It is NULL in the last - * pass. - * - * There are two sections in the loop below: - * 1. Based on the report type (e.g. extract, checkout, tag, etc.), - * decide whether the record should be printed. - * 2. Based on the record type, format and print the data. - */ - for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++) - { - char *workdir; - char *repos; - - if (!hrec_count) - hr = NULL; - if (!accept_hrec (lr, hr)) - continue; - - ty = *(lr->type); - if (!tz_local) - { - time_t t = lr->date + tz_seconds_east_of_GMT; - tm = gmtime (&t); - } - else - tm = localtime (&(lr->date)); - - (void) printf ("%c %04d-%02d-%02d %02d:%02d %s %-*s", ty, - tm->tm_year+1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, - tm->tm_min, tz_name, user_len, lr->user); - - workdir = xmalloc (strlen (lr->dir) + strlen (lr->end) + 10); - (void) sprintf (workdir, "%s%s", lr->dir, lr->end); - if ((cp = strrchr (workdir, '/')) != NULL) - { - if (lr->mod && !strcmp (++cp, lr->mod)) - { - (void) strcpy (cp, "*"); - } - } - repos = xmalloc (strlen (lr->repos) + 10); - (void) strcpy (repos, lr->repos); - if ((cp = strrchr (repos, '/')) != NULL) - { - if (lr->mod && !strcmp (++cp, lr->mod)) - { - (void) strcpy (cp, "*"); - } - } - - switch (ty) - { - case 'T': - /* 'T'ag records: repository is a "tag type", rev is the tag */ - (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev, - repos); - if (working) - (void) printf (" {%s}", workdir); - break; - case 'F': - case 'E': - case 'O': - if (lr->rev && *(lr->rev)) - (void) printf (" [%s]", lr->rev); - (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod, - mod_len + 1 - (int) strlen (lr->mod), - "=", workdir); - break; - case 'W': - case 'U': - case 'P': - case 'C': - case 'G': - case 'M': - case 'A': - case 'R': - (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev, - file_len, lr->file, repos_len, repos, - lr->mod ? lr->mod : "", workdir); - break; - default: - (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty); - break; - } - (void) putchar ('\n'); - free (workdir); - free (repos); - } -} - -static int -accept_hrec (lr, hr) - struct hrec *hr, *lr; -{ - int ty; - - ty = *(lr->type); - - if (last_since_tag && ty == 'T') - return (1); - - if (v_checkout) - { - if (ty != 'O') - return (0); /* Only interested in 'O' records */ - - /* We want to identify all the states that cause the next record - * ("hr") to be different from the current one ("lr") and only - * print a line at the allowed boundaries. - */ - - if (!hr || /* The last record */ - strcmp (hr->user, lr->user) || /* User has changed */ - strcmp (hr->mod, lr->mod) ||/* Module has changed */ - (working && /* If must match "workdir" */ - (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */ - strcmp (hr->end, lr->end)))) /* the 2nd parts differ */ - - return (1); - } - else if (modified) - { - if (!last_entry || /* Don't want only last rec */ - !hr || /* Last entry is a "last entry" */ - strcmp (hr->repos, lr->repos) || /* Repository has changed */ - strcmp (hr->file, lr->file))/* File has changed */ - return (1); - - if (working) - { /* If must match "workdir" */ - if (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */ - strcmp (hr->end, lr->end)) /* the 2nd parts differ */ - return (1); - } - } - else if (module_report) - { - if (!last_entry || /* Don't want only last rec */ - !hr || /* Last entry is a "last entry" */ - strcmp (hr->mod, lr->mod) ||/* Module has changed */ - strcmp (hr->repos, lr->repos) || /* Repository has changed */ - strcmp (hr->file, lr->file))/* File has changed */ - return (1); - } - else - { - /* "extract" and "tag_report" always print selected records. */ - return (1); - } - - return (0); -} |