Skip to content

Commit 25d77e0

Browse files
authored
Merge pull request #91 from nollied/fix-init
Add new functionality for creating merge requests and pull requests with custom titles and descriptions.
2 parents 5d931d8 + 89b8812 commit 25d77e0

File tree

10 files changed

+303
-44
lines changed

10 files changed

+303
-44
lines changed

mindflow/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.3.11"
1+
__version__ = "0.3.12"

mindflow/cli/new_click_cli/cli_main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from mindflow.cli.new_click_cli.commands.git.push import push
55
from mindflow.cli.new_click_cli.commands.git.commit import commit
66
from mindflow.cli.new_click_cli.commands.git.diff import diff
7+
from mindflow.cli.new_click_cli.commands.git.mr import mr
78
from mindflow.cli.new_click_cli.commands.git.pr import pr
89

910
from mindflow.cli.new_click_cli.commands.chat import chat
@@ -38,6 +39,7 @@ def version():
3839
mindflow_cli.add_command(index)
3940
mindflow_cli.add_command(inspect)
4041
mindflow_cli.add_command(login)
42+
mindflow_cli.add_command(mr)
4143
mindflow_cli.add_command(pr)
4244
mindflow_cli.add_command(query)
4345

mindflow/cli/new_click_cli/commands/git/commit.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77

88

