Hello community, here is the log from the commit of package mksusecd for openSUSE:Factory checked in at 2018-11-18 23:30:55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/mksusecd (Old) and /work/SRC/openSUSE:Factory/.mksusecd.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "mksusecd" Sun Nov 18 23:30:55 2018 rev:54 rq:649317 version:1.66 Changes: -------- --- /work/SRC/openSUSE:Factory/mksusecd/mksusecd.changes 2018-09-15 15:41:16.708787661 +0200 +++ /work/SRC/openSUSE:Factory/.mksusecd.new/mksusecd.changes 2018-11-18 23:31:14.861549463 +0100 @@ -1,0 +2,8 @@ +Thu Nov 15 15:30:07 UTC 2018 - snw...@suse.de + +- merge gh#openSUSE/mksusecd#39 +- add HOWTO describing some typical uses +- update git2log script +- 1.66 + +-------------------------------------------------------------------- Old: ---- mksusecd-1.65.tar.xz New: ---- mksusecd-1.66.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ mksusecd.spec ++++++ --- /var/tmp/diff_new_pack.JxAwMC/_old 2018-11-18 23:31:15.549548634 +0100 +++ /var/tmp/diff_new_pack.JxAwMC/_new 2018-11-18 23:31:15.549548634 +0100 @@ -18,7 +18,7 @@ Name: mksusecd -Version: 1.65 +Version: 1.66 Release: 0 Summary: Create SUSE Linux installation ISOs License: GPL-3.0+ ++++++ mksusecd-1.65.tar.xz -> mksusecd-1.66.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mksusecd-1.65/HOWTO.md new/mksusecd-1.66/HOWTO.md --- old/mksusecd-1.65/HOWTO.md 1970-01-01 01:00:00.000000000 +0100 +++ new/mksusecd-1.66/HOWTO.md 2018-11-15 16:30:07.000000000 +0100 @@ -0,0 +1,116 @@ +# How to modify a SUSE Linux installation medium + +Sometimes it's convenient to make small modifications to the regular SUSE installation media. + +For this, `mksusecd` exists. + +## Replace or add files + +You can replace or add any file to the medium. Simply create a directory structure and put the file into it. +Then specify all sources to be merged on the command line. For example + +```sh +mkdir -p /tmp/foo/boot/x86_64/loader/ +cp some_suitable_picture.jpg /tmp/foo/boot/x86_64/loader/welcome.jpg +mksusecd --create foo.iso suse.iso /tmp/foo +``` +will create a new image `foo.iso` from `suse.iso` that has a new startup picture. + + +## Change default boot options + +If you always have to enter the same boot options for each installation, add them: + +```sh +mksusecd --create foo.iso --boot "textmode=1 sshd=1 password=XXX" suse.iso +``` + +It is also possible to add a new boot entry instead of modifying the existing one: + +```sh +mksusecd --create foo.iso --add-entry "XXX" --boot "sshd=1 password=XXX" suse.iso +``` + + +## Change default repository + +If you run your own installation server you might want a medium that connects to it per default. To change the +default network repository do, for example: + +```sh +mksusecd --create foo.iso --net "https://example.com/suse" suse.iso +``` + + +## Create small network iso + +If you are regularly installing via network or need to hand out a small image with your modifications, create a network iso. +For this, simply add the `--nano` option: + +```sh +mksusecd --create foo.iso --nano suse.iso +``` + + +## Integrate driver updates + +To seamlessly integrate any driver updates you want applied, add them to the initrd. `mksusecd` has special code for this. For example + +```sh +mksusecd --create foo.iso --initrd bar.dud suse.iso +``` + +creates a new `foo.iso` that will load the driver update `bar.dud` without any extra user interaction. + + +## Include add-ons + +If you have a collection of rpms you would like to be able to install you can have `mksusecd` create an add-on repository and put it on the medium: + +```sh +mksusecd --create foo.iso --addon foo.rpm bar.rpm -- suse.iso +``` + + +## Update kernel (and kernel modules) + +If you need an updated kernel to be able to run the installation, it can get really tricky. `mksusecd` lets you rework the installation +system included on the medium so that a new kernel is used during the installation. You just need the new kernel packages: + +```sh +mksusecd --create foo.iso --kernel kernel-default.rpm kernel-firmware.rpm -- suse.iso +``` + +If you need also KMP packages, add them, too. + +`mksusecd` will try to include the same modules as in the original installation medium. If some modules are missing, it will show +the differences. + +Sometimes just a module is missing in the installation system that you really need. It's possible to add it this way: + +```sh +mksusecd --create foo.iso --kernel kernel-default.rpm kernel-firmware.rpm --modules bar,zap -- suse.iso +``` + +This replaces the kernel and adds modules `bar` and `zap`. Module dependencies will be automatically taken into account. + +Note that the `--kernel` option does **not** change the kernel that is going to be installed! For this, either create a driver update +with the kernel rpms and integrate the driver update or create an add-on with the kernel packages. + + + +## SLES 15 and later: modules and repositories + +SLES 15 supports so-called 'modules' (not to be confused with kernel modules) which are basically repositories for different product components. + +`mksusecd` lets you create a single medium containing the parts you need. + +```sh +mksusecd --list-repos suse.iso extra.iso +``` + +shows the modules that are on the medium. Pick the modules you need and do, for example: + +```sh +mksusecd --create foo.iso --include-repos SLES15,Basesystem-Module,HPC-Module suse.iso extra.iso +``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mksusecd-1.65/VERSION new/mksusecd-1.66/VERSION --- old/mksusecd-1.65/VERSION 2018-09-14 10:53:41.000000000 +0200 +++ new/mksusecd-1.66/VERSION 2018-11-15 16:30:07.000000000 +0100 @@ -1 +1 @@ -1.65 +1.66 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mksusecd-1.65/changelog new/mksusecd-1.66/changelog --- old/mksusecd-1.65/changelog 2018-09-14 10:53:41.000000000 +0200 +++ new/mksusecd-1.66/changelog 2018-11-15 16:30:07.000000000 +0100 @@ -1,102 +1,141 @@ +2018-11-15: 1.66 + - merge gh#openSUSE/mksusecd#39 + - add HOWTO describing some typical uses + - update git2log script + 2018-09-14: 1.65 - - no longer assume repo-md repositories don't include the installer (bsc #1093145) + - merge gh#openSUSE/mksusecd#38 + - no longer assume repo-md repositories don't include the installer + (bsc#1093145) 2018-08-31: 1.64 - - adjust also isozipl to isoinfo changes (bsc #1097814) + - merge gh#openSUSE/mksusecd#37 + - adjust also isozipl to isoinfo changes (bsc#1097814) 2018-08-29: 1.63 + - merge gh#openSUSE/mksusecd#36 - don't forget to sign addon repository 2018-07-05: 1.62 - - don't miss zstd compression module (bsc #1100236) + - merge gh#openSUSE/mksusecd#35 + - don't miss zstd compression module (bsc#1100236) 2018-06-27: 1.61 - - keep some meta data for media checking even if no iso9660 filesystem is used (bnc #1000947) + - merge gh#openSUSE/mksusecd#34 + - keep some meta data for media checking even if no iso9660 + filesystem is used (#1000947) 2018-06-15: 1.60 + - merge gh#openSUSE/mksusecd#33 + - adjust to isoinfo and syslinux changes (bsc#1097814) - don't pass partition offset to tagmedia - - adjust to isoinfo and syslinux changes (bsc #1097814) 2018-05-25: 1.59 - - add -joliet-long to mkisofs call as some file names on our media are too long (bsc #1094687) + - merge gh#openSUSE/mksusecd#32 + - add -joliet-long to mkisofs call as some file names on our media + are too long (bsc#1094687) 2018-05-08: 1.58 - - add links to related blog articles + - merge gh#openSUSE/mksusecd#31 - added link to obs builds + - add links to related blog articles 2018-05-07: 1.57 - - fix cpio archive parser (bsc #1092147) + - merge gh#openSUSE/mksusecd#30 + - fix cpio archive parser (bsc#1092147) -2018-01-18: 1.56 - - fix handling of repo-md repositories with encrypted media +2018-01-19: 1.56 + - merge gh#openSUSE/mksusecd#29 - allow building of encrypted installation media + - fix handling of repo-md repositories with encrypted media -2017-12-01: 1.55 +2017-12-05: 1.55 + - merge gh#openSUSE/mksusecd#28 - support new CHECKSUMS file -2017-11-29: 1.54 +2017-11-30: 1.54 + - merge gh#openSUSE/mksusecd#26 - manage (sle15-style modules) repomd repositories 2017-10-09: 1.53 + - merge gh#openSUSE/mksusecd#24 + - rework cpio parsing function to handle blobs added by our product + creator + - ensure initrd has really been unpacked when --rebuild-initrd + option is used - beautify code - - ensure initrd has really been unpacked when --rebuild-initrd option is used - - rework cpio parsing function to handle blobs added by our product creator -2017-05-18: 1.52 +2017-05-22: 1.52 + - merge gh#openSUSE/mksusecd#23 + - remove iso9660 header when creating image for usb media + (bsc#939456) - clarify description of --fat option - - remove iso9660 header when creating image for usb media (bsc #939456) 2017-04-28: 1.51 - - document all functions - - enhanced README.md + - merge gh#openSUSE/mksusecd#22 - fix getopt config + - enhanced README.md + - document all functions 2017-04-11: 1.50 + - merge gh#openSUSE/mksusecd#21 + - distinguish between repomd/classical repo types and allow to + specify the default repo location - fix typo in help text - - distinguish between repomd/classical repo types and allow to specify the default repo location 2017-04-05: 1.49 + - merge gh#openSUSE/mksusecd#20 - add support for disk images with FAT file system 2017-04-03: 1.48 - undo accidental mksusecd patch 2017-04-03: 1.47 - - some small doc fixes + - merge gh#openSUSE/mksusecd#19 - add dvd/disk image layout description + - some small doc fixes -2017-03-31: 1.46 +2017-04-03: 1.46 + - merge gh#openSUSE/mksusecd#16 - isohybrid: add --size option to specify image size -2017-03-17: 1.45 - - fix typo +2017-03-20: 1.45 + - merge gh#openSUSE/mksusecd#15 - add --rebuild-initrd option for smaller initrds + - fix typo 2017-03-16: 1.44 + - merge gh#openSUSE/mksusecd#14 - support old mksquashfs version 2017-03-16: 1.43 - - fix to work with older modules.dep format (bsc #1027636) + - merge gh#openSUSE/mksusecd#13 + - fix to work with older modules.dep format (bsc#1027636) 2016-08-12: 1.42 - - extended help text and added some small fixes - - fix choosing an add-on name + - merge gh#openSUSE/mksusecd#12 + - support creating add-ons on the target iso (bsc#991935) - be less restrictive when generating add-on aliases - - support creating add-ons on the target iso (bsc #991935) + - fix choosing an add-on name + - extended help text and added some small fixes 2016-08-02: 1.41 - - catch error when we fail to unpack squashfs images + - merge gh#openSUSE/mksusecd#11 - handle modules.order and modules.builtin when updating a kernel + - catch error when we fail to unpack squashfs images -2016-07-14: 1.40 - - tw support: also sign image files +2016-07-15: 1.40 + - merge gh#openSUSE/mksusecd#10 - sanitize function that removes files from iso - remove tumbleweed images in 'micro' format + - tw support: also sign image files 2016-06-20: 1.39 + - merge gh#openSUSE/mksusecd#9 - create missing 'content' file 2016-05-10: 1.38 + - merge gh#openSUSE/mksusecd#8 - adjust list of specially handled initrd modules 2016-01-18: 1.37 @@ -106,25 +145,25 @@ - add hashes for license and control file 2015-12-04: 1.35 - - support uncompressed initrd (bsc #957847) + - support uncompressed initrd (bsc#957847) 2015-10-21: 1.34 - fix ppc boot iso creation - -2015-09-28: 1.33 + - merge branch master + - merge gh#openSUSE/mksusecd#6 - Add link to linux-devtools documentation 2015-08-12: 1.32 - - move isozipl to /usr/bin - make it work also with mkisofs from cdrtools + - move isozipl to /usr/bin 2015-08-10: 1.31 - fix cpio unpacking bug 2015-07-23: 1.30 + - add zipl binary blobs - integrate isozipl in mksusecd - add some options to isohybrid to make it more flexible - - add zipl binary blobs 2015-07-22: 1.29 - added isozipl to make an iso zipl bootable @@ -133,20 +172,25 @@ - fix cpio archive unpacking 2015-07-07: 1.27 + - merge gh#openSUSE/mksusecd#5 - README: link to mkdud; grammar; formatting 2015-07-06: 1.26 + - merge gh#openSUSE/mksusecd#4 - extended readme 2015-06-25: 1.25 - - keep /content file up-to-date even when new files are added to the iso + - keep /content file up-to-date even when new files are added to + the iso 2015-06-19: 1.24 - ensure '/content' is re-signed when necessary 2015-06-19: 1.23 - - implement --kernel option to replace kernel & modules used for booting - - added --add-entry option to create a new boot menu entry for modifications + - implement --kernel option to replace kernel & modules used for + booting + - added --add-entry option to create a new boot menu entry for + modifications 2015-06-03: 1.22 - work properly when different iso sources are specified @@ -173,13 +217,13 @@ - stick to initrd compression format when extending initrd 2015-02-26: 1.14 - - replace content.key with actual signing key used - handle duplicate filenames + - replace content.key with actual signing key used 2015-02-24: 1.13 - - support initrds with pre-sle12 key management - - support adding driver updates to initrd - no-sign option should also prevent updating 'content' file + - support adding driver updates to initrd + - support initrds with pre-sle12 key management 2015-02-10: 1.12 - simplify key handling @@ -188,8 +232,8 @@ - allow to use a user-supplied signing key 2015-02-06: 1.10 - - re-sign '/content' if necessary - fix git2log script + - re-sign '/content' if necessary 2015-01-26: 1.9 - update git2log script @@ -205,18 +249,18 @@ - handle more than 2 el torito entries 2014-10-13: 1.5 + - added '--pico' option - added 'archive' Makefile target - updated git2log script - - added '--pico' option 2014-08-13: 1.4 - - support adding rpms to the initrd - update checksums in /content file + - support adding rpms to the initrd 2014-05-23: 1.3 - - fix typo - fix micro & nano options - update help text + - fix typo 2014-04-29: 1.2 - better import of old iso9600 metadata @@ -225,25 +269,28 @@ - fix --micro option 2014-04-24: 1.0 - - add ISO meta info - - added --micro and --nano to produce small test isos - use exclude file instead of individual options + - added --micro and --nano to produce small test isos + - add ISO meta info 2014-04-15: 0.5 - fix hybrid iso creation when boot options are added 2014-03-18: 0.4 - - support adding boot options - - support s390x media - - ignore isohybrid warnings - fix file magic usage + - ignore isohybrid warnings + - support s390x media + - support adding boot options 2014-01-31: 0.3 - - add 'initrd' option to add files directly to initrd - - patch VERSION into mksusecd - - added install target - - added README - - add another example - - added help text - support fat + - added help text + - merge gh#openSUSE/mksusecd#1 + - add another example + - merge gh#openSUSE/mksusecd#2 + - added README + - added install target + - merge gh#openSUSE/mksusecd#3 + - patch VERSION into mksusecd + - add 'initrd' option to add files directly to initrd diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mksusecd-1.65/git2log new/mksusecd-1.66/git2log --- old/mksusecd-1.65/git2log 2018-09-14 10:53:41.000000000 +0200 +++ new/mksusecd-1.66/git2log 2018-11-15 16:30:07.000000000 +0100 @@ -1,5 +1,14 @@ #! /usr/bin/perl +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# +# This script is maintained at https://github.com/openSUSE/linuxrc-devtools +# +# If you're in another project, this is just a copy. +# You may update it to the latest version from time to time... +# +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + use strict; use Getopt::Long; @@ -10,289 +19,966 @@ $Data::Dumper::Indent = 1; sub usage; -sub get_branch_tags; -sub get_branch; -sub get_parent_branch; +sub changelog_outdated; +sub get_github_project; sub get_version; +sub get_tags; +sub get_log; +sub is_formatted_tag; +sub get_branch; +sub choose_tags; +sub add_head_tag; +sub tags_to_str; +sub format_log; +sub format_all_logs; +sub fix_dates; +sub add_line_breaks; +sub format_date_obs; +sub format_date_iso; +sub raw_date_to_s; usage 0 if !@ARGV; -my @deps = qw ( .git/HEAD .git/refs/heads .git/refs/tags ); +my @changelog_deps = qw ( .git/HEAD .git/refs/heads .git/refs/tags ); my $branch; my $current_version; my @tags; my @all_tags; +my $config; my $opt_log; my $opt_version; my $opt_branch; my $opt_update; my $opt_file; +my $opt_start; +my $opt_max; +my $opt_width = 66; +my $opt_width_fuzz = 8; +my $opt_sep_width = 68; +my $opt_format = 'internal'; # obs, internal +my $opt_merge_msg_before = 1; # log auto generated pr merge message before the commit messages (vs. after) +my $opt_join_author = 1; # join consecutive commit messages as long as they are by the same author +my $opt_keep_date = 1; # don't join consecutive commit messages if they have different time stamps +my $opt_default_email = 'opensuse-packag...@opensuse.org'; # default email to use in changelog GetOptions( 'help' => sub { usage 0 }, 'version' => \$opt_version, 'branch' => \$opt_branch, 'update' => \$opt_update, + 'start=s' => \$opt_start, + 'format=s' => \$opt_format, + 'max=i' => \$opt_max, + 'width=i' => \$opt_width, + 'fuzz=i' => \$opt_width_fuzz, + 'merge-msg=s' => sub { $opt_merge_msg_before = ($_[1] eq 'after' ? 0 : 1) }, + 'join-author!' => \$opt_join_author, + 'keep-date!' => \$opt_keep_date, 'log|changelog' => \$opt_log, + 'default-email=s' => \$opt_default_email, ) || usage 1; +# ensure we are used correctly usage 1 if @ARGV > 1 || !($opt_log || $opt_version || $opt_branch); $opt_file = @ARGV ? shift : '-'; die "no git repo\n" unless -d ".git"; -if($opt_update && $opt_file ne '-' && -f($opt_file)) { - my $ok = 1; +# if update option has been give write changelog only if git refs are newer +exit 0 if $opt_update && $opt_file ne '-' && -f($opt_file) && !changelog_outdated($opt_file); + +$opt_max = 2 if $opt_version || $opt_branch; - my $t = (stat $opt_file)[9]; +# gather some data +get_github_project; +get_branch; +get_log; +fix_dates; +get_tags; +choose_tags; +add_head_tag; +get_version; + +# just print current branch +if($opt_branch) { + open my $f, ">$opt_file"; + print $f $config->{branch} ? $config->{branch} : "master", "\n"; + close $f; + + exit 0; +} + +# just print current version +if($opt_version) { + my $old_version; + + if($opt_file ne '-' && open(my $f, $opt_file)) { + chomp($old_version = <$f>); + close $f; + } - for (@deps) { - $ok = 0 if (stat)[9] > $t; + if($config->{version} ne $old_version) { + open my $f, ">$opt_file"; + print $f "$config->{version}\n"; + close $f; } - exit 0 if $ok; + exit 0; +} + +# set start tag +if($opt_start) { + my $x = is_formatted_tag $opt_start; + die "$opt_start: not a valid start tag\n" if !$x; + $x->{branch} = $config->{branch} if !$x->{branch}; + $config->{start} = $x; } -@all_tags = `git tag`; -chomp @all_tags; +format_all_logs; -$branch = get_branch; -die "no branch?\n" unless $branch; +open my $f, ">$opt_file"; -@tags = get_branch_tags; -die "no tags at all?\n" unless @tags; +print $f $_->{formatted} for @{$config->{log}}; -if($branch ne 'master') { - if(!grep { /^$branch\-/ } @tags) { - $branch = get_parent_branch; - die "sorry, can't determine branch\n" unless $branch; +close $f; - @tags = get_branch_tags; - die "no tags at all?\n" unless @tags; - } +exit 0; + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# usage(exit_code) +# +# Print help message and exit. +# - exit_code: exit code +# +# Function does not return. +# +sub usage +{ + my $err = shift; + + print <<" usage"; +Usage: git2log [OPTIONS] [FILE] +Create changelog and project version from git repo. + --changelog Write changelog to FILE. + --version Write version number to FILE. + --branch Write current branch to FILE. + --start START_TAG Start with tag START_TAG. + --max N Write at most MAX long entries. + --update Write changelog or version only if FILE is outdated. + --format FORMAT Write log using FORMAT. Supported FORMATs are 'internal' (default) and 'obs'. + --width WIDTH Reformat log entries to be max WIDTH chars wide. + --fuzz FUZZ Allow log lines to be up to FUZZ chars longer as WIDTH to avoid + line breaks leaving tiny bits on the last line. + --merge-msg WHERE Log message about merges before or after the actual merge commit messages. + Valid values for WHERE are 'after' and 'before' (default). + --join-author Join consecutive commits as long as they are by the same author. (default) + --no-join-author Keep consecutive commits by the same author separate. + --keep-date Join consecutive commits only if they have the same date. (default) + --no-keep-date Join consecutive commits even if dates differ. + --default-email Use this email in changelog entries if no other suitable email could be + determined (default: opensuse-packaging\@opensuse.org). + --help Print this help text. + usage + + exit $err; } -else { - @tags = get_branch_tags; - die "no tags at all?\n" unless @tags; + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# res = changelog_outdated(file) +# +# Return status of changelog file. +# - file: changelog file name +# - res: status +# 1: file is newer than the last git repo change and should be updated +# 0: file is still recent enough +# +# Relies on global var @changelog_deps. +# +sub changelog_outdated +{ + my $file = $_[0]; + + my $changelog_time = (stat $file)[9]; + + return 1 if !defined $changelog_time; + + for (@changelog_deps) { + return 1 if (stat)[9] > $changelog_time; + } + + return 0; } -if($opt_branch) { - open my $f, ">$opt_file"; - print $f "$branch\n"; - close $f; - exit 0; +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# get_github_project() +# +# Set $config->{github_project} to the github project name. +# +sub get_github_project +{ + if(`git config remote.origin.url` =~ m#github.com[:/]+(\S+/\S+)#) { + $config->{github_project} = $1; + $config->{github_project} =~ s/\.git$//; + } } -$current_version = get_version; -if($opt_version) { - open my $f, ">$opt_file"; - print $f "$current_version\n"; - close $f; +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# get_version() +# +# Set $config->{branch} and $config->{version} to the current branch and +# version info. +# +# This might be taken directly from HEAD if HEAD is tagged or otherwise be +# exprapolated from the most recent tag (cf. add_head_tag()). +# +sub get_version +{ + $config->{version} = "0.0"; - exit 0; + my $tag = $config->{log}[0]{tags}[0]; + + if($tag->{version}) { + $config->{version} = $tag->{version}; + $config->{branch} = $tag->{branch}; + } } -if($branch ne 'master') { - my ($i1, $i2, $bi); - for (my $i = 0; $i < @tags; $i++) { - if($tags[$i] =~ /^$branch\-(\S+)/) { - $i2 = $i; - $bi = $1; - last; - } - } +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# get_tags() +# +# Parse $config->{raw_log}, extract tag names, and split into per-tag +# sections. +# +# Only tags recognized by is_formatted_tag() are considered. +# +# Tags inside merge commits are ignored. +# +# The parsed logs is stored in $config->{log}, an array of log sections. +# Each section is a hash with these keys: +# - 'tags': array of tags for this section +# - 'commit': git commit id associated with these tags +# - 'lines': git log lines +# +sub get_tags +{ + my $log_entry; - # print STDERR ">> $branch-$bi\n"; + # the end of the merge commit if in a merge + my $merge; - warn "no tags in this branch yet\n" unless $bi; + for (@{$config->{raw_log}}) { + if(/^commit (\S+)( \((.*)\))?/) { + my $commit = $1; + my $tag_list = $3; + my $xtag; + + # we have reached the end of the merge commit + undef $merge if $merge && $commit =~ /^$merge/; + + # ignore tag info inside a merge commit + $tag_list = "" if $merge; + + for my $t (split /, /, $tag_list) { + if($t =~ /tag: (\S+)/) { + my $tag = $1; + my $x = is_formatted_tag $tag; + push @$xtag, $x if $x; + } + } - for (my $i = 0; $i < $i2; $i++) { - if($tags[$i] ge $bi) { - if($tags[$i] eq $bi) { - $i1 = $i; + if($xtag) { + if($log_entry) { + push @{$config->{log}}, $log_entry; + last if $opt_max && @{$config->{log}} >= $opt_max; + } + $log_entry = { commit => $commit, tags => $xtag }; } - elsif($i > 0) { - $i1 = $i - 1; + else { + $log_entry = { commit => $commit } if !$log_entry; } - last; } + elsif(!$merge && /^Merge: (\S+)/) { + # remember end of merge + $merge = $1; + } + + push @{$log_entry->{lines}}, $_ if $log_entry; } +} - splice @tags, $i1, $i2 - $i1; + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# get_log() +# +# Read git log and store lines as array in $config->{raw_log} (trailing +# newlines removed). +# +sub get_log +{ + chomp(@{$config->{raw_log}} = `git log --pretty=medium --date=raw --topo-order --decorate`); } -map { s/(\d+)/$1 + 0/eg } @tags; -push @tags, "HEAD"; +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# hash_ref = is_formatted_tag(tag_name) +# +# Parse tag and return hash ref with branch and version number parts or +# undef if it doesn't match. +# - tag_name: tag as string +# - hash_ref: hash ref with internal tag representation (with keys 'branch' and 'version'). +# +# This expects tags of the form "VERSION" or "BRANCH-VERSION" where VERSION +# consists of decimal numbers separated by dots '.' and BRANCH can be any +# string. +# (Note: it doesn't really have to be the name of an existing branch.) +# +# Tags not conforming to this convention are ignored. +# +sub is_formatted_tag +{ + if($_[0] =~ /^((.+)-)?((\d+\.)*\d+)$/) { + return { branch => $2, version => $3 } + } -# print Dumper(\@tags); + return undef; +} -open F, ">$opt_file"; -for (my $i = @tags - 1; $i > 0; $i--) { - my ($date, @t2); +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# get_branch() +# +# Get currently active git branch and store in $config->{branch}. +# +# 'master' branch is represented by empty 'branch' key. +# +sub get_branch +{ + chomp(my $branch = `git rev-parse --abbrev-ref HEAD`); - my @t = `git log --pretty=medium --date=iso '$tags[$i-1]..$tags[$i]'`; + $branch = "" if $branch eq 'master'; - # print "\n--- $tags[$i-1]..$tags[$i] ---\n", @t, "---\n"; + $config->{branch} = $branch; +} - my $merge = 0; - for (@t) { - $merge = 1 if /^Merge: /; - $merge = 0 if /^commit /; - push @t2, $_ if !$merge; - } - @t = @t2; - undef @t2; - my $detail = 0; - for (@t) { - $detail = 1 if /^ $/; - $detail = 2 if /^ Conflicts:$/; - $detail = 0 if /^commit /; - if(!$detail || !/^ [^\-\s]/) { - push @t2, $_ if $detail < 2; - } +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# res = tag_sort(a, b) +# +# Compare 2 tags. +# - a, b: refs to tag hash +# - res: -1, 0, 1 +# +# This is used when we have to decide between alternative tags. +# (Prefer 'lesser' variant.) +# +sub tag_sort +{ + my ($x, $y); + + $x = length $a->{version}; + $y = length $b->{version}; + + # longer version number first + return $y <=> $x if $y <=> $x; + + return $a->{branch} cmp $b->{branch}; +} + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# str = tag_to_str(tag_ref) +# +# Convert tag into string. +# - tag_ref: ref to hash with 'branch' and 'version' keys +# - str: string (e.g. "foo-1.44") +# +# 'master' branch is represented by missing/empty 'branch' key. +# +sub tag_to_str +{ + my $tag = $_[0]; + my $str; + + $str = "$tag->{branch}-" if $tag->{branch} ne ""; + $str .= $tag->{version}; + + return $str; +} + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# str = tags_to_str(tag_array_ref) +# +# Convert array of tags into string. +# - tag_array_ref: ref to array of tags +# - str: string (e.g. "(tag1, tag2)" +# +# This function is used only internally for debugging. +# +sub tags_to_str +{ + my $tags = $_[0]; + my $str; + + for my $t (@$tags) { + $str .= ", " if $str; + $str .= tag_to_str $t; } - @t = @t2; - # print "\n--- $tags[$i-1]..$tags[$i] ---\n", @t; + return "($str)"; +} + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# choose_tags() +# +# Scan commit messages and extract tag & branch information. +# +# This stores the tag/branch info in $config->{log}[]{tags}. +# +sub choose_tags +{ + my $branch = $config->{branch}; + + for my $x (@{$config->{log}}) { + # printf "# %s\n", tags_to_str($x->{tags}); - chomp @t; - for (@t) { - if(/^Date:\s*(\S+)/) { - $date = $1; - last; + # no tag info? -> ignore + next if !$x->{tags}; + + # single tag? -> remember branch info + if(@{$x->{tags}} == 1) { + $branch = $x->{tags}[0]{branch}; + next; } - } - # handle white space in every first line once and for all - my $empty = 1; - for (@t) { - $empty = 1, $_ = "", next if $_ =~ /^\s*$/; - next if !$empty; - s/^\s*//; - $empty = 0; - } + # several tags? -> choose one + + # any with current branch name? + my @t = grep { $_->{branch} eq $branch } @{$x->{tags}}; + + # no? -> choose among all + @t = @{$x->{tags}} if @t == 0; - @t = grep { !/^(commit|Author:|Date:|Merge:|\s*$)|created.*tag/ } @t; - if(@t) { - # rewrite a bit to have it look more consistent - map { s/(fate|bnc|bsc)#/$1 #/g } @t; - map { s/(fate|bnc|bsc)\s*(\d{4})/$1 #$2/g } @t; - map { s/\(#/(bnc #/g } @t; - map { s/bug\s*#/bnc #/g } @t; - map { s/feat(\.|ure)?\s*#?(\d+)/fate #$2/g } @t; - map { s/^ {4}// } @t; - map { s/^ {8}// } @t; - map { s/^ +/ / } @t; - map { s/^\s*[+\-][\-\s]*/- / } @t; - map { s/^([^ \-])/- $1/ } @t; - map { s/^/\t/ } @t; - map { s/\\'/'/ } @t; + # prefer longest version number, then alphanumerically smallest branch name + @t = sort tag_sort @t; - # print "\n--- $tags[$i-1]..$tags[$i] ---\n", join("\n", @t); + $branch = $t[0]{branch}; + $x->{tags} = [ $t[0] ]; - my $t = $tags[$i]; - $t = "${branch}-$t" if $branch ne 'master' && $t eq "HEAD"; - $t =~ s/HEAD/$current_version/; - print F "$date:\t$t\n"; - print F join("\n", @t), "\n\n"; + # printf "X %s\n", tags_to_str($x->{tags}); } } -close F; - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -sub usage +# add_head_tag() +# +# Suggest tag for HEAD if there isn't one. +# +# Basically, use branch + version from most recent tag and increment version. +# +sub add_head_tag { - my $err = shift; + return if @{$config->{log}} < 2; - print <<" usage"; -Usage: git2log [OPTIONS] [FILE] -Create changelog and project version from git repo. - --changelog Write changelog to FILE. - --version Write version number to FILE. - --branch Write current branch to FILE. - --update Write changelog or version only if FILE is outdated. - --help Print this help text. - usage + # HEAD tagged already? + return if $config->{log}[0]{tags}; - exit $err; + # the first tagged commit if HEAD isn't tagged + my $tag = { %{$config->{log}[1]{tags}[0]} }; + + # increment version + $tag->{version} =~ s/(\d+)$/$1 + 1/e; + + $config->{log}[0]{tags}[0] = $tag; + + # remember that the tag was generated + $config->{log}[0]{was_untagged} = 1; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -sub get_branch_tags +# fix_dates() +# +# Adjust time stamps in entire git log. +# +# The time stamps of the git commits are not necessarily ordered by date. +# But the generated changelog is required to have a strictly monotonic time. +# +# We do this by going through the log in reverse and rewriting any dates we +# find whenever the date decreases. +# +# A minimum time difference of 1 second beween entries is maintained. +# +# Not very subtle but it works. +# +sub fix_dates { - my @ntags; + my $last_date; - for (@all_tags) { - if(/^\d/) { - s/(\d+)/sprintf "%04d", $1/eg; - push @ntags, $_; + for (reverse @{$config->{raw_log}}) { + # e.g. "Date: 1443184889 +0200" + if(/^(Date:\s+)(\S+)(\s+\S+)/) { + if(defined $last_date && $2 < $last_date) { + $_ = "$1$last_date$3\n"; + } + else { + $last_date = $2; + } + + # ensure a minimal time gap of 1 second + $last_date += 1; } - elsif(s/^$branch\-//) { - s/(\d+)/sprintf "%04d", $1/eg; - push @ntags, "$branch-$_"; + } +} + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# format_all_logs() +# +# Format the entire git log. +# +# This is done for every code version individually (the log has already been +# split accordingly). +# +# If $config->{start} is set, use this as starting point. Else format the +# entire git log. +# +sub format_all_logs +{ + # check if start tag actually exists - if not, print nothing + if($config->{start}) { + my $tag_found; + for (@{$config->{log}}) { + $tag_found = 1, last if grep { tag_to_str($config->{start}) eq tag_to_str($_) } @{$_->{tags}}; } + return if !$tag_found; } - return sort @ntags; + for (@{$config->{log}}) { + if($config->{start}) { + # stop if we meet the start tag + last if grep { tag_to_str($config->{start}) eq tag_to_str($_) } @{$_->{tags}}; + } + format_log $_; + } } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -sub get_branch +# format_log(log) +# +# Format log messages. +# - log: is an array ref with individual commits +# +# All commits belong to a specific code version (stored in $log->{tag}). +# $log->{formatted} holds the result. +# +# The process is done in several individual steps, documented below in the code. +# +sub format_log { - my $b; + my $log = $_[0]; + + my $merge; + my $commit; + my $commits; + + for (@{$log->{lines}}) { + if(/^commit (\S+)/) { + $commit = { ref => $1 }; + push @{$commits}, $commit; + + if( + $merge && + $merge->{merge_end} eq substr($commit->{ref}, 0, length($merge->{merge_end})) + ) { + undef $merge; + } + + if($merge) { + $commit->{merge_ref} = $merge->{ref}; + $commit->{date} = $merge->{date}; + $commit->{author} = $merge->{author}; + # add to all commits so it's not lost when we re-arrange + $commit->{merge_msg} = $merge->{msg}; + } + + next; + } + + if(/^Merge: (\S+)/ && !$merge) { + if($commit) { + $merge = { merge_end => $1, ref => $commit->{ref} } unless $merge; + } + next; + } + + if(/^Date:\s+(\S.*)/) { + $commit->{date} ||= $1 if $commit; + $merge->{date} ||= $1 if $merge; + next; + } + + if(/^Author:\s+(\S.*)/) { + $commit->{author} ||= $1 if $commit; + $merge->{author} ||= $1 if $merge; + next; + } + + if($merge) { + if(/^ Merge pull request (#\d+) from (\S+)/) { + if($config->{github_project}) { + push @{$merge->{msg}}, "merge gh#$config->{github_project}$1"; + } + else { + push @{$merge->{msg}}, "merge pr $2"; + } + } + elsif(/^ Merge branch '([^']+)'( into)?/) { + push @{$merge->{msg}}, "merge branch $1" if $2 eq ""; + } + elsif(/^ Merge remote-tracking branch /) { + # ignore + } + elsif(s/^ //) { + push @{$commit->{lines}}, $_ unless /^# /; + } + } + elsif($commit) { + if(s/^ //) { + push @{$commit->{lines}}, $_ unless /^# /; + } + } + } + + # Note: the individual steps below work on the array @$commits and modify + # its content. + + # step 1 + # - if there are paragraphs starting with '@log@' or '@+log@' + # - delete first paragraph (short summary) + # - else + # - keep only first paragraph + # - if there is a paragraph starting with '@-log', delete entire log + # - tag commits that have a '@log@' tag so we can delete untagged commits + # belonging to the same merge commit later + + my $tagged_merges = {}; + + for my $commit (@$commits) { + # skip leading empty lines + for (@{$commit->{lines}}) { + last if !/^\s*$/; + shift @{$commit->{lines}}; + } + my $para_cnt = 0; + my $delete_all = 0; + my $delete_first = 0; + for (@{$commit->{lines}}) { + $para_cnt++ if $_ eq ""; + $para_cnt = 0, $delete_first = 1 if /^\@\+log\@/; + $delete_all = 1 if /^\@\-log\@/; + if(/^\@log\@/) { + $para_cnt = 0; + $commit->{clear} = 1; + $tagged_merges->{$commit->{merge_ref}} = 1 if $commit->{merge_ref} || $log->{was_untagged}; + } + $_ = undef if $para_cnt; + } + shift @{$commit->{lines}} if $delete_first; + $commit->{lines} = [] if $delete_all; + } + + # step 2 + # - clean up tagged commits or commits belonging to tagged merges + + for my $commit (@$commits) { + next unless $commit->{clear} || $tagged_merges->{$commit->{merge_ref}}; + for (@{$commit->{lines}}) { + last if /^\@\+?log\@/; + $_ = undef; + } + } + + # step 3 + # - join lines + + for my $commit (@$commits) { + my $lines; + my $line; + + for (@{$commit->{lines}}) { + next if $_ eq ""; + if( + s/^\s*[+\-][\-\s]*// || + s/^\@\+?log\@// || + $line eq "" + ) { + s/^\s*//; + push @$lines, $line if $line ne ""; + $line = $_; + } + else { + s/^\s*//; + $line .= " " if $line ne ""; + $line .= $_; + } + } + push @$lines, $line if $line ne ""; + + $commit->{formatted} = $lines if $lines; + } + + # step 4 + # - fix small glitches + + for my $commit (@$commits) { + next unless $commit->{formatted}; + for (@{$commit->{formatted}}) { + s/(fate|bnc|bsc|boo)\s*(#\d+)/\L$1\E$2/ig; + } + } + + # step 5 + # - add merge info at the top or bottom (depending on $opt_merge_msg_before) + + my $merge_logged; - for (`git branch`) { - if(/^\*\s+(\S+)/) { - $b = $1; - last; + for my $commit ($opt_merge_msg_before ? reverse(@$commits) : @$commits) { + next unless $commit->{formatted}; + + if($commit->{merge_ref} && !$merge_logged->{$commit->{merge_ref}}) { + $merge_logged->{$commit->{merge_ref}} = 1; + if($commit->{merge_msg}) { + if($opt_merge_msg_before) { + unshift @{$commit->{formatted}}, @{$commit->{merge_msg}}; + } + else { + push @{$commit->{formatted}}, @{$commit->{merge_msg}}; + } + } } } - $b = "master" if $b eq '(no'; + # step 6 + # - join commit messages with same author (optionally even with different dates) + + my $commit0; + + for my $commit (@$commits) { + next if !$commit->{formatted}; + $commit0 = $commit, next if !$commit0; + + if( + # $commit->{merge_ref} eq $commit0->{merge_ref} && + ( + $opt_join_author && ($commit->{author} eq $commit0->{author}) + && (!$opt_keep_date || $commit->{date} eq $commit0->{date}) + ) + || $opt_format eq 'internal' + ) { + unshift @{$commit0->{formatted}}, @{$commit->{formatted}}; + delete $commit->{formatted}; + } + else { + $commit0 = $commit; + } + } + + # step 7 + # - add version tag at the end of the first log entry + + for my $commit (@$commits) { + next unless $commit->{formatted}; + + if($opt_format eq 'obs') { + push @{$commit->{formatted}}, $log->{tags}[0]{version} if defined $log->{tags}[0]{version}; + } + else { + # push @{$commit->{formatted}}, tag_to_str($log->{tags}[0]); + } + + last; + } + + # step 8 + # - remove identical lines + + for my $commit (@$commits) { + next unless $commit->{formatted}; + my %k; + $commit->{formatted} = [ grep { !$k{$_}++ } @{$commit->{formatted}} ] + } + + # step 9 + # - remove shortened lines (that match the beginning of other lines) + + for my $commit (@$commits) { + next unless $commit->{formatted}; + + # return 1 if some other commit line starts with function arg + my $is_substr = sub { + my $str = $_[0]; + $str =~ s/\s*…$//; # github likes to shorten lines with ' …' + my $str_len = length $str; + for (@{$commit->{formatted}}) { + return 1 if $str_len < length($_) && $str eq substr($_, 0, $str_len); + } + + return 0; + }; + + $commit->{formatted} = [ grep { ! $is_substr->($_) } @{$commit->{formatted}} ] + } + + # step 10 + # - add line breaks - return $b; + for my $commit (@$commits) { + next unless $commit->{formatted}; + for (@{$commit->{formatted}}) { + $_ = add_line_breaks $_; + } + } + + # step 11 + # - generate final log message + # + # note: non-(open)suse email addresses are replaced by $opt_default_email + + my $formated_log; + + for my $commit (@$commits) { + next unless $commit->{formatted} && @{$commit->{formatted}}; + + if($opt_format eq 'obs') { + $formated_log .= "-" x $opt_sep_width . "\n"; + $formated_log .= format_date_obs($commit->{date}); + } + else { + $formated_log .= format_date_iso($commit->{date}); + } + if($opt_format eq 'obs') { + my $auth = $commit->{author}; + $auth =~ s/^.*<//; + $auth =~ s/>.*$//; + # replace non-suse e-mail addresses with a generic one + if($auth !~ /\@(suse\.(com|cz|de)|opensuse\.org)$/) { + $auth = $opt_default_email; + } + $formated_log .= " - $auth\n\n"; + } + else { + $formated_log .= ":\t" . tag_to_str($log->{tags}[0]) . "\n"; + } + + for (@{$commit->{formatted}}) { + s/^/\t/mg if $opt_format eq 'internal'; + $formated_log .= "$_\n"; + } + + $formated_log .= "\n"; + } + + $log->{formatted} = $formated_log; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -sub get_parent_branch +# new_text = add_line_breaks(text) +# +# Add line breaks to text. +# - text: some text +# - new_text: same text, reformatted +# +# Lines are formatted to have a maximal length of $opt_width. If this causes +# the last line to be shorter than $opt_width_fuzz, it is appended to the +# previous line. +# +sub add_line_breaks { - my $p; + my @words = split /\s+/, @_[0]; + my $remaining_len = length(join '', @words); - for (`git log -g --pretty=oneline`) { - $p = $1 if /checkout: moving from (\S+) to $branch/; - } + my $str = shift(@words); + my $len = length $str; - # print "parent = $p\n"; + my $next_len; + my $word_len; - return $p || "master"; + for (@words) { + $word_len = length; + $remaining_len -= $word_len; + $next_len = $len + $word_len + 1; + if( + $next_len >= $opt_width && + $next_len + $remaining_len + 1 >= $opt_width + $opt_width_fuzz + ) { + $str .= "\n $_"; + $len = $word_len; + } + else { + $str .= " $_"; + $len += $word_len + 1; + } + } + + return "- " . $str; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -sub get_version +# seconds = raw_date_to_s(git_date) +# +# Convert git raw date to seconds. +# - git_date: raw git format (e.g. "1443184889 +0200") +# - seconds: the seconds part (e.g. "1443184889") +# +sub raw_date_to_s { - my $v = $tags[-1]; - $v =~ s/(\d+)/$1 + 0/eg; + return (split / /, $_[0])[0]; +} - if(`git log --pretty=medium --date=iso '$v..HEAD'`) { - $v =~ s/(\d+)$/$1 + 1/e; - } - $v =~ s/^$branch\-//; +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# date = format_date_obs(git_date) +# +# Convert git raw date to obs format. +# - git_date: raw git format (e.g. "1443184889 +0200") +# - date: obs format ("Fri Sep 25 12:41:29 UTC 2015") +# +sub format_date_obs +{ + my @d = gmtime(raw_date_to_s($_[0])); - return $v; + return + qw(Sun Mon Tue Wed Thu Fri Sat)[$d[6]] . " " . + qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)[$d[4]] . " " . + $d[3] . " " . + sprintf("%02d:%02d:%02d", $d[2], $d[1], $d[0]) . " UTC " . + (1900 + $d[5]); } + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# date = format_date_iso(git_date) +# +# Convert git raw date to iso format. +# - git_date: raw git format (e.g. "1443184889 +0200") +# - date: obs format ("2015-09-25") +# +sub format_date_iso +{ + my @d = gmtime(raw_date_to_s($_[0])); + + return sprintf("%04d-%02d-%02d", 1900 + $d[5], $d[4] + 1, $d[3]); +}