create_changelog 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dbb6680237cbad0391a0726ceb5bcb78dc4a53c9
4
- data.tar.gz: caf83089dd06505fcd8acb43f17f0b5e587f9829
3
+ metadata.gz: 24eaf0e197456ddc73ee36dbe5c4befa32d7a8fb
4
+ data.tar.gz: ac2e4abb1a874e0c8d98f3bc404273c733bf48ea
5
5
  SHA512:
6
- metadata.gz: 22bc16e610025ba7d66e02552327c0e17e0271af8f550a7fd425dbf6e8b5fb914ddaf1b02c427aeca65f9354cdbd4fd85d2b860e317f4bb1e34bfb1b47f22efe
7
- data.tar.gz: ce631d8ff2693ab82f0946a6d9662dd98b521bd1341e4d2a87a5fd94464e3180cd5f1dbc26d23ff280fd66432b929806b2b32c75978b5520eb13547dfd5d4dd1
6
+ metadata.gz: 56de42ae4ca9ccf353baacf98df9729a9c90287871e30af061fc46d2f7087ed99a8ba1ee3f04a47707eb977c98c9f8f6c73417e419a7fb4ff28531df03b8d90b
7
+ data.tar.gz: 40ca54406f53d1f2a7106cbe393e127a7f07f0b781a5276ecec945cb3a213669c0d9807fc286805dad29b543adbf1be049abc5712e9d5cfcc4de99eae91dac96
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ Version 1.3.0 (2015-01-21)
2
+ ========================================================================
3
+
4
+ - CHANGE: Omit empty lines at end of changelog.
5
+ - FIX: Do not crash if Git repository is empty.
6
+ - FIX: Do not produce any output if no changelog information found.
7
+ - FIX: Ensure unique lines if initial commit is included.
8
+ - FIX: Include the very first commit's message in the changelog.
9
+ - FIX: Omit recent changes section if there are no recent changes.
10
+
11
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
12
+
13
+
1
14
  Version 1.2.0 (2015-01-18)
2
15
  ========================================================================
3
16
 
@@ -21,6 +34,5 @@ Version 1.0.0 (2015-01-18)
21
34
  ========================================================================
22
35
 
23
36
 
24
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
25
-
26
37
 
