public void logAnyDifferences()

in git-server/src/main/java/jetbrains/buildServer/buildTriggers/vcs/git/gitProxy/GitProxyChangesCollector.java [460:614]


  public void logAnyDifferences(@NotNull List<ModificationData> jgitData, @NotNull List<ModificationData> gitProxyData,
                                @NotNull RepositoryStateData fromState, @NotNull RepositoryStateData toState,
                                @NotNull VcsRoot root, @NotNull GitApiClient<GitRepoApi> gitRepoApi,
                                @NotNull Map<String, List<String>> submodulePrefixes) {
    long startTime = System.currentTimeMillis();
    if (jgitData.size() != gitProxyData.size()) {
      if (jgitData.size() == 0) {
        if (fromState.getBranchRevisions().size() == 1) {
          String defaultBranchRevision = fromState.getDefaultBranchRevision();
          if (defaultBranchRevision != null && !doesCommitStillExist(defaultBranchRevision, gitRepoApi)) {
            // in this case jgit returns empty result because there is no exisiting branch revisions in FromState
            return;
          }
        }
        logDiffLength(jgitData, gitProxyData, fromState, toState, root, " Jgit result is empty.");
        return;
      }
      Map<String, ModificationData> gitProxyDatMap = new HashMap<>(gitProxyData.size());
      Map<String, ModificationData> jgitDataMap = new HashMap<>(jgitData.size());
      Map<String, String> jgitReverseEdges = new HashMap<>(jgitData.size());
      for (ModificationData data: gitProxyData) {
        gitProxyDatMap.put(data.getVersion(), data);
      }

      Map<String, ExtraDataState> jgitExtraData = new HashMap<>();
      for (ModificationData data: jgitData) {
        jgitDataMap.put(data.getVersion(), data);
        if (!gitProxyDatMap.containsKey(data.getVersion())) {
          jgitExtraData.put(data.getVersion(), ExtraDataState.UNKNOWN);
        }
        for (String parentVersion : data.getParentRevisions()) {
          jgitReverseEdges.put(parentVersion, data.getVersion());
        }
      }

      for (ModificationData data: gitProxyData) {
        if (!jgitDataMap.containsKey(data.getVersion())) {
          logDiffLength(jgitData, gitProxyData, fromState, toState, root, " gitProxy result has extra commits.");
          return;
        }
      }

      for (String bottomModVersion: jgitExtraData.keySet()) {
        if (System.currentTimeMillis() - startTime > 10000) {
          throw new RuntimeException("Compare diff timeout");
        }
        ModificationData data = jgitDataMap.get(bottomModVersion);
        if (data.getParentRevisions().stream().anyMatch(parent -> jgitDataMap.containsKey(parent))) {
          // this is not the bottom modification in the returned result
          continue;
        }

        // try to find the top modification from which this modification is reachable
        String parentVersion = data.getVersion();
        List<ModificationData> branchModifications = new ArrayList<>();
        // this var is to avoid visiting the same commits twice
        boolean forcePushed = false;
        while (parentVersion != null) {
          if (System.currentTimeMillis() - startTime > 10000) {
            throw new RuntimeException("Compare diff timeout");
          }
          if (jgitExtraData.containsKey(parentVersion) && jgitExtraData.get(parentVersion) == ExtraDataState.FORCE_PUSHED) {
            forcePushed = true;
            break;
          }
          branchModifications.add(jgitDataMap.get(parentVersion));
          parentVersion = jgitReverseEdges.get(parentVersion);
        }

        if (!forcePushed) {
          ModificationData topModification = branchModifications.get(branchModifications.size() - 1);
          if (!gitProxyDatMap.containsKey(topModification.getVersion())) {
            // this is likely the case when the branch was rolled back, it is expected to have empty result from git proxy for the branch. There are 2 cases

            // check if from commit still exists, this is the case when the branch was rolled back and the changes were already collected for the old HEAD
            // if it doesn't, then the gitProxy returned correct empty result for the branch
            String correspondingFromRev = findCorrespondingFromRevision(topModification.getVersion(), fromState, toState, jgitDataMap,false);
            ModificationData bottomModData = jgitDataMap.get(bottomModVersion);
            if (correspondingFromRev != null && /*checking that it wasn't some old commit*/!bottomModData.getParentRevisions().contains(correspondingFromRev)
                && !doesCommitStillExist(correspondingFromRev, gitRepoApi)) {
              continue;
            }

            // check if top commit still exists, if it doesn't this means that the git proxy returned correct empty result. This is the case when the branch was rolled back
            // and the changes were not collected for the old HEAD. Otherwise, we should report the difference
            if (doesCommitStillExist(topModification.getVersion(), gitRepoApi)) {
              logDiffLength(jgitData, gitProxyData, fromState, toState, root,
                            String.format(" Version %s still exists, but gitProxy didn't return it.", topModification.getVersion()));
              return;
            }
          } else {
            // we need to find the corresponding from version for this branch to check if jgit returned more commits because of force push
            String correspondingFromRev = findCorrespondingFromRevision(topModification.getVersion(), fromState, toState, jgitDataMap, true);
            if (correspondingFromRev == null) {
              logDiffLength(jgitData, gitProxyData, fromState, toState, root,
                            String.format(" From state revision wasn't found for to state revision %s.", topModification.getVersion()));
              return;
            }

            if (doesCommitStillExist(correspondingFromRev, gitRepoApi)) {
              logDiffLength(jgitData, gitProxyData, fromState, toState, root, String.format(" From state revision %s still exists.", correspondingFromRev));
              return;
            }
          }
        }

        // mark commits as force pushed so we won't try to process them again
        branchModifications.forEach(mod -> {
          if (jgitExtraData.containsKey(mod.getVersion())) {
            jgitExtraData.put(mod.getVersion(), ExtraDataState.FORCE_PUSHED);
          }
        });
      }
      return;
    }

    if (TeamCityProperties.getBoolean(COMPARE_MODIFICATIONS_WITHOUT_ORDER_LOGGING_PROPERTY) && notEqualModificationListsById(jgitData, gitProxyData)) {
      jgitData = new ArrayList<>(jgitData);
      gitProxyData = new ArrayList<>(gitProxyData);
      Collections.sort(jgitData, (a, b) -> a.getVersion().compareTo(b.getVersion()));
      Collections.sort(gitProxyData, (a, b) -> a.getVersion().compareTo(b.getVersion()));
    }

    List<ModificationData> diff = new ArrayList<>();
    List<Integer> differentPostions = new ArrayList<>();
    for (int i = 0; i < jgitData.size(); i++) {
      ModDataComparisonResult cmpRes = compareModifications(jgitData.get(i), gitProxyData.get(i), submodulePrefixes.get(jgitData.get(i).getVersion()));
      if (cmpRes == ModDataComparisonResult.NOT_EQUAL_VCS_CHANGES) {
        diff.add(jgitData.get(i));
        diff.add(gitProxyData.get(i));
        differentPostions.add(i);
      }
      else if (cmpRes == ModDataComparisonResult.NOT_EQUAL_ATTRIBUTES) {
        diff.add(getDataWithoutFileChanges(jgitData.get(i), false, true, gitProxyData.get(i), false));
        diff.add(getDataWithoutFileChanges(gitProxyData.get(i), false, true, jgitData.get(i), true));
        differentPostions.add(i);
      }
      else if (cmpRes == ModDataComparisonResult.NOT_EQUAL_OTHER) {
        diff.add(getDataWithoutFileChanges(jgitData.get(i), false, false));
        diff.add(getDataWithoutFileChanges(gitProxyData.get(i), false, false));
        differentPostions.add(i);
      }

      if (diff.size() > TeamCityProperties.getInteger("teamcity.git.gitProxy.changesCollectionComparison.maxDiffSize", 100)) break; // too many different commits, it does not make sense to compute the rest
    }
    if (!differentPostions.isEmpty()) {
      LOG.info(
        "GitProxy difference was found for VCS root(" + LogUtil.describe(root) + "). Different ModificationData at positions " + myGson.toJson(differentPostions)
        + ". diff:{" + myGson.toJson(diff)
        + "}, "
        + getStateDiff(fromState, toState) +
        ", equal:{" + myGson.toJson(getCommitOnlyModificationDataList(jgitData)) + "}"
      );
    }
  }