Hello community,

here is the log from the commit of package checkmedia for openSUSE:Factory 
checked in at 2018-04-30 22:52:16
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/checkmedia (Old)
 and      /work/SRC/openSUSE:Factory/.checkmedia.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "checkmedia"

Mon Apr 30 22:52:16 2018 rev:29 rq:601622 version:3.8

Changes:
--------
--- /work/SRC/openSUSE:Factory/checkmedia/checkmedia.changes    2017-12-06 
08:54:14.678527129 +0100
+++ /work/SRC/openSUSE:Factory/.checkmedia.new/checkmedia.changes       
2018-04-30 22:52:16.319056423 +0200
@@ -1,0 +2,10 @@
+Thu Apr 26 14:44:26 UTC 2018 - [email protected]
+
+- merge gh#openSUSE/checkmedia#5
+- improve documentation
+- fix Makefile
+- update git2log script
+- add simple usage examples
+- 3.8
+
+--------------------------------------------------------------------

Old:
----
  checkmedia-3.7.tar.xz

New:
----
  checkmedia-3.8.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ checkmedia.spec ++++++
--- /var/tmp/diff_new_pack.MPzrOh/_old  2018-04-30 22:52:16.879035991 +0200
+++ /var/tmp/diff_new_pack.MPzrOh/_new  2018-04-30 22:52:16.891035553 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package checkmedia
 #
-# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -20,7 +20,7 @@
 Summary:        Check Installation Media
 License:        GPL-3.0+
 Group:          System/Management
-Version:        3.7
+Version:        3.8
 Release:        0
 Url:            https://github.com/wfeldt/checkmedia.git
 Source:         %{name}-%{version}.tar.xz

++++++ checkmedia-3.7.tar.xz -> checkmedia-3.8.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/checkmedia-3.7/Makefile new/checkmedia-3.8/Makefile
--- old/checkmedia-3.7/Makefile 2017-12-01 13:07:37.000000000 +0100
+++ new/checkmedia-3.8/Makefile 2018-04-26 16:44:26.000000000 +0200
@@ -33,4 +33,4 @@
        xz -f package/$(PREFIX).tar
 
 clean:
-       rm -rf $(OBJ) package checkmedia *~
+       rm -rf $(OBJ) package checkmedia *~ changelog VERSION
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/checkmedia-3.7/README.md new/checkmedia-3.8/README.md
--- old/checkmedia-3.7/README.md        2017-12-01 13:07:37.000000000 +0100
+++ new/checkmedia-3.8/README.md        2018-04-26 16:44:26.000000000 +0200
@@ -21,12 +21,45 @@
 To avoid problems with isohybrid images, checkmedia also does not check the
 first 512 bytes of the iso image (isohybrid writes an MBR there).
 