38
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
- create-changelog
1
+ [![Gem Version](https://badge.fury.io/rb/create_changelog.svg)](http://badge.fury.io/rb/create_changelog)
2
+
3
+ create_changelog
2
4
  ================
3
5
 
4
6
  Ruby program with command-line interface that creates a changelog from
@@ -27,19 +29,25 @@ The reason why I chose to have special changelog lines rather than
27
29
  commit subjects is that commits are rather technical, but I want to have
28
30
  a changelog that can be read by 'normal' end users.
29
31
 
32
+ create_changelog assumes that each tag in the repository represents a
33
+ release version. The changelog for a version consists of the tag's name,
34
+ the tag's annotation, and the unique combined, filtered changelog lines
35
+ of the tag annotation and all commits since the previous tag.
36
+
30
37
 
31
38
  Usage
32
39
  -----
33
40
 
34
41
  ### Creating log entries
35
- When committing, add a line that fits the above definition to the commit
36
- message, e.g.
42
+
43
+ When committing, add a line to the commit message that fits the above
44
+ definition, e.g.
37
45
 
38
46
  Implement Backup class.
39
47
 
40
48
  - NEW: Ability to back up the files.
41
49
 
42
- Create annotated tags to identify new versions:
50
+ When releasing a new version, create an annotated tag:
43
51
 
44
52
  Version 7.0.0-alpha.3
45
53
 
@@ -63,38 +71,41 @@ changelog entries.
63
71
  To generate a complete change log, simply run the tool in the directory
64
72
  of the git repository, or indicate a working directory:
65
73
 
66
- ccl.rb -d /home/me/my/repository
74
+ ccl -d /home/me/my/repository
67
75
 
68
76
  The output will be in markdown format. If you wish, you can further
69
77
  process it using tools like [pandoc][] for example. Of course, it is
70
78
  also possible to incorporate the command in the content files for a
71
79
  static site generator such as [nanoc][].
72
80
 
73
- If you want to track your log in the git repository, you probably will
74
- not yet have an annotated tag for the version you are preparing. In this
75
- case, the tool will use "Unpublished changes" as the heading for the
76
- latest changes. To use the version number that you are about to use in
77
- the tag, call the tool with an optional argument:
81
+ If you want to track your log in the git repository, you will not yet
82
+ have an annotated tag for the version you are preparing at the time when
83
+ you generate the changelog. In this case, the tool will use "Unpublished
84
+ changes" as the heading for the latest changes. To rather use the
85
+ version number that you are about to use in the tag, execute the tool
86
+ with an optional argument:
78
87
 
79
- ccl.rb 7.0.0-alpha.4
88
+ ccl "Version 7.0.0-alpha.4"
89
+
90
+ You then use this same version number for your release tag.
80
91
 
81
92
  Be aware that currently, the date of the most recent commit (that HEAD
82
93
  points to) will be appended to the heading.
83
94
 
84
95
  To exclude recent changes that were logged since the last tag:
85
96
 
86
- ccl.rb --no-recent
87
- ccl.rb -n
97
+ ccl --no-recent
98
+ ccl -n
88
99
 
89
100
  To just see the (undecorated) changelog entries since the last tag, use:
90
101
 
91
- ccl.rb --recent
92
- ccl.rb -r
102
+ ccl --recent
103
+ ccl -r
93
104
 
94
105
  This can be handy if you want to add a log of recent changes to your tag
95
106
  annotation. For example, using the Vim text editor, issue:
96
107
 
97
- :r!ccl.rb -r
108
+ :r!ccl -r
98
109
 
99
110
  The tool will make sure that no changelog lines are duplicated.
100
111
 
@@ -103,8 +114,17 @@ Changelog format
103
114
  ----------------
104
115
 
105
116
  The changelog format resembles the [suggestions][kacl] made by Olivier
106
- Lacan. I have not yet have the time to implement automatic generation of
107
- subheadings. Maybe I'll add the feature in the future.
117
+ Lacan, but it does not fully comply with Olivier's specification. I have
118
+ not yet have the time to implement automatic generation of subheadings.
119
+ Maybe I'll add the feature in the future.
120
+
121
+
122
+ To do
123
+ -----
124
+
125
+ - Add automatic subheadings
126
+ - Add ability to handle multi-line change log entries
127
+ - Add to-do list feature
108
128
 
109
129
 
110
130
  Live example
@@ -118,11 +138,8 @@ Live example
118
138
  Code
119
139
  ----
120
140
 
121
- To understand the code, you can run `rdoc` in the repository's
122
- directory. The resulting `doc` subdirectory will be ignored by Git.
123
-
124
- Alternatively, you may browse
125
- <http://www.rubydoc.info/github/bovender/create-changelog>.
141
+ The code is documented in [Yard][] style. The `doc` subdirectory is
142
+ git-ignored so you can safely generate the docs.
126
143
 
127
144
 
128
145
  License
@@ -148,5 +165,6 @@ License
148
165
  [nanoc]: http://nanoc.ws
149
166
  [kacl]: http://keepachangelog.com
150
167
  [CHANGELOG.md]: CHANGELOG.md
168
+ [Yard]: http://www.rubydoc.info/gems/yard
151
169
 
152
170
  <!-- vim: set tw=72 : -->
data/bin/ccl CHANGED
@@ -19,54 +19,54 @@
19
19
  # The filtered lines are sorted and written to standard out.
20
20
 
21
21
  require 'optparse'
22
- require_relative '../lib/create_changelog.rb'
22
+ require_relative '../lib/changelog.rb'
23
23
  require_relative '../lib/git.rb'
24
24
  require_relative '../lib/version.rb'
25
25
 
26
- def main
27
- options = {}
28
- working_dir = Dir.pwd
29
- option_parser = OptionParser.new do |opts|
30
- exe_name = File.basename($PROGRAM_NAME)
31
- opts.banner = "create_changelog version #{CreateChangelog::VERSION}\n"
32
- opts.banner += "Creates changelog from log entries in git log\n"
33
- opts.banner += "Usage: #{exe_name} [options] [current_version]"
34
- opts.on("-r", "--recent",
35
- "Include only most recent changes") do
36
- options[:only_recent] = true
37
- end
38
- opts.on("-n", "--no-recent",
39
- "Exclude the most recent changes (from untagged commits)") do
40
- options[:no_recent] = true
41
- end
42
- opts.on("-d WORKING_DIR", "--dir WORKING_DIR",
43
- "Use alternate working directory") do |dir|
44
- working_dir = dir
45
- end
46
- opts.on("-v", "--version", "Print version information and exit.") do
47
- puts CreateChangelog::VERSION
48
- exit
49
- end
26
+ options = {}
27
+ working_dir = Dir.pwd
28
+ option_parser = OptionParser.new do |opts|
29
+ exe_name = File.basename($PROGRAM_NAME)
30
+ opts.banner = "create_changelog version #{CreateChangelog::VERSION}\n"
31
+ opts.banner += "Creates changelog from log entries in git log\n"
32
+ opts.banner += "Usage: #{exe_name} [options] [current_version]"
33
+ opts.on("-r", "--recent",
34
+ "Include only most recent changes") do
35
+ options[:only_recent] = true
50
36
  end
51
- option_parser.parse!
52
-
53
- if options[:no_recent] and options[:only_recent]
54
- abort "FATAL: Cannot combine --recent and --no-recent"
37
+ opts.on("-n", "--no-recent",
38
+ "Exclude the most recent changes (from untagged commits)") do
39
+ options[:no_recent] = true
40
+ end
41
+ opts.on("-d WORKING_DIR", "--dir WORKING_DIR",
42
+ "Use alternate working directory") do |dir|
43
+ working_dir = dir
55
44
  end
45
+ opts.on("-v", "--version", "Print version information and exit.") do
46
+ puts CreateChangelog::VERSION
47
+ exit
48
+ end
49
+ end
50
+ option_parser.parse!
56
51
 
57
- Dir.chdir(working_dir) do
58
- abort "FATAL: Not a git repository." unless Git.is_git_repository?
52
+ if options[:no_recent] and options[:only_recent]
53
+ abort "FATAL: Cannot combine --recent and --no-recent"
54
+ end
59
55
 
56
+ Dir.chdir(working_dir) do
57
+ abort "FATAL: Not a git repository." unless Git.is_git_repository?
58
+
59
+ unless Git.is_empty_repository?
60
60
  change_log = Changelog.new
61
61
  change_log.recent_changes_heading = ARGV[0] unless ARGV.empty?
62
62
  if options[:only_recent]
63
- puts change_log.generate_recent
63
+ output = change_log.generate_recent
64
+ puts output if output
64
65
  else
65
- puts change_log.generate(options[:no_recent])
66
+ output = change_log.generate(options[:no_recent])
67
+ puts output.rstrip if output
66
68
  end
67
69
  end
68
70
  end
69
71
 
70
- main
71
-
72
72
  # vim: nospell
data/bin/ccl~ CHANGED
@@ -19,7 +19,7 @@
19
19
  # The filtered lines are sorted and written to standard out.
20
20
 
21
21
  require 'optparse'
22
- require_relative '../lib/create_changelog.rb'
22
+ require_relative '../lib/changelog.rb'
23
23
  require_relative '../lib/git.rb'
24
24
  require_relative '../lib/version.rb'
25
25
 
@@ -33,7 +33,6 @@ def main
33
33
  opts.banner += "Usage: #{exe_name} [options] [current_version]"
34
34
  opts.on("-r", "--recent",
35
35
  "Include only most recent changes") do
36
- abort "FATAL: Cannot combine --recent and --no-recent" if options[:no_recent]
37
36
  options[:only_recent] = true
38
37
  end
39
38
  opts.on("-n", "--no-recent",
@@ -52,18 +51,22 @@ def main
52
51
  option_parser.parse!
53
52
 
54
53
  if options[:no_recent] and options[:only_recent]
55
- abort "FATAL: Cannot combine --recent and --no-recent" if options[:recent]
54
+ abort "FATAL: Cannot combine --recent and --no-recent"
56
55
  end
57
56
 
58
57
  Dir.chdir(working_dir) do
59
58
  abort "FATAL: Not a git repository." unless Git.is_git_repository?
60
59
 
61
- change_log = Changelog.new
62
- change_log.recent_changes_heading = ARGV[0] unless ARGV.empty?
63
- if options[:only_recent]
64
- puts change_log.generate_recent
65
- else
66
- puts change_log.generate(options[:no_recent])
60
+ unless Git.is_empty_repository?
61
+ change_log = Changelog.new
62
+ change_log.recent_changes_heading = ARGV[0] unless ARGV.empty?
63
+ if options[:only_recent]
64
+ output = change_log.generate_recent
65
+ puts output if output
66
+ else
67
+ output = change_log.generate(options[:no_recent])
68
+ puts output.rstrip if output
69
+ end
67
70
  end
68
71
  end
69
72
  end
data/lib/changelog.rb ADDED
@@ -0,0 +1,124 @@
1
+ # changelog.rb, part of Create-changelog
2
+ # Copyright 2015 Daniel Kraus
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require_relative './commit_changelog.rb'
15
+ require_relative './tag_list.rb'
16
+ require_relative './tag.rb'
17
+
18
+ # Central class that puts together the changelog.
19
+ class Changelog
20
+
21
+ # Heading for the most recent changes.
22
+ attr_writer :recent_changes_heading
23
+
24
+ def initialize
25
+ @recent_changes_heading = "Unpublished changes"
26
+ end
27
+
28
+ @@tags = nil
29
+
30
+ # Generates a decorated changelog for the entire commit history.
31
+ #
32
+ # @param [bool] exclude_recent
33
+ # Indicates whether to exclude recent changelog lines that were
34
+ # added since the last tag.
35
+ #
36
+ # @return [String]
37
+ # Decorated changelog, or nil if no lines were found.
38
+ #
39
+ def generate(exclude_recent = false)
40
+ # Traverse tags
41
+ @@tags = TagList.new(!exclude_recent)
42
+ output = String.new
43
+ @@tags.list.each_cons(2) do |current_tag, previous_tag|
44
+ output << generate_for(current_tag, previous_tag)
45
+ end
46
+ output.length > 0 ? output : nil
47
+ end
48
+
49
+ # Generates a simple, undecorated list of changelog entries
50
+ # since the most recent tag.
51
+ #
52
+ # @return [Array]
53
+ # Array of changelog lines, or nil if no lines were found.
54
+ #
55
+ def generate_recent
56
+ @@tags = TagList.new
57
+ log = CommitChangelog.new(@@tags.list[0], @@tags.list[1])
58
+ # Explicitly add initial commit if there is no tag yet
59
+ # This is necessary because HEAD..OTHER_COMMIT does not include
60
+ # OTHER_COMMIT's message, which is the desired behavior if
61
+ # OTHER_COMMIT is a tag for a previous version, but undesired
62
+ # if OTHER_COMMIT is the initial commit of the repository.
63
+ log.add_commit @@tags.list[1] if @@tags.list.length == 2
64
+ log.changelog
65
+ end
66
+
67
+ private
68
+
69
+ # Generates decorated changelog information including the current_tag's
70
+ # annotation and all changelog lines written in commit messages since the
71
+ # previous_tag. Note that this will exclude previous_tag's annotation,
72
+ # except if previous_tag is not a tag, but the initial commit in the
73
+ # repository.
74
+ #
75
+ # @param [String] current_tag
76
+ # Version tag for which to collect changelog information.
77
+ #
78
+ # @param [String] previous_tag
79
+ # Previous version tag whose changelog information does not belong to
80
+ # the current version's changelog information. However, if previous_tag
81
+ # is the Sha-1 hash of the initial commit, its changelog lines _will_ be
82
+ # included.
83
+ #
84
+ # @return [String]
85
+ # Changelog decorated with markdown formatting, or empty string.
86
+ #
87
+ def generate_for(current_tag, previous_tag)
88
+ tag = Tag.new(current_tag)
89
+ commit_changelog = CommitChangelog.new(current_tag, previous_tag)
90
+
91
+ # If previous_tag is the initial commit, make sure to include its message
92
+ commit_changelog.add_commit previous_tag if previous_tag == @@tags.list[-1]
93
+
94
+ # Combine changelog entries from tag annotation and commit messages
95
+ if tag.changelog
96
+ combined_changelog = tag.changelog.concat(commit_changelog.changelog)
97
+ else
98
+ combined_changelog = commit_changelog.changelog
99
+ end
100
+ combined_changelog.uniq! if combined_changelog
101
+
102
+ output = String.new
103
+ tag.heading = @recent_changes_heading unless tag.heading
104
+ if tag.heading and (tag.text or combined_changelog)
105
+ output << tag.heading + " (#{tag.date})\n"
106
+ output << "=" * 72 + "\n"
107
+ end
108
+ output << tag.text.join("\n") if tag.text
109
+ output << "\n" if tag.heading or tag.text
110
+ output << combined_changelog.join("\n") + "\n" if combined_changelog
111
+ output << end_separator if tag.text or combined_changelog
112
+ output
113
+ end
114
+
115
+ # Markdown-compatible horizontal 'ruler'.
116
+ #
117
+ # @return [String]
118
+ #
119
+ def end_separator
120
+ "\n" + ("* " * 36) +"\n\n\n"
121
+ end
122
+ end
123
+
124
+ # vim: nospell
data/lib/changelog.rb~ ADDED
@@ -0,0 +1,124 @@
1
+ # changelog.rb, part of Create-changelog
2
+ # Copyright 2015 Daniel Kraus
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require_relative './commit_changelog.rb'
15
+ require_relative './tag_list.rb'
16
+ require_relative './tag.rb'
17
+
18
+ # Central class that puts together the changelog.
19
+ class Changelog
20
+
21
+ # Heading for the most recent changes.
22
+ attr_writer :recent_changes_heading
23
+
24
+ def initialize
25
+ @recent_changes_heading = "Unpublished changes"
26
+ end
27
+
28
+ @@tags = nil
29
+
30
+ # Generates a decorated changelog for the entire commit history.
31
+ #
32
+ # @param [bool] exclude_recent
33
+ # Indicates whether to exclude recent changelog lines that were
34
+ # added since the last tag.
35
+ #
36
+ # @return [String]
37
+ # Decorated changelog, or nil if no lines were found.
38
+ #
39
+ def generate(exclude_recent = false)
40
+ # Traverse tags
41
+ @@tags = TagList.new(!exclude_recent)
42
+ output = String.new
43
+ @@tags.list.each_cons(2) do |current_tag, previous_tag|
44
+ output << generate_for(current_tag, previous_tag)
45
+ end
46
+ output.length > 0 ? output : nil
47
+ end
48
+
49
+ # Generates a simple, undecorated list of changelog entries
50
+ # since the most recent tag.
51
+ #
52
+ # @return [Array]
53
+ # Array of changelog lines, or nil if no lines were found.
54
+ #
55
+ def generate_recent
56
+ @@tags = TagList.new
57
+ log = CommitChangelog.new(@@tags.list[0], @@tags.list[1])
58
+ # Explicitly add initial commit if there is no tag yet
59
+ # This is necessary because HEAD..OTHER_COMMIT does not include
60
+ # OTHER_COMMIT's message, which is the desired behavior if
61
+ # OTHER_COMMIT is a tag for a previous version, but undesired
62
+ # if OTHER_COMMIT is the initial commit of the repository.
63
+ log.add_commit @@tags.list[1] if @@tags.list.length == 2
64
+ log.changelog
65
+ end
66
+
67
+ private
68
+
69
+ # Generates decorated changelog information including the current_tag's
70
+ # annotation and all changelog lines written in commit messages since the
71
+ # previous_tag. Note that this will exclude previous_tag's annotation,
72
+ # except if previous_tag is not a tag, but the initial commit in the
73
+ # repository.
74
+ #
75
+ # @param [String] current_tag
76
+ # Version tag for which to collect changelog information.
77
+ #
78
+ # @param [String] previous_tag
79
+ # Previous version tag whose changelog information does not belong to
80
+ # the current version's changelog information. However, if previous_tag
81
+ # is the Sha-1 hash of the initial commit, its changelog lines _will_ be
82
+ # included.
83
+ #
84
+ # @return [String]
85
+ # Changelog decorated with markdown formatting, or empty string.
86
+ #
87
+ def generate_for(current_tag, previous_tag)
88
+ tag = Tag.new(current_tag)
89
+ commit_changelog = CommitChangelog.new(current_tag, previous_tag)
90
+
91
+ # If previous_tag is the initial commit, make sure to include its message
92
+ commit_changelog.add_commit previous_tag if previous_tag == @@tags.list[-1]
93
+
94
+ # Combine changelog entries from tag annotation and commit messages
95
+ if tag.changelog
96
+ combined_changelog = tag.changelog.concat(commit_changelog.changelog)
97
+ else
98
+ combined_changelog = commit_changelog.changelog
99
+ end
100
+ combined_changelog.uniq! if combined_changelog
101
+
102
+ output = String.new
103
+ tag.heading = @recent_changes_heading unless tag.heading
104
+ if tag.heading and (tag.text or combined_changelog)
105
+ output << tag.heading + " (#{tag.date})\n"
106
+ output << "=" * 72 + "\n"
107
+ end
108
+ output << tag.text.join("\n") if tag.text
109
+ output << "\n" if tag.heading or tag.text
110
+ output << combined_changelog.join("\n") + "\n" if combined_changelog
111
+ output << end_separator if tag.text or combined_changelog
112
+ output
113
+ end
114
+
115
+ # Markdown-compatible horizontal 'ruler'.
116
+ #
117
+ # @return [String]
118
+ #
119
+ def end_separator
120
+ "\n" + ("* " * 36) +"\n\n\n"
121
+ end
122
+ end
123
+
124
+ # vim: nospell
@@ -30,10 +30,11 @@ class ChangelogFilter
30
30
  fail "Must call this factory with Array, not " + ary.class.to_s
31
31
  end
32
32
  filter = ChangelogFilter.new
33
- log, filter.other_text = ary.partition do |line|
33
+ log, text = ary.partition do |line|
34
34
  line.match(pattern)
35
35
  end
36
36
  filter.changelog = log.uniq.sort.remove_indent if log.length > 0
37
+ filter.other_text = text if text.length > 0
37
38
  filter
38
39
  end
39
40
 
@@ -15,17 +15,50 @@ require_relative 'changelog_filter'
15
15
 
16
16
  # Filters commit messages for changelog entries.
17
17
  class CommitChangelog
18
+
18
19
  # Contains changelog entries of the commits.
19
20
  attr_reader :changelog
20
21
 
21
22
  # Instantiates an object containing changelog entries between
22
23
  # two git commits.
24
+ #
25
+ # @param [String] to_commit
26
+ # Most recent commit whose changelog lines to include.
27
+ #
28
+ # @param [String] from_commit
29
+ # Earlier commit whose changelog lines will _not_ be included.
30
+ #
31
+ # @return [Array]
32
+ # Array of changelog lines, or nil if none were found.
33
+ #
23
34
  def initialize(to_commit, from_commit)
24
35
  pattern = ChangelogFilter.pattern
25
36
  messages = Git.get_filtered_messages(from_commit, to_commit, pattern)
26
37
  filter = ChangelogFilter.FromString(messages)
27
38
  @changelog = filter.changelog
28
39
  end
40
+
41
+ # Adds changelog information contained in a specific commit message. This
42
+ # method is typically used to parse the initial commit's commit message.
43
+ #
44
+ # @param [String] commit
45
+ # Sha-1 of the commit whose commit message to filter for changelog lines.
46
+ #
47
+ # @return
48
+ # Undefined
49
+ #
50
+ def add_commit(commit)
51
+ pattern = ChangelogFilter.pattern
52
+ filtered_text = Git.get_filtered_message(commit, pattern)
53
+ if filtered_text
54
+ filtered_lines = filtered_text.split("\n").uniq
55
+ if @changelog
56
+ @changelog = @changelog.concat(filtered_lines).uniq
57
+ else
58
+ @changelog = filtered_lines
59
+ end
60
+ end
61
+ end
29
62
  end
30
63
 
31
64
  # vim: nospell
@@ -0,0 +1,56 @@
1
+ # commit_changelog.rb, part of Create-changelog
2
+ # Copyright 2015 Daniel Kraus
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require_relative 'changelog_filter'
15
+
16
+ # Filters commit messages for changelog entries.
17
+ class CommitChangelog
18
+
19
+ # Contains changelog entries of the commits.
20
+ attr_reader :changelog
21
+
22
+ # Instantiates an object containing changelog entries between
23
+ # two git commits.
24
+ #
25
+ # @param [String] to_commit
26
+ # Most recent commit whose changelog lines to include.
27
+ #
28
+ # @param [String] from_commit
29
+ # Earlier commit whose changelog lines will _not_ be included.
30
+ #
31
+ # @return [Array]
32
+ # Array of changelog lines, or nil if none were found.
33
+ #
34
+ def initialize(to_commit, from_commit)
35
+ pattern = ChangelogFilter.pattern
36
+ messages = Git.get_filtered_messages(from_commit, to_commit, pattern)
37
+ filter = ChangelogFilter.FromString(messages)
38
+ @changelog = filter.changelog
39
+ end
40
+
41
+ # Adds changelog information contained in a specific commit message.
42
+ def add_commit(commit)
43
+ pattern = ChangelogFilter.pattern
44
+ filtered_text = Git.get_filtered_message(commit, pattern)
45
+ if filtered_text
46
+ filtered_lines = filtered_text.split("\n").uniq
47
+ if @changelog
48
+ @changelog = @changelog.concat(filtered_lines).uniq
49
+ else
50
+ @changelog = filtered_lines
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ # vim: nospell
data/lib/git.rb CHANGED
@@ -17,19 +17,34 @@ require_relative 'tag_list'
17
17
  class Git
18
18
 
19
19
  # Determines whether the (current) directory is a git repository
20
+ #
21
+ # @param [String] dir
22
+ # Directory to check; if nil, uses the current directory.
23
+ #
24
+ # @return [bool]
25
+ # True if the directory is a Git repository, false if not.
20
26
  def self.is_git_repository?(dir = nil)
21
27
  dir = Dir.pwd if dir.nil?
22
28
  system("git status > /dev/null 2>&1")
23
29
  $? == 0
24
30
  end
25
31
 
32
+ # Determines if the repository in the current directory is empty.
33
+ #
34
+ def self.is_empty_repository?
35
+ `git show HEAD > /dev/null 2>&1`
36
+ $? != 0
37
+ end
38
+
26
39
  # Retrieves the first 99 lines of the annotation of a tag.
40
+ #
27
41
  def self.get_tag_annotation(tag)
28
42
  test_tag tag
29
43
  `git tag -l -n99 #{tag}`.rstrip
30
44
  end
31
45
 
32
- # Retrieves the date of a tag
46
+ # Retrieves the author date of a tag
47
+ #
33
48
  def self.get_tag_date(tag)
34
49
  test_tag tag
35
50
  `git log -1 --format=format:%ai #{tag}`
@@ -41,6 +56,12 @@ class Git
41
56
  `git log #{from_commit}..#{to_commit} -E --grep='#{filter}' --format=%b`
42
57
  end
43
58
 
59
+ # Retrieves one commit message and filters it
60
+ # Todo: Armor this against code injection!
61
+ def self.get_filtered_message(commit, filter)
62
+ `git log #{commit} -E --grep='#{filter}' --format=%b`
63
+ end
64
+
44
65
  @@tags = nil
45
66
 
46
67
  # Ensures lazy loading of the tag list to enable calling code
data/lib/git.rb~ ADDED
@@ -0,0 +1,72 @@
1
+ # git.rb, part of Create-changelog
2
+ # Copyright 2015 Daniel Kraus
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require_relative 'tag_list'
15
+
16
+ # A static wrapper class for git
17
+ class Git
18
+
19
+ # Determines whether the (current) directory is a git repository
20
+ def self.is_git_repository?(dir = nil)
21
+ dir = Dir.pwd if dir.nil?
22
+ system("git status > /dev/null 2>&1")
23
+ $? == 0
24
+ end
25
+
26
+ # Determines if the repository in the current directory is empty.
27
+ def self.is_empty_repository?
28
+ `git show HEAD > /dev/null 2>&1`
29
+ $? != 0
30
+ end
31
+
32
+ # Retrieves the first 99 lines of the annotation of a tag.
33
+ def self.get_tag_annotation(tag)
34
+ test_tag tag
35
+ `git tag -l -n99 #{tag}`.rstrip
36
+ end
37
+
38
+ # Retrieves the date of a tag
39
+ def self.get_tag_date(tag)
40
+ test_tag tag
41
+ `git log -1 --format=format:%ai #{tag}`
42
+ end
43
+
44
+ # Retrieves commit messages and filters them
45
+ # Todo: Armor this against code injection!
46
+ def self.get_filtered_messages(from_commit, to_commit, filter)
47
+ `git log #{from_commit}..#{to_commit} -E --grep='#{filter}' --format=%b`
48
+ end
49
+
50
+ # Retrieves one commit message and filters it
51
+ # Todo: Armor this against code injection!
52
+ def self.get_filtered_message(commit, filter)
53
+ `git log #{commit} -E --grep='#{filter}' --format=%b`
54
+ end
55
+
56
+ @@tags = nil
57
+
58
+ # Ensures lazy loading of the tag list to enable calling code
59
+ # to change the working directory first.
60
+ def self.tags
61
+ @@tags = TagList.new unless @@tags
62
+ @@tags
63
+ end
64
+
65
+ # Tests if the given tag exists and fails if it doesn't
66
+ def self.test_tag(tag)
67
+ fail "Invalid tag: #{tag}" unless tags.list.include?(tag)
68
+ end
69
+ private_class_method :test_tag, :tags
70
+ end
71
+
72
+ # vim: nospell
data/lib/tag.rb CHANGED
@@ -15,7 +15,7 @@ require 'date'
15
15
  require_relative 'git'
16
16
  require_relative 'changelog_filter'
17
17
 
18
- # Represents a git tag and its annotation.
18
+ # Represents a Git tag and its annotation.
19
19
  class Tag
20
20
  # The heading of the tag annotation.
21
21
  attr_accessor :heading
@@ -30,10 +30,9 @@ class Tag
30
30
  attr_reader :date
31
31
 
32
32
  # Gets change information for a specific tagged version.
33
- # This will prepend the summary for the annotated tag before
34
- # the list of changes. If the tag annotation contains changelog
35
- # entries, they are merged with the changelog entries filtered
36
- # from the commit messages, and only unique entries are used.
33
+ #
34
+ # @param [String] tag
35
+ # Tag for which to instantiate the class.
37
36
  def initialize(tag)
38
37
  annotation = Git.get_tag_annotation(tag)
39
38
  @date = Date.parse(Git.get_tag_date(tag))
@@ -42,7 +41,7 @@ class Tag
42
41
  @heading = annotation.shift
43
42
  @heading = @heading.split(' ')[1..-1].join(' ') if @heading
44
43
  filter = ChangelogFilter.FromArray(annotation)
45
- @text = filter.other_text.remove_indent
44
+ @text = filter.other_text.remove_indent if filter.other_text
46
45
  @changelog = filter.changelog
47
46
  end
48
47
  end
data/lib/tag.rb~ ADDED
@@ -0,0 +1,51 @@
1
+ # tag.rb, part of Create-changelog
2
+ # Copyright 2015 Daniel Kraus
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require 'date'
15
+ require_relative 'git'
16
+ require_relative 'changelog_filter'
17
+
18
+ # Represents a git tag and its annotation.
19
+ class Tag
20
+ # The heading of the tag annotation.
21
+ attr_accessor :heading
22
+
23
+ # Array of lines in the tag annotation that are not changelog entries.
24
+ attr_reader :text
25
+
26
+ # Array of lines in the tag annotation that are changelog entries.
27
+ attr_reader :changelog
28
+
29
+ # Author commit date of the tag
30
+ attr_reader :date
31
+
32
+ # Gets change information for a specific tagged version.
33
+ # This will prepend the summary for the annotated tag before
34
+ # the list of changes. If the tag annotation contains changelog
35
+ # entries, they are merged with the changelog entries filtered
36
+ # from the commit messages, and only unique entries are used.
37
+ def initialize(tag)
38
+ annotation = Git.get_tag_annotation(tag)
39
+ @date = Date.parse(Git.get_tag_date(tag))
40
+ if annotation
41
+ annotation = annotation.split("\n")
42
+ @heading = annotation.shift
43
+ @heading = @heading.split(' ')[1..-1].join(' ') if @heading
44
+ filter = ChangelogFilter.FromArray(annotation)
45
+ @text = filter.other_text.remove_indent if filter.other_text
46
+ @changelog = filter.changelog
47
+ end
48
+ end
49
+ end
50
+
51
+ # vim: nospell
data/lib/tag_list.rb CHANGED
@@ -24,8 +24,10 @@ class TagList
24
24
  attr_reader :list
25
25
 
26
26
  # Instantiates the tag list.
27
- # include_head indicates whether or not to include
28
- # the most recent changes
27
+ #
28
+ # @param [bool] include_head
29
+ # Indicates whether or not to include the most recent changes.
30
+ #
29
31
  def initialize(include_head = true)
30
32
  @include_head = include_head
31
33
  @list = build_list
@@ -33,6 +35,9 @@ class TagList
33
35
 
34
36
  # Returns the most recent tag in the git repository,
35
37
  # or the sha1 of the initial commit if there is no tag.
38
+ #
39
+ # @return [String]
40
+ #
36
41
  def latest_tag
37
42
  # Index 0 is HEAD
38
43
  # Index 1 is most recent tag or first commit
@@ -46,10 +51,16 @@ class TagList
46
51
  # of the repository. Usually there should be not more than
47
52
  # one such commit.
48
53
  # See http://stackoverflow.com/a/1007545/270712
54
+ #
49
55
  def get_initial_commit
50
56
  `git rev-list --max-parents=0 HEAD`.chomp
51
57
  end
52
58
 
59
+ # Builds a list of Git tags and encloses it with HEAD and the
60
+ # Sha-1 of the initial commit.
61
+ #
62
+ # @return [Array]
63
+ # Array of tags, surrounded by HEAD and the Sha-1 of the initial commit.
53
64
  def build_list
54
65
  tags = []
55
66
  tags << get_initial_commit
data/lib/tag_list.rb~ ADDED
@@ -0,0 +1,62 @@
1
+ # TagList, part of Create-changelog
2
+ # Copyright 2015 Daniel Kraus
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Builds a list of tags in the current git repository.
16
+ # The tags are enclosed by the sha1 of the first commit
17
+ # and optionally "HEAD" to allow traversing the list
18
+ # with each_con to obtain start and end points of
19
+ # developmental epochs.
20
+ class TagList
21
+ # Returns an array of tag names surrounded by HEAD
22
+ # at the top and the sha1 of the first commit at the
23
+ # bottom.
24
+ attr_reader :list
25
+
26
+ # Instantiates the tag list.
27
+ # include_head indicates whether or not to include
28
+ # the most recent changes
29
+ def initialize(include_head = true)
30
+ @include_head = include_head
31
+ @list = build_list
32
+ end
33
+
34
+ # Returns the most recent tag in the git repository,
35
+ # or the sha1 of the initial commit if there is no tag.
36
+ def latest_tag
37
+ # Index 0 is HEAD
38
+ # Index 1 is most recent tag or first commit
39
+ @list[1]
40
+ end
41
+
42
+ private
43
+
44
+ # Returns the sha1 of the initial commit.
45
+ # In fact, this function returns all parentless commits
46
+ # of the repository. Usually there should be not more than
47
+ # one such commit.
48
+ # See http://stackoverflow.com/a/1007545/270712
49
+ def get_initial_commit
50
+ `git rev-list --max-parents=0 HEAD`.chomp
51
+ end
52
+
53
+ def build_list
54
+ tags = []
55
+ tags << get_initial_commit
56
+ tags += `git tag`.split("\n").map { |s| s.rstrip }
57
+ tags << "HEAD" if @include_head
58
+ tags.reverse
59
+ end
60
+ end
61
+
62
+ # vim: nospell
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module CreateChangelog
2
- VERSION = '1.2.0'
2
+ VERSION = '1.3.0'
3
3
  end
data/lib/version.rb~ CHANGED
@@ -1,3 +1,3 @@
1
1
  module CreateChangelog
2
- VERSION = '1.1.0'
2
+ VERSION = '1.2.0'
3
3
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: create_changelog
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Kraus
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-18 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2015-01-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aruba
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: "\t\tRuby program with command-line interface that creates a changelog
14
28
  from log\n\t\tlines in a git repository that can be read and understood by end users.\n"
15
29
  email: krada@gmx.net
@@ -23,20 +37,26 @@ files:
23
37
  - bin/ccl
24
38
  - bin/ccl~
25
39
  - lib/array.rb
40
+ - lib/changelog.rb
41
+ - lib/changelog.rb~
26
42
  - lib/changelog_filter.rb
27
43
  - lib/commit_changelog.rb
28
- - lib/create_changelog.rb
29
- - lib/create_changelog.rb~
44
+ - lib/commit_changelog.rb~
30
45
  - lib/git.rb
46
+ - lib/git.rb~
31
47
  - lib/tag.rb
48
+ - lib/tag.rb~
32
49
  - lib/tag_list.rb
50
+ - lib/tag_list.rb~
33
51
  - lib/version.rb
34
52
  - lib/version.rb~
35
53
  homepage: https://github.com/bovender/create-changelog
36
54
  licenses:
37
55
  - Apache License version 2
38
56
  metadata: {}
39
- post_install_message:
57
+ post_install_message: |
58
+ create_changelog version 1.3.0 has been installed.
59
+ For usage information, type ccl -h
40
60
  rdoc_options: []
41
61
  require_paths:
42
62
  - lib
@@ -52,8 +72,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
72
  version: '0'
53
73
  requirements: []
54
74
  rubyforge_project:
55
- rubygems_version: 2.2.2
75
+ rubygems_version: 2.4.5
56
76
  signing_key:
57
77
  specification_version: 4
58
78
  summary: Creates end-user-friendly changelog from git messages.
59
79
  test_files: []
80
+ has_rdoc:
@@ -1,77 +0,0 @@
1
- # changelog.rb, part of Create-changelog
2
- # Copyright 2015 Daniel Kraus
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- require_relative './commit_changelog.rb'
15
- require_relative './tag_list.rb'
16
- require_relative './tag.rb'
17
-
18
- # Central class that puts together the changelog.
19
- class Changelog
20
- # Heading for the most recent changes.
21
- attr_writer :recent_changes_heading
22
-
23
- def initialize
24
- @recent_changes_heading = "Unpublished changes"
25
- end
26
-
27
- # Generates a decorated changelog.
28
- def generate(exclude_recent = false)
29
- # Traverse tags
30
- tags = TagList.new(!exclude_recent)
31
- output = String.new
32
- tags.list.each_cons(2) do |current_tag, previous_tag|
33
- output << generate_for(current_tag, previous_tag)
34
- end
35
- output
36
- end
37
-
38
- # Returns a simple, undecorated list of changelog entries
39
- # since the most recent tag.
40
- def generate_recent
41
- tags = TagList.new
42
- log = CommitChangelog.new(tags.list[0], tags.list[1])
43
- log.changelog
44
- end
45
-
46
- private
47
-
48
- def generate_for(current_tag, previous_tag)
49
- tag = Tag.new(current_tag)
50
- commit_changelog = CommitChangelog.new(current_tag, previous_tag)
51
-
52
- # Combine changelog entries from tag annotation and commit messages
53
- if tag.changelog
54
- combined_changelog = tag.changelog.concat(commit_changelog.changelog)
55
- else
56
- combined_changelog = commit_changelog.changelog
57
- end
58
- combined_changelog.uniq! if combined_changelog
59
-
60
- output = String.new
61
- tag.heading = @recent_changes_heading unless tag.heading
62
- if tag.heading
63
- output << tag.heading + " (#{tag.date})\n"
64
- output << "=" * 72 + "\n"
65
- end
66
- output << tag.text.join("\n") + "\n" if tag.text
67
- output << combined_changelog.join("\n") + "\n" if combined_changelog
68
- output << end_separator if tag.heading or tag.text or combined_changelog
69
- output
70
- end
71
-
72
- def end_separator
73
- "\n" + ("* " * 36) +"\n\n\n"
74
- end
75
- end
76
-
77
- # vim: nospell
@@ -1,77 +0,0 @@
1
- # changelog.rb, part of Create-changelog
2
- # Copyright 2015 Daniel Kraus
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- require_relative './commit_changelog.rb'
15
- require_relative './tag_list.rb'
16
- require_relative './tag.rb'
17
-
18
- # Central class that puts together the changelog.
19
- class Changelog
20
- # Heading for the most recent changes.
21
- attr_writer :recent_changes_heading
22
-
23
- def initialize
24
- @recent_changes_heading = "Unpublished changes"
25
- end
26
-
27
- # Generates a decorated changelog.
28
- def generate(exclude_recent = false)
29
- # Traverse tags
30
- tags = TagList.new(!exclude_recent)
31
- output = String.new
32
- tags.list.each_cons(2) do |current_tag, previous_tag|
33
- output << generate_for(current_tag, previous_tag)
34
- end
35
- output
36
- end
37
-
38
- # Returns a simple, undecorated list of changelog entries
39
- # since the most recent tag.
40
- def generate_recent
41
- tags = TagList.new
42
- log = CommitChangelog.new(tags.list[0], tags.list[1])
43
- log.changelog.inspect
44
- end
45
-
46
- private
47
-
48
- def generate_for(current_tag, previous_tag)
49
- tag = Tag.new(current_tag)
50
- commit_changelog = CommitChangelog.new(current_tag, previous_tag)
51
-
52
- # Combine changelog entries from tag annotation and commit messages
53
- if tag.changelog
54
- combined_changelog = tag.changelog.concat(commit_changelog.changelog)
55
- else
56
- combined_changelog = commit_changelog.changelog
57
- end
58
- combined_changelog.uniq! if combined_changelog
59
-
60
- output = String.new
61
- tag.heading = @recent_changes_heading unless tag.heading
62
- if tag.heading
63
- output << tag.heading + " (#{tag.date})\n"
64
- output << "=" * 72 + "\n"
65
- end
66
- output << tag.text.join("\n") + "\n" if tag.text
67
- output << combined_changelog.join("\n") + "\n" if combined_changelog
68
- output << end_separator if tag.heading or tag.text or combined_changelog
69
- output
70
- end
71
-
72
- def end_separator
73
- "\n" + ("* " * 36) +"\n\n\n"
74
- end
75
- end
76
-
77
- # vim: nospell