private void checkIfValidForMerge(String[] backupIds, BackupSystemTable table)
    throws IOException {
    String backupRoot = null;

    final Set<TableName> allTables = new HashSet<>();
    final Set<String> allBackups = new HashSet<>();
    long minTime = Long.MAX_VALUE, maxTime = Long.MIN_VALUE;
    for (String backupId : backupIds) {
      BackupInfo bInfo = table.readBackupInfo(backupId);
      if (bInfo == null) {
        String msg = "Backup session " + backupId + " not found";
        throw new IOException(msg);
      }
      if (backupRoot == null) {
        backupRoot = bInfo.getBackupRootDir();
      } else if (!bInfo.getBackupRootDir().equals(backupRoot)) {
        throw new IOException("Found different backup destinations in a list of a backup sessions "
          + "\n1. " + backupRoot + "\n" + "2. " + bInfo.getBackupRootDir());
      }
      if (bInfo.getType() == BackupType.FULL) {
        throw new IOException("FULL backup image can not be merged for: \n" + bInfo);
      }

      if (bInfo.getState() != BackupState.COMPLETE) {
        throw new IOException("Backup image " + backupId
          + " can not be merged becuase of its state: " + bInfo.getState());
      }
      allBackups.add(backupId);
      allTables.addAll(bInfo.getTableNames());
      long time = bInfo.getStartTs();
      if (time < minTime) {
        minTime = time;
      }
      if (time > maxTime) {
        maxTime = time;
      }
    }

    final long startRangeTime = minTime;
    final long endRangeTime = maxTime;
    final String backupDest = backupRoot;
    // Check we have no 'holes' in backup id list
    // Filter 1 : backupRoot
    // Filter 2 : time range filter
    // Filter 3 : table filter
    BackupInfo.Filter destinationFilter = info -> info.getBackupRootDir().equals(backupDest);

    BackupInfo.Filter timeRangeFilter = info -> {
      long time = info.getStartTs();
      return time >= startRangeTime && time <= endRangeTime;
    };

    BackupInfo.Filter tableFilter = info -> {
      List<TableName> tables = info.getTableNames();
      return !Collections.disjoint(allTables, tables);
    };

    BackupInfo.Filter typeFilter = info -> info.getType() == BackupType.INCREMENTAL;
    BackupInfo.Filter stateFilter = info -> info.getState() == BackupState.COMPLETE;

    List<BackupInfo> allInfos = table.getBackupHistory(-1, destinationFilter, timeRangeFilter,
      tableFilter, typeFilter, stateFilter);
    if (allInfos.size() != allBackups.size()) {
      // Yes we have at least one hole in backup image sequence
      List<String> missingIds = new ArrayList<>();
      for (BackupInfo info : allInfos) {
        if (allBackups.contains(info.getBackupId())) {
          continue;
        }
        missingIds.add(info.getBackupId());
      }
      String errMsg =
        "Sequence of backup ids has 'holes'. The following backup images must be added:"
          + org.apache.hadoop.util.StringUtils.join(",", missingIds);
      throw new IOException(errMsg);
    }
  }
Verifies that backup images are valid for merge.
<ul>
<li>All backups MUST be in the same destination
<li>No FULL backups are allowed - only INCREMENTAL
<li>All backups must be in COMPLETE state
<li>No holes in backup list are allowed
</ul>
<p>
@param backupIds list of backup ids
@param table     backup system table
@throws IOException if the backup image is not valid for merge
hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/impl/BackupAdminImpl.java