private ImmutableList selectNewAndReplacedChangesFromMagicBranch()

in java/com/google/gerrit/server/git/receive/ReceiveCommits.java [2276:2537]


  private ImmutableList<CreateRequest> selectNewAndReplacedChangesFromMagicBranch(Task newProgress)
      throws IOException {
    try (TraceTimer traceTimer = newTimer("selectNewAndReplacedChangesFromMagicBranch")) {
      logger.atFine().log("Finding new and replaced changes");
      List<CreateRequest> newChanges = new ArrayList<>();

      GroupCollector groupCollector =
          GroupCollector.create(receivePackRefCache, psUtil, notesFactory, project.getNameKey());

      BranchCommitValidator validator =
          commitValidatorFactory.create(projectState, magicBranch.dest, user);

      try {
        RevCommit start = setUpWalkForSelectingChanges();
        if (start == null) {
          return ImmutableList.of();
        }

        LinkedHashMap<RevCommit, ChangeLookup> pending = new LinkedHashMap<>();
        Set<Change.Key> newChangeIds = new HashSet<>();
        int maxBatchChanges = receiveConfig.getEffectiveMaxBatchChangesLimit(user);
        int total = 0;
        int alreadyTracked = 0;
        boolean rejectImplicitMerges =
            start.getParentCount() == 1
                && projectCache
                    .get(project.getNameKey())
                    .orElseThrow(illegalState(project.getNameKey()))
                    .is(BooleanProjectConfig.REJECT_IMPLICIT_MERGES)
                // Don't worry about implicit merges when creating changes for
                // already-merged commits; they're already in history, so it's too
                // late.
                && !magicBranch.merged;
        Set<RevCommit> mergedParents;
        if (rejectImplicitMerges) {
          mergedParents = new HashSet<>();
        } else {
          mergedParents = null;
        }

        for (; ; ) {
          RevCommit c = receivePack.getRevWalk().next();
          if (c == null) {
            break;
          }
          total++;
          receivePack.getRevWalk().parseBody(c);
          String name = c.name();
          groupCollector.visit(c);
          Collection<PatchSet.Id> existingPatchSets =
              receivePackRefCache.patchSetIdsFromObjectId(c);

          if (rejectImplicitMerges) {
            Collections.addAll(mergedParents, c.getParents());
            mergedParents.remove(c);
          }

          boolean commitAlreadyTracked = !existingPatchSets.isEmpty();
          if (commitAlreadyTracked) {
            alreadyTracked++;
            // Corner cases where an existing commit might need a new group:
            // A) Existing commit has a null group; wasn't assigned during schema
            //    upgrade, or schema upgrade is performed on a running server.
            // B) Let A<-B<-C, then:
            //      1. Push A to refs/heads/master
            //      2. Push B to refs/for/master
            //      3. Force push A~ to refs/heads/master
            //      4. Push C to refs/for/master.
            //      B will be in existing so we aren't replacing the patch set. It
            //      used to have its own group, but now needs to to be changed to
            //      A's group.
            // C) Commit is a PatchSet of a pre-existing change uploaded with a
            //    different target branch.
            existingPatchSets.stream()
                .forEach(i -> updateGroups.add(new UpdateGroupsRequest(i, c)));
            if (!(newChangeForAllNotInTarget || magicBranch.base != null)) {
              continue;
            }
          }

          List<String> idList = ChangeUtil.getChangeIdsFromFooter(c, urlFormatter.get());
          if (!idList.isEmpty()) {
            pending.put(c, lookupByChangeKey(c, Change.key(idList.get(idList.size() - 1).trim())));
          } else {
            pending.put(c, lookupByCommit(c));
          }

          int n = pending.size() + newChanges.size();
          if (maxBatchChanges != 0 && n > maxBatchChanges) {
            logger.atFine().log("%d changes exceeds limit of %d", n, maxBatchChanges);
            reject(
                magicBranch.cmd,
                "the number of pushed changes in a batch exceeds the max limit " + maxBatchChanges);
            return ImmutableList.of();
          }

          if (commitAlreadyTracked) {
            boolean changeExistsOnDestBranch = false;
            for (ChangeData cd : pending.get(c).destChanges) {
              if (cd.change().getDest().equals(magicBranch.dest)) {
                changeExistsOnDestBranch = true;
                break;
              }
            }
            if (changeExistsOnDestBranch) {
              continue;
            }

            logger.atFine().log(
                "Creating new change for %s even though it is already tracked", name);
          }

          BranchCommitValidator.Result validationResult =
              validator.validateCommit(
                  repo,
                  receivePack.getRevWalk().getObjectReader(),
                  magicBranch.cmd,
                  c,
                  ImmutableListMultimap.copyOf(pushOptions),
                  magicBranch.merged,
                  rejectCommits,
                  null);
          messages.addAll(validationResult.messages());
          if (!validationResult.isValid()) {
            // Not a change the user can propose? Abort as early as possible.
            logger.atFine().log("Aborting early due to invalid commit");
            return ImmutableList.of();
          }

          // Don't allow merges to be uploaded in commit chain via all-not-in-target
          if (newChangeForAllNotInTarget && c.getParentCount() > 1) {
            reject(
                magicBranch.cmd,
                "Pushing merges in commit chains with 'all not in target' is not allowed,\n"
                    + "to override please set the base manually");
            logger.atFine().log("Rejecting merge commit %s with newChangeForAllNotInTarget", name);
            // TODO(dborowitz): Should we early return here?
          }

          if (idList.isEmpty()) {
            newChanges.add(new CreateRequest(c, magicBranch.dest.branch(), newProgress));
            continue;
          }
        }
        logger.atFine().log(
            "Finished initial RevWalk with %d commits total: %d already"
                + " tracked, %d new changes with no Change-Id, and %d deferred"
                + " lookups",
            total, alreadyTracked, newChanges.size(), pending.size());

        if (rejectImplicitMerges) {
          rejectImplicitMerges(mergedParents);
        }

        for (Iterator<ChangeLookup> itr = pending.values().iterator(); itr.hasNext(); ) {
          ChangeLookup p = itr.next();
          if (p.changeKey == null) {
            continue;
          }

          if (newChangeIds.contains(p.changeKey)) {
            logger.atFine().log("Multiple commits with Change-Id %s", p.changeKey);
            reject(magicBranch.cmd, SAME_CHANGE_ID_IN_MULTIPLE_CHANGES);
            return ImmutableList.of();
          }

          List<ChangeData> changes = p.destChanges;
          if (changes.size() > 1) {
            logger.atFine().log(
                "Multiple changes in branch %s with Change-Id %s: %s",
                magicBranch.dest,
                p.changeKey,
                changes.stream().map(cd -> cd.getId().toString()).collect(joining()));
            // WTF, multiple changes in this branch have the same key?
            // Since the commit is new, the user should recreate it with
            // a different Change-Id. In practice, we should never see
            // this error message as Change-Id should be unique per branch.
            //
            reject(magicBranch.cmd, p.changeKey.get() + " has duplicates");
            return ImmutableList.of();
          }

          if (changes.size() == 1) {
            // Schedule as a replacement to this one matching change.
            //

            ObjectId currentPs = changes.get(0).currentPatchSet().commitId();
            // If Commit is already current PatchSet of target Change.
            if (p.commit.equals(currentPs)) {
              if (pending.size() == 1) {
                // There are no commits left to check, all commits in pending were already
                // current PatchSet of the corresponding target changes.
                reject(magicBranch.cmd, "commit(s) already exists (as current patchset)");
              } else {
                // Commit is already current PatchSet.
                // Remove from pending and try next commit.
                itr.remove();
                continue;
              }
            }
            if (requestReplaceAndValidateComments(
                magicBranch.cmd, false, changes.get(0).change(), p.commit)) {
              continue;
            }
            return ImmutableList.of();
          }

          if (changes.isEmpty()) {
            if (!isValidChangeId(p.changeKey.get())) {
              reject(magicBranch.cmd, "invalid Change-Id");
              return ImmutableList.of();
            }

            // In case the change look up from the index failed,
            // double check against the existing refs
            if (foundInExistingPatchSets(receivePackRefCache.patchSetIdsFromObjectId(p.commit))) {
              if (pending.size() == 1) {
                reject(magicBranch.cmd, "commit(s) already exists (as current patchset)");
                return ImmutableList.of();
              }
              itr.remove();
              continue;
            }
            newChangeIds.add(p.changeKey);
          }
          newChanges.add(new CreateRequest(p.commit, magicBranch.dest.branch(), newProgress));
        }
        logger.atFine().log(
            "Finished deferred lookups with %d updates and %d new changes",
            replaceByChange.size(), newChanges.size());
      } catch (IOException e) {
        // Should never happen, the core receive process would have
        // identified the missing object earlier before we got control.
        throw new StorageException("Invalid pack upload; one or more objects weren't sent", e);
      }

      if (newChanges.isEmpty() && replaceByChange.isEmpty()) {
        reject(magicBranch.cmd, "no new changes");
        return ImmutableList.of();
      }
      if (!newChanges.isEmpty() && magicBranch.edit) {
        reject(magicBranch.cmd, "edit is not supported for new changes");
        return ImmutableList.copyOf(newChanges);
      }

      SortedSetMultimap<ObjectId, String> groups = groupCollector.getGroups();
      List<Integer> newIds = seq.nextChangeIds(newChanges.size());
      for (int i = 0; i < newChanges.size(); i++) {
        CreateRequest create = newChanges.get(i);
        create.setChangeId(newIds.get(i));
        create.groups = ImmutableList.copyOf(groups.get(create.commit));
      }
      for (ReplaceRequest replace : replaceByChange.values()) {
        replace.groups = ImmutableList.copyOf(groups.get(replace.newCommitId));
      }
      for (UpdateGroupsRequest update : updateGroups) {
        update.groups = ImmutableList.copyOf(groups.get(update.commit));
      }
      logger.atFine().log("Finished updating groups from GroupCollector");
      return ImmutableList.copyOf(newChanges);
    }
  }