Repository: github/cleanowners

Issue:
Create an Issue or PR for repos with no CODEOWNERS file

Hints from issue comments:
@github-actions: This issue is stale because it has been open 21 days with no activity. Remove stale label or comment or this will be closed in 14 days.@electriquo: @zkoppert Isn't this issue a duplication of https://github.com/github/cleanowners/issues/23?
@electriquo: In this block https://github.com/github/cleanowners/blob/211f5bfcab005f8a90b949d5d68ea867e3ed27d1/cleanowners.py#L96-L99 there is variable named `no_codeowners_count` that counts the amount of repositories without CODEOWNERS file. In this block the `repo` variable can be used to get the repository name without a CODEOWNERS file. The repository name can be added to a list that holds repositories without a CODEOWNERS file.

Later on, `no_codeowners_count` variable can be use to check whether it is greater than zero, and open an issue with the list of repositories without a CODEOWNERS file.

I suggest to always use the same issue and update/close/reopn it accordingly.

A followup question, assume there is a CODEONWERS file and it is empty. What should happen then (maybe https://github.com/github/cleanowners/issues/11)?@zkoppert: Closing as duplicate to #23 

Solution patch:
diff --git a/cleanowners.py b/cleanowners.py
index 3a214ec..cbb034d 100644
--- a/cleanowners.py
+++ b/cleanowners.py
@@ -69,6 +69,7 @@ def main():  # pragma: no cover
     repos = get_repos_iterator(organization, repository_list, github_connection)
 
     repo_and_users_to_remove = {}
+    repos_missing_codeowners = []
     for repo in repos:
         # Check if the repository is in the exempt_repositories_list
         if repo.full_name in exempt_repositories_list:
@@ -87,6 +88,7 @@ def main():  # pragma: no cover
         if not codeowners_file_contents:
             print(f"Skipping {repo.full_name} as it does not have a CODEOWNERS file")
             no_codeowners_count += 1
+            repos_missing_codeowners.append(repo)
             continue
 
         codeowners_count += 1
@@ -167,6 +169,7 @@ def main():  # pragma: no cover
             no_codeowners_count,
             codeowners_count,
             repo_and_users_to_remove,
+            repos_missing_codeowners,
         )
 
 
diff --git a/markdown_writer.py b/markdown_writer.py
index 89f0be8..443043c 100644
--- a/markdown_writer.py
+++ b/markdown_writer.py
@@ -7,6 +7,7 @@ def write_to_markdown(
     no_codeowners_count,
     codeowners_count,
     repo_and_users_to_remove,
+    repos_missing_codeowners,
 ):
     """Write the results to a markdown file"""
     with open("report.md", "w", encoding="utf-8") as file:
@@ -25,3 +26,8 @@ def write_to_markdown(
                 for user in users:
                     file.write(f"- {user}\n")
                 file.write("\n")
+        if repos_missing_codeowners:
+            file.write("## Repositories Missing CODEOWNERS\n")
+            for repo in repos_missing_codeowners:
+                file.write(f"- {repo}\n")
+            file.write("\n")


Test patch:
diff --git a/test_markdown_writer.py b/test_markdown_writer.py
index 48b9d3e..b63258d 100644
--- a/test_markdown_writer.py
+++ b/test_markdown_writer.py
@@ -1,4 +1,4 @@
-""" Unit tests for the write_to_markdown function in markdown_writer.py """
+"""Unit tests for the write_to_markdown function in markdown_writer.py"""
 
 import unittest
 from unittest.mock import call, mock_open, patch
@@ -13,7 +13,7 @@ class TestWriteToMarkdown(unittest.TestCase):
         """Test that the function writes the correct markdown when there are no users to remove"""
         mock_file = mock_open()
         with patch("builtins.open", mock_file):
-            write_to_markdown(0, 0, 2, 3, {})
+            write_to_markdown(0, 0, 2, 3, {}, [])
             mock_file().write.assert_called_once_with(
                 "# Cleanowners Report\n\n"
                 "## Overall Stats\n"
@@ -28,7 +28,7 @@ class TestWriteToMarkdown(unittest.TestCase):
         mock_file = mock_open()
         repo_users = {"repo1": ["user1", "user2"], "repo2": ["user3"]}
         with patch("builtins.open", mock_file):
-            write_to_markdown(1, 2, 3, 4, repo_users)
+            write_to_markdown(1, 2, 3, 4, repo_users, [])
             calls = [
                 call(
                     "# Cleanowners Report\n\n"
@@ -49,11 +49,33 @@ class TestWriteToMarkdown(unittest.TestCase):
             ]
             mock_file().write.assert_has_calls(calls, any_order=False)
 
+    def test_write_with_repos_missing_codeowners(self):
+        """Test that the function writes the correct markdown when there are repos missing CODEOWNERS"""
+        mock_file = mock_open()
+        repos_missing_codeowners = ["repo1", "repo2"]
+        with patch("builtins.open", mock_file):
+            write_to_markdown(0, 0, 2, 0, {}, repos_missing_codeowners)
+            calls = [
+                call(
+                    "# Cleanowners Report\n\n"
+                    "## Overall Stats\n"
+                    "0 Users to Remove\n"
+                    "0 Pull Requests created\n"
+                    "2 Repositories with no CODEOWNERS file\n"
+                    "0 Repositories with CODEOWNERS file\n"
+                ),
+                call("## Repositories Missing CODEOWNERS\n"),
+                call("- repo1\n"),
+                call("- repo2\n"),
+                call("\n"),
+            ]
+            mock_file().write.assert_has_calls(calls, any_order=False)
+
     def test_write_with_empty_inputs(self):
         """Test that the function writes the correct markdown when all inputs are 0"""
         mock_file = mock_open()
         with patch("builtins.open", mock_file):
-            write_to_markdown(0, 0, 0, 0, {})
+            write_to_markdown(0, 0, 0, 0, {}, [])
             mock_file().write.assert_called_once_with(
                 "# Cleanowners Report\n\n"
                 "## Overall Stats\n"