Skip to content

Commit

Permalink
Merge pull request #5 from github/csv
Browse files Browse the repository at this point in the history
feat: write results to markdown
  • Loading branch information
zkoppert committed May 24, 2023
2 parents e9fd795 + bd25899 commit d0fe67a
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 20 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/use-action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,10 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
ORGANIZATION: github
INACTIVE_DAYS: 1

- name: Create issue
uses: peter-evans/create-issue-from-file@v4
with:
title: Stale repository report
content-filepath: ./stale_repos.md
assignees: zkoppert
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,25 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
ORGANIZATION: ${{ secrets.ORGANIZATION }}
INACTIVE_DAYS: 365
INACTIVE_DAYS: 365

- name: Create issue
uses: peter-evans/create-issue-from-file@v4
with:
title: Stale repository report
content-filepath: ./stale_repos.md
assignees: <YOUR_GITHUB_HANDLE_HERE>

```

### Example stale_repos.md output

```markdown
# Inactive Repositories

| Repository URL | Days Inactive |
| --- | ---: |
| https://github.com/github/.github | 5 |
```

## Local usage without Docker
Expand Down
42 changes: 35 additions & 7 deletions stale_repos.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
""" Find stale repositories in a GitHub organization. """

import os
from datetime import datetime
from datetime import datetime, timezone
from os.path import dirname, join

import github3
Expand Down Expand Up @@ -46,29 +46,57 @@ def main():

# Iterate over repos in the org, acquire inactive days,
# and print out the repo url and days inactive if it's over the threshold (inactive_days)
print_inactive_repos(github_connection, inactive_days_threshold, organization)
inactive_repos = get_inactive_repos(
github_connection, inactive_days_threshold, organization
)

# Write the list of inactive repos to a csv file
write_to_markdown(inactive_repos)

def print_inactive_repos(github_connection, inactive_days_threshold, organization):
"""Print out the repo url and days inactive if it's over the threshold (inactive_days).

def get_inactive_repos(github_connection, inactive_days_threshold, organization):
"""Return and print out the repo url and days inactive if it's over
the threshold (inactive_days).
Args:
github_connection: The GitHub connection object.
inactive_days_threshold: The threshold (in days) for considering a repo as inactive.
organization: The name of the organization to retrieve repositories from.
Returns:
A list of tuples containing the repo and days inactive.
"""
inactive_repos = []
for repo in github_connection.repositories_by(organization):
last_push_str = repo.pushed_at # type: ignore
if last_push_str is None:
continue
last_push = parse(last_push_str)
days_inactive = (datetime.now() - last_push).days
if days_inactive > int(inactive_days_threshold):
inactive_repos.append((repo, days_inactive))
days_inactive = (datetime.now(timezone.utc) - last_push).days
if days_inactive > int(inactive_days_threshold) and not repo.archived:
inactive_repos.append((repo.html_url, days_inactive))
print(f"{repo.html_url}: {days_inactive} days inactive") # type: ignore
print(f"Found {len(inactive_repos)} stale repos in {organization}")
return inactive_repos


def write_to_markdown(inactive_repos, file=None):
"""Write the list of inactive repos to a markdown file.
Args:
inactive_repos: A list of tuples containing the repo and days inactive.
file: A file object to write to. If None, a new file will be created.
"""
inactive_repos.sort(key=lambda x: x[1], reverse=True)
with file or open("stale_repos.md", "w", encoding="utf-8") as file:
file.write("# Inactive Repositories\n\n")
file.write("| Repository URL | Days Inactive |\n")
file.write("| --- | ---: |\n")
for repo_url, days_inactive in inactive_repos:
file.write(f"| {repo_url} | {days_inactive} |\n")
print("Wrote stale repos to stale_repos.md")


def auth_to_github():
Expand Down
62 changes: 50 additions & 12 deletions test_stale_repos.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
import io
import os
import unittest
from datetime import datetime, timedelta
from unittest.mock import MagicMock, patch
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock, call, patch

import github3.github
from stale_repos import auth_to_github, print_inactive_repos
from stale_repos import auth_to_github, get_inactive_repos, write_to_markdown


class AuthToGithubTestCase(unittest.TestCase):
Expand Down Expand Up @@ -149,23 +149,25 @@ def test_print_inactive_repos_with_inactive_repos(self):
github_connection = MagicMock()

# Create a mock repository object with a last push time of 30 days ago
thirty_days_ago = datetime.now() - timedelta(days=30)
thirty_days_ago = datetime.now(timezone.utc) - timedelta(days=30)
mock_repo = MagicMock()
mock_repo.pushed_at = thirty_days_ago.isoformat()
mock_repo.html_url = "https://github.com/example/repo"
mock_repo.archived = False
github_connection.repositories_by.return_value = [mock_repo]

# Call the function with a threshold of 20 days
inactive_days_threshold = 20
organization = "example"
with patch("sys.stdout", new_callable=io.StringIO) as mock_stdout:
print_inactive_repos(
github_connection, inactive_days_threshold, organization
)
get_inactive_repos(github_connection, inactive_days_threshold, organization)
output = mock_stdout.getvalue()

# Check that the output contains the expected repo URL and days inactive
expected_output = f"{mock_repo.html_url}: 30 days inactive\nFound 1 stale repos in {organization}\n"
expected_output = (
f"{mock_repo.html_url}: 30 days inactive\n"
f"Found 1 stale repos in {organization}\n"
)
self.assertEqual(expected_output, output)

def test_print_inactive_repos_with_no_inactive_repos(self):
Expand All @@ -179,7 +181,7 @@ def test_print_inactive_repos_with_no_inactive_repos(self):
github_connection = MagicMock()

# Create a mock repository object with a last push time of 30 days ago
thirty_days_ago = datetime.now() - timedelta(days=30)
thirty_days_ago = datetime.now(timezone.utc) - timedelta(days=30)
mock_repo = MagicMock()
mock_repo.pushed_at = thirty_days_ago.isoformat()
mock_repo.html_url = "https://github.com/example/repo"
Expand All @@ -189,15 +191,51 @@ def test_print_inactive_repos_with_no_inactive_repos(self):
inactive_days_threshold = 40
organization = "example"
with patch("sys.stdout", new_callable=io.StringIO) as mock_stdout:
print_inactive_repos(
github_connection, inactive_days_threshold, organization
)
get_inactive_repos(github_connection, inactive_days_threshold, organization)
output = mock_stdout.getvalue()

# Check that the output contains the expected repo URL and days inactive
expected_output = f"Found 0 stale repos in {organization}\n"
self.assertEqual(expected_output, output)


class WriteToMarkdownTestCase(unittest.TestCase):
"""
Unit test case for the write_to_markdown() function.
"""

def test_write_to_markdown(self):
"""Test that the write_to_markdown function writes the expected data to a file.
This test creates a list of inactive repos and a mock file object using MagicMock.
It then calls the write_to_markdown function with the list of inactive repos and
the mock file object. Finally, it uses the assert_has_calls method to check that
the mock file object was called with the expected data.
"""

# Create a list of inactive repos
inactive_repos = [
("https://github.com/example/repo2", 40),
("https://github.com/example/repo1", 30),
]

# Create a mock file object
mock_file = MagicMock()

# Call the write_to_markdown function with the mock file object
write_to_markdown(inactive_repos, file=mock_file)

# Check that the mock file object was called with the expected data
expected_calls = [
call.write("# Inactive Repositories\n\n"),
call.write("| Repository URL | Days Inactive |\n"),
call.write("| --- | ---: |\n"),
call.write("| https://github.com/example/repo2 | 40 |\n"),
call.write("| https://github.com/example/repo1 | 30 |\n"),
]
mock_file.__enter__.return_value.assert_has_calls(expected_calls)


if __name__ == "__main__":
unittest.main()

0 comments on commit d0fe67a

Please sign in to comment.