{"id":13452345,"url":"https://github.com/asharov/git-hammer","last_synced_at":"2026-01-14T07:03:00.480Z","repository":{"id":57434521,"uuid":"166545822","full_name":"asharov/git-hammer","owner":"asharov","description":"Collect and display statistics of git repositories","archived":false,"fork":false,"pushed_at":"2021-04-12T21:49:56.000Z","size":136,"stargazers_count":134,"open_issues_count":10,"forks_count":12,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-10-11T00:28:14.327Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/asharov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-01-19T12:45:28.000Z","updated_at":"2025-04-24T03:43:37.000Z","dependencies_parsed_at":"2022-09-04T15:31:39.335Z","dependency_job_id":null,"html_url":"https://github.com/asharov/git-hammer","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/asharov/git-hammer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asharov%2Fgit-hammer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asharov%2Fgit-hammer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asharov%2Fgit-hammer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asharov%2Fgit-hammer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/asharov","download_url":"https://codeload.github.com/asharov/git-hammer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/asharov%2Fgit-hammer/sbom","scorecard":{"id":211478,"data":{"date":"2025-08-11","repo":{"name":"github.com/asharov/git-hammer","commit":"cdad6799bbf964f0d96736e68d753dbc57744be9"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/21 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"19 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2022-42986 / GHSA-43fp-rhv2-5gv8","Warn: Project is vulnerable to: PYSEC-2023-135 / GHSA-xqr8-7jwr-rhp7","Warn: Project is vulnerable to: PYSEC-2024-4 / GHSA-2mqj-m65w-jghx","Warn: Project is vulnerable to: PYSEC-2023-165 / GHSA-cwvm-v4w8-q58c","Warn: Project is vulnerable to: PYSEC-2022-42992 / GHSA-hcpj-qp55-gfph","Warn: Project is vulnerable to: PYSEC-2023-137 / GHSA-pr76-5cm5-w9cj","Warn: Project is vulnerable to: PYSEC-2023-161 / GHSA-wfm5-v35h-vwf4","Warn: Project is vulnerable to: PYSEC-2024-60 / GHSA-jjg7-2v4v-x38h","Warn: Project is vulnerable to: PYSEC-2022-260 / GHSA-v973-fxgf-6xhp","Warn: Project is vulnerable to: GHSA-6p56-wp2h-9hxr","Warn: Project is vulnerable to: GHSA-fpfv-jqm9-f5jm","Warn: Project is vulnerable to: GHSA-9hjg-9r4m-mvj7","Warn: Project is vulnerable to: GHSA-9wx4-h78v-vm56","Warn: Project is vulnerable to: PYSEC-2023-74 / GHSA-j8r2-6x86-q33q","Warn: Project is vulnerable to: GHSA-34jh-p97f-mpxf","Warn: Project is vulnerable to: PYSEC-2023-212 / GHSA-g4mx-q9vg-27p4","Warn: Project is vulnerable to: GHSA-pq67-6m6q-mj2v","Warn: Project is vulnerable to: PYSEC-2021-108 / GHSA-q2q7-5pp4-w6pg","Warn: Project is vulnerable to: PYSEC-2023-192 / GHSA-v845-jxx5-vc9f"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 12 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T00:50:16.445Z","repository_id":57434521,"created_at":"2025-08-17T00:50:16.445Z","updated_at":"2025-08-17T00:50:16.445Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28412492,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T05:26:33.345Z","status":"ssl_error","status_checked_at":"2026-01-14T05:21:57.251Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T07:01:21.265Z","updated_at":"2026-01-14T07:03:00.464Z","avatar_url":"https://github.com/asharov.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# Git Hammer\n\n[![Build Status](https://travis-ci.com/asharov/git-hammer.svg?branch=master)](https://travis-ci.com/asharov/git-hammer)\n[![codecov](https://codecov.io/gh/asharov/git-hammer/branch/master/graph/badge.svg)](https://codecov.io/gh/asharov/git-hammer)\n[![PyPI](https://img.shields.io/pypi/v/git-hammer)](https://pypi.org/project/git-hammer/)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n\nGit Hammer is a statistics tool for projects in git repositories.\nIts major feature is tracking the number of lines authored by\neach person for every commit, but it currently includes some\nother useful statistics as well, and the data that it collects\ncould be used in multiple new ways as well.\n\nGit Hammer is under active maintenance. New features appear when\na need or desire for them exists. If Git Hammer lacks some\nfeature you would like, all kinds of contributions are welcome,\nfrom simple feature suggestions to complete pull requests\nimplementing the feature.\n\n## Setup\n\nBy default, Git Hammer stores the historical information from\nthe repository in an SQLite database file in the current\ndirectory. If you wish to change this default, set the\n`DATABASE_URL` environment variable to a database URL\naccording to the [SQLAlchemy engine documentation](https://docs.sqlalchemy.org/en/latest/core/engines.html).\nThis database will be created if it does not already exist.\nNote that if you wish to use a database other than SQLite,\nyou may need to install the appropriate Python module to\nconnect to the database.\n\nYou will need Python 3, at least version 3.5. It is a good\nidea to set up a virtual environment, like this:\n```bash\npython3 -m venv venv\nsource venv/bin/activate\n```\nRun these commands wherever you want to run Git Hammer. If\nyou only want to use Git Hammer, you can install it with\n`pip`:\n```bash\npip install git-hammer\n```\nIf you want to use the latest development version or\ncontribute to Git Hammer development, you need to clone\nthis repository and run\n```bash\npip install -r requirements.txt\n```\nin the directory where you cloned Git Hammer (in this\ncase you should create the virtual environment above in\nthat directory as well). The rest of the commands below\nassume that one of these has been done.\n\n## Creating a Project\n\nNow pick some git repository to run Git Hammer on. The examples\nbelow use a hypothetical project called \"baffle\". You should\nreplace the name with your own.\n\n```bash\npython -m githammer init-project baffle ~/projects/baffle\n```\nThis will create the database containing the project baffle\nfrom the repository directory (here `~/projects/baffle`;\nreplace that with the path to your repository). Git Hammer\nwill print out a progress report while it goes through all\nthe commits in the repository.\n\nUsually, you want your main development branch to be checked\nout in the repository, and not change the checked-out branch\nwhen updating Git Hammer data. This makes the statistics more\nrelevant for the whole development team.\n\nWhen the repository gets new development, first update the\ncode in the repository to the latest version, and then run\n```bash\npython -m githammer update-project baffle\n```\nThis will process all the new commits that were not yet seen\ninto the database.\n\nIf the repository is very old, with much history, you might\nnot be interested in capturing all of it. `init-project`\nhas the option `--earliest-commit-date` that provides a date\nso that commits prior to that date are not included. This\nwould be used like\n```bash\npython -m githammer init-project baffle ~/projects/baffle --earliest-commit-date 2018-01-01\n```\nIt is currently not possible to later add commits that were\nexcluded by date when the repository was added.\n\n## Showing Statistics\n\nAfter the project has been initialized and the repository added,\nyou can show some information on it. First try out\n```bash\npython -m githammer summary baffle\n```\nThis will print out three tables: The number of commits for\neach person, the number of lines of code written by each\nperson in the head version, and the number of tests written\nby each person in the head version. This last is only printed\nif the repository configuration includes test recognition (see\nbelow).\n\nThere are a few graphs that Git Hammer can display. To see the\ntypes of supported graphs, enter\n```bash\npython -m githammer graph --help\n```\nThe graphs are\n\nType | Description\n-----|------------\nline-count    | Number of lines in the project over time\nline-author-count | Same as above, except split per author\ntest-count | Number of tests in the project over time\ntest-author-count | Same as above, except split per author\nday-of-week | A histogram showing the number of commits for each day of the week\ntime-of-day | A histogram showing the number of commits for each hour of the day\n\n## Configuring Sources and Tests\n\nBy default, Git Hammer assumes that every file in the repository\nis a source file and that there are no tests. This can be\nmodified by creating a configuration file. The configuration\nfile is JSON having some predefined keys:\n```json\n{\n  \"sourceFiles\": [\n    \"Sources/**/*.py\",\n    \"Tests/**/*.py\",\n    ...\n  ],\n  \"excludedSourceFiles\": [\n    \"Sources/Contrib/**\"\n  ],\n  \"testFiles\": [\n    \"Tests/**/*.py\"\n  ],\n  \"testLineRegex\": \"def test_\"\n}\n```\n\nHere, `sourceFiles` is a list of patterns that match the source\nfiles. Any file not matching one of these patterns is not\nconsidered by Git Hammer. If `sourceFiles` captures too many\nfiles, for instance autogenerated sources, `excludedSourceFiles`\nis a list of patterns that will not be considered source even\nif they match some `sourceFiles` pattern.\n\nTo include test counts, `testFiles` needs to be specified. This\nis again, a list of patterns matching files that contain tests\n(it is up to you if you wish to define this to mean unit tests,\nintegration tests, UI tests, etc.). Git Hammer will look inside\neach of the test files. Any line matching the Python regular\nexpression `testLineRegex` is counted as one test. So\n`testLineRegex` should typically match whatever acts as the\nheader of a test. Here, it is the definition of a function\nnamed starting with `test_`. Other projects, and especially\nother languages, will have different conventions.\n\nAll the file name patterns above (`sourceFiles`,\n`excludedSourceFiles`, `testFiles`) are glob patterns as\ndefined by the\n[globber library](https://github.com/asharov/globber).\n\nThe configuration file can be given as an option to the\n`init-project` command:\n```bash\npython -m githammer init-project baffle ~/projects/baffle --configuration ./baffle-config.json\n```\nIf the `--configuration` option is not given, but the repository\ncontains a file named `git-hammer-config.json`, this file will\nbe read as the configuration. This way you can keep the Git\nHammer configuration for a repository in that repository.\n\nNote: The configuration file path, as well as the repository\npath, will be stored in the database, so they should not be\nmoved. If the configuration changes, data that was already\nin the database will not be reprocessed with the new\nconfiguration.\n\nThere is also a command to check what are the effects of a\nconfiguration. Run\n```bash\npython -m githammer list-sources ~/projects/baffle --configuration ./baffle-config.json\n```\nto print out a list of all files considered source or test files,\nand for each test file, the lines considered to be tests. A missing\n`--configuration` option is treated in the same way as with\n`init-project` above.\n\nA partial output of the `list-sources` command on the Git Hammer\nrepository looks like this:\n```\nS: githammer/dbtypes.py\nS: githammer/frequency.py\nS: githammer/hammer.py\nT: tests/__init__.py\nT: tests/check_regression.py\nT: tests/hammer_test.py\nT: tests/test_init.py\n|---    def test_plain_init_does_not_create_database(self):\n|---    def test_update_fails_when_database_not_created(self):\n```\nSource files are marked with `S`, test files with `T`, and after\neach test file, its test lines are printed indented with `|---`.\n\n## Multi-Repository Projects\n\nSometimes, a team works on multiple repositories that all still\nbelong to the same project. For instance, a piece of functionality\nmay be better to split off into a library in an independent\nrepository. Git Hammer supports such projects by not limiting\nthe project data to a single repository.\n\nTo add another repository to an existing project, just use\n`add-repository`:\n```bash\npython -m githammer add-repository baffle ~/projects/baffle-common\n```\nThis will process the new repository, adding it to the project\ndatabase. After this, any summary information will include\ndata from all repositories of the project. Like `init-project`,\n`add-repository` also accepts the `--configuration` and\n`--earliest-commit-date` options with the same semantics for\nthe added repository.\n\n## Database Migrations\n\nIf you update Git Hammer, it is possible that the database\nschema is updated in the new version. This means that you will\nneed to migrate any existing databases to the latest version.\nMigration is performed by running\n```bash\nHAMMER_DATABASE_URL=\u003cURL of database to migrate\u003e alembic upgrade head\n```\n(If you haven't installed Git Hammer with `pip`, run this command\nin the project directory and add `PYTHONPATH=.` at the beginning.)\n\nIt is safe to run this even if the database schema has not changed\nin the update, so there is no need to try and figure that out before\nrunning the migration.\n\n## License\n\nGit Hammer is licensed under the Apache Software License,\nversion 2.0. See the LICENSE file for precise license terms\nand conditions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasharov%2Fgit-hammer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fasharov%2Fgit-hammer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasharov%2Fgit-hammer/lists"}