diff --git a/changelog.js b/changelog.js
new file mode 100755
index 000000000000..0083d29bb7bc
--- /dev/null
+++ b/changelog.js
@@ -0,0 +1,201 @@
+#!/usr/bin/env node
+
+// TODO(vojta): pre-commit hook for validating messages
+// TODO(vojta): report errors, currently Q silence everything which really sucks
+
+var child = require('child_process');
+var fs = require('fs');
+var util = require('util');
+var q = require('qq');
+
+var GIT_LOG_CMD = 'git log --grep="%s" -E --format=%s %s..HEAD';
+var GIT_TAG_CMD = 'git describe --tags --abbrev=0';
+
+var HEADER_TPL = '\n# %s (%s)\n\n';
+var LINK_ISSUE = '[#%s](https://github.com/angular/angular.js/issues/%s)';
+var LINK_COMMIT = '[%s](https://github.com/angular/angular.js/commit/%s)';
+
+var EMPTY_COMPONENT = '$$';
+var MAX_SUBJECT_LENGTH = 80;
+
+
+var warn = function() {
+ console.log('WARNING:', util.format.apply(null, arguments));
+};
+
+
+var parseRawCommit = function(raw) {
+ if (!raw) return null;
+
+ var lines = raw.split('\n');
+ var msg = {}, match;
+
+ msg.hash = lines.shift();
+ msg.subject = lines.shift();
+ msg.closes = [];
+ msg.breaks = [];
+
+ lines.forEach(function(line) {
+ match = line.match(/Closes\s#(\d+)/);
+ if (match) msg.closes.push(parseInt(match[1]));
+
+ match = line.match(/Breaks\s(.*)/);
+ if (match) msg.breaks.push(match[1]);
+ });
+
+ msg.body = lines.join('\n');
+ match = msg.subject.match(/^(.*)\((.*)\)\:\s(.*)$/);
+
+ if (!match || !match[1] || !match[3]) {
+ warn('Incorrect message: %s %s', msg.hash, msg.subject);
+ return null;
+ }
+
+ if (match[3].length > MAX_SUBJECT_LENGTH) {
+ warn('Too long subject: %s %s', msg.hash, msg.subject);
+ match[3] = match[3].substr(0, MAX_SUBJECT_LENGTH);
+ }
+
+ msg.type = match[1];
+ msg.component = match[2];
+ msg.subject = match[3];
+
+ return msg;
+};
+
+
+var linkToIssue = function(issue) {
+ return util.format(LINK_ISSUE, issue, issue);
+};
+
+
+var linkToCommit = function(hash) {
+ return util.format(LINK_COMMIT, hash.substr(0, 8), hash);
+};
+
+
+var currentDate = function() {
+ var now = new Date();
+ var pad = function(i) {
+ return ('0' + i).substr(-2);
+ };
+
+ return util.format('%d-%s-%s', now.getFullYear(), pad(now.getMonth() + 1), pad(now.getDate()));
+};
+
+
+var printSection = function(stream, title, section) {
+ var NESTED = true;
+ var components = Object.getOwnPropertyNames(section).sort();
+
+ if (!components.length) return;
+
+ stream.write(util.format('\n## %s\n\n', title));
+
+ components.forEach(function(name) {
+ var prefix = '-';
+
+ if (name !== EMPTY_COMPONENT) {
+ if (NESTED) {
+ stream.write(util.format('- **%s:**\n', name));
+ prefix = ' -';
+ } else {
+ prefix = util.format('- **%s:**', name);
+ }
+ }
+
+ section[name].forEach(function(commit) {
+ stream.write(util.format('%s %s (%s', prefix, commit.subject, linkToCommit(commit.hash)));
+ if (commit.closes.length) {
+ stream.write(', closes ' + commit.closes.map(linkToIssue).join(', '));
+ }
+ stream.write(')\n');
+ });
+ });
+
+ stream.write('\n');
+};
+
+
+var readGitLog = function(grep, from) {
+ var deffered = q.defer();
+
+ // TODO(vojta): if it's slow, use spawn and stream it instead
+ child.exec(util.format(GIT_LOG_CMD, grep, '%H%n%s%n%b%n==END==', from), function(code, stdout, stderr) {
+ var commits = [];
+
+ stdout.split('\n==END==\n').forEach(function(rawCommit) {
+ var commit = parseRawCommit(rawCommit);
+ if (commit) commits.push(commit);
+ });
+
+ deffered.resolve(commits);
+ });
+
+ return deffered.promise;
+};
+
+
+var writeChangelog = function(stream, commits, version) {
+ var sections = {
+ fix: {},
+ feat: {},
+ breaks: {}
+ };
+
+ sections.breaks[EMPTY_COMPONENT] = [];
+
+ commits.forEach(function(commit) {
+ var section = sections[commit.type];
+ var component = commit.component || EMPTY_COMPONENT;
+
+ if (section) {
+ section[component] = section[component] || [];
+ section[component].push(commit);
+ }
+
+ commit.breaks.forEach(function(breakMsg) {
+ sections.breaks[EMPTY_COMPONENT].push({
+ subject: breakMsg,
+ hash: commit.hash,
+ closes: []
+ });
+ });
+ });
+
+ stream.write(util.format(HEADER_TPL, version, version, currentDate()));
+ printSection(stream, 'Bug Fixes', sections.fix);
+ printSection(stream, 'Features', sections.feat);
+ printSection(stream, 'Breaking Changes', sections.breaks);
+}
+
+
+var getPreviousTag = function() {
+ var deffered = q.defer();
+ child.exec(GIT_TAG_CMD, function(code, stdout, stderr) {
+ if (code) deffered.reject('Cannot get the previous tag.');
+ else deffered.resolve(stdout.replace('\n', ''));
+ });
+ return deffered.promise;
+};
+
+
+var generate = function(version, file) {
+ getPreviousTag().then(function(tag) {
+ console.log('Reading git log since', tag);
+ readGitLog('^fix|^feat|Breaks', tag).then(function(commits) {
+ console.log('Parsed', commits.length, 'commits');
+ console.log('Generating changelog to', file || 'stdout', '(', version, ')');
+ writeChangelog(file ? fs.createWriteStream(file) : process.stdout, commits, version);
+ });
+ });
+};
+
+
+// publish for testing
+exports.parseRawCommit = parseRawCommit;
+
+// hacky start if not run by jasmine :-D
+if (process.argv.join('').indexOf('jasmine-node') === -1) {
+ generate(process.argv[2], process.argv[3]);
+}
diff --git a/changelog.spec.js b/changelog.spec.js
new file mode 100644
index 000000000000..1d3b7fe81a5f
--- /dev/null
+++ b/changelog.spec.js
@@ -0,0 +1,43 @@
+describe('changelog.js', function() {
+ var ch = require('./changelog');
+
+ describe('parseRawCommit', function() {
+ it('should parse raw commit', function() {
+ var msg = ch.parseRawCommit(
+ '9b1aff905b638aa274a5fc8f88662df446d374bd\n' +
+ 'feat(scope): broadcast $destroy event on scope destruction\n' +
+ 'perf testing shows that in chrome this change adds 5-15% overhead\n' +
+ 'when destroying 10k nested scopes where each scope has a $destroy listener\n');
+
+ expect(msg.type).toBe('feat');
+ expect(msg.hash).toBe('9b1aff905b638aa274a5fc8f88662df446d374bd');
+ expect(msg.subject).toBe('broadcast $destroy event on scope destruction');
+ expect(msg.body).toBe('perf testing shows that in chrome this change adds 5-15% overhead\n' +
+ 'when destroying 10k nested scopes where each scope has a $destroy listener\n')
+ expect(msg.component).toBe('scope');
+ });
+
+
+ it('should parse closed issues', function() {
+ var msg = ch.parseRawCommit(
+ '13f31602f396bc269076ab4d389cfd8ca94b20ba\n' +
+ 'feat(ng-list): Allow custom separator\n' +
+ 'bla bla bla\n\n' +
+ 'Closes #123\nCloses #25\n');
+
+ expect(msg.closes).toEqual([123, 25]);
+ });
+
+
+ it('should parse breaking changes', function() {
+ var msg = ch.parseRawCommit(
+ '13f31602f396bc269076ab4d389cfd8ca94b20ba\n' +
+ 'feat(ng-list): Allow custom separator\n' +
+ 'bla bla bla\n\n' +
+ 'Breaks first breaking change\nsomething else\n' +
+ 'Breaks another breaking change\n');
+
+ expect(msg.breaks).toEqual(['first breaking change', 'another breaking change']);
+ });
+ });
+});
diff --git a/changelog.tmp.md b/changelog.tmp.md
new file mode 100644
index 000000000000..872a43340a0a
--- /dev/null
+++ b/changelog.tmp.md
@@ -0,0 +1,80 @@
+
+# v1.0.0rc3 (2012-03-27)
+
+
+## Bug Fixes
+
+- **$compile:**
+ - create new (isolate) scopes for directives on root elements ([5390fb37](https://github.com/angular/angular.js/commit/5390fb37d2c01937922613fc57df4986af521787), closes [#817](https://github.com/angular/angular.js/issues/817))
+ - don't touch static element attributes ([9cb2195e](https://github.com/angular/angular.js/commit/9cb2195e61a78e99020ec19d687a221ca88b5900))
+ - Merge interpolated css class when replacing an element ([f49eaf8b](https://github.com/angular/angular.js/commit/f49eaf8bf2df5f4e0e82d6c89e849a4f82c8d414))
+- **$http:**
+ - don't send Content-Type header when no data ([1a5bebd9](https://github.com/angular/angular.js/commit/1a5bebd927ecd22f9c34617642fdf58fe3f62efb), closes [#749](https://github.com/angular/angular.js/issues/749))
+- **$log:**
+ - avoid console.log.apply calls in IE ([15213ec2](https://github.com/angular/angular.js/commit/15213ec212769837cb2b7e781ffc5bfd598d27ca), closes [#805](https://github.com/angular/angular.js/issues/805))
+- **$resource:**
+ - support escaping of ':' in resource url ([6d6f8753](https://github.com/angular/angular.js/commit/6d6f875345e01f2c6c63ef95164f6f39e923da15))
+- **compiler:**
+ - allow transclusion of root elements ([9918b748](https://github.com/angular/angular.js/commit/9918b748be01266eb10db39d51b4d3098d54ab66))
+- **e2e runner:**
+ - fix typo that caused errors on IE8 ([ee5a5352](https://github.com/angular/angular.js/commit/ee5a5352fd4b94cedee6ef20d4bf2d43ce77e00b), closes [#806](https://github.com/angular/angular.js/issues/806))
+- **forEach:**
+ - should ignore prototypically inherited properties ([8d7e6948](https://github.com/angular/angular.js/commit/8d7e6948496ff26ef1da8854ba02fcb8eebfed61), closes [#813](https://github.com/angular/angular.js/issues/813))
+- **forms:**
+ - Remove double registering of form ([1faafa31](https://github.com/angular/angular.js/commit/1faafa31582c4e9413f48dc7d12f5b681f9fe9fd))
+ - Set ng-valid/ng-invalid correctly ([08bfea18](https://github.com/angular/angular.js/commit/08bfea183a850b29da270eac47f80b598cbe600f))
+- **init:**
+ - use jQuery#ready for init if available ([cb2ad9ab](https://github.com/angular/angular.js/commit/cb2ad9abf24e6f855cc749efe3155bd7987ece9d), closes [#818](https://github.com/angular/angular.js/issues/818))
+- **json:**
+ - added support for iso8061 timezone ([5ac14f63](https://github.com/angular/angular.js/commit/5ac14f633a69f49973b5512780c6ec7752405967))
+- **matchers.toHaveClass:**
+ - Correct reference to angular.mock.dump ([f701ce08](https://github.com/angular/angular.js/commit/f701ce08f9d63be05fc3b92f57ad473e1e749b2d))
+- **ng-switch:**
+ - properly destroy child scopes ([2315d9b3](https://github.com/angular/angular.js/commit/2315d9b3610994b36c44e4a97fb1427d59471ce8))
+- **ngDocSpec:**
+ - fix broken tests ([53b6f522](https://github.com/angular/angular.js/commit/53b6f522a56eea314cbd084816e08f24b2c7879f))
+- **ngForm:**
+ - alias name||ngForm ([823adb23](https://github.com/angular/angular.js/commit/823adb231995e917bc060bfa49453e2a96bac2b6))
+- **ngRepeat:**
+ - correct variable reference in error message ([935c1018](https://github.com/angular/angular.js/commit/935c1018da05dbf3124b2dd33619c4a3c82d7a2a))
+- **ngView:**
+ - controller not published ([21e74c2d](https://github.com/angular/angular.js/commit/21e74c2d2e8e985b23711785287feb59965cbd90))
+- **q:**
+ - resolve all of nothing to nothing ([ac75079e](https://github.com/angular/angular.js/commit/ac75079e2113949d5d64adbcf23d56f3cf295d41))
+- **select:**
+ - multiselect failes to update view on selection insert ([6ecac8e7](https://github.com/angular/angular.js/commit/6ecac8e71a84792a434d21db2c245b3648c55f18))
+
+
+## Features
+
+- **$compile:**
+ - do not interpolate boolean attributes, rather evaluate them ([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
+- **$controller:**
+ - support controller registration via $controllerProvider ([d54dfecb](https://github.com/angular/angular.js/commit/d54dfecb00fba41455536c5ddd55310592fdaf84))
+- **$route:**
+ - when matching consider trailing slash as optional ([a4fe51da](https://github.com/angular/angular.js/commit/a4fe51da3ba0dc297ecd389e230d6664f250c9a6), closes [#784](https://github.com/angular/angular.js/issues/784))
+- **assertArgFn:**
+ - should support array annotated fns ([4b8d9260](https://github.com/angular/angular.js/commit/4b8d926062eb4d4483555bdbdec4656f585ab40b))
+- **http:**
+ - added params parameter ([73c85930](https://github.com/angular/angular.js/commit/73c8593077155a9f2e8ef42efd4c497eba0bef4f))
+- **injector:**
+ - infer _foo_ as foo ([f13dd339](https://github.com/angular/angular.js/commit/f13dd3393dfb7a33565c9360342c193bc0bddcb6))
+- **input.radio:**
+ - Allow value attribute to be interpolated ([ade6c452](https://github.com/angular/angular.js/commit/ade6c452753145c84884d17027a7865bf4b34b0c))
+- **jqLite:**
+ - make injector() and scope() work with the document object ([5fdab52d](https://github.com/angular/angular.js/commit/5fdab52dd7c269f99839f4fa6b5854d9548269fa))
+ - add .controller() method ([6c5a05ad](https://github.com/angular/angular.js/commit/6c5a05ad49a1e083570c3dfe331403398f899dbe))
+- **ngValue:**
+ - allow radio inputs to have non string values ([09e175f0](https://github.com/angular/angular.js/commit/09e175f02cca0f4a295fd0c9b980cd8f432e722b), closes [#816](https://github.com/angular/angular.js/issues/816))
+- **scope:**
+ - broadcast $destroy event on scope destruction ([9b1aff90](https://github.com/angular/angular.js/commit/9b1aff905b638aa274a5fc8f88662df446d374bd))
+- **scope.$eval:**
+ - Allow passing locals to the expression ([192ff61f](https://github.com/angular/angular.js/commit/192ff61f5d61899e667c6dbce4d3e6e399429d8b))
+
+
+## Breaking Changes
+
+- boolean attrs are evaluated rather than interpolated ([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
+- ng-bind-attr directive removed ([55027132](https://github.com/angular/angular.js/commit/55027132f3d57e5dcf94683e6e6bd7b0aae0087d))
+- any app that depends on this service and its fallback to Modernizr, please ([aaedefb9](https://github.com/angular/angular.js/commit/aaedefb92e6bec6626e173e5155072c91471596a))
+
diff --git a/release-commit.sh b/release-commit.sh
new file mode 100755
index 000000000000..f05bde207d0a
--- /dev/null
+++ b/release-commit.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+function catch_errors() {
+ echo "ERROR. That's life."
+ exit 1
+}
+
+trap catch_errors ERR
+
+TMP_FILE='changelog.tmp'
+CHANGELOG_FILE='CHANGELOG.md'
+
+echo "Getting current version..."
+VERSION=`./version.js --current`
+
+echo "Generating changelog..."
+./changelog.js $VERSION $TMP_FILE
+
+cat $CHANGELOG_FILE >> $TMP_FILE
+mv -f $TMP_FILE $CHANGELOG_FILE
+
+
+echo "Updating version..."
+./version.js --remove-snapshot
+
+echo "CONFIRM TO COMMIT"
+read WHATEVER
+
+
+echo "Creating commit..."
+git commit version.yaml CHANGELOG.md -m "chore(relase): cutting the v$VERSION release"
+
+echo "Creating tag..."
+git tag "v$VERSION"
diff --git a/start-iteration.sh b/start-iteration.sh
new file mode 100644
index 000000000000..bf82478c7fce
--- /dev/null
+++ b/start-iteration.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+./version.js --minor-bump
+VERSION=`./version.js --curent`
+git commit -a -m "chore(relase): start v$VERSION iteration"
diff --git a/version.js b/version.js
new file mode 100755
index 000000000000..f985327e2459
--- /dev/null
+++ b/version.js
@@ -0,0 +1,67 @@
+#!/usr/bin/env node
+
+var FILE = 'version.yaml';
+var fs = require('fs');
+var optimist = require('optimist');
+
+optimist
+ .usage('Manage ' + FILE + '.\nUsage: $0 [options]')
+ .describe('remove-snapshot', 'Remove -snapshot suffix.')
+ .describe('minor-bump', 'Bump minor version one step.')
+ .describe('minor-next', 'Return next minor version.')
+ .describe('current', 'Return current verion')
+ .describe('help', 'Show usage');
+
+
+var bumpMinor = function(version) {
+ var parts = version.split('.');
+ var last = parts.pop();
+
+ var rc = last.match(/(\d*)rc(\d*)/);
+ if (rc) {
+ parts.push(rc[1] + 'rc' + (parseInt(rc[2], 10) + 1));
+ } else {
+ parts.push('' + (parseInt(last, 10) + 1));
+ }
+
+ return parts.join('.');
+};
+
+fs.readFile(FILE, 'utf8', function(err, content) {
+ var version = content.match(/version\:\s([^\-\n]*)/)[1];
+
+ var args = optimist.argv;
+ if (args['remove-snapshot']) {
+ fs.writeFile(FILE, content.replace('-snapshot', ''), function(err) {
+ if (!err) {
+ console.log('Version updated (removed -snapshot).');
+ process.exit(0);
+ } else {
+ console.error('Version update failed.');
+ process.exit(1);
+ }
+ });
+ } else if (args['minor-next']) {
+ process.stdout.write(bumpMinor(version) + '\n');
+ process.exit(0);
+ } else if (args['current']) {
+ process.stdout.write(version + '\n');
+ process.exit(0);
+ } else if (args['minor-bump']) {
+ var bumped = bumpMinor(version);
+
+ if (!content.match(/\-snapshot/)) bumped += '-snapshot';
+ fs.writeFile(FILE, content.replace(version, bumped), function(err) {
+ if (!err) {
+ console.log('Version updated (bumped to ' + bumped + ').');
+ process.exit(0);
+ } else {
+ console.error('Version update failed.');
+ process.exit(1);
+ }
+ });
+ } else {
+ console.log(optimist.help());
+ process.exit(args['help'] ? 0 : 1);
+ }
+});