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);
}