Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Documentation/RelNotes/2.51.0.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ Performance, Internal Implementation, Development Support etc.
* GIT_TEST_INSTALLED was not honored in the recent topic related to
SHA256 hashes, which has been corrected.

* The pop_most_recent_commit() function can have quite expensive
worst case performance characteristics, which has been optimized by
using prio-queue data structure.


Fixes since v2.50
-----------------
Expand Down Expand Up @@ -225,6 +229,11 @@ including security updates, are included in this release.
patches off.
(merge 1f0fed312a bc/contribution-under-non-real-names later to maint).

* "git commit" that concludes a conflicted merge failed to notice and remove
existing comment added automatically (like "# Conflicts:") when the
core.commentstring is set to 'auto'.
(merge 92b7c7c9f5 ac/auto-comment-char-fix later to maint).

* Other code cleanup, docfix, build fix, etc.
(merge b257adb571 lo/my-first-ow-doc-update later to maint).
(merge 8b34b6a220 ly/sequencer-update-squash-is-fixup-only later to maint).
Expand Down
6 changes: 5 additions & 1 deletion builtin/commit.c
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,10 @@ static void adjust_comment_line_char(const struct strbuf *sb)
char candidates[] = "#;@!$%^&|:";
char *candidate;
const char *p;
size_t cutoff;

/* Ignore comment chars in trailing comments (e.g., Conflicts:) */
cutoff = sb->len - ignored_log_message_bytes(sb->buf, sb->len);

