diff --git a/.gitignore b/.gitignore index c7f68db..12bd337 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ temp.txt .DS_Store __pycache__ -test_patch_data.json \ No newline at end of file +test_patch_data.json + +# used for local testing +.env \ No newline at end of file diff --git a/README.md b/README.md index f6b1570..16cebf3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@ ![GitHub tag (with filter)](https://img.shields.io/github/v/tag/hestonhoffman/changed-lines) -This GitHub action returns the file names and modified lines of each file in a pull request. This is useful if you're running a custom linter against your files and you want to compare log lines against modified lines in your PR. +This GitHub action returns the file names and modified lines of each file in a pull request. This action is intended for custom linters, where you want to compare log lines against modified lines in your PR. + +The action uses the patch data returned from the [Git API PR endpoint](https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests-files) to collect the modified file names and lines. Changes in your PR are compared against the target branch and line numbers in the output are relative to the modified file. If the entire file is new, all lines appear in the output. + > [!NOTE] > This action only works with pull requests. @@ -86,4 +89,35 @@ Use the `api_url` input if you need to use a custom GitHub API URL. This is usef run: echo ${{ steps.changed_lines.outputs.changed_files }} ``` -Thanks to Jacob Tomlinson [for a helpful tutorial](https://jacobtomlinson.dev/posts/2019/creating-github-actions-in-python/). \ No newline at end of file +Thanks to Jacob Tomlinson [for a helpful tutorial](https://jacobtomlinson.dev/posts/2019/creating-github-actions-in-python/). + +## Local development + +You can use the instructions below to load some environment variables so you can run the script locally. You'll need a GitHub personal access token with repo privileges. + +If you want to run the script locally: +1. (Optional) Set up a virtual environment using your preferred method. +1. Install the python modules: + ```shell + pip install -r requirements.txt + ``` +1. Make a copy of `example.env` and change the environment variables to point to a PR you want to test against: + + **Note**: This file (`.env`) is ignored by git, but make sure you don't accidentally commit it somehow, as it stores your git token. + + ```shell + cp example.env .env + ``` +1. Source the environment variables: + ```shell + source .env + ``` +1. Run the script: + ```shell + python main.py + ``` +1. Check the generated text file for the results. The environment `GITHUB_OUTPUT` variable sets this to `temp.txt` by default. + +## Deleted files and lines + +Deleted files and lines don't show up in the output. This is intentional because the main use case for the action is to pass changed lines to a linter for GitHub command annotations. The GitHub API doesn't allow annotations on deleted lines. \ No newline at end of file diff --git a/example.env b/example.env new file mode 100644 index 0000000..8a17b1c --- /dev/null +++ b/example.env @@ -0,0 +1,7 @@ +export INPUT_API_URL=https://api.github.com +export INPUT_TOKEN=your_token_here +export INPUT_BRANCH=some_branch +export INPUT_REPO=owner/repo +export INPUT_PR=99 +export GITHUB_OUTPUT=temp.txt +export INPUT_DELIMITER=' ' \ No newline at end of file diff --git a/main.py b/main.py index 7a278aa..b0e2a9c 100644 --- a/main.py +++ b/main.py @@ -24,15 +24,16 @@ def fetch_patch(): 'X-GitHub-Api-Version': '2022-11-28', 'Authorization':f'Bearer {TOKEN}' } - # Fetch the first page + # Fetch the first page with per_page=100 response = git_session.get( - f"{api_url}/repos/{repo}/pulls/{pr}/files", headers=headers + f"{api_url}/repos/{repo}/pulls/{pr}/files?per_page=100", headers=headers ) files = response.json() # Follow 'next' links to fetch the remaining pages while 'next' in response.links: response = git_session.get(response.links['next']['url'], headers=headers) files.extend(response.json()) + return files def parse_patch_data(patch_data): @@ -47,22 +48,22 @@ def parse_patch_data(patch_data): # Some really big files don't have a patch key because GitHub # returns a message in the PR that the file is too large to display if entry['additions'] != 0 and 'patch' in entry: - patch_array = re.split('\n', entry['patch']) - # clean patch array - patch_array = [i for i in patch_array if i] + patch_array = re.split('\n', entry['patch']) + # clean patch array + patch_array = [i for i in patch_array if i] - for item in patch_array: - # Grabs hunk annotation and strips out added lines - if item.startswith('@@ -'): - if sublist: - line_array.append(sublist) - sublist = [re.sub(r'\s@@(.*)','',item.split('+')[1])] - # We don't need removed lines ('-') - elif not item.startswith('-') and not item == '\\ No newline at end of file': - sublist.append(item) - if sublist: - line_array.append(sublist) - final_dict[entry['filename']] = line_array + for item in patch_array: + # Grabs hunk annotation and strips out added lines + if item.startswith('@@ -'): + if sublist: + line_array.append(sublist) + sublist = [re.sub(r'\s@@(.*)','',item.split('+')[1])] + # We don't need removed lines ('-') + elif not item.startswith('-') and not item == '\\ No newline at end of file': + sublist.append(item) + if sublist: + line_array.append(sublist) + final_dict[entry['filename']] = line_array return final_dict def get_lines(line_dict):