Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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