Repository: noqdev/iambic
Issue: The Git Commit on changing relative time to absolute time is including unrelated changes

**Describe the bug**
The Git Commit on changing relative time to absolute time is including unrelated changes

**To Reproduce**
Steps to reproduce the behavior:

1. Add a new template with non-alphabetical ordering, commit to template repository
2. Add a PR that modifies the just added template. 
3. Go through the iambic apply workflow on that PR.
4. Note the git commit associated with relative time to absolute time, it's re-ordering the alphabetical changes.

**Expected behavior**
The git commit associated relative time -> abosolute time, should not include unrelated changes.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**

- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]

**Additional context**
Add any other context about the problem here.

**Community Engagement**
Your vote counts! Please support this bug report by adding a 👍 reaction to the original issue, which will aid the community and maintainers in addressing this problem.

Please refrain from adding "+1" or "me too" comments, as these create unnecessary noise for issue followers and do not help in prioritizing the issue. If you wish to contribute to solving this issue or have submitted a pull request, please leave a comment.


Changed files:
test/plugins/v0_1_0/github/test_github.py, iambic/plugins/v0_1_0/github/github.py, iambic/request_handler/git_apply.py

diff --git a/iambic/plugins/v0_1_0/github/github.py b/iambic/plugins/v0_1_0/github/github.py
index abc8c2b2..886c48c3 100644
--- a/iambic/plugins/v0_1_0/github/github.py
+++ b/iambic/plugins/v0_1_0/github/github.py
@@ -31,6 +31,7 @@ from iambic.core.utils import decode_with_reference_time, yaml
 from iambic.main import run_apply, run_detect, run_expire, run_git_apply, run_git_plan
 from iambic.plugins.v0_1_0.github.iambic_plugin import GithubConfig
 from iambic.plugins.v0_1_0.github.utils import IAMBIC_APPLY_ERROR_METADATA
+from iambic.request_handler.git_apply import lint_git_changes
 
 iambic_app = __import__("iambic.lambda.app", globals(), locals(), [], 0)
 lambda_run_handler = getattr(iambic_app, "lambda").app.run_handler
@@ -46,6 +47,7 @@ COMMIT_MESSAGE_FOR_DETECT = "Import changes from detect operation"
 COMMIT_MESSAGE_FOR_IMPORT = "Import changes from import operation"
 COMMIT_MESSAGE_FOR_EXPIRE = "Periodic Expiration"
 COMMIT_MESSAGE_FOR_GIT_APPLY_ABSOLUTE_TIME = "Replace relative time with absolute time"
+COMMIT_MESSAGE_FOR_LINTING = "Format template using internal linting rules"
 SHARED_CONTAINER_GITHUB_DIRECTORY = "/root/data"
 
 
