Sync usage with man page.
[netbsd-mini2440.git] / external / gpl2 / xcvs / dist / src / history.c
blobe4ed5845dac8a04fa4f85d62d95141dfe308432b
1 /*
2 * Copyright (C) 1994-2005 The Free Software Foundation, Inc.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2, or (at your option)
7 * any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
15 /* **************** History of Users and Module ****************
17 * LOGGING: Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY".
19 * On For each Tag, Add, Checkout, Commit, Update or Release command,
20 * one line of text is written to a History log.
22 * X date | user | CurDir | special | rev(s) | argument '\n'
24 * where: [The spaces in the example line above are not in the history file.]
26 * X is a single character showing the type of event:
27 * T "Tag" cmd.
28 * O "Checkout" cmd.
29 * E "Export" cmd.
30 * F "Release" cmd.
31 * W "Update" cmd - No User file, Remove from Entries file.
32 * U "Update" cmd - File was checked out over User file.
33 * P "Update" cmd - User file was patched.
34 * G "Update" cmd - File was merged successfully.
35 * C "Update" cmd - File was merged and shows overlaps.
36 * M "Commit" cmd - "Modified" file.
37 * A "Commit" cmd - "Added" file.
38 * R "Commit" cmd - "Removed" file.
39 * X "Admin" cmd.
41 * date is a fixed length 8-char hex representation of a Unix time_t.
42 * [Starting here, variable fields are delimited by '|' chars.]
44 * user is the username of the person who typed the command.
46 * CurDir The directory where the action occurred. This should be the
47 * absolute path of the directory which is at the same level as
48 * the "Repository" field (for W,U,P,G,C & M,A,R).
50 * Repository For record types [W,U,P,G,C,M,A,R] this field holds the
51 * repository read from the administrative data where the
52 * command was typed.
53 * T "A" --> New Tag, "D" --> Delete Tag
54 * Otherwise it is the Tag or Date to modify.
55 * O,F,E A "" (null field)
57 * rev(s) Revision number or tag.
58 * T The Tag to apply.
59 * O,E The Tag or Date, if specified, else "" (null field).
60 * F "" (null field)
61 * W The Tag or Date, if specified, else "" (null field).
62 * U,P The Revision checked out over the User file.
63 * G,C The Revision(s) involved in merge.
64 * M,A,R RCS Revision affected.
66 * argument The module (for [TOEF]) or file (for [WUPGCMAR]) affected.
69 *** Report categories: "User" and "Since" modifiers apply to all reports.
70 * [For "sort" ordering see the "sort_order" routine.]
72 * Extract list of record types
74 * -e, -x [TOEFWUPGCMAR]
76 * Extracted records are simply printed, No analysis is performed.
77 * All "field" modifiers apply. -e chooses all types.
79 * Checked 'O'ut modules
81 * -o, -w
82 * Checked out modules. 'F' and 'O' records are examined and if
83 * the last record for a repository/file is an 'O', a line is
84 * printed. "-w" forces the "working dir" to be used in the
85 * comparison instead of the repository.
87 * Committed (Modified) files
89 * -c, -l, -w
90 * All 'M'odified, 'A'dded and 'R'emoved records are examined.
91 * "Field" modifiers apply. -l forces a sort by file within user
92 * and shows only the last modifier. -w works as in Checkout.
94 * Warning: Be careful with what you infer from the output of
95 * "cvs hi -c -l". It means the last time *you*
96 * changed the file, not the list of files for which
97 * you were the last changer!!!
99 * Module history for named modules.
100 * -m module, -l
102 * This is special. If one or more modules are specified, the
103 * module names are remembered and the files making up the
104 * modules are remembered. Only records matching exactly those
105 * files and repositories are shown. Sorting by "module", then
106 * filename, is implied. If -l ("last modified") is specified,
107 * then "update" records (types WUPCG), tag and release records
108 * are ignored and the last (by date) "modified" record.
110 * TAG history
112 * -T All Tag records are displayed.
114 *** Modifiers.
116 * Since ... [All records contain a timestamp, so any report
117 * category can be limited by date.]
119 * -D date - The "date" is parsed into a Unix "time_t" and
120 * records with an earlier time stamp are ignored.
121 * -r rev/tag - A "rev" begins with a digit. A "tag" does not. If
122 * you use this option, every file is searched for the
123 * indicated rev/tag.
124 * -t tag - The "tag" is searched for in the history file and no
125 * record is displayed before the tag is found. An
126 * error is printed if the tag is never found.
127 * -b string - Records are printed only back to the last reference
128 * to the string in the "module", "file" or
129 * "repository" fields.
131 * Field Selections [Simple comparisons on existing fields. All field
132 * selections are repeatable.]
134 * -a - All users.
135 * -u user - If no user is given and '-a' is not given, only
136 * records for the user typing the command are shown.
137 * ==> If -a or -u is not specified, just use "self".
139 * -f filematch - Only records in which the "file" field contains the
140 * string "filematch" are considered.
142 * -p repository - Only records in which the "repository" string is a
143 * prefix of the "repos" field are considered.
145 * -n modulename - Only records which contain "modulename" in the
146 * "module" field are considered.
149 * EXAMPLES: ("cvs history", "cvs his" or "cvs hi")
151 *** Checked out files for username. (default self, e.g. "dgg")
152 * cvs hi [equivalent to: "cvs hi -o -u dgg"]
153 * cvs hi -u user [equivalent to: "cvs hi -o -u user"]
154 * cvs hi -o [equivalent to: "cvs hi -o -u dgg"]
156 *** Committed (modified) files from the beginning of the file.
157 * cvs hi -c [-u user]
159 *** Committed (modified) files since Midnight, January 1, 1990:
160 * cvs hi -c -D 'Jan 1 1990' [-u user]
162 *** Committed (modified) files since tag "TAG" was stored in the history file:
163 * cvs hi -c -t TAG [-u user]
165 *** Committed (modified) files since tag "TAG" was placed on the files:
166 * cvs hi -c -r TAG [-u user]
168 *** Who last committed file/repository X?
169 * cvs hi -c -l -[fp] X
171 *** Modified files since tag/date/file/repos?
172 * cvs hi -c {-r TAG | -D Date | -b string}
174 *** Tag history
175 * cvs hi -T
177 *** History of file/repository/module X.
178 * cvs hi -[fpn] X
180 *** History of user "user".
181 * cvs hi -e -u user
183 *** Dump (eXtract) specified record types
184 * cvs hi -x [TOEFWUPGCMAR]
187 * FUTURE: J[Join], I[Import] (Not currently implemented.)
191 #include "cvs.h"
192 #include "history.h"
193 #include "save-cwd.h"
195 static struct hrec
197 char *type; /* Type of record (In history record) */
198 char *user; /* Username (In history record) */
199 char *dir; /* "Compressed" Working dir (In history record) */
200 char *repos; /* (Tag is special.) Repository (In history record) */
201 char *rev; /* Revision affected (In history record) */
202 char *file; /* Filename (In history record) */
203 char *end; /* Ptr into repository to copy at end of workdir */
204 char *mod; /* The module within which the file is contained */
205 time_t date; /* Calculated from date stored in record */
206 long idx; /* Index of record, for "stable" sort. */
207 } *hrec_head;
208 static long hrec_idx;
211 static void fill_hrec (char *line, struct hrec * hr);
212 static int accept_hrec (struct hrec * hr, struct hrec * lr);
213 static int select_hrec (struct hrec * hr);
214 static int sort_order (const void *l, const void *r);
215 static int within (char *find, char *string);
216 static void expand_modules (void);
217 static void read_hrecs (List *flist);
218 static void report_hrecs (void);
219 static void save_file (char *dir, char *name, char *module);
220 static void save_module (char *module);
221 static void save_user (char *name);
223 #define USER_INCREMENT 2
224 #define FILE_INCREMENT 128
225 #define MODULE_INCREMENT 5
226 #define HREC_INCREMENT 128
228 static short report_count;
230 static short extract;
231 static short extract_all;
232 static short v_checkout;
233 static short modified;
234 static short tag_report;
235 static short module_report;
236 static short working;
237 static short last_entry;
238 static short all_users;
240 static short user_sort;
241 static short repos_sort;
242 static short file_sort;
243 static short module_sort;
245 static short tz_local;
246 static time_t tz_seconds_east_of_GMT;
247 static char *tz_name = "+0000";
249 /* -r, -t, or -b options, malloc'd. These are "" if the option in
250 question is not specified or is overridden by another option. The
251 main reason for using "" rather than NULL is historical. Together
252 with since_date, these are a mutually exclusive set; one overrides the
253 others. */
254 static char *since_rev;
255 static char *since_tag;
256 static char *backto;
257 /* -D option, or 0 if not specified. RCS format. */
258 static char * since_date;
260 static struct hrec *last_since_tag;
261 static struct hrec *last_backto;
263 /* Record types to look for, malloc'd. Probably could be statically
264 allocated, but only if we wanted to check for duplicates more than
265 we do. */
266 static char *rec_types;
268 static int hrec_count;
269 static int hrec_max;
271 static char **user_list; /* Ptr to array of ptrs to user names */
272 static int user_max; /* Number of elements allocated */
273 static int user_count; /* Number of elements used */
275 static struct file_list_str
277 char *l_file;
278 char *l_module;
279 } *file_list; /* Ptr to array file name structs */
280 static int file_max; /* Number of elements allocated */
281 static int file_count; /* Number of elements used */
283 static char **mod_list; /* Ptr to array of ptrs to module names */
284 static int mod_max; /* Number of elements allocated */
285 static int mod_count; /* Number of elements used */
287 /* This is pretty unclear. First of all, separating "flags" vs.
288 "options" (I think the distinction is that "options" take arguments)
289 is nonstandard, and not something we do elsewhere in CVS. Second of
290 all, what does "reports" mean? I think it means that you can only
291 supply one of those options, but "reports" hardly has that meaning in
292 a self-explanatory way. */
293 static const char *const history_usg[] =
295 "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n",
296 " Reports:\n",
297 " -T Produce report on all TAGs\n",
298 " -c Committed (Modified) files\n",
299 " -o Checked out modules\n",
300 " -m <module> Look for specified module (repeatable)\n",
301 " -x [" ALL_HISTORY_REC_TYPES "] Extract by record type\n",
302 " -e Everything (same as -x, but all record types)\n",
303 " Flags:\n",
304 " -a All users (Default is self)\n",
305 " -l Last modified (committed or modified report)\n",
306 " -w Working directory must match\n",
307 " Options:\n",
308 " -D <date> Since date (Many formats)\n",
309 " -b <str> Back to record with str in module/file/repos field\n",
310 " -f <file> Specified file (same as command line) (repeatable)\n",
311 " -n <modulename> In module (repeatable)\n",
312 " -p <repos> In repository (repeatable)\n",
313 " -r <rev/tag> Since rev or tag (looks inside RCS files!)\n",
314 " -t <tag> Since tag record placed in history file (by anyone).\n",
315 " -u <user> For user name (repeatable)\n",
316 " -z <tz> Output for time zone <tz> (e.g. -z -0700)\n",
317 NULL};
319 /* Sort routine for qsort:
320 - If a user is selected at all, sort it first. User-within-file is useless.
321 - If a module was selected explicitly, sort next on module.
322 - Then sort by file. "File" is "repository/file" unless "working" is set,
323 then it is "workdir/file". (Revision order should always track date.)
324 - Always sort timestamp last.
326 static int
327 sort_order (const void *l, const void *r)
329 int i;
330 const struct hrec *left = l;
331 const struct hrec *right = r;
333 if (user_sort) /* If Sort by username, compare users */
335 if ((i = strcmp (left->user, right->user)) != 0)
336 return i;
338 if (module_sort) /* If sort by modules, compare module names */
340 if (left->mod && right->mod)
341 if ((i = strcmp (left->mod, right->mod)) != 0)
342 return i;
344 if (repos_sort) /* If sort by repository, compare them. */
346 if ((i = strcmp (left->repos, right->repos)) != 0)
347 return i;
349 if (file_sort) /* If sort by filename, compare files, NOT dirs. */
351 if ((i = strcmp (left->file, right->file)) != 0)
352 return i;
354 if (working)
356 if ((i = strcmp (left->dir, right->dir)) != 0)
357 return i;
359 if ((i = strcmp (left->end, right->end)) != 0)
360 return i;
365 * By default, sort by date, time
366 * XXX: This fails after 2030 when date slides into sign bit
368 if ((i = ((long) (left->date) - (long) (right->date))) != 0)
369 return i;
371 /* For matching dates, keep the sort stable by using record index */
372 return left->idx - right->idx;
377 /* Get the name of the history log, either from CVSROOT/config, or via the
378 * hard-coded default.
380 static const char *
381 get_history_log_name (time_t now)
383 char *log_name;
385 if (config->HistoryLogPath)
387 /* ~, $VARs, and were expanded via expand_path() when CVSROOT/config
388 * was parsed.
390 log_name = xmalloc (PATH_MAX);
391 if (!now) now = time (NULL);
392 if (!strftime (log_name, PATH_MAX, config->HistoryLogPath,
393 localtime (&now)))
395 error (0, 0, "Invalid date format in HistoryLogPath.");
396 free (config->HistoryLogPath);
397 config->HistoryLogPath = NULL;
401 if (!config->HistoryLogPath)
403 /* Use the default. */
404 log_name = xmalloc (strlen (current_parsed_root->directory)
405 + sizeof (CVSROOTADM)
406 + sizeof (CVSROOTADM_HISTORY) + 3);
407 sprintf (log_name, "%s/%s/%s", current_parsed_root->directory,
408 CVSROOTADM, CVSROOTADM_HISTORY);
411 return log_name;
417 history (int argc, char **argv)
419 int i, c;
420 const char *fname = NULL;
421 List *flist;
423 if (argc == -1)
424 usage (history_usg);
426 since_rev = xstrdup ("");
427 since_tag = xstrdup ("");
428 backto = xstrdup ("");
429 rec_types = xstrdup ("");
430 getoptreset ();
431 while ((c = getopt (argc, argv, "+Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != -1)
433 switch (c)
435 case 'T': /* Tag list */
436 report_count++;
437 tag_report++;
438 break;
439 case 'a': /* For all usernames */
440 all_users++;
441 break;
442 case 'c':
443 report_count++;
444 modified = 1;
445 break;
446 case 'e':
447 report_count++;
448 extract_all++;
449 free (rec_types);
450 rec_types = xstrdup (ALL_HISTORY_REC_TYPES);
451 break;
452 case 'l': /* Find Last file record */
453 last_entry = 1;
454 break;
455 case 'o':
456 report_count++;
457 v_checkout = 1;
458 break;
459 case 'w': /* Match Working Dir (CurDir) fields */
460 working = 1;
461 break;
462 case 'X': /* Undocumented debugging flag */
463 #ifdef DEBUG
464 fname = optarg;
465 #endif
466 break;
468 case 'D': /* Since specified date */
469 if (*since_rev || *since_tag || *backto)
471 error (0, 0, "date overriding rev/tag/backto");
472 *since_rev = *since_tag = *backto = '\0';
474 since_date = Make_Date (optarg);
475 break;
476 case 'b': /* Since specified file/Repos */
477 if (since_date || *since_rev || *since_tag)
479 error (0, 0, "backto overriding date/rev/tag");
480 *since_rev = *since_tag = '\0';
481 if (since_date != NULL)
482 free (since_date);
483 since_date = NULL;
485 free (backto);
486 backto = xstrdup (optarg);
487 break;
488 case 'f': /* For specified file */
489 save_file (NULL, optarg, NULL);
490 break;
491 case 'm': /* Full module report */
492 if (!module_report++) report_count++;
493 /* fall through */
494 case 'n': /* Look for specified module */
495 save_module (optarg);
496 break;
497 case 'p': /* For specified directory */
498 save_file (optarg, NULL, NULL);
499 break;
500 case 'r': /* Since specified Tag/Rev */
501 if (since_date || *since_tag || *backto)
503 error (0, 0, "rev overriding date/tag/backto");
504 *since_tag = *backto = '\0';
505 if (since_date != NULL)
506 free (since_date);
507 since_date = NULL;
509 free (since_rev);
510 since_rev = xstrdup (optarg);
511 break;
512 case 't': /* Since specified Tag/Rev */
513 if (since_date || *since_rev || *backto)
515 error (0, 0, "tag overriding date/marker/file/repos");
516 *since_rev = *backto = '\0';
517 if (since_date != NULL)
518 free (since_date);
519 since_date = NULL;
521 free (since_tag);
522 since_tag = xstrdup (optarg);
523 break;
524 case 'u': /* For specified username */
525 save_user (optarg);
526 break;
527 case 'x':
528 report_count++;
529 extract++;
531 char *cp;
533 for (cp = optarg; *cp; cp++)
534 if (!strchr (ALL_HISTORY_REC_TYPES, *cp))
535 error (1, 0, "%c is not a valid report type", *cp);
537 free (rec_types);
538 rec_types = xstrdup (optarg);
539 break;
540 case 'z':
541 tz_local =
542 (optarg[0] == 'l' || optarg[0] == 'L')
543 && (optarg[1] == 't' || optarg[1] == 'T')
544 && !optarg[2];
545 if (tz_local)
546 tz_name = optarg;
547 else
550 * Convert a known time with the given timezone to time_t.
551 * Use the epoch + 23 hours, so timezones east of GMT work.
553 struct timespec t;
554 char *buf = Xasprintf ("1/1/1970 23:00 %s", optarg);
555 if (get_date (&t, buf, NULL))
558 * Convert to seconds east of GMT, removing the
559 * 23-hour offset mentioned above.
561 tz_seconds_east_of_GMT = (time_t)23 * 60 * 60
562 - t.tv_sec;
563 tz_name = optarg;
565 else
566 error (0, 0, "%s is not a known time zone", optarg);
567 free (buf);
569 break;
570 case '?':
571 default:
572 usage (history_usg);
573 break;
576 argc -= optind;
577 argv += optind;
578 for (i = 0; i < argc; i++)
579 save_file (NULL, argv[i], NULL);
582 /* ================ Now analyze the arguments a bit */
583 if (!report_count)
584 v_checkout++;
585 else if (report_count > 1)
586 error (1, 0, "Only one report type allowed from: \"-Tcomxe\".");
588 #ifdef CLIENT_SUPPORT
589 if (current_parsed_root->isremote)
591 struct file_list_str *f1;
592 char **mod;
594 /* We're the client side. Fire up the remote server. */
595 start_server ();
597 ign_setup ();
599 if (tag_report)
600 send_arg ("-T");
601 if (all_users)
602 send_arg ("-a");
603 if (modified)
604 send_arg ("-c");
605 if (last_entry)
606 send_arg ("-l");
607 if (v_checkout)
608 send_arg ("-o");
609 if (working)
610 send_arg ("-w");
611 if (fname)
612 option_with_arg ("-X", fname);
613 if (since_date)
614 client_senddate (since_date);
615 if (backto[0] != '\0')
616 option_with_arg ("-b", backto);
617 for (f1 = file_list; f1 < &file_list[file_count]; ++f1)
619 if (f1->l_file[0] == '*')
620 option_with_arg ("-p", f1->l_file + 1);
621 else
622 option_with_arg ("-f", f1->l_file);
624 if (module_report)
625 send_arg ("-m");
626 for (mod = mod_list; mod < &mod_list[mod_count]; ++mod)
627 option_with_arg ("-n", *mod);
628 if (*since_rev)
629 option_with_arg ("-r", since_rev);
630 if (*since_tag)
631 option_with_arg ("-t", since_tag);
632 for (mod = user_list; mod < &user_list[user_count]; ++mod)
633 option_with_arg ("-u", *mod);
634 if (extract_all)
635 send_arg ("-e");
636 if (extract)
637 option_with_arg ("-x", rec_types);
638 option_with_arg ("-z", tz_name);
640 send_to_server ("history\012", 0);
641 return get_responses_and_close ();
643 #endif
645 if (all_users)
646 save_user ("");
648 if (mod_list)
649 expand_modules ();
651 if (tag_report)
653 if (!strchr (rec_types, 'T'))
655 rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
656 (void) strcat (rec_types, "T");
659 else if (extract || extract_all)
661 if (user_list)
662 user_sort++;
664 else if (modified)
666 free (rec_types);
667 rec_types = xstrdup ("MAR");
669 * If the user has not specified a date oriented flag ("Since"), sort
670 * by Repository/file before date. Default is "just" date.
672 if (last_entry
673 || (!since_date && !*since_rev && !*since_tag && !*backto))
675 repos_sort++;
676 file_sort++;
678 * If we are not looking for last_modified and the user specified
679 * one or more users to look at, sort by user before filename.
681 if (!last_entry && user_list)
682 user_sort++;
685 else if (module_report)
687 free (rec_types);
688 rec_types = xstrdup (last_entry ? "OMAR" : ALL_HISTORY_REC_TYPES);
689 module_sort++;
690 repos_sort++;
691 file_sort++;
692 working = 0; /* User's workdir doesn't count here */
694 else
695 /* Must be "checkout" or default */
697 free (rec_types);
698 rec_types = xstrdup ("OF");
699 /* See comments in "modified" above */
700 if (!last_entry && user_list)
701 user_sort++;
702 if (last_entry
703 || (!since_date && !*since_rev && !*since_tag && !*backto))
704 file_sort++;
707 /* If no users were specified, use self (-a saves a universal ("") user) */
708 if (!user_list)
709 save_user (getcaller ());
711 /* If we're looking back to a Tag value, must consider "Tag" records */
712 if (*since_tag && !strchr (rec_types, 'T'))
714 rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
715 (void) strcat (rec_types, "T");
718 if (fname)
720 Node *p;
722 flist = getlist ();
723 p = getnode ();
724 p->type = FILES;
725 p->key = Xasprintf ("%s/%s/%s",
726 current_parsed_root->directory, CVSROOTADM, fname);
727 addnode (flist, p);
729 else
731 char *pat;
733 if (config->HistorySearchPath)
734 pat = config->HistorySearchPath;
735 else
736 pat = Xasprintf ("%s/%s/%s",
737 current_parsed_root->directory, CVSROOTADM,
738 CVSROOTADM_HISTORY);
740 flist = find_files (NULL, pat);
741 if (pat != config->HistorySearchPath) free (pat);
744 read_hrecs (flist);
745 if (hrec_count > 0)
746 qsort (hrec_head, hrec_count, sizeof (struct hrec), sort_order);
747 report_hrecs ();
748 if (since_date != NULL)
749 free (since_date);
750 free (since_rev);
751 free (since_tag);
752 free (backto);
753 free (rec_types);
755 return 0;
760 /* An empty LogHistory string in CVSROOT/config will turn logging off.
762 void
763 history_write (int type, const char *update_dir, const char *revs,
764 const char *name, const char *repository)
766 const char *fname;
767 char *workdir;
768 char *username = getcaller ();
769 int fd;
770 char *line;
771 char *slash = "", *cp;
772 const char *cp2, *repos;
773 int i;
774 static char *tilde = "";
775 static char *PrCurDir = NULL;
776 time_t now;
778 if (logoff) /* History is turned off by noexec or
779 * readonlyfs.
781 return;
782 if (!strchr (config->logHistory, type))
783 return;
785 if (nolock)
786 goto out;
788 repos = Short_Repository (repository);
790 if (!PrCurDir)
792 char *pwdir;
794 pwdir = get_homedir ();
795 PrCurDir = CurDir;
796 if (pwdir != NULL)
798 /* Assumes neither CurDir nor pwdir ends in '/' */
799 i = strlen (pwdir);
800 if (!strncmp (CurDir, pwdir, i))
802 PrCurDir += i; /* Point to '/' separator */
803 tilde = "~";
805 else
807 /* Try harder to find a "homedir" */
808 struct saved_cwd cwd;
809 char *homedir;
811 if (save_cwd (&cwd))
812 error (1, errno, "Failed to save current directory.");
814 if (CVS_CHDIR (pwdir) < 0 || (homedir = xgetcwd ()) == NULL)
815 homedir = pwdir;
817 if (restore_cwd (&cwd))
818 error (1, errno,
819 "Failed to restore current directory, `%s'.",
820 cwd.name);
821 free_cwd (&cwd);
823 i = strlen (homedir);
824 if (!strncmp (CurDir, homedir, i))
826 PrCurDir += i; /* Point to '/' separator */
827 tilde = "~";
830 if (homedir != pwdir)
831 free (homedir);
836 if (type == 'T')
838 repos = update_dir;
839 update_dir = "";
841 else if (update_dir && *update_dir)
842 slash = "/";
843 else
844 update_dir = "";
846 workdir = Xasprintf ("%s%s%s%s", tilde, PrCurDir, slash, update_dir);
849 * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
850 * "repos" is the Repository, relative to $CVSROOT where the RCS file is.
852 * "$workdir/$name" is the working file name.
853 * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
855 * First, note that the history format was intended to save space, not
856 * to be human readable.
858 * The working file directory ("workdir") and the Repository ("repos")
859 * usually end with the same one or more directory elements. To avoid
860 * duplication (and save space), the "workdir" field ends with
861 * an integer offset into the "repos" field. This offset indicates the
862 * beginning of the "tail" of "repos", after which all characters are
863 * duplicates.
865 * In other words, if the "workdir" field has a '*' (a very stupid thing
866 * to put in a filename) in it, then every thing following the last '*'
867 * is a hex offset into "repos" of the first character from "repos" to
868 * append to "workdir" to finish the pathname.
870 * It might be easier to look at an example:
872 * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
874 * Indicates that the workdir is really "~/work/cvs/examples", saving
875 * 10 characters, where "~/work*d" would save 6 characters and mean that
876 * the workdir is really "~/work/examples". It will mean more on
877 * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
879 * "workdir" is always an absolute pathname (~/xxx is an absolute path)
880 * "repos" is always a relative pathname. So we can assume that we will
881 * never run into the top of "workdir" -- there will always be a '/' or
882 * a '~' at the head of "workdir" that is not matched by anything in
883 * "repos". On the other hand, we *can* run off the top of "repos".
885 * Only "compress" if we save characters.
888 cp = workdir + strlen (workdir) - 1;
889 cp2 = repos + strlen (repos) - 1;
890 for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
891 i++;
893 if (i > 2)
895 i = strlen (repos) - i;
896 (void) sprintf ((cp + 1), "*%x", i);
899 if (!revs)
900 revs = "";
901 now = time (NULL);
902 line = Xasprintf ("%c%08lx|%s|%s|%s|%s|%s\n", type, (long) now,
903 username, workdir, repos, revs, name);
905 fname = get_history_log_name (now);
907 if (!history_lock (current_parsed_root->directory))
908 /* history_lock() will already have printed an error on failure. */
909 goto out;
911 fd = CVS_OPEN (fname, O_WRONLY | O_APPEND | O_CREAT | OPEN_BINARY, 0666);
912 if (fd < 0)
914 if (!really_quiet)
915 error (0, errno,
916 "warning: cannot open history file `%s' for write", fname);
917 goto out;
920 TRACE (TRACE_FUNCTION, "open (`%s', a)", fname);
922 /* Lessen some race conditions on non-Posix-compliant hosts.
924 * FIXME: I'm guessing the following was necessary for NFS when multiple
925 * simultaneous writes to the same file are possible, since NFS does not
926 * natively support append mode and it must be emulated via lseek(). Now
927 * that the history file is locked for write, the following lseek() may be
928 * unnecessary.
930 if (lseek (fd, (off_t) 0, SEEK_END) == -1)
931 error (1, errno, "cannot seek to end of history file: %s", fname);
933 if (write (fd, line, strlen (line)) < 0)
934 error (1, errno, "cannot write to history file: %s", fname);
935 free (line);
936 if (close (fd) != 0)
937 error (1, errno, "cannot close history file: %s", fname);
938 free (workdir);
939 out:
940 clear_history_lock ();
946 * save_user() adds a user name to the user list to select. Zero-length
947 * username ("") matches any user.
949 static void
950 save_user (char *name)
952 if (user_count == user_max)
954 user_max = xsum (user_max, USER_INCREMENT);
955 if (size_overflow_p (xtimes (user_max, sizeof (char *))))
957 error (0, 0, "save_user: too many users");
958 return;
960 user_list = xnrealloc (user_list, user_max, sizeof (char *));
962 user_list[user_count++] = xstrdup (name);
966 * save_file() adds file name and associated module to the file list to select.
968 * If "dir" is null, store a file name as is.
969 * If "name" is null, store a directory name with a '*' on the front.
970 * Else, store concatenated "dir/name".
972 * Later, in the "select" stage:
973 * - if it starts with '*', it is prefix-matched against the repository.
974 * - if it has a '/' in it, it is matched against the repository/file.
975 * - else it is matched against the file name.
977 static void
978 save_file (char *dir, char *name, char *module)
980 struct file_list_str *fl;
982 if (file_count == file_max)
984 file_max = xsum (file_max, FILE_INCREMENT);
985 if (size_overflow_p (xtimes (file_max, sizeof (*fl))))
987 error (0, 0, "save_file: too many files");
988 return;
990 file_list = xnrealloc (file_list, file_max, sizeof (*fl));
992 fl = &file_list[file_count++];
993 fl->l_module = module;
995 if (dir && *dir)
997 if (name && *name)
998 fl->l_file = Xasprintf ("%s/%s", dir, name);
999 else
1000 fl->l_file = Xasprintf ("*%s", dir);
1002 else
1004 if (name && *name)
1005 fl->l_file = xstrdup (name);
1006 else
1007 error (0, 0, "save_file: null dir and file name");
1011 static void
1012 save_module (char *module)
1014 if (mod_count == mod_max)
1016 mod_max = xsum (mod_max, MODULE_INCREMENT);
1017 if (size_overflow_p (xtimes (mod_max, sizeof (char *))))
1019 error (0, 0, "save_module: too many modules");
1020 return;
1022 mod_list = xnrealloc (mod_list, mod_max, sizeof (char *));
1024 mod_list[mod_count++] = xstrdup (module);
1027 static void
1028 expand_modules (void)
1032 /* fill_hrec
1034 * Take a ptr to 7-part history line, ending with a newline, for example:
1036 * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
1038 * Split it into 7 parts and drop the parts into a "struct hrec".
1039 * Return a pointer to the character following the newline.
1043 #define NEXT_BAR(here) do { \
1044 while (isspace (*line)) line++; \
1045 hr->here = line; \
1046 while ((c = *line++) && c != '|') ; \
1047 if (!c) return; line[-1] = '\0'; \
1048 } while (0)
1050 static void
1051 fill_hrec (char *line, struct hrec *hr)
1053 char *cp;
1054 int c;
1056 hr->type = hr->user = hr->dir = hr->repos = hr->rev = hr->file =
1057 hr->end = hr->mod = NULL;
1058 hr->date = -1;
1059 hr->idx = ++hrec_idx;
1061 while (isspace ((unsigned char) *line))
1062 line++;
1064 hr->type = line++;
1065 hr->date = strtoul (line, &cp, 16);
1066 if (cp == line || *cp != '|')
1067 return;
1068 line = cp + 1;
1069 NEXT_BAR (user);
1070 NEXT_BAR (dir);
1071 if ((cp = strrchr (hr->dir, '*')) != NULL)
1073 *cp++ = '\0';
1074 hr->end = line + strtoul (cp, NULL, 16);
1076 else
1077 hr->end = line - 1; /* A handy pointer to '\0' */
1078 NEXT_BAR (repos);
1079 NEXT_BAR (rev);
1080 if (strchr ("FOET", *(hr->type)))
1081 hr->mod = line;
1083 NEXT_BAR (file);
1087 #ifndef STAT_BLOCKSIZE
1088 #if HAVE_STRUCT_STAT_ST_BLKSIZE
1089 #define STAT_BLOCKSIZE(s) (s).st_blksize
1090 #else
1091 #define STAT_BLOCKSIZE(s) (4 * 1024)
1092 #endif
1093 #endif
1096 /* read_hrecs_file's job is to read a history file and fill in new "hrec"
1097 * (history record) array elements with the ones we need to print.
1099 * Logic:
1100 * - Read a block from the file.
1101 * - Walk through the block parsing line into hr records.
1102 * - if the hr isn't used, free its strings, if it is, bump the hrec counter
1103 * - at the end of a block, copy the end of the current block to the start
1104 * of space for the next block, then read in the next block. If we get less
1105 * than the whole block, we're done.
1107 static int
1108 read_hrecs_file (Node *p, void *closure)
1110 char *cpstart, *cpend, *cp, *nl;
1111 char *hrline;
1112 int i;
1113 int fd;
1114 struct stat st_buf;
1115 const char *fname = p->key;
1117 if ((fd = CVS_OPEN (fname, O_RDONLY | OPEN_BINARY)) < 0)
1119 error (0, errno, "cannot open history file `%s'", fname);
1120 return 0;
1123 if (fstat (fd, &st_buf) < 0)
1125 error (0, errno, "can't stat history file `%s'", fname);
1126 return 0;
1129 if (!(st_buf.st_size))
1131 error (0, 0, "history file `%s' is empty", fname);
1132 return 0;
1135 cpstart = xnmalloc (2, STAT_BLOCKSIZE (st_buf));
1136 cpstart[0] = '\0';
1137 cp = cpend = cpstart;
1139 for (;;)
1141 for (nl = cp; nl < cpend && *nl != '\n'; nl++)
1142 if (!isprint (*nl)) *nl = ' ';
1144 if (nl >= cpend)
1146 if (nl - cp >= STAT_BLOCKSIZE (st_buf))
1148 error(1, 0, "history line %ld too long (> %lu)", hrec_idx + 1,
1149 (unsigned long) STAT_BLOCKSIZE(st_buf));
1151 if (nl > cp)
1152 memmove (cpstart, cp, nl - cp);
1153 nl = cpstart + (nl - cp);
1154 cp = cpstart;
1155 i = read (fd, nl, STAT_BLOCKSIZE(st_buf));
1156 if (i > 0)
1158 cpend = nl + i;
1159 *cpend = '\0';
1160 continue;
1162 if (i < 0)
1164 error (0, errno, "error reading history file `%s'", fname);
1165 return 0;
1167 if (nl == cp) break;
1168 error (0, 0, "warning: no newline at end of history file `%s'",
1169 fname);
1171 *nl = '\0';
1173 if (hrec_count == hrec_max)
1175 struct hrec *old_head = hrec_head;
1177 hrec_max += HREC_INCREMENT;
1178 hrec_head = xnrealloc (hrec_head, hrec_max, sizeof (struct hrec));
1179 if (last_since_tag)
1180 last_since_tag = hrec_head + (last_since_tag - old_head);
1181 if (last_backto)
1182 last_backto = hrec_head + (last_backto - old_head);
1185 /* fill_hrec dates from when history read the entire
1186 history file in one chunk, and then records were pulled out
1187 by pointing to the various parts of this big chunk. This is
1188 why there are ugly hacks here: I don't want to completely
1189 re-write the whole history stuff right now. */
1191 hrline = xstrdup (cp);
1192 fill_hrec (hrline, &hrec_head[hrec_count]);
1193 if (select_hrec (&hrec_head[hrec_count]))
1194 hrec_count++;
1195 else
1196 free (hrline);
1198 cp = nl + 1;
1200 free (cpstart);
1201 close (fd);
1202 return 1;
1207 /* Read the history records in from a list of history files. */
1208 static void
1209 read_hrecs (List *flist)
1211 int files_read;
1213 /* The global history records are already initialized to 0 according to
1214 * ANSI C.
1216 hrec_max = HREC_INCREMENT;
1217 hrec_head = xmalloc (hrec_max * sizeof (struct hrec));
1218 hrec_idx = 0;
1220 files_read = walklist (flist, read_hrecs_file, NULL);
1221 if (!files_read)
1222 error (1, 0, "No history files read.");
1224 /* Special selection problem: If "since_tag" is set, we have saved every
1225 * record from the 1st occurrence of "since_tag", when we want to save
1226 * records since the *last* occurrence of "since_tag". So what we have
1227 * to do is bump hrec_head forward and reduce hrec_count accordingly.
1229 if (last_since_tag)
1231 hrec_count -= (last_since_tag - hrec_head);
1232 hrec_head = last_since_tag;
1235 /* Much the same thing is necessary for the "backto" option. */
1236 if (last_backto)
1238 hrec_count -= (last_backto - hrec_head);
1239 hrec_head = last_backto;
1245 /* Utility program for determining whether "find" is inside "string" */
1246 static int
1247 within (char *find, char *string)
1249 int c, len;
1251 if (!find || !string)
1252 return 0;
1254 c = *find++;
1255 len = strlen (find);
1257 while (*string)
1259 if (!(string = strchr (string, c)))
1260 return 0;
1261 string++;
1262 if (!strncmp (find, string, len))
1263 return 1;
1265 return 0;
1268 /* The purpose of "select_hrec" is to apply the selection criteria based on
1269 * the command arguments and defaults and return a flag indicating whether
1270 * this record should be remembered for printing.
1272 static int
1273 select_hrec (struct hrec *hr)
1275 char **cpp, *cp, *cp2;
1276 struct file_list_str *fl;
1277 int count;
1279 /* basic validity checking */
1280 if (!hr->type || !hr->user || !hr->dir || !hr->repos || !hr->rev ||
1281 !hr->file || !hr->end)
1283 error (0, 0, "warning: history line %ld invalid", hr->idx);
1284 return 0;
1287 /* "Since" checking: The argument parser guarantees that only one of the
1288 * following four choices is set:
1290 * 1. If "since_date" is set, it contains the date specified on the
1291 * command line. hr->date fields earlier than "since_date" are ignored.
1292 * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
1293 * number (which is of limited use) or a symbolic TAG. Each RCS file
1294 * is examined and the date on the specified revision (or the revision
1295 * corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
1296 * compared against hr->date as in 1. above.
1297 * 3. If "since_tag" is set, matching tag records are saved. The field
1298 * "last_since_tag" is set to the last one of these. Since we don't
1299 * know where the last one will be, all records are saved from the
1300 * first occurrence of the TAG. Later, at the end of "select_hrec"
1301 * records before the last occurrence of "since_tag" are skipped.
1302 * 4. If "backto" is set, all records with a module name or file name
1303 * matching "backto" are saved. In addition, all records with a
1304 * repository field with a *prefix* matching "backto" are saved.
1305 * The field "last_backto" is set to the last one of these. As in
1306 * 3. above, "select_hrec" adjusts to include the last one later on.
1308 if (since_date)
1310 char *ourdate = date_from_time_t (hr->date);
1311 count = RCS_datecmp (ourdate, since_date);
1312 free (ourdate);
1313 if (count < 0)
1314 return 0;
1316 else if (*since_rev)
1318 Vers_TS *vers;
1319 time_t t;
1320 struct file_info finfo;
1322 memset (&finfo, 0, sizeof finfo);
1323 finfo.file = hr->file;
1324 /* Not used, so don't worry about it. */
1325 finfo.update_dir = NULL;
1326 finfo.fullname = finfo.file;
1327 finfo.repository = hr->repos;
1328 finfo.entries = NULL;
1329 finfo.rcs = NULL;
1331 vers = Version_TS (&finfo, NULL, since_rev, NULL, 1, 0);
1332 if (vers->vn_rcs)
1334 if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, NULL, 0))
1335 != (time_t) 0)
1337 if (hr->date < t)
1339 freevers_ts (&vers);
1340 return 0;
1344 freevers_ts (&vers);
1346 else if (*since_tag)
1348 if (*(hr->type) == 'T')
1351 * A 'T'ag record, the "rev" field holds the tag to be set,
1352 * while the "repos" field holds "D"elete, "A"dd or a rev.
1354 if (within (since_tag, hr->rev))
1356 last_since_tag = hr;
1357 return 1;
1359 else
1360 return 0;
1362 if (!last_since_tag)
1363 return 0;
1365 else if (*backto)
1367 if (within (backto, hr->file) || within (backto, hr->mod) ||
1368 within (backto, hr->repos))
1369 last_backto = hr;
1370 else
1371 return 0;
1374 /* User checking:
1376 * Run down "user_list", match username ("" matches anything)
1377 * If "" is not there and actual username is not there, return failure.
1379 if (user_list && hr->user)
1381 for (cpp = user_list, count = user_count; count; cpp++, count--)
1383 if (!**cpp)
1384 break; /* null user == accept */
1385 if (!strcmp (hr->user, *cpp)) /* found listed user */
1386 break;
1388 if (!count)
1389 return 0; /* Not this user */
1392 /* Record type checking:
1394 * 1. If Record type is not in rec_types field, skip it.
1395 * 2. If mod_list is null, keep everything. Otherwise keep only modules
1396 * on mod_list.
1397 * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list". If
1398 * file_list is null, keep everything. Otherwise, keep only files on
1399 * file_list, matched appropriately.
1401 if (!strchr (rec_types, *(hr->type)))
1402 return 0;
1403 if (!strchr ("TFOE", *(hr->type))) /* Don't bother with "file" if "TFOE" */
1405 if (file_list) /* If file_list is null, accept all */
1407 for (fl = file_list, count = file_count; count; fl++, count--)
1409 /* 1. If file_list entry starts with '*', skip the '*' and
1410 * compare it against the repository in the hrec.
1411 * 2. If file_list entry has a '/' in it, compare it against
1412 * the concatenation of the repository and file from hrec.
1413 * 3. Else compare the file_list entry against the hrec file.
1415 char *cmpfile = NULL;
1417 if (*(cp = fl->l_file) == '*')
1419 cp++;
1420 /* if argument to -p is a prefix of repository */
1421 if (!strncmp (cp, hr->repos, strlen (cp)))
1423 hr->mod = fl->l_module;
1424 break;
1427 else
1429 if (strchr (cp, '/'))
1431 cmpfile = Xasprintf ("%s/%s", hr->repos, hr->file);
1432 cp2 = cmpfile;
1434 else
1436 cp2 = hr->file;
1439 /* if requested file is found within {repos}/file fields */
1440 if (within (cp, cp2))
1442 hr->mod = fl->l_module;
1443 if (cmpfile != NULL)
1444 free (cmpfile);
1445 break;
1447 if (cmpfile != NULL)
1448 free (cmpfile);
1451 if (!count)
1452 return 0; /* String specified and no match */
1455 if (mod_list)
1457 for (cpp = mod_list, count = mod_count; count; cpp++, count--)
1459 if (hr->mod && !strcmp (hr->mod, *cpp)) /* found module */
1460 break;
1462 if (!count)
1463 return 0; /* Module specified & this record is not one of them. */
1466 return 1; /* Select this record unless rejected above. */
1469 /* The "sort_order" routine (when handed to qsort) has arranged for the
1470 * hrecs files to be in the right order for the report.
1472 * Most of the "selections" are done in the select_hrec routine, but some
1473 * selections are more easily done after the qsort by "accept_hrec".
1475 static void
1476 report_hrecs (void)
1478 struct hrec *hr, *lr;
1479 struct tm *tm;
1480 int i, count, ty;
1481 char *cp;
1482 int user_len, file_len, rev_len, mod_len, repos_len;
1484 if (*since_tag && !last_since_tag)
1486 (void) printf ("No tag found: %s\n", since_tag);
1487 return;
1489 else if (*backto && !last_backto)
1491 (void) printf ("No module, file or repository with: %s\n", backto);
1492 return;
1494 else if (hrec_count < 1)
1496 (void) printf ("No records selected.\n");
1497 return;
1500 user_len = file_len = rev_len = mod_len = repos_len = 0;
1502 /* Run through lists and find maximum field widths */
1503 hr = lr = hrec_head;
1504 hr++;
1505 for (count = hrec_count; count--; lr = hr, hr++)
1507 char *repos;
1509 if (!count)
1510 hr = NULL;
1511 if (!accept_hrec (lr, hr))
1512 continue;
1514 ty = *(lr->type);
1515 repos = xstrdup (lr->repos);
1516 if ((cp = strrchr (repos, '/')) != NULL)
1518 if (lr->mod && !strcmp (++cp, lr->mod))
1520 (void) strcpy (cp, "*");
1523 if ((i = strlen (lr->user)) > user_len)
1524 user_len = i;
1525 if ((i = strlen (lr->file)) > file_len)
1526 file_len = i;
1527 if (ty != 'T' && (i = strlen (repos)) > repos_len)
1528 repos_len = i;
1529 if (ty != 'T' && (i = strlen (lr->rev)) > rev_len)
1530 rev_len = i;
1531 if (lr->mod && (i = strlen (lr->mod)) > mod_len)
1532 mod_len = i;
1533 free (repos);
1536 /* Walk through hrec array setting "lr" (Last Record) to each element.
1537 * "hr" points to the record following "lr" -- It is NULL in the last
1538 * pass.
1540 * There are two sections in the loop below:
1541 * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
1542 * decide whether the record should be printed.
1543 * 2. Based on the record type, format and print the data.
1545 for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
1547 char *workdir;
1548 char *repos;
1550 if (!hrec_count)
1551 hr = NULL;
1552 if (!accept_hrec (lr, hr))
1553 continue;
1555 ty = *(lr->type);
1556 if (!tz_local)
1558 time_t t = lr->date + tz_seconds_east_of_GMT;
1559 tm = gmtime (&t);
1561 else
1562 tm = localtime (&(lr->date));
1564 (void) printf ("%c %04d-%02d-%02d %02d:%02d %s %-*s", ty,
1565 tm->tm_year+1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour,
1566 tm->tm_min, tz_name, user_len, lr->user);
1568 workdir = xmalloc (strlen (lr->dir) + strlen (lr->end) + 10);
1569 (void) sprintf (workdir, "%s%s", lr->dir, lr->end);
1570 if ((cp = strrchr (workdir, '/')) != NULL)
1572 if (lr->mod && !strcmp (++cp, lr->mod))
1574 (void) strcpy (cp, "*");
1577 repos = xmalloc (strlen (lr->repos) + 10);
1578 (void) strcpy (repos, lr->repos);
1579 if ((cp = strrchr (repos, '/')) != NULL)
1581 if (lr->mod && !strcmp (++cp, lr->mod))
1583 (void) strcpy (cp, "*");
1587 switch (ty)
1589 case 'T':
1590 /* 'T'ag records: repository is a "tag type", rev is the tag */
1591 (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev,
1592 repos);
1593 if (working)
1594 (void) printf (" {%s}", workdir);
1595 break;
1596 case 'F':
1597 case 'E':
1598 case 'O':
1599 if (lr->rev && *(lr->rev))
1600 (void) printf (" [%s]", lr->rev);
1601 (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
1602 mod_len + 1 - (int) strlen (lr->mod),
1603 "=", workdir);
1604 break;
1605 case 'W':
1606 case 'U':
1607 case 'P':
1608 case 'C':
1609 case 'G':
1610 case 'M':
1611 case 'A':
1612 case 'R':
1613 (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
1614 file_len, lr->file, repos_len, repos,
1615 lr->mod ? lr->mod : "", workdir);
1616 break;
1617 default:
1618 (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
1619 break;
1621 (void) putchar ('\n');
1622 free (workdir);
1623 free (repos);
1627 static int
1628 accept_hrec (struct hrec *lr, struct hrec *hr)
1630 int ty;
1632 ty = *(lr->type);
1634 if (last_since_tag && ty == 'T')
1635 return 1;
1637 if (v_checkout)
1639 if (ty != 'O')
1640 return 0; /* Only interested in 'O' records */
1642 /* We want to identify all the states that cause the next record
1643 * ("hr") to be different from the current one ("lr") and only
1644 * print a line at the allowed boundaries.
1647 if (!hr || /* The last record */
1648 strcmp (hr->user, lr->user) || /* User has changed */
1649 strcmp (hr->mod, lr->mod) ||/* Module has changed */
1650 (working && /* If must match "workdir" */
1651 (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
1652 strcmp (hr->end, lr->end)))) /* the 2nd parts differ */
1654 return 1;
1656 else if (modified)
1658 if (!last_entry || /* Don't want only last rec */
1659 !hr || /* Last entry is a "last entry" */
1660 strcmp (hr->repos, lr->repos) || /* Repository has changed */
1661 strcmp (hr->file, lr->file))/* File has changed */
1662 return 1;
1664 if (working)
1665 { /* If must match "workdir" */
1666 if (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
1667 strcmp (hr->end, lr->end)) /* the 2nd parts differ */
1668 return 1;
1671 else if (module_report)
1673 if (!last_entry || /* Don't want only last rec */
1674 !hr || /* Last entry is a "last entry" */
1675 strcmp (hr->mod, lr->mod) ||/* Module has changed */
1676 strcmp (hr->repos, lr->repos) || /* Repository has changed */
1677 strcmp (hr->file, lr->file))/* File has changed */
1678 return 1;
1680 else
1682 /* "extract" and "tag_report" always print selected records. */
1683 return 1;
1686 return 0;