Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100755 211 lines (156 sloc) 5.571 kb
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
1 #!/usr/bin/env node
2
3 // TODO(vojta): pre-commit hook for validating messages
4 // TODO(vojta): report errors, currently Q silence everything which really sucks
5
6 var child = require('child_process');
7 var fs = require('fs');
8 var util = require('util');
9 var q = require('qq');
10
11 var GIT_LOG_CMD = 'git log --grep="%s" -E --format=%s %s..HEAD';
12 var GIT_TAG_CMD = 'git describe --tags --abbrev=0';
13
14 var HEADER_TPL = '<a name="%s"></a>\n# %s (%s)\n\n';
15 var LINK_ISSUE = '[#%s](https://github.com/angular/angular.js/issues/%s)';
16 var LINK_COMMIT = '[%s](https://github.com/angular/angular.js/commit/%s)';
17
18 var EMPTY_COMPONENT = '$$';
19 var MAX_SUBJECT_LENGTH = 80;
20
21
22 var warn = function() {
23 console.log('WARNING:', util.format.apply(null, arguments));
24 };
25
26
27 var parseRawCommit = function(raw) {
28 if (!raw) return null;
29
30 var lines = raw.split('\n');
31 var msg = {}, match;
32
33 msg.hash = lines.shift();
34 msg.subject = lines.shift();
35 msg.closes = [];
36 msg.breaks = [];
37
38 lines.forEach(function(line) {
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
39 match = line.match(/(?:Closes|Fixes)\s#(\d+)/);
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
40 if (match) msg.closes.push(parseInt(match[1]));
41 });
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
42
e9ccec7 Igor Minar docs(changelog): release notes for 1.0.0rc5 reality-distortion
IgorMinar authored
43 match = raw.match(/BREAKING CHANGE:([\s\S]*)/);
44 if (match) {
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
45 msg.breaking = match[1];
e9ccec7 Igor Minar docs(changelog): release notes for 1.0.0rc5 reality-distortion
IgorMinar authored
46 }
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
47
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
48
49 msg.body = lines.join('\n');
50 match = msg.subject.match(/^(.*)\((.*)\)\:\s(.*)$/);
51
52 if (!match || !match[1] || !match[3]) {
53 warn('Incorrect message: %s %s', msg.hash, msg.subject);
54 return null;
55 }
56
57 if (match[3].length > MAX_SUBJECT_LENGTH) {
58 warn('Too long subject: %s %s', msg.hash, msg.subject);
59 match[3] = match[3].substr(0, MAX_SUBJECT_LENGTH);
60 }
61
62 msg.type = match[1];
63 msg.component = match[2];
64 msg.subject = match[3];
65
66 return msg;
67 };
68
69
70 var linkToIssue = function(issue) {
71 return util.format(LINK_ISSUE, issue, issue);
72 };
73
74
75 var linkToCommit = function(hash) {
76 return util.format(LINK_COMMIT, hash.substr(0, 8), hash);
77 };
78
79
80 var currentDate = function() {
81 var now = new Date();
82 var pad = function(i) {
83 return ('0' + i).substr(-2);
84 };
85
86 return util.format('%d-%s-%s', now.getFullYear(), pad(now.getMonth() + 1), pad(now.getDate()));
87 };
88
89
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
90 var printSection = function(stream, title, section, printCommitLinks) {
91 printCommitLinks = printCommitLinks === undefined ? true : printCommitLinks;
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
92 var components = Object.getOwnPropertyNames(section).sort();
93
94 if (!components.length) return;
95
96 stream.write(util.format('\n## %s\n\n', title));
97
98 components.forEach(function(name) {
99 var prefix = '-';
7c430c5 Vojta Jina chore(release scripts): group changelog only if more than 1 entry
vojtajina authored
100 var nested = section[name].length > 1;
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
101
102 if (name !== EMPTY_COMPONENT) {
7c430c5 Vojta Jina chore(release scripts): group changelog only if more than 1 entry
vojtajina authored
103 if (nested) {
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
104 stream.write(util.format('- **%s:**\n', name));
105 prefix = ' -';
106 } else {
107 prefix = util.format('- **%s:**', name);
108 }
109 }
110
111 section[name].forEach(function(commit) {
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
112 if (printCommitLinks) {
113 stream.write(util.format('%s %s\n (%s', prefix, commit.subject, linkToCommit(commit.hash)));
114 if (commit.closes.length) {
115 stream.write(',\n ' + commit.closes.map(linkToIssue).join(', '));
116 }
117 stream.write(')\n');
118 } else {
119 stream.write(util.format('%s %s', prefix, commit.subject));
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
120 }
121 });
122 });
123
124 stream.write('\n');
125 };
126
127
128 var readGitLog = function(grep, from) {
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
129 var deferred = q.defer();
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
130
131 // TODO(vojta): if it's slow, use spawn and stream it instead
132 child.exec(util.format(GIT_LOG_CMD, grep, '%H%n%s%n%b%n==END==', from), function(code, stdout, stderr) {
133 var commits = [];
134
135 stdout.split('\n==END==\n').forEach(function(rawCommit) {
136 var commit = parseRawCommit(rawCommit);
137 if (commit) commits.push(commit);
138 });
139
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
140 deferred.resolve(commits);
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
141 });
142
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
143 return deferred.promise;
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
144 };
145
146
147 var writeChangelog = function(stream, commits, version) {
148 var sections = {
149 fix: {},
150 feat: {},
151 breaks: {}
152 };
153
154 sections.breaks[EMPTY_COMPONENT] = [];
155
156 commits.forEach(function(commit) {
157 var section = sections[commit.type];
158 var component = commit.component || EMPTY_COMPONENT;
159
160 if (section) {
161 section[component] = section[component] || [];
162 section[component].push(commit);
163 }
164
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
165 if (commit.breaking) {
166 sections.breaks[component] = sections.breaks[component] || [];
167 sections.breaks[component].push({
168 subject: util.format("due to %s,\n %s", linkToCommit(commit.hash), commit.breaking),
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
169 hash: commit.hash,
170 closes: []
171 });
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
172 };
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
173 });
174
175 stream.write(util.format(HEADER_TPL, version, version, currentDate()));
176 printSection(stream, 'Bug Fixes', sections.fix);
177 printSection(stream, 'Features', sections.feat);
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
178 printSection(stream, 'Breaking Changes', sections.breaks, false);
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
179 }
180
181
182 var getPreviousTag = function() {
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
183 var deferred = q.defer();
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
184 child.exec(GIT_TAG_CMD, function(code, stdout, stderr) {
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
185 if (code) deferred.reject('Cannot get the previous tag.');
186 else deferred.resolve(stdout.replace('\n', ''));
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
187 });
072d45f Igor Minar chore(changelog.js): improve the changelog script
IgorMinar authored
188 return deferred.promise;
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
189 };
190
191
192 var generate = function(version, file) {
193 getPreviousTag().then(function(tag) {
194 console.log('Reading git log since', tag);
db87fd5 Igor Minar chore(changelog.js): pickup breaking changes f/ chore/refactor commits
IgorMinar authored
195 readGitLog('^fix|^feat|BREAKING', tag).then(function(commits) {
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
196 console.log('Parsed', commits.length, 'commits');
197 console.log('Generating changelog to', file || 'stdout', '(', version, ')');
198 writeChangelog(file ? fs.createWriteStream(file) : process.stdout, commits, version);
199 });
200 });
201 };
202
203
204 // publish for testing
205 exports.parseRawCommit = parseRawCommit;
206
207 // hacky start if not run by jasmine :-D
208 if (process.argv.join('').indexOf('jasmine-node') === -1) {
209 generate(process.argv[2], process.argv[3]);
210 }
Something went wrong with that request. Please try again.