mirror of
https://github.com/git/git.git
synced 2024-11-24 10:26:17 +08:00
b3dfeebb92
The `rebase` family of Git commands avoid applying patches that were already integrated upstream. They do that by using the revision walking option that computes the patch IDs of the two sides of the rebase (local-only patches vs upstream-only ones) and skipping those local patches whose patch ID matches one of the upstream ones. In many cases, this causes unnecessary churn, as already the set of paths touched by a given commit would suffice to determine that an upstream patch has no local equivalent. This hurts performance in particular when there are a lot of upstream patches, and/or large ones. Therefore, let's introduce the concept of a "diff-header-only" patch ID, compare those first, and only evaluate the "full" patch ID lazily. Please note that in contrast to the "full" patch IDs, those "diff-header-only" patch IDs are prone to collide with one another, as adjacent commits frequently touch the very same files. Hence we now have to be careful to allow multiple hash entries with the same hash. We accomplish that by using the hashmap_add() function that does not even test for hash collisions. This also allows us to evaluate the full patch ID lazily, i.e. only when we found commits with matching diff-header-only patch IDs. We add a performance test that demonstrates ~1-6% improvement. In practice this will depend on various factors such as how many upstream changes and how big those changes are along with whether file system caches are cold or warm. As Git's test suite has no way of catching performance regressions, we also add a regression test that verifies that the full patch ID computation is skipped when the diff-header-only computation suffices. Signed-off-by: Kevin Willford <kcwillford@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
99 lines
2.8 KiB
C
99 lines
2.8 KiB
C
#include "cache.h"
|
|
#include "diff.h"
|
|
#include "commit.h"
|
|
#include "sha1-lookup.h"
|
|
#include "patch-ids.h"
|
|
|
|
int commit_patch_id(struct commit *commit, struct diff_options *options,
|
|
unsigned char *sha1, int diff_header_only)
|
|
{
|
|
if (commit->parents)
|
|
diff_tree_sha1(commit->parents->item->object.oid.hash,
|
|
commit->object.oid.hash, "", options);
|
|
else
|
|
diff_root_tree_sha1(commit->object.oid.hash, "", options);
|
|
diffcore_std(options);
|
|
return diff_flush_patch_id(options, sha1, diff_header_only);
|
|
}
|
|
|
|
/*
|
|
* When we cannot load the full patch-id for both commits for whatever
|
|
* reason, the function returns -1 (i.e. return error(...)). Despite
|
|
* the "cmp" in the name of this function, the caller only cares about
|
|
* the return value being zero (a and b are equivalent) or non-zero (a
|
|
* and b are different), and returning non-zero would keep both in the
|
|
* result, even if they actually were equivalent, in order to err on
|
|
* the side of safety. The actual value being negative does not have
|
|
* any significance; only that it is non-zero matters.
|
|
*/
|
|
static int patch_id_cmp(struct patch_id *a,
|
|
struct patch_id *b,
|
|
struct diff_options *opt)
|
|
{
|
|
if (is_null_sha1(a->patch_id) &&
|
|
commit_patch_id(a->commit, opt, a->patch_id, 0))
|
|
return error("Could not get patch ID for %s",
|
|
oid_to_hex(&a->commit->object.oid));
|
|
if (is_null_sha1(b->patch_id) &&
|
|
commit_patch_id(b->commit, opt, b->patch_id, 0))
|
|
return error("Could not get patch ID for %s",
|
|
oid_to_hex(&b->commit->object.oid));
|
|
return hashcmp(a->patch_id, b->patch_id);
|
|
}
|
|
|
|
int init_patch_ids(struct patch_ids *ids)
|
|
{
|
|
memset(ids, 0, sizeof(*ids));
|
|
diff_setup(&ids->diffopts);
|
|
DIFF_OPT_SET(&ids->diffopts, RECURSIVE);
|
|
diff_setup_done(&ids->diffopts);
|
|
hashmap_init(&ids->patches, (hashmap_cmp_fn)patch_id_cmp, 256);
|
|
return 0;
|
|
}
|
|
|
|
int free_patch_ids(struct patch_ids *ids)
|
|
{
|
|
hashmap_free(&ids->patches, 1);
|
|
return 0;
|
|
}
|
|
|
|
static int init_patch_id_entry(struct patch_id *patch,
|
|
struct commit *commit,
|
|
struct patch_ids *ids)
|
|
{
|
|
unsigned char header_only_patch_id[GIT_SHA1_RAWSZ];
|
|
|
|
patch->commit = commit;
|
|
if (commit_patch_id(commit, &ids->diffopts, header_only_patch_id, 1))
|
|
return -1;
|
|
|
|
hashmap_entry_init(patch, sha1hash(header_only_patch_id));
|
|
return 0;
|
|
}
|
|
|
|
struct patch_id *has_commit_patch_id(struct commit *commit,
|
|
struct patch_ids *ids)
|
|
{
|
|
struct patch_id patch;
|
|
|
|
memset(&patch, 0, sizeof(patch));
|
|
if (init_patch_id_entry(&patch, commit, ids))
|
|
return NULL;
|
|
|
|
return hashmap_get(&ids->patches, &patch, &ids->diffopts);
|
|
}
|
|
|
|
struct patch_id *add_commit_patch_id(struct commit *commit,
|
|
struct patch_ids *ids)
|
|
{
|
|
struct patch_id *key = xcalloc(1, sizeof(*key));
|
|
|
|
if (init_patch_id_entry(key, commit, ids)) {
|
|
free(key);
|
|
return NULL;
|
|
}
|
|
|
|
hashmap_add(&ids->patches, key);
|
|
return key;
|
|
}
|