remote: make add_missing_tags() linear

The add_missing_tags() method currently has quadratic behavior. This is due to a linear number (based on number of tags T) of calls to in_merge_bases_many, which has linear performance (based on number of commits C in the repository). Replace this O(T * C) algorithm with an O(T + C) algorithm by using get_reachable_subset(). We ignore the return list and focus instead on the reachable_flag assigned to the commits we care about, because we need to interact with the tag ref and not just the commit object. Signed-off-by: Derrick Stolee <dstolee@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed Nov 2, 2018 at 06:14 UTC 85daa01f6b80cb6eb14c5f484a6da9f0b1247143
1 file changed +33 -1
remote.c
+33 -1
@@ -1205,9 +1205,36 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
1205 * sent to the other side.
1206 */
1207 if (sent_tips.nr) {
1208 + const int reachable_flag = 1;
1209 + struct commit_list *found_commits;
1210 + struct commit **src_commits;
1211 + int nr_src_commits = 0, alloc_src_commits = 16;
1212 + ALLOC_ARRAY(src_commits, alloc_src_commits);
1213 +
1214 for_each_string_list_item(item, &src_tag) {
1215 struct ref *ref = item->util;
1216 + struct commit *commit;
1217 +
1218 + if (is_null_oid(&ref->new_oid))
1219 + continue;
1220 + commit = lookup_commit_reference_gently(the_repository,
1221 + &ref->new_oid,
1222 + 1);
1223 + if (!commit)
1224 + /* not pushing a commit, which is not an error */
1225 + continue;
1226 +
1227 + ALLOC_GROW(src_commits, nr_src_commits + 1, alloc_src_commits);
1228 + src_commits[nr_src_commits++] = commit;
1229 + }
1230 +
1231 + found_commits = get_reachable_subset(sent_tips.tip, sent_tips.nr,
1232 + src_commits, nr_src_commits,
1233 + reachable_flag);
1234 +
1235 + for_each_string_list_item(item, &src_tag) {
1236 struct ref *dst_ref;
1237 + struct ref *ref = item->util;
1238 struct commit *commit;
1239
1240 if (is_null_oid(&ref->new_oid))
@@ -1223,7 +1250,7 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
1250 * Is this tag, which they do not have, reachable from
1251 * any of the commits we are sending?
1252 */
1226 - if (!in_merge_bases_many(commit, sent_tips.nr, sent_tips.tip))
1253 + if (!(commit->object.flags & reachable_flag))
1254 continue;
1255
1256 /* Add it in */
@@ -1231,7 +1258,12 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
1258 oidcpy(&dst_ref->new_oid, &ref->new_oid);
1259 dst_ref->peer_ref = copy_ref(ref);
1260 }
1261 +
1262 + clear_commit_marks_many(nr_src_commits, src_commits, reachable_flag);
1263 + free(src_commits);
1264 + free_commit_list(found_commits);
1265 }
1266 +
1267 string_list_clear(&src_tag, 0);
1268 free(sent_tips.tip);
1269 }