if (!memchr(sb->buf, candidates[0], sb->len)) {
free(comment_line_str_to_free);
Expand All @@ -700,7 +704,7 @@ static void adjust_comment_line_char(const struct strbuf *sb)
candidate = strchr(candidates, *p);
if (candidate)
*candidate = ' ';
for (p = sb->buf; *p; p++) {
for (p = sb->buf; p + 1 < sb->buf + cutoff; p++) {
if ((p[0] == '\n' || p[0] == '\r') && p[1]) {
candidate = strchr(candidates, p[1]);
if (candidate)
Expand Down
14 changes: 11 additions & 3 deletions commit.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "parse.h"
#include "object-file.h"
#include "object-file-convert.h"
#include "prio-queue.h"

static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);

Expand Down Expand Up @@ -739,20 +740,27 @@ void commit_list_sort_by_date(struct commit_list **list)
commit_list_sort(list, commit_list_compare_by_date);
}

struct commit *pop_most_recent_commit(struct commit_list **list,
struct commit *pop_most_recent_commit(struct prio_queue *queue,
unsigned int mark)
{
struct commit *ret = pop_commit(list);
struct commit *ret = prio_queue_peek(queue);
int get_pending = 1;
struct commit_list *parents = ret->parents;

while (parents) {
struct commit *commit = parents->item;
if (!repo_parse_commit(the_repository, commit) && !(commit->object.flags & mark)) {
commit->object.flags |= mark;
commit_list_insert_by_date(commit, list);
if (get_pending)
prio_queue_replace(queue, commit);
else
prio_queue_put(queue, commit);
get_pending = 0;
}
parents = parents->next;
}
if (get_pending)
prio_queue_get(queue);
return ret;
}

Expand Down
8 changes: 4 additions & 4 deletions commit.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,10 @@ const char *repo_logmsg_reencode(struct repository *r,

const char *skip_blank_lines(const char *msg);

/** Removes the first commit from a list sorted by date, and adds all
* of its parents.
**/
struct commit *pop_most_recent_commit(struct commit_list **list,
struct prio_queue;

/* Removes the first commit from a prio_queue and adds its parents. */
struct commit *pop_most_recent_commit(struct prio_queue *queue,
unsigned int mark);

struct commit *pop_commit(struct commit_list **stack);
Expand Down
6 changes: 4 additions & 2 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -1534,9 +1534,11 @@ static int git_default_core_config(const char *var, const char *value,
!strcmp(var, "core.commentstring")) {
if (!value)
return config_error_nonbool(var);
else if (!strcasecmp(value, "auto"))
else if (!strcasecmp(value, "auto")) {
auto_comment_line_char = 1;
else if (value[0]) {
FREE_AND_NULL(comment_line_str_to_free);
comment_line_str = "#";
} else if (value[0]) {
if (strchr(value, '\n'))
return error(_("%s cannot contain newline"), var);
comment_line_str = value;
Expand Down
13 changes: 8 additions & 5 deletions fetch-pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "commit-graph.h"
#include "sigchain.h"
#include "mergesort.h"
#include "prio-queue.h"

static int transfer_unpack_limit = -1;
static int fetch_unpack_limit = -1;
Expand Down Expand Up @@ -601,15 +602,15 @@ static int find_common(struct fetch_negotiator *negotiator,
return count ? retval : 0;
}

static struct commit_list *complete;
static struct prio_queue complete = { compare_commits_by_commit_date };

static int mark_complete(const struct object_id *oid)
{
struct commit *commit = deref_without_lazy_fetch(oid, 1);

if (commit && !(commit->object.flags & COMPLETE)) {
commit->object.flags |= COMPLETE;
commit_list_insert(commit, &complete);
prio_queue_put(&complete, commit);
}
return 0;
}
Expand All @@ -626,9 +627,12 @@ static int mark_complete_oid(const char *refname UNUSED,
static void mark_recent_complete_commits(struct fetch_pack_args *args,
timestamp_t cutoff)
{
while (complete && cutoff <= complete->item->date) {
while (complete.nr) {
struct commit *item = prio_queue_peek(&complete);
if (item->date < cutoff)
break;
print_verbose(args, _("Marking %s as complete"),
oid_to_hex(&complete->item->object.oid));
oid_to_hex(&item->object.oid));
pop_most_recent_commit(&complete, COMPLETE);
}
}
Expand Down Expand Up @@ -798,7 +802,6 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
refs_for_each_rawref(get_main_ref_store(the_repository),
mark_complete_oid, NULL);
for_each_cached_alternate(NULL, mark_alternate_complete);
commit_list_sort_by_date(&complete);
if (cutoff)
mark_recent_complete_commits(args, cutoff);
}
Expand Down
10 changes: 5 additions & 5 deletions object-name.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "commit-reach.h"
#include "date.h"
#include "object-file-convert.h"
#include "prio-queue.h"

static int get_oid_oneline(struct repository *r, const char *, struct object_id *,
const struct commit_list *);
Expand Down Expand Up @@ -1461,7 +1462,7 @@ static int get_oid_oneline(struct repository *r,
const char *prefix, struct object_id *oid,
const struct commit_list *list)
{
struct commit_list *copy = NULL, **copy_tail = &copy;
struct prio_queue copy = { compare_commits_by_commit_date };
const struct commit_list *l;
int found = 0;
int negative = 0;
Expand All @@ -1483,9 +1484,9 @@ static int get_oid_oneline(struct repository *r,

for (l = list; l; l = l->next) {
l->item->object.flags |= ONELINE_SEEN;
copy_tail = &commit_list_insert(l->item, copy_tail)->next;
prio_queue_put(&copy, l->item);
}
while (copy) {
while (copy.nr) {
const char *p, *buf;
struct commit *commit;
int matches;
Expand All @@ -1507,7 +1508,7 @@ static int get_oid_oneline(struct repository *r,
regfree(&regex);
for (l = list; l; l = l->next)
clear_commit_marks(l->item, ONELINE_SEEN);
free_commit_list(copy);
clear_prio_queue(&copy);
return found ? 0 : -1;
}

Expand Down Expand Up @@ -2061,7 +2062,6 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo,
cb.list = &list;
refs_for_each_ref(get_main_ref_store(repo), handle_one_ref, &cb);
refs_head_ref(get_main_ref_store(repo), handle_one_ref, &cb);
commit_list_sort_by_date(&list);
ret = get_oid_oneline(repo, name + 2, oid, list);

free_commit_list(list);
Expand Down
45 changes: 32 additions & 13 deletions prio-queue.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,10 @@ void prio_queue_put(struct prio_queue *queue, void *thing)
}
}

void *prio_queue_get(struct prio_queue *queue)
static void sift_down_root(struct prio_queue *queue)
{
void *result;
size_t ix, child;

if (!queue->nr)
return NULL;
if (!queue->compare)
return queue->array[--queue->nr].data; /* LIFO */

result = queue->array[0].data;
if (!--queue->nr)
return result;

queue->array[0] = queue->array[queue->nr];

/* Push down the one at the root */
for (ix = 0; ix * 2 + 1 < queue->nr; ix = child) {
child = ix * 2 + 1; /* left */
Expand All @@ -86,6 +74,23 @@ void *prio_queue_get(struct prio_queue *queue)

swap(queue, child, ix);
}
}

void *prio_queue_get(struct prio_queue *queue)
{
void *result;

if (!queue->nr)
return NULL;
if (!queue->compare)
return queue->array[--queue->nr].data; /* LIFO */

result = queue->array[0].data;
if (!--queue->nr)
return result;

queue->array[0] = queue->array[queue->nr];
sift_down_root(queue);
return result;
}

Expand All @@ -97,3 +102,17 @@ void *prio_queue_peek(struct prio_queue *queue)
return queue->array[queue->nr - 1].data;
return queue->array[0].data;
}

void prio_queue_replace(struct prio_queue *queue, void *thing)
{
if (!queue->nr) {
prio_queue_put(queue, thing);
} else if (!queue->compare) {
queue->array[queue->nr - 1].ctr = queue->insertion_ctr++;
queue->array[queue->nr - 1].data = thing;
} else {
queue->array[0].ctr = queue->insertion_ctr++;
queue->array[0].data = thing;
sift_down_root(queue);
}
}
8 changes: 8 additions & 0 deletions prio-queue.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ void *prio_queue_get(struct prio_queue *);
*/
void *prio_queue_peek(struct prio_queue *);

/*
* Replace the "thing" that compares the smallest with a new "thing",
* like prio_queue_get()+prio_queue_put() would do, but in a more
* efficient way. Does the same as prio_queue_put() if the queue is
* empty.
*/
void prio_queue_replace(struct prio_queue *queue, void *thing);

void clear_prio_queue(struct prio_queue *);

/* Reverse the LIFO elements */
Expand Down
1 change: 1 addition & 0 deletions t/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,7 @@ benchmarks = [
'perf/p1450-fsck.sh',
'perf/p1451-fsck-skip-list.sh',
'perf/p1500-graph-walks.sh',
'perf/p1501-rev-parse-oneline.sh',
'perf/p2000-sparse-operations.sh',
'perf/p3400-rebase.sh',
'perf/p3404-rebase-interactive.sh',
Expand Down
71 changes: 71 additions & 0 deletions t/perf/p1501-rev-parse-oneline.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/bin/sh

test_description='Test :/ object name notation'

. ./perf-lib.sh

test_perf_fresh_repo

#
# Creates lots of merges to make history traversal costly. In
# particular it creates 2^($max_level-1)-1 2-way merges on top of
# 2^($max_level-1) root commits. E.g., the commit history looks like
# this for a $max_level of 3:
#
# _1_
# / \
# 2 3
# / \ / \
# 4 5 6 7
#
# The numbers are the fast-import marks, which also are the commit
# messages. 1 is the HEAD commit and a merge, 2 and 3 are also merges,
# 4-7 are the root commits.
#
build_history () {
local max_level="$1" &&
local level="${2:-1}" &&
local mark="${3:-1}" &&
if test $level -eq $max_level
then
echo "reset refs/heads/master" &&
echo "from $ZERO_OID" &&
echo "commit refs/heads/master" &&
echo "mark :$mark" &&
echo "committer C <c@example.com> 1234567890 +0000" &&
echo "data <<EOF" &&
echo "$mark" &&
echo "EOF"
else
local level1=$((level+1)) &&
local mark1=$((2*mark)) &&
local mark2=$((2*mark+1)) &&
build_history $max_level $level1 $mark1 &&
build_history $max_level $level1 $mark2 &&
echo "commit refs/heads/master" &&
echo "mark :$mark" &&
echo "committer C <c@example.com> 1234567890 +0000" &&
echo "data <<EOF" &&
echo "$mark" &&
echo "EOF" &&
echo "from :$mark1" &&
echo "merge :$mark2"
fi
}

test_expect_success 'setup' '
build_history 16 | git fast-import &&
git log --format="%H %s" --reverse >commits &&
sed -n -e "s/ .*$//p" -e "q" <commits >expect &&
sed -n -e "s/^.* //p" -e "q" <commits >needle
'

test_perf "rev-parse :/$(cat needle)" '
git rev-parse :/$(cat needle) >actual
'

test_expect_success 'verify result' '
test_cmp expect actual
'

test_done
13 changes: 13 additions & 0 deletions t/t3418-rebase-continue.sh
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,19 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas
test_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec
'

test_expect_success 'no change in comment character due to conflicts markers with core.commentChar=auto' '
git checkout -b branch-a &&
test_commit A F1 &&
git checkout -b branch-b HEAD^ &&
test_commit B F1 &&
test_must_fail git rebase branch-a &&
printf "B\nA\n" >F1 &&
git add F1 &&
GIT_EDITOR="cat >actual" git -c core.commentChar=auto rebase --continue &&
# Check that "#" is still the comment character.
test_grep "^# Changes to be committed" actual
'

test_orig_head_helper () {
test_when_finished 'git rebase --abort &&
git checkout topic &&
Expand Down
Loading