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 <felipe.contreras@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Felipe Contreras committed Feb 23, 2012 at 00:43 UTC 6ddba5e241ebe484d53e3573c72386f487e25697
7 files changed +60 -5
Documentation/git-push.txt
+9 -1
@@ -10,7 +10,7 @@ SYNOPSIS
10 --------
11 [verse]
12 '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]
14 [<repository> [<refspec>...]]
15
16 DESCRIPTION
@@ -71,6 +71,14 @@ nor in any Push line of the corresponding remotes file---see below).
71 Instead of naming each ref to push, specifies that all
72 refs under `refs/heads/` be pushed.
73
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 +
82 --mirror::
83 Instead of naming each ref to push, specifies that all
84 refs under `refs/` (which includes but is not
builtin/push.c
+2
@@ -261,6 +261,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
261 OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
262 TRANSPORT_PUSH_SET_UPSTREAM),
263 OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
264 + OPT_BIT(0, "prune", &flags, "prune locally removed refs",
265 + TRANSPORT_PUSH_PRUNE),
266 OPT_END()
267 };
268
remote.c
+28 -3
@@ -8,6 +8,8 @@
8 #include "tag.h"
9 #include "string-list.h"
10
11 +enum map_direction { FROM_SRC, FROM_DST };
12 +
13 static struct refspec s_tag_refspec = {
14 0,
15 1,
@@ -1115,7 +1117,7 @@ static int match_explicit_refs(struct ref *src, struct ref *dst,
1117 }
1118
1119 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)
1121 {
1122 const struct refspec *pat;
1123 char *name;
@@ -1130,7 +1132,12 @@ static char *get_ref_match(const struct refspec *rs, int rs_nr, const struct ref
1132
1133 if (rs[i].pattern) {
1134 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) {
1141 matching_refs = i;
1142 break;
1143 }
@@ -1177,6 +1184,7 @@ int match_push_refs(struct ref *src, struct ref **dst,
1184 struct refspec *rs;
1185 int send_all = flags & MATCH_REFS_ALL;
1186 int send_mirror = flags & MATCH_REFS_MIRROR;
1187 + int send_prune = flags & MATCH_REFS_PRUNE;
1188 int errs;
1189 static const char *default_refspec[] = { ":", NULL };
1190 struct ref *ref, **dst_tail = tail_ref(dst);
@@ -1197,7 +1205,7 @@ int match_push_refs(struct ref *src, struct ref **dst,
1205 if (ref->peer_ref)
1206 continue;
1207
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);
1209 if (!dst_name)
1210 continue;
1211
@@ -1224,6 +1232,23 @@ int match_push_refs(struct ref *src, struct ref **dst,
1232 free_name:
1233 free(dst_name);
1234 }
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 + }
1252 if (errs)
1253 return -1;
1254 return 0;
remote.h
+2 -1
@@ -145,7 +145,8 @@ int branch_merge_matches(struct branch *, int n, const char *);
145 enum match_refs_flags {
146 MATCH_REFS_NONE = 0,
147 MATCH_REFS_ALL = (1 << 0),
148 - MATCH_REFS_MIRROR = (1 << 1)
148 + MATCH_REFS_MIRROR = (1 << 1),
149 + MATCH_REFS_PRUNE = (1 << 2)
150 };
151
152 /* Reporting of tracking info */
t/t5516-fetch-push.sh
+16
@@ -979,4 +979,20 @@ test_expect_success 'push --porcelain --dry-run rejected' '
979 test_cmp .git/foo .git/bar
980 '
981
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 +
998 test_done
transport.c
+2
@@ -1028,6 +1028,8 @@ int transport_push(struct transport *transport,
1028 match_flags |= MATCH_REFS_ALL;
1029 if (flags & TRANSPORT_PUSH_MIRROR)
1030 match_flags |= MATCH_REFS_MIRROR;
1031 + if (flags & TRANSPORT_PUSH_PRUNE)
1032 + match_flags |= MATCH_REFS_PRUNE;
1033
1034 if (match_push_refs(local_refs, &remote_refs,
1035 refspec_nr, refspec, match_flags)) {
transport.h
+1
@@ -102,6 +102,7 @@ struct transport {
102 #define TRANSPORT_PUSH_PORCELAIN 16
103 #define TRANSPORT_PUSH_SET_UPSTREAM 32
104 #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
105 +#define TRANSPORT_PUSH_PRUNE 128
106
107 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
108