All skills
grasmash avatar

/drupal-contrib-mgmt

@eb9b3d3

Comprehensive guide for managing Drupal contributed modules via Composer, including updates, patches, version compatibility, and Drupal 11 upgrades. Use when updating modules or resolving dependency issues.

Use this Skill: https://skilld.dev/gh/grasmash/drupal-claude-skills/drupal-contrib-mgmt

This session only. Nothing lands on disk.

referencesdrupal-patches-workflow.md

≈8.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Drupal Patches: Complete Workflow Guide

Finding Patches in Issue Queues

Step 1: Navigate to Module Issue Queue

URL Pattern: https://www.drupal.org/project/issues/MODULE_NAME

Example: https://www.drupal.org/project/issues/audiofield

Step 2: Search for Your Issue

Filter Options:

  • Status: Open, Needs review, Reviewed & tested by the community (RTBC)
  • Category: Bug report, Task, Feature request, Support request
  • Version: Match your module version
  • Priority: Critical, Major, Normal, Minor

Search Tips:

  • Use specific error messages in search
  • Search for "Deprecated" or "Drupal 11" for compatibility issues
  • Look for "[META]" issues that track multiple related problems
  • Check "Needs tests" status - patches with tests are more reliable

Step 3: Evaluate the Issue

Good Signs: ✅ Status is "Reviewed & tested by the community" (RTBC) ✅ Automated tests are passing (green checkmark) ✅ Multiple people report it works ✅ Recent activity/comments ✅ Patch is against the version you're using ✅ Maintainer has reviewed/commented

Red Flags: ❌ Tests failing (red X) ❌ Old patch (1+ years) with no recent activity ❌ Comments saying "doesn't work" or "breaks X" ❌ Patch is for wrong version (e.g., 8.x patch for 9.x module) ❌ Multiple competing patches with no consensus

Step 4: Find the Patch File

Look for:

  • Green "Interdiff" and "File" links in comments
  • File attachments with .patch extension
  • Most recent patch at bottom of issue
  • Patch naming: module-brief-description-NODEID-COMMENT.patch

Example:

audiofield-file-validator-3432063-12.patch
  └─ module: audiofield
  └─ description: file-validator
  └─ node ID: 3432063
  └─ comment number: 12

Composer-Patches Plugin Workflow

Understanding the Plugin Commands

The cweagans/composer-patches plugin provides specific commands for managing patches:

composer patches-relock:

  • Regenerates patches.lock.json from composer.json definitions
  • Run after adding/removing/modifying patch definitions
  • Similar to how composer update --lock works for dependencies

composer patches-repatch:

  • Removes all patched dependencies and reinstalls them with current patches
  • WARNING: This deletes dependency directories - commit changes first!
  • Use after patches-relock to apply new patches

composer patches-doctor:

  • Diagnostic tool to identify configuration issues
  • Run this first when patches fail

Proper Workflow for Adding Patches

Step 1: Define patch in composer.json

{
  "extra": {
    "patches": {
      "drupal/module_name": {
        "Description of fix": "patches/module-fix.patch"
      }
    }
  }
}

Step 2: Regenerate patches lock file

composer patches-relock

Step 3: Apply patches

# WARNING: This removes and reinstalls dependencies!
# Commit or stash changes first
composer patches-repatch

Step 4: Verify and commit

# Test that patches applied correctly
drush upgrade_status:analyze module_name

# Commit all three files
git add composer.json composer.lock patches.lock.json patches/
git commit -m "Add patch for module_name"

Important Files

patches.lock.json:

  • Locks patch definitions like composer.lock locks versions
  • Generated by composer patches-relock
  • Must be committed to version control
  • When present, patches install from here (not composer.json)

Key Insight: Once patches.lock.json exists, it's the source of truth for patch application, not composer.json directly.

Applying Patches via Composer

Method 1: Remote Patch (from Drupal.org)

{
  "extra": {
    "patches": {
      "drupal/audiofield": {
        "Fix file_validate_extensions deprecation": "https://www.drupal.org/files/issues/2024-06-15/audiofield-file-validator-3432063-12.patch"
      }
    }
  }
}

