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 */