Skip to content

Commit a22f542

Browse files
author
Junio C Hamano
committed
Merge branch 'jc/push-delete-ref'
* jc/push-delete-ref: Allow git push to delete remote ref.
2 parents 88ffc1f + d4f694b commit a22f542

File tree

4 files changed

+84
-23
lines changed

4 files changed

+84
-23
lines changed

connect.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ struct refspec {
144144
* +A:B means overwrite remote B with local A.
145145
* +A is a shorthand for +A:A.
146146
* A is a shorthand for A:A.
147+
* :B means delete remote B.
147148
*/
148149
static struct refspec *parse_ref_spec(int nr_refspec, char **refspec)
149150
{
@@ -240,6 +241,13 @@ static struct ref *try_explicit_object_name(const char *name)
240241
unsigned char sha1[20];
241242
struct ref *ref;
242243
int len;
244+
245+
if (!*name) {
246+
ref = xcalloc(1, sizeof(*ref) + 20);
247+
strcpy(ref->name, "(delete)");
248+
hashclr(ref->new_sha1);
249+
return ref;
250+
}
243251
if (get_sha1(name, sha1))
244252
return NULL;
245253
len = strlen(name) + 1;
@@ -262,7 +270,8 @@ static int match_explicit_refs(struct ref *src, struct ref *dst,
262270
break;
263271
case 0:
264272
/* The source could be in the get_sha1() format
265-
* not a reference name.
273+
* not a reference name. :refs/other is a
274+
* way to delete 'other' ref at the remote end.
266275
*/
267276
matched_src = try_explicit_object_name(rs[i].src);
268277
if (matched_src)

receive-pack.c

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ static int deny_non_fast_forwards = 0;
1414
static int unpack_limit = 5000;
1515
static int report_status;
1616

17-
static char capabilities[] = "report-status";
17+
static char capabilities[] = " report-status delete-refs ";
1818
static int capabilities_sent;
1919

2020
static int receive_pack_config(const char *var, const char *value)
@@ -113,12 +113,14 @@ static int update(struct command *cmd)
113113

114114
strcpy(new_hex, sha1_to_hex(new_sha1));
115115
strcpy(old_hex, sha1_to_hex(old_sha1));
116-
if (!has_sha1_file(new_sha1)) {
116+
117+
if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
117118
cmd->error_string = "bad pack";
118119
return error("unpack should have generated %s, "
119120
"but I can't find it!", new_hex);
120121
}
121-
if (deny_non_fast_forwards && !is_null_sha1(old_sha1)) {
122+
if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
123+
!is_null_sha1(old_sha1)) {
122124
struct commit *old_commit, *new_commit;
123125
struct commit_list *bases, *ent;
124126

@@ -138,14 +140,22 @@ static int update(struct command *cmd)
138140
return error("hook declined to update %s", name);
139141
}
140142

141-
lock = lock_any_ref_for_update(name, old_sha1);
142-
if (!lock) {
143-
cmd->error_string = "failed to lock";
144-
return error("failed to lock %s", name);
143+
if (is_null_sha1(new_sha1)) {
144+
if (delete_ref(name, old_sha1)) {
145+
cmd->error_string = "failed to delete";
146+
return error("failed to delete %s", name);
147+
}
148+
fprintf(stderr, "%s: %s -> deleted\n", name, old_hex);
149+
}
150+
else {
151+
lock = lock_any_ref_for_update(name, old_sha1);
152+
if (!lock) {
153+
cmd->error_string = "failed to lock";
154+
return error("failed to lock %s", name);
155+
}
156+
write_ref_sha1(lock, new_sha1, "push");
157+
fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
145158
}
146-
write_ref_sha1(lock, new_sha1, "push");
147-
148-
fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
149159
return 0;
150160
}
151161

@@ -375,6 +385,16 @@ static void report(const char *unpack_status)
375385
packet_flush(1);
376386
}
377387

388+
static int delete_only(struct command *cmd)
389+
{
390+
while (cmd) {
391+
if (!is_null_sha1(cmd->new_sha1))
392+
return 0;
393+
cmd = cmd->next;
394+
}
395+
return 1;
396+
}
397+
378398
int main(int argc, char **argv)
379399
{
380400
int i;
@@ -408,7 +428,10 @@ int main(int argc, char **argv)
408428

409429
read_head_info();
410430
if (commands) {
411-
const char *unpack_status = unpack();
431+
const char *unpack_status = NULL;
432+
433+
if (!delete_only(commands))
434+
unpack_status = unpack();
412435
if (!unpack_status)
413436
execute_commands();
414437
if (pack_lockfile)

send-pack.c

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
271271
int new_refs;
272272
int ret = 0;
273273
int ask_for_status_report = 0;
274+
int allow_deleting_refs = 0;
274275
int expect_status_report = 0;
275276

276277
/* No funny business with the matcher */
@@ -280,6 +281,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
280281
/* Does the other end support the reporting? */
281282
if (server_supports("report-status"))
282283
ask_for_status_report = 1;
284+
if (server_supports("delete-refs"))
285+
allow_deleting_refs = 1;
283286

284287
/* match them up */
285288
if (!remote_tail)
@@ -299,9 +302,19 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
299302
new_refs = 0;
300303
for (ref = remote_refs; ref; ref = ref->next) {
301304
char old_hex[60], *new_hex;
305+
int delete_ref;
306+
302307
if (!ref->peer_ref)
303308
continue;
304-
if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
309+
310+
delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
311+
if (delete_ref && !allow_deleting_refs) {
312+
error("remote does not support deleting refs");
313+
ret = -2;
314+
continue;
315+
}
316+
if (!delete_ref &&
317+
!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
305318
if (verbose)
306319
fprintf(stderr, "'%s': up-to-date\n", ref->name);
307320
continue;
@@ -321,9 +334,13 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
321334
*
322335
* (3) if both new and old are commit-ish, and new is a
323336
* descendant of old, it is OK.
337+
*
338+
* (4) regardless of all of the above, removing :B is
339+
* always allowed.
324340
*/
325341

326342
if (!force_update &&
343+
!delete_ref &&
327344
!is_zero_sha1(ref->old_sha1) &&
328345
!ref->force) {
329346
if (!has_sha1_file(ref->old_sha1) ||
@@ -347,12 +364,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
347364
}
348365
}
349366
hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
350-
if (is_zero_sha1(ref->new_sha1)) {
351-
error("cannot happen anymore");
352-
ret = -3;
353-
continue;
354-
}
355-
new_refs++;
367+
if (!delete_ref)
368+
new_refs++;
356369
strcpy(old_hex, sha1_to_hex(ref->old_sha1));
357370
new_hex = sha1_to_hex(ref->new_sha1);
358371

@@ -366,10 +379,16 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
366379
else
367380
packet_write(out, "%s %s %s",
368381
old_hex, new_hex, ref->name);
369-
fprintf(stderr, "updating '%s'", ref->name);
370-
if (strcmp(ref->name, ref->peer_ref->name))
371-
fprintf(stderr, " using '%s'", ref->peer_ref->name);
372-
fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex);
382+
if (delete_ref)
383+
fprintf(stderr, "deleting '%s'\n", ref->name);
384+
else {
385+
fprintf(stderr, "updating '%s'", ref->name);
386+
if (strcmp(ref->name, ref->peer_ref->name))
387+
fprintf(stderr, " using '%s'",
388+
ref->peer_ref->name);
389+
fprintf(stderr, "\n from %s\n to %s\n",
390+
old_hex, new_hex);
391+
}
373392
}
374393

375394
packet_flush(out);

t/t5400-send-pack.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ test_expect_success \
6464
cmp victim/.git/refs/heads/master .git/refs/heads/master
6565
'
6666

67+
test_expect_success \
68+
'push can be used to delete a ref' '
69+
cd victim &&
70+
git branch extra master &&
71+
cd .. &&
72+
test -f victim/.git/refs/heads/extra &&
73+
git-send-pack ./victim/.git/ :extra master &&
74+
! test -f victim/.git/refs/heads/extra
75+
'
76+
6777
unset GIT_CONFIG GIT_CONFIG_LOCAL
6878
HOME=`pwd`/no-such-directory
6979
export HOME ;# this way we force the victim/.git/config to be used.

0 commit comments

Comments
 (0)