diff options
| author | Vojta Jina | 2012-03-17 03:10:03 -0700 | 
|---|---|---|
| committer | Igor Minar | 2012-03-29 07:22:13 -0700 | 
| commit | 4557881cf84f168855fc8615e174f24d6c2dd6ce (patch) | |
| tree | fbc956578b95dc1af6ffa85158fe55fbba44b175 | |
| parent | af0ad6561c0d75c4f155b07e9cfc36a983af55bd (diff) | |
| download | angular.js-4557881cf84f168855fc8615e174f24d6c2dd6ce.tar.bz2 | |
chore(release scripts): auto release scripts
| -rwxr-xr-x | changelog.js | 201 | ||||
| -rw-r--r-- | changelog.spec.js | 43 | ||||
| -rw-r--r-- | changelog.tmp.md | 80 | ||||
| -rwxr-xr-x | release-commit.sh | 34 | ||||
| -rw-r--r-- | start-iteration.sh | 5 | ||||
| -rwxr-xr-x | version.js | 67 | 
6 files changed, 430 insertions, 0 deletions
| diff --git a/changelog.js b/changelog.js new file mode 100755 index 00000000..0083d29b --- /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 = '<a name="%s"></a>\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 00000000..1d3b7fe8 --- /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 00000000..872a4334 --- /dev/null +++ b/changelog.tmp.md @@ -0,0 +1,80 @@ +<a name="v1.0.0rc3"></a> +# 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 00000000..f05bde20 --- /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 00000000..bf82478c --- /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 00000000..f985327e --- /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); +  } +}); | 
