Skip to content

Commit e930385

Browse files
authored
Merge pull request #26 from githubnext/copilot/fix-merge-credentials-issue
Fix credential ordering in sync-branches workflow
2 parents a2ccf62 + 29892fa commit e930385

File tree

3 files changed

+107
-6
lines changed

3 files changed

+107
-6
lines changed

.github/workflows/sync-branches.lock.yml

Lines changed: 5 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/test_scheduling.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,3 +759,77 @@ def test_clone_before_scheduling(self):
759759
f"'{self.CLONE_STEP}' (index {clone_idx}) must come before "
760760
f"'{self.SCHED_STEP}' (index {sched_idx}). Steps: {steps}"
761761
)
762+
763+
764+
class TestSyncBranchesCredentialOrdering:
765+
"""Verify that Git credentials are configured before the merge/push step.
766+
767+
The sync-branches workflow merges the default branch into autoloop/*
768+
branches. Merge commits require a Git identity (user.name/user.email)
769+
and pushes/fetches need an authenticated remote URL. Both must be
770+
configured before the merge step runs.
771+
"""
772+
773+
CRED_STEP = "Set up Git identity and authentication"
774+
MERGE_STEP = "Merge default branch into all autoloop program branches"
775+
776+
def _load_steps(self):
777+
"""Return the list of pre-step names from workflows/sync-branches.md."""
778+
import os
779+
780+
wf_path = os.path.join(os.path.dirname(__file__), "..", "workflows", "sync-branches.md")
781+
with open(wf_path) as f:
782+
content = f.read()
783+
step_names = []
784+
for m in re.finditer(r'^\s*-\s*name:\s*(.+)$', content, re.MULTILINE):
785+
step_names.append(m.group(1).strip())
786+
return step_names
787+
788+
def _load_lock_steps(self):
789+
"""Return the list of step names from .github/workflows/sync-branches.lock.yml."""
790+
import os
791+
import yaml
792+
793+
lock_path = os.path.join(
794+
os.path.dirname(__file__), "..", ".github", "workflows", "sync-branches.lock.yml"
795+
)
796+
with open(lock_path) as f:
797+
data = yaml.safe_load(f)
798+
# Collect step names from the 'agent' job
799+
steps = data.get("jobs", {}).get("agent", {}).get("steps", [])
800+
return [s.get("name", "") for s in steps if s.get("name")]
801+
802+
def test_cred_step_exists(self):
803+
"""A step that configures Git identity/auth must exist in the source."""
804+
steps = self._load_steps()
805+
assert self.CRED_STEP in steps, (
806+
f"Expected step '{self.CRED_STEP}' not found. Steps: {steps}"
807+
)
808+
809+
def test_creds_before_merge(self):
810+
"""The credential step must come before the merge step in the source."""
811+
steps = self._load_steps()
812+
cred_idx = steps.index(self.CRED_STEP)
813+
merge_idx = steps.index(self.MERGE_STEP)
814+
assert cred_idx < merge_idx, (
815+
f"'{self.CRED_STEP}' (index {cred_idx}) must come before "
816+
f"'{self.MERGE_STEP}' (index {merge_idx}). Steps: {steps}"
817+
)
818+
819+
def test_lock_creds_before_merge(self):
820+
"""In the compiled lock file, Configure Git credentials must come before the merge step."""
821+
steps = self._load_lock_steps()
822+
cred_names = [s for s in steps if "Configure Git credentials" in s]
823+
assert cred_names, (
824+
f"No 'Configure Git credentials' step found in lock file. Steps: {steps}"
825+
)
826+
merge_names = [s for s in steps if "Merge default branch" in s]
827+
assert merge_names, (
828+
f"No merge step found in lock file. Steps: {steps}"
829+
)
830+
cred_idx = steps.index(cred_names[0])
831+
merge_idx = steps.index(merge_names[0])
832+
assert cred_idx < merge_idx, (
833+
f"'Configure Git credentials' (index {cred_idx}) must come before "
834+
f"merge step (index {merge_idx}). Steps: {steps}"
835+
)

workflows/sync-branches.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,34 @@ tools:
2020
bash: true
2121

2222
steps:
23+
- name: Set up Git identity and authentication
24+
env:
25+
GH_TOKEN: ${{ github.token }}
26+
GITHUB_REPOSITORY: ${{ github.repository }}
27+
GITHUB_SERVER_URL: ${{ github.server_url }}
28+
run: |
29+
node - << 'JSEOF'
30+
const { spawnSync } = require('child_process');
31+
function git(...args) {
32+
const result = spawnSync('git', args, { encoding: 'utf-8' });
33+
if (result.status !== 0) {
34+
console.error('git ' + args.join(' ') + ' failed: ' + result.stderr);
35+
process.exit(1);
36+
}
37+
return result;
38+
}
39+
git('config', '--global', 'user.email', 'github-actions[bot]@users.noreply.github.com');
40+
git('config', '--global', 'user.name', 'github-actions[bot]');
41+
const ghToken = process.env.GH_TOKEN || '';
42+
const serverUrl = process.env.GITHUB_SERVER_URL || 'https://github.com';
43+
const repo = process.env.GITHUB_REPOSITORY || '';
44+
if (ghToken && repo) {
45+
const authUrl = serverUrl.replace('https://', 'https://x-access-token:' + ghToken + '@') + '/' + repo + '.git';
46+
git('remote', 'set-url', 'origin', authUrl);
47+
}
48+
console.log('Git identity and authentication configured.');
49+
JSEOF
50+
2351
- name: Merge default branch into all autoloop program branches
2452
env:
2553
GITHUB_REPOSITORY: ${{ github.repository }}

0 commit comments

Comments
 (0)