diff --git a/.coveragerc b/.coveragerc index 152cfdf..3d61c16 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,7 +1,7 @@ [run] branch = True omit = - gitfame/tests/* + tests/* relative_files = True [report] show_missing = True diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 44774f1..2de8120 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ github: casperdcl -custom: https://caspersci.uk.to/donate +custom: https://www.caspersci.uk.to/sponsor diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1a35d3a..fd32419 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,33 +14,28 @@ jobs: with: fetch-depth: 0 - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: set PYSHA + - name: Prepare cache run: echo "PYSHA=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV - uses: actions/cache@v1 with: path: ~/.cache/pre-commit key: pre-commit|${{ env.PYSHA }}|${{ hashFiles('.pre-commit-config.yaml') }} - - name: Test - run: | - pip install -U tox - tox - env: - TOXENV: 'setup.py,nodeps' - - name: Self install - run: pip install -U .[dev] - - name: Build - run: | - python setup.py sdist bdist_wheel - twine check dist/* + - name: Dependencies + run: pip install -U pre-commit - uses: reviewdog/action-setup@v1 - if: github.event_name != 'schedule' - name: flake8 + name: Comment run: | - pre-commit run -a flake8 | reviewdog -f=pep8 -name=Format -tee -reporter=github-check -filter-mode nofilter + if [[ $EVENT == pull_request ]]; then + REPORTER=github-pr-review + else + REPORTER=github-check + fi + pre-commit run -a todo | reviewdog -efm="%f:%l: %m" -name=TODO -tee -reporter=$REPORTER -filter-mode nofilter + pre-commit run -a flake8 | reviewdog -f=pep8 -name=flake8 -tee -reporter=$REPORTER -filter-mode nofilter env: REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + EVENT: ${{ github.event_name }} - name: Lint run: pre-commit run -a --show-diff-on-failure test: @@ -60,24 +55,33 @@ jobs: - name: Install run: pip install -U tox - name: Test - run: tox -e py${PYVER/./} + run: | + if [[ "$PYVER" == py3.8 ]]; then + tox -e py${PYVER/./},nodeps # full:incl nodeps + else + tox -e py${PYVER/./} # normal + fi env: PYVER: ${{ matrix.python }} - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - name: Coveralls Parallel - uses: AndreMiras/coveralls-python-action@develop - with: - parallel: true + COVERALLS_FLAG_NAME: py${{ matrix.python }} + COVERALLS_PARALLEL: true + COVERALLS_SERVICE_NAME: github + # coveralls needs explicit token + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} finish: if: github.event_name != 'pull_request' || github.head_ref != 'devel' name: Coverage + continue-on-error: ${{ github.event_name != 'push' }} needs: test runs-on: ubuntu-latest steps: + - uses: actions/setup-python@v2 - name: Coveralls Finished - uses: AndreMiras/coveralls-python-action@develop - with: - parallel-finished: true + run: | + pip install -U coveralls + coveralls --finish || : + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} deploy: if: github.event_name != 'pull_request' || github.head_ref != 'devel' name: Deploy @@ -88,39 +92,36 @@ jobs: with: fetch-depth: 0 - uses: actions/setup-python@v2 - with: - python-version: '3.x' - name: Install run: | sudo apt-get install -yqq pandoc - pip install .[dev] + pip install -r .meta/requirements-build.txt make build .dockerignore - - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') - uses: casperdcl/deploy-pypi@v1 + - id: dist + uses: casperdcl/deploy-pypi@v2 with: password: ${{ secrets.TWINE_PASSWORD }} gpg_key: ${{ secrets.GPG_KEY }} - skip_existing: true + upload: ${{ github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') }} - id: collect_assets name: Collect assets run: | - echo "::set-output name=asset_path::$(ls dist/*.whl)" - echo "::set-output name=asset_name::$(basename dist/*.whl)" - echo "::set-output name=asset_path_sig::$(ls dist/*.whl.asc 2>/dev/null)" - echo "::set-output name=asset_name_sig::$(basename dist/*.whl.asc 2>/dev/null)" if [[ $GITHUB_REF == refs/tags/v* ]]; then echo ::set-output name=docker_tags::latest,${GITHUB_REF/refs\/tags\/v/} + echo ::set-output name=snap_channel::stable,candidate,edge elif [[ $GITHUB_REF == refs/heads/master ]]; then echo ::set-output name=docker_tags::master + echo ::set-output name=snap_channel::candidate,edge elif [[ $GITHUB_REF == refs/heads/devel ]]; then echo ::set-output name=docker_tags::devel + echo ::set-output name=snap_channel::edge fi git log --pretty='format:%d%n- %s%n%b---' $(git tag --sort=v:refname | tail -n2 | head -n1)..HEAD > _CHANGES.md - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') id: create_release uses: actions/create-release@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} with: tag_name: ${{ github.ref }} release_name: git-fame ${{ github.ref }} stable @@ -129,21 +130,29 @@ jobs: - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') uses: actions/upload-release-asset@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ${{ steps.collect_assets.outputs.asset_path }} - asset_name: ${{ steps.collect_assets.outputs.asset_name }} + asset_path: dist/${{ steps.dist.outputs.whl }} + asset_name: ${{ steps.dist.outputs.whl }} asset_content_type: application/zip - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') uses: actions/upload-release-asset@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ${{ steps.collect_assets.outputs.asset_path_sig }} - asset_name: ${{ steps.collect_assets.outputs.asset_name_sig }} + asset_path: dist/${{ steps.dist.outputs.whl_asc }} + asset_name: ${{ steps.dist.outputs.whl_asc }} asset_content_type: text/plain + - uses: snapcore/action-build@v1 + id: snap_build + - if: github.event_name == 'push' && steps.collect_assets.outputs.snap_channel + uses: snapcore/action-publish@v1 + with: + store_login: ${{ secrets.SNAP_TOKEN }} + snap: ${{ steps.snap_build.outputs.snap }} + release: ${{ steps.collect_assets.outputs.snap_channel }} - name: Docker build push uses: elgohr/Publish-Docker-Github-Action@master with: @@ -161,30 +170,3 @@ jobs: username: ${{ github.actor }} registry: docker.pkg.github.com no_push: ${{ steps.collect_assets.outputs.docker_tags == '' }} - deploy-snap: - if: github.event_name != 'pull_request' || github.head_ref != 'devel' - name: Deploy Snap - needs: [check, test] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - id: snap_channel - name: Snap channel - run: | - if [[ $GITHUB_REF == refs/tags/v* ]]; then - echo ::set-output name=release::stable,candidate - elif [[ $GITHUB_REF == refs/heads/master ]]; then - echo ::set-output name=release::candidate - elif [[ $GITHUB_REF == refs/heads/devel ]]; then - echo ::set-output name=release::edge - fi - - id: snap_build - uses: snapcore/action-build@v1 - - if: github.event_name == 'push' && steps.snap_channel.outputs.release - uses: snapcore/action-publish@v1 - with: - store_login: ${{ secrets.SNAP_TOKEN }} - snap: ${{ steps.snap_build.outputs.snap }} - release: ${{ steps.snap_channel.outputs.release }} diff --git a/.meta/requirements-build.txt b/.meta/requirements-build.txt new file mode 100644 index 0000000..5e3fc70 --- /dev/null +++ b/.meta/requirements-build.txt @@ -0,0 +1,3 @@ +py-make>=0.1.0 +twine +wheel diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c6c7843..3071409 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,27 +2,52 @@ default_language_version: python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.3.0 + rev: v3.4.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-docstring-first - id: check-executables-have-shebangs - id: check-toml + - id: check-merge-conflict - id: check-yaml + - id: debug-statements - id: end-of-file-fixer - id: mixed-line-ending + - id: sort-simple-yaml - id: trailing-whitespace -- hooks: +- repo: local + hooks: + - id: todo + name: Check TODO + language: pygrep + entry: WIP + args: [-i] + types: [text] + exclude: ^(.pre-commit-config.yaml|.github/workflows/test.yml)$ + - id: nose + name: Run tests + language: python + entry: nosetests + args: ['-d', tests/] + types: [python] + pass_filenames: false + additional_dependencies: + - nose + - argopt + - tabulate + - tqdm +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 + hooks: - id: flake8 + args: ['-j8'] additional_dependencies: - flake8-bugbear - flake8-comprehensions - flake8-debugger - flake8-string-format - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 -- hooks: +- repo: https://github.com/PyCQA/isort + rev: 5.7.0 + hooks: - id: isort - repo: https://github.com/timothycrosley/isort - rev: 5.6.4 diff --git a/Makefile b/Makefile index a8b0165..d80b668 100644 --- a/Makefile +++ b/Makefile @@ -87,13 +87,13 @@ prebuildclean: coverclean: @+python -c "import os; os.remove('.coverage') if os.path.exists('.coverage') else None" @+python -c "import shutil; shutil.rmtree('gitfame/__pycache__', True)" - @+python -c "import shutil; shutil.rmtree('gitfame/tests/__pycache__', True)" + @+python -c "import shutil; shutil.rmtree('tests/__pycache__', True)" clean: @+python -c "import os, glob; [os.remove(i) for i in glob.glob('*.py[co]')]" @+python -c "import os, glob; [os.remove(i) for i in glob.glob('gitfame/*.py[co]')]" @+python -c "import os, glob; [os.remove(i) for i in glob.glob('gitfame/*.c')]" @+python -c "import os, glob; [os.remove(i) for i in glob.glob('gitfame/*.so')]" - @+python -c "import os, glob; [os.remove(i) for i in glob.glob('gitfame/tests/*.py[co]')]" + @+python -c "import os, glob; [os.remove(i) for i in glob.glob('tests/*.py[co]')]" toxclean: @+python -c "import shutil; shutil.rmtree('.tox', True)" diff --git a/README.rst b/README.rst index a85c1a7..8893eac 100644 --- a/README.rst +++ b/README.rst @@ -11,30 +11,31 @@ Pretty-print ``git`` repository collaborators sorted by contributions. .. code:: sh - ~$ git fame --cost hour,month - Blame: 100%|██████████| 74/74 [00:00<00:00, 96.51file/s] - Total commits: 1173 - Total ctimes: 1055 - Total files: 180 - Total hours: 255.1 - Total loc: 2716 - Total months: 8.7 - | Author | hrs | mths | loc | coms | fils | distribution | - |:---------------------------|------:|-------:|------:|-------:|-------:|:----------------| - | Casper da Costa-Luis | 100 | 7 | 2171 | 770 | 63 | 79.9/65.6/35.0 | - | Stephen Larroque | 16 | 1 | 243 | 202 | 19 | 8.9/17.2/10.6 | - | Kyle Altendorf | 6 | 0 | 41 | 31 | 3 | 1.5/ 2.6/ 1.7 | - | Guangshuo Chen | 2 | 0 | 35 | 18 | 6 | 1.3/ 1.5/ 3.3 | - | Matthew Stevens | 2 | 0 | 32 | 3 | 2 | 1.2/ 0.3/ 1.1 | - | Noam Yorav-Raphael | 3 | 0 | 23 | 11 | 4 | 0.8/ 0.9/ 2.2 | - | Daniel Panteleit | 2 | 0 | 16 | 2 | 2 | 0.6/ 0.2/ 1.1 | - | Mikhail Korobov | 2 | 0 | 15 | 11 | 6 | 0.6/ 0.9/ 3.3 | - | Hadrien Mary | 3 | 0 | 15 | 31 | 10 | 0.6/ 2.6/ 5.6 | - | Johannes Hansen | 2 | 0 | 14 | 1 | 2 | 0.5/ 0.1/ 1.1 | + ~$ git fame --cost hour,month --loc ins + Processing: 100%|██████████████████████████| 1/1 [00:00<00:00, 2.16repo/s] + Total commits: 1775 + Total ctimes: 2770 + Total files: 461 + Total hours: 449.7 + Total loc: 41659 + Total months: 151.0 + | Author | hrs | mths | loc | coms | fils | distribution | + |:---------------------|------:|-------:|------:|-------:|-------:|:----------------| + | Casper da Costa-Luis | 228 | 108 | 28572 | 1314 | 172 | 68.6/74.0/37.3 | + | Stephen Larroque | 28 | 18 | 5243 | 203 | 25 | 12.6/11.4/ 5.4 | + | pgajdos | 2 | 9 | 2606 | 2 | 18 | 6.3/ 0.1/ 3.9 | + | Martin Zugnoni | 2 | 5 | 1656 | 3 | 3 | 4.0/ 0.2/ 0.7 | + | Kyle Altendorf | 7 | 2 | 541 | 31 | 7 | 1.3/ 1.7/ 1.5 | + | Hadrien Mary | 5 | 1 | 469 | 31 | 17 | 1.1/ 1.7/ 3.7 | + | Richard Sheridan | 2 | 1 | 437 | 23 | 3 | 1.0/ 1.3/ 0.7 | + | Guangshuo Chen | 3 | 1 | 321 | 18 | 7 | 0.8/ 1.0/ 1.5 | + | Noam Yorav-Raphael | 4 | 1 | 229 | 11 | 6 | 0.5/ 0.6/ 1.3 | + | github-actions[bot] | 2 | 1 | 186 | 1 | 51 | 0.4/ 0.1/11.1 | + ... The ``distribution`` column is a percentage breakdown of ``loc/coms/fils``. (e.g. in the table above, Casper has written surviving code in -``63/180 = 35.0%`` of all files). +``172/461 = 37.3%`` of all files). ------------------------------------------ @@ -60,7 +61,7 @@ Latest development release on GitHub |GitHub-Status| |GitHub-Stars| |GitHub-Commits| |GitHub-Forks| |GitHub-Updated| -Pull and install in the current directory: +Pull and install: .. code:: sh @@ -179,7 +180,7 @@ Documentation -v, --version Print module version and exit. --branch= Branch or tag [default: HEAD] up to which to check. --sort= [default: loc]|commits|files|hours|months. - --loc= surviving|ins(ertions)|del(etions) + --loc= surv(iving)|ins(ertions)|del(etions) What `loc` represents. Use 'ins,del' to count both. defaults to 'surviving' unless `--cost` is specified. --excl= Excluded files (default: None). @@ -220,6 +221,28 @@ If multiple user names and/or emails correspond to the same user, aggregate ``git-fame`` statistics and maintain a ``git`` repository properly by adding a `.mailmap file `_. +FAQs +~~~~ + +Options such as ``-w``, ``-M``, and ``-C`` can increase accuracy, but take +longer to compute. + +Note that specifying ``--sort=hours`` or ``--sort=months`` requires ``--cost`` +to be specified appropriately. + +Note that ``--cost=months`` (``--cost=COCOMO``) approximates +`person-months `_ and should be used with +``--loc=ins``. + +Meanwhile, ``--cost=hours`` (``--cost=commits``) approximates +`person-hours `_. + +Extra care should be taken when using ``ins`` and/or ``del`` for ``--loc`` +since all historical files (including those no longer surviving) are counted. +In such cases, ``--excl`` may need to be significantly extended. +On the plus side, it is faster to compute ``ins`` and ``del`` compared to +``surv``. + Examples -------- diff --git a/gitfame/_gitfame.py b/gitfame/_gitfame.py index afa46c8..59916e9 100755 --- a/gitfame/_gitfame.py +++ b/gitfame/_gitfame.py @@ -12,7 +12,7 @@ -v, --version Print module version and exit. --branch= Branch or tag [default: HEAD] up to which to check. --sort= [default: loc]|commits|files|hours|months. - --loc= surviving|ins(ertions)|del(etions) + --loc= surv(iving)|ins(ertions)|del(etions) What `loc` represents. Use 'ins,del' to count both. defaults to 'surviving' unless `--cost` is specified. --excl= Excluded files (default: None). diff --git a/setup.cfg b/setup.cfg index 17554b6..205a6f6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,21 +1,21 @@ [metadata] -name = git-fame -url = https://github.com/casperdcl/git-fame -project_urls = - Changelog = https://github.com/casperdcl/git-fame/releases - Documentation = https://github.com/casperdcl/git-fame/#git-fame -licence = MPL 2.0 -license_file = LICENCE -description = Pretty-print `git` repository collaborators sorted by contributions -long_description = file: README.rst -long_description_content_type = text/x-rst -author = Casper da Costa-Luis -author_email = casper.dcl@physics.org -keywords = git, blame, git-blame, git-log, code-analysis, cost, loc, author, commit, shortlog, ls-files -platforms = any -provides = gitfame +name=git-fame +url=https://github.com/casperdcl/git-fame +project_urls= + Changelog=https://github.com/casperdcl/git-fame/releases + Documentation=https://github.com/casperdcl/git-fame/#git-fame +licence=MPL 2.0 +license_file=LICENCE +description=Pretty-print `git` repository collaborators sorted by contributions +long_description=file: README.rst +long_description_content_type=text/x-rst +author=Casper da Costa-Luis +author_email=casper.dcl@physics.org +keywords=git, blame, git-blame, git-log, code-analysis, cost, loc, author, commit, shortlog, ls-files +platforms=any +provides=gitfame # Trove classifiers (https://pypi.org/pypi?%3Aaction=list_classifiers) -classifiers = +classifiers= Development Status :: 5 - Production/Stable Environment :: Console Environment :: MacOS X @@ -70,30 +70,32 @@ classifiers = Topic :: Terminals Topic :: Utilities [options] -setup_requires = setuptools>=42; setuptools_scm[toml]>=3.4 -install_requires = argopt>=0.3.5; setuptools; tabulate; tqdm>=4.44.0 -python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* -tests_require = nose; flake8; coverage -include_package_data = True -packages = find: +setup_requires=setuptools>=42; setuptools_scm[toml]>=3.4 +install_requires=argopt>=0.3.5; setuptools; tabulate; tqdm>=4.44.0 +python_requires=>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* +tests_require=nose; flake8; coverage +include_package_data=True +packages=find: [options.extras_require] -yaml = pyyaml -tabulate = -full = pyyaml -dev = pyyaml; py-make>=0.1.0; twine; wheel; pre-commit +yaml=pyyaml +tabulate= +full=pyyaml +dev=pyyaml; py-make>=0.1.0; twine; wheel; pre-commit [options.entry_points] -console_scripts = - git-fame = gitfame:main +console_scripts= + git-fame=gitfame:main +[options.packages.find] +exclude=tests [options.package_data] -gitfame = git-fame.1 +gitfame=git-fame.1 [bdist_wheel] -universal = 1 +universal=1 [flake8] -ignore = E111,E114 -max_line_length = 88 -exclude = .eggs,.tox,dist,build,dist,.git,__pycache__ +extend-ignore=E111,E114 +max_line_length=88 +exclude=.eggs,.tox,dist,build,dist,.git,__pycache__ [isort] -profile = black -known_first_party = gitfame,tests +profile=black +known_first_party=gitfame,tests diff --git a/gitfame/tests/tests_gitfame.py b/tests/tests_gitfame.py similarity index 99% rename from gitfame/tests/tests_gitfame.py rename to tests/tests_gitfame.py index 0a55913..aca8fc1 100644 --- a/gitfame/tests/tests_gitfame.py +++ b/tests/tests_gitfame.py @@ -177,7 +177,7 @@ def test_main(): res = subprocess.Popen( (sys.executable, '-c', 'import gitfame; import sys;\ sys.argv = ["", "--silent-progress", "%s"];\ - gitfame.main()' % dn(dn(dn(__file__)))), + gitfame.main()' % dn(dn(__file__))), stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0] diff --git a/gitfame/tests/tests_utils.py b/tests/tests_utils.py similarity index 100% rename from gitfame/tests/tests_utils.py rename to tests/tests_utils.py diff --git a/tox.ini b/tox.ini index a10cd76..6986362 100644 --- a/tox.ini +++ b/tox.ini @@ -5,59 +5,34 @@ [tox] # deprecation warning: py{26,32,33,34} -envlist = py{27,35,36,37,38,39,py,py3}, flake8, setup.py, nodeps -isolated_build = True +envlist=py{27,35,36,37,38,39,py,py3}, flake8, setup.py, nodeps +isolated_build=True [core] -deps = nose -commands = nosetests -d -v gitfame/ - -[coverage] -deps = - {[core]deps} +deps= + nose + nose-timer coverage coveralls -commands = - nosetests --with-coverage --cover-package=gitfame -d -v gitfame/ - - coveralls - -[extra] -deps = - {[coverage]deps} - nose-timer - codecov -commands = - nosetests --with-coverage --with-timer --cover-package=gitfame -d -v gitfame/ - - coveralls codecov [testenv] -passenv = CI TOXENV CODECOV_* COVERALLS_* -deps = - {[extra]deps} +passenv=TOXENV CI GITHUB_* CODECOV_* COVERALLS_* +deps= + {[core]deps} tqdm pyyaml -commands = {[extra]commands} +commands= + nosetests --with-coverage --with-timer --cover-package=gitfame -d -v tests/ + coverage xml + - coveralls + codecov -X pycov -e TOXENV [testenv:py34] -deps = - {[extra]deps} +deps= + {[core]deps} tqdm pyyaml==5.2 -commands = {[extra]commands} [testenv:nodeps] -deps = {[extra]deps} - -[testenv:flake8] -deps = flake8 -commands = flake8 -j 8 --count --statistics . - -[testenv:setup.py] -deps = - docutils - pygments - py-make>=0.1.0 -commands = - {envpython} setup.py check --restructuredtext --metadata --strict - {envpython} setup.py make none +deps={[core]deps}