Repository: canonical/actions
Pull request #13: Add HEAD_REF to changed-files action
Author: login: RenanGreca
Created: 2025-04-08T07:02:49Z | Merged: 2025-04-09T23:11:41Z

Hi @Saviq, unfortunately we need to make one more change here. Although `github.ref` results in `refs/pull/<number>/merge`, it appears that the fetch needs to use that value *both* as a reference and as a branch name.

This is now producing the output I expect:
<img width="648" alt="Screenshot 2025-04-08 at 09 01 14" src="https://github.com/user-attachments/assets/744295f9-eed5-4ca9-afe7-a7d9e7a65fab" />


Diff:
diff --git a/.github/workflows/test_changed_files.yaml b/.github/workflows/test_changed_files.yaml
new file mode 100644
index 0000000..5d81f93
--- /dev/null
+++ b/.github/workflows/test_changed_files.yaml
@@ -0,0 +1,96 @@
+name: Test changed-files action
+
+on:
+  pull_request:
+    paths:
+      - 'changed-files/action.yml'
+
+jobs:
+  test-changed-file:
+    name: Check that changed-files action detects changed files in the current PR
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 1
+
+      - name: Get changed files
+        id: changed-files
+        uses: ./changed-files
+
+      - name: Check changed files output
+        run: |
+          changed_files="${{ steps.changed-files.outputs.changed-files }}"
+          # This action only triggers on changes to `changed-files/action.yml`
+          # Thus `changed-files/action.yml` should be in the list of changed files
+          expected_file="changed-files/action.yml"
+
+          # Check that the file is in the list of changed files
+          if echo $changed_files | grep --fixed-strings --quiet --line-regexp "$expected_file"; then
+            echo "$expected_file found in changed files"
+          else
+            echo "$expected_file not found in changed files"
+            exit 1
+          fi
+
+  test-known-refs:
+    name: Check that changed-files action works with custom refs
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 1
+
+      - name: Get changed files
+        id: changed-files
+        uses: ./changed-files
+        with:
+          base-ref: a7fc56f262863f374b44aa020e163c013d3c75ad
+          head-ref: 20063fbd0a3dde28a87d0a8767a4f16c7a5c9c6e
+
+      - name: Check changed files output
+        run: |
+          changed_files="${{ steps.changed-files.outputs.changed-files }}"
+          # We know that `changed-files/action.yml` is changed between the two refs
+          expected_file="changed-files/action.yml"
+
+          # Check that the file is in the list of changed files
+          if echo $changed_files | grep --fixed-strings --quiet --line-regexp "$expected_file"; then
+            echo "$expected_file found in changed files"
+          else
+            echo "$expected_file not found in changed files"
+            exit 1
+          fi
+
+  test-unchanged-file:
+    name: Check that changed-files action does not detect unchanged files
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 1
+
+      - name: Get changed files
+        id: changed-files
+        uses: ./changed-files
+        with:
+          base-ref: a7fc56f262863f374b44aa020e163c013d3c75ad
+          head-ref: 20063fbd0a3dde28a87d0a8767a4f16c7a5c9c6e
+
+      - name: Check changed files output
+        run: |
+          changed_files="${{ steps.changed-files.outputs.changed-files }}"
+          # We know that `renovate.json` is not changed between the two refs
+          expected_file="renovate.json"
+
+          # Check that the file is *not* in the list of changed files
+          if echo $changed_files | grep --fixed-strings --quiet --line-regexp "$expected_file"; then
+            echo "$expected_file found in changed files"
+            exit 1
+          else
+            echo "$expected_file not found in changed files"
+          fi
+
diff --git a/changed-files/action.yml b/changed-files/action.yml
index 5053cfc..93f55eb 100644
--- a/changed-files/action.yml
+++ b/changed-files/action.yml
@@ -1,13 +1,13 @@
 name: Changed files
-description: Get list of changed files between two branches
+description: Get list of changed files between two git refs.
 
 inputs:
