1 #define USE_THE_REPOSITORY_VARIABLE
3 #include "git-compat-util.h"
6 #include "../fetch-negotiator.h"
8 #include "../prio-queue.h"
10 #include "../repository.h"
13 /* Remember to update object flag allocation in object.h */
15 * Both us and the server know that both parties have this object.
17 #define COMMON (1U << 2)
19 * The server has told us that it has this object. We still need to tell the
20 * server that we have this object (or one of its descendants), but since we are
21 * going to do that, we do not need to tell the server about its ancestors.
23 #define ADVERTISED (1U << 3)
25 * This commit has entered the priority queue.
27 #define SEEN (1U << 4)
29 * This commit has left the priority queue.
31 #define POPPED (1U << 5)
36 * An entry in the priority queue.
39 struct commit
*commit
;
42 * Used only if commit is not COMMON.
44 uint16_t original_ttl
;
49 struct prio_queue rev_list
;
52 * The number of non-COMMON commits in rev_list.
57 static int compare(const void *a_
, const void *b_
, void *data UNUSED
)
59 const struct entry
*a
= a_
;
60 const struct entry
*b
= b_
;
61 return compare_commits_by_commit_date(a
->commit
, b
->commit
, NULL
);
64 static struct entry
*rev_list_push(struct data
*data
, struct commit
*commit
, int mark
)
67 commit
->object
.flags
|= mark
| SEEN
;
69 CALLOC_ARRAY(entry
, 1);
70 entry
->commit
= commit
;
71 prio_queue_put(&data
->rev_list
, entry
);
74 data
->non_common_revs
++;
78 static int clear_marks(const char *refname
, const char *referent UNUSED
, const struct object_id
*oid
,
82 struct object
*o
= deref_tag(the_repository
, parse_object(the_repository
, oid
), refname
, 0);
84 if (o
&& o
->type
== OBJ_COMMIT
)
85 clear_commit_marks((struct commit
*)o
,
86 COMMON
| ADVERTISED
| SEEN
| POPPED
);
91 * Mark this SEEN commit and all its parsed SEEN ancestors as COMMON.
93 static void mark_common(struct data
*data
, struct commit
*seen_commit
)
95 struct prio_queue queue
= { NULL
};
98 if (seen_commit
->object
.flags
& COMMON
)
101 prio_queue_put(&queue
, seen_commit
);
102 seen_commit
->object
.flags
|= COMMON
;
103 while ((c
= prio_queue_get(&queue
))) {
104 struct commit_list
*p
;
106 if (!(c
->object
.flags
& POPPED
))
107 data
->non_common_revs
--;
109 if (!c
->object
.parsed
)
111 for (p
= c
->parents
; p
; p
= p
->next
) {
112 if (!(p
->item
->object
.flags
& SEEN
) ||
113 (p
->item
->object
.flags
& COMMON
))
116 p
->item
->object
.flags
|= COMMON
;
117 prio_queue_put(&queue
, p
->item
);
121 clear_prio_queue(&queue
);
125 * Ensure that the priority queue has an entry for to_push, and ensure that the
126 * entry has the correct flags and ttl.
128 * This function returns 1 if an entry was found or created, and 0 otherwise
129 * (because the entry for this commit had already been popped).
131 static int push_parent(struct data
*data
, struct entry
*entry
,
132 struct commit
*to_push
)
134 struct entry
*parent_entry
;
136 if (to_push
->object
.flags
& SEEN
) {
138 if (to_push
->object
.flags
& POPPED
)
140 * The entry for this commit has already been popped,
141 * due to clock skew. Pretend that this parent does not
146 * Find the existing entry and use it.
148 for (i
= 0; i
< data
->rev_list
.nr
; i
++) {
149 parent_entry
= data
->rev_list
.array
[i
].data
;
150 if (parent_entry
->commit
== to_push
)
153 BUG("missing parent in priority queue");
157 parent_entry
= rev_list_push(data
, to_push
, 0);
160 if (entry
->commit
->object
.flags
& (COMMON
| ADVERTISED
)) {
161 mark_common(data
, to_push
);
163 uint16_t new_original_ttl
= entry
->ttl
164 ? entry
->original_ttl
: entry
->original_ttl
* 3 / 2 + 1;
165 uint16_t new_ttl
= entry
->ttl
166 ? entry
->ttl
- 1 : new_original_ttl
;
167 if (parent_entry
->original_ttl
< new_original_ttl
) {
168 parent_entry
->original_ttl
= new_original_ttl
;
169 parent_entry
->ttl
= new_ttl
;
176 static const struct object_id
*get_rev(struct data
*data
)
178 struct commit
*to_send
= NULL
;
180 while (to_send
== NULL
) {
182 struct commit
*commit
;
183 struct commit_list
*p
;
184 int parent_pushed
= 0;
186 if (data
->rev_list
.nr
== 0 || data
->non_common_revs
== 0)
189 entry
= prio_queue_get(&data
->rev_list
);
190 commit
= entry
->commit
;
191 commit
->object
.flags
|= POPPED
;
192 if (!(commit
->object
.flags
& COMMON
))
193 data
->non_common_revs
--;
195 if (!(commit
->object
.flags
& COMMON
) && !entry
->ttl
)
198 repo_parse_commit(the_repository
, commit
);
199 for (p
= commit
->parents
; p
; p
= p
->next
)
200 parent_pushed
|= push_parent(data
, entry
, p
->item
);
202 if (!(commit
->object
.flags
& COMMON
) && !parent_pushed
)
204 * This commit has no parents, or all of its parents
205 * have already been popped (due to clock skew), so send
213 return &to_send
->object
.oid
;
216 static void known_common(struct fetch_negotiator
*n
, struct commit
*c
)
218 if (c
->object
.flags
& SEEN
)
220 rev_list_push(n
->data
, c
, ADVERTISED
);
223 static void add_tip(struct fetch_negotiator
*n
, struct commit
*c
)
225 n
->known_common
= NULL
;
226 if (c
->object
.flags
& SEEN
)
228 rev_list_push(n
->data
, c
, 0);
231 static const struct object_id
*next(struct fetch_negotiator
*n
)
233 n
->known_common
= NULL
;
235 return get_rev(n
->data
);
238 static int ack(struct fetch_negotiator
*n
, struct commit
*c
)
240 int known_to_be_common
= !!(c
->object
.flags
& COMMON
);
241 if (!(c
->object
.flags
& SEEN
))
242 die("received ack for commit %s not sent as 'have'",
243 oid_to_hex(&c
->object
.oid
));
244 mark_common(n
->data
, c
);
245 return known_to_be_common
;
248 static void release(struct fetch_negotiator
*n
)
250 struct data
*data
= n
->data
;
251 for (int i
= 0; i
< data
->rev_list
.nr
; i
++)
252 free(data
->rev_list
.array
[i
].data
);
253 clear_prio_queue(&data
->rev_list
);
257 void skipping_negotiator_init(struct fetch_negotiator
*negotiator
)
260 negotiator
->known_common
= known_common
;
261 negotiator
->add_tip
= add_tip
;
262 negotiator
->next
= next
;
263 negotiator
->ack
= ack
;
264 negotiator
->release
= release
;
265 negotiator
->data
= CALLOC_ARRAY(data
, 1);
266 data
->rev_list
.compare
= compare
;
269 refs_for_each_ref(get_main_ref_store(the_repository
),