Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions codeplain_REST_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from plain2code_state import RunState

MAX_RETRIES = 3
MAX_RETRIES = 4
RETRY_DELAY = 3


Expand Down Expand Up @@ -131,7 +131,9 @@ def post_request(self, endpoint_url, headers, payload, run_state: Optional[RunSt
retry_delay *= 2
else:
self.console.error(f"Max retries ({MAX_RETRIES}) exceeded. Last error: {e}")
raise type(e)(f"Error requesting to the codeplain API: {e}")
raise RequestException(
f"Connection error: Unable to reach the Codeplain API at {self.api_url}. Please try again or contact support."
)

def get_plain_source_tree(self, plain_source, loaded_templates):
"""
Expand Down
6 changes: 0 additions & 6 deletions examples/example_hello_world_golang/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@ if [ $VERBOSE -eq 1 ]; then
echo "Running Go lang hello world example in verbose mode."
fi

# Check if render-range and render-from exist in config.yaml
if ! (grep -q "render-range:" $CONFIG_FILE || grep -q "render-from:" $CONFIG_FILE); then
echo "Removing conformance tests folder"
rm -rf conformance_tests
fi

# Execute the command
python ../../plain2code.py hello_world_golang.plain

Expand Down
6 changes: 0 additions & 6 deletions examples/example_hello_world_python/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ if [ $VERBOSE -eq 1 ]; then
echo "Running the hello world example for Python in verbose mode."
fi

# Check if render-range and render-from exist in config.yaml
if ! (grep -q "render-range:" $CONFIG_FILE || grep -q "render-from:" $CONFIG_FILE); then
echo "Removing conformance tests folder"
rm -rf conformance_tests
fi

# Execute the command
python ../../plain2code.py hello_world_python.plain

Expand Down
7 changes: 0 additions & 7 deletions examples/example_hello_world_react/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,6 @@ if [ $VERBOSE -eq 1 ]; then
echo "Running the hello world example for React in verbose mode."
fi

# Check if render-range and render-from exist in config.yaml
if ! (grep -q "render-range:" $CONFIG_FILE || grep -q "render-from:" $CONFIG_FILE); then
echo "Removing conformance tests folder"
rm -rf conformance_tests
rm -rf node_conformance_tests
fi

# Execute the command
python ../../plain2code.py hello_world_react.plain

Expand Down
87 changes: 59 additions & 28 deletions git_utils.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import os
from typing import Union
from typing import Optional, Union

from git import Repo

import file_utils

RENDERED_FRID_MESSAGE = "Changes related to Functional requirement ID (FRID): {}"
RENDER_ID_MESSAGE = "Render ID: {}"
BASE_FOLDER_COMMIT_MESSAGE = "Initialize build with Base Folder content"
REFACTORED_CODE_COMMIT_MESSAGE = "Refactored code after implementing {}"
INITIAL_COMMIT_MESSAGE = "[Codeplain] Initial commit"
BASE_FOLDER_COMMIT_MESSAGE = "[Codeplain] Initialize build with Base Folder content"
REFACTORED_CODE_COMMIT_MESSAGE = "[Codeplain] Refactored code after implementing {}"
CONFORMANCE_TESTS_PASSED_COMMIT_MESSAGE = (
"Fixed issues in the implementation code identified during conformance testing"
"[Codeplain] Fixed issues in the implementation code identified during conformance testing"
)
FUNCTIONAL_REQUIREMENT_FINISHED_COMMIT_MESSAGE = "Functional requirement ID (FRID): {} fully implemented"
FUNCTIONAL_REQUIREMENT_FINISHED_COMMIT_MESSAGE = "[Codeplain] Functional requirement ID (FRID):{} fully implemented"

RENDERED_FRID_MESSAGE = "Changes related to Functional requirement ID (FRID): {}"
RENDER_ID_MESSAGE = "Render ID: {}"


# The commit hash of the empty tree
EMPTY_TREE_COMMIT_HASH = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
class InvalidGitRepositoryError(Exception):
"""Raised when the git repository is in an invalid state."""

pass


def init_git_repo(path_to_repo: Union[str, os.PathLike]) -> Repo:
Expand All @@ -30,6 +35,7 @@ def init_git_repo(path_to_repo: Union[str, os.PathLike]) -> Repo:
os.makedirs(path_to_repo)

repo = Repo.init(path_to_repo)
repo.git.commit("--allow-empty", "-m", INITIAL_COMMIT_MESSAGE)

return repo

Expand Down Expand Up @@ -74,10 +80,23 @@ def revert_changes(repo_path: Union[str, os.PathLike]) -> Repo:
return repo


def revert_to_commit_with_frid(repo_path: Union[str, os.PathLike], frid: str) -> Repo:
"""Finds commit with given frid mentioned in the commit message and reverts the branch to it."""
def revert_to_commit_with_frid(repo_path: Union[str, os.PathLike], frid: Optional[str] = None) -> Repo:
"""
Finds commit with given frid mentioned in the commit message and reverts the branch to it.

If frid argument is not provided (None), repo is reverted to the initial state. In case the base folder doesn't exist,
code is reverted to the initial repo commit. Otherwise, the repo is reverted to the base folder commit.

It is expected that the repo has at least one commit related to provided frid if frid is not None.
In case the frid related commit is not found, an exception is raised.
"""
repo = Repo(repo_path)
commit = _get_commit_with_frid(repo, frid)

commit = _get_commit(repo, frid)

if not commit:
raise InvalidGitRepositoryError("Git repository is in an invalid state. Relevant commit could not be found.")

repo.git.reset("--hard", commit)
repo.git.clean("-xdf")
return repo
Expand All @@ -99,20 +118,13 @@ def diff(repo_path: Union[str, os.PathLike], previous_frid: str = None) -> dict:
"""
repo = Repo(repo_path)

if previous_frid:
commit = _get_commit_with_frid(repo, previous_frid)
else:
commit = _get_base_folder_commit(repo)
commit = _get_commit(repo, previous_frid)

# Add all files to the index to get a clean diff
repo.git.add("-N", ".")

# Get the raw git diff output, excluding .pyc files
if not commit:
# If there is no base commit, we are listing all files as new
diff_output = repo.git.diff(EMPTY_TREE_COMMIT_HASH, "--text", ":!*.pyc")
else:
diff_output = repo.git.diff(commit, "--text", ":!*.pyc")
diff_output = repo.git.diff(commit, "--text", ":!*.pyc")

if not diff_output:
return {}
Expand Down Expand Up @@ -165,18 +177,37 @@ def diff(repo_path: Union[str, os.PathLike], previous_frid: str = None) -> dict:
return diff_dict


def _get_commit(repo: Repo, frid: str = None) -> str:
if frid:
commit = _get_commit_with_frid(repo, frid)
else:
commit = _get_base_folder_commit(repo)
if not commit:
commit = _get_initial_commit(repo)

return commit


def _get_commit_with_frid(repo: Repo, frid: str) -> str:
"""Finds commit with given frid mentioned in the commit message."""
current_branch = repo.active_branch.name
commit = repo.git.rev_list(
current_branch, "--grep", FUNCTIONAL_REQUIREMENT_FINISHED_COMMIT_MESSAGE.format(frid), "-n", "1"
)
commit = _get_commit_with_message(repo, FUNCTIONAL_REQUIREMENT_FINISHED_COMMIT_MESSAGE.format(frid))
if not commit:
raise Exception(f"No commit with frid {frid} found.")
raise InvalidGitRepositoryError(f"No commit with frid {frid} found.")
return commit


def _get_base_folder_commit(repo: Repo) -> str:
"""Finds commit related to copy of the base folder."""
current_branch = repo.active_branch.name
return repo.git.rev_list(current_branch, "--grep", BASE_FOLDER_COMMIT_MESSAGE, "-n", "1")
return _get_commit_with_message(repo, BASE_FOLDER_COMMIT_MESSAGE)


def _get_initial_commit(repo: Repo) -> str:
"""Finds initial commit."""
return _get_commit_with_message(repo, INITIAL_COMMIT_MESSAGE)


def _get_commit_with_message(repo: Repo, message: str) -> str:
"""Finds commit with given message."""
escaped_message = message.replace("[", "\\[").replace("]", "\\]")

return repo.git.rev_list(repo.active_branch.name, "--grep", escaped_message, "-n", "1")
20 changes: 12 additions & 8 deletions plain2code.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import yaml
from liquid2.exceptions import TemplateNotFoundError
from requests.exceptions import RequestException

import file_utils
import git_utils
Expand Down Expand Up @@ -584,11 +585,8 @@ def conformance_and_acceptance_testing( # noqa: C901
if args.verbose:
console.info(f"Running conformance tests attempt {conformance_tests_run_count}.")

if frid == plain_spec.get_first_frid(plain_source_tree):
code_diff = git_utils.diff(args.build_folder)
else:
# full diff between the previous frid and the current frid (including refactoring commits)
code_diff = git_utils.diff(args.build_folder, plain_spec.get_previous_frid(plain_source_tree, frid))
# full diff between the previous frid and the current frid (including refactoring commits)
code_diff = git_utils.diff(args.build_folder, plain_spec.get_previous_frid(plain_source_tree, frid))

[
success,
Expand Down Expand Up @@ -794,6 +792,8 @@ def render_functional_requirement( # noqa: C901

return

previous_frid = plain_spec.get_previous_frid(plain_source_tree, frid)

functional_requirement_render_attempt = 0
while True:
existing_files = file_utils.list_all_text_files(args.build_folder)
Expand Down Expand Up @@ -895,7 +895,7 @@ def render_functional_requirement( # noqa: C901
"Unittests could not be fixed after rendering the functional requirement. "
f"Restarting rendering the functional requirement {frid} from scratch."
)
git_utils.revert_changes(args.build_folder)
git_utils.revert_to_commit_with_frid(args.build_folder, previous_frid)
continue

exit_with_error(
Expand Down Expand Up @@ -991,10 +991,11 @@ def render_functional_requirement( # noqa: C901

if should_rerender_functional_requirement:
# Restore the code state to initial
git_utils.revert_changes(args.build_folder)
git_utils.revert_to_commit_with_frid(args.build_folder, previous_frid)

if args.render_conformance_tests:
# Restore the conformance tests state to initial
git_utils.revert_changes(args.conformance_tests_folder)
git_utils.revert_to_commit_with_frid(args.conformance_tests_folder, previous_frid)

retry_state.mark_failed_conformance_testing_rendering()

Expand Down Expand Up @@ -1208,6 +1209,9 @@ def exit_with_error(message, last_successful_frid=None, render_id=None):
console.error("Keyboard interrupt")
# Don't print the traceback here because it's going to be from keyboard interrupt and we don't really care about that
console.debug(f"Render ID: {run_state.render_id}")
except RequestException as e:
console.error(f"Error rendering plain code: {str(e)}\n")
console.debug(f"Render ID: {run_state.render_id}")
except Exception as e:
console.error(f"Error rendering plain code: {str(e)}\n")
console.debug(f"Render ID: {run_state.render_id}")
Expand Down