Commit bdaa40d3da5f57be275b2f1ba8d04253a2fcaea7

Vicent Martí 2012-10-01T08:28:46

Merge pull request #957 from carlosmn/include-tag Include tag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
diff --git a/include/git2/refspec.h b/include/git2/refspec.h
index 9e84aad..1100e90 100644
--- a/include/git2/refspec.h
+++ b/include/git2/refspec.h
@@ -20,14 +20,6 @@
 GIT_BEGIN_DECL
 
 /**
- * Parse a refspec string and create a refspec object
- *
- * @param refspec pointer to the refspec structure to be used
- * @param str the refspec as a string
- */
-GIT_EXTERN(int) git_refspec_parse(git_refspec *refspec, const char *str);
-
-/**
  * Get the source specifier
  *
  * @param refspec the refspec
diff --git a/include/git2/remote.h b/include/git2/remote.h
index a3913af..c015289 100644
--- a/include/git2/remote.h
+++ b/include/git2/remote.h
@@ -304,6 +304,30 @@ struct git_remote_callbacks {
  */
 GIT_EXTERN(void) git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks);
 
+enum {
+	GIT_REMOTE_DOWNLOAD_TAGS_UNSET,
+	GIT_REMOTE_DOWNLOAD_TAGS_NONE,
+	GIT_REMOTE_DOWNLOAD_TAGS_AUTO,
+	GIT_REMOTE_DOWNLOAD_TAGS_ALL
+};
+
+/**
+ * Retrieve the tag auto-follow setting
+ *
+ * @param remote the remote to query
+ * @return the auto-follow setting
+ */
+GIT_EXTERN(int) git_remote_autotag(git_remote *remote);
+
+/**
+ * Set the tag auto-follow setting
+ *
+ * @param remote the remote to configure
+ * @param value a GIT_REMOTE_DOWNLOAD_TAGS value
+ */
+GIT_EXTERN(void) git_remote_set_autotag(git_remote *remote, int value);
+
+
 /** @} */
 GIT_END_DECL
 #endif
diff --git a/src/fetch.c b/src/fetch.c
index 98e1f0b..f9cc8aa 100644
--- a/src/fetch.c
+++ b/src/fetch.c
@@ -21,7 +21,7 @@
 
 struct filter_payload {
 	git_remote *remote;
-	const git_refspec *spec;
+	const git_refspec *spec, *tagspec;
 	git_odb *odb;
 	int found_head;
 };
@@ -29,18 +29,21 @@ struct filter_payload {
 static int filter_ref__cb(git_remote_head *head, void *payload)
 {
 	struct filter_payload *p = payload;
+	int match = 0;
 
-	if (!p->found_head && strcmp(head->name, GIT_HEAD_FILE) == 0) {
+	if (!git_reference_is_valid_name(head->name))
+		return 0;
+
+	if (!p->found_head && strcmp(head->name, GIT_HEAD_FILE) == 0)
 		p->found_head = 1;
-	} else {
-		/* If it doesn't match the refpec, we don't want it */
-		if (!git_refspec_src_matches(p->spec, head->name))
-			return 0;
+	else if (git_refspec_src_matches(p->spec, head->name))
+			match = 1;
+	else if (p->remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL &&
+		 git_refspec_src_matches(p->tagspec, head->name))
+			match = 1;
 
-		/* Don't even try to ask for the annotation target */
-		if (!git__suffixcmp(head->name, "^{}"))
-			return 0;
-	}
+	if (!match)
+		return 0;
 
 	/* If we have the object, mark it so we don't ask for it */
 	if (git_odb_exists(p->odb, &head->oid))
@@ -54,8 +57,11 @@ static int filter_ref__cb(git_remote_head *head, void *payload)
 static int filter_wants(git_remote *remote)
 {
 	struct filter_payload p;
+	git_refspec tagspec;
 
 	git_vector_clear(&remote->refs);
+	if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
+		return -1;
 
 	/*
 	 * The fetch refspec can be NULL, and what this means is that the
@@ -64,6 +70,7 @@ static int filter_wants(git_remote *remote)
 	 * HEAD, which will be stored in FETCH_HEAD after the fetch.
 	 */
 	p.spec = git_remote_fetchspec(remote);
+	p.tagspec = &tagspec;
 	p.found_head = 0;
 	p.remote = remote;
 
diff --git a/src/pkt.c b/src/pkt.c
index ad0149d..91f9b65 100644
--- a/src/pkt.c
+++ b/src/pkt.c
@@ -354,6 +354,9 @@ static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps
 	if (caps->multi_ack)
 		git_buf_puts(&str, GIT_CAP_MULTI_ACK " ");
 
+	if (caps->include_tag)
+		git_buf_puts(&str, GIT_CAP_INCLUDE_TAG " ");
+
 	if (git_buf_oom(&str))
 		return -1;
 
diff --git a/src/protocol.c b/src/protocol.c
index 4526c85..8f673cd 100644
--- a/src/protocol.c
+++ b/src/protocol.c
@@ -80,6 +80,12 @@ int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps)
 			continue;
 		}
 
+		if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
+			caps->common = caps->include_tag = 1;
+			ptr += strlen(GIT_CAP_INCLUDE_TAG);
+			continue;
+		}
+
 		/* Keep side-band check after side-band-64k */
 		if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {
 			caps->common = caps->side_band_64k = 1;
diff --git a/src/refs.c b/src/refs.c
index 693870a..903accc 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -1199,6 +1199,7 @@ int git_reference_create_symbolic(
 {
 	char normalized[GIT_REFNAME_MAX];
 	git_reference *ref = NULL;
+	int error;
 
 	if (git_reference__normalize_name_lax(
 		normalized,
@@ -1206,8 +1207,8 @@ int git_reference_create_symbolic(
 		name) < 0)
 			return -1;
 
-	if (reference_can_write(repo, normalized, NULL, force) < 0)
-		return -1;
+	if ((error = reference_can_write(repo, normalized, NULL, force)) < 0)
+		return error;
 
 	if (reference_alloc(&ref, repo, normalized) < 0)
 		return -1;
@@ -1236,6 +1237,7 @@ int git_reference_create_oid(
 	const git_oid *id,
 	int force)
 {
+	int error;
 	git_reference *ref = NULL;
 	char normalized[GIT_REFNAME_MAX];
 
@@ -1245,8 +1247,8 @@ int git_reference_create_oid(
 		name) < 0)
 			return -1;
 
-	if (reference_can_write(repo, normalized, NULL, force) < 0)
-		return -1;
+	if ((error = reference_can_write(repo, normalized, NULL, force)) < 0)
+		return error;
 
 	if (reference_alloc(&ref, repo, name) < 0)
 		return -1;
diff --git a/src/refspec.c b/src/refspec.c
index 1265c56..cd3a528 100644
--- a/src/refspec.c
+++ b/src/refspec.c
@@ -125,35 +125,10 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
 	return -1;
 }
 
-int git_refspec_parse(git_refspec *refspec, const char *str)
+void git_refspec__free(git_refspec *refspec)
 {
-	char *delim;
-
-	memset(refspec, 0x0, sizeof(git_refspec));
-
-	if (*str == '+') {
-		refspec->force = 1;
-		str++;
-	}
-
-	delim = strchr(str, ':');
-	if (delim == NULL) {
-		refspec->src = git__strdup(str);
-		GITERR_CHECK_ALLOC(refspec->src);
-		return 0;
-	}
-
-	refspec->src = git__strndup(str, delim - str);
-	GITERR_CHECK_ALLOC(refspec->src);
-
-	refspec->dst = git__strdup(delim + 1);
-	if (refspec->dst == NULL) {
-		git__free(refspec->src);
-		refspec->src = NULL;
-		return -1;
-	}
-
-	return 0;
+	git__free(refspec->src);
+	git__free(refspec->dst);
 }
 
 const char *git_refspec_src(const git_refspec *refspec)
diff --git a/src/refspec.h b/src/refspec.h
index 2f46b3e..a5df458 100644
--- a/src/refspec.h
+++ b/src/refspec.h
@@ -19,12 +19,16 @@ struct git_refspec {
 		matching :1;
 };
 
+#define GIT_REFSPEC_TAGS "refs/tags/*:refs/tags/*"
+
 int git_refspec_parse(struct git_refspec *refspec, const char *str);
 int git_refspec__parse(
 	struct git_refspec *refspec,
 	const char *str,
 	bool is_fetch);
 
+void git_refspec__free(git_refspec *refspec);
+
 /**
  * Transform a reference to its target following the refspec's rules,
  * and writes the results into a git_buf.
diff --git a/src/remote.c b/src/remote.c
index 7bc631d..fd78164 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -18,41 +18,42 @@
 
 #include <regex.h>
 
-static int refspec_parse(git_refspec *refspec, const char *str)
+static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var, bool is_fetch)
 {
-	char *delim;
-
-	memset(refspec, 0x0, sizeof(git_refspec));
-
-	if (*str == '+') {
-		refspec->force = 1;
-		str++;
-	}
-
-	delim = strchr(str, ':');
-	if (delim == NULL) {
-		giterr_set(GITERR_NET, "Invalid refspec, missing ':'");
-		return -1;
-	}
-
-	refspec->src = git__strndup(str, delim - str);
-	GITERR_CHECK_ALLOC(refspec->src);
+	int error;
+	const char *val;
 
-	refspec->dst = git__strdup(delim + 1);
-	GITERR_CHECK_ALLOC(refspec->dst);
+	if ((error = git_config_get_string(&val, cfg, var)) < 0)
+		return error;
 
-	return 0;
+	return git_refspec__parse(refspec, val, is_fetch);
 }
 
-static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var)
+static int download_tags_value(git_remote *remote, git_config *cfg)
 {
-	int error;
 	const char *val;
+	git_buf buf = GIT_BUF_INIT;
+	int error;
 
-	if ((error = git_config_get_string(&val, cfg, var)) < 0)
-		return error;
+	if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSET)
+		return 0;
 
-	return refspec_parse(refspec, val);
+	/* This is the default, let's see if we need to change it */
+	remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO;
+	if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0)
+		return -1;
+
+	error = git_config_get_string(&val, cfg, git_buf_cstr(&buf));
+	git_buf_free(&buf);
+	if (!error && !strcmp(val, "--no-tags"))
+		remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE;
+	else if (!error && !strcmp(val, "--tags"))
+		remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL;
+
+	if (error == GIT_ENOTFOUND)
+		error = 0;
+
+	return error;
 }
 
 int git_remote_new(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
@@ -81,7 +82,7 @@ int git_remote_new(git_remote **out, git_repository *repo, const char *name, con
 	}
 
 	if (fetch != NULL) {
-		if (refspec_parse(&remote->fetch, fetch) < 0)
+		if (git_refspec__parse(&remote->fetch, fetch, true) < 0)
 			goto on_error;
 	}
 
@@ -157,7 +158,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
 		goto cleanup;
 	}
 
-	error = parse_remote_refspec(config, &remote->fetch, git_buf_cstr(&buf));
+	error = parse_remote_refspec(config, &remote->fetch, git_buf_cstr(&buf), true);
 	if (error == GIT_ENOTFOUND)
 		error = 0;
 
@@ -172,7 +173,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
 		goto cleanup;
 	}
 
-	error = parse_remote_refspec(config, &remote->push, git_buf_cstr(&buf));
+	error = parse_remote_refspec(config, &remote->push, git_buf_cstr(&buf), false);
 	if (error == GIT_ENOTFOUND)
 		error = 0;
 
@@ -181,6 +182,9 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
 		goto cleanup;
 	}
 
+	if (download_tags_value(remote, config) < 0)
+		goto cleanup;
+
 	*out = remote;
 
 cleanup:
@@ -317,11 +321,10 @@ int git_remote_set_fetchspec(git_remote *remote, const char *spec)
 
 	assert(remote && spec);
 
-	if (refspec_parse(&refspec, spec) < 0)
+	if (git_refspec__parse(&refspec, spec, true) < 0)
 		return -1;
 
-	git__free(remote->fetch.src);
-	git__free(remote->fetch.dst);
+	git_refspec__free(&remote->fetch);
 	remote->fetch.src = refspec.src;
 	remote->fetch.dst = refspec.dst;
 
@@ -340,11 +343,10 @@ int git_remote_set_pushspec(git_remote *remote, const char *spec)
 
 	assert(remote && spec);
 
-	if (refspec_parse(&refspec, spec) < 0)
+	if (git_refspec__parse(&refspec, spec, false) < 0)
 		return -1;
 
