Skip to content

Commit f49ea73

Browse files
Update sync-dev-to-main.yml
1 parent 3aa9781 commit f49ea73

File tree

1 file changed

+0
-318
lines changed

1 file changed

+0
-318
lines changed
Lines changed: 0 additions & 318 deletions
Original file line numberDiff line numberDiff line change
@@ -1,319 +1 @@
1-
name: Sync dev to main
21

3-
# Automatically merge dev → main daily at 8 AM PST (16:00 UTC)
4-
# Uses github-actions[bot] with bypass permissions to skip PR approval requirements
5-
# Handles conflicts gracefully (logs warning, skips merge, doesn't fail)
6-
7-
on:
8-
schedule:
9-
# Run daily at 8:00 AM PST (16:00 UTC)
10-
- cron: '0 16 * * *'
11-
12-
workflow_dispatch: # Allow manual triggering
13-
14-
permissions:
15-
contents: write # Push to main branch
16-
issues: write # Create issues on conflict
17-
actions: read # Check workflow status (optional)
18-
19-
env:
20-
SOURCE_BRANCH: dev
21-
TARGET_BRANCH: main
22-
# Teams webhook URL stored in GitHub Secrets for security
23-
TEAMS_WEBHOOK_URL: ${{ secrets.TEAMS_WEBHOOK_URL }}
24-
25-
jobs:
26-
sync-branches:
27-
runs-on: ubuntu-latest
28-
29-
steps:
30-
- name: Checkout repository
31-
uses: actions/checkout@v4
32-
with:
33-
fetch-depth: 0 # Full history for merge
34-
token: ${{ secrets.GITHUB_TOKEN }}
35-
36-
- name: Configure git
37-
run: |
38-
git config user.name "github-actions[bot]"
39-
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
40-
41-
- name: Check if dev has new commits
42-
id: check-new-commits
43-
run: |
44-
git fetch origin ${{ env.SOURCE_BRANCH }}
45-
git fetch origin ${{ env.TARGET_BRANCH }}
46-
47-
# Get commit counts
48-
COMMITS_AHEAD=$(git rev-list --count origin/${{ env.TARGET_BRANCH }}..origin/${{ env.SOURCE_BRANCH }})
49-
50-
echo "commits_ahead=$COMMITS_AHEAD" >> $GITHUB_OUTPUT
51-
52-
if [ "$COMMITS_AHEAD" -eq 0 ]; then
53-
echo "ℹ️ No new commits in ${{ env.SOURCE_BRANCH }} - nothing to sync"
54-
echo "has_new_commits=false" >> $GITHUB_OUTPUT
55-
else
56-
echo "✅ Found $COMMITS_AHEAD new commit(s) in ${{ env.SOURCE_BRANCH }}"
57-
echo "has_new_commits=true" >> $GITHUB_OUTPUT
58-
fi
59-
60-
- name: Wait for build workflow to complete
61-
if: steps.check-new-commits.outputs.has_new_commits == 'true'
62-
run: |
63-
echo "⏳ Waiting for 'build-and-deploy.yml' workflow to complete on ${{ env.SOURCE_BRANCH }}..."
64-
65-
MAX_WAIT=1800 # 30 minutes max wait
66-
WAIT_INTERVAL=30 # Check every 30 seconds
67-
ELAPSED=0
68-
69-
while [ $ELAPSED -lt $MAX_WAIT ]; do
70-
# Get latest build workflow run on dev
71-
BUILD_RUN=$(gh run list --workflow="build-and-deploy.yml" \
72-
--branch ${{ env.SOURCE_BRANCH }} --limit 1 --json status,conclusion,databaseId \
73-
--jq '.[0]' 2>/dev/null || echo '{}')
74-
75-
STATUS=$(echo "$BUILD_RUN" | jq -r '.status // "unknown"')
76-
CONCLUSION=$(echo "$BUILD_RUN" | jq -r '.conclusion // "none"')
77-
RUN_ID=$(echo "$BUILD_RUN" | jq -r '.databaseId // "none"')
78-
79-
echo "Build status: $STATUS | Conclusion: $CONCLUSION | Run ID: $RUN_ID"
80-
81-
# Check if build completed
82-
if [ "$STATUS" = "completed" ]; then
83-
if [ "$CONCLUSION" = "success" ]; then
84-
echo "✅ Build workflow completed successfully!"
85-
break
86-
elif [ "$CONCLUSION" = "failure" ]; then
87-
echo "❌ Build workflow failed on ${{ env.SOURCE_BRANCH }}"
88-
echo "⚠️ Cannot sync to main - build must pass first"
89-
exit 1
90-
elif [ "$CONCLUSION" = "cancelled" ]; then
91-
echo "⚠️ Build workflow was cancelled"
92-
echo "⚠️ Cannot sync to main - build must complete successfully"
93-
exit 1
94-
else
95-
echo "⚠️ Build completed with conclusion: $CONCLUSION"
96-
exit 1
97-
fi
98-
fi
99-
100-
# Build still in progress
101-
echo "⏳ Build still running... waiting ${WAIT_INTERVAL}s (${ELAPSED}s elapsed)"
102-
sleep $WAIT_INTERVAL
103-
ELAPSED=$((ELAPSED + WAIT_INTERVAL))
104-
done
105-
106-
if [ $ELAPSED -ge $MAX_WAIT ]; then
107-
echo "❌ Timeout: Build did not complete within ${MAX_WAIT}s"
108-
exit 1
109-
fi
110-
env:
111-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
112-
113-
- name: Attempt merge
114-
id: merge
115-
if: steps.check-new-commits.outputs.has_new_commits == 'true'
116-
run: |
117-
echo "🔄 Attempting to merge ${{ env.SOURCE_BRANCH }} → ${{ env.TARGET_BRANCH }}..."
118-
119-
# Checkout target branch
120-
git checkout ${{ env.TARGET_BRANCH }}
121-
122-
# Attempt merge (don't fail on conflict)
123-
set +e
124-
git merge --no-edit --no-ff origin/${{ env.SOURCE_BRANCH }} 2>&1 | tee merge_output.txt
125-
MERGE_EXIT_CODE=$?
126-
set -e
127-
128-
if [ $MERGE_EXIT_CODE -ne 0 ]; then
129-
echo "❌ Merge conflict detected"
130-
echo "conflict=true" >> $GITHUB_OUTPUT
131-
132-
# Get conflicted files
133-
git status --short | grep '^UU\|^AA\|^DD' > conflicts.txt || echo "Unable to detect specific files" > conflicts.txt
134-
135-
# Abort the merge
136-
git merge --abort
137-
138-
echo "⚠️ Skipping automated merge due to conflicts"
139-
exit 0 # Exit success to allow graceful handling
140-
else
141-
echo "✅ Merge completed successfully"
142-
echo "conflict=false" >> $GITHUB_OUTPUT
143-
fi
144-
145-
- name: Push to main
146-
if: steps.check-new-commits.outputs.has_new_commits == 'true' && steps.merge.outputs.conflict == 'false'
147-
run: |
148-
echo "📤 Pushing merged changes to ${{ env.TARGET_BRANCH }}..."
149-
150-
# Push directly to main (bypass branch protection via github-actions[bot])
151-
git push origin ${{ env.TARGET_BRANCH }}
152-
153-
echo "✅ Successfully synced ${{ env.SOURCE_BRANCH }} → ${{ env.TARGET_BRANCH }}"
154-
155-
- name: Get merge details for notification
156-
if: steps.check-new-commits.outputs.has_new_commits == 'true' && steps.merge.outputs.conflict == 'false'
157-
id: merge-details
158-
run: |
159-
# Get commit details
160-
COMMIT_COUNT=${{ steps.check-new-commits.outputs.commits_ahead }}
161-
COMMIT_SHA=$(git rev-parse HEAD | cut -c1-7)
162-
163-
# Get commit messages
164-
git log --oneline origin/${{ env.TARGET_BRANCH }}~$COMMIT_COUNT..origin/${{ env.TARGET_BRANCH }} | head -10 > commits.txt
165-
166-
echo "commit_count=$COMMIT_COUNT" >> $GITHUB_OUTPUT
167-
echo "commit_sha=$COMMIT_SHA" >> $GITHUB_OUTPUT
168-
169-
- name: Send success notification to Teams
170-
if: steps.check-new-commits.outputs.has_new_commits == 'true' && steps.merge.outputs.conflict == 'false'
171-
run: |
172-
# Get commit messages (truncate if too long)
173-
COMMITS=$(cat commits.txt | head -5 | sed 's/^/ - /')
174-
175-
curl -H "Content-Type: application/json" -d '{
176-
"@type": "MessageCard",
177-
"@context": "https://schema.org/extensions",
178-
"summary": "Dev → Main Sync Successful",
179-
"themeColor": "28a745",
180-
"title": "✅ Automated Sync: dev → main",
181-
"sections": [{
182-
"activityTitle": "Successfully merged '${{ steps.check-new-commits.outputs.commits_ahead }}' commits",
183-
"activitySubtitle": "Repository: netwrix/docs",
184-
"facts": [
185-
{"name": "Commits merged:", "value": "${{ steps.merge-details.outputs.commit_count }}"},
186-
{"name": "Latest commit:", "value": "${{ steps.merge-details.outputs.commit_sha }}"},
187-
{"name": "Triggered by:", "value": "${{ github.event_name }}"},
188-
{"name": "Workflow:", "value": "${{ github.workflow }}"}
189-
],
190-
"text": "**Recent commits:**\n\n'"$(echo "$COMMITS" | sed 's/$/\\n/' | tr -d '\n')"'"
191-
}],
192-
"potentialAction": [{
193-
"@type": "OpenUri",
194-
"name": "View Workflow Run",
195-
"targets": [{"os": "default", "uri": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}]
196-
}, {
197-
"@type": "OpenUri",
198-
"name": "View Repository",
199-
"targets": [{"os": "default", "uri": "${{ github.server_url }}/${{ github.repository }}"}]
200-
}]
201-
}' "${{ env.TEAMS_WEBHOOK_URL }}" || echo "Failed to send Teams notification"
202-
203-
- name: Create GitHub issue for conflict
204-
if: steps.check-new-commits.outputs.has_new_commits == 'true' && steps.merge.outputs.conflict == 'true'
205-
uses: actions/github-script@v7
206-
with:
207-
script: |
208-
const conflictedFiles = require('fs').readFileSync('conflicts.txt', 'utf8');
209-
const mergeOutput = require('fs').readFileSync('merge_output.txt', 'utf8');
210-
211-
const issue = await github.rest.issues.create({
212-
owner: context.repo.owner,
213-
repo: context.repo.repo,
214-
title: '⚠️ Automated dev→main sync blocked by merge conflict',
215-
labels: ['automated-sync', 'merge-conflict'],
216-
body: '## Automated Sync Conflict\n\n' +
217-
'The automated sync from `dev` to `main` encountered merge conflicts and was skipped.\n\n' +
218-
'**Workflow Run:** ' + context.serverUrl + '/' + context.repo.owner + '/' + context.repo.repo + '/actions/runs/' + context.runId + '\n' +
219-
'**Triggered by:** ' + context.eventName + '\n' +
220-
'**Date:** ' + new Date().toISOString() + '\n\n' +
221-
'### Conflicted Files\n\n' +
222-
'```\n' +
223-
conflictedFiles + '\n' +
224-
'```\n\n' +
225-
'### Merge Output\n\n' +
226-
'```\n' +
227-
mergeOutput + '\n' +
228-
'```\n\n' +
229-
'### Resolution Steps\n\n' +
230-
'1. Manually resolve conflicts:\n' +
231-
' ```bash\n' +
232-
' git checkout main\n' +
233-
' git pull origin main\n' +
234-
' git merge origin/dev\n' +
235-
' # Resolve conflicts in your editor\n' +
236-
' git add .\n' +
237-
' git commit\n' +
238-
' git push origin main\n' +
239-
' ```\n\n' +
240-
'2. Or create a PR from dev → main and resolve conflicts there\n\n' +
241-
'3. Once resolved, the next scheduled sync will proceed normally\n\n' +
242-
'---\n' +
243-
'*This issue was created automatically by the sync-dev-to-main workflow.*'
244-
});
245-
246-
console.log('Created issue #' + issue.data.number);
247-
248-
- name: Send conflict notification to Teams
249-
if: steps.check-new-commits.outputs.has_new_commits == 'true' && steps.merge.outputs.conflict == 'true'
250-
run: |
251-
curl -H "Content-Type: application/json" -d '{
252-
"@type": "MessageCard",
253-
"@context": "https://schema.org/extensions",
254-
"summary": "Dev → Main Sync Blocked by Conflict",
255-
"themeColor": "ff9800",
256-
"title": "⚠️ Automated Sync Blocked: Merge Conflict",
257-
"sections": [{
258-
"activityTitle": "Manual intervention required",
259-
"activitySubtitle": "Repository: netwrix/docs",
260-
"facts": [
261-
{"name": "Source branch:", "value": "${{ env.SOURCE_BRANCH }}"},
262-
{"name": "Target branch:", "value": "${{ env.TARGET_BRANCH }}"},
263-
{"name": "Commits waiting:", "value": "${{ steps.check-new-commits.outputs.commits_ahead }}"},
264-
{"name": "Status:", "value": "Merge conflict detected"}
265-
],
266-
"text": "The automated sync encountered merge conflicts. A GitHub issue has been created with details. Please resolve the conflicts manually."
267-
}],
268-
"potentialAction": [{
269-
"@type": "OpenUri",
270-
"name": "View Workflow Run",
271-
"targets": [{"os": "default", "uri": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}]
272-
}, {
273-
"@type": "OpenUri",
274-
"name": "View Issues",
275-
"targets": [{"os": "default", "uri": "${{ github.server_url }}/${{ github.repository }}/issues?q=is:issue+is:open+label:merge-conflict"}]
276-
}]
277-
}' "${{ env.TEAMS_WEBHOOK_URL }}" || echo "Failed to send Teams notification"
278-
279-
- name: Send skip notification to Teams
280-
if: steps.check-new-commits.outputs.has_new_commits == 'false'
281-
run: |
282-
curl -H "Content-Type: application/json" -d '{
283-
"@type": "MessageCard",
284-
"@context": "https://schema.org/extensions",
285-
"summary": "No Changes to Sync",
286-
"themeColor": "0078D4",
287-
"title": "ℹ️ No Changes to Sync",
288-
"sections": [{
289-
"activityTitle": "Branches are already in sync",
290-
"activitySubtitle": "Repository: netwrix/docs",
291-
"text": "No new commits found in dev branch. Nothing to sync."
292-
}]
293-
}' "${{ env.TEAMS_WEBHOOK_URL }}" || echo "Failed to send Teams notification"
294-
295-
- name: Workflow summary
296-
if: always()
297-
run: |
298-
echo "## Sync Dev to Main - Summary" >> $GITHUB_STEP_SUMMARY
299-
echo "" >> $GITHUB_STEP_SUMMARY
300-
301-
if [ "${{ steps.check-new-commits.outputs.has_new_commits }}" == "false" ]; then
302-
echo "ℹ️ **Status:** No new commits to sync" >> $GITHUB_STEP_SUMMARY
303-
echo "- Source: \`${{ env.SOURCE_BRANCH }}\`" >> $GITHUB_STEP_SUMMARY
304-
echo "- Target: \`${{ env.TARGET_BRANCH }}\`" >> $GITHUB_STEP_SUMMARY
305-
elif [ "${{ steps.merge.outputs.conflict }}" == "true" ]; then
306-
echo "⚠️ **Status:** Merge conflict detected" >> $GITHUB_STEP_SUMMARY
307-
echo "- Commits waiting: ${{ steps.check-new-commits.outputs.commits_ahead }}" >> $GITHUB_STEP_SUMMARY
308-
echo "- Action: GitHub issue created" >> $GITHUB_STEP_SUMMARY
309-
echo "- Resolution: Manual merge required" >> $GITHUB_STEP_SUMMARY
310-
else
311-
echo "✅ **Status:** Successfully synced" >> $GITHUB_STEP_SUMMARY
312-
echo "- Commits merged: ${{ steps.merge-details.outputs.commit_count }}" >> $GITHUB_STEP_SUMMARY
313-
echo "- Latest commit: \`${{ steps.merge-details.outputs.commit_sha }}\`" >> $GITHUB_STEP_SUMMARY
314-
echo "- Pushed to: \`${{ env.TARGET_BRANCH }}\`" >> $GITHUB_STEP_SUMMARY
315-
fi
316-
317-
echo "" >> $GITHUB_STEP_SUMMARY
318-
echo "---" >> $GITHUB_STEP_SUMMARY
319-
echo "*Automated by github-actions[bot] with branch protection bypass*" >> $GITHUB_STEP_SUMMARY

0 commit comments

Comments
 (0)