fix(framework): fail closed on missing base and overlapping pages
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
This commit is contained in:
1 parent
abab3a67e2
commit
ed5332b73b
14 files changed
+222
-11
No files matched your search
@@ -219,7 +219,7 @@ scheduling may merge immediately and returns observed merged state rather than q
|
||||
|
||||
Schema 1/2 remain readable. Schema 3 adds optional `merge`, with strict key/value validation
|
||||
through the shared declaration validator and repo-decl helper. Consumers apply the existing
|
||||
branch gates to both versions 2 and 3. Absent merge policy means allowed=[squash], default=squash. The legacy merge interface retains its options and reads a declaration from the PR base commit; a missing or unreadable declaration uses the legacy path. It refuses when that policy selects a non-squash default, unless an explicit squash is allowed. Explicit `--json` calls use the adapter and require `--expect-head`.
|
||||
branch gates to both versions 2 and 3. Absent merge policy means allowed=[squash], default=squash. The legacy merge interface retains its options and reads a declaration from the PR base commit; an absent declaration uses the legacy path; an unavailable base or a declaration that cannot be read or parsed refuses. It refuses when that policy selects a non-squash default, unless an explicit squash is allowed. Explicit `--json` calls use the adapter and require `--expect-head`.
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -302,6 +302,7 @@ class Client:
|
||||
def pages(self, path, key=None):
|
||||
result = []
|
||||
expected_total = 0
|
||||
previous = None
|
||||
for page in range(1, MAX_PAGES + 1):
|
||||
query = ('&' if '?' in path else '?') + ('per_page' if self.provider == 'github' else 'limit') + '=100&page=' + str(page)
|
||||
value = self.http(path + query)
|
||||
@@ -313,7 +314,18 @@ class Client:
|
||||
value = value.get(key)
|
||||
if not isinstance(value, list):
|
||||
raise Failure(6, 'malformed', 'Provider collection is not a list')
|
||||
if self.provider in ('gitea', 'forgejo') and previous and value:
|
||||
by_id = all(isinstance(row, dict) and row.get('id') is not None for row in previous + value)
|
||||
before = [row['id'] for row in previous] if by_id else previous
|
||||
current = [row['id'] for row in value] if by_id else value
|
||||
if current == before:
|
||||
if len(result) < expected_total:
|
||||
raise Failure(6, 'incomplete', 'Provider returned fewer records than total_count')
|
||||
return result
|
||||
if any(row == old for row in current for old in before):
|
||||
raise Failure(6, 'incomplete', 'Provider pages overlap; collection incomplete')
|
||||
result.extend(value)
|
||||
previous = value
|
||||
if not value or self.provider == 'github' and len(value) < LIMIT:
|
||||
if len(result) < expected_total:
|
||||
raise Failure(6,'incomplete','Provider returned fewer records than total_count')
|
||||
|
||||
@@ -181,12 +181,25 @@ PR_AUTHOR="$(printf '%s' "$PR_METADATA" | python3 -c 'import json, sys; value=js
|
||||
# Target branches are validated against the declaration, NEVER hardcoded;
|
||||
# the legacy main/next check survives only for undeclared repos during the
|
||||
# rollout window (4.3 irreversible class, loud warning).
|
||||
# A missing or unreadable declaration at the observed base keeps legacy behavior.
|
||||
# An absent declaration at the observed base keeps legacy behavior.
|
||||
# Never load merge policy or transition data from the caller's working tree.
|
||||
pinned_decl=""
|
||||
pinned_dir=$(mktemp -d "${TMPDIR:-/tmp}/mosaic-merge-policy.XXXXXX")
|
||||
MERGE_TEMP_DIRS+=("$pinned_dir")
|
||||
if git show "$BASE_SHA:.mosaic/repo.json" > "$pinned_dir/repo.json" 2>/dev/null; then
|
||||
if ! git cat-file -e "$BASE_SHA^{commit}" 2>/dev/null; then
|
||||
echo "POLICY-REFUSED: pinned base commit is not available locally; fetch it before merging." >&2
|
||||
exit 3
|
||||
fi
|
||||
if ! git ls-tree "$BASE_SHA" -- .mosaic/repo.json > "$pinned_dir/listing" 2>/dev/null; then
|
||||
echo "POLICY-REFUSED: cannot inspect pinned base declaration." >&2
|
||||
exit 3
|
||||
fi
|
||||
if [[ -s "$pinned_dir/listing" ]]; then
|
||||
if ! git show "$BASE_SHA:.mosaic/repo.json" > "$pinned_dir/repo.json" 2>/dev/null ||
|
||||
! python3 -I -c 'import json,sys; d=json.load(open(sys.argv[1])); assert isinstance(d,dict)' "$pinned_dir/repo.json" 2>/dev/null; then
|
||||
echo "POLICY-REFUSED: pinned base declaration cannot be read or parsed." >&2
|
||||
exit 3
|
||||
fi
|
||||
pinned_decl="$pinned_dir/repo.json"
|
||||
fi
|
||||
# shellcheck source=packages/mosaic/framework/tools/git/repo-decl.sh
|
||||
|
||||
@@ -30,7 +30,7 @@ for flag in ['--config','-K']:
|
||||
observed={'path':u.path,'method':method,'argv_leak':any(secret in x for x in a),'authenticated':config=='header = '+json.dumps('Authorization: token '+secret)+chr(10),'anonymous':anonymous}
|
||||
with open(os.environ['TEST_AUTH_OBSERVATIONS'],'a') as f: f.write(json.dumps(observed)+chr(10))
|
||||
if observed['argv_leak']: sys.exit(97)
|
||||
head='a'*40; base='b'*40
|
||||
head='a'*40; base=os.environ['TEST_AUTH_BASE']
|
||||
pull=dict(number=1,state='open',draft=False,merged=False,mergeable=True,title='Title',head={'sha':head,'ref':'task','repo':{'full_name':'owner/repo'}},base={'sha':base,'ref':'main','repo':{'full_name':'owner/repo'}},user={'login':'author'})
|
||||
if u.path=='/swagger.v1.json': body={'info':{'title':'Gitea API','version':'1.24.7'}}
|
||||
elif u.path.endswith('/user'): body={'login':'fixture-user'}
|
||||
@@ -111,6 +111,8 @@ class Auth(unittest.TestCase):
|
||||
repo=self.root/'repo'; repo.mkdir()
|
||||
subprocess.run(['git','init','-q','-b','task',str(repo)],check=True,env=self.env)
|
||||
subprocess.run(['git','-C',str(repo),'remote','add','origin','https://forge.example.test/owner/repo'],check=True,env=self.env)
|
||||
subprocess.run(['git','-C',str(repo),'-c','user.name=Fixture','-c','[email protected]','commit','--allow-empty','-qm','base'],check=True,env=self.env)
|
||||
self.env['TEST_AUTH_BASE']=subprocess.run(['git','-C',str(repo),'rev-parse','HEAD'],check=True,env=self.env,capture_output=True,text=True).stdout.strip()
|
||||
bin_dir=self.root/'bin';bin_dir.mkdir()
|
||||
(bin_dir/'curl').write_text(LEGACY_CURL);(bin_dir/'curl').chmod(0o755)
|
||||
(bin_dir/'tea').write_text('#!/bin/sh\nexit 1\n');(bin_dir/'tea').chmod(0o755)
|
||||
|
||||
@@ -30,6 +30,9 @@ entry=fixtures.get(method+' '+url)
|
||||
if entry is None:
|
||||
print('unexpected provider URL '+url,file=sys.stderr); sys.exit(98)
|
||||
response=entry['response']; path=entry['path']
|
||||
log=pathlib.Path(os.environ['FAKE_LOG'])
|
||||
if response.get('after_merge') and log.exists() and any(json.loads(line)['method']=='POST' and json.loads(line)['path'].endswith('/pulls/1/merge') for line in log.read_text().splitlines()):
|
||||
response=dict(response,data=response['after_merge'])
|
||||
query=urllib.parse.urlsplit(url).query
|
||||
if '--input' in args or '--data-binary' in args:
|
||||
body=json.load(sys.stdin)
|
||||
@@ -224,6 +227,42 @@ class CLI(unittest.TestCase):
|
||||
|
||||
|
||||
|
||||
def test_gitea_status_with_unpaginated_comments_and_protections(self):
|
||||
self.gitea(); head='a'*40
|
||||
self.fixtures['GET /repos/owner/repo/pulls/1']={'data':{'number':1,'state':'open','draft':False,'merged':False,'mergeable':True,'head':{'sha':head,'ref':'task','repo':{'full_name':'owner/repo'}},'base':{'sha':'b'*40,'ref':'next','repo':{'full_name':'owner/repo'}},'user':{'login':'author'}}}
|
||||
values={'/repos/owner/repo/branch_protections':[{'branch_name':'next','enable_status_check':True,'status_check_contexts':['build'],'required_approvals':1}],'/repos/owner/repo/issues/1/comments':[{'id':11,'body':'issue'}],'/repos/owner/repo/pulls/1/reviews/1/comments':[{'id':22,'resolver':{'login':'reviewer'},'user':{'login':'reviewer'}}]}
|
||||
for path,rows in values.items():
|
||||
self.fixtures['GET '+path]={'data':rows}
|
||||
self.fixtures['GET '+path+'?limit=100&page=2']={'data':rows}
|
||||
self.fixtures['GET /repos/owner/repo/commits/'+head+'/statuses']={'data':[{'id':1,'context':'build','state':'success'}]}
|
||||
self.fixtures['GET /repos/owner/repo/pulls/1/reviews']={'data':[{'id':1,'state':'APPROVED','commit_id':head,'user':{'login':'reviewer'}}]}
|
||||
p,v=self.run_wrapper('pr-status','-n','1',host='git.example.test')
|
||||
self.assertEqual(p.returncode,0,p.stdout+p.stderr)
|
||||
self.assertTrue(v['data']['ready']); self.assertEqual(len(v['data']['issue_comments']),1)
|
||||
self.assertEqual(len(v['data']['comments']),1)
|
||||
# Exercise the actual JSON merge wrapper with a local pinned base and a
|
||||
# fake queue guard. Provider reads and mutation remain fixture-only.
|
||||
import shutil
|
||||
repo=self.root/'repo'; repo.mkdir()
|
||||
def git(*args):
|
||||
return subprocess.run(['git','-C',str(repo),*args],env=self.env,capture_output=True,text=True,check=True).stdout.strip()
|
||||
git('init','-q'); git('remote','add','origin','https://git.example.test/owner/repo')
|
||||
git('-c','user.name=Fixture','-c','[email protected]','commit','--allow-empty','-qm','base')
|
||||
pr=self.fixtures['GET /repos/owner/repo/pulls/1']['data']
|
||||
pr['base'].update(sha=git('rev-parse','HEAD'),ref='main')
|
||||
values['/repos/owner/repo/branch_protections'][0]['branch_name']='main'
|
||||
self.fixtures['GET /repos/owner/repo']={'data':{'default_branch':'main','allow_squash_merge':True}}
|
||||
self.fixtures['GET /user']={'data':{'login':'fixture-user'}}
|
||||
self.fixtures['POST /repos/owner/repo/pulls/1/merge']={'data':None}
|
||||
self.fixtures['GET /repos/owner/repo/pulls/1']['after_merge']=dict(pr,merged=True,merge_commit_sha='d'*40)
|
||||
tools=self.root/'tools/git'; shutil.copytree(HERE,tools)
|
||||
guard=tools/'ci-queue-wait.sh'; guard.write_text('#!/bin/sh\nexit 0\n'); guard.chmod(0o755)
|
||||
self.write_responses('git.example.test')
|
||||
p=subprocess.run(['bash',str(tools/'pr-merge.sh'),'--host','git.example.test','--repo','owner/repo','--json','-n','1','--expect-head',head],cwd=repo,env=self.env,capture_output=True,text=True)
|
||||
self.assertEqual(p.returncode,0,p.stdout+p.stderr)
|
||||
self.assertTrue(json.loads(p.stdout)['data']['merged'])
|
||||
self.assertEqual(sum(call['method']=='POST' and call['path'].endswith('/pulls/1/merge') for call in self.calls()),1)
|
||||
|
||||
def test_watch_predicate_preserves_baseline_and_read_errors(self):
|
||||
self.test_gitea_status_and_ci_wait_use_exact_head()
|
||||
key='GET /repos/owner/repo/commits/'+'a'*40+'/statuses'
|
||||
|
||||
@@ -107,6 +107,80 @@ class Contract(unittest.TestCase):
|
||||
self.assertEqual(len(result), 120)
|
||||
self.assertEqual(self.api.reviews(result, 'a', 'author')['decision'], 'changes_requested')
|
||||
|
||||
def test_forge_duplicate_and_partial_pages(self):
|
||||
import urllib.parse
|
||||
for provider in ('gitea','forgejo'):
|
||||
for first,second in [([{'id':1,'v':'old'},{'id':2}],[{'id':1,'v':'new'},{'id':2}]), ([{'branch_name':'next'}],[{'branch_name':'next'}])]:
|
||||
c=self.api.Client('git.example.test','owner/repo'); c.provider=provider
|
||||
calls=[]
|
||||
def http(path):
|
||||
page=int(urllib.parse.parse_qs(urllib.parse.urlsplit(path).query)['page'][0]); calls.append(page)
|
||||
return first if page == 1 else second
|
||||
c.http=http
|
||||
self.assertEqual(c.pages('/collection'),first)
|
||||
self.assertEqual(calls,[1,2])
|
||||
for first,second in [([{'id':1},{'id':2}],[{'id':2},{'id':3}]), ([{'name':'one'},{'name':'two'}],[{'name':'two'},{'name':'three'}])]:
|
||||
c=self.api.Client('git.example.test','owner/repo'); c.provider=provider
|
||||
c.http=lambda path: first if 'page=1' in path else second
|
||||
with self.assertRaises(self.api.Failure) as caught: c.pages('/collection')
|
||||
self.assertEqual((caught.exception.code,caught.exception.kind),(6,'incomplete'))
|
||||
|
||||
def test_forge_partial_overlap_fails_closed(self):
|
||||
for provider in ('gitea','forgejo'):
|
||||
for first,second in [([{'id':1},{'id':2}],[{'id':2},{'id':3}]), ([{'name':'one'},{'name':'two'}],[{'name':'two'},{'name':'three'}])]:
|
||||
with self.subTest(provider=provider,first=first):
|
||||
c=self.api.Client('git.example.test','owner/repo'); c.provider=provider
|
||||
c.http=lambda path: first if 'page=1' in path else second if 'page=2' in path else []
|
||||
with self.assertRaises(self.api.Failure) as caught: c.pages('/collection')
|
||||
self.assertEqual((caught.exception.code,caught.exception.kind),(6,'incomplete'))
|
||||
|
||||
def test_forge_unpaginated_collections_status_and_merge_json(self):
|
||||
import contextlib, copy, io, json, os, sys, urllib.parse
|
||||
from unittest.mock import patch
|
||||
api=self.api
|
||||
for provider,version in [('gitea','1.24.7'),('forgejo','16.0.0-dev')]:
|
||||
with self.subTest(provider=provider):
|
||||
c=api.Client('git.example.test','owner/repo'); c.provider=provider; c.version=version
|
||||
c.detect=lambda: None
|
||||
head='a'*40
|
||||
pr={'number':1,'state':'open','draft':False,'merged':False,'mergeable':True,'head':{'sha':head,'ref':'task','repo':{'full_name':'owner/repo'}},'base':{'sha':'b'*40,'ref':'next','repo':{'full_name':'owner/repo'}},'user':{'login':'author'}}
|
||||
rules=[{'branch_name':'next','enable_status_check':True,'status_check_contexts':['build'],'required_approvals':1}]
|
||||
issue=[{'id':11,'body':'issue'}]
|
||||
comment=[{'id':22,'body':'review','resolver':{'login':'reviewer'},'user':{'login':'reviewer'}}]
|
||||
state={'merged':False,'calls':[]}
|
||||
def http(path,method='GET',body=None,public=False):
|
||||
clean=path.split('?',1)[0]; state['calls'].append((clean,method,path))
|
||||
if method=='POST': state['merged']=True; return None
|
||||
if clean.endswith('/pulls/1'):
|
||||
data=copy.deepcopy(pr)
|
||||
if state['merged']: data.update(merged=True,merge_commit_sha='d'*40)
|
||||
return data
|
||||
if clean.endswith('/branch_protections'): return rules
|
||||
if clean.endswith('/issues/1/comments'): return issue
|
||||
if clean.endswith('/reviews/1/comments'): return comment
|
||||
page=int(urllib.parse.parse_qs(urllib.parse.urlsplit(path).query).get('page',['1'])[0])
|
||||
if clean.endswith('/statuses'): return [{'id':2,'context':'build','state':'success'}] if page==1 else []
|
||||
if clean.endswith('/reviews'): return [{'id':1,'state':'APPROVED','commit_id':head,'user':{'login':'reviewer'}}] if page==1 else []
|
||||
if clean==c.root: return {'allow_squash_merge':True}
|
||||
raise AssertionError(path)
|
||||
c.http=http
|
||||
def run_main(operation):
|
||||
out=io.StringIO()
|
||||
args=['forge-api.py',operation,'--host','git.example.test','--repo','owner/repo','-n','1','--json','--expect-head',head]
|
||||
with patch.object(sys,'argv',args), patch.dict(os.environ,{'MOSAIC_GIT_IDENTITY':'fixture'}), patch.object(api,'Client',return_value=c), patch.object(api,'declared_method',return_value='squash'), patch.object(api,'queue_guard'), contextlib.redirect_stdout(out):
|
||||
code=api.main()
|
||||
return code,json.loads(out.getvalue())
|
||||
with self.subTest(operation='status'):
|
||||
code,data=run_main('status'); self.assertEqual(code,0,data)
|
||||
self.assertTrue(data['data']['ready']); self.assertEqual(data['data']['issue_comments'],issue)
|
||||
self.assertEqual(len(data['data']['comments']),1)
|
||||
with self.subTest(operation='merge'):
|
||||
code,data=run_main('merge'); self.assertEqual(code,0,data)
|
||||
self.assertTrue(data['data']['merged'])
|
||||
self.assertEqual(sum(method=='POST' for _,method,_ in state['calls']),1)
|
||||
for suffix in ('branch_protections','issues/1/comments','reviews/1/comments'):
|
||||
self.assertTrue(any(path.endswith(suffix) and 'page=2' in url for path,_,url in state['calls']))
|
||||
|
||||
def test_gitea_empty_write_and_post_merge_moved_base(self):
|
||||
import os, types
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -150,6 +150,58 @@ class Policy(unittest.TestCase):
|
||||
result=subprocess.run(['bash',str(git_tools/'pr-merge.sh'),'-n','1','--dry-run'],cwd=repo,env=env,capture_output=True,text=True)
|
||||
self.assertNotEqual(result.returncode,0,result.stdout+result.stderr)
|
||||
|
||||
def test_legacy_pinned_base_availability_and_declaration(self):
|
||||
import os, shutil
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root=pathlib.Path(directory); repo=root/'repo'; repo.mkdir()
|
||||
git_tools=root/'tools/git'; git_tools.mkdir(parents=True)
|
||||
structure=root/'tools/structure'; structure.mkdir()
|
||||
for name in ['pr-merge.sh','repo-decl.sh','merge-policy.py']:
|
||||
shutil.copy(HERE/name,git_tools/name)
|
||||
shutil.copy(HERE.parent/'structure/validate-repo-json.sh',structure/'validate-repo-json.sh')
|
||||
(git_tools/'detect-platform.sh').write_text('detect_platform() { echo github; }\nget_repo_owner() { echo owner; }\nget_repo_name() { echo repo; }\n')
|
||||
def git(*args):
|
||||
return subprocess.run(['git',*args],cwd=repo,capture_output=True,text=True,check=True).stdout.strip()
|
||||
git('init','-q'); git('remote','add','origin','https://github.com/owner/repo')
|
||||
git('-c','user.name=Fixture','-c','[email protected]','commit','--allow-empty','-qm','absent')
|
||||
absent=git('rev-parse','HEAD')
|
||||
(repo/'.mosaic').mkdir()
|
||||
doc=dict(schema_version=3,integration_trunk='next',release_branch='main',flow='trunk-release',canonical_remote='https://github.com/owner/repo',canonical_clone='host:/src/repo',merge=dict(allowed_methods=['merge'],default_method='merge',shapes={'promotion':'merge'}))
|
||||
path=repo/'.mosaic/repo.json'; path.write_text(json.dumps(doc)); git('add','.mosaic/repo.json')
|
||||
git('-c','user.name=Fixture','-c','[email protected]','commit','-qm','declared')
|
||||
declared=git('rev-parse','HEAD')
|
||||
script=git_tools/'pr-metadata.sh'
|
||||
metadata=dict(baseRefName='main',baseRefOid='f'*40,baseRepository='owner/repo',headRefName='feature',headRefOid='a'*40,headRepository='owner/repo')
|
||||
env=dict(os.environ,HOME=str(root),GIT_CONFIG_GLOBAL='/dev/null',GIT_CONFIG_NOSYSTEM='1')
|
||||
def invoke():
|
||||
script.write_text("#!/bin/sh\nprintf '%s\\n' "+repr(json.dumps(metadata))+"\n"); script.chmod(0o755)
|
||||
return subprocess.run(['bash',str(git_tools/'pr-merge.sh'),'-n','1','--dry-run'],cwd=repo,env=env,capture_output=True,text=True)
|
||||
for head in ['feature','next']:
|
||||
with self.subTest(head=head):
|
||||
metadata['headRefName']=head
|
||||
result=invoke()
|
||||
self.assertEqual(result.returncode,3,result.stdout+result.stderr)
|
||||
self.assertIn('fetch',result.stderr.lower())
|
||||
self.assertIn('not available locally',result.stderr.lower())
|
||||
metadata.update(headRefName='feature',baseRefName='next',baseRefOid=absent)
|
||||
result=invoke(); self.assertEqual(result.returncode,0,result.stdout+result.stderr)
|
||||
self.assertIn('Dry run:',result.stdout)
|
||||
metadata['baseRefOid']=declared
|
||||
bindir=root/'bin'; bindir.mkdir()
|
||||
realgit=shutil.which('git')
|
||||
fake=bindir/'git'; fake.write_text('#!/bin/sh\ncase "$1" in show) exit 1;; esac\nexec '+realgit+' "$@"\n'); fake.chmod(0o755)
|
||||
env['PATH']=str(bindir)+os.pathsep+os.environ['PATH']
|
||||
result=invoke(); self.assertEqual(result.returncode,3,result.stdout+result.stderr)
|
||||
self.assertIn('declaration',result.stderr.lower())
|
||||
fake.write_text('#!/bin/sh\ncase "$1" in ls-tree) exit 1;; esac\nexec '+realgit+' "$@"\n')
|
||||
result=invoke(); self.assertEqual(result.returncode,3,result.stdout+result.stderr)
|
||||
self.assertIn('inspect',result.stderr.lower())
|
||||
env['PATH']=os.environ['PATH']
|
||||
path.write_text('{invalid'); git('add','.mosaic/repo.json')
|
||||
git('-c','user.name=Fixture','-c','[email protected]','commit','-qm','invalid')
|
||||
metadata['baseRefOid']=git('rev-parse','HEAD')
|
||||
result=invoke(); self.assertEqual(result.returncode,3,result.stdout+result.stderr)
|
||||
|
||||
def test_github_metadata_reads_pr_repository_identities(self):
|
||||
import os, shutil
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
|
||||
@@ -18,12 +18,15 @@ OUT_LOG="$WORK_DIR/out.log"
|
||||
|
||||
rm -rf "$WORK_DIR"
|
||||
mkdir -p "$FIXTURE_DIR"
|
||||
git -C "$WORK_DIR" init -q
|
||||
git -C "$WORK_DIR" -c user.name=Fixture -c user.email=[email protected] commit --allow-empty -qm base
|
||||
export MOSAIC_TEST_BASE_SHA="$(git -C "$WORK_DIR" rev-parse HEAD)"
|
||||
cp "$SCRIPT_DIR/pr-merge.sh" "$FIXTURE_DIR/pr-merge.sh"
|
||||
cp "$SCRIPT_DIR/detect-platform.sh" "$FIXTURE_DIR/detect-platform.sh"
|
||||
|
||||
cat > "$FIXTURE_DIR/pr-metadata.sh" <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s\n' '{"baseRefOid":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","baseRefName":"mosaic-cli-p0","baseRepository":"mosaicstack/stack","headRefName":"mosaic-cli-p2-socket-res","headRefOid":"0123456789abcdef0123456789abcdef01234567","headRepository":"mosaicstack/stack"}'
|
||||
printf '%s\n' '{"baseRefOid":"'"$MOSAIC_TEST_BASE_SHA"'","baseRefName":"mosaic-cli-p0","baseRepository":"mosaicstack/stack","headRefName":"mosaic-cli-p2-socket-res","headRefOid":"0123456789abcdef0123456789abcdef01234567","headRepository":"mosaicstack/stack"}'
|
||||
SH
|
||||
|
||||
cat > "$FIXTURE_DIR/ci-queue-wait.sh" <<'SH'
|
||||
|
||||
@@ -21,7 +21,7 @@ cp "$SCRIPT_DIR/detect-platform.sh" "$FIXTURE_DIR/detect-platform.sh"
|
||||
|
||||
cat > "$FIXTURE_DIR/pr-metadata.sh" <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s\n' '{"baseRefOid":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","baseRefName":"next","headRefName":"fix/b1-fork-branch","headRefOid":"fedcba9876543210fedcba9876543210fedcba98","headRepository":"stack-mos-dt-0/stack"}'
|
||||
printf '%s\n' '{"baseRefOid":"'"$MOSAIC_TEST_BASE_SHA"'","baseRefName":"next","headRefName":"fix/b1-fork-branch","headRefOid":"fedcba9876543210fedcba9876543210fedcba98","headRepository":"stack-mos-dt-0/stack"}'
|
||||
SH
|
||||
|
||||
cat > "$FIXTURE_DIR/ci-queue-wait.sh" <<'SH'
|
||||
@@ -33,6 +33,8 @@ chmod +x "$FIXTURE_DIR"/*.sh
|
||||
|
||||
# A git repo with an origin remote, so the origin fallback resolves.
|
||||
git init -q "$WORK_DIR/upstream"
|
||||
git -C "$WORK_DIR/upstream" -c user.name=Fixture -c user.email=[email protected] commit --allow-empty -qm base
|
||||
export MOSAIC_TEST_BASE_SHA="$(git -C "$WORK_DIR/upstream" rev-parse HEAD)"
|
||||
git -C "$WORK_DIR/upstream" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
|
||||
|
||||
set +e
|
||||
|
||||
@@ -93,7 +93,7 @@ if [[ "$args" == *"/api/v1/repos/mosaicstack/stack/commits/0123456789abcdef01234
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$args" == *"/api/v1/repos/mosaicstack/stack/pulls/123"* && "$args" != *"/api/v1/repos/mosaicstack/stack/pulls/123/merge"* ]]; then
|
||||
emit_response '{"number":123,"title":"mock","state":"open","user":{"login":"tester"},"head":{"ref":"feature/mock","sha":"0123456789abcdef0123456789abcdef01234567","repo":{"full_name":"mosaicstack/stack"}},"base":{"ref":"main","sha":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"},"labels":[],"assignees":[],"html_url":"https://git.mosaicstack.dev/mosaicstack/stack/pulls/123","mergeable":true}'
|
||||
emit_response "$(printf '%s' '{"number":123,"title":"mock","state":"open","user":{"login":"tester"},"head":{"ref":"feature/mock","sha":"0123456789abcdef0123456789abcdef01234567","repo":{"full_name":"mosaicstack/stack"}},"base":{"ref":"main","sha":"BASE_SHA_FIXTURE"},"labels":[],"assignees":[],"html_url":"https://git.mosaicstack.dev/mosaicstack/stack/pulls/123","mergeable":true}' | sed "s/BASE_SHA_FIXTURE/$MOSAIC_TEST_BASE_SHA/")"
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$args" == *"-X POST"* && "$args" == *"/api/v1/repos/mosaicstack/stack/pulls/123/merge"* ]]; then
|
||||
@@ -116,6 +116,8 @@ chmod +x "$MOCK_BIN/curl"
|
||||
|
||||
cd "$REPO_DIR"
|
||||
git init -q
|
||||
git -c user.name=Fixture -c user.email=[email protected] commit --allow-empty -qm base
|
||||
export MOSAIC_TEST_BASE_SHA="$(git rev-parse HEAD)"
|
||||
git remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
|
||||
# HERMETICITY (#1007) — TWO mechanisms with DIFFERENT jobs; do not conflate them.
|
||||
#
|
||||
|
||||
@@ -17,11 +17,13 @@ make_fixture() {
|
||||
cp "$SCRIPT_DIR/pr-merge.sh" "$tools/pr-merge.sh"
|
||||
cp "$SCRIPT_DIR/detect-platform.sh" "$tools/detect-platform.sh"
|
||||
git -C "$root/repo" init -q
|
||||
git -C "$root/repo" -c user.name=Fixture -c user.email=[email protected] commit --allow-empty -qm base
|
||||
local base_sha; base_sha=$(git -C "$root/repo" rev-parse HEAD)
|
||||
git -C "$root/repo" remote add origin "$remote"
|
||||
local base_branch="${3:-main}"
|
||||
cat > "$tools/pr-metadata.sh" <<SH
|
||||
#!/usr/bin/env bash
|
||||
printf '%s\n' '{"baseRefOid":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","baseRefName":"$base_branch","headRefName":"fix/pinned","headRefOid":"$SHA","headRepository":"contributor/widgets-fork"}'
|
||||
printf '%s\n' '{"baseRefOid":"$base_sha","baseRefName":"$base_branch","headRefName":"fix/pinned","headRefOid":"$SHA","headRepository":"contributor/widgets-fork"}'
|
||||
SH
|
||||
cat > "$tools/ci-queue-wait.sh" <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
|
||||
@@ -12,6 +12,9 @@ failures=0
|
||||
|
||||
rm -rf "$WORK_DIR"
|
||||
mkdir -p "$WORK_DIR"
|
||||
git -C "$WORK_DIR" init -q
|
||||
git -C "$WORK_DIR" -c user.name=Fixture -c user.email=[email protected] commit --allow-empty -qm base
|
||||
export MOSAIC_TEST_BASE_SHA="$(git -C "$WORK_DIR" rev-parse HEAD)"
|
||||
|
||||
fail() {
|
||||
echo "FAIL $1" >&2
|
||||
@@ -62,7 +65,7 @@ case "${MOSAIC_TEST_COMMITS_MODE:?}" in
|
||||
single) head_sha=1111111111111111111111111111111111111111 ;;
|
||||
*) echo "unknown commits mode" >&2; exit 2 ;;
|
||||
esac
|
||||
printf '{"number":42,"title":"%s","author":"poster","baseRefOid":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","baseRefName":"main","headRefName":"feature/fixture","headRefOid":"%s","headRepository":"acme/widgets"}\n' "$title" "$head_sha"
|
||||
printf '{"number":42,"title":"%s","author":"poster","baseRefOid":"%s","baseRefName":"main","headRefName":"feature/fixture","headRefOid":"%s","headRepository":"acme/widgets"}\n' "$title" "$MOSAIC_TEST_BASE_SHA" "$head_sha"
|
||||
SH
|
||||
|
||||
cat > "$case_dir/ci-queue-wait.sh" <<'SH'
|
||||
@@ -269,6 +272,7 @@ SH
|
||||
run_case() {
|
||||
local case_dir="$1" mode="$2"
|
||||
shift 2
|
||||
cd "$WORK_DIR" || return
|
||||
MOSAIC_TEST_COMMITS_MODE="$mode" \
|
||||
MOSAIC_TEST_CURL_LOG="$case_dir/curl.log" \
|
||||
MOSAIC_TEST_CURL_BOUNDS_LOG="$case_dir/curl-bounds.log" \
|
||||
|
||||
@@ -14,12 +14,15 @@ CALL_LOG="$WORK_DIR/queue-call.log"
|
||||
|
||||
rm -rf "$WORK_DIR"
|
||||
mkdir -p "$FIXTURE_DIR"
|
||||
git -C "$WORK_DIR" init -q
|
||||
git -C "$WORK_DIR" -c user.name=Fixture -c user.email=[email protected] commit --allow-empty -qm base
|
||||
export MOSAIC_TEST_BASE_SHA="$(git -C "$WORK_DIR" rev-parse HEAD)"
|
||||
cp "$SCRIPT_DIR/pr-merge.sh" "$FIXTURE_DIR/pr-merge.sh"
|
||||
cp "$SCRIPT_DIR/detect-platform.sh" "$FIXTURE_DIR/detect-platform.sh"
|
||||
|
||||
cat > "$FIXTURE_DIR/pr-metadata.sh" <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s\n' '{"baseRefOid":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","baseRefName":"main","baseRepository":"mosaicstack/stack","headRefName":"fix/no-ci-fixture","headRefOid":"0123456789abcdef0123456789abcdef01234567","headRepository":"mosaicstack/stack"}'
|
||||
printf '%s\n' '{"baseRefOid":"'"$MOSAIC_TEST_BASE_SHA"'","baseRefName":"main","baseRepository":"mosaicstack/stack","headRefName":"fix/no-ci-fixture","headRefOid":"0123456789abcdef0123456789abcdef01234567","headRepository":"mosaicstack/stack"}'
|
||||
SH
|
||||
|
||||
cat > "$FIXTURE_DIR/ci-queue-wait.sh" <<'SH'
|
||||
|
||||
@@ -10,12 +10,15 @@ CALL_LOG="$WORK_DIR/queue-call.log"
|
||||
|
||||
rm -rf "$WORK_DIR"
|
||||
mkdir -p "$FIXTURE_DIR"
|
||||
git -C "$WORK_DIR" init -q
|
||||
git -C "$WORK_DIR" -c user.name=Fixture -c user.email=[email protected] commit --allow-empty -qm base
|
||||
export MOSAIC_TEST_BASE_SHA="$(git -C "$WORK_DIR" rev-parse HEAD)"
|
||||
cp "$SCRIPT_DIR/pr-merge.sh" "$FIXTURE_DIR/pr-merge.sh"
|
||||
cp "$SCRIPT_DIR/detect-platform.sh" "$FIXTURE_DIR/detect-platform.sh"
|
||||
|
||||
cat > "$FIXTURE_DIR/pr-metadata.sh" <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s\n' '{"baseRefOid":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","baseRefName":"main","baseRepository":"mosaicstack/stack","headRefName":"fix/rm-03-fixture","headRefOid":"0123456789abcdef0123456789abcdef01234567","headRepository":"contributor/widgets-fork"}'
|
||||
printf '%s\n' '{"baseRefOid":"'"$MOSAIC_TEST_BASE_SHA"'","baseRefName":"main","baseRepository":"mosaicstack/stack","headRefName":"fix/rm-03-fixture","headRefOid":"0123456789abcdef0123456789abcdef01234567","headRepository":"contributor/widgets-fork"}'
|
||||
SH
|
||||
|
||||
cat > "$FIXTURE_DIR/ci-queue-wait.sh" <<'SH'
|
||||
|
||||
Reference in new issue
Block a user