int commit_interleaver::merge_targets()

in src/commit_interleaver.h [907:1120]


int commit_interleaver::merge_targets(MergeRequest &merge,
                                      sha1_ref &new_commit) {
  if (mark_independent_targets(merge))
    return 1;

  if (!merge.head_is_independent && !merge.is_octopus) {
    new_commit = merge.targets.front().mono;
    return 0;
  }

  // Function for adding parents.
  int new_srev = 0;
  int max_urev = 0;
  git_cache::parsed_metadata::string_ref max_cd;
  unsigned long long max_ct = 0;
  std::string first_subject;
  auto add_parent = [&](sha1_ref base, sha1_ref mono) {
    merge.new_parents.push_back(mono);
    int srev = 0;
    cache.compute_rev(mono, /*is_split=*/false, srev);
    merge.parent_revs.push_back(srev);
    update_revs(new_srev, max_urev, srev);

    if (!merge.is_octopus)
      return 0;

    // Extract the commit date.
    git_cache::parsed_metadata parsed;
    {
      const char *metadata;
      bool is_merge;
      sha1_ref first_parent;
      if (cache.compute_metadata(base ? base : mono, metadata, is_merge,
                                 first_parent))
        return error("failed to compute commit metadata for target '" +
                     mono->to_string() + "'");
      if (cache.parse_commit_metadata_impl(metadata, parsed))
        return error("failed to parse commit metadata for target '" +
                     mono->to_string() + "'");
    }
    if (head != mono && first_subject.empty())
      if (cache.extract_subject(first_subject, parsed.message))
        return error("failed to extract subject for target '" +
                     mono->to_string() + "'");

    // Choose the newest commit date.
    const char *current = parsed.cd.first;
    unsigned long long ct = 0;
    if (parse_num(current, ct) || *current != ' ')
      return error("failed to parse commit date timestamp for target '" +
                   mono->to_string() + "'");
    // fprintf(stderr, "   + ct = %llu\n", ct);
    if (ct > max_ct) {
      max_ct = ct;
      max_cd = parsed.cd;
    }
    return 0;
  };

  // Add the head as the initial parent.  If this is a new branch and there
  // were no commits to translate, then we may not have a head yet.  Also skip
  // it if somehow it's a descendent of one of the other commits.
  sha1_ref primary_parent;
  if (merge.head_is_independent) {
    primary_parent = head;
    if (add_parent(sha1_ref(), head))
      return 1;
  }

  // Add the target commits for each source where the target was not reached,
  // filling up the tree with items as appropriate.
  bool source_includes_root = false;
  dir_mask source_dirs;

  git_tree head_tree;
  auto ls_tree_head = [&]() {
    if (head_tree.sha1)
      return 0;
    assert(primary_parent);
    head_tree.sha1 = primary_parent;
    return cache.ls_tree(head_tree);
  };

  std::vector<const char *> source_dir_names;
  for (const MergeTarget &target : merge.targets) {
    const commit_source &source = *target.source;
    if (make_partial_tree(source, target.base, target.mono, merge.items,
                          source_dirs))
      return error("failed to add items to merge");

    dir_mask changed_dirs;
    auto add_target_as_parent = [&]() {
      if (add_parent(target.base, target.mono))
        return 1;
      if (source.is_repeat) {
        if (merge.head_is_independent) {
          // If we have a head, look up what's getting merged in.
          if (!changed_dirs.any())
            if (ls_tree_head() ||
                compare_trees(source, head_tree, target.mono, changed_dirs))
              return 1;
          for (int d = 0, de = dirs.list.size(); d != de; ++d)
            if (changed_dirs.test(d))
              source_dir_names.push_back(dirs.list[d].name);
        } else {
          // If this is an initial merge, where we don't even have a head yet,
          // assume everything is getting merged.
          source_dir_names.insert(source_dir_names.end(),
                                  repeated_dir_names.begin(),
                                  repeated_dir_names.end());
        }
      } else {
        source_dir_names.push_back(dirs.list[source.dir_index].name);
      }

      // Keep track of whether the monorepo root is included.
      source_includes_root |= source.has_root;
      return 0;
    };

    if (target.is_independent) {
      if (add_target_as_parent())
        return 1;
      if (!primary_parent)
        head = primary_parent = target.mono;
      continue;
    } else if (!merge.is_octopus) {
      // We don't need to analyze this too much.
      add_target_as_parent();
      continue;
    }

    // Look up the head tree if we don't have it yet.
    if (ls_tree_head())
      return 1;
    if (source.has_root || source.is_repeat) {
      if (compare_trees(source, head_tree,
                        source.is_repeat ? target.mono : target.base,
                        changed_dirs))
        return 1;
      if (changed_dirs.any())
        if (add_target_as_parent())
          return 1;
      continue;
    }

    // Just look up the one item in the head tree.
    sha1_ref base_tree;
    if (cache.compute_commit_tree(target.base, base_tree))
      return 1;
    assert(base_tree);
    bool found = false;
    for (int i = 0, ie = head_tree.num_items; i != ie; ++i) {
      auto &item = head_tree.items[i];
      if (item.sha1 != base_tree)
        continue;
      if (item.type != git_tree::item_type::tree)
        continue;
      if (strcmp(item.name, dirs.list[source.dir_index].name))
        continue;
      found = true;
      break;
    }
    if (!found)
      if (add_target_as_parent())
        return 1;
  }

  // Check whether we can skip the merge.
  assert(!merge.new_parents.empty());
  if (merge.new_parents.size() == 1) {
    new_commit = merge.new_parents.front();
    return 0;
  }

  // Add the other tree items.
  sha1_ref new_tree;
  if (finish_making_tree_outside_source(
          /*head_p=*/0, /*base_commit=*/sha1_ref(), source_dirs,
          source_includes_root, merge.new_parents, merge.parent_revs,
          merge.items, new_tree))
    return error("failed to make tree for targets merge");

  if (merge.is_octopus) {
    // Construct a commit message.
    git_cache::commit_tree_buffers &buffers = merge.buffers;
    buffers.message = "Merge ";
    cache.apply_dir_names_in_subject(buffers.message,
                                     dir_name_range(source_dir_names));
    if (merge.head_is_independent && merge.new_parents.size() == 2 &&
        !first_subject.empty()) {
      git_cache::commit_tree_buffers merged_buffers;
      buffers.message += ": ";
      buffers.message += first_subject;
    } else {
      buffers.message += '\n';
    }
    buffers.message += '\n';
    cache.apply_dir_name_trailers(buffers.message,
                                  dir_name_range(source_dir_names));
    cache.apply_metadata_env_names(buffers);
    cache.apply_merge_authorship(buffers, max_cd);
  } else {
    if (cache.parse_commit_metadata(
            merge.targets.front().base ? merge.targets.front().base
                                       : merge.targets.front().mono,
            merge.buffers, /*is_merge=*/true, dir_name_range(source_dir_names)))
      return 1;
  }

  return cache.commit_tree_impl(new_tree, merge.new_parents, new_commit,
                                merge.buffers) ||
         cache.set_base_rev(new_commit, new_srev);
}