diff --git a/README.md b/README.md index 5b22db3..e8fefa4 100644 --- a/README.md +++ b/README.md @@ -141,9 +141,10 @@ need to install dependencies which are only necessary for the release. Defines new properties and methods to add to the `Release` object. -#### abort( msg ) +#### abort( msg [, error ] ) -Aborts the release and prints the message. +Aborts the release and prints the message. If an error object is provided, it is +used for the stack trace, otherwise the current call stack is used. #### exec( command, options ) diff --git a/lib/changelog.js b/lib/changelog.js index 6d29e53..cd1dba3 100644 --- a/lib/changelog.js +++ b/lib/changelog.js @@ -1,16 +1,20 @@ -var fs = require( "fs" ); +var fs = require( "fs" ), + changelogplease = require( "changelogplease" ); module.exports = function( Release ) { Release.define({ - _generateChangelog: function() { - var changelogPath = Release.dir.base + "/changelog", - changelog = Release.changelogShell() + - Release._generateCommitChangelog() + - Release._generateIssueChangelog(); - - fs.writeFileSync( changelogPath, changelog ); - console.log( "Stored changelog in " + changelogPath.cyan + "." ); + _generateChangelog: function( callback ) { + Release._generateCommitChangelog(function( commitChangelog ) { + var changelogPath = Release.dir.base + "/changelog", + changelog = Release.changelogShell() + + commitChangelog + + Release._generateIssueChangelog(); + + fs.writeFileSync( changelogPath, changelog ); + console.log( "Stored changelog in " + changelogPath.cyan + "." ); + callback(); + }); }, changelogShell: function() { @@ -21,39 +25,21 @@ Release.define({ return Release.newVersion; }, - _generateCommitChangelog: function() { - var commits, - commitRef = "[%h](" + Release._repositoryUrl() + "/commit/%H)", - fullFormat = "* %s (TICKETREF, " + commitRef + ")", - ticketUrl = Release._ticketUrl(); - + _generateCommitChangelog: function( callback ) { console.log( "Adding commits..." ); - Release.chdir( Release.dir.repo ); - commits = Release.gitLog( fullFormat ); - - console.log( "Adding links to tickets..." ); - return commits - - // Add ticket references - .map(function( commit ) { - var tickets = []; - - commit.replace( /Fix(?:e[sd])? #(\d+)/g, function( match, ticket ) { - tickets.push( ticket ); - }); - - return tickets.length ? - commit.replace( "TICKETREF", tickets.map(function( ticket ) { - return "[#" + ticket + "](" + ticketUrl + ticket + ")"; - }).join( ", " ) ) : - - // Leave TICKETREF token in place so it's easy to find commits without tickets - commit; - }) - // Sort commits so that they're grouped by component - .sort() - .join( "\n" ) + "\n"; + changelogplease({ + ticketUrl: Release._ticketUrl() + "{id}", + commitUrl: Release._repositoryUrl() + "/commit/{id}", + repo: Release.dir.repo, + committish: Release.prevVersion + ".." + Release.newVersion + }, function( error, log ) { + if ( error ) { + Release.abort( "Error generating commit changelog.", error ); + } + + callback( log ); + }); }, _generateIssueChangelog: function() { diff --git a/lib/util.js b/lib/util.js index b7338c6..3adf5df 100644 --- a/lib/util.js +++ b/lib/util.js @@ -29,12 +29,17 @@ Release.define({ process.chdir( directory ); }, - abort: function( msg ) { - var error = new Error(); - Error.captureStackTrace( error ); + abort: function( msg, error ) { + if ( !error ) { + error = new Error( msg ); + Error.captureStackTrace( error, Release.abort ); + } + console.log( msg.red ); console.log( "Aborting.".red ); + console.log(); console.log( error.stack ); + process.exit( 1 ); }, diff --git a/node_modules/changelogplease/.npmignore b/node_modules/changelogplease/.npmignore new file mode 100644 index 0000000..36f30bb --- /dev/null +++ b/node_modules/changelogplease/.npmignore @@ -0,0 +1,4 @@ +/node_modules/ +/tests/ +.travis.yml +Gruntfile.js diff --git a/node_modules/changelogplease/LICENSE.txt b/node_modules/changelogplease/LICENSE.txt new file mode 100644 index 0000000..2354819 --- /dev/null +++ b/node_modules/changelogplease/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright 2014 Scott González http://scottgonzalez.com + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/changelogplease/README.md b/node_modules/changelogplease/README.md new file mode 100644 index 0000000..b69685b --- /dev/null +++ b/node_modules/changelogplease/README.md @@ -0,0 +1,60 @@ +# Changelog, please + +Generate changelogs from git commit messages using node.js. The generated changelogs are written in markdown. + +Support this project by [donating on Gittip](https://www.gittip.com/scottgonzalez/). + +## Installation + +``` +npm install changelogplease +``` + +## Usage + +```javascript +var changelog = require( "changelogplease" ); +var parsed = changelog({ + ticketUrl: "https://github.com/scottgonzalez/changelogplease/issues/{id}", + commitUrl: "https://github.com/scottgonzalez/changelogplease/commit/{id}", + repo: "/path/to/repo", + committish: "1.2.3..1.2.4" +}); +``` + +## API + +### changelog( options, callback ) + +* `options` (Object): Options for creating the changelog. + * `ticketUrl` (String): Template for ticket/issue URLs; `{id}` will be replaced with the ticket id. + * `commitUrl (String)`: Template for commit URLs; `{id}` will be replaced with the commit hash. + * `repo` (String): Path to the repository. + * `committish` (String): The range of commits for the changelog. +* `callback` (Function; `function( error, log )`): Function to invoke after generating the changelog. + * `log` (String): Generated changelog, written in markdown. + +### Changelog + +`changelog( options, callback )` is a shorthand for the following: + +```js +var Changelog = require( "changelogplease" ).Changelog; +var instance = new Changelog( options ); +instance.parse( callback ); +``` + +Changelog generation is tailored to a specific format based on the needs of the various jQuery +projects. However, the `Changelog` constructor and prototype are exposed to allow flexibility. +Be aware that these methods are not currently documented because they may change. Feel free to +submit [feature requests](https://github.com/scottgonzalez/changelogplease/issues/new) if you don't +feel comfortable hacking in your own changes (or even if you do). + + +## License + +Copyright 2014 Scott González. Released under the terms of the MIT license. + +--- + +Support this project by [donating on Gittip](https://www.gittip.com/scottgonzalez/). diff --git a/node_modules/changelogplease/index.js b/node_modules/changelogplease/index.js new file mode 100644 index 0000000..1cbf0b3 --- /dev/null +++ b/node_modules/changelogplease/index.js @@ -0,0 +1,107 @@ +var Repo = require( "git-tools" ); + +exports = module.exports = changelog; +exports.Changelog = Changelog; + +function changelog( options, callback ) { + var instance = new Changelog( options ); + + if ( callback ) { + instance.parse( callback ); + } + + return instance; +} + +function Changelog( options ) { + this.options = options; + this.repo = new Repo( this.options.repo ); + + // Bind all methods to the instance + for ( var method in this ) { + if ( typeof this[ method ] === "function" ) { + this[ method ] = this[ method ].bind( this ); + } + } +} + +Changelog.prototype.parse = function( callback ) { + this.getLog(function( error, log ) { + if ( error ) { + return callback( error ); + } + + callback( null, this.parseCommits( log ) ); + }.bind( this )); +}; + +Changelog.prototype.ticketUrl = function( id ) { + return this.options.ticketUrl.replace( "{id}", id ); +}; + +Changelog.prototype.getLog = function( callback ) { + var commitUrl = this.options.commitUrl.replace( "{id}", "%H" ); + + this.repo.exec( "log", + "--format=" + + "__COMMIT__%n" + + "%s (__TICKETREF__, [%h](" + commitUrl + "))%n" + + "%b", + this.options.committish, + callback ); +}; + +Changelog.prototype.parseCommits = function( commits ) { + commits = commits.split( "__COMMIT__\n" ); + commits.shift(); + + return commits + + // Parse each individual commit + .map( this.parseCommit ) + + // Sort commits so that they're grouped by component + .sort() + .join( "\n" ) + "\n"; +}; + +Changelog.prototype.parseCommit = function( commit ) { + var ticketUrl = this.ticketUrl; + var tickets = []; + + // Sane global exec with iteration over matches + commit.replace( + /Fix(?:e[sd])? ((?:[a-zA-Z0-9_-]{1,39}\/[a-zA-Z0-9_-]{1,100}#)|#|gh-)(\d+)/g, + function( match, refType, ticketId ) { + var ticketRef = { + url: ticketUrl( ticketId ), + label: "#" + ticketId + }; + + // If the refType has anything before the #, assume it's a GitHub ref + if ( /.#$/.test( refType ) ) { + refType = refType.replace( /#$/, "" ); + ticketRef.url = "https://github.com/" + refType + "/issues/" + ticketId; + ticketRef.label = refType + ticketRef.label; + } + + tickets.push( ticketRef ); + } + ); + + // Only keep the summary for the changelog; drop the body + var parsed = "* " + commit.split( /\r?\n/ )[ 0 ]; + + // Add in ticket references + // Leave __TICKETREF__ token in place so it's easy to find commits without tickets + if ( tickets.length ) { + parsed = parsed.replace( "__TICKETREF__", tickets.map(function( ticket ) { + return "[" + ticket.label + "](" + ticket.url + ")"; + }).join( ", " ) ); + } + + // Remove cherry pick references + parsed = parsed.replace( / \(cherry picked from commit [^)]+\)/, "" ); + + return parsed; +}; diff --git a/node_modules/changelogplease/node_modules/git-tools/.npmignore b/node_modules/changelogplease/node_modules/git-tools/.npmignore new file mode 100644 index 0000000..3a4259c --- /dev/null +++ b/node_modules/changelogplease/node_modules/git-tools/.npmignore @@ -0,0 +1,3 @@ +/test/ +.jshintrc +Gruntfile.js diff --git a/node_modules/changelogplease/node_modules/git-tools/LICENSE-MIT.txt b/node_modules/changelogplease/node_modules/git-tools/LICENSE-MIT.txt new file mode 100644 index 0000000..b99c1ea --- /dev/null +++ b/node_modules/changelogplease/node_modules/git-tools/LICENSE-MIT.txt @@ -0,0 +1,20 @@ +Copyright 2013 Scott González http://scottgonzalez.com + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/changelogplease/node_modules/git-tools/README.md b/node_modules/changelogplease/node_modules/git-tools/README.md new file mode 100644 index 0000000..ad92887 --- /dev/null +++ b/node_modules/changelogplease/node_modules/git-tools/README.md @@ -0,0 +1,265 @@ +# node-git-tools + +Tools for parsing data out of git repositories. + +Support this project by [donating on Gittip](https://www.gittip.com/scottgonzalez/). + +## About + +The goal of node-git-tools is to provide a set of tools that can be used to +easily write custom git commands or other scripts that are tightly integrated +with git. + +I expect the API to grow over time and will happily entertain any feature +requests. If there is anything you'd like added, just file an issue. + +## Installation + +```sh +npm install git-tools +``` + +## Usage + +```js +var Repo = require( "git-tools" ); +var repo = new Repo( "path/to/repo" ); +repo.authors(function( error, authors ) { + console.log( authors ); +}); +``` + + + +## API + +### Repo.clone( options, callback ) + +Clones a repository and returns the new `Repo` instance. + +* `options` (Object): Options for cloning the repository. + * `repo` (String): The repository to clone from. + * `path` (String): The path to clone into. + * extra: Additional options can be provided, as documented below. +* `callback` (Function; `function( error, repo )`): Function to invoke after cloning the repository. + * `repo` (Object): A new `Repo` instance for the cloned repository. + +This function accepts arbitrary options to pass to `git clone`. +For example, to create a bare repository: + +```js +Repo.clone({ + repo: "git://github.com/scottgonzalez/node-git-tools.git", + dir: "/tmp/git-tools", + bare: true +}); +``` + +Or to create a repo with limited history: + +```js +Repo.clone({ + repo: "git://github.com/scottgonzalez/node-git-tools.git", + dir: "/tmp/git-tools", + depth: 5 +}); +``` + + + +### Repo.isRepo( path, callback ) + +Determines if the specified path is a git repository. + +* `path` (String): The path to check. +* `callback` (Function; `function( error, isRepo )`): Function to invoke after determining if the path is a git repository. + * `isRepo` (Boolean): Whether the path is a git repository. + +*Note: This is equivalent to creating a `Repo` instance with `path` and calling `isRepo()` on the instance.* + + + +### activeDays( [committish], callback ) + +Gets the number of active days (unique days of authorship). Includes activity +by day, month, year, and the entire history. + +* `committish` (String; default: `"master"`): Committish range to analyze. +* `callback` (Function; `function( error, activeDays )`): Function to invoke after getting active days. + * `activeDays` (Object): Summary of active days. + +The `activeDays` object has the following form: + +```js +{ + activeDays: Number, + commits: Number, + dates: { + "YYYY-MM-DD": Number( commits ), + ... + }, + years: { + "YYYY": { + activeDays: Number, + commits: Number, + months: { + "M": { + activeDays: Number, + commits: Number, + days: { + "D": { + commits: Number + }, + ... + } + }, + ... + } + }, + ... + } +} +``` + + + +### age( callback ) + +Gets the age of the repository. + +* `callback` (Function; `function( error, age )`): Function to invoke after getting the age. + * `age` (String): The age of the repository. + + + +### authors( [committish], callback ) + +Gets all authors, sorted by number of commits. + +* `committish` (String; default: `"master"`): Committish range to analyze. +* `callback` (Function; `function( error, authors )`): Function to invoke after getting authors. + * `authors` (Array): All authors, sorted by number of commits. + +Each author object contains the following properties: + +* `email` (String): Author's email address. +* `name` (String): Author's name. +* `commits` (Number): Number of commits. +* `commitsPercent` (Number): Percentage of commits. + + + +### blame( options, callback ) + +Determine what revision and author last modified each line of a file. + +* `options` (Object): Options for the blame. + * `path` (String): The path to the file to run the blame for. + * `committish` (String; default: `"HEAD"`): Revision or range to blame against. +* `callback` (Function; `function( error, blame )`): Function to invoke after blaming the file. + * `blame` (Array): Commit information for each line. + +Each blame item contains the following properties: + +* `commit`: SHA of commit that most recently modified the line. +* `path`: Path to the file at the time of the most recent modification to the line. +* `lineNumber`: Line number within the file. +* `content`: Contents of the line. + + + +### branches( callback ) + +Gets all branches in order of most recent commit. + +* `callback` (Function; `function( error, branches )`): Function to invoke after getting branches. + * `branches` (Array): All branches, sorted by most recent commit date. + +Each branch object contains the following properties: + +* `name` (String): Branch name. +* `sha` (String): SHA-1 of most recent commit. +* `date` (Date): Author date of most recent commit. +* `subject` (String): Subject (first line) of most recent commit. +* `author` (Object): Author of most recent commit. + * `email` (String): Author's email address. + * `name` (String): Author's name. + + + +### config( name, callback ) + +Gets the value of a configuration option. + +* `name` (String): The name of the configuration option. +* `callback` (Function; `function( error, value )`): Function to invoke after getting the configuration option. + * `value` (String|null): The value for the configuration option, or `null` if no value is set. + + + +### currentBranch( callback ) + +Gets the name of the currently checked out branch, if any. + +* `callback` (Function; `function( error, branch )`): Function to invoke after getting the branch. + * `branch` (String|null): Branch name, or `null` if in detached HEAD state. + + + +### isRepo( callback ) + +Determines if the specified path is a git repository. + +* `callback` (Function; `function( error, isRepo )`): Function to invoke after determining if the path is a git repository. + * `isRepo` (Boolean): Whether the path is a git repository. + + + +### remotes( callback ) + +Gets all remote repositories. + +* `callback` (Function; `function( error, remotes )`): Function to invoke after getting the remotes. + * `remotes` (Array): All remote repositories. + +Each remote object contains the following properties: + +* `name` (String): Remote name. +* `url` (String): URL for the remote repository. + + + +### resolveCommittish( committish, callback ) + +Resolves a committish to a SHA1. + +* `committish` (String): Any committish to resolve. +* `callback` (Function; `function( error, sha )`): Function to invoke after resolving the comittish. + * `sha`: SHA1 of the resolved committish. + + + +### tags( callback ) + +Gets all tags in reverse chronological order. + +Lightweight tags are sorted by author date and annotated tags are sorted by tagger date. + +* `callback` (Function; `function( error, tags )`): Function to invoke after getting tags. + * `tags` (Array): All tags, sorted by date. + +Each tag object contains the following properties: + +* `name` (String): Tag name. +* `sha` (String): SHA-1 for the tag. For lightweight tags, this is the SHA-1 of the commit. +* `date` (Date): Author date for ligthweight tags, tagger date for annotated tags. + + + +## License + +Copyright 2013 Scott González. Released under the terms of the MIT license. + +--- + +Support this project by [donating on Gittip](https://www.gittip.com/scottgonzalez/). diff --git a/node_modules/changelogplease/node_modules/git-tools/git-tools.js b/node_modules/changelogplease/node_modules/git-tools/git-tools.js new file mode 100644 index 0000000..541ce59 --- /dev/null +++ b/node_modules/changelogplease/node_modules/git-tools/git-tools.js @@ -0,0 +1,396 @@ +var spawn = require( "child_process" ).spawn; + +function extend( a, b ) { + for ( var prop in b ) { + a[ prop ] = b[ prop ]; + } + + return a; +} + +function copy( obj ) { + return extend( {}, obj ); +} + +function Repo( path ) { + this.path = path; +} + +Repo.parsePerson = (function() { + var rPerson = /^(\S+)\s(.+)$/; + return function( person ) { + var matches = rPerson.exec( person ); + return { + email: matches[ 1 ], + name: matches[ 2 ] + }; + }; +})(); + +Repo.clone = function( options, callback ) { + var dir = options.dir; + var args = [ "clone", options.repo, dir ]; + options = copy( options ); + delete options.repo; + delete options.dir; + + Object.keys( options ).forEach(function( option ) { + args.push( "--" + option ); + + var value = options[ option ]; + if ( value !== true ) { + args.push( value ); + } + }); + + args.push(function( error ) { + if ( error ) { + return callback( error ); + } + + callback( null, new Repo( dir ) ); + }); + + var repo = new Repo( process.cwd() ); + repo.exec.apply( repo, args ); +}; + +Repo.isRepo = function( path, callback ) { + var repo = new Repo( path ); + repo.isRepo( callback ); +}; + +Repo.prototype.exec = function() { + var args = [].slice.call( arguments ); + var callback = args.pop(); + var stdout = ""; + var stderr = ""; + var child = spawn( "git", args, { cwd: this.path } ); + var hadError = false; + child.on( "error", function( error ) { + hadError = true; + callback( error ); + }); + child.stdout.on( "data", function( data ) { + stdout += data; + }); + child.stderr.on( "data", function( data ) { + stderr += data; + }); + child.on( "close", function( code ) { + if ( hadError ) { + return; + } + + var error; + if ( code ) { + error = new Error( stderr ); + error.code = code; + return callback( error ); + } + + callback( null, stdout.trimRight() ); + }); +}; + +Repo.prototype.activeDays = function( committish, callback ) { + if ( !callback ) { + callback = committish; + committish = "master"; + } + + this.exec( "log", "--format=%at", committish, function( error, dates ) { + if ( error ) { + return callback( error ); + } + + var dateMap = { + activeDays: 0, + commits: 0, + dates: {}, + years: {} + }; + + dates.split( "\n" ).sort().forEach(function( timestamp ) { + var date = new Date( timestamp * 1000 ); + var year = date.getFullYear(); + var month = date.getMonth() + 1; + var day = date.getDate(); + + date = year + "-" + + (month < 10 ? "0" : "") + month + "-" + + (day < 10 ? "0" : "") + day; + + if ( !dateMap.dates[ date ] ) { + dateMap.dates[ date ] = 0; + } + dateMap.commits++; + dateMap.dates[ date ]++; + + if ( !dateMap.years[ year ] ) { + dateMap.years[ year ] = { + activeDays: 0, + commits: 0, + months: {} + }; + } + dateMap.years[ year ].commits++; + + if ( !dateMap.years[ year ].months[ month ] ) { + dateMap.years[ year ].months[ month ] = { + activeDays: 0, + commits: 0, + days: {} + }; + } + dateMap.years[ year ].months[ month ].commits++; + + if ( !dateMap.years[ year ].months[ month ].days[ day ] ) { + dateMap.years[ year ].months[ month ].days[ day ] = { + commits: 0 + }; + dateMap.activeDays++; + dateMap.years[ year ].activeDays++; + dateMap.years[ year ].months[ month ].activeDays++; + } + dateMap.years[ year ].months[ month ].days[ day ].commits++; + }); + + callback( null, dateMap ); + }); +}; + +Repo.prototype.age = function( callback ) { + this.exec( "log", "--reverse", "--format=%cr", function( error, stdout ) { + if ( error ) { + return callback( error ); + } + + callback( null, stdout.split( "\n" )[ 0 ].replace( /\sago/, "" ) ); + }); +}; + +Repo.prototype.authors = function( committish, callback ) { + if ( !callback ) { + callback = committish; + committish = "master"; + } + + this.exec( "log", "--format=%aE %aN", committish, function( error, data ) { + if ( error ) { + return callback( error ); + } + + var authors = data.split( "\n" ); + var authorMap = {}; + var totalCommits = 0; + + authors.forEach(function( author ) { + if ( !authorMap[ author ] ) { + authorMap[ author ] = 0; + } + + authorMap[ author ]++; + totalCommits++; + }); + + authors = Object.keys( authorMap ).map(function( author ) { + var commits = authorMap[ author ]; + return extend( Repo.parsePerson( author ), { + commits: commits, + commitsPercent: (commits * 100 / totalCommits).toFixed( 1 ) + }); + }).sort(function( a, b ) { + return b.commits - a.commits; + }); + + callback( null, authors ); + }); +}; + +Repo.prototype.blame = function( options, callback ) { + var args = [ "blame", "-s" ]; + + if ( options.committish ) { + args.push( options.committish ); + } + + args.push( "--", options.path ); + + var rBlame = /^(\w+)(\s(\S+))?\s+(\d+)\)\s(.*)$/; + + args.push(function( error, blame ) { + if ( error ) { + return callback( error ); + } + + var lines = blame.split( /\r?\n/ ); + lines = lines.map(function( line ) { + var matches = rBlame.exec( line ); + + return { + commit: matches[ 1 ], + path: matches[ 3 ] || options.path, + lineNumber: parseInt( matches[ 4 ], 10 ), + content: matches[ 5 ] + }; + }); + + callback( null, lines ); + }); + + this.exec.apply( this, args ); +}; + +Repo.prototype.branches = function( callback ) { + this.exec( "for-each-ref", + "--format=" + + "%(refname:short)%0a" + + "%(authordate:rfc2822)%0a" + + "%(authoremail) %(authorname)%0a" + + "%(subject)%0a" + + "%(objectname)%0a", + "refs/heads", + function( error, data ) { + if ( error ) { + return callback( error ); + } + + var branches = data.split( "\n\n" ).map(function( branch ) { + var lines = branch.split( "\n" ); + var name = lines[ 0 ]; + var date = new Date( lines[ 1 ] ); + var author = Repo.parsePerson( lines[ 2 ] ); + var subject = lines[ 3 ]; + var sha = lines[ 4 ]; + + return { + name: name, + sha: sha, + date: date, + subject: subject, + author: author + }; + }).sort(function( a, b ) { + return b.date - a.date; + }); + + callback( null, branches ); + }); +}; + +Repo.prototype.config = function( name, callback ) { + this.exec( "config --get " + name, function( error, stdout ) { + if ( error ) { + if ( /^Command failed:\s+$/.test( error.message ) ) { + return callback( null, null ); + } + + return callback( error ); + } + + callback( null, stdout.trim() ); + }); +}; + +Repo.prototype.currentBranch = function( callback ) { + this.exec( "rev-parse", "--abbrev-ref", "HEAD", function( error, data ) { + if ( error ) { + return callback( error ); + } + + var branch = data === "HEAD" ? null : data; + callback( null, branch ); + }); +}; + +Repo.prototype.isRepo = function( callback ) { + this.exec( "rev-parse", "--git-dir", function( error ) { + if ( error ) { + if ( error.message.indexOf( "Not a git repository" ) ) { + return callback( null, false ); + } + + // If the path doesn't exist, don't return an error + if ( error.code === "ENOENT" ) { + return callback( null, false ); + } + + return callback( error ); + } + + callback( null, true ); + }); +}; + +Repo.prototype.remotes = function( callback ) { + this.exec( "remote", "-v", function( error, data ) { + if ( error ) { + return callback( error ); + } + + var remotes = data.split( "\n" ); + var rRemote = /^(\S+)\s(\S+)/; + var remoteMap = {}; + + remotes.forEach(function( remote ) { + var matches = rRemote.exec( remote ); + + // New repositories with no remotes will have `origin` but no URL + if ( !matches ) { + return; + } + + var name = matches[ 1 ]; + var url = matches[ 2 ]; + + remoteMap[ name ] = url; + }); + + remotes = Object.keys( remoteMap ).map(function( remote ) { + return { + name: remote, + url: remoteMap[ remote ] + }; + }); + + callback( null, remotes ); + }); +}; + +Repo.prototype.resolveCommittish = function( committish, callback ) { + this.exec( "rev-parse", committish, callback ); +}; + +Repo.prototype.tags = function( callback ) { + this.exec( "for-each-ref", + "--format=" + + "%(refname:short)%0a" + + "%(authordate)%(taggerdate)%0a" + + "%(objectname)%0a", + "refs/tags", + function( error, data ) { + if ( error ) { + return callback( error ); + } + + var tags = data.split( "\n\n" ).map(function( tag ) { + var lines = tag.split( "\n" ); + var name = lines[ 0 ]; + var date = new Date( lines[ 1 ] ); + var sha = lines[ 2 ]; + + return { + name: name, + sha: sha, + date: date + }; + }).sort(function( a, b ) { + return b.date - a.date; + }); + + callback( null, tags ); + }); +}; + +module.exports = Repo; diff --git a/node_modules/changelogplease/node_modules/git-tools/package.json b/node_modules/changelogplease/node_modules/git-tools/package.json new file mode 100644 index 0000000..a24419f --- /dev/null +++ b/node_modules/changelogplease/node_modules/git-tools/package.json @@ -0,0 +1,32 @@ +{ + "name": "git-tools", + "version": "0.1.0", + "description": "Tools for parsing data out of git repositories.", + "keywords": [ + "git" + ], + "homepage": "https://github.com/scottgonzalez/node-git-tools", + "bugs": { + "url": "https://github.com/scottgonzalez/node-git-tools/issues" + }, + "author": { + "name": "Scott González", + "email": "scott.gonzalez@gmail.com", + "url": "http://scottgonzalez.com" + }, + "main": "git-tools.js", + "repository": { + "type": "git", + "url": "git://github.com/scottgonzalez/node-git-tools.git" + }, + "devDependencies": { + "grunt": "~0.4.0", + "grunt-contrib-jshint": "~0.1.1", + "grunt-contrib-nodeunit": "~0.1.2", + "rimraf": "~2.1.4" + }, + "readme": "# node-git-tools\n\nTools for parsing data out of git repositories.\n\nSupport this project by [donating on Gittip](https://www.gittip.com/scottgonzalez/).\n\n## About\n\nThe goal of node-git-tools is to provide a set of tools that can be used to\neasily write custom git commands or other scripts that are tightly integrated\nwith git.\n\nI expect the API to grow over time and will happily entertain any feature\nrequests. If there is anything you'd like added, just file an issue.\n\n## Installation\n\n```sh\nnpm install git-tools\n```\n\n## Usage\n\n```js\nvar Repo = require( \"git-tools\" );\nvar repo = new Repo( \"path/to/repo\" );\nrepo.authors(function( error, authors ) {\n\tconsole.log( authors );\n});\n```\n\n\n\n## API\n\n### Repo.clone( options, callback )\n\nClones a repository and returns the new `Repo` instance.\n\n* `options` (Object): Options for cloning the repository.\n * `repo` (String): The repository to clone from.\n * `path` (String): The path to clone into.\n * extra: Additional options can be provided, as documented below.\n* `callback` (Function; `function( error, repo )`): Function to invoke after cloning the repository.\n * `repo` (Object): A new `Repo` instance for the cloned repository.\n\nThis function accepts arbitrary options to pass to `git clone`.\nFor example, to create a bare repository:\n\n```js\nRepo.clone({\n\trepo: \"git://github.com/scottgonzalez/node-git-tools.git\",\n\tdir: \"/tmp/git-tools\",\n\tbare: true\n});\n```\n\nOr to create a repo with limited history:\n\n```js\nRepo.clone({\n\trepo: \"git://github.com/scottgonzalez/node-git-tools.git\",\n\tdir: \"/tmp/git-tools\",\n\tdepth: 5\n});\n```\n\n\n\n### Repo.isRepo( path, callback )\n\nDetermines if the specified path is a git repository.\n\n* `path` (String): The path to check.\n* `callback` (Function; `function( error, isRepo )`): Function to invoke after determining if the path is a git repository.\n * `isRepo` (Boolean): Whether the path is a git repository.\n\n*Note: This is equivalent to creating a `Repo` instance with `path` and calling `isRepo()` on the instance.*\n\n\n\n### activeDays( [committish], callback )\n\nGets the number of active days (unique days of authorship). Includes activity\nby day, month, year, and the entire history.\n\n* `committish` (String; default: `\"master\"`): Committish range to analyze.\n* `callback` (Function; `function( error, activeDays )`): Function to invoke after getting active days.\n * `activeDays` (Object): Summary of active days.\n\nThe `activeDays` object has the following form:\n\n```js\n{\n\tactiveDays: Number,\n\tcommits: Number,\n\tdates: {\n\t\t\"YYYY-MM-DD\": Number( commits ),\n\t\t...\n\t},\n\tyears: {\n\t\t\"YYYY\": {\n\t\t\tactiveDays: Number,\n\t\t\tcommits: Number,\n\t\t\tmonths: {\n\t\t\t\t\"M\": {\n\t\t\t\t\tactiveDays: Number,\n\t\t\t\t\tcommits: Number,\n\t\t\t\t\tdays: {\n\t\t\t\t\t\t\"D\": {\n\t\t\t\t\t\t\tcommits: Number\n\t\t\t\t\t\t},\n\t\t\t\t\t\t...\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t...\n\t\t\t}\n\t\t},\n\t\t...\n\t}\n}\n```\n\n\n\n### age( callback )\n\nGets the age of the repository.\n\n* `callback` (Function; `function( error, age )`): Function to invoke after getting the age.\n * `age` (String): The age of the repository.\n\n\n\n### authors( [committish], callback )\n\nGets all authors, sorted by number of commits.\n\n* `committish` (String; default: `\"master\"`): Committish range to analyze.\n* `callback` (Function; `function( error, authors )`): Function to invoke after getting authors.\n * `authors` (Array): All authors, sorted by number of commits.\n\nEach author object contains the following properties:\n\n* `email` (String): Author's email address.\n* `name` (String): Author's name.\n* `commits` (Number): Number of commits.\n* `commitsPercent` (Number): Percentage of commits.\n\n\n\n### blame( options, callback )\n\nDetermine what revision and author last modified each line of a file.\n\n* `options` (Object): Options for the blame.\n * `path` (String): The path to the file to run the blame for.\n * `committish` (String; default: `\"HEAD\"`): Revision or range to blame against.\n* `callback` (Function; `function( error, blame )`): Function to invoke after blaming the file.\n * `blame` (Array): Commit information for each line.\n\nEach blame item contains the following properties:\n\n* `commit`: SHA of commit that most recently modified the line.\n* `path`: Path to the file at the time of the most recent modification to the line.\n* `lineNumber`: Line number within the file.\n* `content`: Contents of the line.\n\n\n\n### branches( callback )\n\nGets all branches in order of most recent commit.\n\n* `callback` (Function; `function( error, branches )`): Function to invoke after getting branches.\n * `branches` (Array): All branches, sorted by most recent commit date.\n\nEach branch object contains the following properties:\n\n* `name` (String): Branch name.\n* `sha` (String): SHA-1 of most recent commit.\n* `date` (Date): Author date of most recent commit.\n* `subject` (String): Subject (first line) of most recent commit.\n* `author` (Object): Author of most recent commit.\n * `email` (String): Author's email address.\n * `name` (String): Author's name.\n\n\n\n### config( name, callback )\n\nGets the value of a configuration option.\n\n* `name` (String): The name of the configuration option.\n* `callback` (Function; `function( error, value )`): Function to invoke after getting the configuration option.\n * `value` (String|null): The value for the configuration option, or `null` if no value is set.\n\n\n\n### currentBranch( callback )\n\nGets the name of the currently checked out branch, if any.\n\n* `callback` (Function; `function( error, branch )`): Function to invoke after getting the branch.\n * `branch` (String|null): Branch name, or `null` if in detached HEAD state.\n\n\n\n### isRepo( callback )\n\nDetermines if the specified path is a git repository.\n\n* `callback` (Function; `function( error, isRepo )`): Function to invoke after determining if the path is a git repository.\n * `isRepo` (Boolean): Whether the path is a git repository.\n\n\n\n### remotes( callback )\n\nGets all remote repositories.\n\n* `callback` (Function; `function( error, remotes )`): Function to invoke after getting the remotes.\n * `remotes` (Array): All remote repositories.\n\nEach remote object contains the following properties:\n\n* `name` (String): Remote name.\n* `url` (String): URL for the remote repository.\n\n\n\n### resolveCommittish( committish, callback )\n\nResolves a committish to a SHA1.\n\n* `committish` (String): Any committish to resolve.\n* `callback` (Function; `function( error, sha )`): Function to invoke after resolving the comittish.\n * `sha`: SHA1 of the resolved committish.\n\n\n\n### tags( callback )\n\nGets all tags in reverse chronological order.\n\nLightweight tags are sorted by author date and annotated tags are sorted by tagger date.\n\n* `callback` (Function; `function( error, tags )`): Function to invoke after getting tags.\n * `tags` (Array): All tags, sorted by date.\n\nEach tag object contains the following properties:\n\n* `name` (String): Tag name.\n* `sha` (String): SHA-1 for the tag. For lightweight tags, this is the SHA-1 of the commit.\n* `date` (Date): Author date for ligthweight tags, tagger date for annotated tags.\n\n\n\n## License\n\nCopyright 2013 Scott González. Released under the terms of the MIT license.\n\n---\n\nSupport this project by [donating on Gittip](https://www.gittip.com/scottgonzalez/).\n", + "readmeFilename": "README.md", + "_id": "git-tools@0.1.0", + "_from": "git-tools@0.1.0" +} diff --git a/node_modules/changelogplease/package.json b/node_modules/changelogplease/package.json new file mode 100644 index 0000000..cd018ba --- /dev/null +++ b/node_modules/changelogplease/package.json @@ -0,0 +1,41 @@ +{ + "name": "changelogplease", + "version": "1.0.1", + "description": "Generate changelogs from git commit messages", + "author": { + "name": "Scott González", + "email": "scott.gonzalez@gmail.com", + "url": "http://scottgonzalez.com" + }, + "license": "MIT", + "main": "index.js", + "homepage": "https://github.com/scottgonzalez/changelogplease", + "repository": { + "type": "git", + "url": "git://github.com/scottgonzalez/changelogplease.git" + }, + "bugs": { + "url": "https://github.com/scottgonzalez/changelogplease/issues" + }, + "keywords": [ + "changelog", + "release" + ], + "scripts": { + "test": "nodeunit tests" + }, + "dependencies": { + "git-tools": "0.1.0" + }, + "devDependencies": { + "nodeunit": "0.8.6" + }, + "readme": "# Changelog, please\n\nGenerate changelogs from git commit messages using node.js. The generated changelogs are written in markdown.\n\nSupport this project by [donating on Gittip](https://www.gittip.com/scottgonzalez/).\n\n## Installation\n\n```\nnpm install changelogplease\n```\n\n## Usage\n\n```javascript\nvar changelog = require( \"changelogplease\" );\nvar parsed = changelog({\n\tticketUrl: \"https://github.com/scottgonzalez/changelogplease/issues/{id}\",\n\tcommitUrl: \"https://github.com/scottgonzalez/changelogplease/commit/{id}\",\n\trepo: \"/path/to/repo\",\n\tcommittish: \"1.2.3..1.2.4\"\n});\n```\n\n## API\n\n### changelog( options, callback )\n\n* `options` (Object): Options for creating the changelog.\n * `ticketUrl` (String): Template for ticket/issue URLs; `{id}` will be replaced with the ticket id.\n * `commitUrl (String)`: Template for commit URLs; `{id}` will be replaced with the commit hash.\n * `repo` (String): Path to the repository.\n * `committish` (String): The range of commits for the changelog.\n* `callback` (Function; `function( error, log )`): Function to invoke after generating the changelog.\n * `log` (String): Generated changelog, written in markdown.\n\n### Changelog\n\n`changelog( options, callback )` is a shorthand for the following:\n\n```js\nvar Changelog = require( \"changelogplease\" ).Changelog;\nvar instance = new Changelog( options );\ninstance.parse( callback );\n```\n\nChangelog generation is tailored to a specific format based on the needs of the various jQuery\nprojects. However, the `Changelog` constructor and prototype are exposed to allow flexibility.\nBe aware that these methods are not currently documented because they may change. Feel free to\nsubmit [feature requests](https://github.com/scottgonzalez/changelogplease/issues/new) if you don't\nfeel comfortable hacking in your own changes (or even if you do).\n\n\n## License\n\nCopyright 2014 Scott González. Released under the terms of the MIT license.\n\n---\n\nSupport this project by [donating on Gittip](https://www.gittip.com/scottgonzalez/).\n", + "readmeFilename": "README.md", + "_id": "changelogplease@1.0.1", + "dist": { + "shasum": "2953e41245f475cd0a3d66b2861e3344166d6c30" + }, + "_from": "changelogplease@1.0.1", + "_resolved": "https://registry.npmjs.org/changelogplease/-/changelogplease-1.0.1.tgz" +} diff --git a/package.json b/package.json index 0926c46..000ec82 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "dependencies": { + "changelogplease": "1.0.1", "colors": "0.6.2", "semver": "2.2.1", "shelljs": "0.2.6",