@@ -379,6 +381,16 @@ def handle_iambic_git_apply(
             # but templates config is now already stored in the templates repo itself.
             getattr(iambic_app, "lambda").app.PLAN_OUTPUT_PATH = proposed_changes_path
 
+        # run lint before apply to separate out formatting change from the relative time change
+        config_path = asyncio.run(resolve_config_template_path(repo_dir))
+        asyncio.run(lint_git_changes(config_path, repo_dir, False, None, None))
+        repo.git.add(".")
+        diff_list = repo.head.commit.diff()
+        if len(diff_list) > 0:
+            repo.git.commit("-m", COMMIT_MESSAGE_FOR_LINTING)
+        else:
+            log.debug("git_apply did not introduce linting changes")
+
         template_changes = run_git_apply(
             False, None, None, repo_dir, proposed_changes_path
         )
diff --git a/iambic/request_handler/git_apply.py b/iambic/request_handler/git_apply.py
index 630819b5..46f41e80 100644
--- a/iambic/request_handler/git_apply.py
+++ b/iambic/request_handler/git_apply.py
@@ -105,6 +105,61 @@ async def apply_git_changes(
     return template_changes
 
 
+async def lint_git_changes(
+    config_path: str,
+    repo_dir: str,
+    allow_dirty: bool = False,
+    from_sha: str = None,
+    to_sha: str = None,
+) -> list[TemplateChangeDetails]:
+    """Retrieves files added/updated/or removed when comparing the current branch to master
+
+    Works by taking the diff and adding implied changes to the templates that were modified.
+    These are the changes and this function detects:
+        Deleting a file
+        Removing 1 or more aws_accounts from included_accounts
+        Adding 1 or more aws_accounts to excluded_accounts
+
+    :param config_path:
+    :param repo_dir:
+    :param context:
+    :param allow_dirty:
+    :param from_sha:
+    :param to_sha:
+    :return:
+    """
+
+    config = await load_config(config_path)
+    file_changes = await retrieve_git_changes(
+        repo_dir,
+        config.template_map,
+        allow_dirty=allow_dirty,
+        from_sha=from_sha,
+        to_sha=to_sha,
+    )
+    if (
+        not file_changes["new_files"]
+        and not file_changes["modified_files"]
+        and not file_changes["deleted_files"]
+    ):
+        log.info("No changes found.")
+        return []
+
+    new_templates = load_templates(
+        [git_diff.path for git_diff in file_changes["new_files"]],
+        config.template_map,
+    )
+
+    modified_templates_doubles = create_templates_for_modified_files(
+        config,
+        file_changes["modified_files"],
+    )
+
+    for template in itertools.chain(new_templates, modified_templates_doubles):
+        # write always write in linted format
+        template.write()
+
+
 def commit_deleted_templates(
     repo_dir: str, templates: list[BaseTemplate], details: list[TemplateChangeDetails]
 ):
diff --git a/test/plugins/v0_1_0/github/test_github.py b/test/plugins/v0_1_0/github/test_github.py
index 23fec202..f8460de5 100644
--- a/test/plugins/v0_1_0/github/test_github.py
+++ b/test/plugins/v0_1_0/github/test_github.py
@@ -185,6 +185,14 @@ def mock_run_git_apply():
                     yield _mock_run_git_plan
 
 
+@pytest.fixture
+def mock_lint_git_changes():
+    with patch(
+        "iambic.plugins.v0_1_0.github.github.lint_git_changes", autospec=True
+    ) as _mock_lint_git_changes:
+        yield _mock_lint_git_changes
+
+
 @pytest.fixture
 def mock_repository():
     with patch("iambic.core.git.Repo", autospec=True) as _mock_repository:
@@ -214,6 +222,8 @@ def test_issue_comment_with_not_applicable_comment_body(
 def test_issue_comment_with_clean_mergeable_state(
     mock_github_client,
     issue_comment_git_apply_context,
+    mock_lint_git_changes,
+    mock_resolve_config_template_path,
     mock_run_git_apply,
     mock_repository,
 ):
@@ -225,6 +235,8 @@ def test_issue_comment_with_clean_mergeable_state(
         issue_comment_git_apply_context["sha"]
     )
     handle_issue_comment(mock_github_client, issue_comment_git_apply_context)
+    assert mock_resolve_config_template_path.called
+    assert mock_lint_git_changes.called
     assert mock_run_git_apply.called
     assert mock_pull_request.merge.called
 
@@ -233,6 +245,8 @@ def test_issue_comment_with_clean_mergeable_state(
 def test_issue_comment_with_clean_mergeable_state_and_lambda_handler_crashed(
     mock_github_client,
     issue_comment_git_apply_context,
+    mock_lint_git_changes,
+    mock_resolve_config_template_path,
     mock_run_git_apply,
     mock_repository,
 ):
@@ -245,6 +259,8 @@ def test_issue_comment_with_clean_mergeable_state_and_lambda_handler_crashed(
     mock_run_git_apply.side_effect = Exception("unexpected failure")
     with pytest.raises(Exception):
         handle_issue_comment(mock_github_client, issue_comment_git_apply_context)
+    assert mock_resolve_config_template_path.called
+    assert mock_lint_git_changes.called
     assert mock_run_git_apply.called
     assert mock_pull_request.create_issue_comment.called
     assert "Traceback" in mock_pull_request.create_issue_comment.call_args[0][0]
@@ -443,6 +459,8 @@ def test_issue_comment_with_not_allowed_approver(
 def test_issue_comment_with_clean_mergeable_state_with_additional_commits(
     mock_github_client,
     issue_comment_git_apply_context,
+    mock_lint_git_changes,
+    mock_resolve_config_template_path,
     mock_run_git_apply,
     mock_repository,
 ):
@@ -462,6 +480,8 @@ def test_issue_comment_with_clean_mergeable_state_with_additional_commits(
     )
 
     handle_issue_comment(mock_github_client, issue_comment_git_apply_context)
+    assert mock_resolve_config_template_path.called
+    assert mock_lint_git_changes.called
     assert mock_run_git_apply.called
 
     # verify we did push back the changes to remote