Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100755 206 lines (151 sloc) 5.25 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) {
39 match = line.match(/Closes\s#(\d+)/);
40 if (match) msg.closes.push(parseInt(match[1]));
41 });
e9ccec7 Igor Minar docs(changelog): release notes for 1.0.0rc5 reality-distortion
IgorMinar authored
42
43 match = raw.match(/BREAKING CHANGE:([\s\S]*)/);
44 if (match) {
45 console.log('found!!!')
46 msg.breaks.push(match[1]);
47 }
48
4557881 Vojta Jina chore(release scripts): auto release scripts
vojtajina authored
49
50 msg.body = lines.join('\n');
51 match = msg.subject.match(/^(.*)\((.*)\)\:\s(.*)$/);
52
53 if (!match || !match[1] || !match[3]) {
54 warn('Incorrect message: %s %s', msg.hash, msg.subject);
55 return null;
56 }
57
58 if (match[3].length > MAX_SUBJECT_LENGTH) {
59 warn('Too long subject: %s %s', msg.hash, msg.subject);
60 match[3] = match[3].substr(0, MAX_SUBJECT_LENGTH);
61 }
62
63 msg.type = match[1];
64 msg.component = match[2];
65 msg.subject = match[3];
66
67 return msg;
68 };
69
70
71 var linkToIssue = function(issue) {
72 return util.format(LINK_ISSUE, issue, issue);
73 };
74
75
76 var linkToCommit = function(hash) {
77 return util.format(LINK_COMMIT, hash.substr(0, 8), hash);
78 };
79
80
81 var currentDate = function() {
82 var now = new Date();
83 var pad = function(i) {
84 return ('0' + i).substr(-2);
85 };
86
87 return util.format('%d-%s-%s', now.getFullYear(), pad(now.getMonth() + 1), pad(now.getDate()));
88 };
89
90
91 var printSection = function(stream, title, section) {
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) {
112 stream.write(util.format('%s %s (%s', prefix, commit.subject, linkToCommit(commit.hash)));
113 if (commit.closes.length) {
114 stream.write(', closes ' + commit.closes.map(linkToIssue).join(', '));
115 }
116 stream.write(')\n');
117 });
118 });
119
120 stream.write('\n');
121 };
122
123
124 var readGitLog = function(grep, from) {
125 var deffered = q.defer();
126
127 // TODO(vojta): if it's slow, use spawn and stream it instead
128 child.exec(util.format(GIT_LOG_CMD, grep, '%H%n%s%n%b%n==END==', from), function(code, stdout, stderr) {
129 var commits = [];
130
131 stdout.split('\n==END==\n').forEach(function(rawCommit) {
132 var commit = parseRawCommit(rawCommit);
133 if (commit) commits.push(commit);
134 });
135
136 deffered.resolve(commits);
137 });
138
139 return deffered.promise;
140 };
141
142
143 var writeChangelog = function(stream, commits, version) {
144 var sections = {
145 fix: {},
146 feat: {},
147 breaks: {}
148 };
149
150 sections.breaks[EMPTY_COMPONENT] = [];
151
152 commits.forEach(function(commit) {
153 var section = sections[commit.type];
154 var component = commit.component || EMPTY_COMPONENT;
155
156 if (section) {
157 section[component] = section[component] || [];
158 section[component].push(commit);
159 }
160
161 commit.breaks.forEach(function(breakMsg) {
162 sections.breaks[EMPTY_COMPONENT].push({
163 subject: breakMsg,
164 hash: commit.hash,
165 closes: []
166 });
167 });
168 });
169
170 stream.write(util.format(HEADER_TPL, version, version, currentDate()));
171 printSection(stream, 'Bug Fixes', sections.fix);
172 printSection(stream, 'Features', sections.feat);
173 printSection(stream, 'Breaking Changes', sections.breaks);
174 }
175
176
177 var getPreviousTag = function() {
178 var deffered = q.defer();
179 child.exec(GIT_TAG_CMD, function(code, stdout, stderr) {
180 if (code) deffered.reject('Cannot get the previous tag.');
181 else deffered.resolve(stdout.replace('\n', ''));
182 });
183 return deffered.promise;
184 };
185
186
187 var generate = function(version, file) {
188 getPreviousTag().then(function(tag) {
189 console.log('Reading git log since', tag);
190 readGitLog('^fix|^feat|Breaks', tag).then(function(commits) {
191 console.log('Parsed', commits.length, 'commits');
192 console.log('Generating changelog to', file || 'stdout', '(', version, ')');
193 writeChangelog(file ? fs.createWriteStream(file) : process.stdout, commits, version);
194 });
195 });
196 };
197
198
199 // publish for testing
200 exports.parseRawCommit = parseRawCommit;
201
202 // hacky start if not run by jasmine :-D
203 if (process.argv.join('').indexOf('jasmine-node') === -1) {
204 generate(process.argv[2], process.argv[3]);
205 }
Something went wrong with that request. Please try again.