index-pack: make quantum of work smaller
[git] / negotiator / default.c
1 #include "cache.h"
2 #include "default.h"
3 #include "../commit.h"
4 #include "../fetch-negotiator.h"
5 #include "../prio-queue.h"
6 #include "../refs.h"
7 #include "../tag.h"
8
9 /* Remember to update object flag allocation in object.h */
10 #define COMMON          (1U << 2)
11 #define COMMON_REF      (1U << 3)
12 #define SEEN            (1U << 4)
13 #define POPPED          (1U << 5)
14
15 static int marked;
16
17 struct negotiation_state {
18         struct prio_queue rev_list;
19         int non_common_revs;
20 };
21
22 static void rev_list_push(struct negotiation_state *ns,
23                           struct commit *commit, int mark)
24 {
25         if (!(commit->object.flags & mark)) {
26                 commit->object.flags |= mark;
27
28                 if (parse_commit(commit))
29                         return;
30
31                 prio_queue_put(&ns->rev_list, commit);
32
33                 if (!(commit->object.flags & COMMON))
34                         ns->non_common_revs++;
35         }
36 }
37
38 static int clear_marks(const char *refname, const struct object_id *oid,
39                        int flag, void *cb_data)
40 {
41         struct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0);
42
43         if (o && o->type == OBJ_COMMIT)
44                 clear_commit_marks((struct commit *)o,
45                                    COMMON | COMMON_REF | SEEN | POPPED);
46         return 0;
47 }
48
49 /*
50  * This function marks a rev and its ancestors as common.
51  * In some cases, it is desirable to mark only the ancestors (for example
52  * when only the server does not yet know that they are common).
53  */
54 static void mark_common(struct negotiation_state *ns, struct commit *commit,
55                 int ancestors_only, int dont_parse)
56 {
57         if (commit != NULL && !(commit->object.flags & COMMON)) {
58                 struct object *o = (struct object *)commit;
59
60                 if (!ancestors_only)
61                         o->flags |= COMMON;
62
63                 if (!(o->flags & SEEN))
64                         rev_list_push(ns, commit, SEEN);
65                 else {
66                         struct commit_list *parents;
67
68                         if (!ancestors_only && !(o->flags & POPPED))
69                                 ns->non_common_revs--;
70                         if (!o->parsed && !dont_parse)
71                                 if (parse_commit(commit))
72                                         return;
73
74                         for (parents = commit->parents;
75                                         parents;
76                                         parents = parents->next)
77                                 mark_common(ns, parents->item, 0,
78                                             dont_parse);
79                 }
80         }
81 }
82
83 /*
84  * Get the next rev to send, ignoring the common.
85  */
86 static const struct object_id *get_rev(struct negotiation_state *ns)
87 {
88         struct commit *commit = NULL;
89
90         while (commit == NULL) {
91                 unsigned int mark;
92                 struct commit_list *parents;
93
94                 if (ns->rev_list.nr == 0 || ns->non_common_revs == 0)
95                         return NULL;
96
97                 commit = prio_queue_get(&ns->rev_list);
98                 parse_commit(commit);
99                 parents = commit->parents;
100
101                 commit->object.flags |= POPPED;
102                 if (!(commit->object.flags & COMMON))
103                         ns->non_common_revs--;
104
105                 if (commit->object.flags & COMMON) {
106                         /* do not send "have", and ignore ancestors */
107                         commit = NULL;
108                         mark = COMMON | SEEN;
109                 } else if (commit->object.flags & COMMON_REF)
110                         /* send "have", and ignore ancestors */
111                         mark = COMMON | SEEN;
112                 else
113                         /* send "have", also for its ancestors */
114                         mark = SEEN;
115
116                 while (parents) {
117                         if (!(parents->item->object.flags & SEEN))
118                                 rev_list_push(ns, parents->item, mark);
119                         if (mark & COMMON)
120                                 mark_common(ns, parents->item, 1, 0);
121                         parents = parents->next;
122                 }
123         }
124
125         return &commit->object.oid;
126 }
127
128 static void known_common(struct fetch_negotiator *n, struct commit *c)
129 {
130         if (!(c->object.flags & SEEN)) {
131                 rev_list_push(n->data, c, COMMON_REF | SEEN);
132                 mark_common(n->data, c, 1, 1);
133         }
134 }
135
136 static void add_tip(struct fetch_negotiator *n, struct commit *c)
137 {
138         n->known_common = NULL;
139         rev_list_push(n->data, c, SEEN);
140 }
141
142 static const struct object_id *next(struct fetch_negotiator *n)
143 {
144         n->known_common = NULL;
145         n->add_tip = NULL;
146         return get_rev(n->data);
147 }
148
149 static int ack(struct fetch_negotiator *n, struct commit *c)
150 {
151         int known_to_be_common = !!(c->object.flags & COMMON);
152         mark_common(n->data, c, 0, 1);
153         return known_to_be_common;
154 }
155
156 static void release(struct fetch_negotiator *n)
157 {
158         clear_prio_queue(&((struct negotiation_state *)n->data)->rev_list);
159         FREE_AND_NULL(n->data);
160 }
161
162 void default_negotiator_init(struct fetch_negotiator *negotiator)
163 {
164         struct negotiation_state *ns;
165         negotiator->known_common = known_common;
166         negotiator->add_tip = add_tip;
167         negotiator->next = next;
168         negotiator->ack = ack;
169         negotiator->release = release;
170         negotiator->data = ns = xcalloc(1, sizeof(*ns));
171         ns->rev_list.compare = compare_commits_by_commit_date;
172
173         if (marked)
174                 for_each_ref(clear_marks, NULL);
175         marked = 1;
176 }