Skip to content

Commit

Permalink
Merge pull request #29 from Nr18/develop
Browse files Browse the repository at this point in the history
Release v0.3.0
  • Loading branch information
Joris Conijn committed Feb 10, 2022
2 parents c888e1d + 1c4b105 commit c601f08
Show file tree
Hide file tree
Showing 14 changed files with 717 additions and 186 deletions.
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
[![Continuous Integration](https://github.com/Nr18/pull-request-codecommit/actions/workflows/ci.yml/badge.svg)](https://github.com/Nr18/pull-request-codecommit/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/Nr18/pull-request-codecommit/branch/main/graph/badge.svg?token=H6zsiLbNjP)](https://codecov.io/gh/Nr18/pull-request-codecommit)

> Note, this project is still under development and is not functional yet!
This tool makes it easy to create pull requests for [AWS CodeCommit](https://aws.amazon.com/codecommit/). It relies on the
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) standard. It looks at the git commits between the
current and the destination branch. Then it tries to create a pull request with this information as input.
Expand Down Expand Up @@ -52,6 +50,36 @@ To use `pull-request-codecommit` you just execute the following command:
pull-request-codecommit
```

### Auto merge

In some cases it makes sense to directly merge the pull request, in those cases you can use:

```bash
pull-request-codecommit --auto-merge
```

This will directly merge the pull request using the fast-forward merge strategy.
If the merge is successful, it will:

- Merge and close the pull request.
- Checkout the destination branch.
- Pull the latest changes. (This will pull the just merged changes locally)
- Remove the working branch.

From this point you are ready for the next change.

### Update existing pull request

When a pull requests exists a proposal is made to update the existing pull request.

### Overwrite target branch

When you want to overwrite the target branch you need to supply the `--branch <name>` option:

```bash
pull-request-codecommit --branch my-target-branch
```

## Testing locally

```bash
Expand Down
63 changes: 48 additions & 15 deletions pull_request_codecommit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,79 @@

import click

from pull_request_codecommit.repository import Repository
from .pull_request import PullRequest
from .repository import Repository


@click.command()
@click.option("-r", "--repository-path", default=None)
def main(repository_path: Optional[str]) -> None:
@click.option("-b", "--branch", default=None)
@click.option("--auto-merge/--no-auto-merge", default=False)
def main(
repository_path: Optional[str], branch: Optional[str], auto_merge: bool
) -> None:
"""
pull-request-codecommit
"""
repo = Repository(repository_path)
repo = __load_repository(repository_path=repository_path, target_branch=branch)
__display_repository_information(repo)
pr = __create_pull_request(repo)

if auto_merge:
status = pr.merge()
click.echo(f"Auto merging resulted in: {status}")


def __load_repository(
repository_path: Optional[str], target_branch: Optional[str]
) -> Repository:
repo = Repository(path=repository_path, target_branch=target_branch)

if not repo.remote.supported:
raise click.ClickException("The repository is not compatible with this tool!")

return repo


def __create_pull_request(repo: Repository) -> PullRequest:
pr = PullRequest(repo)

if not pr.has_changes:
click.echo(
f"There are no differences between {repo.destination} and {repo.branch}!"
)
exit(0)

__propose_title_description(pr)
pr.save()

click.echo(f"Please review/approve: {pr.link}")
click.echo()
click.echo(click.style(pr.title, bold=True))
click.echo(pr.description)

return pr


def __display_repository_information(repo: Repository) -> None:
click.echo(f"Remote: {repo.remote.url}")
click.echo(f"Region: {repo.remote.region}")
click.echo(f"Profile: {repo.remote.profile}")
click.echo(f"Repo: {repo.remote.name}")
click.echo(f"Branch: {repo.branch}")
click.echo()
pull_request = repo.pull_request_information()

if not pull_request.has_changes:
click.echo(
f"There are no differences between {repo.destination} and {repo.branch}!"
)
exit(0)

message = click.edit(f"{pull_request.title}\n\n{pull_request.description}")
def __propose_title_description(pr: PullRequest) -> None:
message = click.edit(f"{pr.title}\n\n{pr.description}")

if message is None:
raise click.ClickException("Pull request was not created")

title = message.splitlines()[0]
description = "\n".join(message.splitlines()[1:]).lstrip("\n")
link = repo.create_pull_request(title, description)
click.echo(f"Please review/approve: {link}")
click.echo()
click.echo(click.style(title, bold=True))
click.echo(description)

pr.update(title, description)


if __name__ == "__main__":
Expand Down
102 changes: 88 additions & 14 deletions pull_request_codecommit/aws/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self, profile: Optional[str], region: Optional[str]) -> None:

@property
def base_command(self) -> List[str]:
base_command = ["aws"]
base_command = ["aws", "--output", "json"]

if self.__profile:
base_command.extend(["--profile", self.__profile])
Expand All @@ -36,6 +36,46 @@ def __execute(self, parameters: List[str]) -> str:

return response.stdout.decode("utf-8").strip("\n")

def get_open_pull_request(
self, repository: str, source: str, destination: str
) -> dict:
response = self.__execute(
[
"codecommit",
"list-pull-requests",
"--repository-name",
repository,
"--pull-request-status",
"OPEN",
]
)
data = json.loads(response)
open_pull_requests = data.get("pullRequestIds")

for pull_request_id in open_pull_requests:
pr = self.__get_pull_request(pull_request_id)
target = pr.get("pullRequestTargets", [{}])[0]

if (
target.get("sourceReference") == f"refs/heads/{source}"
and target.get("destinationReference") == f"refs/heads/{destination}"
):
return pr

return {}

def __get_pull_request(self, pull_request_id: int) -> dict:
response = self.__execute(
[
"codecommit",
"get-pull-request",
"--pull-request-id",
str(pull_request_id),
]
)
data = json.loads(response)
return data.get("pullRequest", {})

def create_pull_request(
self,
title: str,
Expand All @@ -44,18 +84,52 @@ def create_pull_request(
source: str,
destination: str,
) -> dict:
command = [
"codecommit",
"create-pull-request",
"--title",
title,
"--description",
description,
"--targets",
f"repositoryName={repository}, sourceReference={source}, destinationReference={destination}",
]
# print(command)
# return {"pullRequestId": 1}
response = self.__execute(command)
response = self.__execute(
[
"codecommit",
"create-pull-request",
"--title",
title,
"--description",
description,
"--targets",
f"repositoryName={repository}, sourceReference={source}, destinationReference={destination}",
]
)
data = json.loads(response)

return data.get("pullRequest")

def update_pull_request(
self,
pull_request_id: int,
description: str,
) -> dict:
response = self.__execute(
[
"codecommit",
"update-pull-request-description",
"--pull-request-id",
str(pull_request_id),
"--description",
description,
]
)
data = json.loads(response)

return data.get("pullRequest")

def merge_pull_request(self, repository: str, pull_request_id: int) -> dict:
response = self.__execute(
[
"codecommit",
"merge-pull-request-by-fast-forward",
"--pull-request-id",
str(pull_request_id),
"--repository-name",
repository,
]
)
data = json.loads(response)

return data.get("pullRequest")
9 changes: 9 additions & 0 deletions pull_request_codecommit/git/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,12 @@ def get_commit_messages(self, destination_branch: str) -> Commits:
)

return Commits(messages)

def checkout(self, destination: str) -> None:
self.__execute(["checkout", destination])

def pull(self) -> None:
self.__execute(["pull"])

def delete_branch(self, branch: str) -> None:
self.__execute(["branch", "-d", branch])
10 changes: 10 additions & 0 deletions pull_request_codecommit/git/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,13 @@ def name(self) -> str:
self.__name = name

return self.__name

@property
def codecommit_url(self) -> str:
return f"https://{self.region}.console.aws.amazon.com/codesuite/codecommit/repositories/{self.name}"

def pull_request_url(self, pull_request_id: int) -> str:
return (
self.codecommit_url
+ f"/pull-requests/{pull_request_id}/details?region={self.region}"
)
63 changes: 37 additions & 26 deletions pull_request_codecommit/pull_request.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,53 @@
from pull_request_codecommit.git import Commits
from typing import Optional

from .pull_request_codecommit import PullRequestCodeCommit
from .pull_request_proposal import PullRequestProposal
from .repository import Repository
from .git import Commits


class PullRequest:
"""
Understands pull requests
"""

def __init__(self, commits: Commits) -> None:
self.__commits = commits
def __init__(self, repo: Repository) -> None:
self.__repo: Repository = repo
self.__proposal: Optional[PullRequestProposal] = None
self.__commits: Commits = self.__repo.commits()
self.__pull_request: PullRequestCodeCommit = PullRequestCodeCommit(
repository=repo
)

@property
def has_changes(self) -> bool:
return True if self.__commits.first else False
def proposal(self) -> PullRequestProposal:
if not self.__proposal:
self.__proposal = PullRequestProposal(self.__pull_request, self.__commits)

return self.__proposal

@property
def title(self) -> str:
if not self.__commits.first:
return ""
return self.proposal.title

title = self.__commits.first.message.subject

if self.__commits.issues:
title += f" ({', '.join(self.__commits.issues)})"
@property
def description(self) -> str:
return self.proposal.description

return title
@property
def link(self) -> str:
return self.__pull_request.link

@property
def description(self) -> str:
if len(self.__commits) == 1:
description = (
[self.__commits.first.message.body] if self.__commits.first else []
)
else:
description = list(
map(lambda commit: commit.message.subject, self.__commits)
)

if self.__commits.issues:
description.append(f"\nIssues: " + ", ".join(self.__commits.issues))

return "\n".join(description).lstrip("\n")
def has_changes(self) -> bool:
return True if self.__commits.first else False

def save(self) -> None:
self.__repo.push()
self.__pull_request.save(self.title, self.description)

def update(self, title: str, description: str) -> None:
self.proposal.update(title=title, description=description)

def merge(self) -> str:
return self.__pull_request.merge()
Loading

0 comments on commit c601f08

Please sign in to comment.