upload-pack: introduce fetch server command

Introduce the 'fetch' server command. Signed-off-by: Brandon Williams <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Brandon Williams committed Mar 15, 2018 at 10:31 UTC 3145ea957d2ca6ba7afb087c9ed680e08d705690
5 files changed +400
Documentation/technical/protocol-v2.txt
+125
@@ -199,3 +199,128 @@ The output of ls-refs is as follows:
199 ref-attribute = (symref | peeled)
200 symref = "symref-target:" symref-target
201 peeled = "peeled:" obj-id
202 +
203 + fetch
204 +~~~~~~~
205 +
206 +`fetch` is the command used to fetch a packfile in v2. It can be looked
207 +at as a modified version of the v1 fetch where the ref-advertisement is
208 +stripped out (since the `ls-refs` command fills that role) and the
209 +message format is tweaked to eliminate redundancies and permit easy
210 +addition of future extensions.
211 +
212 +Additional features not supported in the base command will be advertised
213 +as the value of the command in the capability advertisement in the form
214 +of a space separated list of features: "<command>=<feature 1> <feature 2>"
215 +
216 +A `fetch` request can take the following arguments:
217 +
218 + want <oid>
219 + Indicates to the server an object which the client wants to
220 + retrieve. Wants can be anything and are not limited to
221 + advertised objects.
222 +
223 + have <oid>
224 + Indicates to the server an object which the client has locally.
225 + This allows the server to make a packfile which only contains
226 + the objects that the client needs. Multiple 'have' lines can be
227 + supplied.
228 +
229 + done
230 + Indicates to the server that negotiation should terminate (or
231 + not even begin if performing a clone) and that the server should
232 + use the information supplied in the request to construct the
233 + packfile.
234 +
235 + thin-pack
236 + Request that a thin pack be sent, which is a pack with deltas
237 + which reference base objects not contained within the pack (but
238 + are known to exist at the receiving end). This can reduce the
239 + network traffic significantly, but it requires the receiving end
240 + to know how to "thicken" these packs by adding the missing bases
241 + to the pack.
242 +
243 + no-progress
244 + Request that progress information that would normally be sent on
245 + side-band channel 2, during the packfile transfer, should not be
246 + sent. However, the side-band channel 3 is still used for error
247 + responses.
248 +
249 + include-tag
250 + Request that annotated tags should be sent if the objects they
251 + point to are being sent.
252 +
253 + ofs-delta
254 + Indicate that the client understands PACKv2 with delta referring
255 + to its base by position in pack rather than by an oid. That is,
256 + they can read OBJ_OFS_DELTA (ake type 6) in a packfile.
257 +
258 +The response of `fetch` is broken into a number of sections separated by
259 +delimiter packets (0001), with each section beginning with its section
260 +header.
261 +
262 + output = *section
263 + section = (acknowledgments | packfile)
264 + (flush-pkt | delim-pkt)
265 +
266 + acknowledgments = PKT-LINE("acknowledgments" LF)
267 + (nak | *ack)
268 + (ready)
269 + ready = PKT-LINE("ready" LF)
270 + nak = PKT-LINE("NAK" LF)
271 + ack = PKT-LINE("ACK" SP obj-id LF)
272 +
273 + packfile = PKT-LINE("packfile" LF)
274 + *PKT-LINE(%x01-03 *%x00-ff)
275 +
276 + acknowledgments section
277 + * If the client determines that it is finished with negotiations
278 + by sending a "done" line, the acknowledgments sections MUST be
279 + omitted from the server's response.
280 +
281 + * Always begins with the section header "acknowledgments"
282 +
283 + * The server will respond with "NAK" if none of the object ids sent
284 + as have lines were common.
285 +
286 + * The server will respond with "ACK obj-id" for all of the
287 + object ids sent as have lines which are common.
288 +
289 + * A response cannot have both "ACK" lines as well as a "NAK"
290 + line.
291 +
292 + * The server will respond with a "ready" line indicating that
293 + the server has found an acceptable common base and is ready to
294 + make and send a packfile (which will be found in the packfile
295 + section of the same response)
296 +
297 + * If the server has found a suitable cut point and has decided
298 + to send a "ready" line, then the server can decide to (as an
299 + optimization) omit any "ACK" lines it would have sent during
300 + its response. This is because the server will have already
301 + determined the objects it plans to send to the client and no
302 + further negotiation is needed.
303 +
304 + packfile section
305 + * This section is only included if the client has sent 'want'
306 + lines in its request and either requested that no more
307 + negotiation be done by sending 'done' or if the server has
308 + decided it has found a sufficient cut point to produce a
309 + packfile.
310 +
311 + * Always begins with the section header "packfile"
312 +
313 + * The transmission of the packfile begins immediately after the
314 + section header
315 +
316 + * The data transfer of the packfile is always multiplexed, using
317 + the same semantics of the 'side-band-64k' capability from
318 + protocol version 1. This means that each packet, during the
319 + packfile data stream, is made up of a leading 4-byte pkt-line
320 + length (typical of the pkt-line format), followed by a 1-byte
321 + stream code, followed by the actual data.
322 +
323 + The stream code can be one of:
324 + 1 - pack data
325 + 2 - progress messages
326 + 3 - fatal error message just before stream aborts
serve.c
+2
@@ -6,6 +6,7 @@
6 #include "argv-array.h"
7 #include "ls-refs.h"
8 #include "serve.h"
9 +#include "upload-pack.h"
10
11 static int always_advertise(struct repository *r,
12 struct strbuf *value)
@@ -54,6 +55,7 @@ struct protocol_capability {
55 static struct protocol_capability capabilities[] = {
56 { "agent", agent_advertise, NULL },
57 { "ls-refs", always_advertise, ls_refs },
58 + { "fetch", always_advertise, upload_pack_v2 },
59 };
60
61 static void advertise_capabilities(void)
t/t5701-git-serve.sh
+1
@@ -9,6 +9,7 @@ test_expect_success 'test capability advertisement' '
9 version 2
10 agent=git/$(git version | cut -d" " -f3)
11 ls-refs
12 + fetch
13 0000
14 EOF
15
upload-pack.c
+266
@@ -18,6 +18,7 @@
18 #include "prio-queue.h"
19 #include "protocol.h"
20 #include "upload-pack.h"
21 +#include "serve.h"
22
23 /* Remember to update object flag allocation in object.h */
24 #define THEY_HAVE (1u << 11)
@@ -1065,3 +1066,268 @@ void upload_pack(struct upload_pack_options *options)
1066 create_pack_file();
1067 }
1068 }
1069 +
1070 +struct upload_pack_data {
1071 + struct object_array wants;
1072 + struct oid_array haves;
1073 +
1074 + unsigned stateless_rpc : 1;
1075 +
1076 + unsigned use_thin_pack : 1;
1077 + unsigned use_ofs_delta : 1;
1078 + unsigned no_progress : 1;
1079 + unsigned use_include_tag : 1;
1080 + unsigned done : 1;
1081 +};
1082 +
1083 +static void upload_pack_data_init(struct upload_pack_data *data)
1084 +{
1085 + struct object_array wants = OBJECT_ARRAY_INIT;
1086 + struct oid_array haves = OID_ARRAY_INIT;
1087 +
1088 + memset(data, 0, sizeof(*data));
1089 + data->wants = wants;
1090 + data->haves = haves;
1091 +}
1092 +
1093 +static void upload_pack_data_clear(struct upload_pack_data *data)
1094 +{
1095 + object_array_clear(&data->wants);
1096 + oid_array_clear(&data->haves);
1097 +}
1098 +
1099 +static int parse_want(const char *line)
1100 +{
1101 + const char *arg;
1102 + if (skip_prefix(line, "want ", &arg)) {
1103 + struct object_id oid;
1104 + struct object *o;
1105 +
1106 + if (get_oid_hex(arg, &oid))
1107 + die("git upload-pack: protocol error, "
1108 + "expected to get oid, not '%s'", line);
1109 +
1110 + o = parse_object(&oid);
1111 + if (!o) {
1112 + packet_write_fmt(1,
1113 + "ERR upload-pack: not our ref %s",
1114 + oid_to_hex(&oid));
1115 + die("git upload-pack: not our ref %s",
1116 + oid_to_hex(&oid));
1117 + }
1118 +
1119 + if (!(o->flags & WANTED)) {
1120 + o->flags |= WANTED;
1121 + add_object_array(o, NULL, &want_obj);
1122 + }
1123 +
1124 + return 1;
1125 + }
1126 +
1127 + return 0;
1128 +}
1129 +
1130 +static int parse_have(const char *line, struct oid_array *haves)
1131 +{
1132 + const char *arg;
1133 + if (skip_prefix(line, "have ", &arg)) {
1134 + struct object_id oid;
1135 +
1136 + if (get_oid_hex(arg, &oid))
1137 + die("git upload-pack: expected SHA1 object, got '%s'", arg);
1138 + oid_array_append(haves, &oid);
1139 + return 1;
1140 + }
1141 +
1142 + return 0;
1143 +}
1144 +
1145 +static void process_args(struct packet_reader *request,
1146 + struct upload_pack_data *data)
1147 +{
1148 + while (packet_reader_read(request) != PACKET_READ_FLUSH) {
1149 + const char *arg = request->line;
1150 +
1151 + /* process want */
1152 + if (parse_want(arg))
1153 + continue;
1154 + /* process have line */
1155 + if (parse_have(arg, &data->haves))
1156 + continue;
1157 +
1158 + /* process args like thin-pack */
1159 + if (!strcmp(arg, "thin-pack")) {
1160 + use_thin_pack = 1;
1161 + continue;
1162 + }
1163 + if (!strcmp(arg, "ofs-delta")) {
1164 + use_ofs_delta = 1;
1165 + continue;
1166 + }
1167 + if (!strcmp(arg, "no-progress")) {
1168 + no_progress = 1;
1169 + continue;
1170 + }
1171 + if (!strcmp(arg, "include-tag")) {
1172 + use_include_tag = 1;
1173 + continue;
1174 + }
1175 + if (!strcmp(arg, "done")) {
1176 + data->done = 1;
1177 + continue;
1178 + }
1179 +
1180 + /* ignore unknown lines maybe? */
1181 + die("unexpect line: '%s'", arg);
1182 + }
1183 +}
1184 +
1185 +static int process_haves(struct oid_array *haves, struct oid_array *common)
1186 +{
1187 + int i;
1188 +
1189 + /* Process haves */
1190 + for (i = 0; i < haves->nr; i++) {
1191 + const struct object_id *oid = &haves->oid[i];
1192 + struct object *o;
1193 + int we_knew_they_have = 0;
1194 +
1195 + if (!has_object_file(oid))
1196 + continue;
1197 +
1198 + oid_array_append(common, oid);
1199 +
1200 + o = parse_object(oid);
1201 + if (!o)
1202 + die("oops (%s)", oid_to_hex(oid));
1203 + if (o->type == OBJ_COMMIT) {
1204 + struct commit_list *parents;
1205 + struct commit *commit = (struct commit *)o;
1206 + if (o->flags & THEY_HAVE)
1207 + we_knew_they_have = 1;
1208 + else
1209 + o->flags |= THEY_HAVE;
1210 + if (!oldest_have || (commit->date < oldest_have))
1211 + oldest_have = commit->date;
1212 + for (parents = commit->parents;
1213 + parents;
1214 + parents = parents->next)
1215 + parents->item->object.flags |= THEY_HAVE;
1216 + }
1217 + if (!we_knew_they_have)
1218 + add_object_array(o, NULL, &have_obj);
1219 + }
1220 +
1221 + return 0;
1222 +}
1223 +
1224 +static int send_acks(struct oid_array *acks, struct strbuf *response)
1225 +{
1226 + int i;
1227 +
1228 + packet_buf_write(response, "acknowledgments\n");
1229 +
1230 + /* Send Acks */
1231 + if (!acks->nr)
1232 + packet_buf_write(response, "NAK\n");
1233 +
1234 + for (i = 0; i < acks->nr; i++) {
1235 + packet_buf_write(response, "ACK %s\n",
1236 + oid_to_hex(&acks->oid[i]));
1237 + }
1238 +
1239 + if (ok_to_give_up()) {
1240 + /* Send Ready */
1241 + packet_buf_write(response, "ready\n");
1242 + return 1;
1243 + }
1244 +
1245 + return 0;
1246 +}
1247 +
1248 +static int process_haves_and_send_acks(struct upload_pack_data *data)
1249 +{
1250 + struct oid_array common = OID_ARRAY_INIT;
1251 + struct strbuf response = STRBUF_INIT;
1252 + int ret = 0;
1253 +
1254 + process_haves(&data->haves, &common);
1255 + if (data->done) {
1256 + ret = 1;
1257 + } else if (send_acks(&common, &response)) {
1258 + packet_buf_delim(&response);
1259 + ret = 1;
1260 + } else {
1261 + /* Add Flush */
1262 + packet_buf_flush(&response);
1263 + ret = 0;
1264 + }
1265 +
1266 + /* Send response */
1267 + write_or_die(1, response.buf, response.len);
1268 + strbuf_release(&response);
1269 +
1270 + oid_array_clear(&data->haves);
1271 + oid_array_clear(&common);
1272 + return ret;
1273 +}
1274 +
1275 +enum fetch_state {
1276 + FETCH_PROCESS_ARGS = 0,
1277 + FETCH_SEND_ACKS,
1278 + FETCH_SEND_PACK,
1279 + FETCH_DONE,
1280 +};
1281 +
1282 +int upload_pack_v2(struct repository *r, struct argv_array *keys,
1283 + struct packet_reader *request)
1284 +{
1285 + enum fetch_state state = FETCH_PROCESS_ARGS;
1286 + struct upload_pack_data data;
1287 +
1288 + upload_pack_data_init(&data);
1289 + use_sideband = LARGE_PACKET_MAX;
1290 +
1291 + while (state != FETCH_DONE) {
1292 + switch (state) {
1293 + case FETCH_PROCESS_ARGS:
1294 + process_args(request, &data);
1295 +
1296 + if (!want_obj.nr) {
1297 + /*
1298 + * Request didn't contain any 'want' lines,
1299 + * guess they didn't want anything.
1300 + */
1301 + state = FETCH_DONE;
1302 + } else if (data.haves.nr) {
1303 + /*
1304 + * Request had 'have' lines, so lets ACK them.
1305 + */
1306 + state = FETCH_SEND_ACKS;
1307 + } else {
1308 + /*
1309 + * Request had 'want's but no 'have's so we can
1310 + * immedietly go to construct and send a pack.
1311 + */
1312 + state = FETCH_SEND_PACK;
1313 + }
1314 + break;
1315 + case FETCH_SEND_ACKS:
1316 + if (process_haves_and_send_acks(&data))
1317 + state = FETCH_SEND_PACK;
1318 + else
1319 + state = FETCH_DONE;
1320 + break;
1321 + case FETCH_SEND_PACK:
1322 + packet_write_fmt(1, "packfile\n");
1323 + create_pack_file();
1324 + state = FETCH_DONE;
1325 + break;
1326 + case FETCH_DONE:
1327 + continue;
1328 + }
1329 + }
1330 +
1331 + upload_pack_data_clear(&data);
1332 + return 0;
1333 +}
upload-pack.h
+6
@@ -10,4 +10,10 @@ struct upload_pack_options {
10
11 void upload_pack(struct upload_pack_options *options);
12
13 +struct repository;
14 +struct argv_array;
15 +struct packet_reader;
16 +extern int upload_pack_v2(struct repository *r, struct argv_array *keys,
17 + struct packet_reader *request);
18 +
19 #endif /* UPLOAD_PACK_H */