+## Usage
+
+  - add checksum
+```
+tagmedia --sha256 foo.iso
+```
+
+  - verify checksum
+
+```
+checkmedia foo.iso
+```
+
+## Downloads
+
+Get the latest version from the [openSUSE Build 
Service](https://software.opensuse.org/package/checkmedia).
+
 ## openSUSE Development
 
-The package is automatically submitted from the `master` branch to
-[system:install:head](https://build.opensuse.org/package/show/system:install:head/checkmedia)
-OBS project. From that place it is forwarded to
-[openSUSE Factory](https://build.opensuse.org/project/show/openSUSE:Factory).
+To build, simply run `make`. Install with `make install`.
+
+Basically every new commit into the master branch of the repository will be 
auto-submitted
+to all current SUSE products. No further action is needed except accepting the 
pull request.
+
+Submissions are managed by a SUSE internal [jenkins](https://jenkins.io) node 
in the InstallTools tab.
+
+Each time a new commit is integrated into the master branch of the repository,
+a new submit request is created to the openSUSE Build Service. The devel 
project
+is 
[system:install:head](https://build.opensuse.org/package/show/system:install:head/checkmedia).
+
+`*.changes` and version numbers are auto-generated from git commits, you don't 
have to worry about this.
+
+The spec file is maintained in the Build Service only. If you need to change 
it for the `master` branch,
+submit to the
+[devel 
project](https://build.opensuse.org/package/show/system:install:head/checkmedia)
+in the build service directly.
+
+Development happens exclusively in the `master` branch. The branch is used for 
all current products.
 
-You can find more information about this workflow in the [linuxrc-devtools
+You can find more information about the changes auto-generation and the
+tools used for jenkis submissions in the [linuxrc-devtools
 
documentation](https://github.com/openSUSE/linuxrc-devtools#opensuse-development).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/checkmedia-3.7/VERSION new/checkmedia-3.8/VERSION
--- old/checkmedia-3.7/VERSION  2017-12-01 13:07:37.000000000 +0100
+++ new/checkmedia-3.8/VERSION  2018-04-26 16:44:26.000000000 +0200
@@ -1 +1 @@
-3.7
+3.8
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/checkmedia-3.7/changelog new/checkmedia-3.8/changelog
--- old/checkmedia-3.7/changelog        2017-12-01 13:07:37.000000000 +0100
+++ new/checkmedia-3.8/changelog        2018-04-26 16:44:26.000000000 +0200
@@ -1,24 +1,34 @@
-2017-12-01:    3e4db1283b3c552694550d8c20dc454d601d4383-3.7
+2018-04-26:    3.8
+       - merge gh#openSUSE/checkmedia#5
+       - improve documentation
+       - fix Makefile
+       - update git2log script
+       - add simple usage examples
+
+2017-12-01:    3.7
+       - merge gh#openSUSE/checkmedia#4
+       - determine image size correctly (bsc#1070745)
        - more comments
-       - determine image size correctly (bsc #1070745)
 
-2017-05-27:    3.6
+2017-09-12:    3.6
+       - merge gh#openSUSE/checkmedia#3
        - sort input files (boo#1041090)
 
-2015-09-28:    3.5
-       - Add information about openSUSE development
+2015-09-29:    3.5
+       - merge gh#openSUSE/checkmedia#2
        - Convert README to Markdown
+       - Add information about openSUSE development
 
 2015-01-27:    3.4
        - updated git2log script
 
 2014-10-14:    3.3
-       - added 'archive' Makefile target
        - update git2log
+       - added 'archive' Makefile target
 
 2014-04-30:    3.2
+       - ignore padding area when calculating checksum (bnc#804160)
        - added 'package' target to Makefile
-       - ignore padding area when calculating checksum (bnc #804160)
 
 2013-11-05:    3.1
        - make sure WORDS_BIGENDIAN is defined correctly where needed
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/checkmedia-3.7/git2log new/checkmedia-3.8/git2log
--- old/checkmedia-3.7/git2log  2017-12-01 13:07:37.000000000 +0100
+++ new/checkmedia-3.8/git2log  2018-04-26 16:44:26.000000000 +0200
@@ -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,283 +19,927 @@
 $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 = '[email protected]';     # 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);
 
-  my $t = (stat $opt_file)[9];
+$opt_max = 2 if $opt_version || $opt_branch;
 
-  for (@deps) {
-    $ok = 0 if (stat)[9] > $t;
-  }
+# 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 if $ok;
+  exit 0;
 }
 
-@all_tags = `git tag`;
-chomp @all_tags;
+# just print current version
+if($opt_version) {
+  my $old_version;
 
-$branch = get_branch;
-die "no branch?\n" unless $branch;
+  if($opt_file ne '-' && open(my $f, $opt_file)) {
+    chomp($old_version = <$f>);
+    close $f;
+  }
 
-@tags = get_branch_tags;
-die "no tags at all?\n" unless @tags;
+  if($config->{version} ne $old_version) {
+    open my $f, ">$opt_file";
+    print $f "$config->{version}\n";
+    close $f;
+  }
 
-if(!grep { /^$branch\-/ } @tags) {
-  $branch = get_parent_branch;
-  die "sorry, can't determine branch\n" unless $branch;
+  exit 0;
+}
 
-  @tags = get_branch_tags;
-  die "no tags at all?\n" unless @tags;
+# 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;
 }
 
-if($opt_branch) {
-  open my $f, ">$opt_file";
-  print $f "$branch\n";
-  close $f;
+format_all_logs;
 
-  exit 0;
+open my $f, ">$opt_file";
+
+print $f $_->{formatted} for @{$config->{log}};
+
+close $f;
+
+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;
 }
 
-$current_version = get_version;
 
-if($opt_version) {
-  open my $f, ">$opt_file";
-  print $f "$current_version\n";
-  close $f;
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# 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];
 
-  exit 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($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_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$//;
   }
+}
 
-  # print STDERR ">> $branch-$bi\n";
 
-  warn "no tags in this branch yet\n" unless $bi;
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# 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";
 
-  for (my $i = 0; $i < $i2; $i++) {
-    if($tags[$i] ge $bi) {
-      if($tags[$i] eq $bi) {
-        $i1 = $i;
+  my $tag = $config->{log}[0]{tags}[0];
+
+  if($tag->{version}) {
+    $config->{version} = $tag->{version};
+    $config->{branch} = $tag->{branch};
+  }
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# get_tags()
+#
+# Parse $config->{raw_log}, extract tag names, and split into per-tag
+# sections.
+#
+# Only tags recognized by is_formatted_tag() are considered.
+#
+# 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;
+
+  for (@{$config->{raw_log}}) {
+    if(/^commit (\S+)( \((.*)\))?/) {
+      my $commit = $1;
+      my $tag_list = $3;
+      my $xtag;
+
+      for my $t (split /, /, $tag_list) {
+        if($t =~ /tag: (\S+)/) {
+          my $tag = $1;
+          my $x = is_formatted_tag $tag;
+          push @$xtag, $x if $x;
+        }
+      }
+
+      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;
     }
+
+    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});
+
+    # no tag info? -> ignore
+    next if !$x->{tags};
 
-  chomp @t;
-  for (@t) {
-    if(/^Date:\s*(\S+)/) {
-      $date = $1;
-      last;
+    # 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}};
 
-  @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;
+    # no? -> choose among all
+    @t = @{$x->{tags}} if @t == 0;
 
-    # print "\n--- $tags[$i-1]..$tags[$i] ---\n", join("\n", @t);
+    # prefer longest version number, then alphanumerically smallest branch name
+    @t = sort tag_sort @t;
 
-    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";
+    $branch = $t[0]{branch};
+    $x->{tags} = [ $t[0] ];
+
+    # 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 $saved_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};
+        # add to all commits so it's not lost when we re-arrange
+        $commit->{merge_msg} = $merge->{msg} if $merge->{msg};
+        # saved entry no longer needed
+        undef $saved_commit;
+      }
+
+      next;
+    }
+
+    if(/^Merge: (\S+)/) {
+      if($commit) {
+        $merge = { merge_end => $1, ref => $commit->{ref} } unless $merge;
+        $saved_commit = pop @{$commits};
+      }
+      undef $commit;
+      next;
+    }
+
+    if(/^Date:\s+(\S.*)/) {
+      if($commit) {
+        $commit->{date} = $1 if !$commit->{date};
+      }
+      elsif($merge) {
+        $merge->{date} = $1 if !$merge->{date};
+      }
+      next;
+    }
+
+    if(/^Author:\s+(\S.*)/) {
+      $commit->{author} = $1 if $commit;
+      $merge->{author} = $1 if $merge && !$merge->{author};
+      next;
+    }
+
+    if($commit) {
+      push @{$commit->{lines}}, $_ if s/^    //;
+    }
+    elsif($merge && !$merge->{msg}) {
+      if(/^    Merge pull request (#\d+) from (\S+)/) {
+        if($config->{github_project}) {
+          $merge->{msg} = "merge gh#$config->{github_project}$1";
+        }
+        else {
+          $merge->{msg} = "merge pr $2";
+        }
+      }
+      elsif(/^    Merge branch '([^']+)'/) {
+        $merge->{msg} = "merge branch $1";
+      }
+    }
+  }
+
+  # it can happen that there's a lonely merge commit left at the end
+  if($merge && $saved_commit) {
+    $saved_commit->{merge_ref} = $merge->{ref};
+    $saved_commit->{date} = $merge->{date};
+    $saved_commit->{author} = $merge->{author};
+    $saved_commit->{merge_msg} = $merge->{msg} if $merge->{msg};
+    $saved_commit->{formatted} = [];
+
+    push @{$commits}, $saved_commit;
+  }
+
+  # 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) {
+    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)\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 my $commit ($opt_merge_msg_before ? reverse(@$commits) : @$commits) {
+    next unless $commit->{formatted};
 
-  for (`git branch`) {
-    if(/^\*\s+(\S+)/) {
-      $b = $1;
-      last;
+    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;
 
-  return $b;
+  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
+  # - add line breaks
+
+  for my $commit (@$commits) {
+    next unless $commit->{formatted};
+    for (@{$commit->{formatted}}) {
+      $_ = add_line_breaks $_;
+    }
+  }
+
+  # step 9
+  # - 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;
+
+  my $next_len;
+  my $word_len;
 
-  # print "parent = $p\n";
+  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 $p || "master";
+  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]);
+}


Reply via email to