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
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,12 @@ The action follows this workflow:
2. **Stash changes** - Safely stashes any uncommitted changes (including untracked files)
3. **Sync with upstream** - Pulls latest changes using rebase to maintain linear history
4. **Restore changes** - Pops the stash to restore your changes
- **Automatic conflict resolution**: If conflicts occur between stashed changes and pulled changes, the action automatically resolves them by accepting your stashed changes (the newer modifications from this workflow)
- This ensures concurrent workflow changes are properly merged without leaving conflict markers
5. **Stage files** - Adds files matching the specified pattern
6. **Commit** - Creates a commit if there are staged changes
7. **Push with retry** - Attempts to push with automatic retry on failure:
6. **Safety check** - Verifies no conflict markers are present in staged files (additional safeguard)
7. **Commit** - Creates a commit if there are staged changes
8. **Push with retry** - Attempts to push with automatic retry on failure:
- On push failure, rebases again and retries
- Continues up to `max_retries` attempts
- Handles race conditions from concurrent workflows
Expand Down Expand Up @@ -297,6 +300,14 @@ This usually means concurrent changes occurred. The action automatically handles
max_retries: 10 # Increase for high-concurrency scenarios
```

### Conflict markers in committed files

The action now includes automatic conflict resolution and a safety check to prevent committing conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`). If you see the error "Conflict markers detected in staged files!", this means:
1. A merge conflict occurred that couldn't be auto-resolved
2. The action prevented committing corrupted files
3. Review the workflow logs to understand the conflict
4. This is a safety feature to protect your repository from corrupted files

### No commit created but expected changes

Check that:
Expand Down
89 changes: 85 additions & 4 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,69 @@ runs:

# Stash any uncommitted changes (including untracked files)
echo "📦 Stashing current changes..."
git stash push -u -m "temp-stashed-changes" || true
STASH_RESULT=0
git stash push -u -m "temp-stashed-changes" || STASH_RESULT=$?

if [ $STASH_RESULT -ne 0 ]; then
# Check if it's because there's nothing to stash (expected) or a real error
if git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
echo "ℹ️ Nothing to stash"
STASH_RESULT=0
else
echo "::error::Failed to stash changes"
exit 1
fi
fi

# Pull latest changes with rebase
echo "⬇️ Pulling latest changes with rebase..."
git pull --rebase origin "$(git branch --show-current)"

# Pop the stash to restore changes
# Pop the stash to restore changes (if we stashed anything)
echo "📂 Restoring stashed changes..."
git stash pop || true
if [ "$(git stash list | grep -c 'temp-stashed-changes')" -gt 0 ]; then
if ! git stash pop; then
echo "⚠️ Merge conflicts detected during stash pop"

# Get list of conflicted files
CONFLICTED_FILES=$(git diff --name-only --diff-filter=U)

if [ -n "$CONFLICTED_FILES" ]; then
echo "🔧 Resolving conflicts by accepting stashed changes..."

# For each conflicted file, accept the stashed version
# In stash pop context: --theirs = stashed changes (what we want)
RESOLUTION_FAILED=0
while IFS= read -r file; do
if [ -n "$file" ]; then
echo " - Resolving: $file"
if ! git checkout --theirs "$file"; then
echo "::error::Failed to resolve conflict in $file"
RESOLUTION_FAILED=1
break
fi
fi
done <<< "$CONFLICTED_FILES"

if [ $RESOLUTION_FAILED -ne 0 ]; then
# Drop the stash to clean up
git stash drop
exit 1
fi

# Drop the stash since we've successfully applied it
git stash drop

echo "✅ Conflicts resolved"
else
echo "ℹ️ No actual conflicts found, continuing..."
# Still need to drop the stash
git stash drop
fi
fi
else
echo "ℹ️ No stash to restore"
fi

# Stage files matching the pattern
echo "➕ Staging files matching pattern: $FILE_PATTERN"
Expand All @@ -106,6 +160,20 @@ runs:
exit 0
fi

# Safety check: ensure no conflict markers are being committed
echo "🔍 Checking for conflict markers in staged files..."
if git diff --cached | grep -qE "^\+.*<<<<<<<|^\+.*=======|^\+.*>>>>>>>"; then
echo "::error::Conflict markers detected in staged files! Aborting commit."
echo "The following files contain unresolved conflicts:"
STAGED_FILES=$(git diff --cached --name-only)
while IFS= read -r file; do
if [ -n "$file" ] && grep -qE "<<<<<<<|=======|>>>>>>>" "$file" 2>/dev/null; then
echo " - $file"
fi
done <<< "$STAGED_FILES"
exit 1
fi

# Create commit
echo "💾 Creating commit..."
git commit -m "$COMMIT_MESSAGE"
Expand All @@ -127,7 +195,20 @@ runs:

if [ $ATTEMPT -lt $MAX_RETRIES ]; then
echo "⚠️ Push failed, rebasing and retrying..."
git pull --rebase origin "$(git branch --show-current)"
# Use theirs strategy to prefer our committed changes over remote changes during rebase
# In rebase context: theirs = our local commit (what we want to keep)
if ! git pull --rebase -X theirs origin "$(git branch --show-current)"; then
echo "::error::Rebase failed during retry despite conflict resolution strategy."
echo "::error::This indicates a complex conflict that cannot be auto-resolved."

# Check if we're in a rebase state
if [ -d .git/rebase-merge ] || [ -d .git/rebase-apply ]; then
echo "Aborting rebase..."
git rebase --abort
fi

exit 1
fi
fi

ATTEMPT=$((ATTEMPT + 1))
Expand Down
Loading