├── .gitignore ├── .pre-commit-config.yaml ├── .pre-commit-hooks.yaml ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── authors.rst ├── conf.py ├── contributing.rst ├── history.rst ├── index.rst ├── installation.rst ├── make.bat ├── readme.rst └── usage.rst ├── giticket ├── __init__.py └── giticket.py ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── tests └── test_giticket.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | *.egg-info/ 4 | .tox/ 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v3.4.0 6 | hooks: 7 | - id: trailing-whitespace 8 | stages: [commit] 9 | - id: end-of-file-fixer 10 | stages: [commit] 11 | - id: check-yaml 12 | stages: [commit] 13 | - id: check-added-large-files 14 | stages: [commit] 15 | - repo: https://github.com/pycqa/flake8 16 | rev: '3.9.1' 17 | hooks: 18 | - id: flake8 19 | stages: [commit] 20 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - id: giticket 3 | name: giticket 4 | entry: giticket 5 | language: python 6 | stages: [commit-msg] 7 | description: Utility to prepend your commits with info from your branch. 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.org 2 | 3 | language: python 4 | python: 5 | - 3.6 6 | - 3.5 7 | - 2.7 8 | 9 | # Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 10 | install: pip install -U tox-travis 11 | 12 | # Command to run tests, e.g. python setup.py test 13 | script: tox 14 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Credits 2 | ------- 3 | 4 | Development Lead 5 | ~~~~~~~~~~~~~~~~ 6 | 7 | * Milind Shakya - https://github.com/milin 8 | 9 | Contributors 10 | ~~~~~~~~~~~~ 11 | 12 | * Scott Peshak - https://github.com/speshak 13 | * kir0ul - https://github.com/kir0ul 14 | * Daniil Penkin - https://github.com/detouched 15 | * Joseph S. Tate - https://github.com/josephtate 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/milin/giticket/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | giticket could always use more documentation, whether as part of the 42 | official giticket docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/milin/giticket/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `giticket` for local development. 61 | 62 | 1. Fork the `giticket` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/giticket.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv giticket 70 | $ cd giticket/ 71 | $ python setup.py develop 72 | 73 | 4. Set up pre-commit:: 74 | 75 | $ pip install pre-commit 76 | $ pre-commit install 77 | $ pre-commit install --hook-type commit-msg 78 | 79 | 5. Create a branch for local development:: 80 | 81 | $ git checkout -b name-of-your-bugfix-or-feature 82 | 83 | Now you can make your changes locally. 84 | 85 | 6. When you're done making changes, check that your changes pass flake8 and the 86 | tests, including testing other Python versions with tox:: 87 | 88 | $ flake8 giticket tests # flake8 will also run on commit 89 | $ python setup.py test or py.test 90 | $ tox 91 | 92 | To get flake8 and tox, just pip install them into your virtualenv. 93 | 94 | 7. Commit your changes and push your branch to GitHub:: 95 | 96 | $ git add . 97 | $ git commit -m "Your detailed description of your changes." 98 | $ git push origin name-of-your-bugfix-or-feature 99 | 100 | 8. Submit a pull request through the GitHub website. 101 | 102 | Pull Request Guidelines 103 | ----------------------- 104 | 105 | Before you submit a pull request, check that it meets these guidelines: 106 | 107 | 1. The pull request should include tests. 108 | 2. If the pull request adds functionality, the docs should be updated. Put 109 | your new functionality into a function with a docstring, and add the 110 | feature to the list in README.rst. 111 | 3. The pull request should work for Python 2.7, 3.4, 3.5 and 3.6, and for PyPy. Check 112 | https://travis-ci.org/milin/giticket/pull_requests 113 | and make sure that the tests pass for all supported Python versions. 114 | 115 | Tips 116 | ---- 117 | 118 | To run a subset of tests:: 119 | 120 | $ py.test tests.test_giticket 121 | 122 | 123 | Deploying 124 | --------- 125 | 126 | A reminder for the maintainers on how to deploy. 127 | Make sure all your changes are committed (including an entry in HISTORY.rst). 128 | Then run:: 129 | 130 | $ bumpversion patch # possible: major / minor / patch 131 | $ git push 132 | $ git push --tags 133 | 134 | Travis will then deploy to PyPI if tests pass. 135 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.1.5 (2019-04-23) 6 | ------------------ 7 | 8 | * Add custom commit message template to be passed in. 9 | 10 | 11 | 0.1.0 (2019-01-02) 12 | ------------------ 13 | 14 | * First release on PyPI. 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019, Milind Shakya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CONTRIBUTING.rst 2 | include HISTORY.rst 3 | include LICENSE 4 | include README.rst 5 | 6 | recursive-include tests * 7 | recursive-exclude * __pycache__ 8 | recursive-exclude * *.py[co] 9 | 10 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | try: 8 | from urllib import pathname2url 9 | except: 10 | from urllib.request import pathname2url 11 | 12 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 13 | endef 14 | export BROWSER_PYSCRIPT 15 | 16 | define PRINT_HELP_PYSCRIPT 17 | import re, sys 18 | 19 | for line in sys.stdin: 20 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 21 | if match: 22 | target, help = match.groups() 23 | print("%-20s %s" % (target, help)) 24 | endef 25 | export PRINT_HELP_PYSCRIPT 26 | 27 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 28 | 29 | help: 30 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 31 | 32 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 33 | 34 | clean-build: ## remove build artifacts 35 | rm -fr build/ 36 | rm -fr dist/ 37 | rm -fr .eggs/ 38 | find . -name '*.egg-info' -exec rm -fr {} + 39 | find . -name '*.egg' -exec rm -f {} + 40 | 41 | clean-pyc: ## remove Python file artifacts 42 | find . -name '*.pyc' -exec rm -f {} + 43 | find . -name '*.pyo' -exec rm -f {} + 44 | find . -name '*~' -exec rm -f {} + 45 | find . -name '__pycache__' -exec rm -fr {} + 46 | 47 | clean-test: ## remove test and coverage artifacts 48 | rm -fr .tox/ 49 | rm -f .coverage 50 | rm -fr htmlcov/ 51 | rm -fr .pytest_cache 52 | 53 | lint: ## check style with flake8 54 | flake8 giticket tests 55 | 56 | test: ## run tests quickly with the default Python 57 | py.test 58 | 59 | test-all: ## run tests on every Python version with tox 60 | tox 61 | 62 | coverage: ## check code coverage quickly with the default Python 63 | coverage run --source giticket -m pytest 64 | coverage report -m 65 | coverage html 66 | $(BROWSER) htmlcov/index.html 67 | 68 | docs: ## generate Sphinx HTML documentation, including API docs 69 | rm -f docs/giticket.rst 70 | rm -f docs/modules.rst 71 | sphinx-apidoc -o docs/ giticket 72 | $(MAKE) -C docs clean 73 | $(MAKE) -C docs html 74 | $(BROWSER) docs/_build/html/index.html 75 | 76 | servedocs: docs ## compile the docs watching for changes 77 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 78 | 79 | release: dist ## package and upload a release 80 | twine upload dist/* 81 | 82 | dist: clean ## builds source and wheel package 83 | python setup.py sdist 84 | python setup.py bdist_wheel 85 | ls -l dist 86 | 87 | install: clean ## install the package to the active Python's site-packages 88 | python setup.py install 89 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | giticket 3 | ======== 4 | 5 | .. image:: https://api.star-history.com/svg?repos=milin/giticket&type=Date)](https://star-history.com/#milin/giticket&Date 6 | 7 | .. image:: https://img.shields.io/pypi/v/giticket.svg 8 | :target: https://pypi.python.org/pypi/giticket 9 | 10 | .. image:: https://travis-ci.com/milin/giticket.svg?branch=master 11 | :target: https://travis-ci.org/milin/giticket 12 | 13 | .. image:: https://readthedocs.org/projects/giticket/badge/?version=latest 14 | :target: https://giticket.readthedocs.io/en/latest/?badge=latest 15 | :alt: Documentation Status 16 | 17 | 18 | 19 | 20 | Auto add ticket info to your git commits. 21 | 22 | 23 | * Free software: MIT license 24 | * Documentation: https://giticket.readthedocs.io. 25 | 26 | 27 | Features 28 | -------- 29 | 30 | This hook saves developers time by prepending ticket numbers to commit-msgs. 31 | For this to work the following two conditions must be met: 32 | - The ticket format regex specified must match, if the regex is passed in. 33 | - Unless you use ``regex_match`` mode, the branch name format must be _ 34 | 35 | For e.g. if you name your branch ``JIRA-1234_awesome_feature`` and commit ``Fix some bug``, the commit will be updated to ``JIRA-1234 Fix some bug``. 36 | 37 | Pass ``--regex=`` or update ``args: [--regex=]`` in your .yaml file if you have custom ticket regex. 38 | By default it's ``[A-Z]+-\d+``. 39 | 40 | Pass ``--format=`` or update ``args: [--format=]`` in your .yaml file if you have custom message replacement. 41 | By default it's ``'{ticket} {commit_msg}``, where ``ticket`` is replaced with the found ticket number and ``commit_msg`` is replaced with the original commit message. 42 | 43 | Pass ``--mode=`` or update ``args: [--mode=regex_match]`` in your .yaml file to extract ticket by the regex rather than relying on branch name convention. 44 | With this mode you can also make use of ``{tickets}`` placeholder in ``format`` argument value to put multiple comma-separated tickets in the commit message in case your branch contains more than one ticket. 45 | 46 | It is best used along with pre-commit_. You can use it along with pre-commit by adding the following hook in your ``.pre-commit-config.yaml`` file. 47 | 48 | :: 49 | 50 | repos: 51 | - repo: https://github.com/milin/giticket 52 | rev: v1.3 53 | hooks: 54 | - id: giticket 55 | args: ['--regex=PROJ-[0-9]', '--format={ticket} {commit_msg}'] # Optional 56 | 57 | 58 | You need to have precommit setup to use this hook. 59 | -------------------------------------------------- 60 | Install Pre-commit and the commit-msg hook-type. 61 | 62 | 63 | :: 64 | 65 | pip install pre-commit 66 | pre-commit install 67 | pre-commit install --hook-type commit-msg 68 | 69 | 70 | .. _pre-commit: https://pre-commit.com/ 71 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = giticket 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # giticket documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another 17 | # directory, add these directories to sys.path here. If the directory is 18 | # relative to the documentation root, use os.path.abspath to make it 19 | # absolute, like shown here. 20 | # 21 | import os 22 | import sys 23 | sys.path.insert(0, os.path.abspath('..')) 24 | 25 | import giticket 26 | 27 | # -- General configuration --------------------------------------------- 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # 31 | # needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 35 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = '.rst' 45 | 46 | # The master toctree document. 47 | master_doc = 'index' 48 | 49 | # General information about the project. 50 | project = u'giticket' 51 | copyright = u"2019, Milind Shakya" 52 | author = u"Milind Shakya" 53 | 54 | # The version info for the project you're documenting, acts as replacement 55 | # for |version| and |release|, also used in various other places throughout 56 | # the built documents. 57 | # 58 | # The short X.Y version. 59 | version = giticket.__version__ 60 | # The full version, including alpha/beta/rc tags. 61 | release = giticket.__version__ 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = None 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | # This patterns also effect to html_static_path and html_extra_path 73 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = 'sphinx' 77 | 78 | # If true, `todo` and `todoList` produce output, else they produce nothing. 79 | todo_include_todos = False 80 | 81 | 82 | # -- Options for HTML output ------------------------------------------- 83 | 84 | # The theme to use for HTML and HTML Help pages. See the documentation for 85 | # a list of builtin themes. 86 | # 87 | html_theme = 'alabaster' 88 | 89 | # Theme options are theme-specific and customize the look and feel of a 90 | # theme further. For a list of options available for each theme, see the 91 | # documentation. 92 | # 93 | # html_theme_options = {} 94 | 95 | # Add any paths that contain custom static files (such as style sheets) here, 96 | # relative to this directory. They are copied after the builtin static files, 97 | # so a file named "default.css" will overwrite the builtin "default.css". 98 | html_static_path = ['_static'] 99 | 100 | 101 | # -- Options for HTMLHelp output --------------------------------------- 102 | 103 | # Output file base name for HTML help builder. 104 | htmlhelp_basename = 'giticketdoc' 105 | 106 | 107 | # -- Options for LaTeX output ------------------------------------------ 108 | 109 | latex_elements = { 110 | # The paper size ('letterpaper' or 'a4paper'). 111 | # 112 | # 'papersize': 'letterpaper', 113 | 114 | # The font size ('10pt', '11pt' or '12pt'). 115 | # 116 | # 'pointsize': '10pt', 117 | 118 | # Additional stuff for the LaTeX preamble. 119 | # 120 | # 'preamble': '', 121 | 122 | # Latex figure (float) alignment 123 | # 124 | # 'figure_align': 'htbp', 125 | } 126 | 127 | # Grouping the document tree into LaTeX files. List of tuples 128 | # (source start file, target name, title, author, documentclass 129 | # [howto, manual, or own class]). 130 | latex_documents = [ 131 | (master_doc, 'giticket.tex', 132 | u'giticket Documentation', 133 | u'Milind Shakya', 'manual'), 134 | ] 135 | 136 | 137 | # -- Options for manual page output ------------------------------------ 138 | 139 | # One entry per manual page. List of tuples 140 | # (source start file, name, description, authors, manual section). 141 | man_pages = [ 142 | (master_doc, 'giticket', 143 | u'giticket Documentation', 144 | [author], 1) 145 | ] 146 | 147 | 148 | # -- Options for Texinfo output ---------------------------------------- 149 | 150 | # Grouping the document tree into Texinfo files. List of tuples 151 | # (source start file, target name, title, author, 152 | # dir menu entry, description, category) 153 | texinfo_documents = [ 154 | (master_doc, 'giticket', 155 | u'giticket Documentation', 156 | author, 157 | 'giticket', 158 | 'One line description of project.', 159 | 'Miscellaneous'), 160 | ] 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to giticket's documentation! 2 | ====================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | readme 9 | installation 10 | usage 11 | modules 12 | contributing 13 | history 14 | authors 15 | 16 | Indices and tables 17 | ================== 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Stable release 9 | -------------- 10 | 11 | To install giticket, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install giticket 16 | 17 | This is the preferred method to install giticket, as it will always install the most recent stable release. 18 | 19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide 20 | you through the process. 21 | 22 | .. _pip: https://pip.pypa.io 23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ 24 | 25 | 26 | From sources 27 | ------------ 28 | 29 | The sources for giticket can be downloaded from the `Github repo`_. 30 | 31 | You can either clone the public repository: 32 | 33 | .. code-block:: console 34 | 35 | $ git clone git://github.com/milin/giticket 36 | 37 | Or download the `tarball`_: 38 | 39 | .. code-block:: console 40 | 41 | $ curl -OL https://github.com/milin/giticket/tarball/master 42 | 43 | Once you have a copy of the source, you can install it with: 44 | 45 | .. code-block:: console 46 | 47 | $ python setup.py install 48 | 49 | 50 | .. _Github repo: https://github.com/milin/giticket 51 | .. _tarball: https://github.com/milin/giticket/tarball/master 52 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=giticket 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | To use giticket in a project:: 6 | 7 | import giticket 8 | -------------------------------------------------------------------------------- /giticket/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Top-level package for giticket.""" 4 | 5 | __author__ = """Milind Shakya""" 6 | __email__ = 'sh.milind@gmail.com' 7 | __version__ = '0.1.0' 8 | -------------------------------------------------------------------------------- /giticket/giticket.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | from __future__ import unicode_literals 4 | 5 | import argparse 6 | import io 7 | import re 8 | import subprocess 9 | import sys 10 | 11 | import six 12 | 13 | underscore_split_mode = 'underscore_split' 14 | regex_match_mode = 'regex_match' 15 | 16 | 17 | def update_commit_message(filename, regex, mode, format_string): 18 | with io.open(filename, 'r+') as fd: 19 | contents = fd.readlines() 20 | commit_msg = contents[0].rstrip('\r\n') 21 | # Check if we can grab ticket info from branch name. 22 | branch = get_branch_name() 23 | 24 | # Bail if commit message already contains tickets 25 | if any(re.search(regex, content) for content in contents): 26 | return 27 | 28 | tickets = re.findall(regex, branch) 29 | if tickets: 30 | if mode == underscore_split_mode: 31 | tickets = [branch.split(six.text_type('_'))[0]] 32 | tickets = [t.strip() for t in tickets] 33 | 34 | new_commit_msg = format_string.format( 35 | ticket=tickets[0], tickets=', '.join(tickets), 36 | commit_msg=commit_msg 37 | ) 38 | 39 | contents[0] = six.text_type(new_commit_msg + "\n") 40 | fd.seek(0) 41 | fd.writelines(contents) 42 | fd.truncate() 43 | 44 | 45 | def get_branch_name(): 46 | # Only git support for right now. 47 | return subprocess.check_output( 48 | [ 49 | 'git', 50 | 'rev-parse', 51 | '--abbrev-ref', 52 | 'HEAD', 53 | ], 54 | ).decode('UTF-8') 55 | 56 | 57 | def main(argv=None): 58 | """This hook saves developers time by prepending ticket numbers to commit-msgs. 59 | For this to work the following two conditions must be met: 60 | 61 | - The ticket format regex specified must match. 62 | - The branch name format must be _ 63 | """ 64 | parser = argparse.ArgumentParser() 65 | parser.add_argument('filenames', nargs='+') 66 | parser.add_argument('--regex') 67 | parser.add_argument('--format') 68 | parser.add_argument('--mode', nargs='?', const=underscore_split_mode, 69 | default=underscore_split_mode, 70 | choices=[underscore_split_mode, regex_match_mode]) 71 | args = parser.parse_args(argv) 72 | regex = args.regex or r'[A-Z]+-\d+' # noqa 73 | format_string = args.format or '{ticket} {commit_msg}' # noqa 74 | update_commit_message(args.filenames[0], regex, args.mode, format_string) 75 | 76 | 77 | if __name__ == '__main__': 78 | sys.exit(main()) 79 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip==19.2 2 | bumpversion==0.5.3 3 | wheel==0.32.1 4 | watchdog==0.9.0 5 | flake8==3.5.0 6 | tox==3.5.2 7 | coverage==4.5.1 8 | Sphinx==1.8.1 9 | twine==1.12.1 10 | six 11 | mock 12 | 13 | pytest==3.8.2 14 | pytest-runner==4.2 15 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:giticket/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | max-line-length = 120 20 | ignore = E501 21 | 22 | 23 | [aliases] 24 | # Define setup.py command aliases here 25 | test = pytest 26 | 27 | [tool:pytest] 28 | collect_ignore = ['setup.py'] 29 | 30 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """The setup script.""" 5 | 6 | from setuptools import setup, find_packages 7 | 8 | with open('README.rst') as readme_file: 9 | readme = readme_file.read() 10 | 11 | with open('HISTORY.rst') as history_file: 12 | history = history_file.read() 13 | 14 | requirements = [ 15 | 'pre-commit', 16 | 'six' 17 | ] 18 | 19 | setup_requirements = ['pytest-runner', ] 20 | 21 | test_requirements = ['pytest', ] 22 | 23 | setup( 24 | author="Milind Shakya", 25 | author_email='sh.milind@gmail.com', 26 | classifiers=[ 27 | 'Development Status :: 2 - Pre-Alpha', 28 | 'Intended Audience :: Developers', 29 | 'License :: OSI Approved :: MIT License', 30 | 'Natural Language :: English', 31 | 'Programming Language :: Python :: 2.7', 32 | 'Programming Language :: Python :: 3.5', 33 | 'Programming Language :: Python :: 3.6', 34 | 'Programming Language :: Python :: 3.7', 35 | ], 36 | description="Auto add ticket info to your git commits.", 37 | install_requires=requirements, 38 | license="MIT license", 39 | long_description=readme + '\n\n' + history, 40 | include_package_data=True, 41 | keywords='giticket', 42 | name='giticket', 43 | packages=find_packages(include=['giticket']), 44 | setup_requires=setup_requirements, 45 | test_suite='tests', 46 | tests_require=test_requirements, 47 | url='https://github.com/milin/giticket', 48 | version='0.1.5', 49 | zip_safe=False, 50 | entry_points={ 51 | 'console_scripts': [ 52 | 'giticket = giticket.giticket:main', 53 | ] 54 | 55 | } 56 | ) 57 | -------------------------------------------------------------------------------- /tests/test_giticket.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import unicode_literals 3 | 4 | import mock 5 | import pytest 6 | import six 7 | 8 | from giticket.giticket import get_branch_name 9 | from giticket.giticket import main 10 | from giticket.giticket import update_commit_message 11 | 12 | TESTING_MODULE = 'giticket.giticket' 13 | 14 | COMMIT_MESSAGE = 'Test commit message\n\nFoo bar\nBaz qux' 15 | 16 | 17 | @pytest.mark.parametrize('msg', ( 18 | 'Test ABC-1 message', 19 | 'ABC-2 Test message', 20 | 'Test message ABC-3', 21 | )) 22 | @mock.patch(TESTING_MODULE + '.get_branch_name') 23 | def test_update_commit_message_no_modification(mock_branch_name, msg, tmpdir): 24 | mock_branch_name.return_value = 'JIRA-1234_new_feature' 25 | path = tmpdir.join('file.txt') 26 | path.write(msg) 27 | update_commit_message(six.text_type(path), r'[A-Z]+-\d+', 28 | 'underscore_split', '{ticket} {commit_msg}') 29 | # Message should remain intact as it contains some ticket 30 | assert path.read() == msg 31 | 32 | 33 | @pytest.mark.parametrize('test_data', ( 34 | ('JIRA-1234', 'JIRA-1234'), 35 | ('JIRA-1234_bar', 'JIRA-1234'), 36 | ('foo-JIRA-1234_bar', 'foo-JIRA-1234'), 37 | ('foo/JIRA-1234-bar', 'foo/JIRA-1234-bar'), 38 | ('foo_JIRA-1234_bar', 'foo'), 39 | )) 40 | @mock.patch(TESTING_MODULE + '.get_branch_name') 41 | def test_update_commit_message_underscore_split_mode(mock_branch_name, 42 | test_data, tmpdir): 43 | mock_branch_name.return_value = test_data[0] 44 | path = tmpdir.join('file.txt') 45 | path.write(COMMIT_MESSAGE) 46 | update_commit_message(six.text_type(path), r'[A-Z]+-\d+', 47 | 'underscore_split', '{ticket}: {commit_msg}') 48 | assert path.read() == '{expected_ticket}: {message}'.format( 49 | expected_ticket=test_data[1], message=COMMIT_MESSAGE 50 | ) 51 | 52 | 53 | @pytest.mark.parametrize('branch_name', ( 54 | 'JIRA-1234', 55 | 'JIRA-1234_bar', 56 | 'foo_JIRA-1234_bar', 57 | 'foo-JIRA-1234-bar', 58 | 'foo/JIRA-1234-bar', 59 | 'fooJIRA-1234bar', 60 | 'foo/bar/JIRA-1234', 61 | )) 62 | @mock.patch(TESTING_MODULE + '.get_branch_name') 63 | def test_update_commit_message_regex_match_mode(mock_branch_name, 64 | branch_name, tmpdir): 65 | mock_branch_name.return_value = branch_name 66 | path = tmpdir.join('file.txt') 67 | path.write(COMMIT_MESSAGE) 68 | update_commit_message(six.text_type(path), r'[A-Z]+-\d+', 69 | 'regex_match', '{ticket}: {commit_msg}') 70 | assert path.read() == 'JIRA-1234: {message}'.format(message=COMMIT_MESSAGE) 71 | 72 | 73 | @pytest.mark.parametrize('test_data', ( 74 | ('JIRA-1234', 'JIRA-1234'), 75 | ('JIRA-1234-JIRA-239', 'JIRA-1234'), 76 | ('JIRA-239-JIRA-1234', 'JIRA-239'), 77 | )) 78 | @mock.patch(TESTING_MODULE + '.get_branch_name') 79 | def test_update_commit_message_multiple_ticket_first_selected(mock_branch_name, 80 | test_data, 81 | tmpdir): 82 | mock_branch_name.return_value = test_data[0] 83 | path = tmpdir.join('file.txt') 84 | path.write(COMMIT_MESSAGE) 85 | update_commit_message(six.text_type(path), r'[A-Z]+-\d+', 86 | 'regex_match', '{ticket}: {commit_msg}') 87 | assert path.read() == '{expected_ticket}: {message}'.format( 88 | expected_ticket=test_data[1], message=COMMIT_MESSAGE 89 | ) 90 | 91 | 92 | @pytest.mark.parametrize('test_data', ( 93 | ('JIRA-1234', 'JIRA-1234'), 94 | ('JIRA-1234-JIRA-239', 'JIRA-1234, JIRA-239'), 95 | ('JIRA-239-JIRA-1234', 'JIRA-239, JIRA-1234'), 96 | )) 97 | @mock.patch(TESTING_MODULE + '.get_branch_name') 98 | def test_update_commit_message_multiple_ticket_all_selected(mock_branch_name, 99 | test_data, tmpdir): 100 | mock_branch_name.return_value = test_data[0] 101 | path = tmpdir.join('file.txt') 102 | path.write(COMMIT_MESSAGE) 103 | update_commit_message(six.text_type(path), r'[A-Z]+-\d+', 104 | 'regex_match', '{tickets}: {commit_msg}') 105 | assert path.read() == '{expected_tickets}: {message}'.format( 106 | expected_tickets=test_data[1], message=COMMIT_MESSAGE 107 | ) 108 | 109 | 110 | @pytest.mark.parametrize('msg', ( 111 | "\n", 112 | "a bogus message\n" 113 | """A message 114 | 115 | With a description\n""", 116 | )) 117 | @mock.patch(TESTING_MODULE + '.get_branch_name') 118 | def test_ci_message_with_nl_regex_match_mode(mock_branch_name, msg, tmpdir): 119 | first_line = msg.split('\n')[0].strip() 120 | mock_branch_name.return_value = "JIRA-239_mock_branch" 121 | path = tmpdir.join('file.txt') 122 | path.write(msg) 123 | update_commit_message(six.text_type(path), r'[A-Z]+-\d+', 124 | 'regex_match', '{commit_msg} - {ticket}') 125 | assert path.read().split('\n')[0] == "{first_line} - {ticket}".format(first_line=first_line, ticket="JIRA-239") 126 | 127 | 128 | @pytest.mark.parametrize('msg', ( 129 | """A descriptive header 130 | 131 | A descriptive body. 132 | 133 | Issue: 2397""", 134 | )) 135 | @mock.patch(TESTING_MODULE + '.get_branch_name') 136 | def test_update_commit_message_no_modification_if_ticket_in_body(mock_branch_name, msg, tmpdir): 137 | mock_branch_name.return_value = "team_name/2397/a_nice_feature" 138 | path = tmpdir.join('file.txt') 139 | path.write(msg) 140 | update_commit_message(six.text_type(path), r'\d{4,}', 141 | 'regex_match', '{commit_msg}\n\nIssue: {ticket}') 142 | assert path.read() == msg 143 | 144 | 145 | @mock.patch(TESTING_MODULE + '.subprocess') 146 | def test_get_branch_name(mock_subprocess): 147 | get_branch_name() 148 | mock_subprocess.check_output.assert_called_once_with( 149 | [ 150 | 'git', 151 | 'rev-parse', 152 | '--abbrev-ref', 153 | 'HEAD', 154 | ], 155 | ) 156 | 157 | 158 | @mock.patch(TESTING_MODULE + '.argparse') 159 | @mock.patch(TESTING_MODULE + '.update_commit_message') 160 | def test_main(mock_update_commit_message, mock_argparse): 161 | mock_args = mock.Mock() 162 | mock_args.filenames = ['foo.txt'] 163 | mock_args.regex = None 164 | mock_args.format = None 165 | mock_args.mode = 'underscore_split' 166 | mock_argparse.ArgumentParser.return_value.parse_args.return_value = mock_args 167 | main() 168 | mock_update_commit_message.assert_called_once_with('foo.txt', r'[A-Z]+-\d+', 169 | 'underscore_split', 170 | '{ticket} {commit_msg}') 171 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27, py34, py35, py36, flake8 3 | 4 | [travis] 5 | python = 6 | 3.6: py36 7 | 3.5: py35 8 | 2.7: py27 9 | 10 | [testenv:flake8] 11 | basepython = python 12 | deps = flake8 13 | commands = flake8 giticket 14 | 15 | [testenv] 16 | setenv = 17 | PYTHONPATH = {toxinidir} 18 | deps = 19 | -r{toxinidir}/requirements_dev.txt 20 | ; If you want to make tox run the tests with the same versions, create a 21 | ; requirements.txt with the pinned versions and uncomment the following line: 22 | ; -r{toxinidir}/requirements.txt 23 | commands = 24 | pip install -U pip 25 | py.test --basetemp={envtmpdir} 26 | --------------------------------------------------------------------------------