Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 62 additions & 15 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
go build -buildvcs=false -o ./action-template/dist/commit-headless-linux-amd64 .

- name: Upload action
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: action
path: action-template/
Expand All @@ -56,7 +56,7 @@ jobs:
fetch-depth: 0

- &download
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: action
path: action/
Expand Down Expand Up @@ -103,6 +103,17 @@ jobs:
exit 1
fi

- &verify-no-tmp-branches
name: Verify no leftover working branches
run: |
tmp_branches=$(git ls-remote --heads origin | grep -- '${{ env.TEST_BRANCH }}--headless-tmp-' || true)
if [ -n "$tmp_branches" ]; then
echo "ERROR: leftover working branches found:"
echo "$tmp_branches"
exit 1
fi
echo "No leftover working branches"

- &cleanup
name: Cleanup
if: always()
Expand Down Expand Up @@ -163,16 +174,7 @@ jobs:
exit 1
fi

- &verify-no-tmp-branches
name: Verify no leftover working branches
run: |
tmp_branches=$(git ls-remote --heads origin | grep -- '${{ env.TEST_BRANCH }}--headless-tmp-' || true)
if [ -n "$tmp_branches" ]; then
echo "ERROR: leftover working branches found:"
echo "$tmp_branches"
exit 1
fi
echo "No leftover working branches"
- *verify-no-tmp-branches

- *cleanup

Expand All @@ -184,7 +186,6 @@ jobs:
- *checkout
- *download
- *configure-git

- *create-branch

- name: Sync local with remote
Expand All @@ -198,6 +199,7 @@ jobs:
branch: ${{ env.TEST_BRANCH }}
command: push

- *verify-no-tmp-branches
- *cleanup

test-force:
Expand Down Expand Up @@ -339,6 +341,7 @@ jobs:
exit 1
fi

- *verify-no-tmp-branches
- *cleanup

test-commit:
Expand All @@ -349,7 +352,6 @@ jobs:
- *checkout
- *download
- *configure-git

- *create-branch

- name: Stage changes
Expand Down Expand Up @@ -393,6 +395,7 @@ jobs:
message: "this should not appear"
command: commit

- *verify-no-tmp-branches
- *cleanup

test-modes:
Expand All @@ -403,7 +406,6 @@ jobs:
- *checkout
- *download
- *configure-git

- *create-branch

- name: Create executable
Expand Down Expand Up @@ -436,6 +438,50 @@ jobs:
- *verify-no-tmp-branches
- *cleanup

test-binary-content:
runs-on: ubuntu-latest
needs: build
env: *env
steps:
- *checkout
- *download
- *configure-git
- *create-branch

- name: Stage binary from dist
run: |
cp action/dist/commit-headless-linux-amd64 ./test-binary
chmod +x test-binary
sha256sum test-binary > expected-checksum.txt
echo "Local binary checksum:"
cat expected-checksum.txt

git add test-binary expected-checksum.txt
git commit -m "test: add binary executable"

- name: Push binary
uses: ./action/
with:
branch: ${{ env.TEST_BRANCH }}
command: push

- name: Verify binary content is not corrupted
run: |
git fetch origin ${{ env.TEST_BRANCH }}
git checkout origin/${{ env.TEST_BRANCH }} -- test-binary expected-checksum.txt

echo "Remote binary checksum:"
sha256sum test-binary

if ! sha256sum -c expected-checksum.txt; then
echo "ERROR: binary content was corrupted during push"
exit 1
fi
echo "Binary content integrity verified"

- *verify-no-tmp-branches
- *cleanup

test-replay:
runs-on: ubuntu-latest
needs: build
Expand Down Expand Up @@ -568,4 +614,5 @@ jobs:

echo "Single commit replay test passed"

- *verify-no-tmp-branches
- *cleanup
4 changes: 2 additions & 2 deletions github.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,8 +393,8 @@ func (c *Client) prepTree(ctx context.Context, headCommit string, change Change)
// Deletion: SHA must be empty string for go-github to omit it
} else {
blob, _, err := c.git.CreateBlob(ctx, c.owner, c.repo, github.Blob{
Content: github.Ptr(string(fe.Content)),
Encoding: github.Ptr("utf-8"),
Content: github.Ptr(base64.StdEncoding.EncodeToString(fe.Content)),
Encoding: github.Ptr("base64"),
})
if err != nil {
return "", fmt.Errorf("create blob for %s: %w", path, err)
Expand Down
78 changes: 78 additions & 0 deletions github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -744,6 +745,83 @@ func TestPushChanges(t *testing.T) {
})
}

func TestRESTBlobUsesBase64Encoding(t *testing.T) {
// Regression test: binary file content must be base64-encoded when creating
// blobs via the REST API. Using utf-8 encoding corrupts binary data (e.g. ELF
// executables) because invalid UTF-8 sequences are mangled during string
// conversion and JSON marshaling.
binaryContent := []byte{0x7f, 'E', 'L', 'F', 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0x80, 0x90}

var capturedEncoding, capturedContent string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

switch {
case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/repos/test-owner/test-repo/git/commits/"):
json.NewEncoder(w).Encode(github.Commit{
SHA: github.Ptr("parent-sha"),
Tree: &github.Tree{SHA: github.Ptr("parent-tree-sha")},
})

case r.Method == http.MethodPost && r.URL.Path == "/repos/test-owner/test-repo/git/blobs":
var req struct {
Content string `json:"content"`
Encoding string `json:"encoding"`
}
json.NewDecoder(r.Body).Decode(&req)
capturedEncoding = req.Encoding
capturedContent = req.Content

w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(github.Blob{SHA: github.Ptr("blob-sha")})

case r.Method == http.MethodPost && r.URL.Path == "/repos/test-owner/test-repo/git/trees":
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(github.Tree{SHA: github.Ptr("tree-sha")})

default:
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{})
}
}))
defer server.Close()

client := newTestClient(t, server)
change := Change{
entries: map[string]FileEntry{
"binary-file": {Content: binaryContent, Mode: "100755"},
},
}

_, err := client.prepTree(context.Background(), "parent-sha", change)
if err != nil {
t.Fatalf("prepTree failed: %v", err)
}

if capturedEncoding != "base64" {
t.Errorf("expected blob encoding 'base64', got %q", capturedEncoding)
}

expectedContent := base64.StdEncoding.EncodeToString(binaryContent)
if capturedContent != expectedContent {
t.Errorf("blob content mismatch\n got: %q\n want: %q", capturedContent, expectedContent)
}

// Verify round-trip: decoding the sent content must yield the original bytes
decoded, err := base64.StdEncoding.DecodeString(capturedContent)
if err != nil {
t.Fatalf("failed to decode captured content: %v", err)
}
if len(decoded) != len(binaryContent) {
t.Fatalf("decoded length %d != original length %d", len(decoded), len(binaryContent))
}
for i := range binaryContent {
if decoded[i] != binaryContent[i] {
t.Errorf("byte %d: got 0x%02x, want 0x%02x", i, decoded[i], binaryContent[i])
}
}
}

func TestChooseStrategy(t *testing.T) {
tests := []struct {
name string
Expand Down
2 changes: 1 addition & 1 deletion version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package main

const VERSION = "3.1.1"
const VERSION = "3.2.0"
Loading