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);
16 # Fix bug 21022 by restricting to C locale
17 setlocale(&POSIX::LC_ALL, "C");
20 $Text::Wrap::columns = 77;
21 $Text::Wrap::unexpand = 0;
24 my (@files, @ebuilds, @conflicts, @trivial, @unknown, @new_versions, %actions);
25 my ($input, $editor, $entry, $user, $date, $text, $year, $vcs);
26 my ($opt_help, $opt_strict, $opt_version);
34 diff => "cvs -f diff -U0",
35 status => "cvs -fn up",
38 regex => qr/^Index: (([^\/]*?)\.ebuild)\s*$/
41 diff => "svn diff -N",
42 status => "svn status",
45 regex => qr/^Index: (([^\/]*?)\.ebuild)\s*$/
49 status => "git diff-index HEAD --name-status",
51 # This value should usually be 3 but on new file mode we need skip+1.
52 # So 4 should be fine anyway.
54 regex => qr/^diff \-\-git \S*\/((\S*)\.ebuild)/
59 (my $usage = <<" EOF") =~ s/^\t//gm;
60 Usage: echangelog [options] <changelog message>
63 --help err, this screen ...
64 --strict abort on trivial/no changes
65 --version show version info
72 my $Revision = "Last svn change rev";
73 my $Date = "Last svn change date";
75 print "echangelog\n$Revision$foo \n$Date$foo\n";
81 'strict' => \$opt_strict,
82 'version' => \$opt_version,
86 version() if $opt_version;
88 # Figure out what kind of repo we are in.
91 } elsif ( -d '.svn' ) {
93 } elsif ( -f '/usr/bin/git' and open GIT, "git rev-parse --git-dir |" ) {
97 die "No CVS, .git, .svn directories found, what kind of repo is this?";
100 # Read the current ChangeLog
101 if (-f 'ChangeLog') {
102 open I, '<ChangeLog' or die "Can't open ChangeLog for input: $!\n";
103 { local $/ = undef; $text = <I>; }
106 # No ChangeLog here, maybe we should make one...
108 open C, "portageq envvar PORTDIR |" or die "Can't find PORTDIR";
113 open I, "< $new/skel.ChangeLog"
114 or die "Can't open $new/skel.ChangeLog for input: $!\n";
115 { local $/ = undef; $text = <I>; }
117 $text =~ s/^\*.*//ms; # don't need the fake entry
119 die "This should be run in a directory with ebuilds...\n";
123 # Figure out what has changed around here
124 open C, $vcs{$vcs}{status}.' 2>&1 |' or die "Can't run ".$vcs{$vcs}{status}.": $!\n";
129 $filename =~ /\S*\/(\S*)/;
141 } elsif (/^\?\s+(\S+)/) {
144 $filename =~ /\S*\/(\S*)/;
158 } elsif (/^([ARMD])\s+\+?\s*(\S+)/) {
159 my ($status, $filename) = ($1,$2);
162 open P, "git rev-parse --sq --show-prefix |";
164 $prefix = substr($prefix, 0, -1);
167 if ($filename =~ /$prefix(\S*)/) {
179 push @files, $filename;
180 ($actions{$filename} = $status) =~ tr/DARM/-+-/d;
184 # git only shows files already added so we need to check for unknown files
187 find(\&git_unknown_objects, "./");
190 sub git_unknown_objects {
192 my ($dev,$ino,$mode,$nlink,$uid,$gid);
194 # Ignore empty directories - git doesn't version them and cvs removes them.
195 if ( (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && ! -d _ ) {
196 open C, $vcs." status $_ 2>&1 1>/dev/null |";
200 push @unknown, $object;
207 # Separate out the trivial files for now
209 !/files.digest|Manifest|ChangeLog/ or do { push @trivial, $_; 0; }
213 !/files.digest|Manifest|ChangeLog/ or do { push @trivial, $_; 0; }
216 # Don't allow any conflicts
219 $vcs reports the following conflicts. Please resolve them before
222 print STDERR map "C $_\n", @conflicts;
226 # Don't allow unknown files (other than the trivial files that were separated
230 $vcs reports the following unknown files. Please use "$vcs add" before
231 running echangelog, or remove the files in question.
233 print STDERR map "? $_\n", @unknown;
237 # Sort the list of files as portage does. None of the operations through
238 # the rest of the script should break this sort.
241 (my $va = $a) =~ s/.*?-(\d.*?)(?:\.ebuild)?$/$1/;
242 (my $vb = $b) =~ s/.*?-(\d.*?)(?:\.ebuild)?$/$1/;
243 my ($na, $sa, $sna, $ra) = ($va =~ /^(.*?)(?:_(alpha|beta||pre|rc|p)(\d*))?(?:-r(\d+))?$/);
244 my ($nb, $sb, $snb, $rb) = ($vb =~ /^(.*?)(?:_(alpha|beta||pre|rc|p)(\d*))?(?:-r(\d+))?$/);
245 my (@na) = split /\.|(?<=\d)(?=[^\d\.])/, $na;
246 my (@nb) = split /\.|(?<=\d)(?=[^\d\.])/, $nb;
250 # compare version numbers first
252 for (my $i = 0; defined $na[$i] or defined $nb[$i]; $i++) {
254 return +1 if defined $na[$i] and !defined $nb[$i];
255 return -1 if defined $nb[$i] and !defined $na[$i];
258 if ($na[$i] =~ /^\d/ and $nb[$i] =~ /^\d/) {
259 $retval = ($na[$i] <=> $nb[$i]);
260 return $retval if $retval;
265 if ($na[$i] =~ /^\D/ and $nb[$i] =~ /^\D/) {
266 $retval = ($na[$i] cmp $nb[$i]);
267 return $retval if $retval;
272 $retval = ($na[$i] =~ /\d/ and -1 or +1);
277 # compare suffix second
279 if (defined $sa and !defined $sb) {
280 return +2 if $sa eq "p";
283 if (defined $sb and !defined $sa) {
284 return -3 if $sb eq "p";
288 if (defined $sa) { # and defined $sb
289 $retval = ($sa cmp $sb);
291 return +4 if $sa eq "p";
292 return -4 if $sb eq "p";
293 return $retval; # suffixes happen to be alphabetical order, mostly
296 # compare suffix number
297 return +5 if defined $sna and !defined $snb;
298 return -5 if defined $snb and !defined $sna;
300 if (defined $sna) { # and defined $snb
301 $retval = ($sna <=> $snb);
302 return $retval if $retval;
309 return +6 if defined $ra and !defined $rb;
310 return -6 if defined $rb and !defined $ra;
312 if (defined $ra) { # and defined $rb
313 return ($ra <=> $rb);
317 # nothing left to compare
322 @files = sort sortfunc @files;
324 # Just to ensure we don't get duplicate entries.
328 foreach my $value (@_) {
329 push(@{$aref}, $value) if !grep(/^$value$/, @{$aref});
333 # Forget ebuilds that only have changed copyrights, unless that's all
334 # the changed files we have
336 @ebuilds = grep /\.ebuild$/, @files;
337 @files = grep !/\.ebuild$/, @files;
341 open C, $vcs{$vcs}{diff}." HEAD -- @ebuilds 2>&1 |" or die "Can't run: ".$vcs{$vcs}{diff}."$!\n";
343 open C, $vcs{$vcs}{diff}." @ebuilds 2>&1 |" or die "Can't run: ".$vcs{$vcs}{diff}."$!\n";
349 # only possible with cvs
350 if (/^$vcs diff: (([^\/]*?)\.ebuild) was removed/) {
353 # We assume GNU diff output format here.
354 # git format: diff --git a/app-doc/repodoc/metadata.xml b/app-doc/repodoc/metadata.xml
355 elsif (/$vcs{$vcs}{regex}/) {
362 last if /^deleted file mode|^index/;
363 if (/^new file mode/) {
365 mypush(@new_versions, $version);
371 # check if more than just copyright date changed.
372 # skip some lines (vcs dependent)
373 foreach(1..$vcs{$vcs}{skip}) {
379 if (/^[-+](?!# Copyright)/) {
385 # at this point we've either added $f to @files or not,
386 # and we have the next line in $_ for processing
389 elsif (/^$vcs.*?: (([^\/]*?)\.ebuild) is a new entry/) {
391 mypush(@new_versions, $2);
394 # other cvs output is ignored
400 # Subversion diff doesn't identify new versions. So use the status command
401 if (($vcs eq "svn") and (@ebuilds)) {
402 open C, $vcs{$vcs}{status}." @ebuilds 2>&1 |" or die "Can't run: ".$vcs{$vcs}{status}."$!\n";
406 if (/^A\s+\+?\s*(([^\s]*)\.ebuild)/) {
408 mypush(@new_versions, $2);
415 # When a package move occurs, the versions appear to be new even though they are
416 # not. Trim them from @new_versions in that case.
417 @new_versions = grep { $text !~ /^\*\Q$_\E\s/m } @new_versions;
419 # Check if we have any files left, otherwise re-insert ebuild list
420 # (of course, both might be empty anyway)
421 @files = @ebuilds unless (@files);
423 # Allow ChangeLog entries with no changed files, but give a fat warning
426 print STDERR "** NOTE: No non-trivial changed files found. Normally echangelog\n";
427 print STDERR "** should be run after all affected files have been added and/or\n";
428 print STDERR "** modified. Did you forget to $vcs add?\n";
432 print STDERR "** In strict mode, exiting\n";
436 @files = sort sortfunc @trivial;
437 @files = qw/ChangeLog/ unless @files; # last resort to put something in the list
441 @files = sort sortfunc @files;
442 @new_versions = sort sortfunc @new_versions;
444 # Get the input from the cmdline, editor or stdin
448 # Testing for defined() allows ECHANGELOG_EDITOR='' to cancel EDITOR
449 $editor = defined($ENV{'ECHANGELOG_EDITOR'}) ? $ENV{'ECHANGELOG_EDITOR'} :
450 $ENV{'EDITOR'} || undef;
453 system("$editor ChangeLog.new");
456 # This usually happens when the editor got forcefully killed; and
457 # the terminal is probably messed up: so we reset things.
458 system('/usr/bin/stty sane');
459 print STDERR "Editor died! Reverting to stdin method.\n";
462 if (open I, "<ChangeLog.new") {
467 print STDERR "Error opening ChangeLog.new: $!\n";
468 print STDERR "Reverting to stdin method.\n";
472 unlink 'ChangeLog.new';
477 print "Please type the log entry: use Ctrl-d to finish, Ctrl-c to abort...\n";
482 die "Empty entry; aborting\n" unless $input =~ /\S/;
484 # If there are any long lines, then wrap the input at $columns chars
485 # (leaving 2 chars on left, one char on right, after adding indentation below).
486 $input =~ s/^\s*(.*?)\s*\z/$1/s; # trim whitespace
487 $input = Text::Wrap::fill(' ', ' ', $input);
489 # Prepend the user info to the input
490 unless ($user = $ENV{'ECHANGELOG_USER'}) {
491 my ($fullname, $username) = (getpwuid($<))[6,0];
492 $fullname =~ s/,.*//; # remove GECOS, bug 80011
493 $user = sprintf "%s <%s\@gentoo.org>", $fullname, $username;
496 # Make sure that we didn't get "root"
497 die "Please set ECHANGELOG_USER or run as non-root\n" if $user =~ /<root@/;
499 $date = strftime("%d %b %Y", gmtime);
500 $entry = "$date; $user ";
501 $entry .= join ', ', map "$actions{$_}$_", @files;
503 $entry = Text::Wrap::fill(' ', ' ', $entry); # does not append a \n
504 $entry .= "\n$input"; # append user input
506 # Each one of these regular expressions will eat the whitespace
507 # leading up to the next entry (except the two-space leader on the
508 # front of a dated entry), so it needs to be replaced with a
509 # double carriage-return. This helps to normalize the spacing in
512 # Insert at the top with a new version marker
513 $text =~ s/^( .*? ) # grab header
514 \s*\n(?=\ \ \d|\*|\z) # suck up trailing whitespace
516 join("\n", map "*$_ ($date)", reverse @new_versions) .
518 or die "Failed to insert new entry (4)\n";
520 # Changing an existing patch or ebuild, no new version marker
522 $text =~ s/^( .*? ) # grab header
523 \s*\n(?=\ \ \d|\*|\z) # suck up trailing whitespace
525 or die "Failed to insert new entry (3)\n";
530 my ($cwd) = getcwd();
532 $cwd =~ m|.*/(\w+-\w+\|virtual)/([^/]+)|
533 or die "Can't figure out category/package.. sorry!\n";
534 my ($category, $package_name) = ($1, $2);
535 $t =~ s/^(# ChangeLog for).*/$1 $category\/$package_name/;
540 # New packages and/or ones that have moved around often have stale data here.
541 # But only do that in places where ebuilds are around (as echangelog can be
542 # used in profiles/ and such places).
543 if (grep(/\.ebuild$/, @files)) {
544 $text = update_cat_pn($text);
547 sub update_copyright {
549 (my $year = $date) =~ s/.* //;
551 $t =~ s/^# Copyright \d+(?= )/$&-$year/m or
552 $t =~ s/^(# Copyright) \d+-(\d+)/$1 1999-$year/m;
557 # Update the copyright year in the ChangeLog
558 $text = update_copyright($text);
560 # Write the new ChangeLog
561 open O, '>ChangeLog.new' or die "Can't open ChangeLog.new for output: $!\n";
562 print O $text or die "Can't write ChangeLog.new: $!\n";
563 close O or die "Can't close ChangeLog.new: $!\n";
565 # Update affected ebuild copyright dates. There is no reason to update the
566 # copyright lines on ebuilds that haven't changed. I verified this with an IP
568 for my $e (grep /\.ebuild$/, @files) {
570 my ($etext, $netext);
572 open E, "<$e" or warn("Can't read $e to update copyright year\n"), next;
573 { local $/ = undef; $etext = <E>; }
576 # Attempt the substitution and compare
577 $netext = update_copyright($etext);
578 next if $netext eq $etext; # skip this file if no change.
580 # Write the new ebuild
581 open E, ">$e.new" or warn("Can't open $e.new\n"), next;
583 close E or warn("Can't write $e.new\n"), next;
585 # Move things around and show the diff
586 system "diff -U 0 $e $e.new";
587 rename "$e.new", $e or warn("Can't rename $e.new: $!\n");
591 # Move things around and show the ChangeLog diff
592 system 'diff -Nu ChangeLog ChangeLog.new';
593 rename 'ChangeLog.new', 'ChangeLog' or die "Can't rename ChangeLog.new: $!\n";
595 # Okay, now we have a starter ChangeLog to work with.
596 # The text will be added just like with any other ChangeLog below.
597 # Add the new ChangeLog to vcs before continuing.
599 if (open F, "CVS/Entries") {
600 system("cvs -f add ChangeLog") unless (scalar grep /^\/ChangeLog\//, <F>);
602 } elsif ($vcs eq "svn") {
603 if (open F, ".svn/entries") {
604 system("svn add ChangeLog") unless (scalar grep /ChangeLog/, <F>);
607 system("$vcs{$vcs}{add} ChangeLog 2>&1 >> /dev/null");
610 # vim: set ts=4 sw=4 tw=0: