Skip to main content

Resolve merge conflicts

Merge conflicts occur when the same infrastructure object is modified differently in both the source and destination branches. This guide shows you how to identify, review, and resolve conflicts in Infrahub.

Prerequisites

Before resolving merge conflicts, ensure you have:
  • An existing proposed change with detected conflicts
  • Appropriate permissions to resolve conflicts and merge changes
  • Understanding of the changes in both branches

Understand conflicts in Infrahub

Unlike traditional text-based version control, Infrahub detects conflicts at the data level—identifying specific attribute and relationship conflicts within objects.

Conflict types

Attribute conflicts occur when:
  • The same attribute is changed to different values in each branch
  • An attribute is modified in one branch and the object is deleted in another
Relationship conflicts occur when:
  • The same relationship points to different objects in each branch
  • A relationship is added/removed in one branch while modified in another
Node conflicts occur when:
  • An object is modified in one branch but deleted in another
  • An object is added in both branches with different data
See ConflictsEnricher at backend/infrahub/core/diff/conflicts_enricher.py:34 for implementation details.

Detect conflicts

Infrahub automatically detects conflicts when you create a proposed change or when the destination branch changes.

Check for conflicts

Query a proposed change to see if conflicts exist:
query {
  CoreProposedChange(id: "<proposed-change-id>") {
    checks {
      edges {
        node {
          name {
            value
          }
          kind {
            value
          }
          conclusion {
            value
          }
          message {
            value
          }
        }
      }
    }
  }
}
Look for checks with:
  • kind: "DataIntegrity"
  • conclusion: "failure"
  • Message indicating conflicts

Validate branch for conflicts

Use the BranchValidate mutation to check for conflicts:
mutation {
  BranchValidate(
    data: {
      name: "feature-network-redesign"
    }
    wait_until_completion: true
  ) {
    ok
  }
}
A successful validation (ok: true) indicates no conflicts. A failure indicates conflicts that must be resolved. See BranchValidate mutation at backend/infrahub/graphql/mutations/branch.py:228.

View conflict details

Retrieve detailed information about specific conflicts in the diff.

Query conflicts in diff

query {
  CoreProposedChange(id: "<proposed-change-id>") {
    data_diff {
      id
      name
      action
      conflict {
        uuid
        base_branch_action
        base_branch_value
        diff_branch_action
        diff_branch_value
        selected_branch
      }
      attributes {
        name
        action
        conflict {
          uuid
          base_branch_value
          diff_branch_value
          selected_branch
        }
      }
      relationships {
        name
        action
        conflict {
          uuid
          base_branch_action
          diff_branch_action
          selected_branch
        }
      }
    }
  }
}
Conflict fields:
  • uuid: Unique identifier for the conflict
  • base_branch_action: Action in the destination branch (ADDED, UPDATED, REMOVED)
  • base_branch_value: Value in the destination branch
  • diff_branch_action: Action in the source branch
  • diff_branch_value: Value in the source branch
  • selected_branch: Which branch’s value was chosen (if resolved)

Resolve conflicts

For each conflict, choose which branch’s value to keep. Conflicts can be resolved at the node, attribute, or relationship level.

Resolve an attribute conflict

Choose which branch’s value to keep for a specific attribute:
mutation {
  CoreConflictResolve(
    data: {
      conflict_id: "<conflict-uuid>"
      selected_branch: "source_branch"
    }
  ) {
    ok
  }
}
Parameters:
  • conflict_id: UUID of the conflict to resolve
  • selected_branch: Which branch to use (source_branch or destination_branch)

Resolve a node conflict

When an object is modified in one branch but deleted in another:
mutation {
  CoreConflictResolve(
    data: {
      conflict_id: "<conflict-uuid>"
      selected_branch: "destination_branch"  # Keep the deletion
    }
  ) {
    ok
  }
}
Choosing destination_branch keeps the deletion, while source_branch keeps the modifications.

Resolve a relationship conflict

mutation {
  CoreConflictResolve(
    data: {
      conflict_id: "<conflict-uuid>"
      selected_branch: "source_branch"  # Use source branch's relationship
    }
  ) {
    ok
  }
}