-  base-branch:
-    description: Base branch (destination of a pull request)
-    required: true
+  base-ref:
+    description: Base branch (destination of a pull request). Defaults to the base branch of the pull request.
+    required: false
     default: ${{ github.base_ref }}
-  head-branch:
-    description: Head branch (source of a pull request)
+  head-ref:
+    description: Head branch (source of a pull request). Defaults to the ref of the pull request.
     required: false
     default: ${{ github.ref }}
 
@@ -23,14 +23,11 @@ runs:
       id: changed-files
       shell: bash
       run: |
-        BASE_BRANCH=${{ inputs.base-branch }}
-        HEAD_BRANCH=${{ inputs.head-branch }}
-
-        git fetch origin $BASE_BRANCH --depth 1
-        git fetch origin $HEAD_BRANCH --depth 1
+        git fetch origin ${{ inputs.base-ref }}:refs/heads/changed_base --depth 1
+        git fetch origin ${{ inputs.head-ref }}:refs/heads/changed_head --depth 1
 
         cat <<END_OUTPUT >> $GITHUB_OUTPUT
         all_changed_files<<EOF
-        $( git diff --name-only origin/$BASE_BRANCH $HEAD_BRANCH )
+        $( git diff --name-only refs/heads/changed_base refs/heads/changed_head )
         EOF
         END_OUTPUT


Reviews:
user: login: Saviq
body: @RenanGreca I think we need tests here... if someone changes `head-branch`, they'll end up with something really weird:

```
# ...
      head-branch: foobar

# ...
      git fetch origin refs/pull/123/merge:foobar
```

So it will pretend to compare `foobar` to `$BASE_REF`, but will actually compare `${{ github.ref }}` instead?
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-08T07:11:04Z
user: login: Saviq
body: > But I'm not sure how to automate conditions such as an incoming branch vs an incoming fork.

You can just explicitly set the inputs to some known refs and check some static output.

In fact, with the changes I proposed I would rename `_branch` to `_ref`, as it's no longer about branches. You could give it SHAs from history.
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-09T08:16:24Z
user: login: RenanGreca
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T09:54:14Z
user: login: RenanGreca
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T09:57:07Z
user: login: Saviq
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-09T10:52:25Z
user: login: Saviq
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-09T23:11:14Z

Review comments:
diff_hunk: @@ -25,9 +25,10 @@ runs:
       run: |
         BASE_BRANCH=${{ inputs.base-branch }}
         HEAD_BRANCH=${{ inputs.head-branch }}
+        HEAD_REF=${{ github.ref }}:$HEAD_BRANCH
 
         git fetch origin $BASE_BRANCH --depth 1
-        git fetch origin $HEAD_BRANCH --depth 1
+        git fetch origin $HEAD_REF --depth 1
path: changed-files/action.yml
user: login: Saviq
body: ```suggestion
        git fetch origin ${{ inputs.base_ref }}:refs/heads/changed_base --depth 1
        git fetch origin ${{ inputs.head_ref }}:refs/heads/changed_head --depth 1
```

What this does is tells `git` to fetch things from`origin` to local refs.

Below, you would then do `git diff --name-only refs/heads/compare_base refs/heads/compare_head`.
created_at: 2025-04-09T08:12:01Z
author_association: COLLABORATOR
diff_hunk: @@ -25,9 +25,10 @@ runs:
       run: |
         BASE_BRANCH=${{ inputs.base-branch }}
         HEAD_BRANCH=${{ inputs.head-branch }}
+        HEAD_REF=${{ github.ref }}:$HEAD_BRANCH
path: changed-files/action.yml
user: login: Saviq
body: This isn't a `_REF`, mind you.