99
@passthrough_command(help="Generate a git commit response by feeding git diff to gpt")
10+
# @overloaded_option(
11+
# "-m",
12+
# "--message",
13+
# help="Don't use mindflow to generate a commit message, use this one instead.",
14+
# default=None,
15+
# )
1016
@click.option(
1117
"-m",
1218
"--message",
@@ -17,7 +23,6 @@ def commit(args: Tuple[str], message: Optional[str] = None):
1723
"""
1824
Commit command.
1925
"""
20-
2126
if message is not None:
2227
click.echo(
2328
f"Warning: Using message '{message}' instead of mindflow generated message."
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from typing import Optional, Tuple
2+
3+
import click
4+
from mindflow.cli.new_click_cli.util import passthrough_command
5+
from mindflow.core.git.mr import run_mr
6+
7+
8+
@click.group()
9+
def mr():
10+
"""
11+
MR command.
12+
"""
13+
pass
14+
15+
16+
@passthrough_command(help="Generate a git pr response by feeding git diff to gpt")
17+
@click.option(
18+
"-t",
19+
"--title",
20+
help="Don't use mindflow to generate a pr title, use this one instead.",
21+
default=None,
22+
)
23+
@click.option(
24+
"-d",
25+
"--description",
26+
help="Don't use mindflow to generate a pr body, use this one instead.",
27+
default=None,
28+
)
29+
def create(
30+
args: Tuple[str], title: Optional[str] = None, description: Optional[str] = None
31+
):
32+
"""
33+
PR command.
34+
"""
35+
if title is not None:
36+
click.echo(
37+
f"Warning: Using message '{title}' instead of mindflow generated message."
38+
)
39+
click.echo("It's recommended that you don't use the -t/--title flag.")
40+
41+
if description is not None:
42+
click.echo(
43+
f"Warning: Using message '{description}' instead of mindflow generated message."
44+
)
45+
click.echo("It's recommended that you don't use the -d/--description flag.")
46+
47+
run_mr(args, title=title, description=description)
48+
49+
50+
mr.add_command(create)
Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,48 @@
1-
import click
1+
from typing import Optional, Tuple
22

3+
import click
4+
from mindflow.cli.new_click_cli.util import passthrough_command
35
from mindflow.core.git.pr import run_pr
46

57

6-
@click.command(help="Generate a git pr response by feeding git diff to gpt")
8+
@click.group()
79
def pr():
810
"""
911
PR command.
1012
"""
11-
run_pr()
13+
pass
14+
15+
16+
@passthrough_command(help="Generate a git pr response by feeding git diff to gpt")
17+
@click.option(
18+
"-t",
19+
"--title",
20+
help="Don't use mindflow to generate a pr title, use this one instead.",
21+
default=None,
22+
)
23+
@click.option(
24+
"-b",
25+
"--body",
26+
help="Don't use mindflow to generate a pr body, use this one instead.",
27+
default=None,
28+
)
29+
def create(args: Tuple[str], title: Optional[str] = None, body: Optional[str] = None):
30+
"""
31+
PR command.
32+
"""
33+
if title is not None:
34+
click.echo(
35+
f"Warning: Using message '{title}' instead of mindflow generated message."
36+
)
37+
click.echo("It's recommended that you don't use the -t/--title flag.")
38+
39+
if body is not None:
40+
click.echo(
41+
f"Warning: Using message '{body}' instead of mindflow generated message."
42+
)
43+
click.echo("It's recommended that you don't use the -d/--description flag.")
44+
45+
run_pr(args, title=title, body=body)
46+
47+
48+
pr.add_command(create)

mindflow/cli/new_click_cli/util.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33

44
def passthrough_command(*command_args, **command_kwargs):
5-
# this function is a decorator that takes the input function and wraps it
5+
"""Just like the @click.command decorator, but allows all args to pass through (for wrapper cli commands)."""
6+
67
def _decorator(func):
78
context_settings = command_kwargs.get("context_settings", {})
89

@@ -24,3 +25,41 @@ def _decorator(func):
2425
return func
2526

2627
return _decorator
28+
29+
30+
# def overloaded_option(*option_args, **option_kwargs):
31+
# if message is not None:
32+
# click.echo(
33+
# f"Warning: Using message '{message}' instead of mindflow generated message."
34+
# )
35+
# click.echo("It's recommended that you don't use the -m/--message flag.")
36+
37+
38+
# @click.option(
39+
# "-m",
40+
# "--message",
41+
# help="Don't use mindflow to generate a commit message, use this one instead.",
42+
# default=None,
43+
# )
44+
45+
# def _decorator(func):
46+
# dec1 = click.option(*option_args, **option_kwargs)
47+
48+
# func = dec1(func)
49+
# print(func)
50+
# # option_name = func.params[-1].name
51+
# option_name = func.__click_params__[-1].name
52+
53+
# def wrapped_func(**kwargs):
54+
# if option_name in kwargs:
55+
# v = kwargs[option_name]
56+
# click.echo(
57+
# f"Warning: MindFlow overrides {option_name}, but since you passed in it's value as '{v}', that will be used instead."
58+
# )
59+
# click.echo("We recommend not overriding options, but we understand sometimes it's necessary.")
60+
61+
# return func(**kwargs)
62+
63+
# return wrapped_func
64+
65+
# return _decorator

mindflow/core/git/mr.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import subprocess
2+
from typing import Optional, Tuple, List
3+
4+
from mindflow.core.git.pr import create_title_and_body, is_valid_pr
5+
from mindflow.utils.command_parse import get_flag_value
6+
7+
8+
def run_mr(
9+
args: Tuple[str], title: Optional[str] = None, description: Optional[str] = None
10+
):
11+
base_branch = get_flag_value(args, ["--target-branch", "-b"])
12+
head_branch = get_flag_value(args, ["--source-branch", "-s"])
13+
14+
if base_branch is None:
15+
# Determine the name of the default branch
16+
base_branch = (
17+
subprocess.check_output(["git", "symbolic-ref", "refs/remotes/origin/HEAD"])
18+
.decode()
19+
.strip()
20+
.split("/")[-1]
21+
)
22+
23+
if head_branch is None:
24+
# Get the name of the current branch
25+
head_branch = (
26+
subprocess.check_output(["git", "symbolic-ref", "--short", "HEAD"])
27+
.decode("utf-8")
28+
.strip()
29+
)
30+
31+
if not is_valid_pr(base_branch, head_branch):
32+
return
33+
34+
if not title or not description:
35+
title, description = create_title_and_body(base_branch, title, description)
36+
37+
create_merge_request(args, title, description)
38+
39+
40+
def create_merge_request(args: Tuple[str], title: str, description: str):
41+
command: List[str] = ["glab", "mr", "create"] + list(args) + ["--title", title, "--description", description] # type: ignore
42+
pr_result = subprocess.check_output(command).decode("utf-8")
43+
if "https://" in pr_result:
44+
print("Merge request created successfully")
45+
print(pr_result)
46+
else:
47+
print(
48+
"Failed to create pull request. Please raise an issue at: https://github.com/nollied/mindflow-cli/issues"
49+
)
50+
exit()

mindflow/core/git/pr.py

Lines changed: 67 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,91 @@
11
import concurrent.futures
22
import subprocess
3-
from typing import List
3+
from typing import List, Optional, Tuple
44

55
from mindflow.core.git.diff import run_diff
66
from mindflow.settings import Settings
7+
from mindflow.utils.command_parse import get_flag_value
78
from mindflow.utils.prompt_builders import build_context_prompt
89
from mindflow.utils.prompts import PR_BODY_PREFIX
910
from mindflow.utils.prompts import PR_TITLE_PREFIX
1011

1112

12-
def run_pr():
13-
# Get the name of the current branch
14-
current_branch = (
15-
subprocess.check_output(["git", "symbolic-ref", "--short", "HEAD"])
16-
.decode("utf-8")
17-
.strip()
18-
)
13+
def run_pr(args: Tuple[str], title: Optional[str] = None, body: Optional[str] = None):
14+
base_branch = get_flag_value(args, ["--base", "-B"])
15+
head_branch = get_flag_value(args, ["--head", "-H"])
1916

20-
# Determine the name of the default branch
21-
default_branch = (
22-
subprocess.check_output(["git", "symbolic-ref", "refs/remotes/origin/HEAD"])
23-
.decode()
24-
.strip()
25-
.split("/")[-1]
26-
)
17+
if base_branch is None:
18+
# Determine the name of the default branch
19+
base_branch = (
20+
subprocess.check_output(["git", "symbolic-ref", "refs/remotes/origin/HEAD"])
21+
.decode()
22+
.strip()
23+
.split("/")[-1]
24+
)
2725

28-
if current_branch == default_branch:
29-
print("Cannot create pull request from default branch")
26+
if head_branch is None:
27+
# Get the name of the current branch
28+
head_branch = (
29+
subprocess.check_output(["git", "symbolic-ref", "--short", "HEAD"])
30+
.decode("utf-8")
31+
.strip()
32+
)
33+
34+
if not is_valid_pr(base_branch, head_branch):
3035
return
3136

32-
if not has_remote_branch(current_branch):
37+
if not title or not body:
38+
title, body = create_title_and_body(base_branch, title, body)
39+
40+
create_pull_request(args, title, body)
41+
42+
43+
def is_valid_pr(head_branch: str, base_branch: str) -> bool:
44+
if head_branch == base_branch:
45+
print("Cannot create pull request from default branch")
46+
return False
47+
48+
if not has_remote_branch(head_branch):
3349
print("No remote branch for current branch")
34-
return
50+
return False
3551

3652
if needs_push():
3753
print("Current branch needs to be pushed to remote repository")
38-
return
54+
return False
55+
56+
return True
3957

58+
59+
def create_title_and_body(
60+
base_branch, title: Optional[str], body: Optional[str]
61+
) -> Tuple[str, str]:
4062
settings = Settings()
4163

42-
diff_output = run_diff((default_branch,))
64+
diff_output = run_diff((base_branch,))
4365

44-
pr_title_prompt = build_context_prompt(PR_TITLE_PREFIX, diff_output)
45-
pr_body_prompt = build_context_prompt(PR_BODY_PREFIX, diff_output)
66+
if title is None and body is None:
67+
pr_title_prompt = build_context_prompt(PR_TITLE_PREFIX, diff_output)
68+
pr_body_prompt = build_context_prompt(PR_BODY_PREFIX, diff_output)
4669

47-
with concurrent.futures.ThreadPoolExecutor() as executor:
48-
future_title = executor.submit(
49-
settings.mindflow_models.query.model, pr_title_prompt
50-
)
51-
future_body = executor.submit(
52-
settings.mindflow_models.query.model, pr_body_prompt
53-
)
70+
with concurrent.futures.ThreadPoolExecutor() as executor:
71+
future_title = executor.submit(
72+
settings.mindflow_models.query.model, pr_title_prompt
73+
)
74+
future_body = executor.submit(
75+
settings.mindflow_models.query.model, pr_body_prompt
76+
)
5477

55-
pr_title = future_title.result()
56-
pr_body = future_body.result()
78+
title = future_title.result()
79+
body = future_body.result()
80+
else:
81+
if title is None:
82+
pr_title_prompt = build_context_prompt(PR_TITLE_PREFIX, diff_output)
83+
title = settings.mindflow_models.query.model(pr_title_prompt)
84+
if body is None:
85+
pr_body_prompt = build_context_prompt(PR_BODY_PREFIX, diff_output)
86+
body = settings.mindflow_models.query.model(pr_body_prompt)
5787

58-
create_pull_request(pr_title, pr_body)
88+
return title, body
5989

6090

6191
def needs_push() -> bool:
@@ -69,22 +99,22 @@ def needs_push() -> bool:
6999
return "Your branch is ahead of" in git_status
70100

71101

72-
def has_remote_branch(current_branch: str) -> bool:
102+
def has_remote_branch(head_branch: str) -> bool:
73103
"""
74104
Returns True if there is a remote branch for the current branch, False otherwise.
75105
"""
76106
# Check if there is a remote branch for the current branch
77107
try:
78108
subprocess.check_output(
79-
["git", "ls-remote", "--exit-code", "--heads", "origin", current_branch]
109+
["git", "ls-remote", "--exit-code", "--heads", "origin", head_branch]
80110
)
81111
return True
82112
except subprocess.CalledProcessError:
83113
return False
84114

85115

86-
def create_pull_request(title, body):
87-
command: List[str] = ["gh", "pr", "create", "--title", title, "--body", body] # type: ignore
116+
def create_pull_request(args: Tuple[str], title: str, body: str):
117+
command: List[str] = ["gh", "pr", "create"] + list(args) + ["--title", title, "--body", body] # type: ignore
88118
pr_result = subprocess.check_output(command).decode("utf-8")
89119
if "https://" in pr_result:
90120
print("Pull request created successfully")

0 commit comments

Comments
 (0)