3 # echangelog: Update the ChangeLog for an ebuild. For example:
5 # $ echangelog 'Add ~alpha to KEYWORDS'
7 # > 10 Feb 2003; Aron Griffis <agriffis@gentoo.org> oaf-0.6.8-r1.ebuild :
8 # > Add ~alpha to KEYWORDS
12 use POSIX qw(strftime getcwd setlocale);
14 # Fix bug 21022 by restricting to C locale
15 setlocale(&POSIX::LC_ALL, "C");
18 $Text::Wrap::columns = 77;
19 $Text::Wrap::unexpand = 0;
22 my (@files, @ebuilds, @conflicts, @trivial, @unknown, @new_versions, %actions);
23 my ($input, $editor, $entry, $user, $date, $text, $version, $year, $vcs);
25 my %vcs = ( cvs => { diff => "cvs -f -U0 diff",
26 status => "cvs -fn up",
29 entries => "CVS/Entries" },
30 svn => { diff => "svn diff -N",
31 status => "svn status",
34 entries => ".svn/entries" },
35 git => { diff => "git diff",
47 # Figure out what kind of repo we are in.
51 } elsif ( -d '.svn' ) {
53 } elsif ( -d '.git' ) {
56 print STDERR "** NOTE: No CVS, .git, .svn directories found, cannot know modifications\n";
60 # Read the current ChangeLog
62 open I, '<ChangeLog' or die "Can't open ChangeLog for input: $!\n";
63 { local $/ = undef; $text = <I>; }
66 # No ChangeLog here, maybe we should make one...
68 open I, '<../../skel.ChangeLog'
69 or die "Can't open ../../skel.ChangeLog for input: $!\n";
70 { local $/ = undef; $text = <I>; }
73 $cwd =~ m|.*/(\w+-\w+)/([^/]+)|
74 or die "Can't figure out category/package.. sorry!\n";
75 my ($category, $package_name) = ($1, $2);
76 $text =~ s/^\*.*//ms; # don't need the fake entry
77 $text =~ s/<CATEGORY>/$category/;
78 $text =~ s/<PACKAGE_NAME>/$package_name/;
80 die "This should be run in a directory with ebuilds...\n";
84 # Figure out what has changed around here
85 open C, $vcs{$vcs}{status}.' 2>&1 |' or die "Can't run ".$vcs{$vcs}{status}.": $!\n";
87 if (/^C\s+\+?\s+(\S+)/) {
90 } elsif (/^\?\s+\+?\s+(\S+)/) {
94 } elsif (/^([ARMD])\s+\+?\s+(\S+)/) {
96 ($actions{$2} = $1) =~ tr/ARDM/+--/d;
100 # Separate out the trivial files for now
102 !/files.digest|Manifest|ChangeLog|^files$|^\.$/ or do { push @trivial, $_; 0; }
106 !/files.digest|Manifest|ChangeLog|^files$|^\.$/ or do { push @trivial, $_; 0; }
109 # Don't allow any conflicts
112 $vcs reports the following conflicts. Please resolve them before
115 print STDERR map "C $_\n", @conflicts;
119 # Don't allow unknown files (other than the trivial files that were separated
123 $vcs reports the following unknown files. Please use "cvs add" before
124 running echangelog, or remove the files in question.
126 print STDERR map "? $_\n", @unknown;
130 # Sort the list of files as portage does. None of the operations through
131 # the rest of the script should break this sort.
134 (my $va = $a) =~ s/.*?-(\d.*?)(?:\.ebuild)?$/$1/;
135 (my $vb = $b) =~ s/.*?-(\d.*?)(?:\.ebuild)?$/$1/;
136 my ($na, $sa, $sna, $ra) = ($va =~ /^(.*?)(?:_(alpha|beta||pre|rc|p)(\d*))?(?:-r(\d+))?$/);
137 my ($nb, $sb, $snb, $rb) = ($vb =~ /^(.*?)(?:_(alpha|beta||pre|rc|p)(\d*))?(?:-r(\d+))?$/);
138 my (@na) = split /\.|(?<=\d)(?=[^\d\.])/, $na;
139 my (@nb) = split /\.|(?<=\d)(?=[^\d\.])/, $nb;
143 # compare version numbers first
145 for (my $i = 0; defined $na[$i] or defined $nb[$i]; $i++) {
147 return +1 if defined $na[$i] and !defined $nb[$i];
148 return -1 if defined $nb[$i] and !defined $na[$i];
151 if ($na[$i] =~ /^\d/ and $nb[$i] =~ /^\d/) {
152 $retval = ($na[$i] <=> $nb[$i]);
153 return $retval if $retval;
158 if ($na[$i] =~ /^\D/ and $nb[$i] =~ /^\D/) {
159 $retval = ($na[$i] cmp $nb[$i]);
160 return $retval if $retval;
165 $retval = ($na[$i] =~ /\d/ and -1 or +1);
170 # compare suffix second
172 if (defined $sa and !defined $sb) {
173 return +2 if $sa eq "p";
176 if (defined $sb and !defined $sa) {
177 return -3 if $sb eq "p";
181 if (defined $sa) { # and defined $sb
182 $retval = ($sa cmp $sb);
184 return +4 if $sa eq "p";
185 return -4 if $sb eq "p";
186 return $retval; # suffixes happen to be alphabetical order, mostly
189 # compare suffix number
190 return +5 if defined $sna and !defined $snb;
191 return -5 if defined $snb and !defined $sna;
192 if (defined $sna) { # and defined $snb
193 $retval = ($sna <=> $snb);
194 return $retval if $retval;
201 return +6 if defined $ra and !defined $rb;
202 return -6 if defined $rb and !defined $ra;
203 if (defined $ra) { # and defined $rb
204 return ($ra <=> $rb);
208 # nothing left to compare
212 @files = sort sortfunc @files;
214 # Forget ebuilds that only have changed copyrights, unless that's all
215 # the changed files we have
216 # does not work with svn TODO
217 #@ebuilds = grep /\.ebuild$/, @files;
218 #@files = grep !/\.ebuild$/, @files;
221 open C, $vcs{$vcs}{diff}.@ebuilds." 2>&1 |" or die "Can't run: ".$vcs{$vcs}{diff}."$!\n";
224 if (/^$vcs diff: (([^\/]*?)\.ebuild) was removed/) {
227 elsif (/^Index: (([^\/]*?)\.ebuild)\s*$/) {
228 my ($f, $v) = ($1, $2);
229 # check if more than just copyright date changed.
231 foreach(1..$vcs{$vcs}{skip}){
236 if (/^[-+](?!# Copyright)/) {
241 # at this point we've either added $f to @files or not,
242 # and we have the next line in $_ for processing
245 elsif (/^$vcs.*?: (([^\/]*?)\.ebuild) is a new entry/) {
247 push @new_versions, $2; # new ebuild, will create a new entry
249 # other cvs output is ignored
255 # When a package move occurs, the versions appear to be new even though they are
256 # not. Trim them from @new_versions in that case.
257 @new_versions = grep { $text !~ /^\*\Q$_\E\s/m } @new_versions;
259 # Check if we have any files left, otherwise re-insert ebuild list
260 # (of course, both might be empty anyway)
261 @files = @ebuilds unless (@files);
263 # Allow ChangeLog entries with no changed files, but give a fat warning
266 print STDERR "** NOTE: No non-trivial changed files found. Normally echangelog\n";
267 print STDERR "** should be run after all affected files have been added and/or\n";
268 print STDERR "** modified. Did you forget to cvs add?\n";
270 @files = sort sortfunc @trivial;
271 @files = qw/ChangeLog/ unless @files; # last resort to put something in the list
274 # Get the input from the cmdline, editor or stdin
278 # Testing for defined() allows ECHANGELOG_EDITOR='' to cancel EDITOR
279 $editor = defined($ENV{'ECHANGELOG_EDITOR'}) ? $ENV{'ECHANGELOG_EDITOR'} :
280 $ENV{'EDITOR'} || undef;
282 system("$editor ChangeLog.new");
284 # This usually happens when the editor got forcefully killed; and
285 # the terminal is probably messed up: so we reset things.
286 system('/usr/bin/stty sane');
287 print STDERR "Editor died! Reverting to stdin method.\n";
290 if (open I, "<ChangeLog.new") {
295 print STDERR "Error opening ChangeLog.new: $!\n";
296 print STDERR "Reverting to stdin method.\n";
299 unlink 'ChangeLog.new';
303 print "Please type the log entry: use Ctrl-d to finish, Ctrl-c to abort...\n";
308 die "Empty entry; aborting\n" unless $input =~ /\S/;
310 # If there are any long lines, then wrap the input at $columns chars
311 # (leaving 2 chars on left, one char on right, after adding indentation below).
312 $input =~ s/^\s*(.*?)\s*\z/$1/s; # trim whitespace
313 $input = Text::Wrap::fill('', '', $input) if ($input =~ /^.{80}/m);
314 $input =~ s/^/ /gm; # add indentation
316 # Prepend the user info to the input
317 unless ($user = $ENV{'ECHANGELOG_USER'}) {
318 my ($fullname, $username) = (getpwuid($<))[6,0];
319 $fullname =~ s/,.*//; # remove GECOS, bug 80011
320 $user = sprintf "%s <%s\@gentoo.org>", $fullname, $username;
322 # Make sure that we didn't get "root"
323 die "Please set ECHANGELOG_USER or run as non-root\n" if $user =~ /<root@/;
324 $date = strftime("%d %b %Y", gmtime);
325 $entry = "$date; $user ";
326 $entry .= join ', ', map "$actions{$_}$_", @files;
328 $entry = Text::Wrap::fill(' ', ' ', $entry); # does not append a \n
329 $entry .= "\n$input"; # append user input
331 # Each one of these regular expressions will eat the whitespace
332 # leading up to the next entry (except the two-space leader on the
333 # front of a dated entry), so it needs to be replaced with a
334 # double carriage-return. This helps to normalize the spacing in
337 # Insert at the top with a new version marker
338 $text =~ s/^( .*? ) # grab header
339 \s*\n(?=\ \ \d|\*|\z) # suck up trailing whitespace
341 join("\n", map "*$_ ($date)", reverse @new_versions) .
343 or die "Failed to insert new entry (4)\n";
345 # Changing an existing patch or ebuild, no new version marker
347 $text =~ s/^( .*? ) # grab header
348 \s*\n(?=\ \ \d|\*|\z) # suck up trailing whitespace
350 or die "Failed to insert new entry (3)\n";
353 sub update_copyright {
355 (my $year = $date) =~ s/.* //;
356 $t =~ s/^# Copyright \d+(?= )/$&-$year/m or
357 $t =~ s/^(# Copyright \d+)-(\d+)/$1-$year/m;
361 # Update the copyright year in the ChangeLog
362 $text = update_copyright($text);
364 # Write the new ChangeLog
365 open O, '>ChangeLog.new' or die "Can't open ChangeLog.new for output: $!\n";
366 print O $text or die "Can't write ChangeLog.new: $!\n";
367 close O or die "Can't close ChangeLog.new: $!\n";
369 # Update affected ebuild copyright dates. There is no reason to update the
370 # copyright lines on ebuilds that haven't changed. I verified this with an IP
372 for my $e (grep /\.ebuild$/, @files) {
373 my ($etext, $netext);
374 open E, "<$e" or warn("Can't read $e to update copyright year\n"), next;
375 { local $/ = undef; $etext = <E>; }
378 # Attempt the substitution and compare
379 $netext = update_copyright($etext);
380 next if $netext eq $etext; # skip this file if no change.
382 # Write the new ebuild
383 open E, ">$e.new" or warn("Can't open $e.new\n"), next;
385 close E or warn("Can't write $e.new\n"), next;
387 # Move things around and show the diff
388 system "diff -U 0 $e $e.new";
389 rename "$e.new", $e or warn("Can't rename $e.new: $!\n");
392 # Move things around and show the ChangeLog diff
393 system 'diff -Nu ChangeLog ChangeLog.new';
394 rename 'ChangeLog.new', 'ChangeLog' or die "Can't rename ChangeLog.new: $!\n";
396 # Okay, now we have a starter ChangeLog to work with.
397 # The text will be added just like with any other ChangeLog below.
398 # Add the new ChangeLog to cvs before continuing.
399 if (open F, $vcs{$vcs}{entries} ) {
400 system("$vcs{$vcs}{add} ChangeLog") unless (scalar grep /\/?ChangeLog\/?/, <F>);
403 # vim:sw=4 ts=8 expandtab