```suggestion
```
created_at: 2025-04-09T08:12:35Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,29 @@
+on:
+  pull_request:
+    paths:
+      - 'changed-files/action.yml'
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 1
+
+      - name: Get changed files
+        id: changed-files
+        uses: ./changed-files
+
+      - name: Check changed files output
+        run: |
+          changed_files="${{ steps.changed-files.outputs.changed-files }}"
+          expected_file="changed-files/action.yml"
+
+          if [[ $changed_files =~ $expected_file ]]; then
path: .github/workflows/test_changed_files.yaml
user: login: Saviq
body: No need for regexes.
```suggestion
          if [ "$changed_files" = "$expected_file" ]; then
```
created_at: 2025-04-09T08:16:17Z
author_association: COLLABORATOR
diff_hunk: @@ -25,9 +25,10 @@ runs:
       run: |
         BASE_BRANCH=${{ inputs.base-branch }}
         HEAD_BRANCH=${{ inputs.head-branch }}
+        HEAD_REF=${{ github.ref }}:$HEAD_BRANCH
 
         git fetch origin $BASE_BRANCH --depth 1
-        git fetch origin $HEAD_BRANCH --depth 1
+        git fetch origin $HEAD_REF --depth 1
path: changed-files/action.yml
user: login: RenanGreca
body: This is good, thanks!
created_at: 2025-04-09T09:54:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,29 @@
+on:
+  pull_request:
+    paths:
+      - 'changed-files/action.yml'
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 1
+
+      - name: Get changed files
+        id: changed-files
+        uses: ./changed-files
+
+      - name: Check changed files output
+        run: |
+          changed_files="${{ steps.changed-files.outputs.changed-files }}"
+          expected_file="changed-files/action.yml"
+
+          if [[ $changed_files =~ $expected_file ]]; then
path: .github/workflows/test_changed_files.yaml
user: login: RenanGreca
body: Wouldn't this break if `changed_files` includes any other file? For example this PR also includes `.github/workflows/test_changed_files.yaml` itself.

Perhaps `echo $changed_files | grep -F -q -x "$expected_file"`?
created_at: 2025-04-09T09:57:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,29 @@
+on:
+  pull_request:
+    paths:
+      - 'changed-files/action.yml'
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 1
+
+      - name: Get changed files
+        id: changed-files
+        uses: ./changed-files
+
+      - name: Check changed files output
+        run: |
+          changed_files="${{ steps.changed-files.outputs.changed-files }}"
+          expected_file="changed-files/action.yml"
+
+          if [[ $changed_files =~ $expected_file ]]; then
path: .github/workflows/test_changed_files.yaml
user: login: Saviq
body: Ah, that's what you're after - yeah that would be better indeed.

Though I prefer long options for scripts:

```
echo $changed_files | grep --fixed-strings --quiet --line-regexp $expected_file
```
created_at: 2025-04-09T10:52:24Z
author_association: COLLABORATOR

PR comments:
user: login: RenanGreca
created_at: 2025-04-08T07:22:24Z
body: Yes, that's true. Perhaps it's better to simply remove `head-branch` as an input, and make this action exclusively use a PR as head. Customizing `head-branch` may make sense if the PR is within a single repository/remote, but it seems to me like it would always break if it's coming from a (non-public) fork.
author_association: CONTRIBUTOR
user: login: RenanGreca
created_at: 2025-04-08T07:24:26Z
body: As for tests... would it make sense to add an action to this repository that simulates the scenarios when a PR touches this file?
author_association: CONTRIBUTOR
user: login: RenanGreca
created_at: 2025-04-08T08:09:43Z
body: I added a simple test that at least ensures the action logic is sound (if `changed-files/action.yml` is in the PR, then it should be detected as modified...). But I'm not sure how to automate conditions such as an incoming branch vs an incoming fork.
author_association: CONTRIBUTOR
user: login: RenanGreca
created_at: 2025-04-09T13:33:47Z
body: This is looking nicer now!

Here's the test run in my forked repository:
<img width="922" alt="Screenshot 2025-04-09 at 15 30 47" src="https://github.com/user-attachments/assets/aad2cd49-e7ea-4405-9849-dd17ccb20b55" />
It won't run in this PR because `test_changed_files.yaml` is not in `main` yet.
author_association: CONTRIBUTOR