This is a Trino connector to access git repos. Please keep in mind that this is not production ready and it was created for tests.
To run a Docker container with the connector and the latest release, run the following:
VERSION=$(curl -fsSL https://api.github.com/repos/nineinchnick/trino-git/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
docker run \
-d \
--name trino-git \
-e REPO_URL=https://github.com/nineinchnick/trino-rest.git \
-p 8080:8080 \
nineinchnick/trino-git:$VERSIONThe image has a git catalog that reads the repo from REPO_URL.
Then connect to it with the Trino CLI that is included in the image:
docker exec -it trino-git trino --catalog git --schema defaultYou can also use any other SQL client to connect to Trino running at http://localhost:8080.
Download the ZIP package of the latest release,
unzip it, and copy the trino-git-<version> directory as plugin/git on every node in your Trino cluster:
VERSION=$(curl -fsSL https://api.github.com/repos/nineinchnick/trino-git/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
curl -fsSLO https://github.com/nineinchnick/trino-git/releases/download/v$VERSION/trino-git-$VERSION.zip
unzip trino-git-$VERSION.zip
cp -r trino-git-$VERSION ${TRINO_HOME}/plugin/gitCreate a catalog properties file, for example etc/catalog/git.properties, that points to a remote repo.
The catalog name is the file name, so this file creates the git catalog.
You can also use a path to a local repo, if it exists on every node.
connector.name=git
git.url=https://github.com/nineinchnick/trino-git.git
The catalog supports these properties:
| Property | Description | Default |
|---|---|---|
git.url |
URL of the repository, available as the default schema. Must not contain credentials. Previously called metadata-uri. |
|
git.repositories |
More repositories, as comma separated schema=url pairs, each available as a schema with the same tables. |
|
git.username |
Username used to authenticate to the repository. | |
git.password |
Password or access token used to authenticate to the repository. | |
git.cache-directory |
Directory where every node clones the repository. Only the user running Trino may access it. | ${java.io.tmpdir}/trino-git |
git.fetch-interval |
Minimum time between fetching new commits from a repository, on every node. Set to 0s to always fetch. |
1m |
git.timeout |
Timeout for network operations when cloning or fetching a repository. | 5m |
git.commits-per-split |
Maximum number of commits read by one split of the commits, trees and diff_stats tables, so large repositories are read by many splits in parallel. |
1000 |
Keep credentials out of the catalog file by reading them from environment variables, for example
git.password=${ENV:GIT_TOKEN}. For GitHub, use a token as the password and any non-empty username.
After restarting Trino, the git catalog has the tables and views described below in the default schema.
To see who added and deleted the most lines:
SELECT
c.author_name,
count(DISTINCT c.object_id) AS commits,
sum(s.added_lines) AS added_lines,
sum(s.deleted_lines) AS deleted_lines
FROM commits c
JOIN diff_stats s ON s.commit_id = c.object_id
GROUP BY c.author_name
ORDER BY commits DESC
LIMIT 5;See examples/achievements.sql for more complex queries.
The commits, diff_stats and trees tables read every commit reachable from any ref:
HEAD, branches and tags of the remote repository.
Branches of the remote repository.
| Column | Type | Description |
|---|---|---|
object_id |
varchar |
Id of the commit the branch points to |
name |
varchar |
Full ref name, like refs/heads/main |
is_merged |
boolean |
Whether the branch is merged into the HEAD commit |
| Column | Type | Description |
|---|---|---|
object_id |
varchar |
Commit id |
author_name |
varchar |
Author name |
author_email |
varchar |
Author email |
committer_name |
varchar |
Committer name |
committer_email |
varchar |
Committer email |
message |
varchar |
Full commit message |
parents |
array(varchar) |
Ids of the parent commits |
tree_id |
varchar |
Id of the root tree |
commit_time |
timestamp(0) with time zone |
Time when the commit was made, in the committer's time zone |
author_time |
timestamp(0) with time zone |
Time when the change was originally made, in the author's time zone, which differs from commit_time after a rebase or a cherry-pick |
Time zone offsets that are out of range, which some old repositories have, like +5000, are read as UTC.
Files changed in every commit compared with its first parent, with renames detected.
Merge commits are compared only with their first parent, like git log -p --first-parent,
and root commits with an empty tree, so all their files are added.
Copies are reported only for files moved to more than one path in the same commit: one is a rename, and the others
are copies. Copying a file that is kept is an added file.
| Column | Type | Description |
|---|---|---|
commit_id |
varchar |
Commit id |
old_commit_id |
varchar |
Id of the first parent commit, NULL for a root commit |
object_id |
varchar |
Id of the new file contents, NULL for a deleted file |
old_object_id |
varchar |
Id of the old file contents, NULL for an added file |
path_name |
varchar |
New file path, NULL for a deleted file |
old_path_name |
varchar |
Old file path, NULL for an added file |
change_type |
varchar |
One of added, modified, deleted, renamed, copied |
similarity_score |
integer |
Similarity of the old and new file for renames and copies, 0 to 100 |
added_lines |
integer |
Number of added lines, NULL for binary files |
deleted_lines |
integer |
Number of deleted lines, NULL for binary files |
Contents of every file (blob) reachable from any branch or tag.
| Column | Type | Description |
|---|---|---|
object_id |
varchar |
Blob id |
contents |
varbinary |
File contents, NULL for blobs too large to load, over 50 MB by default |
| Column | Type | Description |
|---|---|---|
object_id |
varchar |
Id of the object the tag points to: a tag object or a commit |
commit_id |
varchar |
Id of the tagged commit, NULL if the tag points to a tree or a blob |
name |
varchar |
Full ref name, like refs/tags/v1.0 |
tag_time |
timestamp(0) with time zone |
Time of an annotated tag, NULL for lightweight tags and tags without one |
All files and directories in every commit. Directories come before their contents.
| Column | Type | Description |
|---|---|---|
commit_id |
varchar |
Commit id |
file_mode |
varchar |
One of file, executable, symlink, submodule, directory |
mode |
varchar |
Raw file mode in octal, like 100644, the same as in git ls-tree |
object_id |
varchar |
Id of the file contents, the directory tree, or the submodule commit |
file_name |
varchar |
File name |
path_name |
varchar |
Full file path |
attributes |
map(varchar, varchar) |
Git attributes of the file from .gitattributes, true or false for attributes that are set or unset |
depth |
integer |
Number of parent directories of the file |
Authors and committers grouped into people: names used with the same email, and emails used with the same name,
belong to the same person, also indirectly. Names and emails are first mapped using the .mailmap file from HEAD,
like git shortlog does, and the mapped identity becomes the main one.
Otherwise, the main identity is the first name and email in alphabetical order.
| Column | Type | Description |
|---|---|---|
email |
varchar |
Main email |
name |
varchar |
Main name |
extra_emails |
array(varchar) |
Other emails used by the same person |
extra_names |
array(varchar) |
Other names used by the same person |
The connector also provides a view:
commit_stats- every column ofcommits, with the number ofadded_lines,deleted_linesandchanged_files, the averagesimilarity_scoreof renames and copies, and allchange_typesfromdiff_stats.
These filters make the connector read less data:
commits.object_id,trees.commit_idanddiff_stats.commit_id- only the selected commits are read.objects.object_id- only the selected objects are read, and they don't have to be reachable from any branch or tag.trees.path_nameanddiff_stats.path_name- other files are skipped. Trino still applies these filters, because the connector also returns directories containing matching files, and renames of them.
Filters need to compare columns with constants, like path_name = 'README.md' or object_id IN (...).
CALL git.system.refresh()- makes every node fetch new commits the next time it reads a repository, without waiting forgit.fetch-interval. Pass a schema name, likeCALL git.system.refresh('default'), to refresh only that repository.
- Every worker clones the repositories into
git.cache-directory, and keeps them there. git.usernameandgit.passwordare used for all repositories.- When joining
treeswithcommits, putcommitson the right side of the join, liketrees t JOIN commits c ON t.commit_id = c.object_id. The connector doesn't provide table statistics, so Trino doesn't reorder such joins, and only this order lets Trino read the trees of the matching commits instead of all of them. - Queries can return data up to
git.fetch-intervalold. All tables in one query read the same commits: the coordinator pins the refs of every repository when a query first reads it, so the coordinator keeps a clone too.
Run all the tests:
./mvnw testBuild the plugin ZIP package, target/trino-git-<version>.zip:
./mvnw clean package -DskipTestsThe ZIP has a trino-git-<version> directory with the plugin and all its dependencies.
Unzip it, and copy that directory as plugin/git on every node in your Trino cluster.
An example command to run the Trino server with a locally built plugin and the git catalog:
VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)
TRINO_VERSION=$(./mvnw help:evaluate -Dexpression=dep.trino.version -q -DforceStdout)
unzip -o -d target target/trino-git-$VERSION.zip
docker run \
-d \
--name trino \
-e REPO_URL=https://github.com/nineinchnick/trino-git.git \
-v "$PWD/target/trino-git-$VERSION:/usr/lib/trino/plugin/git" \
-v "$PWD/catalog/git.properties:/etc/trino/catalog/git.properties" \
-p 8080:8080 \
trinodb/trino:$TRINO_VERSIONConnect to that server using:
docker exec -it trino trino --catalog git --schema defaultIf you're looking to analyze the structure or contents of a Git repo, gitbase could be more suitable for such task. It could even work with Trino, since Trino has a MySQL connector.
If you also want to analyze Github issues, pull requests (with review comments) or workflow runs and jobs, check out the Github connector in trino-rest.
This effort is inspired by Acha, to be able to calculate achievements based on contents of a Git repository using SQL.