summaryrefslogtreecommitdiff
path: root/config/cvsu
blob: 4c7071fcb0dbd37bfa1a65eaf7548c5c28f98b93 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
#!/usr/bin/perl -w
# An efficient substitute for `cvs -n update'.

use strict;
use Getopt::Long;

# Do a quick check to see what files are out of date.
# tromey Thu Mar 16 1995
#
# derived from http://www.cygnus.com/~tromey/ - jmm

# To Do:
# Add option to include leading (non-`.') directory names of mentioned files

(my $VERSION = '$Revision: 1.1 $ ') =~ tr/[0-9].//cd;
(my $program_name = $0) =~ s|.*/||;

my @months = qw (Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my @days = qw (Sun Mon Tue Wed Thu Fri Sat);

my $debug = 0;

# If this is set, do only local files.
my $local = 0;

# If this is set, show conflicts with C
my $conflicts = 0;

# If this is set, then don't check any dates and just print the names
# of all version-controlled files (but no directories).
my $list_all_files = 0;

# Regex that matches file (as opposed to dir) entries in CVS/Entries.
# Note that we allow an empty value ('*' vs '+') for timestamp, to
# work around an odd bug in CVS.
my $file_entry_re = qr{^/([^/]+)/([^/]+)/([^/]*)};

sub usage ($)
{
  my ($exit_code) = @_;
  no strict 'refs';
  no strict 'subs';
  my $STREAM = ($exit_code == 0 ? STDOUT : STDERR);
  if ($exit_code != 0)
    {
      print $STREAM "Try `$program_name --help' for more information.\n";
    }
  else
    {
      print $STREAM <<EOF;
Usage: $program_name [OPTIONS] [DIRECTORY]...

An efficient substitute for `cvs -n update'.

In a cvs-checked-out working directory, list all cvs-controlled files
that have been modified (or even touched but not changed), cvs added, or
cvs removed.  This script is a lot faster than `cvs -n update' because
it doesn't use the repository.  So for people at remote sites, it's MUCH
faster.  Also, when you have changes to files in subdirectories, the
listing it produces is more useful since it includes the relative path
name on each line indicating an Added, Removed, or Modified file.

NOTE: since $program_name works only on the local files it may indicate
files are modified that cvs can determine where merely touched. Similarly
files with a C may have had conflicts that have since been removed.

Here are the options:

   --list-all-files   don't check any dates; just print the names of all
                        version-controlled files (but no directories)
   --local (-l)       don't process subdirectories (like cvs' -l option)
   --help             display this help and exit
   --version          output version information and exit
   --conflicts        show conflicts with C instead of the default M

EOF
    }
  exit $exit_code;
}

sub do_directory ($$);

{
  GetOptions
    (
     debug => \$debug,
     'list-all-files' => \$list_all_files,
     conflicts => \$conflicts,
     local => \$local,
     l => \$local,
     help => sub { usage 0 },
     version => sub { print "$program_name version $VERSION\n"; exit },
    ) or usage 1;

  unshift (@ARGV, ".") if !@ARGV;
  # print "$#ARGV ; $ARGV[0], $ARGV[1]\n";
  foreach (@ARGV)
    {
      do_directory ($_, 1);
    }

  exit 0;
}

sub do_directory ($$) {
    my ($thisdir, $is_command_line_arg) = @_;

    $thisdir =~ s,^\./,,;
    my $prefix = ($thisdir eq '.' ? '' : "$thisdir/");

    print "\tCALL; thisdir = $thisdir\n"
      if $debug;

    # Scan CVS/Entries.
    my %version;
    my %entries;
    my %is_dir;

    my $entries_file = "${prefix}CVS/Entries";
    if ( ! open (ENTRIES, '<', $entries_file))
      {
	my $warn = $is_command_line_arg ? '' : "Warning: ";
	warn "$program_name: ${warn}couldn't open $entries_file: $!\n";
	$is_command_line_arg
	  and exit 1;
	return;
      }

    while (<ENTRIES>) {
        # Ignore entries for directories.
	if (m,^D,)
	  {
	    next if /^D$/;
	    if (m,^D/([^/]+)/,)
	      {
		$is_dir{$1} = 1;
		next;
	      }
	    # else fall through so we get the `invalid line' error
	  }

	/$file_entry_re/
	    || die "$program_name: $entries_file: $.: invalid line\n";
	$entries{$1} = $3 || 'Empty-Timestamp';
	$version{$1} = $2;
    }
    close (ENTRIES);

    # process Entries.Log file if it exists
    # lines are prefixed by A (add) or R (remove)
    # we add or delete accordingly.
    my $entries_log_file = "${prefix}CVS/Entries.Log";
    my $type;
    if (open (ENTRIES, "< $entries_log_file")) {
	while (<ENTRIES>) {
	    if (!/^([AR]) (.*)$/) {
		warn "$program_name: $entries_log_file: $.: unrecognized line format\n";
		next;
	    }
	    ($type, $_) = ($1,$2);
	    # Ignore entries for directories.
	    if (m,^D,)
	      {
		next if /^D$/;
		if (m,^D/([^/]+)/,)
		  {
		    if ($type eq 'A') {
			$is_dir{$1} = 1;
		    } else {
			delete $is_dir{$1};
		    }
		    next;
		  }
		# else fall through so we get the `invalid line' error
	      }

	/$file_entry_re/
		|| die "$program_name: $entries_log_file: $.: invalid line\n";
	    if ($type eq 'A') {
		$entries{$1} = $3;
		$version{$1} = $2;
	    } else {
		delete $entries{$1};
		delete $version{$1};
	    }
	}
	close (ENTRIES);
    }

    foreach (sort keys %entries) {
	# Handle directories later.
	die "$program_name: bogus entry: $prefix$_\n"
	  if ($_ eq 'CVS' || $_ eq '.' || $_ eq '..');
	(print "$prefix$_\n"), next if $list_all_files;
	next if -l "$prefix$_";
	next unless $entries{$_};
	if ($version{$_} =~ /^-/)
	  {
	    # A negative revision number (e.g., `-1.9') means the file is
	    # slated for removal.
	    print "R $prefix$_\n";
	    next;
	  }
	elsif ($version{$_} eq '0')
	  {
	    # A revision number of `0' means the file is slated for addition.
	    print "A $prefix$_\n";
	    next;
	  }

	# These strings appear in the date field only for additions
	# and removals.
	die "$program_name: unexpected entry for $_: $entries{$_}\n"
	  if $entries{$_} eq 'dummy timestamp'
	    || $entries{$_} =~ /^Initial /;

	next unless -f _;

	my $mtime = (stat _) [9];
	print "\t$mtime $_\n"
	  if $debug;
	my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime ($mtime);
	my $s = ($days[$wday] . ' ' . $months[$mon] . ' '
	      . sprintf ("%2d %02d:%02d:%02d %02d", $mday, $hour, $min,
			 $sec, 1900 + $year));
	if ($entries{$_} ne $s) {
	    my $t = 'M';
	    $t = 'C'
		if ($conflicts && $entries{$_} =~ /^Result of merge\+/);
	    print "$t $prefix$_\n";
	    if ($debug) {
		print "\t$entries{$_}\n";
		print "\t$s\n";
		print "================\n";
	    }
	}
    }

    # Now do directories.
    if (!$local)
      {
	foreach (sort keys %is_dir)
	  {
	    print "\tdir = $thisdir, _ = $_\n"
	      if $debug;
	    do_directory ("$prefix$_", 0);
	  }
      }
}