-	git__free(remote->push.src);
-	git__free(remote->push.dst);
+	git_refspec__free(&remote->push);
 	remote->push.src = refspec.src;
 	remote->push.dst = refspec.dst;
 
@@ -445,25 +447,35 @@ int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats 
 
 int git_remote_update_tips(git_remote *remote)
 {
-	int error = 0;
+	int error = 0, autotag;
 	unsigned int i = 0;
 	git_buf refname = GIT_BUF_INIT;
 	git_oid old;
+	git_pkt *pkt;
+	git_odb *odb;
 	git_vector *refs;
 	git_remote_head *head;
 	git_reference *ref;
 	struct git_refspec *spec;
+	git_refspec tagspec;
 
 	assert(remote);
 
-	refs = &remote->refs;
+	refs = &remote->transport->refs;
 	spec = &remote->fetch;
 
 	if (refs->length == 0)
 		return 0;
 
+	if (git_repository_odb(&odb, remote->repo) < 0)
+		return -1;
+
+	if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
+		return -1;
+
 	/* HEAD is only allowed to be the first in the list */
-	head = refs->contents[0];
+	pkt = refs->contents[0];
+	head = &((git_pkt_ref *)pkt)->head;
 	if (!strcmp(head->name, GIT_HEAD_FILE)) {
 		if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0)
 			return -1;
@@ -473,10 +485,38 @@ int git_remote_update_tips(git_remote *remote)
 	}
 
 	for (; i < refs->length; ++i) {
-		head = refs->contents[i];
+		autotag = 0;
+		git_pkt *pkt = refs->contents[i];
 
-		if (git_refspec_transform_r(&refname, spec, head->name) < 0)
-			goto on_error;
+		if (pkt->type == GIT_PKT_REF)
+			head = &((git_pkt_ref *)pkt)->head;
+		else
+			continue;
+
+		/* Ignore malformed ref names (which also saves us from tag^{} */
+		if (!git_reference_is_valid_name(head->name))
+			continue;
+
+		if (git_refspec_src_matches(spec, head->name)) {
+			if (git_refspec_transform_r(&refname, spec, head->name) < 0)
+				goto on_error;
+		} else if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_NONE) {
+
+			if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_ALL)
+				autotag = 1;
+
+			if (!git_refspec_src_matches(&tagspec, head->name))
+				continue;
+
+			git_buf_clear(&refname);
+			if (git_buf_puts(&refname, head->name) < 0)
+				goto on_error;
+		} else {
+			continue;
+		}
+
+		if (autotag && !git_odb_exists(odb, &head->oid))
+			continue;
 
 		error = git_reference_name_to_oid(&old, remote->repo, refname.ptr);
 		if (error < 0 && error != GIT_ENOTFOUND)
@@ -488,7 +528,9 @@ int git_remote_update_tips(git_remote *remote)
 		if (!git_oid_cmp(&old, &head->oid))
 			continue;
 
-		if (git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, 1) < 0)
+		/* In autotag mode, don't overwrite any locally-existing tags */
+		error = git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, !autotag);
+		if (error < 0 && error != GIT_EEXISTS)
 			goto on_error;
 
 		git_reference_free(ref);
