Skip to content

Commit 6ddba5e

Browse files
felipecgitster
authored andcommitted
push: add '--prune' option
When pushing groups of refs to a remote, there is no simple way to remove old refs that still exist at the remote that is no longer updated from us. This will allow us to remove such refs from the remote. With this change, running this command $ git push --prune remote refs/heads/*:refs/remotes/laptop/* removes refs/remotes/laptop/foo from the remote if we do not have branch "foo" locally anymore. Signed-off-by: Felipe Contreras <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 6765524 commit 6ddba5e

File tree

7 files changed

+60
-5
lines changed

7 files changed

+60
-5
lines changed

Documentation/git-push.txt

+9-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
13-
[--repo=<repository>] [-f | --force] [-v | --verbose] [-u | --set-upstream]
13+
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
1414
[<repository> [<refspec>...]]
1515

1616
DESCRIPTION
@@ -71,6 +71,14 @@ nor in any Push line of the corresponding remotes file---see below).
7171
Instead of naming each ref to push, specifies that all
7272
refs under `refs/heads/` be pushed.
7373

74+
--prune::
75+
Remove remote branches that don't have a local counterpart. For example
76+
a remote branch `tmp` will be removed if a local branch with the same
77+
name doesn't exist any more. This also respects refspecs, e.g.
78+
`git push --prune remote refs/heads/{asterisk}:refs/tmp/{asterisk}` would
79+
make sure that remote `refs/tmp/foo` will be removed if `refs/heads/foo`
80+
doesn't exist.
81+
7482
--mirror::
7583
Instead of naming each ref to push, specifies that all
7684
refs under `refs/` (which includes but is not

builtin/push.c

+2
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
261261
OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
262262
TRANSPORT_PUSH_SET_UPSTREAM),
263263
OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
264+
OPT_BIT(0, "prune", &flags, "prune locally removed refs",
265+
TRANSPORT_PUSH_PRUNE),
264266
OPT_END()
265267
};
266268

remote.c

+28-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include "tag.h"
99
#include "string-list.h"
1010

11+
enum map_direction { FROM_SRC, FROM_DST };
12+
1113
static struct refspec s_tag_refspec = {
1214
0,
1315
1,
@@ -1115,7 +1117,7 @@ static int match_explicit_refs(struct ref *src, struct ref *dst,
11151117
}
11161118

11171119
static char *get_ref_match(const struct refspec *rs, int rs_nr, const struct ref *ref,
1118-
int send_mirror, const struct refspec **ret_pat)
1120+
int send_mirror, int direction, const struct refspec **ret_pat)
11191121
{
11201122
const struct refspec *pat;
11211123
char *name;
@@ -1130,7 +1132,12 @@ static char *get_ref_match(const struct refspec *rs, int rs_nr, const struct ref
11301132

11311133
if (rs[i].pattern) {
11321134
const char *dst_side = rs[i].dst ? rs[i].dst : rs[i].src;
1133-
if (match_name_with_pattern(rs[i].src, ref->name, dst_side, &name)) {
1135+
int match;
1136+
if (direction == FROM_SRC)
1137+
match = match_name_with_pattern(rs[i].src, ref->name, dst_side, &name);
1138+
else
1139+
match = match_name_with_pattern(dst_side, ref->name, rs[i].src, &name);
1140+
if (match) {
11341141
matching_refs = i;
11351142
break;
11361143
}
@@ -1177,6 +1184,7 @@ int match_push_refs(struct ref *src, struct ref **dst,
11771184
struct refspec *rs;
11781185
int send_all = flags & MATCH_REFS_ALL;
11791186
int send_mirror = flags & MATCH_REFS_MIRROR;
1187+
int send_prune = flags & MATCH_REFS_PRUNE;
11801188
int errs;
11811189
static const char *default_refspec[] = { ":", NULL };
11821190
struct ref *ref, **dst_tail = tail_ref(dst);
@@ -1197,7 +1205,7 @@ int match_push_refs(struct ref *src, struct ref **dst,
11971205
if (ref->peer_ref)
11981206
continue;
11991207

1200-
dst_name = get_ref_match(rs, nr_refspec, ref, send_mirror, &pat);
1208+
dst_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_SRC, &pat);
12011209
if (!dst_name)
12021210
continue;
12031211

@@ -1224,6 +1232,23 @@ int match_push_refs(struct ref *src, struct ref **dst,
12241232
free_name:
12251233
free(dst_name);
12261234
}
1235+
if (send_prune) {
1236+
/* check for missing refs on the remote */
1237+
for (ref = *dst; ref; ref = ref->next) {
1238+
char *src_name;
1239+
1240+
if (ref->peer_ref)
1241+
/* We're already sending something to this ref. */
1242+
continue;
1243+
1244+
src_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_DST, NULL);
1245+
if (src_name) {
1246+
if (!find_ref_by_name(src, src_name))
1247+
ref->peer_ref = alloc_delete_ref();
1248+
free(src_name);
1249+
}
1250+
}
1251+
}
12271252
if (errs)
12281253
return -1;
12291254
return 0;

remote.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ int branch_merge_matches(struct branch *, int n, const char *);
145145
enum match_refs_flags {
146146
MATCH_REFS_NONE = 0,
147147
MATCH_REFS_ALL = (1 << 0),
148-
MATCH_REFS_MIRROR = (1 << 1)
148+
MATCH_REFS_MIRROR = (1 << 1),
149+
MATCH_REFS_PRUNE = (1 << 2)
149150
};
150151

151152
/* Reporting of tracking info */

t/t5516-fetch-push.sh

+16
Original file line numberDiff line numberDiff line change
@@ -979,4 +979,20 @@ test_expect_success 'push --porcelain --dry-run rejected' '
979979
test_cmp .git/foo .git/bar
980980
'
981981

982+
test_expect_success 'push --prune' '
983+
mk_test heads/master heads/second heads/foo heads/bar &&
984+
git push --prune testrepo &&
985+
check_push_result $the_commit heads/master &&
986+
check_push_result $the_first_commit heads/second &&
987+
! check_push_result $the_first_commit heads/foo heads/bar
988+
'
989+
990+
test_expect_success 'push --prune refspec' '
991+
mk_test tmp/master tmp/second tmp/foo tmp/bar &&
992+
git push --prune testrepo "refs/heads/*:refs/tmp/*" &&
993+
check_push_result $the_commit tmp/master &&
994+
check_push_result $the_first_commit tmp/second &&
995+
! check_push_result $the_first_commit tmp/foo tmp/bar
996+
'
997+
982998
test_done

transport.c

+2
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,8 @@ int transport_push(struct transport *transport,
10281028
match_flags |= MATCH_REFS_ALL;
10291029
if (flags & TRANSPORT_PUSH_MIRROR)
10301030
match_flags |= MATCH_REFS_MIRROR;
1031+
if (flags & TRANSPORT_PUSH_PRUNE)
1032+
match_flags |= MATCH_REFS_PRUNE;
10311033

10321034
if (match_push_refs(local_refs, &remote_refs,
10331035
refspec_nr, refspec, match_flags)) {

transport.h

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ struct transport {
102102
#define TRANSPORT_PUSH_PORCELAIN 16
103103
#define TRANSPORT_PUSH_SET_UPSTREAM 32
104104
#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
105+
#define TRANSPORT_PUSH_PRUNE 128
105106

106107
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
107108

0 commit comments

Comments
 (0)