home *** CD-ROM | disk | FTP | other *** search
/ Geek Gadgets 1 / ADE-1.bin / ade-dist / cvs-1.8.7-src.tgz / tar.out / fsf / cvs / src / history.c < prev    next >
C/C++ Source or Header  |  1996-09-28  |  42KB  |  1,497 lines

  1. /*
  2.  *
  3.  *    You may distribute under the terms of the GNU General Public License
  4.  *    as specified in the README file that comes with the CVS 1.0 kit.
  5.  *
  6.  * **************** History of Users and Module ****************
  7.  *
  8.  * LOGGING:  Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY".
  9.  *
  10.  * On For each Tag, Add, Checkout, Commit, Update or Release command,
  11.  * one line of text is written to a History log.
  12.  *
  13.  *    X date | user | CurDir | special | rev(s) | argument '\n'
  14.  *
  15.  * where: [The spaces in the example line above are not in the history file.]
  16.  *
  17.  *  X        is a single character showing the type of event:
  18.  *        T    "Tag" cmd.
  19.  *        O    "Checkout" cmd.
  20.  *        F    "Release" cmd.
  21.  *        W    "Update" cmd - No User file, Remove from Entries file.
  22.  *        U    "Update" cmd - File was checked out over User file.
  23.  *        G    "Update" cmd - File was merged successfully.
  24.  *        C    "Update" cmd - File was merged and shows overlaps.
  25.  *        M    "Commit" cmd - "Modified" file.
  26.  *        A    "Commit" cmd - "Added" file.
  27.  *        R    "Commit" cmd - "Removed" file.
  28.  *
  29.  *  date    is a fixed length 8-char hex representation of a Unix time_t.
  30.  *        [Starting here, variable fields are delimited by '|' chars.]
  31.  *
  32.  *  user    is the username of the person who typed the command.
  33.  *
  34.  *  CurDir    The directory where the action occurred.  This should be the
  35.  *        absolute path of the directory which is at the same level as
  36.  *        the "Repository" field (for W,U,G,C & M,A,R).
  37.  *
  38.  *  Repository    For record types [W,U,G,C,M,A,R] this field holds the
  39.  *        repository read from the administrative data where the
  40.  *        command was typed.
  41.  *        T    "A" --> New Tag, "D" --> Delete Tag
  42.  *            Otherwise it is the Tag or Date to modify.
  43.  *        O,F    A "" (null field)
  44.  *
  45.  *  rev(s)    Revision number or tag.
  46.  *        T    The Tag to apply.
  47.  *        O    The Tag or Date, if specified, else "" (null field).
  48.  *        F    "" (null field)
  49.  *        W    The Tag or Date, if specified, else "" (null field).
  50.  *        U    The Revision checked out over the User file.
  51.  *        G,C    The Revision(s) involved in merge.
  52.  *        M,A,R    RCS Revision affected.
  53.  *
  54.  *  argument    The module (for [TOUF]) or file (for [WUGCMAR]) affected.
  55.  *
  56.  *
  57.  *** Report categories: "User" and "Since" modifiers apply to all reports.
  58.  *            [For "sort" ordering see the "sort_order" routine.]
  59.  *
  60.  *   Extract list of record types
  61.  *
  62.  *    -e, -x [TOFWUGCMAR]
  63.  *
  64.  *        Extracted records are simply printed, No analysis is performed.
  65.  *        All "field" modifiers apply.  -e chooses all types.
  66.  *
  67.  *   Checked 'O'ut modules
  68.  *
  69.  *    -o, -w
  70.  *        Checked out modules.  'F' and 'O' records are examined and if
  71.  *        the last record for a repository/file is an 'O', a line is
  72.  *        printed.  "-w" forces the "working dir" to be used in the
  73.  *        comparison instead of the repository.
  74.  *
  75.  *   Committed (Modified) files
  76.  *
  77.  *    -c, -l, -w
  78.  *        All 'M'odified, 'A'dded and 'R'emoved records are examined.
  79.  *        "Field" modifiers apply.  -l forces a sort by file within user
  80.  *        and shows only the last modifier.  -w works as in Checkout.
  81.  *
  82.  *        Warning: Be careful with what you infer from the output of
  83.  *             "cvs hi -c -l".  It means the last time *you*
  84.  *             changed the file, not the list of files for which
  85.  *             you were the last changer!!!
  86.  *
  87.  *   Module history for named modules.
  88.  *    -m module, -l
  89.  *
  90.  *        This is special.  If one or more modules are specified, the
  91.  *        module names are remembered and the files making up the
  92.  *        modules are remembered.  Only records matching exactly those
  93.  *        files and repositories are shown.  Sorting by "module", then
  94.  *        filename, is implied.  If -l ("last modified") is specified,
  95.  *        then "update" records (types WUCG), tag and release records
  96.  *        are ignored and the last (by date) "modified" record.
  97.  *
  98.  *   TAG history
  99.  *
  100.  *    -T    All Tag records are displayed.
  101.  *
  102.  *** Modifiers.
  103.  *
  104.  *   Since ...        [All records contain a timestamp, so any report
  105.  *             category can be limited by date.]
  106.  *
  107.  *    -D date        - The "date" is parsed into a Unix "time_t" and
  108.  *              records with an earlier time stamp are ignored.
  109.  *    -r rev/tag    - A "rev" begins with a digit.  A "tag" does not.  If
  110.  *              you use this option, every file is searched for the
  111.  *              indicated rev/tag.
  112.  *    -t tag        - The "tag" is searched for in the history file and no
  113.  *              record is displayed before the tag is found.  An
  114.  *              error is printed if the tag is never found.
  115.  *    -b string    - Records are printed only back to the last reference
  116.  *              to the string in the "module", "file" or
  117.  *              "repository" fields.
  118.  *
  119.  *   Field Selections    [Simple comparisons on existing fields.  All field
  120.  *             selections are repeatable.]
  121.  *
  122.  *    -a        - All users.
  123.  *    -u user        - If no user is given and '-a' is not given, only
  124.  *              records for the user typing the command are shown.
  125.  *              ==> If -a or -u is not specified, just use "self".
  126.  *
  127.  *    -f filematch    - Only records in which the "file" field contains the
  128.  *              string "filematch" are considered.
  129.  *
  130.  *    -p repository    - Only records in which the "repository" string is a
  131.  *              prefix of the "repos" field are considered.
  132.  *
  133.  *    -m modulename    - Only records which contain "modulename" in the
  134.  *              "module" field are considered.
  135.  *
  136.  *
  137.  * EXAMPLES: ("cvs history", "cvs his" or "cvs hi")
  138.  *
  139.  *** Checked out files for username.  (default self, e.g. "dgg")
  140.  *    cvs hi            [equivalent to: "cvs hi -o -u dgg"]
  141.  *    cvs hi -u user        [equivalent to: "cvs hi -o -u user"]
  142.  *    cvs hi -o        [equivalent to: "cvs hi -o -u dgg"]
  143.  *
  144.  *** Committed (modified) files from the beginning of the file.
  145.  *    cvs hi -c [-u user]
  146.  *
  147.  *** Committed (modified) files since Midnight, January 1, 1990:
  148.  *    cvs hi -c -D 'Jan 1 1990' [-u user]
  149.  *
  150.  *** Committed (modified) files since tag "TAG" was stored in the history file:
  151.  *    cvs hi -c -t TAG [-u user]
  152.  *
  153.  *** Committed (modified) files since tag "TAG" was placed on the files:
  154.  *    cvs hi -c -r TAG [-u user]
  155.  *
  156.  *** Who last committed file/repository X?
  157.  *    cvs hi -c -l -[fp] X
  158.  *
  159.  *** Modified files since tag/date/file/repos?
  160.  *    cvs hi -c {-r TAG | -D Date | -b string}
  161.  *
  162.  *** Tag history
  163.  *    cvs hi -T
  164.  *
  165.  *** History of file/repository/module X.
  166.  *    cvs hi -[fpn] X
  167.  *
  168.  *** History of user "user".
  169.  *    cvs hi -e -u user
  170.  *
  171.  *** Dump (eXtract) specified record types
  172.  *    cvs hi -x [TOFWUGCMAR]
  173.  *
  174.  *
  175.  * FUTURE:        J[Join], I[Import]  (Not currently implemented.)
  176.  *
  177.  */
  178.  
  179. #include "cvs.h"
  180.  
  181. static struct hrec
  182. {
  183.     char *type;        /* Type of record (In history record) */
  184.     char *user;        /* Username (In history record) */
  185.     char *dir;        /* "Compressed" Working dir (In history record) */
  186.     char *repos;    /* (Tag is special.) Repository (In history record) */
  187.     char *rev;        /* Revision affected (In history record) */
  188.     char *file;        /* Filename (In history record) */
  189.     char *end;        /* Ptr into repository to copy at end of workdir */
  190.     char *mod;        /* The module within which the file is contained */
  191.     time_t date;    /* Calculated from date stored in record */
  192.     int idx;        /* Index of record, for "stable" sort. */
  193. } *hrec_head;
  194.  
  195.  
  196. static char *fill_hrec PROTO((char *line, struct hrec * hr));
  197. static int accept_hrec PROTO((struct hrec * hr, struct hrec * lr));
  198. static int select_hrec PROTO((struct hrec * hr));
  199. static int sort_order PROTO((const PTR l, const PTR r));
  200. static int within PROTO((char *find, char *string));
  201. static time_t date_and_time PROTO((char *date_str));
  202. static void expand_modules PROTO((void));
  203. static void read_hrecs PROTO((char *fname));
  204. static void report_hrecs PROTO((void));
  205. static void save_file PROTO((char *dir, char *name, char *module));
  206. static void save_module PROTO((char *module));
  207. static void save_user PROTO((char *name));
  208.  
  209. #define ALL_REC_TYPES "TOFWUCGMAR"
  210. #define USER_INCREMENT    2
  211. #define FILE_INCREMENT    128
  212. #define MODULE_INCREMENT 5
  213. #define HREC_INCREMENT    128
  214.  
  215. static short report_count;
  216.  
  217. static short extract;
  218. static short v_checkout;
  219. static short modified;
  220. static short tag_report;
  221. static short module_report;
  222. static short working;
  223. static short last_entry;
  224. static short all_users;
  225.  
  226. static short user_sort;
  227. static short repos_sort;
  228. static short file_sort;
  229. static short module_sort;
  230.  
  231. #ifdef HAVE_RCS5
  232. static short tz_local;
  233. static time_t tz_seconds_east_of_GMT;
  234. static char *tz_name = "+0000";
  235. #else
  236. static char tz_name[] = "LT";
  237. #endif
  238.  
  239. static time_t since_date;
  240. static char since_rev[20];    /* Maxrev ~= 99.99.99.999 */
  241. static char since_tag[64];
  242. static struct hrec *last_since_tag;
  243. static char backto[128];
  244. static struct hrec *last_backto;
  245. static char rec_types[20];
  246.  
  247. static int hrec_count;
  248. static int hrec_max;
  249.  
  250. static char **user_list;    /* Ptr to array of ptrs to user names */
  251. static int user_max;        /* Number of elements allocated */
  252. static int user_count;        /* Number of elements used */
  253.  
  254. static struct file_list_str
  255. {
  256.     char *l_file;
  257.     char *l_module;
  258. } *file_list;            /* Ptr to array file name structs */
  259. static int file_max;        /* Number of elements allocated */
  260. static int file_count;        /* Number of elements used */
  261.  
  262. static char **mod_list;        /* Ptr to array of ptrs to module names */
  263. static int mod_max;        /* Number of elements allocated */
  264. static int mod_count;        /* Number of elements used */
  265.  
  266. static char *histfile;        /* Ptr to the history file name */
  267.  
  268. static const char *const history_usg[] =
  269. {
  270.     "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n",
  271.     "   Reports:\n",
  272.     "        -T              Produce report on all TAGs\n",
  273.     "        -c              Committed (Modified) files\n",
  274.     "        -o              Checked out modules\n",
  275.     "        -m <module>     Look for specified module (repeatable)\n",
  276.     "        -x [TOFWUCGMAR] Extract by record type\n",
  277.     "   Flags:\n",
  278.     "        -a              All users (Default is self)\n",
  279.     "        -e              Everything (same as -x, but all record types)\n",
  280.     "        -l              Last modified (committed or modified report)\n",
  281.     "        -w              Working directory must match\n",
  282.     "   Options:\n",
  283.     "        -D <date>       Since date (Many formats)\n",
  284.     "        -b <str>        Back to record with str in module/file/repos field\n",
  285.     "        -f <file>       Specified file (same as command line) (repeatable)\n",
  286.     "        -n <modulename> In module (repeatable)\n",
  287.     "        -p <repos>      In repository (repeatable)\n",
  288.     "        -r <rev/tag>    Since rev or tag (looks inside RCS files!)\n",
  289.     "        -t <tag>        Since tag record placed in history file (by anyone).\n",
  290.     "        -u <user>       For user name (repeatable)\n",
  291.     "        -z <tz>         Output for time zone <tz> (e.g. -z -0700)\n",
  292.     NULL};
  293.  
  294. /* Sort routine for qsort:
  295.    - If a user is selected at all, sort it first. User-within-file is useless.
  296.    - If a module was selected explicitly, sort next on module.
  297.    - Then sort by file.  "File" is "repository/file" unless "working" is set,
  298.      then it is "workdir/file".  (Revision order should always track date.)
  299.    - Always sort timestamp last.
  300. */
  301. static int
  302. sort_order (l, r)
  303.     const PTR l;
  304.     const PTR r;
  305. {
  306.     int i;
  307.     const struct hrec *left = (const struct hrec *) l;
  308.     const struct hrec *right = (const struct hrec *) r;
  309.  
  310.     if (user_sort)    /* If Sort by username, compare users */
  311.     {
  312.     if ((i = strcmp (left->user, right->user)) != 0)
  313.         return (i);
  314.     }
  315.     if (module_sort)    /* If sort by modules, compare module names */
  316.     {
  317.     if (left->mod && right->mod)
  318.         if ((i = strcmp (left->mod, right->mod)) != 0)
  319.         return (i);
  320.     }
  321.     if (repos_sort)    /* If sort by repository, compare them. */
  322.     {
  323.     if ((i = strcmp (left->repos, right->repos)) != 0)
  324.         return (i);
  325.     }
  326.     if (file_sort)    /* If sort by filename, compare files, NOT dirs. */
  327.     {
  328.     if ((i = strcmp (left->file, right->file)) != 0)
  329.         return (i);
  330.  
  331.     if (working)
  332.     {
  333.         if ((i = strcmp (left->dir, right->dir)) != 0)
  334.         return (i);
  335.  
  336.         if ((i = strcmp (left->end, right->end)) != 0)
  337.         return (i);
  338.     }
  339.     }
  340.  
  341.     /*
  342.      * By default, sort by date, time
  343.      * XXX: This fails after 2030 when date slides into sign bit
  344.      */
  345.     if ((i = ((long) (left->date) - (long) (right->date))) != 0)
  346.     return (i);
  347.  
  348.     /* For matching dates, keep the sort stable by using record index */
  349.     return (left->idx - right->idx);
  350. }
  351.  
  352. static time_t
  353. date_and_time (date_str)
  354.     char *date_str;
  355. {
  356.     time_t t;
  357.  
  358.     t = get_date (date_str, (struct timeb *) NULL);
  359.     if (t == (time_t) - 1)
  360.     error (1, 0, "Can't parse date/time: %s", date_str);
  361.     return (t);
  362. }
  363.  
  364. int
  365. history (argc, argv)
  366.     int argc;
  367.     char **argv;
  368. {
  369.     int i, c;
  370.     char fname[PATH_MAX];
  371.  
  372.     if (argc == -1)
  373.     usage (history_usg);
  374.  
  375.     optind = 1;
  376.     while ((c = getopt (argc, argv, "Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != -1)
  377.     {
  378.     switch (c)
  379.     {
  380.         case 'T':            /* Tag list */
  381.         report_count++;
  382.         tag_report++;
  383.         break;
  384.         case 'a':            /* For all usernames */
  385.         all_users++;
  386.         break;
  387.         case 'c':
  388.         report_count++;
  389.         modified = 1;
  390.         break;
  391.         case 'e':
  392.         report_count++;
  393.         extract++;
  394.         (void) strcpy (rec_types, ALL_REC_TYPES);
  395.         break;
  396.         case 'l':            /* Find Last file record */
  397.         last_entry = 1;
  398.         break;
  399.         case 'o':
  400.         report_count++;
  401.         v_checkout = 1;
  402.         break;
  403.         case 'w':            /* Match Working Dir (CurDir) fields */
  404.         working = 1;
  405.         break;
  406.         case 'X':            /* Undocumented debugging flag */
  407.         histfile = optarg;
  408.         break;
  409.         case 'D':            /* Since specified date */
  410.         if (*since_rev || *since_tag || *backto)
  411.         {
  412.             error (0, 0, "date overriding rev/tag/backto");
  413.             *since_rev = *since_tag = *backto = '\0';
  414.         }
  415.         since_date = date_and_time (optarg);
  416.         break;
  417.         case 'b':            /* Since specified file/Repos */
  418.         if (since_date || *since_rev || *since_tag)
  419.         {
  420.             error (0, 0, "backto overriding date/rev/tag");
  421.             *since_rev = *since_tag = '\0';
  422.             since_date = 0;
  423.         }
  424.         if (strlen (optarg) >= sizeof (backto))
  425.         {
  426.             error (0, 0, "backto truncated to %d bytes",
  427.                (int) sizeof (backto) - 1);
  428.             optarg[sizeof (backto) - 1] = '\0';
  429.         }
  430.         (void) strcpy (backto, optarg);
  431.         break;
  432.         case 'f':            /* For specified file */
  433.         save_file ("", optarg, (char *) NULL);
  434.         break;
  435.         case 'm':            /* Full module report */
  436.         report_count++;
  437.         module_report++;
  438.         case 'n':            /* Look for specified module */
  439.         save_module (optarg);
  440.         break;
  441.         case 'p':            /* For specified directory */
  442.         save_file (optarg, "", (char *) NULL);
  443.         break;
  444.         case 'r':            /* Since specified Tag/Rev */
  445.         if (since_date || *since_tag || *backto)
  446.         {
  447.             error (0, 0, "rev overriding date/tag/backto");
  448.             *since_tag = *backto = '\0';
  449.             since_date = 0;
  450.         }
  451.         (void) strcpy (since_rev, optarg);
  452.         break;
  453.         case 't':            /* Since specified Tag/Rev */
  454.         if (since_date || *since_rev || *backto)
  455.         {
  456.             error (0, 0, "tag overriding date/marker/file/repos");
  457.             *since_rev = *backto = '\0';
  458.             since_date = 0;
  459.         }
  460.         (void) strcpy (since_tag, optarg);    /* tag */
  461.         break;
  462.         case 'u':            /* For specified username */
  463.         save_user (optarg);
  464.         break;
  465.         case 'x':
  466.         report_count++;
  467.         extract++;
  468.         {
  469.             char *cp;
  470.  
  471.             for (cp = optarg; *cp; cp++)
  472.             if (!strchr (ALL_REC_TYPES, *cp))
  473.                 error (1, 0, "%c is not a valid report type", *cp);
  474.         }
  475.         (void) strcpy (rec_types, optarg);
  476.         break;
  477.         case 'z':
  478. #ifndef HAVE_RCS5
  479.         error (0, 0, "-z not supported with RCS 4");
  480. #else
  481.         tz_local = 
  482.             (optarg[0] == 'l' || optarg[0] == 'L')
  483.             && (optarg[1] == 't' || optarg[1] == 'T')
  484.             && !optarg[2];
  485.         if (tz_local)
  486.             tz_name = optarg;
  487.         else
  488.         {
  489.             /*
  490.              * Convert a known time with the given timezone to time_t.
  491.              * Use the epoch + 23 hours, so timezones east of GMT work.
  492.              */
  493.             static char f[] = "1/1/1970 23:00 %s";
  494.             char *buf = xmalloc (sizeof (f) - 2 + strlen (optarg));
  495.             time_t t;
  496.             sprintf (buf, f, optarg);
  497.             t = get_date (buf, (struct timeb *) NULL);
  498.             free (buf);
  499.             if (t == (time_t) -1)
  500.             error (0, 0, "%s is not a known time zone", optarg);
  501.             else
  502.             {
  503.             /*
  504.              * Convert to seconds east of GMT, removing the
  505.              * 23-hour offset mentioned above.
  506.              */
  507.             tz_seconds_east_of_GMT = (time_t)23 * 60 * 60  -  t;
  508.             tz_name = optarg;
  509.             }
  510.         }
  511. #endif
  512.         break;
  513.         case '?':
  514.         default:
  515.         usage (history_usg);
  516.         break;
  517.     }
  518.     }
  519.     c = optind;                /* Save the handled option count */
  520.  
  521.     /* ================ Now analyze the arguments a bit */
  522.     if (!report_count)
  523.     v_checkout++;
  524.     else if (report_count > 1)
  525.     error (1, 0, "Only one report type allowed from: \"-Tcomx\".");
  526.  
  527. #ifdef CLIENT_SUPPORT
  528.     if (client_active)
  529.     {
  530.     struct file_list_str *f1;
  531.     char **mod;
  532.  
  533.     /* We're the client side.  Fire up the remote server.  */
  534.     start_server ();
  535.     
  536.     ign_setup ();
  537.  
  538.     if (tag_report)
  539.         send_arg("-T");
  540.     if (all_users)
  541.         send_arg("-a");
  542.     if (modified)
  543.         send_arg("-c");
  544.     if (last_entry)
  545.         send_arg("-l");
  546.     if (v_checkout)
  547.         send_arg("-o");
  548.     if (working)
  549.         send_arg("-w");
  550.     if (histfile)
  551.         send_arg("-X");
  552.     if (since_date)
  553.         option_with_arg ("-D", asctime (gmtime (&since_date)));
  554.     if (backto[0] != '\0')
  555.         option_with_arg ("-b", backto);
  556.     for (f1 = file_list; f1 < &file_list[file_count]; ++f1)
  557.     {
  558.         if (f1->l_file[0] == '*')
  559.         option_with_arg ("-p", f1->l_file + 1);
  560.         else
  561.         option_with_arg ("-f", f1->l_file);
  562.     }
  563.     if (module_report)
  564.         send_arg("-m");
  565.     for (mod = mod_list; mod < &mod_list[mod_count]; ++mod)
  566.         option_with_arg ("-n", *mod);
  567.     if (*since_rev)
  568.         option_with_arg ("-r", since_rev);
  569.     if (*since_tag)
  570.         option_with_arg ("-t", since_tag);
  571.     for (mod = user_list; mod < &user_list[user_count]; ++mod)
  572.         option_with_arg ("-u", *mod);
  573.     if (extract)
  574.         option_with_arg ("-x", rec_types);
  575.     option_with_arg ("-z", tz_name);
  576.  
  577.     send_to_server ("history\012", 0);
  578.         return get_responses_and_close ();
  579.     }
  580. #endif
  581.  
  582.     if (all_users)
  583.     save_user ("");
  584.  
  585.     if (mod_list)
  586.     expand_modules ();
  587.  
  588.     if (tag_report)
  589.     {
  590.     if (!strchr (rec_types, 'T'))
  591.         (void) strcat (rec_types, "T");
  592.     }
  593.     else if (extract)
  594.     {
  595.     if (user_list)
  596.         user_sort++;
  597.     }
  598.     else if (modified)
  599.     {
  600.     (void) strcpy (rec_types, "MAR");
  601.     /*
  602.      * If the user has not specified a date oriented flag ("Since"), sort
  603.      * by Repository/file before date.  Default is "just" date.
  604.      */
  605.     if (!since_date && !*since_rev && !*since_tag && !*backto)
  606.     {
  607.         repos_sort++;
  608.         file_sort++;
  609.         /*
  610.          * If we are not looking for last_modified and the user specified
  611.          * one or more users to look at, sort by user before filename.
  612.          */
  613.         if (!last_entry && user_list)
  614.         user_sort++;
  615.     }
  616.     }
  617.     else if (module_report)
  618.     {
  619.     (void) strcpy (rec_types, last_entry ? "OMAR" : ALL_REC_TYPES);
  620.     module_sort++;
  621.     repos_sort++;
  622.     file_sort++;
  623.     working = 0;            /* User's workdir doesn't count here */
  624.     }
  625.     else
  626.     /* Must be "checkout" or default */
  627.     {
  628.     (void) strcpy (rec_types, "OF");
  629.     /* See comments in "modified" above */
  630.     if (!last_entry && user_list)
  631.         user_sort++;
  632.     if (!since_date && !*since_rev && !*since_tag && !*backto)
  633.         file_sort++;
  634.     }
  635.  
  636.     /* If no users were specified, use self (-a saves a universal ("") user) */
  637.     if (!user_list)
  638.     save_user (getcaller ());
  639.  
  640.     /* If we're looking back to a Tag value, must consider "Tag" records */
  641.     if (*since_tag && !strchr (rec_types, 'T'))
  642.     (void) strcat (rec_types, "T");
  643.  
  644.     argc -= c;
  645.     argv += c;
  646.     for (i = 0; i < argc; i++)
  647.     save_file ("", argv[i], (char *) NULL);
  648.  
  649.     if (histfile)
  650.     (void) strcpy (fname, histfile);
  651.     else
  652.     (void) sprintf (fname, "%s/%s/%s", CVSroot_directory,
  653.             CVSROOTADM, CVSROOTADM_HISTORY);
  654.  
  655.     read_hrecs (fname);
  656.     qsort ((PTR) hrec_head, hrec_count, sizeof (struct hrec), sort_order);
  657.     report_hrecs ();
  658.  
  659.     return (0);
  660. }
  661.  
  662. void
  663. history_write (type, update_dir, revs, name, repository)
  664.     int type;
  665.     char *update_dir;
  666.     char *revs;
  667.     char *name;
  668.     char *repository;
  669. {
  670.     char fname[PATH_MAX], workdir[PATH_MAX], homedir[PATH_MAX];
  671.     char *username = getcaller ();
  672.     int fd;
  673.     char *line;
  674.     char *slash = "", *cp, *cp2, *repos;
  675.     int i;
  676.     static char *tilde = "";
  677.     static char *PrCurDir = NULL;
  678.  
  679.     if (logoff)            /* History is turned off by cmd line switch */
  680.     return;
  681.     (void) sprintf (fname, "%s/%s/%s", CVSroot_directory,
  682.             CVSROOTADM, CVSROOTADM_HISTORY);
  683.  
  684.     /* turn off history logging if the history file does not exist */
  685.     if (!isfile (fname))
  686.     {
  687.     logoff = 1;
  688.     return;
  689.     }
  690.  
  691.     if (trace)
  692. #ifdef SERVER_SUPPORT
  693.     fprintf (stderr, "%c-> fopen(%s,a)\n",
  694.          (server_active) ? 'S' : ' ', fname);
  695. #else
  696.     fprintf (stderr, "-> fopen(%s,a)\n", fname);
  697. #endif
  698.     if (noexec)
  699.     return;
  700.     fd = CVS_OPEN (fname, O_WRONLY | O_APPEND | O_CREAT | OPEN_BINARY, 0666);
  701.     if (fd < 0)
  702.     error (1, errno, "cannot open history file: %s", fname);
  703.  
  704.     repos = Short_Repository (repository);
  705.  
  706.     if (!PrCurDir)
  707.     {
  708.     char *pwdir;
  709.  
  710.     pwdir = get_homedir ();
  711.     PrCurDir = CurDir;
  712.     if (pwdir != NULL)
  713.     {
  714.         /* Assumes neither CurDir nor pwdir ends in '/' */
  715.         i = strlen (pwdir);
  716.         if (!strncmp (CurDir, pwdir, i))
  717.         {
  718.         PrCurDir += i;        /* Point to '/' separator */
  719.         tilde = "~";
  720.         }
  721.         else
  722.         {
  723.         /* Try harder to find a "homedir" */
  724.         if (!getwd (workdir))
  725.             error (1, errno, "can't getwd in history");
  726.         if ( CVS_CHDIR (pwdir) < 0)
  727.             error (1, errno, "can't chdir(%s)", pwdir);
  728.         if (!getwd (homedir))
  729.             error (1, errno, "can't getwd in %s", pwdir);
  730.         (void) CVS_CHDIR (workdir);
  731.  
  732.         i = strlen (homedir);
  733.         if (!strncmp (CurDir, homedir, i))
  734.         {
  735.             PrCurDir += i;    /* Point to '/' separator */
  736.             tilde = "~";
  737.         }
  738.         }
  739.     }
  740.     }
  741.  
  742.     if (type == 'T')
  743.     {
  744.     repos = update_dir;
  745.     update_dir = "";
  746.     }
  747.     else if (update_dir && *update_dir)
  748.     slash = "/";
  749.     else
  750.     update_dir = "";
  751.  
  752.     (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir);
  753.  
  754.     /*
  755.      * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
  756.      * "repos"    is the Repository, relative to $CVSROOT where the RCS file is.
  757.      *
  758.      * "$workdir/$name" is the working file name.
  759.      * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
  760.      *
  761.      * First, note that the history format was intended to save space, not
  762.      * to be human readable.
  763.      *
  764.      * The working file directory ("workdir") and the Repository ("repos")
  765.      * usually end with the same one or more directory elements.  To avoid
  766.      * duplication (and save space), the "workdir" field ends with
  767.      * an integer offset into the "repos" field.  This offset indicates the
  768.      * beginning of the "tail" of "repos", after which all characters are
  769.      * duplicates.
  770.      *
  771.      * In other words, if the "workdir" field has a '*' (a very stupid thing
  772.      * to put in a filename) in it, then every thing following the last '*'
  773.      * is a hex offset into "repos" of the first character from "repos" to
  774.      * append to "workdir" to finish the pathname.
  775.      *
  776.      * It might be easier to look at an example:
  777.      *
  778.      *  M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
  779.      *
  780.      * Indicates that the workdir is really "~/work/cvs/examples", saving
  781.      * 10 characters, where "~/work*d" would save 6 characters and mean that
  782.      * the workdir is really "~/work/examples".  It will mean more on
  783.      * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
  784.      *
  785.      * "workdir" is always an absolute pathname (~/xxx is an absolute path)
  786.      * "repos" is always a relative pathname.  So we can assume that we will
  787.      * never run into the top of "workdir" -- there will always be a '/' or
  788.      * a '~' at the head of "workdir" that is not matched by anything in
  789.      * "repos".  On the other hand, we *can* run off the top of "repos".
  790.      *
  791.      * Only "compress" if we save characters.
  792.      */
  793.  
  794.     if (!repos)
  795.     repos = "";
  796.  
  797.     cp = workdir + strlen (workdir) - 1;
  798.     cp2 = repos + strlen (repos) - 1;
  799.     for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
  800.     i++;
  801.  
  802.     if (i > 2)
  803.     {
  804.     i = strlen (repos) - i;
  805.     (void) sprintf ((cp + 1), "*%x", i);
  806.     }
  807.  
  808.     if (!revs)
  809.     revs = "";
  810.     line = xmalloc (strlen (username) + strlen (workdir) + strlen (repos)
  811.             + strlen (revs) + strlen (name) + 100);
  812.     sprintf (line, "%c%08lx|%s|%s|%s|%s|%s\n",
  813.          type, (long) time ((time_t *) NULL),
  814.          username, workdir, repos, revs, name);
  815.  
  816.     /* Lessen some race conditions on non-Posix-compliant hosts.  */
  817.     if (lseek (fd, (off_t) 0, SEEK_END) == -1)
  818.     error (1, errno, "cannot seek to end of history file: %s", fname);
  819.  
  820.     if (write (fd, line, strlen (line)) < 0)
  821.     error (1, errno, "cannot write to history file: %s", fname);
  822.     free (line);
  823.     if (close (fd) != 0)
  824.     error (1, errno, "cannot close history file: %s", fname);
  825. }
  826.  
  827. /*
  828.  * save_user() adds a user name to the user list to select.  Zero-length
  829.  *        username ("") matches any user.
  830.  */
  831. static void
  832. save_user (name)
  833.     char *name;
  834. {
  835.     if (user_count == user_max)
  836.     {
  837.     user_max += USER_INCREMENT;
  838.     user_list = (char **) xrealloc ((char *) user_list,
  839.                     (int) user_max * sizeof (char *));
  840.     }
  841.     user_list[user_count++] = xstrdup (name);
  842. }
  843.  
  844. /*
  845.  * save_file() adds file name and associated module to the file list to select.
  846.  *
  847.  * If "dir" is null, store a file name as is.
  848.  * If "name" is null, store a directory name with a '*' on the front.
  849.  * Else, store concatenated "dir/name".
  850.  *
  851.  * Later, in the "select" stage:
  852.  *    - if it starts with '*', it is prefix-matched against the repository.
  853.  *    - if it has a '/' in it, it is matched against the repository/file.
  854.  *    - else it is matched against the file name.
  855.  */
  856. static void
  857. save_file (dir, name, module)
  858.     char *dir;
  859.     char *name;
  860.     char *module;
  861. {
  862.     char *cp;
  863.     struct file_list_str *fl;
  864.  
  865.     if (file_count == file_max)
  866.     {
  867.     file_max += FILE_INCREMENT;
  868.     file_list = (struct file_list_str *) xrealloc ((char *) file_list,
  869.                            file_max * sizeof (*fl));
  870.     }
  871.     fl = &file_list[file_count++];
  872.     fl->l_file = cp = xmalloc (strlen (dir) + strlen (name) + 2);
  873.     fl->l_module = module;
  874.  
  875.     if (dir && *dir)
  876.     {
  877.     if (name && *name)
  878.     {
  879.         (void) strcpy (cp, dir);
  880.         (void) strcat (cp, "/");
  881.         (void) strcat (cp, name);
  882.     }
  883.     else
  884.     {
  885.         *cp++ = '*';
  886.         (void) strcpy (cp, dir);
  887.     }
  888.     }
  889.     else
  890.     {
  891.     if (name && *name)
  892.     {
  893.         (void) strcpy (cp, name);
  894.     }
  895.     else
  896.     {
  897.         error (0, 0, "save_file: null dir and file name");
  898.     }
  899.     }
  900. }
  901.  
  902. static void
  903. save_module (module)
  904.     char *module;
  905. {
  906.     if (mod_count == mod_max)
  907.     {
  908.     mod_max += MODULE_INCREMENT;
  909.     mod_list = (char **) xrealloc ((char *) mod_list,
  910.                        mod_max * sizeof (char *));
  911.     }
  912.     mod_list[mod_count++] = xstrdup (module);
  913. }
  914.  
  915. static void
  916. expand_modules ()
  917. {
  918. }
  919.  
  920. /* fill_hrec
  921.  *
  922.  * Take a ptr to 7-part history line, ending with a newline, for example:
  923.  *
  924.  *    M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
  925.  *
  926.  * Split it into 7 parts and drop the parts into a "struct hrec".
  927.  * Return a pointer to the character following the newline.
  928.  */
  929.  
  930. #define NEXT_BAR(here) do { while (isspace(*line)) line++; hr->here = line; while ((c = *line++) && c != '|') ; if (!c) return(rtn); *(line - 1) = '\0'; } while (0)
  931.  
  932. static char *
  933. fill_hrec (line, hr)
  934.     char *line;
  935.     struct hrec *hr;
  936. {
  937.     char *cp, *rtn;
  938.     int c;
  939.     int off;
  940.     static int idx = 0;
  941.     unsigned long date;
  942.  
  943.     memset ((char *) hr, 0, sizeof (*hr));
  944.     while (isspace (*line))
  945.     line++;
  946.     if (!(rtn = strchr (line, '\n')))
  947.     return ("");
  948.     *rtn++ = '\0';
  949.  
  950.     hr->type = line++;
  951.     (void) sscanf (line, "%lx", &date);
  952.     hr->date = date;
  953.     while (*line && strchr ("0123456789abcdefABCDEF", *line))
  954.     line++;
  955.     if (*line == '\0')
  956.     return (rtn);
  957.  
  958.     line++;
  959.     NEXT_BAR (user);
  960.     NEXT_BAR (dir);
  961.     if ((cp = strrchr (hr->dir, '*')) != NULL)
  962.     {
  963.     *cp++ = '\0';
  964.     (void) sscanf (cp, "%x", &off);
  965.     hr->end = line + off;
  966.     }
  967.     else
  968.     hr->end = line - 1;        /* A handy pointer to '\0' */
  969.     NEXT_BAR (repos);
  970.     NEXT_BAR (rev);
  971.     hr->idx = idx++;
  972.     if (strchr ("FOT", *(hr->type)))
  973.     hr->mod = line;
  974.  
  975.     NEXT_BAR (file);    /* This returns ptr to next line or final '\0' */
  976.     return (rtn);    /* If it falls through, go on to next record */
  977. }
  978.  
  979. /* read_hrecs's job is to read the history file and fill in all the "hrec"
  980.  * (history record) array elements with the ones we need to print.
  981.  *
  982.  * Logic:
  983.  * - Read the whole history file into a single buffer.
  984.  * - Walk through the buffer, parsing lines out of the buffer.
  985.  *   1. Split line into pointer and integer fields in the "next" hrec.
  986.  *   2. Apply tests to the hrec to see if it is wanted.
  987.  *   3. If it *is* wanted, bump the hrec pointer down by one.
  988.  */
  989. static void
  990. read_hrecs (fname)
  991.     char *fname;
  992. {
  993.     char *cp, *cp2;
  994.     int i, fd;
  995.     struct hrec *hr;
  996.     struct stat st_buf;
  997.  
  998.     if ((fd = CVS_OPEN (fname, O_RDONLY | OPEN_BINARY)) < 0)
  999.     error (1, errno, "cannot open history file: %s", fname);
  1000.  
  1001.     if (fstat (fd, &st_buf) < 0)
  1002.     error (1, errno, "can't stat history file");
  1003.  
  1004.     /* Exactly enough space for lines data */
  1005.     if (!(i = st_buf.st_size))
  1006.     error (1, 0, "history file is empty");
  1007.     cp = xmalloc (i + 2);
  1008.  
  1009.     if (read (fd, cp, i) != i)
  1010.     error (1, errno, "cannot read log file");
  1011.     (void) close (fd);
  1012.  
  1013.     if (*(cp + i - 1) != '\n')
  1014.     {
  1015.     *(cp + i) = '\n';        /* Make sure last line ends in '\n' */
  1016.     i++;
  1017.     }
  1018.     *(cp + i) = '\0';
  1019.     for (cp2 = cp; cp2 - cp < i; cp2++)
  1020.     {
  1021.     if (*cp2 != '\n' && !isprint (*cp2))
  1022.         *cp2 = ' ';
  1023.     }
  1024.  
  1025.     hrec_max = HREC_INCREMENT;
  1026.     hrec_head = (struct hrec *) xmalloc (hrec_max * sizeof (struct hrec));
  1027.  
  1028.     while (*cp)
  1029.     {
  1030.     if (hrec_count == hrec_max)
  1031.     {
  1032.         struct hrec *old_head = hrec_head;
  1033.  
  1034.         hrec_max += HREC_INCREMENT;
  1035.         hrec_head = (struct hrec *) xrealloc ((char *) hrec_head,
  1036.                        hrec_max * sizeof (struct hrec));
  1037.         if (hrec_head != old_head)
  1038.         {
  1039.         if (last_since_tag)
  1040.             last_since_tag = hrec_head + (last_since_tag - old_head);
  1041.         if (last_backto)
  1042.             last_backto = hrec_head + (last_backto - old_head);
  1043.         }
  1044.     }
  1045.  
  1046.     hr = hrec_head + hrec_count;
  1047.     cp = fill_hrec (cp, hr); /* cp == next line or '\0' at end of buffer */
  1048.  
  1049.     if (select_hrec (hr))
  1050.         hrec_count++;
  1051.     }
  1052.  
  1053.     /* Special selection problem: If "since_tag" is set, we have saved every
  1054.      * record from the 1st occurrence of "since_tag", when we want to save
  1055.      * records since the *last* occurrence of "since_tag".  So what we have
  1056.      * to do is bump hrec_head forward and reduce hrec_count accordingly.
  1057.      */
  1058.     if (last_since_tag)
  1059.     {
  1060.     hrec_count -= (last_since_tag - hrec_head);
  1061.     hrec_head = last_since_tag;
  1062.     }
  1063.  
  1064.     /* Much the same thing is necessary for the "backto" option. */
  1065.     if (last_backto)
  1066.     {
  1067.     hrec_count -= (last_backto - hrec_head);
  1068.     hrec_head = last_backto;
  1069.     }
  1070. }
  1071.  
  1072. /* Utility program for determining whether "find" is inside "string" */
  1073. static int
  1074. within (find, string)
  1075.     char *find, *string;
  1076. {
  1077.     int c, len;
  1078.  
  1079.     if (!find || !string)
  1080.     return (0);
  1081.  
  1082.     c = *find++;
  1083.     len = strlen (find);
  1084.  
  1085.     while (*string)
  1086.     {
  1087.     if (!(string = strchr (string, c)))
  1088.         return (0);
  1089.     string++;
  1090.     if (!strncmp (find, string, len))
  1091.         return (1);
  1092.     }
  1093.     return (0);
  1094. }
  1095.  
  1096. /* The purpose of "select_hrec" is to apply the selection criteria based on
  1097.  * the command arguments and defaults and return a flag indicating whether
  1098.  * this record should be remembered for printing.
  1099.  */
  1100. static int
  1101. select_hrec (hr)
  1102.     struct hrec *hr;
  1103. {
  1104.     char **cpp, *cp, *cp2;
  1105.     struct file_list_str *fl;
  1106.     int count;
  1107.  
  1108.     /* "Since" checking:  The argument parser guarantees that only one of the
  1109.      *              following four choices is set:
  1110.      *
  1111.      * 1. If "since_date" is set, it contains a Unix time_t specified on the
  1112.      *    command line. hr->date fields earlier than "since_date" are ignored.
  1113.      * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
  1114.      *    number (which is of limited use) or a symbolic TAG.  Each RCS file
  1115.      *    is examined and the date on the specified revision (or the revision
  1116.      *    corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
  1117.      *    compared against hr->date as in 1. above.
  1118.      * 3. If "since_tag" is set, matching tag records are saved.  The field
  1119.      *    "last_since_tag" is set to the last one of these.  Since we don't
  1120.      *    know where the last one will be, all records are saved from the
  1121.      *    first occurrence of the TAG.  Later, at the end of "select_hrec"
  1122.      *    records before the last occurrence of "since_tag" are skipped.
  1123.      * 4. If "backto" is set, all records with a module name or file name
  1124.      *    matching "backto" are saved.  In addition, all records with a
  1125.      *    repository field with a *prefix* matching "backto" are saved.
  1126.      *    The field "last_backto" is set to the last one of these.  As in
  1127.      *    3. above, "select_hrec" adjusts to include the last one later on.
  1128.      */
  1129.     if (since_date)
  1130.     {
  1131.     if (hr->date < since_date)
  1132.         return (0);
  1133.     }
  1134.     else if (*since_rev)
  1135.     {
  1136.     Vers_TS *vers;
  1137.     time_t t;
  1138.     struct file_info finfo;
  1139.  
  1140.     memset (&finfo, 0, sizeof finfo);
  1141.     finfo.file = hr->file;
  1142.     /* Not used, so don't worry about it.  */
  1143.     finfo.update_dir = NULL;
  1144.     finfo.fullname = finfo.file;
  1145.     finfo.repository = hr->repos;
  1146.     finfo.entries = NULL;
  1147.     finfo.rcs = NULL;
  1148.  
  1149.     vers = Version_TS (&finfo, (char *) NULL, since_rev, (char *) NULL,
  1150.                1, 0);
  1151.     if (vers->vn_rcs)
  1152.     {
  1153.         if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0))
  1154.         != (time_t) 0)
  1155.         {
  1156.         if (hr->date < t)
  1157.         {
  1158.             freevers_ts (&vers);
  1159.             return (0);
  1160.         }
  1161.         }
  1162.     }
  1163.     freevers_ts (&vers);
  1164.     }
  1165.     else if (*since_tag)
  1166.     {
  1167.     if (*(hr->type) == 'T')
  1168.     {
  1169.         /*
  1170.          * A 'T'ag record, the "rev" field holds the tag to be set,
  1171.          * while the "repos" field holds "D"elete, "A"dd or a rev.
  1172.          */
  1173.         if (within (since_tag, hr->rev))
  1174.         {
  1175.         last_since_tag = hr;
  1176.         return (1);
  1177.         }
  1178.         else
  1179.         return (0);
  1180.     }
  1181.     if (!last_since_tag)
  1182.         return (0);
  1183.     }
  1184.     else if (*backto)
  1185.     {
  1186.     if (within (backto, hr->file) || within (backto, hr->mod) ||
  1187.         within (backto, hr->repos))
  1188.         last_backto = hr;
  1189.     else
  1190.         return (0);
  1191.     }
  1192.  
  1193.     /* User checking:
  1194.      *
  1195.      * Run down "user_list", match username ("" matches anything)
  1196.      * If "" is not there and actual username is not there, return failure.
  1197.      */
  1198.     if (user_list && hr->user)
  1199.     {
  1200.     for (cpp = user_list, count = user_count; count; cpp++, count--)
  1201.     {
  1202.         if (!**cpp)
  1203.         break;            /* null user == accept */
  1204.         if (!strcmp (hr->user, *cpp))    /* found listed user */
  1205.         break;
  1206.     }
  1207.     if (!count)
  1208.         return (0);            /* Not this user */
  1209.     }
  1210.  
  1211.     /* Record type checking:
  1212.      *
  1213.      * 1. If Record type is not in rec_types field, skip it.
  1214.      * 2. If mod_list is null, keep everything.  Otherwise keep only modules
  1215.      *    on mod_list.
  1216.      * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list".  If
  1217.      *    file_list is null, keep everything.  Otherwise, keep only files on
  1218.      *    file_list, matched appropriately.
  1219.      */
  1220.     if (!strchr (rec_types, *(hr->type)))
  1221.     return (0);
  1222.     if (!strchr ("TFO", *(hr->type)))    /* Don't bother with "file" if "TFO" */
  1223.     {
  1224.     if (file_list)            /* If file_list is null, accept all */
  1225.     {
  1226.         for (fl = file_list, count = file_count; count; fl++, count--)
  1227.         {
  1228.         /* 1. If file_list entry starts with '*', skip the '*' and
  1229.          *    compare it against the repository in the hrec.
  1230.          * 2. If file_list entry has a '/' in it, compare it against
  1231.          *    the concatenation of the repository and file from hrec.
  1232.          * 3. Else compare the file_list entry against the hrec file.
  1233.          */
  1234.         char cmpfile[PATH_MAX];
  1235.  
  1236.         if (*(cp = fl->l_file) == '*')
  1237.         {
  1238.             cp++;
  1239.             /* if argument to -p is a prefix of repository */
  1240.             if (!strncmp (cp, hr->repos, strlen (cp)))
  1241.             {
  1242.             hr->mod = fl->l_module;
  1243.             break;
  1244.             }
  1245.         }
  1246.         else
  1247.         {
  1248.             if (strchr (cp, '/'))
  1249.             {
  1250.             (void) sprintf (cp2 = cmpfile, "%s/%s",
  1251.                     hr->repos, hr->file);
  1252.             }
  1253.             else
  1254.             {
  1255.             cp2 = hr->file;
  1256.             }
  1257.  
  1258.             /* if requested file is found within {repos}/file fields */
  1259.             if (within (cp, cp2))
  1260.             {
  1261.             hr->mod = fl->l_module;
  1262.             break;
  1263.             }
  1264.         }
  1265.         }
  1266.         if (!count)
  1267.         return (0);        /* String specified and no match */
  1268.     }
  1269.     }
  1270.     if (mod_list)
  1271.     {
  1272.     for (cpp = mod_list, count = mod_count; count; cpp++, count--)
  1273.     {
  1274.         if (hr->mod && !strcmp (hr->mod, *cpp))    /* found module */
  1275.         break;
  1276.     }
  1277.     if (!count)
  1278.         return (0);    /* Module specified & this record is not one of them. */
  1279.     }
  1280.  
  1281.     return (1);        /* Select this record unless rejected above. */
  1282. }
  1283.  
  1284. /* The "sort_order" routine (when handed to qsort) has arranged for the
  1285.  * hrecs files to be in the right order for the report.
  1286.  *
  1287.  * Most of the "selections" are done in the select_hrec routine, but some
  1288.  * selections are more easily done after the qsort by "accept_hrec".
  1289.  */
  1290. static void
  1291. report_hrecs ()
  1292. {
  1293.     struct hrec *hr, *lr;
  1294.     struct tm *tm;
  1295.     int i, count, ty;
  1296.     char *cp;
  1297.     int user_len, file_len, rev_len, mod_len, repos_len;
  1298.  
  1299.     if (*since_tag && !last_since_tag)
  1300.     {
  1301.     (void) printf ("No tag found: %s\n", since_tag);
  1302.     return;
  1303.     }
  1304.     else if (*backto && !last_backto)
  1305.     {
  1306.     (void) printf ("No module, file or repository with: %s\n", backto);
  1307.     return;
  1308.     }
  1309.     else if (hrec_count < 1)
  1310.     {
  1311.     (void) printf ("No records selected.\n");
  1312.     return;
  1313.     }
  1314.  
  1315.     user_len = file_len = rev_len = mod_len = repos_len = 0;
  1316.  
  1317.     /* Run through lists and find maximum field widths */
  1318.     hr = lr = hrec_head;
  1319.     hr++;
  1320.     for (count = hrec_count; count--; lr = hr, hr++)
  1321.     {
  1322.     char repos[PATH_MAX];
  1323.  
  1324.     if (!count)
  1325.         hr = NULL;
  1326.     if (!accept_hrec (lr, hr))
  1327.         continue;
  1328.  
  1329.     ty = *(lr->type);
  1330.     (void) strcpy (repos, lr->repos);
  1331.     if ((cp = strrchr (repos, '/')) != NULL)
  1332.     {
  1333.         if (lr->mod && !strcmp (++cp, lr->mod))
  1334.         {
  1335.         (void) strcpy (cp, "*");
  1336.         }
  1337.     }
  1338.     if ((i = strlen (lr->user)) > user_len)
  1339.         user_len = i;
  1340.     if ((i = strlen (lr->file)) > file_len)
  1341.         file_len = i;
  1342.     if (ty != 'T' && (i = strlen (repos)) > repos_len)
  1343.         repos_len = i;
  1344.     if (ty != 'T' && (i = strlen (lr->rev)) > rev_len)
  1345.         rev_len = i;
  1346.     if (lr->mod && (i = strlen (lr->mod)) > mod_len)
  1347.         mod_len = i;
  1348.     }
  1349.  
  1350.     /* Walk through hrec array setting "lr" (Last Record) to each element.
  1351.      * "hr" points to the record following "lr" -- It is NULL in the last
  1352.      * pass.
  1353.      *
  1354.      * There are two sections in the loop below:
  1355.      * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
  1356.      *    decide whether the record should be printed.
  1357.      * 2. Based on the record type, format and print the data.
  1358.      */
  1359.     for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
  1360.     {
  1361.     char workdir[PATH_MAX], repos[PATH_MAX];
  1362.  
  1363.     if (!hrec_count)
  1364.         hr = NULL;
  1365.     if (!accept_hrec (lr, hr))
  1366.         continue;
  1367.  
  1368.     ty = *(lr->type);
  1369. #ifdef HAVE_RCS5
  1370.     if (!tz_local)
  1371.     {
  1372.         time_t t = lr->date + tz_seconds_east_of_GMT;
  1373.         tm = gmtime (&t);
  1374.     }
  1375.     else
  1376. #endif
  1377.     tm = localtime (&(lr->date));
  1378.     (void) printf ("%c %02d/%02d %02d:%02d %s %-*s", ty, tm->tm_mon + 1,
  1379.           tm->tm_mday, tm->tm_hour, tm->tm_min, tz_name,
  1380.           user_len, lr->user);
  1381.  
  1382.     (void) sprintf (workdir, "%s%s", lr->dir, lr->end);
  1383.     if ((cp = strrchr (workdir, '/')) != NULL)
  1384.     {
  1385.         if (lr->mod && !strcmp (++cp, lr->mod))
  1386.         {
  1387.         (void) strcpy (cp, "*");
  1388.         }
  1389.     }
  1390.     (void) strcpy (repos, lr->repos);
  1391.     if ((cp = strrchr (repos, '/')) != NULL)
  1392.     {
  1393.         if (lr->mod && !strcmp (++cp, lr->mod))
  1394.         {
  1395.         (void) strcpy (cp, "*");
  1396.         }
  1397.     }
  1398.  
  1399.     switch (ty)
  1400.     {
  1401.         case 'T':
  1402.         /* 'T'ag records: repository is a "tag type", rev is the tag */
  1403.         (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev,
  1404.                    repos);
  1405.         if (working)
  1406.             (void) printf (" {%s}", workdir);
  1407.         break;
  1408.         case 'F':
  1409.         case 'O':
  1410.         if (lr->rev && *(lr->rev))
  1411.             (void) printf (" [%s]", lr->rev);
  1412.         (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
  1413.                    mod_len + 1 - (int) strlen (lr->mod),
  1414.                    "=", workdir);
  1415.         break;
  1416.         case 'W':
  1417.         case 'U':
  1418.         case 'C':
  1419.         case 'G':
  1420.         case 'M':
  1421.         case 'A':
  1422.         case 'R':
  1423.         (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
  1424.                    file_len, lr->file, repos_len, repos,
  1425.                    lr->mod ? lr->mod : "", workdir);
  1426.         break;
  1427.         default:
  1428.         (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
  1429.         break;
  1430.     }
  1431.     (void) putchar ('\n');
  1432.     }
  1433. }
  1434.  
  1435. static int
  1436. accept_hrec (lr, hr)
  1437.     struct hrec *hr, *lr;
  1438. {
  1439.     int ty;
  1440.  
  1441.     ty = *(lr->type);
  1442.  
  1443.     if (last_since_tag && ty == 'T')
  1444.     return (1);
  1445.  
  1446.     if (v_checkout)
  1447.     {
  1448.     if (ty != 'O')
  1449.         return (0);            /* Only interested in 'O' records */
  1450.  
  1451.     /* We want to identify all the states that cause the next record
  1452.      * ("hr") to be different from the current one ("lr") and only
  1453.      * print a line at the allowed boundaries.
  1454.      */
  1455.  
  1456.     if (!hr ||            /* The last record */
  1457.         strcmp (hr->user, lr->user) ||    /* User has changed */
  1458.         strcmp (hr->mod, lr->mod) ||/* Module has changed */
  1459.         (working &&            /* If must match "workdir" */
  1460.          (strcmp (hr->dir, lr->dir) ||    /*    and the 1st parts or */
  1461.           strcmp (hr->end, lr->end))))    /*    the 2nd parts differ */
  1462.  
  1463.         return (1);
  1464.     }
  1465.     else if (modified)
  1466.     {
  1467.     if (!last_entry ||        /* Don't want only last rec */
  1468.         !hr ||            /* Last entry is a "last entry" */
  1469.         strcmp (hr->repos, lr->repos) ||    /* Repository has changed */
  1470.         strcmp (hr->file, lr->file))/* File has changed */
  1471.         return (1);
  1472.  
  1473.     if (working)
  1474.     {                /* If must match "workdir" */
  1475.         if (strcmp (hr->dir, lr->dir) ||    /*    and the 1st parts or */
  1476.         strcmp (hr->end, lr->end))    /*    the 2nd parts differ */
  1477.         return (1);
  1478.     }
  1479.     }
  1480.     else if (module_report)
  1481.     {
  1482.     if (!last_entry ||        /* Don't want only last rec */
  1483.         !hr ||            /* Last entry is a "last entry" */
  1484.         strcmp (hr->mod, lr->mod) ||/* Module has changed */
  1485.         strcmp (hr->repos, lr->repos) ||    /* Repository has changed */
  1486.         strcmp (hr->file, lr->file))/* File has changed */
  1487.         return (1);
  1488.     }
  1489.     else
  1490.     {
  1491.     /* "extract" and "tag_report" always print selected records. */
  1492.     return (1);
  1493.     }
  1494.  
  1495.     return (0);
  1496. }
  1497.