Rebase to update with main

Rebasing updates your branch with the latest changes from main, which can help identify conflicts early.

Rebase a branch

mutation {
  BranchRebase(
    data: {
      name: "feature-network-redesign"
    }
    wait_until_completion: true
  ) {
    ok
    object {
      name
      status
      branched_from
    }
  }
}
Parameters:
  • wait_until_completion (optional, default: true): Wait for rebase to complete
Rebasing:
  1. Updates branched_from to the current time
  2. Incorporates all changes from main since the original branch point
  3. Identifies conflicts with main
  4. Re-runs migrations if schema changes exist
If conflicts are detected during rebase, the operation will fail with an error message describing the conflicts. See BranchRebase mutation at backend/infrahub/graphql/mutations/branch.py:184 and rebase_branch task at backend/infrahub/core/branch/tasks.py:110.

Rebase requirements

Before rebasing, ensure:
  1. No unresolved conflicts exist in the diff
  2. The branch status is OPEN or NEED_REBASE
  3. You have reviewed the changes in main since branching
The rebase will fail if:
  • Conflicts exist between the branch and main
  • Schema migrations are required but fail validation
  • The branch status is NEED_UPGRADE_REBASE

Common conflict scenarios

Scenario 1: Conflicting attribute values

Situation:
  • Main branch: Device hostname changed to router-01-nyc
  • Feature branch: Same device hostname changed to router-core-nyc
Resolution:
mutation {
  CoreConflictResolve(
    data: {
      conflict_id: "<conflict-uuid>"
      selected_branch: "source_branch"  # Use feature branch value
    }
  ) {
    ok
  }
}

Scenario 2: Object modified vs. deleted

Situation:
  • Main branch: Device deleted
  • Feature branch: Device IP address updated
Resolution:
mutation {
  CoreConflictResolve(
    data: {
      conflict_id: "<conflict-uuid>"
      selected_branch: "destination_branch"  # Keep the deletion from main
    }
  ) {
    ok
  }
}

Scenario 3: Relationship conflicts

Situation:
  • Main branch: Interface connected to Switch-A
  • Feature branch: Same interface connected to Switch-B
Resolution:
mutation {
  CoreConflictResolve(
    data: {
      conflict_id: "<conflict-uuid>"
      selected_branch: "source_branch"  # Use feature branch relationship
    }
  ) {
    ok
  }
}

Verify conflict resolution

After resolving all conflicts, verify the proposed change is ready to merge.

Check resolution status

query {
  CoreProposedChange(id: "<proposed-change-id>") {
    data_diff {
      conflict {
        uuid
        selected_branch
      }
      attributes {
        conflict {
          uuid
          selected_branch
        }
      }
    }
    checks {
      edges {
        node {
          kind {
            value
          }
          conclusion {
            value
          }
        }
      }
    }
  }
}
Ensure:
  • All conflicts have a selected_branch value
  • Data integrity check shows conclusion: "success"

Re-run validation

Trigger validation checks after resolving conflicts:
mutation {
  CoreProposedChangeRunChecks(
    data: {
      id: "<proposed-change-id>"
    }
  ) {
    ok
  }
}
This re-runs all validation checks to confirm conflicts are fully resolved.

Best practices

Rebase before creating proposed changes

Update your branch with main before creating a proposed change:
  1. Rebase the branch to incorporate latest main changes
  2. Resolve any conflicts in your branch
  3. Create the proposed change with a clean diff
This approach identifies conflicts early and simplifies the review process.

Document conflict decisions

When resolving conflicts, add comments explaining your decision:
mutation {
  CoreProposedChangeAddComment(
    data: {
      proposed_change_id: "<proposed-change-id>"
      text: "Resolved hostname conflict by keeping feature branch value (router-core-nyc) as it follows the new naming convention."
    }
  ) {
    ok
  }
}

Coordinate with team members

Before resolving conflicts:
  1. Review the changes in both branches
  2. Understand the intent of each change
  3. Consult with the authors if unclear
  4. Document the resolution reasoning
This ensures conflict resolutions align with team goals and don’t inadvertently discard important changes.

Build docs developers (and LLMs) love