Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: commitizen/cz-cli
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v3.0.7
Choose a base ref
...
head repository: commitizen/cz-cli
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v3.1.0
Choose a head ref
  • 2 commits
  • 5 files changed
  • 2 contributors

Commits on Feb 22, 2019

  1. docs(readme): document behavior of npx git-cz in non-Commitizen frien…

    …dly repository (#612)
    
    Fixes #564
    fdlk authored and jimthedev committed Feb 22, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    515a57e View commit details

Commits on Apr 18, 2019

  1. feat(cli): Implement --hook option for git hooks integration (#615)

    fixes #448 (re #462)
    
    This pr allows project maintainers to enforce Commitizen generated commit messages as part of the workflow triggered by the `git commit` command. 
    
    * implements the `--hook` flag, which directs Commitizen to edit the `.git/COMMIT_EDITMSG` file directly. 
    * documents the use of the `--hook` flag in the `README`, both through traditional `git hooks` and `husky`.
    olgn authored and jimthedev committed Apr 18, 2019
    Copy the full SHA
    26533fc View commit details
Showing with 163 additions and 39 deletions.
  1. +33 −1 README.md
  2. +6 −1 src/cli/strategies/git-cz.js
  3. +68 −29 src/git/commit.js
  4. +39 −0 test/tests/commit.js
  5. +17 −8 test/tests/parsers.js
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ When you're working in a Commitizen friendly repository, you'll be prompted to f

[![Add and commit with Commitizen](https://github.com/commitizen/cz-cli/raw/master/meta/screenshots/add-commit.png)](https://github.com/commitizen/cz-cli/raw/master/meta/screenshots/add-commit.png)

If you're not working in a Commitizen friendly repository, then `git cz` will work just the same as `git commit`.
If you're not working in a Commitizen friendly repository, then `git cz` will work just the same as `git commit` but `npx git-cz` will use the [streamich/git-cz](https://github.com/streamich/git-cz) adapter.

#### Conventional commit messages as a global utility

@@ -141,6 +141,38 @@ This will be more convenient for your users because then if they want to do a co

> **NOTE:** if you are using `precommit` hooks thanks to something like `husky`, you will need to name your script some thing other than "commit" (e.g. "cm": "git-cz"). The reason is because npm-scripts has a "feature" where it automatically runs scripts with the name *prexxx* where *xxx* is the name of another script. In essence, npm and husky will run "precommit" scripts twice if you name the script "commit," and the work around is to prevent the npm-triggered *precommit* script.
#### Optional: Running Commitizen on `git commit`

This example shows how to incorporate Commitizen into the existing `git commit` workflow by using git hooks and the `--hook` command-line option. This is useful for project maintainers
who wish to ensure the proper commit format is enforced on contributions from those unfamiliar with Commitizen.

Once either of these methods is implemented, users running `git commit` will be presented with an interactive Commitizen session that helps them write useful commit messages.

> **NOTE:** This example assumes that the project has been set up to [use Commitizen locally](https://github.com/commitizen/cz-cli#optional-install-and-run-commitizen-locally).
##### Traditional git hooks

Update `.git/hooks/prepare-commit-msg` with the following code:

```
#!/bin/bash
exec < /dev/tty
node_modules/.bin/git-cz --hook
```

##### Husky
For `husky` users, add the following configuration to the project's `package.json`:

```
"husky": {
"hooks": {
"prepare-commit-msg": "exec < /dev/tty && git cz --hook",
}
}
```

> **Why `exec < /dev/tty`?** By default, git hooks are not interactive. This command allows the user to use their terminal to interact with Commitizen during the hook.
#### Congratulations your repo is Commitizen-friendly. Time to flaunt it!

Add the Commitizen-friendly badge to your README using the following markdown:
7 changes: 6 additions & 1 deletion src/cli/strategies/git-cz.js
Original file line number Diff line number Diff line change
@@ -35,6 +35,10 @@ function gitCz (rawGitArgs, environment, adapterConfig) {
// normal commit.
let retryLastCommit = rawGitArgs && rawGitArgs[0] === '--retry';

// Determine if we need to process this commit using interactive hook mode
// for husky prepare-commit-message
let hookMode = !(typeof parsedCommitizenArgs.hook === 'undefined');

let resolvedAdapterConfigPath = resolveAdapterPath(adapterConfig.path);
let resolvedAdapterRootPath = findRoot(resolvedAdapterConfigPath);
let prompter = getPrompter(adapterConfig.path);
@@ -57,7 +61,8 @@ function gitCz (rawGitArgs, environment, adapterConfig) {
disableAppendPaths: true,
emitData: true,
quiet: false,
retryLastCommit
retryLastCommit,
hookMode
}, function (error) {
if (error) {
throw error;
97 changes: 68 additions & 29 deletions src/git/commit.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { spawn } from 'child_process';

import path from 'path';

import { writeFileSync, openSync, closeSync } from 'fs';

import dedent from 'dedent';

export { commit };
@@ -9,35 +13,70 @@ export { commit };
*/
function commit (sh, repoPath, message, options, done) {
let called = false;
let args = ['commit', '-m', dedent(message), ...(options.args || [])];
let child = spawn('git', args, {
cwd: repoPath,
stdio: options.quiet ? 'ignore' : 'inherit'
});

child.on('error', function (err) {
if (called) return;
called = true;

done(err);
});

child.on('exit', function (code, signal) {
if (called) return;
called = true;

if (code) {
if (code === 128) {
console.warn(`
Git exited with code 128. Did you forget to run:
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
`)

// commit the file by spawning a git process, unless the --hook
// option was provided. in that case, write the commit message into
// the .git/COMMIT_EDITMSG file
if (!options.hookMode) {
let args = ['commit', '-m', dedent(message), ...(options.args || [])];
let child = spawn('git', args, {
cwd: repoPath,
stdio: options.quiet ? 'ignore' : 'inherit'
});

child.on('error', function (err) {
if (called) return;
called = true;

done(err);
});

child.on('exit', function (code, signal) {
if (called) return;
called = true;

if (code) {
if (code === 128) {
console.warn(`
Git exited with code 128. Did you forget to run:
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
`)
}
done(Object.assign(new Error(`git exited with error code ${code}`), { code, signal }));
} else {
done(null);
}
});
} else {
const commitFilePath = path.join(repoPath, '/.git/COMMIT_EDITMSG');
try {
const fd = openSync(commitFilePath, 'w');
try {
writeFileSync(fd, dedent(message));
done(null);
} catch (e) {
done(e);
} finally {
closeSync(fd);
}
} catch (e) {
// windows doesn't allow opening existing hidden files
// in 'w' mode... but it does let you do 'r+'!
try {
const fd = openSync(commitFilePath, 'r+');
try {
writeFileSync(fd, dedent(message));
done(null);
} catch (e) {
done(e);
} finally {
closeSync(fd);
}
} catch (e) {
done(e);
}
done(Object.assign(new Error(`git exited with error code ${code}`), { code, signal }));
} else {
done(null);
}
});
}
}
39 changes: 39 additions & 0 deletions test/tests/commit.js
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

import { expect } from 'chai';
import os from 'os';
import fs from 'fs';
import path from 'path';

import inquirer from 'inquirer';
@@ -274,6 +275,44 @@ ${(os.platform === 'win32') ? '' : ' '}

});

it('should save directly to .git/COMMIT_EDITMSG with --hook option', function (done) {

this.timeout(config.maxTimeout);

// SETUP
let dummyCommitMessage = `doggies!`;

let repoConfig = {
path: config.paths.endUserRepo,
files: {
dummyfile: {
contents: 'arf arf!',
filename: 'woof.txt'
}
}
};

// Describe an adapter
let adapterConfig = {
path: path.join(repoConfig.path, '/node_modules/cz-jira-smart-commit'),
npmName: 'cz-jira-smart-commit'
}

// Quick setup the repos, adapter, and grab a simple prompter
let prompter = quickPrompterSetup(sh, repoConfig, adapterConfig, dummyCommitMessage);
// TEST

// This is a successful commit directly to .git/COMMIT_EDITMSG
commitizenCommit(sh, inquirer, repoConfig.path, prompter, { disableAppendPaths: true, quiet: true, emitData: true, hookMode: true }, function (err) {
const commitFilePath = path.join(repoConfig.path, '.git/COMMIT_EDITMSG')
const commitFile = fs.openSync(commitFilePath, 'r+')
let commitContents = fs.readFileSync(commitFile, { flags: 'r+' }).toString();
fs.closeSync(commitFile);
expect(commitContents).to.have.string(dummyCommitMessage);
expect(err).to.be.a('null');
done();
});
});
});

afterEach(function () {
25 changes: 17 additions & 8 deletions test/tests/parsers.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,45 @@
/* eslint-env mocha */

import { expect } from 'chai';
import { parse } from '../../src/cli/parsers/git-cz';
import { gitCz as gitCzParser, commitizen as commitizenParser } from '../../src/cli/parsers';

describe('parsers', () => {
describe('git-cz', () => {
it('should parse --message "Hello, World!"', () => {
expect(parse(['--amend', '--message', 'Hello, World!'])).to.deep.equal(['--amend']);
expect(gitCzParser.parse(['--amend', '--message', 'Hello, World!'])).to.deep.equal(['--amend']);
});

it('should parse --message="Hello, World!"', () => {
expect(parse(['--amend', '--message=Hello, World!'])).to.deep.equal(['--amend']);
expect(gitCzParser.parse(['--amend', '--message=Hello, World!'])).to.deep.equal(['--amend']);
});

it('should parse -amwip', () => {
expect(parse(['-amwip'])).to.deep.equal(['-a']);
expect(gitCzParser.parse(['-amwip'])).to.deep.equal(['-a']);
});

it('should parse -am=wip', () => {
expect(parse(['-am=wip'])).to.deep.equal(['-a']);
expect(gitCzParser.parse(['-am=wip'])).to.deep.equal(['-a']);
});

it('should parse -am wip', () => {
expect(parse(['-am', 'wip'])).to.deep.equal(['-a']);
expect(gitCzParser.parse(['-am', 'wip'])).to.deep.equal(['-a']);
});

it('should parse -a -m wip -n', () => {
expect(parse(['-a', '-m', 'wip', '-n'])).to.deep.equal(['-a', '-n']);
expect(gitCzParser.parse(['-a', '-m', 'wip', '-n'])).to.deep.equal(['-a', '-n']);
});

it('should parse -a -m=wip -n', () => {
expect(parse(['-a', '-m=wip', '-n'])).to.deep.equal(['-a', '-n']);
expect(gitCzParser.parse(['-a', '-m=wip', '-n'])).to.deep.equal(['-a', '-n']);
});
});

describe('commitizen', () => {
it('should parse out the --amend option', () => {
expect(commitizenParser.parse(['--amend'])).to.deep.equal({ _: [], amend: true })
});
it('should parse out the --hook option', () => {
expect(commitizenParser.parse(['--hook'])).to.deep.equal({ _: [], hook: true })
});
});
});