Steps:

  1. Right-click patch link → Copy link address
  2. Add to composer.json patches section
  3. Run composer patches-relock, then composer reinstall drupal/audiofield (a plain composer install does not re-patch an already-installed package)

Method 2: Local Patch

{
  "extra": {
    "patches": {
      "drupal/audiofield": {
        "Custom fix for file validation": "patches/audiofield-custom-fix.patch"
      }
    }
  }
}

Directory Structure:

project-root/
├── patches/
│   ├── audiofield-custom-fix.patch
│   ├── entity_limit-user-roles-fix.patch
│   └── module-name-issue-description.patch
├── composer.json
└── docroot/

Method 3: Merge Request Diff

For GitLab merge requests:

{
  "extra": {
    "patches": {
      "drupal/social_auth_google": {
        "Icon fix": "https://git.drupalcode.org/project/social_auth_google/-/merge_requests/4/diffs.patch"
      }
    }
  }
}

Format: https://git.drupalcode.org/project/MODULE/-/merge_requests/NUMBER/diffs.patch

Creating Your Own Patches

When to Create a Patch

  1. No existing patch in issue queue
  2. Existing patch is outdated and doesn't apply
  3. Quick local fix while waiting for upstream
  4. Custom modification specific to your project

Method 1: Git Diff (Recommended)

# Navigate to contrib module (web/modules/contrib/audiofield on web/ layouts)
cd docroot/modules/contrib/audiofield

# Composer-installed contrib is not a git repo: take a temporary baseline
git init -q && git add -A && git commit -qm pristine

# Make your changes to the files
# Edit src/AudioFieldPluginBase.php, etc.

# Create patch (paths relative to the module root, as composer-patches expects)
git diff > ../../../../patches/audiofield-custom-fix.patch
git checkout -- . && rm -rf .git

If your project commits contrib code, git diff --relative run inside the module directory gives the same module-relative paths; a plain git diff docroot/modules/contrib/audiofield from the project root does NOT (its paths start with docroot/... and will not apply to the package).

Advantages:

  • Clean, standard format
  • Preserves file paths correctly
  • Works with composer-patches plugin

Creating Patches for Modules with Existing Patches

Three Scenarios:

  1. Independent patches (different files or non-conflicting sections)

    • Create patch against original source - patches apply in any order
    • No special handling needed
  2. Patches that need to stack (same file, but don't conflict)

    • Create new patch against patched state
    • Patches apply in order defined in composer.json
    • Line numbers in new patch account for previous patches
  3. Conflicting patches (overlapping changes)

    • Best practice: Create a combined patch that replaces the conflicting patches
    • Incorporates all changes from conflicting patches into one
    • Simpler, more maintainable, more reliable

This section covers scenario 3: Creating combined patches when conflicts exist.

Why Combined Patches?

  • ✅ Single source of truth for all changes
  • ✅ No dependency on patch application order
  • ✅ Easier to review and understand
  • ✅ Eliminates stacking complexity
  • ✅ More maintainable long-term

Solution: Create combined patch from the installed module directory.

# Step 1: Let composer install the module with all existing patches applied
composer install

# Step 2: Navigate to the installed contrib module (now has ALL patches applied)
cd docroot/modules/contrib/entity_limit

# Step 3: Initialize temporary git repo to track changes
git init
git add -A
git commit -m "After all existing patches"

# Step 4: Make your additional changes
# Edit src/Plugin/EntityLimit/UserLimit.php, etc.

# Step 5: Generate combined patch with --no-prefix flag
git diff --no-prefix > /path/to/patches/entity_limit-combined-fixes-d11.patch

# Step 6: Clean up temporary git repo
cd /path/to/project
rm -rf docroot/modules/contrib/entity_limit/.git

Step 7: Update composer.json

{
  "extra": {
    "patches": {
      "drupal/entity_limit": {
        // Remove or comment out the old conflicting patches:
        // "checkAccess() throws an exception": "https://...",
        // "Drupal calls should be avoided": "https://...",

        // Add your combined patch that includes both fixes plus new changes:
        "Combined D11 compatibility fixes": "patches/entity_limit-combined-fixes-d11.patch"
      }
    }
  }
}

Why combined patches are better:

  • Single patch incorporates all changes (old patches + your new changes)
  • Replaces multiple conflicting patches in composer.json
  • No dependency on patch application order
  • Easier to maintain and understand

Alternative Method (as described by user):

# Create temp directory
mkdir /tmp/patch-work
cd /tmp/patch-work

# Clone the module repo at the correct tag/version
git clone --branch 3.0.0-beta1 https://git.drupalcode.org/project/entity_limit.git
cd entity_limit

# Apply existing patches manually if needed
patch -p1 < /path/to/existing-patch-1.patch
patch -p1 < /path/to/existing-patch-2.patch

# Make your changes
git add -A
git commit -m "Apply fix"

# Generate patch
git format-patch -1 --no-prefix > /path/to/patches/entity_limit-new-fix.patch

# Clean up
cd /path/to/project
rm -rf /tmp/patch-work

Real-World Example - Stacking Approach:

entity_limit had two existing patches:

  1. https://www.drupal.org/files/.../entity_limit--use_getkey_in_access_check--3347700-5.patch
  2. https://www.drupal.org/files/.../3432063-2.patch

When adding a third patch to fix user_roles():

  • Patches modified different parts of the file (RoleLimit.php vs UserLimit.php)
  • No conflicts, but same file context
  • Used stacking approach: created patch after existing patches applied
  • Result: Three patches stack cleanly in composer.json

When to use combined patch instead: If existing patches had also modified UserLimit.php and conflicted with the user_roles() fix, the better approach would be:

  1. Create combined patch including all changes
  2. Replace all three patches with one combined patch in composer.json
  3. Simpler maintenance, no stacking complexity

Decision Guide:

Scenario Approach Reasoning
Patches in different files Independent patches No conflicts possible
Patches in same file, different sections Stack patches Works fine, no conflicts
Patches modify overlapping lines Combined patch Eliminates conflicts
Many small patches to same area Combined patch Easier maintenance
Mix of upstream + local patches Stack patches Keep upstream patches separate for easier updates

Common Mistakes to Avoid:

  • ❌ Trying to stack patches that actually conflict (use combined patch instead)
  • ❌ Creating too many small stacking patches (combine them!)
  • ❌ Manually adjusting line numbers in patch files
  • ❌ Creating combined patches when simple stacking would work fine

Pro tip: Combined patches are your friend when patches conflict. Don't try to make conflicting patches stack - merge them!

Method 2: Diff Command (Alternative)

# Create backup of original file
cp original.php original.php.bak

# Make changes to original.php

# Create patch
diff -Naur original.php.bak original.php > module-fix.patch

# For directories
diff -Naur original-module/ modified-module/ > module-fix.patch

Method 3: Export from Issue Queue

If you made changes and want to contribute back:

cd docroot/modules/contrib/module_name

# Ensure clean git state
git status

# Make your changes

# Create patch for issue queue
git diff > module-issue-brief-description-NODEID-XX.patch

Patch Naming Convention

Format: module-brief-description-NODEID-COMMENT.patch

Examples:

  • audiofield-file-validator-3432063-12.patch
  • entity_limit-user-roles-fix-3445678-2.patch
  • licensing-d11-compat-3456789-5.patch

Best Practices:

  • Use lowercase, hyphens (not underscores)
  • Keep description brief but descriptive
  • Include node ID if contributing to d.o issue
  • Increment comment number for revisions

Handling Dev Branches

When Fix is Committed but Not Released

Scenario: Issue is closed as "Fixed" but no new release yet.

Check the Status:

  1. Go to module's Drupal.org project page
  2. Click "Releases" tab
  3. Check "Development release" section
  4. Note the latest commit or branch

Option 1: Use Dev Version

# Switch to dev branch (e.g., 1.x-dev)
composer require drupal/module_name:1.x-dev --with-all-dependencies

composer.json:

{
  "require": {
    "drupal/module_name": "1.x-dev"
  }
}

Warning: Dev versions are unstable - use cautiously in production

Option 2: Use Specific Commit

{
  "require": {
    "drupal/module_name": "dev-1.x#abc123def456"
  }
}

Replace abc123def456 with actual commit hash from GitLab.

Option 3: Wait for Release

If it's close to release, consider waiting and using temporary patch.

Verifying Patches During Module Upgrades

CRITICAL: When Removing Patches After Upgrade

The Problem: When upgrading a module, patches may fail to apply. It's tempting to simply remove non-applying patches from composer.json, but this can cause you to lose important customizations.

The Rule: BEFORE removing any patch, you MUST verify one of three things:

  1. ✅ The patch was merged upstream - Changes are now in the module
  2. ✅ A new patch exists - Updated patch in the issue queue for the new version
  3. ✅ You can re-roll the patch - Create an updated patch for the new version

Never remove a patch without checking! If none of the above are true, you've just lost your customizations.

Step-by-Step Verification Process

Step 1: Identify which patches failed

When you upgrade and patches fail:

composer update drupal/module_name --with-all-dependencies

# Output shows:
# Cannot apply patch https://git.drupalcode.org/project/module/-/merge_requests/10.diff!
# Cannot apply patch patches/module-custom-fix.patch!

Step 2: For each failed patch, check its status

For Merge Request patches:

# Visit the MR URL in a browser
# Example: https://git.drupalcode.org/project/social_auth_apple/-/merge_requests/10

# Check:
# - Is it merged? (Look for "Merged" badge)
# - What issue does it address? (Check the description)
# - What changes does it make? (View the diff)

For Issue Queue patches:

# Visit the issue node
# Example: https://www.drupal.org/node/3432063

# Check:
# - Status: "Fixed" means merged, "Active" means not merged
# - Are there newer patches for your version?
# - Read recent comments for status updates

Step 3: Verify if changes are in the new version

Method 1: Check the actual code

# Read the file that the patch modified
cat docroot/modules/contrib/module/src/FileName.php | grep "specific_function_or_code"

# Download the patch to see what it changed
curl https://git.drupalcode.org/project/module/-/merge_requests/10.diff | head -50

# Compare: Does the current code include the patch's changes?

Method 2: Check PATCHES.txt

# Some modules document applied patches
cat docroot/modules/contrib/module/PATCHES.txt

# This may list patches that were committed

Method 3: Compare with upstream

# Initialize git in the module directory
cd docroot/modules/contrib/module
git init
git add -A
git commit -m "Current version"

# Download the patch and try to apply it
curl -O https://path/to/patch.patch
patch -p1 --dry-run < patch.patch

# If it says "already applied", the changes are in!
# If it fails, read the error to see why

Step 4: Take appropriate action

Based on your findings:

Case A: Patch was merged upstream ✅

{
  "patches": {
    "drupal/module": {
      // Remove this patch - it's now in the module
      // "Fix from MR !10": "https://git.drupalcode.org/project/module/-/merge_requests/10.diff"
    }
  }
}

No further action needed - your customization is preserved in the new version.

Case B: Patch not merged, but updated version exists ✅

{
  "patches": {
    "drupal/module": {
      // Update to new patch for new version
      "Fix from issue #123": "https://www.drupal.org/files/issues/2024-11-01/module-fix-123-15.patch"
    }
  }
}

Case C: Patch not merged, no update exists - MUST RE-ROLL ⚠️

# Step 1: Install the new version (without the patch temporarily)
composer update drupal/module --with-all-dependencies

# Step 2: Navigate to the module
cd docroot/modules/contrib/module

# Step 3: Initialize git repo
git init
git add -A
git commit -m "Clean install of version X.Y.Z"

# Step 4: Recreate the patch's changes manually
# - Review the old patch to understand what it did
# - Make the same logical changes in the new code
# - The code may have moved or been refactored

# Step 5: Generate new patch
git diff --no-prefix > /path/to/patches/module-fix-rerolled-for-XY.patch

# Step 6: Clean up
rm -rf .git
cd /path/to/project

# Step 7: Update composer.json
{
  "patches": {
    "drupal/module": {
      "Fix X (re-rolled for 2.x)": "patches/module-fix-rerolled-for-XY.patch"
    }
  }
}

# Step 8: Relock and apply the new patch, then test
composer patches-relock
composer reinstall drupal/module
drush cr
# Test functionality

Real-World Example: social_auth 3.x → 4.x Upgrade

Situation: Upgrading from social_auth 3.x to 4.x, two patches failed to apply.

Patch 1: social_auth_google Icon (MR !4)

# Check MR status
# URL: https://git.drupalcode.org/project/social_auth_google/-/merge_requests/4
# Status: Open (not merged)

# Check if change is in 4.x
cat docroot/modules/contrib/social_auth_google/img/google_logo.svg | head -3
# Output: <svg version="1.1" xmlns="http://www.w3.org/2000/svg"...

# Compare with patch
curl -s https://git.drupalcode.org/project/social_auth_google/-/merge_requests/4/diffs.patch | grep -A 2 "^+"
# Output shows same SVG code!

# Conclusion: ✅ Patch was merged upstream
# Action: Remove patch from composer.json - no re-roll needed

Patch 2: social_auth_apple Allow league settings alter (MR !10)

# Check MR status
# Status: Open (not merged)

# Read the new code
cat docroot/modules/contrib/social_auth_apple/src/Plugin/Network/AppleAuth.php

# The 4.x version has completely different architecture!
# Old: initSdk() was in module, patch added alter hook
# New: initSdk() is in parent class, module uses getExtraSdkSettings()

# Conclusion: ⚠️ Patch not merged, architecture changed - must re-roll

# Action: Re-implement the alter hook for new architecture

Re-rolling the Apple patch:

# Init git repo
cd docroot/modules/contrib/social_auth_apple
git init && git add -A && git commit -m "Initial 2.0.1"

# Override initSdk() method to add alter hook (adapted for new architecture)
# Edit src/Plugin/Network/AppleAuth.php
# Add:
#   protected function initSdk(): mixed {
#     // Copy parent logic, add alter hook before instantiation
#     $this->networkManager->getModuleHandler()->alter('social_auth_apple_settings', $league_settings, $this->settings);
#     return new $network['class_name']($league_settings);
#   }

# Generate patch
git diff --no-prefix > /path/to/patches/social_auth_apple-allow-league-settings-alter-2x.patch

# Clean up
rm -rf .git

# Update composer.json with new patch

Checklist for Patch Verification

Use this checklist when removing patches after an upgrade:

  • Identified all patches that failed to apply
  • For each patch, determined what it fixes/adds
  • Checked if MR/issue is merged upstream
  • Verified if changes exist in the new version's code
  • If not merged: Searched for updated patch in issue queue
  • If no update: Re-rolled the patch for new version
  • Tested that re-rolled patch applies cleanly
  • Verified functionality still works
  • Updated composer.json with new/removed patches
  • Documented changes in commit message

Common Mistakes to Avoid

❌ Mistake 1: Removing patches without checking if they were merged

# Wrong approach:
# "Patch doesn't apply anymore, just remove it"
# Result: Lost customization

✅ Correct: Check if the functionality is in the new version

# Read the patch to understand what it does
# Check the new code to see if those changes are present
# Only remove if confirmed upstream

❌ Mistake 2: Assuming failed patch means it's no longer needed

# Wrong assumption:
# "Module was upgraded, probably fixed now"
# Result: Feature broken, users affected

✅ Correct: Verify the specific functionality

# Test the feature the patch was enabling/fixing
# If still broken, re-roll the patch

❌ Mistake 3: Re-rolling patch without understanding architecture changes

# Wrong approach:
# "Just make the patch apply to the new file"
# Result: Patch applies but doesn't work

✅ Correct: Understand how the new version works

# Read both old and new code
# Understand what changed architecturally
# Adapt the patch logic to new architecture

Testing Patches

Before Applying

# Download patch
curl -O https://www.drupal.org/files/issues/2024-01-15/module-fix-1234567-8.patch

# Preview what will change
patch -p1 --dry-run < module-fix-1234567-8.patch

# Check if it applies cleanly
cd docroot/modules/contrib/module_name
git apply --check /path/to/patch.patch

After Applying

# Run database updates if needed (updatedb rebuilds caches when it finishes)
drush updb -y

# Check for errors
drush watchdog:show --severity=Error --count=20

# Test functionality
# Visit pages that use the module
# Perform actions affected by the patch

# Run module's tests if available
cd docroot
../vendor/bin/phpunit modules/contrib/module_name/tests/

Verify with Upgrade Status

# Re-scan module to confirm fix
drush upgrade_status:analyze module_name

# Should show issue as resolved

Common Patch Scenarios

Scenario 1: Patch Fails to Apply

Error: "patch ... failed at line X"

Solutions:

  1. Check module version:
composer show drupal/module_name
# Ensure patch matches your version
  1. Look for updated patch:

    • Go to issue node: drupal.org/node/NODEID
    • Read recent comments for newer patch
    • Update composer.json with new patch URL
  2. Rebase patch manually:

cd docroot/modules/contrib/module_name
# Contrib is not a git repo: take a temporary baseline first
git init -q && git add -A && git commit -qm pristine
# Apply what works
patch -p1 < /path/to/patch.patch
# Manually fix conflicts
# Create new patch
git diff > /path/to/patches/module-rebased.patch
rm -rf .git

Scenario 2: Multiple Patches for Same Module

composer.json:

{
  "extra": {
    "patches": {
      "drupal/entity_limit": {
        "Fix 1: Access check exception": "https://www.drupal.org/files/issues/2023-09-24/entity_limit-3347700-5.patch",
        "Fix 2: Drupal calls removed": "https://www.drupal.org/files/issues/2024-03-19/3432063-2.patch",
        "Fix 3: D11 info.yml": "patches/entity_limit-d11-info.patch"
      }
    }
  }
}

Order Matters: Patches apply in order listed. Ensure they don't conflict.

Scenario 3: Patch Already Applied

Error: "Skipping patch ... (already applied)"

Cause: Module maintainer merged the patch

Solution: Remove patch from composer.json

# Edit composer.json - remove patch entry, then relock and reinstall
composer patches-relock
composer reinstall drupal/module_name

Scenario 4: Understanding Patched vs Unpatched State

CRITICAL: Before creating or applying patches, understand the current state of files on disk.

Check if files are already patched:

# List patches applied to a module
composer show drupal/module_name

# Check the PATCHES.txt file (if it exists)
cat docroot/modules/contrib/module_name/PATCHES.txt

# Review composer.json to see what should be applied
grep -A 5 "drupal/module_name" composer.json

Verify actual file state:

# Read the actual code
cat docroot/modules/contrib/module_name/src/SomeFile.php | grep -A 5 "deprecated_function"

# Compare with original from drupal.org
curl -s https://ftp.drupal.org/files/projects/module_name-VERSION.tar.gz | tar xzO module_name/src/SomeFile.php | grep -A 5 "deprecated_function"

When adding a new patch to a module with existing patches:

  1. Apply patches incrementally to understand dependencies:
# Temporarily remove all but the first patch from composer.json
# Run: composer patches-relock && composer reinstall drupal/module_name
# Check what changed
# Add second patch, relock + reinstall, check again
# Continue until you find the conflict
  1. Check if existing patches already fix your issue:
# Download and read existing patch
curl https://www.drupal.org/files/issues/YYYY-MM-DD/module-fix-NODEID-X.patch

# Look for your function name
grep "user_roles\|system_retrieve_file\|_drupal_flush" downloaded.patch
  1. If conflict exists, create combined patch:
# Ensure module is in clean patched state (existing patches applied)
composer install

cd docroot/modules/contrib/module_name   # or web/modules/contrib/module_name

# Contrib is not a git repo: take a temporary baseline of the patched state
git init -q && git add -A && git commit -qm "After existing patches"

# Make your additional changes
# Edit files as needed

# Create combined patch that includes your changes ON TOP of existing patches
git diff > ../../../../patches/module-combined-fixes.patch
rm -rf .git

# Update composer.json: remove conflicting individual patches, add combined one

Scenario 5: Debugging Patch Application Failures

Systematic approach:

# Step 1: Check module version matches patch
composer show drupal/module_name | grep versions

# Step 2: Try applying patch manually to see exact error
cd docroot/modules/contrib/module_name
curl -O https://www.drupal.org/files/issues/.../patch.patch
patch -p1 --dry-run < patch.patch
# Read the error carefully - which file? which line?

# Step 3: Inspect the file that's failing
cat src/FailingFile.php | head -100
# Is this file already modified by another patch?

# Step 4: Check patch order in composer.json
# Patches apply in the order listed
# Earlier patches may modify context for later patches

# Step 5: Apply patches one by one
# Remove all patches from composer.json except first
# composer patches-relock && composer reinstall drupal/module_name
# Add second patch, relock + reinstall again
# Continue until failure occurs
# Now you know which two patches conflict

Resolution strategies:

  1. Patches complement each other (modify different files):

    • Keep both patches, order doesn't matter
  2. Patches modify same file, different sections:

    • Try reversing order in composer.json
    • If still fails, create combined patch
  3. Patches modify overlapping code:

    • Must create combined patch
    • Apply first patch, then manually apply second patch changes, create new patch from result

Scenario 6: Creating Patch for Deprecation

Example: Replace user_roles() in licensing module

cd docroot/modules/contrib/licensing   # or web/modules/contrib/licensing

# Contrib is not a git repo: take a temporary baseline first
git init -q && git add -A && git commit -qm pristine

# Edit src/Form/LicenseTypeForm.php
# Replace user_roles() with Role::loadMultiple() pattern

# Create patch
git diff > ../../../../patches/licensing-user-roles-d11-fix.patch
rm -rf .git

# Verify patch format
cat ../../../../patches/licensing-user-roles-d11-fix.patch

Add to composer.json:

{
  "extra": {
    "patches": {
      "drupal/licensing": {
        "Replace deprecated user_roles() for D11": "patches/licensing-user-roles-d11-fix.patch",
        "Drupal 11 .info.yml support": "patches/licensing-d11-info.patch"
      }
    }
  }
}

Lessons Learned: Real-World Patch Conflicts

Case Study: entity_limit user_roles() Fix

Initial Situation:

  • Module had 3 existing patches applied
  • Needed to add fix for user_roles() deprecation
  • New patch failed to apply: "Cannot apply patch!"

Root Cause:

  • Existing patch (#3432063-2) had already modified RoleLimit.php
  • New patch tried to modify same lines
  • Patch context didn't match because file was already in patched state

Wrong Approach ❌:

  • Edit files directly without understanding existing patches
  • Try to create patch from scratch against original module

Right Approach ✅:

  1. Check which files existing patches modify:

    curl https://www.drupal.org/files/issues/2024-03-19/3432063-2.patch | grep "^diff"
    curl https://www.drupal.org/files/issues/2024-03-19/3432063-2.patch | grep "user_roles"
  2. Discovered existing patch already fixed RoleLimit.php!

  3. Only needed to fix UserLimit.php (not touched by existing patches)

  4. Edit UserLimit.php directly after ensuring composer patches are applied

  5. No new patch needed - direct file edit works because it doesn't conflict

Key Takeaway: Always read existing patches before creating new ones. They may already include your fix.

Case Study: When to Create Combined Patches

Scenario: Need to fix 3 issues in same module:

  • Issue A: Fixed by remote patch (patch-A.patch)
  • Issue B: Fixed by remote patch (patch-B.patch)
  • Issue C: No patch exists, need custom fix

If patch-A and patch-B modify same file:

Option 1: Try applying sequentially

{
  "patches": {
    "drupal/module": {
      "Fix A": "https://drupal.org/files/patch-A.patch",
      "Fix B": "https://drupal.org/files/patch-B.patch",
      "Fix C": "patches/custom-fix-C.patch"
    }
  }
}

If this fails:

Option 2: Create combined local patch

# Apply patch A
composer require drupal/module
# Manually apply patch B changes
# Add your fix C changes
# Create combined patch
git diff > patches/module-combined-A-B-C.patch
{
  "patches": {
    "drupal/module": {
      "Combined fixes for A, B, and C": "patches/module-combined-A-B-C.patch"
    }
  }
}

Document in patch what it includes:

Combined patch for drupal/module includes:
- Fix A from drupal.org/node/XXXXX (patch-A.patch)
- Fix B from drupal.org/node/YYYYY (patch-B.patch)
- Custom fix C for issue described here

Patch Management Best Practices

Organization

patches/
├── contrib/           # Patches for contrib modules
│   ├── audiofield-file-validator-fix.patch
│   └── entity_limit-user-roles-fix.patch
├── core/             # Patches for Drupal core
│   └── core-fix-something-123456-7.patch
└── custom/           # Patches for custom code (rare)

Alternative: Keep all in patches/ with descriptive names

Documentation

Add comments in composer.json:

{
  "extra": {
    "patches": {
      "drupal/audiofield": {
        "Fix file_validate_extensions deprecation (D11) - See drupal.org/node/3432063": "patches/audiofield-file-validator-3432063-12.patch"
      }
    }
  }
}

Version Control

Always commit:

  • composer.json changes
  • Patch files in patches/ directory
  • composer.lock after applying

Ignore:

  • Modified contrib module files (patches handle changes)
  • Temporary patch files

.gitignore:

docroot/modules/contrib/*/
!patches/

Updating Patches

When module updates, patches may need updating:

# Update module
composer require drupal/module_name:^2.0 --with-all-dependencies

# If patch fails:
# 1. Check if fix is in new version (remove patch)
# 2. Find updated patch in issue queue
# 3. Rebase patch manually if needed

# Test after reapplying (updatedb rebuilds caches when it finishes)
drush updb -y

Contributing Patches Back

Create Issue-Ready Patch

cd docroot/modules/contrib/module_name

# Create patch with proper format
git diff > /tmp/module-issue-brief-description-NODEID-XX.patch

# Test patch applies cleanly
git apply --reverse /tmp/module-issue-brief-description-NODEID-XX.patch
git apply /tmp/module-issue-brief-description-NODEID-XX.patch

Upload to Issue Queue

  1. Comment on issue: Explain your changes
  2. Upload patch: Use "File" button
  3. Set status: Usually "Needs review"
  4. Provide test results: Describe testing performed
  5. Tag appropriately: Add version tags

Interdiff for Revisions

When updating someone else's patch:

# Download previous patch
curl -O https://www.drupal.org/files/issues/2024-01-15/module-fix-NODEID-10.patch

# Create your new patch
git diff > module-fix-NODEID-12.patch

# Create interdiff showing changes between patches
interdiff module-fix-NODEID-10.patch module-fix-NODEID-12.patch > NODEID-10-12-interdiff.txt

# Upload both: new patch AND interdiff

Quick Reference

Essential Commands

# Apply patch manually
patch -p1 < patch-file.patch

# Reverse patch
patch -p1 -R < patch-file.patch

# Create patch from git
git diff > patch-file.patch

# Test if patch applies
git apply --check patch-file.patch

# View patch contents
cat patch-file.patch

# Apply with composer (after editing extra.patches)
composer patches-relock
composer reinstall drupal/module_name

Common Patch Locations

  • Issue queue: drupal.org/project/issues/MODULE_NAME
  • Module releases: drupal.org/project/MODULE_NAME/releases
  • Git commits: git.drupalcode.org/project/MODULE_NAME
  • Merge requests: git.drupalcode.org/project/MODULE_NAME/-/merge_requests

Troubleshooting Quick Fixes

Problem Solution
Patch won't apply Check module version, find updated patch
Patch already applied Remove from composer.json
Wrong path in patch Edit patch file or use -pX flag
Conflicts after update Rebase patch or check if fix is included
Tests failing May not be patch issue - check logs

Resources

Source: SKILL.md on GitHub

3 warnings16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides a comprehensive and secure workflow for managing Drupal contributed modules, emphasizing the use of official community sources, standard tools like Composer and Drush, and best practices for patch management and version upgrades.

  • Socket16d

    1 alert: gptAnomaly

  • Snyk16d

    Risk: MEDIUM · 1 issue

  • Runlayer6mo

    5/8 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at eb9b3d3. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 4 months ago

README badge

README badge for grasmash/drupal-claude-skills/drupal-contrib-mgmt