From 479b3c4e6f98ed6a4df68cedf76aa99c4c5e22cf Mon Sep 17 00:00:00 2001 From: Hamza Tahir Date: Tue, 24 Sep 2024 10:57:27 +0200 Subject: [PATCH 01/12] Added PR workflow --- .github/workflows/generate-pr-description.yml | 42 +++++++++ scripts/generate_pr_description.py | 92 +++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 .github/workflows/generate-pr-description.yml create mode 100644 scripts/generate_pr_description.py diff --git a/.github/workflows/generate-pr-description.yml b/.github/workflows/generate-pr-description.yml new file mode 100644 index 00000000000..273797acbd2 --- /dev/null +++ b/.github/workflows/generate-pr-description.yml @@ -0,0 +1,42 @@ +name: Auto PR Description + +on: + pull_request: + types: [opened] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + auto-describe: + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + permissions: + contents: read + pull-requests: write + steps: + - name: Checkout code + uses: actions/checkout@v4.1.1 + + - name: Set up Python + uses: actions/setup-python@v5.0.0 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + source $HOME/.cargo/env + uv pip install --system requests openai + + - name: Generate PR description + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: python scripts/generate_pr_description.py + + - name: Check for errors + if: failure() + run: | + echo "The PR description generation failed. Please check the logs for more information." \ No newline at end of file diff --git a/scripts/generate_pr_description.py b/scripts/generate_pr_description.py new file mode 100644 index 00000000000..4a65bbbf7ea --- /dev/null +++ b/scripts/generate_pr_description.py @@ -0,0 +1,92 @@ +import os +import requests +import openai + +MAX_CHARS = 400000 # Maximum characters for changes summary + +def truncate_changes(changes_summary): + """Truncates the changes summary to fit within MAX_CHARS.""" + total_chars = 0 + truncated_summary = [] + for change in changes_summary: + change_chars = len(change) + if total_chars + change_chars > MAX_CHARS: + remaining_chars = MAX_CHARS - total_chars + if remaining_chars > 50: # Ensure we're not adding just a few characters + truncated_change = change[:remaining_chars] + truncated_summary.append(truncated_change + "...") + break + total_chars += change_chars + truncated_summary.append(change) + return truncated_summary + +def generate_pr_description(): + # GitHub API setup + token = os.environ['GITHUB_TOKEN'] + repo = os.environ['GITHUB_REPOSITORY'] + pr_number = os.environ['GITHUB_EVENT_NUMBER'] + headers = {'Authorization': f'token {token}'} + api_url = f'https://api.github.com/repos/{repo}/pulls/{pr_number}' + + # Get current PR description + pr_info = requests.get(api_url, headers=headers).json() + current_description = pr_info['body'] or '' + + # Check if description matches the default template + default_template_indicator = "I implemented/fixed _ to achieve _." + + if default_template_indicator in current_description: + # Get PR files + files_url = f'{api_url}/files' + files = requests.get(files_url, headers=headers).json() + + # Process files + changes_summary = [] + for file in files: + filename = file['filename'] + status = file['status'] + + if status == 'added': + changes_summary.append(f"Added new file: {filename}") + elif status == 'removed': + changes_summary.append(f"Removed file: {filename}") + elif status == 'modified': + if file['binary']: + changes_summary.append(f"Modified binary file: {filename}") + else: + patch = file.get('patch', '') + if patch: + changes_summary.append(f"Modified {filename}:") + changes_summary.append(patch) + elif status == 'renamed': + changes_summary.append(f"Renamed file from {file['previous_filename']} to {filename}") + + # Truncate changes summary if it's too long + truncated_changes = truncate_changes(changes_summary) + changes_text = "\n".join(truncated_changes) + + # Generate description using OpenAI + openai.api_key = os.environ['OPENAI_API_KEY'] + response = openai.ChatCompletion.create( + model="gpt-3.5-turbo", + messages=[ + {"role": "system", "content": "You are a helpful assistant that generates concise pull request descriptions based on changes to files."}, + {"role": "user", "content": f"Generate a brief, informative pull request description based on these changes:\n\n{changes_text}"} + ], + max_tokens=250 + ) + + generated_description = response.choices[0].message['content'].strip() + + # Replace the placeholder in the template with the generated description + updated_description = current_description.replace(default_template_indicator, generated_description) + + # Update PR description + data = {'body': updated_description} + requests.patch(api_url, json=data, headers=headers) + print(f"Updated PR description with generated content") + else: + print("PR already has a non-default description. No action taken.") + +if __name__ == "__main__": + generate_pr_description() \ No newline at end of file From e110bda012c17304b6880dfd97fc2815befc1483 Mon Sep 17 00:00:00 2001 From: Hamza Tahir Date: Tue, 24 Sep 2024 11:02:47 +0200 Subject: [PATCH 02/12] Added PR workflow --- .github/workflows/generate-pr-description.yml | 32 +++++++++++++++++-- scripts/generate_pr_description.py | 4 ++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/.github/workflows/generate-pr-description.yml b/.github/workflows/generate-pr-description.yml index 273797acbd2..0a21d5d5257 100644 --- a/.github/workflows/generate-pr-description.yml +++ b/.github/workflows/generate-pr-description.yml @@ -2,10 +2,10 @@ name: Auto PR Description on: pull_request: - types: [opened] + types: [opened, edited] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true jobs: @@ -15,6 +15,7 @@ jobs: permissions: contents: read pull-requests: write + issues: write steps: - name: Checkout code uses: actions/checkout@v4.1.1 @@ -30,12 +31,39 @@ jobs: source $HOME/.cargo/env uv pip install --system requests openai + - name: Check for previous successful run + id: check_comment + run: | + PR_NUMBER="${{ github.event.pull_request.number }}" + COMMENT=$(gh api -X GET "/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" | jq '.[] | select(.body | contains("Auto PR description generated successfully")) | .id') + if [ -n "$COMMENT" ]; then + echo "Workflow has already run successfully for this PR." + echo "skip=true" >> $GITHUB_OUTPUT + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Wait for potential edits + if: steps.check_comment.outputs.skip == 'false' + run: sleep 300 # Wait for 5 minutes + - name: Generate PR description + if: steps.check_comment.outputs.skip == 'false' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: python scripts/generate_pr_description.py + - name: Add success comment + if: steps.check_comment.outputs.skip == 'false' + run: | + PR_NUMBER="${{ github.event.pull_request.number }}" + gh issue comment ${PR_NUMBER} --body "Auto PR description generated successfully" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Check for errors if: failure() run: | diff --git a/scripts/generate_pr_description.py b/scripts/generate_pr_description.py index 4a65bbbf7ea..0c718c4aae5 100644 --- a/scripts/generate_pr_description.py +++ b/scripts/generate_pr_description.py @@ -85,8 +85,10 @@ def generate_pr_description(): data = {'body': updated_description} requests.patch(api_url, json=data, headers=headers) print(f"Updated PR description with generated content") + return True else: print("PR already has a non-default description. No action taken.") + return False if __name__ == "__main__": - generate_pr_description() \ No newline at end of file + generate_pr_description() From cee2803d29e93b5f7764f08be7042a5752537aa7 Mon Sep 17 00:00:00 2001 From: Hamza Tahir Date: Tue, 24 Sep 2024 11:02:49 +0200 Subject: [PATCH 03/12] Added PR workflow --- scripts/generate_pr_description.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/scripts/generate_pr_description.py b/scripts/generate_pr_description.py index 0c718c4aae5..e373a740cfa 100644 --- a/scripts/generate_pr_description.py +++ b/scripts/generate_pr_description.py @@ -67,22 +67,19 @@ def generate_pr_description(): # Generate description using OpenAI openai.api_key = os.environ['OPENAI_API_KEY'] - response = openai.ChatCompletion.create( - model="gpt-3.5-turbo", + response = openai.OpenAI().chat.completions.create( + model="gpt-4o-mini", messages=[ {"role": "system", "content": "You are a helpful assistant that generates concise pull request descriptions based on changes to files."}, {"role": "user", "content": f"Generate a brief, informative pull request description based on these changes:\n\n{changes_text}"} ], - max_tokens=250 + max_tokens=1000 ) generated_description = response.choices[0].message['content'].strip() - # Replace the placeholder in the template with the generated description - updated_description = current_description.replace(default_template_indicator, generated_description) - # Update PR description - data = {'body': updated_description} + data = {'body': generated_description} requests.patch(api_url, json=data, headers=headers) print(f"Updated PR description with generated content") return True From f8b044fc922090643a22fee10083108f1ec6fd73 Mon Sep 17 00:00:00 2001 From: Hamza Tahir Date: Tue, 24 Sep 2024 11:11:55 +0200 Subject: [PATCH 04/12] Added PR workflow --- scripts/generate_pr_description.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/generate_pr_description.py b/scripts/generate_pr_description.py index e373a740cfa..e0a3648dce9 100644 --- a/scripts/generate_pr_description.py +++ b/scripts/generate_pr_description.py @@ -24,7 +24,12 @@ def generate_pr_description(): # GitHub API setup token = os.environ['GITHUB_TOKEN'] repo = os.environ['GITHUB_REPOSITORY'] - pr_number = os.environ['GITHUB_EVENT_NUMBER'] + + # Get PR number from the event payload + with open(os.environ['GITHUB_EVENT_PATH']) as f: + event = json.load(f) + pr_number = event['pull_request']['number'] + headers = {'Authorization': f'token {token}'} api_url = f'https://api.github.com/repos/{repo}/pulls/{pr_number}' @@ -78,6 +83,9 @@ def generate_pr_description(): generated_description = response.choices[0].message['content'].strip() + # Replace the placeholder in the template with the generated description + updated_description = current_description.replace(default_template_indicator, generated_description) + # Update PR description data = {'body': generated_description} requests.patch(api_url, json=data, headers=headers) From 051a1f06905136acf26c5b5cc479e17f3b9ca7c0 Mon Sep 17 00:00:00 2001 From: Hamza Tahir Date: Tue, 24 Sep 2024 11:12:18 +0200 Subject: [PATCH 05/12] Added PR workflow --- .github/workflows/generate-pr-description.yml | 2 +- scripts/generate_pr_description.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/generate-pr-description.yml b/.github/workflows/generate-pr-description.yml index 0a21d5d5257..d1843017d70 100644 --- a/.github/workflows/generate-pr-description.yml +++ b/.github/workflows/generate-pr-description.yml @@ -47,7 +47,7 @@ jobs: - name: Wait for potential edits if: steps.check_comment.outputs.skip == 'false' - run: sleep 300 # Wait for 5 minutes + run: sleep 60 # Wait for 1 min - name: Generate PR description if: steps.check_comment.outputs.skip == 'false' diff --git a/scripts/generate_pr_description.py b/scripts/generate_pr_description.py index e0a3648dce9..4f6b7ec119b 100644 --- a/scripts/generate_pr_description.py +++ b/scripts/generate_pr_description.py @@ -87,7 +87,7 @@ def generate_pr_description(): updated_description = current_description.replace(default_template_indicator, generated_description) # Update PR description - data = {'body': generated_description} + data = {'body': updated_description} requests.patch(api_url, json=data, headers=headers) print(f"Updated PR description with generated content") return True From d60382152a7cbad9aefea3ad2a55463274cf1f3a Mon Sep 17 00:00:00 2001 From: Hamza Tahir Date: Tue, 24 Sep 2024 11:16:19 +0200 Subject: [PATCH 06/12] Added PR workflow --- scripts/generate_pr_description.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/generate_pr_description.py b/scripts/generate_pr_description.py index 4f6b7ec119b..6699db3dcc7 100644 --- a/scripts/generate_pr_description.py +++ b/scripts/generate_pr_description.py @@ -1,3 +1,4 @@ +import json import os import requests import openai From 8dcc99b5cdf93d7da99ca09b2838d8a6f380109a Mon Sep 17 00:00:00 2001 From: Hamza Tahir Date: Tue, 24 Sep 2024 11:18:32 +0200 Subject: [PATCH 07/12] Added PR workflow --- scripts/generate_pr_description.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_pr_description.py b/scripts/generate_pr_description.py index 6699db3dcc7..55a302870a7 100644 --- a/scripts/generate_pr_description.py +++ b/scripts/generate_pr_description.py @@ -82,7 +82,7 @@ def generate_pr_description(): max_tokens=1000 ) - generated_description = response.choices[0].message['content'].strip() + generated_description =response.choices[0].message.content.strip() # Replace the placeholder in the template with the generated description updated_description = current_description.replace(default_template_indicator, generated_description) From 350094a1df7f0cb8335fb2a8affce4abcb771cdf Mon Sep 17 00:00:00 2001 From: Hamza Tahir Date: Tue, 24 Sep 2024 11:19:04 +0200 Subject: [PATCH 08/12] Added PR workflow --- scripts/generate_pr_description.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_pr_description.py b/scripts/generate_pr_description.py index 55a302870a7..e5dea3c5965 100644 --- a/scripts/generate_pr_description.py +++ b/scripts/generate_pr_description.py @@ -82,7 +82,7 @@ def generate_pr_description(): max_tokens=1000 ) - generated_description =response.choices[0].message.content.strip() + generated_description = response.choices[0].message.content.strip() # Replace the placeholder in the template with the generated description updated_description = current_description.replace(default_template_indicator, generated_description) From 74c3468f05db01ab423ea7a47326a321ef5eab4e Mon Sep 17 00:00:00 2001 From: Hamza Tahir Date: Tue, 24 Sep 2024 11:36:31 +0200 Subject: [PATCH 09/12] Added PR workflow --- .github/workflows/generate-pr-description.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/generate-pr-description.yml b/.github/workflows/generate-pr-description.yml index d1843017d70..c3aa72a2b15 100644 --- a/.github/workflows/generate-pr-description.yml +++ b/.github/workflows/generate-pr-description.yml @@ -45,10 +45,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Wait for potential edits - if: steps.check_comment.outputs.skip == 'false' - run: sleep 60 # Wait for 1 min - - name: Generate PR description if: steps.check_comment.outputs.skip == 'false' env: From fd8ac2314ae33a9939e7fe95401ef9fa11f0e410 Mon Sep 17 00:00:00 2001 From: Hamza Tahir Date: Tue, 24 Sep 2024 11:37:38 +0200 Subject: [PATCH 10/12] Added PR workflow --- .github/workflows/generate-pr-description.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate-pr-description.yml b/.github/workflows/generate-pr-description.yml index c3aa72a2b15..bd5e7c3ff98 100644 --- a/.github/workflows/generate-pr-description.yml +++ b/.github/workflows/generate-pr-description.yml @@ -2,7 +2,7 @@ name: Auto PR Description on: pull_request: - types: [opened, edited] + types: [opened] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} From 155f389d1354e38bb7023a6f04b5d3770c6ce4ef Mon Sep 17 00:00:00 2001 From: Hamza Tahir Date: Tue, 24 Sep 2024 11:49:59 +0200 Subject: [PATCH 11/12] Added PR workflow --- scripts/generate_pr_description.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/scripts/generate_pr_description.py b/scripts/generate_pr_description.py index e5dea3c5965..becb480462b 100644 --- a/scripts/generate_pr_description.py +++ b/scripts/generate_pr_description.py @@ -84,14 +84,22 @@ def generate_pr_description(): generated_description = response.choices[0].message.content.strip() - # Replace the placeholder in the template with the generated description - updated_description = current_description.replace(default_template_indicator, generated_description) + # Fetch the latest PR description right before updating + pr_info = requests.get(api_url, headers=headers).json() + latest_description = pr_info['body'] or '' - # Update PR description - data = {'body': updated_description} - requests.patch(api_url, json=data, headers=headers) - print(f"Updated PR description with generated content") - return True + # Only replace the default template indicator with the generated description + if default_template_indicator in latest_description: + updated_description = latest_description.replace(default_template_indicator, generated_description) + + # Update PR description + data = {'body': updated_description} + requests.patch(api_url, json=data, headers=headers) + print(f"Updated PR description with generated content") + return True + else: + print("Default template indicator no longer present. No changes made.") + return False else: print("PR already has a non-default description. No action taken.") return False From a816e2666eb8b090a32a687bf122be9042b3977f Mon Sep 17 00:00:00 2001 From: Hamza Tahir Date: Tue, 24 Sep 2024 15:28:33 +0200 Subject: [PATCH 12/12] Added PR workflow --- scripts/generate_pr_description.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/generate_pr_description.py b/scripts/generate_pr_description.py index becb480462b..2f81edf8a62 100644 --- a/scripts/generate_pr_description.py +++ b/scripts/generate_pr_description.py @@ -52,11 +52,9 @@ def generate_pr_description(): filename = file['filename'] status = file['status'] - if status == 'added': - changes_summary.append(f"Added new file: {filename}") - elif status == 'removed': + if status == 'removed': changes_summary.append(f"Removed file: {filename}") - elif status == 'modified': + elif status == 'added' or status == 'modified': if file['binary']: changes_summary.append(f"Modified binary file: {filename}") else: