├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .gitownrc ├── .pre-commit-config.yaml ├── .pre-commit-hooks.yaml ├── .pylintrc ├── .travis.yml ├── AUTHORS.rst ├── CODEOWNERS ├── 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 ├── gitown ├── __init__.py └── gitown.py ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py └── test_gitown.py └── tox.ini /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * gitown version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # IDE settings 105 | .vscode/ -------------------------------------------------------------------------------- /.gitownrc: -------------------------------------------------------------------------------- 1 | { 2 | "_milin@github.com": "@milin" 3 | } 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/milin/gitown 3 | rev: v0.1.2 4 | hooks: 5 | - id: gitown 6 | args: ['--ownership_threshold=25', '--codeowners_filename=CODEOWNERS'] 7 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - id: gitown 3 | name: gitown 4 | entry: gitown 5 | language: python 6 | stages: [commit] 7 | description: Keep your CODEOWNERS file up to date to streamline code review process. 8 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [FORMAT] 2 | max-line-length=120 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.com 2 | 3 | language: python 4 | python: 5 | - 3.8 6 | - 3.7 7 | - 3.6 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 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Milind Shakya 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | # These owners will be the default owners for everything in the repo. 4 | # * <@insert_github_username> 5 | # 6 | # Order is important. The last matching pattern has the most precedence. 7 | 8 | .pre-commit-config.yaml @milin 9 | -------------------------------------------------------------------------------- /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/gitown/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 | gitown could always use more documentation, whether as part of the 42 | official gitown 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/gitown/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 `gitown` for local development. 61 | 62 | 1. Fork the `gitown` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/gitown.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 gitown 70 | $ cd gitown/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the 80 | tests, including testing other Python versions with tox:: 81 | 82 | $ flake8 gitown tests 83 | $ python setup.py test or pytest 84 | $ tox 85 | 86 | To get flake8 and tox, just pip install them into your virtualenv. 87 | 88 | 6. Commit your changes and push your branch to GitHub:: 89 | 90 | $ git add . 91 | $ git commit -m "Your detailed description of your changes." 92 | $ git push origin name-of-your-bugfix-or-feature 93 | 94 | 7. Submit a pull request through the GitHub website. 95 | 96 | Pull Request Guidelines 97 | ----------------------- 98 | 99 | Before you submit a pull request, check that it meets these guidelines: 100 | 101 | 1. The pull request should include tests. 102 | 2. If the pull request adds functionality, the docs should be updated. Put 103 | your new functionality into a function with a docstring, and add the 104 | feature to the list in README.rst. 105 | 3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8, and for PyPy. Check 106 | https://travis-ci.com/milin/gitown/pull_requests 107 | and make sure that the tests pass for all supported Python versions. 108 | 109 | Tips 110 | ---- 111 | 112 | To run a subset of tests:: 113 | 114 | 115 | $ python -m unittest tests.test_gitown 116 | 117 | Deploying 118 | --------- 119 | 120 | A reminder for the maintainers on how to deploy. 121 | Make sure all your changes are committed (including an entry in HISTORY.rst). 122 | Then run:: 123 | 124 | $ bump2version patch # possible: major / minor / patch 125 | $ git push 126 | $ git push --tags 127 | 128 | Travis will then deploy to PyPI if tests pass. 129 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.1.0 (2020-11-07) 6 | ------------------ 7 | 8 | * First release on PyPI. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020, 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 AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | 7 | recursive-include tests * 8 | recursive-exclude * __pycache__ 9 | recursive-exclude * *.py[co] 10 | 11 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 12 | -------------------------------------------------------------------------------- /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 | from urllib.request import pathname2url 8 | 9 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 10 | endef 11 | export BROWSER_PYSCRIPT 12 | 13 | define PRINT_HELP_PYSCRIPT 14 | import re, sys 15 | 16 | for line in sys.stdin: 17 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 18 | if match: 19 | target, help = match.groups() 20 | print("%-20s %s" % (target, help)) 21 | endef 22 | export PRINT_HELP_PYSCRIPT 23 | 24 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 25 | 26 | help: 27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 28 | 29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 30 | 31 | clean-build: ## remove build artifacts 32 | rm -fr build/ 33 | rm -fr dist/ 34 | rm -fr .eggs/ 35 | find . -name '*.egg-info' -exec rm -fr {} + 36 | find . -name '*.egg' -exec rm -f {} + 37 | 38 | clean-pyc: ## remove Python file artifacts 39 | find . -name '*.pyc' -exec rm -f {} + 40 | find . -name '*.pyo' -exec rm -f {} + 41 | find . -name '*~' -exec rm -f {} + 42 | find . -name '__pycache__' -exec rm -fr {} + 43 | 44 | clean-test: ## remove test and coverage artifacts 45 | rm -fr .tox/ 46 | rm -f .coverage 47 | rm -fr htmlcov/ 48 | rm -fr .pytest_cache 49 | 50 | lint: ## check style with flake8 51 | flake8 gitown tests 52 | 53 | test: ## run tests quickly with the default Python 54 | python setup.py test 55 | 56 | test-all: ## run tests on every Python version with tox 57 | tox 58 | 59 | coverage: ## check code coverage quickly with the default Python 60 | coverage run --source gitown setup.py test 61 | coverage report -m 62 | coverage html 63 | $(BROWSER) htmlcov/index.html 64 | 65 | docs: ## generate Sphinx HTML documentation, including API docs 66 | rm -f docs/gitown.rst 67 | rm -f docs/modules.rst 68 | sphinx-apidoc -o docs/ gitown 69 | $(MAKE) -C docs clean 70 | $(MAKE) -C docs html 71 | $(BROWSER) docs/_build/html/index.html 72 | 73 | servedocs: docs ## compile the docs watching for changes 74 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 75 | 76 | release: dist ## package and upload a release 77 | twine upload dist/* 78 | 79 | dist: clean ## builds source and wheel package 80 | python setup.py sdist 81 | python setup.py bdist_wheel 82 | ls -l dist 83 | 84 | install: clean ## install the package to the active Python's site-packages 85 | python setup.py install 86 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | gitown 3 | ====== 4 | 5 | 6 | .. image:: https://img.shields.io/pypi/v/gitown.svg 7 | :target: https://pypi.python.org/pypi/gitown 8 | 9 | .. image:: https://img.shields.io/travis/milin/gitown.svg 10 | :target: https://travis-ci.com/milin/gitown 11 | 12 | .. image:: https://readthedocs.org/projects/gitown/badge/?version=latest 13 | :target: https://gitown.readthedocs.io/en/latest/?badge=latest 14 | :alt: Documentation Status 15 | 16 | 17 | Keep your github CODEOWNERS file up to date. 18 | 19 | 20 | * Free software: MIT license 21 | * Documentation: https://gitown.readthedocs.io. 22 | 23 | 24 | Features 25 | -------- 26 | 27 | * Keep your github CODEOWNERS_ file up to date. 28 | 29 | Why to keep CODEOWNERS_ file up to date? 30 | 31 | - Allows for automated code reviewing process and integrates with github auto selecting the reviewer most likely to know the most about the change list. 32 | - Can integrate into slack once code reviewer is auto assigned and helps manage code review process. 33 | - Helps in introducing standard process during team code-reviews. 34 | 35 | 36 | Details 37 | ------- 38 | * If a committer exceeds the ``ownership_threshold`` percentage in a file, then the committer is added to the ``CODEOWNERS`` file. 39 | * Default ``ownership_threshold`` is set to 25%. You can change it by passing ``--ownership_threshold``. Meaning, if a person has 25% of more changes in a file, he is considered a codeowner, and the CODEOWNER file is updated accordingly. 40 | * Can be used as a ``pre-commit`` hook. 41 | 42 | **IMPORTANT** 43 | 44 | * You need to create a gitownrc_ file and have a mapping of github emails to github usernames in order to use this tool. 45 | * `gitown` reads that file and only those users are considered to be added to the ``CODEOWNERS`` file. 46 | 47 | 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. 48 | 49 | :: 50 | 51 | repos: 52 | - repo: https://github.com/milin/gitown 53 | rev: v0.1.7 54 | hooks: 55 | - id: gitown 56 | args: ['--ownership_threshold=25', '--codeowners_filename=CODEOWNERS'] # Optional 57 | 58 | 59 | You need to have precommit setup to use this hook. 60 | -------------------------------------------------- 61 | Install Pre-commit and the commit-msg hook-type. 62 | 63 | 64 | :: 65 | 66 | pip install pre-commit 67 | pre-commit install 68 | 69 | 70 | .. _pre-commit: https://pre-commit.com/ 71 | .. _gitownrc: https://github.com/milin/gitown/blob/master/.gitownrc 72 | .. _CODEOWNERS: https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/about-code-owners 73 | -------------------------------------------------------------------------------- /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 = gitown 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 | # 3 | # gitown documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another 16 | # directory, add these directories to sys.path here. If the directory is 17 | # relative to the documentation root, use os.path.abspath to make it 18 | # absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | import gitown 25 | 26 | # -- General configuration --------------------------------------------- 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | # 30 | # needs_sphinx = '1.0' 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 34 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # The suffix(es) of source filenames. 40 | # You can specify multiple suffix as a list of string: 41 | # 42 | # source_suffix = ['.rst', '.md'] 43 | source_suffix = '.rst' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = 'gitown' 50 | copyright = "2020, Milind Shakya" 51 | author = "Milind Shakya" 52 | 53 | # The version info for the project you're documenting, acts as replacement 54 | # for |version| and |release|, also used in various other places throughout 55 | # the built documents. 56 | # 57 | # The short X.Y version. 58 | version = gitown.__version__ 59 | # The full version, including alpha/beta/rc tags. 60 | release = gitown.__version__ 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | # This patterns also effect to html_static_path and html_extra_path 72 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 73 | 74 | # The name of the Pygments (syntax highlighting) style to use. 75 | pygments_style = 'sphinx' 76 | 77 | # If true, `todo` and `todoList` produce output, else they produce nothing. 78 | todo_include_todos = False 79 | 80 | 81 | # -- Options for HTML output ------------------------------------------- 82 | 83 | # The theme to use for HTML and HTML Help pages. See the documentation for 84 | # a list of builtin themes. 85 | # 86 | html_theme = 'alabaster' 87 | 88 | # Theme options are theme-specific and customize the look and feel of a 89 | # theme further. For a list of options available for each theme, see the 90 | # documentation. 91 | # 92 | # html_theme_options = {} 93 | 94 | # Add any paths that contain custom static files (such as style sheets) here, 95 | # relative to this directory. They are copied after the builtin static files, 96 | # so a file named "default.css" will overwrite the builtin "default.css". 97 | html_static_path = ['_static'] 98 | 99 | 100 | # -- Options for HTMLHelp output --------------------------------------- 101 | 102 | # Output file base name for HTML help builder. 103 | htmlhelp_basename = 'gitowndoc' 104 | 105 | 106 | # -- Options for LaTeX output ------------------------------------------ 107 | 108 | latex_elements = { 109 | # The paper size ('letterpaper' or 'a4paper'). 110 | # 111 | # 'papersize': 'letterpaper', 112 | 113 | # The font size ('10pt', '11pt' or '12pt'). 114 | # 115 | # 'pointsize': '10pt', 116 | 117 | # Additional stuff for the LaTeX preamble. 118 | # 119 | # 'preamble': '', 120 | 121 | # Latex figure (float) alignment 122 | # 123 | # 'figure_align': 'htbp', 124 | } 125 | 126 | # Grouping the document tree into LaTeX files. List of tuples 127 | # (source start file, target name, title, author, documentclass 128 | # [howto, manual, or own class]). 129 | latex_documents = [ 130 | (master_doc, 'gitown.tex', 131 | 'gitown Documentation', 132 | 'Milind Shakya', 'manual'), 133 | ] 134 | 135 | 136 | # -- Options for manual page output ------------------------------------ 137 | 138 | # One entry per manual page. List of tuples 139 | # (source start file, name, description, authors, manual section). 140 | man_pages = [ 141 | (master_doc, 'gitown', 142 | 'gitown Documentation', 143 | [author], 1) 144 | ] 145 | 146 | 147 | # -- Options for Texinfo output ---------------------------------------- 148 | 149 | # Grouping the document tree into Texinfo files. List of tuples 150 | # (source start file, target name, title, author, 151 | # dir menu entry, description, category) 152 | texinfo_documents = [ 153 | (master_doc, 'gitown', 154 | 'gitown Documentation', 155 | author, 156 | 'gitown', 157 | 'One line description of project.', 158 | 'Miscellaneous'), 159 | ] 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to gitown's documentation! 2 | ====================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | readme 9 | installation 10 | usage 11 | modules 12 | contributing 13 | authors 14 | history 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 gitown, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install gitown 16 | 17 | This is the preferred method to install gitown, 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 gitown 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/gitown 36 | 37 | Or download the `tarball`_: 38 | 39 | .. code-block:: console 40 | 41 | $ curl -OJL https://github.com/milin/gitown/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/gitown 51 | .. _tarball: https://github.com/milin/gitown/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=gitown 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 gitown in a project:: 6 | 7 | import gitown 8 | -------------------------------------------------------------------------------- /gitown/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for gitown.""" 2 | 3 | __author__ = """Milind Shakya""" 4 | __email__ = 'sh.milind@gmail.com' 5 | __version__ = '0.1.0' 6 | -------------------------------------------------------------------------------- /gitown/gitown.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from functools import lru_cache 3 | import sys 4 | import csv 5 | import pathlib 6 | from invoke import run 7 | import simplejson as json 8 | 9 | DEFAULT_CODEOWNERS_FILE = 'CODEOWNERS' 10 | DEFAULT_OWNERSHIP_THRESHOLD = 25 11 | 12 | cache = lru_cache(maxsize=None) 13 | 14 | 15 | class CodeOwnersUpdater: 16 | def __init__( 17 | self, 18 | files, 19 | owners, 20 | ownership_threshold=DEFAULT_OWNERSHIP_THRESHOLD, 21 | codeowners_filename=DEFAULT_CODEOWNERS_FILE, 22 | verbose=False 23 | ): 24 | self.files = files, 25 | self.original_codeowner_data = {} 26 | self.updated_codeowner_data = {} 27 | self.updated = False 28 | self.owners = owners 29 | self.ownership_threshold = ownership_threshold 30 | self.codeowners_file = codeowners_filename 31 | self.verbose = verbose 32 | 33 | with open(self.codeowners_file, newline='') as csvfile: 34 | reader = csv.reader(csvfile, delimiter=' ') 35 | for row in reader: 36 | try: 37 | if row[0] == '#': 38 | continue 39 | except IndexError: 40 | continue 41 | self.original_codeowner_data[row[0]] = row[1:] 42 | 43 | def check_files(self, files): 44 | codeowners_data = {} 45 | for file in files: 46 | file_committers = self.get_committers_for_file(file) 47 | # Some files may be not meet committer threshold, so we ignore those. 48 | if file_committers: 49 | codeowners_data[file] = file_committers 50 | for key, value in self.original_codeowner_data.items(): 51 | self.updated_codeowner_data[key] = codeowners_data.get(key, value) 52 | for key, value in codeowners_data.items(): 53 | self.updated_codeowner_data[key] = value 54 | 55 | self.update_file(self.updated_codeowner_data) 56 | 57 | def update_file(self, updated_data): 58 | if updated_data != self.original_codeowner_data: 59 | with open(self.codeowners_file, 'w', newline='', encoding='utf-8') as csvfile: 60 | csvfile.write("# Lines starting with '#' are comments.\n") 61 | csvfile.write("# Each line is a file pattern followed by one or more owners.\n") 62 | csvfile.write("# These owners will be the default owners for everything in the repo.\n") 63 | csvfile.write("# * <@insert_github_username>\n") 64 | csvfile.write("#\n") 65 | csvfile.write("# Order is important. The last matching pattern has the most precedence.\n") 66 | csvfile.write("\n") 67 | csvfile.write("\n") 68 | csvfile.write("# This file is also being managed automatically by the gitown tool.\n") 69 | 70 | writer = csv.writer(csvfile, delimiter=' ', lineterminator='\n') 71 | for key, value in updated_data.items(): 72 | writer.writerow([key] + value) 73 | if self.verbose: 74 | print(f"updated data: {updated_data}") 75 | print(f"original data: {self.original_codeowner_data}") 76 | self.updated = True 77 | 78 | def get_committer_line_frequency_percentage(self, committer_email, filename): 79 | blame_file_content = self.get_blame_file_content(filename) 80 | total_lines = blame_file_content.count('\n') 81 | total_lines_by_committer = blame_file_content.count(committer_email) 82 | frequency_percentage = 0 83 | if total_lines: 84 | return (total_lines_by_committer / total_lines) * 100 85 | return frequency_percentage 86 | 87 | @cache 88 | def get_blame_file_content(self, filename): 89 | return run(f"git blame {filename} -e", hide=True).stdout 90 | 91 | def get_committers_for_file(self, filename): 92 | """ 93 | Returns a list of committers usernames sorted by blame frequency 94 | """ 95 | committer_line_frequency_map = {} 96 | for key, value in self.owners.items(): 97 | commiter_frequency = self.get_committer_line_frequency_percentage(key, filename) 98 | committer_line_frequency_map[value] = committer_line_frequency_map.get(value, 0) + commiter_frequency 99 | return [ 100 | a[0] for a in sorted( 101 | committer_line_frequency_map.items(), 102 | key=lambda item: item[1] 103 | ) if a[1] > self.ownership_threshold 104 | ] 105 | 106 | def main(self): 107 | self.check_files(self.files) 108 | return 1 if self.updated else 0 109 | 110 | 111 | def main(): 112 | parser = argparse.ArgumentParser() 113 | parser.add_argument('filenames', nargs='+') 114 | parser.add_argument('--ownership_threshold') 115 | parser.add_argument('--codeowners_filename') 116 | parser.add_argument('--verbose', '-v', action='count', default=0) 117 | args = parser.parse_args() 118 | files = args.filenames[0] 119 | ownership_threshold = int(args.ownership_threshold or DEFAULT_OWNERSHIP_THRESHOLD) 120 | codeowners_filename = args.codeowners_filename 121 | verbose = bool(args.verbose) 122 | 123 | if len(files) == 0: 124 | parser.error('No filenames provided') 125 | try: 126 | owners_raw = pathlib.Path('.gitownrc').read_text('utf-8') 127 | owners = json.loads(owners_raw) 128 | except FileNotFoundError as e: 129 | message = "A .gitownrc file is required. Please see the github repo for details" 130 | raise Exception(message).with_traceback(e.__traceback__) 131 | 132 | codeowners = CodeOwnersUpdater( 133 | files, 134 | owners, 135 | ownership_threshold=ownership_threshold, 136 | codeowners_filename=codeowners_filename or DEFAULT_CODEOWNERS_FILE, 137 | verbose=verbose 138 | ) 139 | codeowners.main() 140 | 141 | 142 | if __name__ == '__main__': 143 | sys.exit(main()) 144 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip==19.2.3 2 | bump2version==0.5.11 3 | wheel==0.33.6 4 | watchdog==0.9.0 5 | flake8==3.7.8 6 | tox==3.14.0 7 | coverage==4.5.4 8 | Sphinx==1.8.5 9 | twine==1.14.0 10 | 11 | -------------------------------------------------------------------------------- /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:gitown/__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 | [pycodestyle] 23 | exclude = docs 24 | max-line-length = 120 25 | 26 | [aliases] 27 | # Define setup.py command aliases here 28 | 29 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """The setup script.""" 4 | 5 | from setuptools import setup, find_packages 6 | 7 | with open('README.rst') as readme_file: 8 | readme = readme_file.read() 9 | 10 | with open('HISTORY.rst') as history_file: 11 | history = history_file.read() 12 | 13 | requirements = [ 14 | 'pre-commit', 15 | 'invoke', 16 | 'simplejson' 17 | ] 18 | 19 | setup_requirements = [ ] 20 | 21 | test_requirements = [ ] 22 | 23 | setup( 24 | author="Milind Shakya", 25 | author_email='sh.milind@gmail.com', 26 | python_requires='>=3.5', 27 | classifiers=[ 28 | 'Development Status :: 2 - Pre-Alpha', 29 | 'Intended Audience :: Developers', 30 | 'License :: OSI Approved :: MIT License', 31 | 'Natural Language :: English', 32 | 'Programming Language :: Python :: 3.6', 33 | 'Programming Language :: Python :: 3.7', 34 | 'Programming Language :: Python :: 3.8', 35 | ], 36 | description="Keep your github CODEOWNERS file up to date.", 37 | install_requires=requirements, 38 | license="MIT license", 39 | long_description=readme + '\n\n' + history, 40 | include_package_data=True, 41 | keywords='gitown', 42 | name='gitown', 43 | packages=find_packages(include=['gitown', 'gitown.*']), 44 | setup_requires=setup_requirements, 45 | test_suite='tests', 46 | tests_require=test_requirements, 47 | url='https://github.com/milin/gitown', 48 | version='0.1.1', 49 | entry_points={ 50 | 'console_scripts': [ 51 | 'gitown = gitown.gitown:main', 52 | ] 53 | }, 54 | zip_safe=False, 55 | ) 56 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit test package for gitown.""" 2 | -------------------------------------------------------------------------------- /tests/test_gitown.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Tests for `gitown` package.""" 4 | 5 | 6 | import unittest 7 | 8 | from gitown import gitown 9 | 10 | 11 | class TestGitown(unittest.TestCase): 12 | """Tests for `gitown` package.""" 13 | 14 | def setUp(self): 15 | """Set up test fixtures, if any.""" 16 | 17 | def tearDown(self): 18 | """Tear down test fixtures, if any.""" 19 | 20 | def test_000_something(self): 21 | """Test something.""" 22 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36, py37, py38, flake8 3 | 4 | [travis] 5 | python = 6 | 3.8: py38 7 | 3.7: py37 8 | 3.6: py36 9 | 10 | [testenv:flake8] 11 | basepython = python 12 | deps = flake8 13 | commands = flake8 gitown tests 14 | 15 | [testenv] 16 | setenv = 17 | PYTHONPATH = {toxinidir} 18 | 19 | commands = python setup.py test 20 | --------------------------------------------------------------------------------