func MergeUserMarkAndBlockRange(leftUM []*model.UserMark, leftBR []*model.BlockRange,
	rightUM []*model.UserMark, rightBR []*model.BlockRange,
	conflictSolution map[string]MergeSolution) ([]*model.UserMark, []*model.BlockRange, IDChanges, error) {
	if conflictSolution == nil {
		conflictSolution = map[string]MergeSolution{}
	}
	left := joinToUserMarkBlockRange(leftUM, leftBR)
	right := joinToUserMarkBlockRange(rightUM, rightBR)
	var merged []*model.UserMarkBlockRange
	var changes IDChanges
	var err error
	for {
		merged, changes, err = mergeUMBR(left, right, conflictSolution)
		if err == nil {
			um, br := splitUserMarkBlockRange(merged)
			return um, br, changes, nil
		}
		switch err := err.(type) {
		case MergeConflictError:
			autoConflictSolution, sErr := solveEqualityMergeConflict(err.Conflicts)
			for key, autoSol := range autoConflictSolution {
				conflictSolution[key] = autoSol
			}
			if sErr == nil {
				continue
			}
			if reflect.DeepEqual(err.Conflicts, sErr.(MergeConflictError).Conflicts) {
				return nil, nil, IDChanges{}, sErr
			}
		default:
			return nil, nil, IDChanges{}, err
		}
	}
}
MergeUserMarkAndBlockRange joins UserMarks and BlockRanges from both sides and
tries to merge them. Afterwards it will update the IDs of UserMark and BlockRange
and returns them separately againg. If there is a collision, it will try to solve
it using duplicate detection and - if that fails - returns an error asking for
specification how it should handle it. MergeConflicts will be returned as a joined
UserMarkBlockRange struct to make it easier representing conflicts.
The returned IDChanges indicate if a UserMarkID has changed in the merge process.
package merger
import (
	"fmt"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
	"github.com/AndreasSko/go-jwlm/model"
)
type brFrom struct {
	side MergeSide
	br   *model.BlockRange
}
type umbrFrom struct {
	side MergeSide
	umbr []*model.UserMarkBlockRange
}