From 8b566d28d1a0f41b36968fb118c365c2182ee52b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 15:33:14 +0000 Subject: [PATCH 01/11] Initial plan From 4aced1547dee9ef91845667fc03516f1735fe518 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 15:38:45 +0000 Subject: [PATCH 02/11] Add validation to check END_DATE is not before START_DATE Co-authored-by: zkoppert <6935431+zkoppert@users.noreply.github.com> --- env.py | 26 +++++++++++++++++ test_env.py | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/env.py b/env.py index b160dc2..6712d08 100644 --- a/env.py +++ b/env.py @@ -70,6 +70,31 @@ def validate_date_format(env_var_name: str) -> str: return date_to_validate +def validate_date_range(start_date: str, end_date: str) -> None: + """Validate that start_date is before or equal to end_date. + + Does nothing if either date is not set. + + Args: + start_date: The start date string in YYYY-MM-DD format. + end_date: The end date string in YYYY-MM-DD format. + + Raises: + ValueError: If end_date is before start_date. + """ + if not start_date or not end_date: + return + + pattern = "%Y-%m-%d" + start_dt = datetime.datetime.strptime(start_date, pattern) + end_dt = datetime.datetime.strptime(end_date, pattern) + + if end_dt < start_dt: + raise ValueError( + f"END_DATE ('{end_date}') must be on or after START_DATE ('{start_date}')" + ) + + def get_env_vars( test: bool = False, ) -> tuple[ @@ -142,6 +167,7 @@ def get_env_vars( start_date = validate_date_format("START_DATE") end_date = validate_date_format("END_DATE") + validate_date_range(start_date, end_date) sponsor_info = get_bool_env_var("SPONSOR_INFO", False) link_to_profile = get_bool_env_var("LINK_TO_PROFILE", False) diff --git a/test_env.py b/test_env.py index 638e6e5..6f51435 100644 --- a/test_env.py +++ b/test_env.py @@ -222,6 +222,87 @@ def test_get_env_vars_auth_with_github_app_installation_missing_inputs(self): "GH_APP_ID set and GH_APP_INSTALLATION_ID or GH_APP_PRIVATE_KEY variable not set", ) + @patch.dict( + os.environ, + { + "ORGANIZATION": "org", + "REPOSITORY": "repo", + "GH_TOKEN": "token", + "START_DATE": "2025-01-01", + "END_DATE": "2024-01-01", + }, + clear=True, + ) + def test_get_env_vars_end_date_before_start_date(self): + """Test that an error is raised when END_DATE is before START_DATE""" + with self.assertRaises(ValueError) as cm: + env.get_env_vars() + the_exception = cm.exception + self.assertEqual( + str(the_exception), + "END_DATE ('2024-01-01') must be on or after START_DATE ('2025-01-01')", + ) + + @patch.dict( + os.environ, + { + "ORGANIZATION": "org", + "REPOSITORY": "repo", + "GH_TOKEN": "token", + "START_DATE": "2024-01-01", + "END_DATE": "2024-01-01", + }, + clear=True, + ) + def test_get_env_vars_equal_start_and_end_date(self): + """Test that equal START_DATE and END_DATE are allowed""" + ( + _, + _, + _, + _, + _, + _, + _, + _, + start_date, + end_date, + _, + _, + ) = env.get_env_vars() + self.assertEqual(start_date, "2024-01-01") + self.assertEqual(end_date, "2024-01-01") + + @patch.dict( + os.environ, + { + "ORGANIZATION": "org", + "REPOSITORY": "repo", + "GH_TOKEN": "token", + "START_DATE": "2024-01-01", + "END_DATE": "2025-01-01", + }, + clear=True, + ) + def test_get_env_vars_valid_date_range(self): + """Test that valid date range (START_DATE before END_DATE) is accepted""" + ( + _, + _, + _, + _, + _, + _, + _, + _, + start_date, + end_date, + _, + _, + ) = env.get_env_vars() + self.assertEqual(start_date, "2024-01-01") + self.assertEqual(end_date, "2025-01-01") + if __name__ == "__main__": unittest.main() From 5f38ece56b4b9abbf5fb2b2c39a2a0ae9aa17b6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 15:41:58 +0000 Subject: [PATCH 03/11] Address code review feedback: simplify date comparison and improve test readability Co-authored-by: zkoppert <6935431+zkoppert@users.noreply.github.com> --- env.py | 7 ++----- test_env.py | 34 ++++++---------------------------- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/env.py b/env.py index 6712d08..a4a5e91 100644 --- a/env.py +++ b/env.py @@ -85,11 +85,8 @@ def validate_date_range(start_date: str, end_date: str) -> None: if not start_date or not end_date: return - pattern = "%Y-%m-%d" - start_dt = datetime.datetime.strptime(start_date, pattern) - end_dt = datetime.datetime.strptime(end_date, pattern) - - if end_dt < start_dt: + # YYYY-MM-DD format allows direct string comparison + if end_date < start_date: raise ValueError( f"END_DATE ('{end_date}') must be on or after START_DATE ('{start_date}')" ) diff --git a/test_env.py b/test_env.py index 6f51435..12898fc 100644 --- a/test_env.py +++ b/test_env.py @@ -256,20 +256,9 @@ def test_get_env_vars_end_date_before_start_date(self): ) def test_get_env_vars_equal_start_and_end_date(self): """Test that equal START_DATE and END_DATE are allowed""" - ( - _, - _, - _, - _, - _, - _, - _, - _, - start_date, - end_date, - _, - _, - ) = env.get_env_vars() + result = env.get_env_vars() + start_date = result[8] + end_date = result[9] self.assertEqual(start_date, "2024-01-01") self.assertEqual(end_date, "2024-01-01") @@ -286,20 +275,9 @@ def test_get_env_vars_equal_start_and_end_date(self): ) def test_get_env_vars_valid_date_range(self): """Test that valid date range (START_DATE before END_DATE) is accepted""" - ( - _, - _, - _, - _, - _, - _, - _, - _, - start_date, - end_date, - _, - _, - ) = env.get_env_vars() + result = env.get_env_vars() + start_date = result[8] + end_date = result[9] self.assertEqual(start_date, "2024-01-01") self.assertEqual(end_date, "2025-01-01") From 2f4b463fb467294147d06235bbbc6d00993de034 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 15:43:51 +0000 Subject: [PATCH 04/11] Simplify test assertions for better clarity Co-authored-by: zkoppert <6935431+zkoppert@users.noreply.github.com> --- test_env.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/test_env.py b/test_env.py index 12898fc..64dab9d 100644 --- a/test_env.py +++ b/test_env.py @@ -257,10 +257,8 @@ def test_get_env_vars_end_date_before_start_date(self): def test_get_env_vars_equal_start_and_end_date(self): """Test that equal START_DATE and END_DATE are allowed""" result = env.get_env_vars() - start_date = result[8] - end_date = result[9] - self.assertEqual(start_date, "2024-01-01") - self.assertEqual(end_date, "2024-01-01") + self.assertEqual(result[8], "2024-01-01") # start_date + self.assertEqual(result[9], "2024-01-01") # end_date @patch.dict( os.environ, @@ -276,10 +274,8 @@ def test_get_env_vars_equal_start_and_end_date(self): def test_get_env_vars_valid_date_range(self): """Test that valid date range (START_DATE before END_DATE) is accepted""" result = env.get_env_vars() - start_date = result[8] - end_date = result[9] - self.assertEqual(start_date, "2024-01-01") - self.assertEqual(end_date, "2025-01-01") + self.assertEqual(result[8], "2024-01-01") # start_date + self.assertEqual(result[9], "2025-01-01") # end_date if __name__ == "__main__": From 9d8c0bda9f29d72117dac2e7d62091533ae69ce0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 17:14:06 +0000 Subject: [PATCH 05/11] Change validation to require END_DATE strictly after START_DATE Co-authored-by: zkoppert <6935431+zkoppert@users.noreply.github.com> --- env.py | 8 ++++---- test_env.py | 14 +++++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/env.py b/env.py index a4a5e91..0e412e4 100644 --- a/env.py +++ b/env.py @@ -71,7 +71,7 @@ def validate_date_format(env_var_name: str) -> str: def validate_date_range(start_date: str, end_date: str) -> None: - """Validate that start_date is before or equal to end_date. + """Validate that start_date is before end_date. Does nothing if either date is not set. @@ -80,15 +80,15 @@ def validate_date_range(start_date: str, end_date: str) -> None: end_date: The end date string in YYYY-MM-DD format. Raises: - ValueError: If end_date is before start_date. + ValueError: If end_date is before or equal to start_date. """ if not start_date or not end_date: return # YYYY-MM-DD format allows direct string comparison - if end_date < start_date: + if end_date <= start_date: raise ValueError( - f"END_DATE ('{end_date}') must be on or after START_DATE ('{start_date}')" + f"END_DATE ('{end_date}') must be after START_DATE ('{start_date}')" ) diff --git a/test_env.py b/test_env.py index 64dab9d..6059760 100644 --- a/test_env.py +++ b/test_env.py @@ -240,7 +240,7 @@ def test_get_env_vars_end_date_before_start_date(self): the_exception = cm.exception self.assertEqual( str(the_exception), - "END_DATE ('2024-01-01') must be on or after START_DATE ('2025-01-01')", + "END_DATE ('2024-01-01') must be after START_DATE ('2025-01-01')", ) @patch.dict( @@ -255,10 +255,14 @@ def test_get_env_vars_end_date_before_start_date(self): clear=True, ) def test_get_env_vars_equal_start_and_end_date(self): - """Test that equal START_DATE and END_DATE are allowed""" - result = env.get_env_vars() - self.assertEqual(result[8], "2024-01-01") # start_date - self.assertEqual(result[9], "2024-01-01") # end_date + """Test that an error is raised when START_DATE equals END_DATE""" + with self.assertRaises(ValueError) as cm: + env.get_env_vars() + the_exception = cm.exception + self.assertEqual( + str(the_exception), + "END_DATE ('2024-01-01') must be after START_DATE ('2024-01-01')", + ) @patch.dict( os.environ, From 82e77d928fe5659bc98d6aacd02c63fa676e94b6 Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Tue, 10 Feb 2026 11:17:21 -0800 Subject: [PATCH 06/11] Update Super Linter version to v8.5.0 Signed-off-by: Zack Koppert --- .github/workflows/super-linter.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/super-linter.yaml b/.github/workflows/super-linter.yaml index a1bb627..8326c6d 100644 --- a/.github/workflows/super-linter.yaml +++ b/.github/workflows/super-linter.yaml @@ -27,7 +27,7 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt -r requirements-test.txt - name: Lint Code Base - uses: super-linter/super-linter@502f4fe48a81a392756e173e39a861f8c8efe056 + uses: super-linter/super-linter@61abc07d755095a68f4987d1c2c3d1d64408f1f9 # v8.5.0 env: DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From fa5d5f63c9d5ade15734b3af747f44849c44bf52 Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Tue, 10 Feb 2026 11:19:54 -0800 Subject: [PATCH 07/11] Update test_env.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Zack Koppert --- test_env.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/test_env.py b/test_env.py index 6059760..fb1734d 100644 --- a/test_env.py +++ b/test_env.py @@ -277,9 +277,22 @@ def test_get_env_vars_equal_start_and_end_date(self): ) def test_get_env_vars_valid_date_range(self): """Test that valid date range (START_DATE before END_DATE) is accepted""" - result = env.get_env_vars() - self.assertEqual(result[8], "2024-01-01") # start_date - self.assertEqual(result[9], "2025-01-01") # end_date + ( + _organization, + _repository_list, + _gh_app_id, + _gh_app_installation_id, + _gh_app_private_key, + _gh_app_enterprise_only, + _token, + _ghe, + start_date, + end_date, + _sponsor_info, + _link_to_profile, + ) = env.get_env_vars() + self.assertEqual(start_date, "2024-01-01") + self.assertEqual(end_date, "2025-01-01") if __name__ == "__main__": From 6e29dd6e9e01a6794748382ca23b1a7264265ab8 Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Tue, 10 Feb 2026 11:27:45 -0800 Subject: [PATCH 08/11] Update env.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Zack Koppert --- env.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/env.py b/env.py index 0e412e4..486f536 100644 --- a/env.py +++ b/env.py @@ -85,8 +85,16 @@ def validate_date_range(start_date: str, end_date: str) -> None: if not start_date or not end_date: return - # YYYY-MM-DD format allows direct string comparison - if end_date <= start_date: + pattern = "%Y-%m-%d" + try: + start = datetime.datetime.strptime(start_date, pattern).date() + end = datetime.datetime.strptime(end_date, pattern).date() + except ValueError as exc: + raise ValueError( + "start_date and end_date must be in the format YYYY-MM-DD" + ) from exc + + if end <= start: raise ValueError( f"END_DATE ('{end_date}') must be after START_DATE ('{start_date}')" ) From 52a20e12da65ef23cdb139bdc077640b9448f367 Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Tue, 10 Feb 2026 11:32:24 -0800 Subject: [PATCH 09/11] chore: fix spelling Signed-off-by: Zack Koppert --- .github/linters/.markdown-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml index c796544..615c339 100644 --- a/.github/linters/.markdown-lint.yml +++ b/.github/linters/.markdown-lint.yml @@ -20,7 +20,7 @@ ############### # line length MD013: false -# singe h1 +# single h1 MD025: false # duplicate headers MD024: false From d10345ccc7ef36e1c0963600e4bb2484391ac6ce Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Tue, 10 Feb 2026 11:38:23 -0800 Subject: [PATCH 10/11] fix: use specific hash for each action version called. Signed-off-by: Zack Koppert --- .github/workflows/copilot-setup-steps.yml | 4 ++-- .github/workflows/docker-ci.yml | 2 +- .github/workflows/python-ci.yml | 4 ++-- .github/workflows/scorecard.yml | 4 ++-- .github/workflows/stale.yml | 2 +- .github/workflows/super-linter.yaml | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index ed4fae7..6ad6a56 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -26,12 +26,12 @@ jobs: # If you do not check out your code, Copilot will do this for you. steps: - name: Checkout code - uses: actions/checkout@v6.0.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python - uses: actions/setup-python@v6.2.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: 3.12 diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml index 1f627ff..bbb93d4 100644 --- a/.github/workflows/docker-ci.yml +++ b/.github/workflows/docker-ci.yml @@ -14,7 +14,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Build the Docker image diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 45a7e78..dcac5fe 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -20,11 +20,11 @@ jobs: matrix: python-version: [3.11, 3.12] steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6.2.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 36491ad..fa0f8ca 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -25,7 +25,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@v6.0.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -36,7 +36,7 @@ jobs: results_format: sarif publish_results: true - name: "Upload artifact" - uses: actions/upload-artifact@v6.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 59b2411..cdf7790 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -11,7 +11,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v10.1.1 + - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: stale-issue-message: "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." close-issue-message: "This issue was closed because it has been stalled for 35 days with no activity." diff --git a/.github/workflows/super-linter.yaml b/.github/workflows/super-linter.yaml index 8326c6d..bab1cb9 100644 --- a/.github/workflows/super-linter.yaml +++ b/.github/workflows/super-linter.yaml @@ -18,7 +18,7 @@ jobs: statuses: write steps: - name: Checkout Code - uses: actions/checkout@v6.0.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false From c6cc92ec5a0c73ce9e4c9d231d6fbfd76741b24f Mon Sep 17 00:00:00 2001 From: Zack Koppert Date: Tue, 10 Feb 2026 11:48:00 -0800 Subject: [PATCH 11/11] fix cant spell to save my life Signed-off-by: Zack Koppert --- contributor_stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributor_stats.py b/contributor_stats.py index ffd960e..2a77353 100644 --- a/contributor_stats.py +++ b/contributor_stats.py @@ -104,7 +104,7 @@ def merge_contributors(contributors: list) -> list: merged_contributors: List[ContributorStats] = [] for contributor_list in contributors: for contributor in contributor_list: - # if the contributor is already in the merged list, merge their relavent attributes + # if the contributor is already in the merged list, merge their relevant attributes if contributor.username in [c.username for c in merged_contributors]: for merged_contributor in merged_contributors: if merged_contributor.username == contributor.username: