diff --git a/scripts/interpreter/__init__.py b/scripts/interpreter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/interpreter/gen_commit.py b/scripts/interpreter/gen_commit.py new file mode 100755 index 0000000..0a2ff55 --- /dev/null +++ b/scripts/interpreter/gen_commit.py @@ -0,0 +1,140 @@ +import asyncio +import os +import sys +from dotenv import load_dotenv +from interpreter import OpenInterpreter + +load_dotenv() # Load environment variables from .env + + +def get_git_credentials(): + """Retrieve Git credentials and project path from command-line arguments.""" + if len(sys.argv) != 4: + print("Usage: python gen_commit.py ") + sys.exit(1) + return sys.argv[1], sys.argv[2], sys.argv[3] + + +def gen_commit(project_path): + """Use OpenInterpreter to generate a commit message based on git diff.""" + + agent = OpenInterpreter() + agent.llm.model = "gpt-4o" + agent.auto_run = True + + # Read both convention and git commit files + convention_path = os.path.join( + os.path.dirname(__file__), "resources/commit_convention.md" + ) + git_commit_path = os.path.join(os.path.dirname(__file__), "resources/git_commit.md") + + with open(convention_path, "r") as f: + convention_content = f.read() + with open(git_commit_path, "r") as f: + git_commit_content = f.read() + + agent.system_message = f""" + You are a helpful assistant that generates commit messages based on uncommitted code. + + Follow these rules: + 1. Use English. + 2. Be concise and follow this format: + {convention_content} + + 3. Use these git commands to analyze changes as you see fit: + {git_commit_content} + + 4. Only return the commit message. Do not add explanation or extra output. + """ + + prompt = f""" + Generate a commit message based on the following changes in the project directory: + + First, change directory: + cd {project_path} + + Then check the uncommitted changes. + + Then generate a commit message according to the changes. + """ + + response = agent.chat(prompt) + + if isinstance(response, list) and len(response) > 0: + if isinstance(response[-1], dict) and "content" in response[-1]: + return response[-1]["content"].strip() + + return str(response).strip() + + +async def commit_submodules(): + """Commit uncommitted changes in submodules (if any).""" + print("Checking for submodule changes...") + proc = await asyncio.create_subprocess_shell( + "git submodule foreach --quiet '" + "if [ -n \"$(git status --porcelain)\" ]; then " + "echo Committing submodule: $name; " + "git add . && git commit -m \"Auto-commit submodule changes\" || true; " + "fi'", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await proc.communicate() + if stdout: + print(stdout.decode()) + if stderr: + print(f"Submodule commit stderr:\n{stderr.decode()}") + + +async def push_code(): + """Pull, commit, and push code with AI-generated message.""" + git_user, git_pass, project_path = get_git_credentials() + + # Change to project path + os.chdir(project_path) + print(f"Changed to project path: {project_path}") + + # Get git remote origin URL using asyncio.subprocess + proc = await asyncio.create_subprocess_exec( + "git", "remote", "get-url", "origin", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await proc.communicate() + git_repo = stdout.decode().strip() + + if stderr: + print(f"Failed to get remote URL: {stderr.decode()}") + + # Clean URL + git_repo = git_repo.replace(".git", "").replace("https://", "") + remote_url = f"https://{git_user}:{git_pass}@{git_repo}" + + # Commit any submodule changes + await commit_submodules() + + # Generate commit message + commit_msg = gen_commit(project_path) + print(f"Generated commit message:\n{commit_msg}\n") + + # Pull, add, commit, and push + commands = [ + f"git pull {remote_url} || true", + "git add .", + f'git commit -m "{commit_msg}" || true', + f"git push {remote_url} || true", + ] + + for cmd in commands: + proc = await asyncio.create_subprocess_shell( + cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await proc.communicate() + if stdout: + print(stdout.decode()) + if stderr: + print(f"Error executing {cmd}:\n{stderr.decode()}") + + +if __name__ == "__main__": + asyncio.run(push_code()) diff --git a/scripts/interpreter/resources/commit_convention.md b/scripts/interpreter/resources/commit_convention.md new file mode 100644 index 0000000..6474983 --- /dev/null +++ b/scripts/interpreter/resources/commit_convention.md @@ -0,0 +1,127 @@ +# Conventional Commit Messages + +See how [a minor change](#examples) to your commit message style can make a difference. + +> [!TIP] +> Take a look at **[git-conventional-commits](https://github.com/qoomon/git-conventional-commits)** , a CLI util to ensure these conventions, determine version and generate changelogs + +## Commit Message Formats + +### Default +
+<type>(<optional scope>): <description>
+empty separator line
+<optional body>
+empty separator line
+<optional footer>
+
+ +### Merge Commit +
+Merge branch '<branch name>'
+
+Follows default git merge message + +### Revert Commit +
+Revert "<reverted commit subject line>"
+
+Follows default git revert message + +### Inital Commit +``` +chore: init +``` + +### Types +- API or UI relevant changes + - `feat` Commits, that add or remove a new feature to the API or UI + - `fix` Commits, that fix an API or UI bug of a preceded `feat` commit +- `refactor` Commits, that rewrite/restructure your code, however do not change any API or UI behaviour + - `perf` Commits are special `refactor` commits, that improve performance +- `style` Commits, that do not affect the meaning (white-space, formatting, missing semi-colons, etc) +- `test` Commits, that add missing tests or correcting existing tests +- `docs` Commits, that affect documentation only +- `build` Commits, that affect build components like build tools, dependencies, project version, ci pipelines, ... +- `ops` Commits, that affect operational components like infrastructure, deployment, backup, recovery, ... +- `chore` Miscellaneous commits e.g. modifying `.gitignore` + +### Scopes +The `scope` provides additional contextual information. +* Is an **optional** part of the format +* Allowed Scopes depend on the specific project +* Don't use issue identifiers as scopes + +### Breaking Changes Indicator +Breaking changes should be indicated by an `!` before the `:` in the subject line e.g. `feat(api)!: remove status endpoint` +- Is an **optional** part of the format +- Breaking changes **must** be described in the [commit footer section](#footer) + +### Description +The `description` contains a concise description of the change. +- It is a **mandatory** part of the format +- Use the imperative, present tense: "change" not "changed" nor "changes" + - Think of `This commit will...` or `This commit should...` +- Don't capitalize the first letter +- No dot (`.`) at the end + +### Body +The `body` should include the motivation for the change and contrast this with previous behavior. +- Is an **optional** part of the format +- Use the imperative, present tense: "change" not "changed" nor "changes" +- This is the place to mention issue identifiers and their relations + +### Footer +The `footer` should contain any information about **Breaking Changes** and is also the place to **reference Issues** that this commit refers to. +- Is an **optional** part of the format +- **optionally** reference an issue by its id. +- **Breaking Changes** should start with the word `BREAKING CHANGE:` followed by space or two newlines. The rest of the commit message is then used for this. + +### Versioning +- **If** your next release contains commit with... + - **breaking changes** incremented the **major version** + - **API relevant changes** (`feat` or `fix`) incremented the **minor version** +- **Else** increment the **patch version** + + +### Examples +- ``` + feat: add email notifications on new direct messages + ``` +- ``` + feat(shopping cart): add the amazing button + ``` +- ``` + feat!: remove ticket list endpoint + + refers to JIRA-1337 + + BREAKING CHANGE: ticket endpoints no longer supports list all entities. + ``` +- ``` + fix(shopping-cart): prevent order an empty shopping cart + ``` +- ``` + fix(api): fix wrong calculation of request body checksum + ``` +- ``` + fix: add missing parameter to service call + + The error occurred due to . + ``` +- ``` + perf: decrease memory footprint for determine uniqe visitors by using HyperLogLog + ``` +- ``` + build: update dependencies + ``` +- ``` + build(release): bump version to 1.0.0 + ``` +- ``` + refactor: implement fibonacci number calculation as recursion + ``` +- ``` + style: remove empty line + ``` + diff --git a/scripts/interpreter/resources/git_commit.md b/scripts/interpreter/resources/git_commit.md new file mode 100644 index 0000000..8c5c061 --- /dev/null +++ b/scripts/interpreter/resources/git_commit.md @@ -0,0 +1,22 @@ +How to show uncommitted changes in Git + +The command you are looking for is git diff. + +git diff - Show changes between commits, commit and working tree, etc + +Here are some of the options it expose which you can use + +git diff (no parameters) +Print out differences between your working directory and the index. + +git diff --cached: +Print out differences between the index and HEAD (current commit). + +git diff HEAD: +Print out differences between your working directory and the HEAD. + +git diff --name-only +Show only names of changed files. + +git diff --name-status +Show only names and status of changed files. \ No newline at end of file