@@ -499,10 +541,12 @@ int git_remote_update_tips(git_remote *remote)
 		}
 	}
 
+	git_refspec__free(&tagspec);
 	git_buf_free(&refname);
 	return 0;
 
 on_error:
+	git_refspec__free(&tagspec);
 	git_buf_free(&refname);
 	return -1;
 
@@ -536,10 +580,8 @@ void git_remote_free(git_remote *remote)
 
 	git_vector_free(&remote->refs);
 
-	git__free(remote->fetch.src);
-	git__free(remote->fetch.dst);
-	git__free(remote->push.src);
-	git__free(remote->push.dst);
+	git_refspec__free(&remote->fetch);
+	git_refspec__free(&remote->push);
 	git__free(remote->url);
 	git__free(remote->pushurl);
 	git__free(remote->name);
@@ -655,3 +697,13 @@ void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callback
 		remote->transport->cb_data = remote->callbacks.data;
 	}
 }
+
+int git_remote_autotag(git_remote *remote)
+{
+	return remote->download_tags;
+}
+
+void git_remote_set_autotag(git_remote *remote, int value)
+{
+	remote->download_tags = value;
+}
diff --git a/src/remote.h b/src/remote.h
index 67933a3..b8bb2c5 100644
--- a/src/remote.h
+++ b/src/remote.h
@@ -24,7 +24,8 @@ struct git_remote {
 	git_repository *repo;
 	git_remote_callbacks callbacks;
 	unsigned int need_pack:1,
-		check_cert;
+		download_tags:2, /* There are four possible values */
+		check_cert:1;
 };
 
 const char* git_remote__urlfordirection(struct git_remote *remote, int direction);
diff --git a/src/transport.h b/src/transport.h
index ff3a58d..9c91afd 100644
--- a/src/transport.h
+++ b/src/transport.h
@@ -23,13 +23,15 @@
 #define GIT_CAP_MULTI_ACK "multi_ack"
 #define GIT_CAP_SIDE_BAND "side-band"
 #define GIT_CAP_SIDE_BAND_64K "side-band-64k"
+#define GIT_CAP_INCLUDE_TAG "include-tag"
 
 typedef struct git_transport_caps {
 	int common:1,
 		ofs_delta:1,
 		multi_ack: 1,
 		side_band:1,
-		side_band_64k:1;
+		side_band_64k:1,
+		include_tag:1;
 } git_transport_caps;
 
 #ifdef GIT_SSL
diff --git a/tests-clar/network/refspecs.c b/tests-clar/network/refspecs.c
index bfe0af4..3b12817 100644
--- a/tests-clar/network/refspecs.c
+++ b/tests-clar/network/refspecs.c
@@ -8,6 +8,7 @@ static void assert_refspec(unsigned int direction, const char *input, bool is_ex
 	int error;
 
 	error = git_refspec__parse(&refspec, input, direction == GIT_DIR_FETCH);
+	git_refspec__free(&refspec);
 
 	if (is_expected_to_be_valid)
 		cl_assert_equal_i(0, error);
diff --git a/tests-clar/refs/create.c b/tests-clar/refs/create.c
index af5b203..bf234bc 100644
--- a/tests-clar/refs/create.c
+++ b/tests-clar/refs/create.c
@@ -147,3 +147,18 @@ void test_refs_create__oid_unknown(void)
 	/* Ensure the reference can't be looked-up... */
 	cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, new_head));
 }
+
+void test_refs_create__propagate_eexists(void)
+{
+	int error;
+	git_oid oid;
+	git_reference *ref;
+
+	/* Make sure it works for oid and for symbolic both */
+	git_oid_fromstr(&oid, current_master_tip);
+	error = git_reference_create_oid(&ref, g_repo, current_head_target, &oid, false);
+	cl_assert(error == GIT_EEXISTS);
+
+	error = git_reference_create_symbolic(&ref, g_repo, "HEAD", current_head_target, false);
+	cl_assert(error == GIT_EEXISTS);
+}