diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index aede48877f..48760db337 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=] - [--repo=] [-f | --force] [-v | --verbose] [-u | --set-upstream] + [--repo=] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream] [ [...]] DESCRIPTION @@ -71,6 +71,14 @@ nor in any Push line of the corresponding remotes file---see below). Instead of naming each ref to push, specifies that all refs under `refs/heads/` be pushed. +--prune:: + Remove remote branches that don't have a local counterpart. For example + a remote branch `tmp` will be removed if a local branch with the same + name doesn't exist any more. This also respects refspecs, e.g. + `git push --prune remote refs/heads/{asterisk}:refs/tmp/{asterisk}` would + make sure that remote `refs/tmp/foo` will be removed if `refs/heads/foo` + doesn't exist. + --mirror:: Instead of naming each ref to push, specifies that all refs under `refs/` (which includes but is not diff --git a/builtin/push.c b/builtin/push.c index 35cce532f2..fdfb2c4512 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -261,6 +261,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status", TRANSPORT_PUSH_SET_UPSTREAM), OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"), + OPT_BIT(0, "prune", &flags, "prune locally removed refs", + TRANSPORT_PUSH_PRUNE), OPT_END() }; diff --git a/remote.c b/remote.c index c3cf736600..b296d17404 100644 --- a/remote.c +++ b/remote.c @@ -8,6 +8,8 @@ #include "tag.h" #include "string-list.h" +enum map_direction { FROM_SRC, FROM_DST }; + static struct refspec s_tag_refspec = { 0, 1, @@ -1115,7 +1117,7 @@ static int match_explicit_refs(struct ref *src, struct ref *dst, } static char *get_ref_match(const struct refspec *rs, int rs_nr, const struct ref *ref, - int send_mirror, const struct refspec **ret_pat) + int send_mirror, int direction, const struct refspec **ret_pat) { const struct refspec *pat; char *name; @@ -1130,7 +1132,12 @@ static char *get_ref_match(const struct refspec *rs, int rs_nr, const struct ref if (rs[i].pattern) { const char *dst_side = rs[i].dst ? rs[i].dst : rs[i].src; - if (match_name_with_pattern(rs[i].src, ref->name, dst_side, &name)) { + int match; + if (direction == FROM_SRC) + match = match_name_with_pattern(rs[i].src, ref->name, dst_side, &name); + else + match = match_name_with_pattern(dst_side, ref->name, rs[i].src, &name); + if (match) { matching_refs = i; break; } @@ -1177,6 +1184,7 @@ int match_push_refs(struct ref *src, struct ref **dst, struct refspec *rs; int send_all = flags & MATCH_REFS_ALL; int send_mirror = flags & MATCH_REFS_MIRROR; + int send_prune = flags & MATCH_REFS_PRUNE; int errs; static const char *default_refspec[] = { ":", NULL }; struct ref *ref, **dst_tail = tail_ref(dst); @@ -1197,7 +1205,7 @@ int match_push_refs(struct ref *src, struct ref **dst, if (ref->peer_ref) continue; - dst_name = get_ref_match(rs, nr_refspec, ref, send_mirror, &pat); + dst_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_SRC, &pat); if (!dst_name) continue; @@ -1224,6 +1232,23 @@ int match_push_refs(struct ref *src, struct ref **dst, free_name: free(dst_name); } + if (send_prune) { + /* check for missing refs on the remote */ + for (ref = *dst; ref; ref = ref->next) { + char *src_name; + + if (ref->peer_ref) + /* We're already sending something to this ref. */ + continue; + + src_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_DST, NULL); + if (src_name) { + if (!find_ref_by_name(src, src_name)) + ref->peer_ref = alloc_delete_ref(); + free(src_name); + } + } + } if (errs) return -1; return 0; diff --git a/remote.h b/remote.h index b3955983ba..9ad8eb6cc6 100644 --- a/remote.h +++ b/remote.h @@ -145,7 +145,8 @@ int branch_merge_matches(struct branch *, int n, const char *); enum match_refs_flags { MATCH_REFS_NONE = 0, MATCH_REFS_ALL = (1 << 0), - MATCH_REFS_MIRROR = (1 << 1) + MATCH_REFS_MIRROR = (1 << 1), + MATCH_REFS_PRUNE = (1 << 2) }; /* Reporting of tracking info */ diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index b69cf574d7..b5417cc951 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -979,4 +979,20 @@ test_expect_success 'push --porcelain --dry-run rejected' ' test_cmp .git/foo .git/bar ' +test_expect_success 'push --prune' ' + mk_test heads/master heads/second heads/foo heads/bar && + git push --prune testrepo && + check_push_result $the_commit heads/master && + check_push_result $the_first_commit heads/second && + ! check_push_result $the_first_commit heads/foo heads/bar +' + +test_expect_success 'push --prune refspec' ' + mk_test tmp/master tmp/second tmp/foo tmp/bar && + git push --prune testrepo "refs/heads/*:refs/tmp/*" && + check_push_result $the_commit tmp/master && + check_push_result $the_first_commit tmp/second && + ! check_push_result $the_first_commit tmp/foo tmp/bar +' + test_done diff --git a/transport.c b/transport.c index cac0c065ff..c20267ce4f 100644 --- a/transport.c +++ b/transport.c @@ -1028,6 +1028,8 @@ int transport_push(struct transport *transport, match_flags |= MATCH_REFS_ALL; if (flags & TRANSPORT_PUSH_MIRROR) match_flags |= MATCH_REFS_MIRROR; + if (flags & TRANSPORT_PUSH_PRUNE) + match_flags |= MATCH_REFS_PRUNE; if (match_push_refs(local_refs, &remote_refs, refspec_nr, refspec, match_flags)) { diff --git a/transport.h b/transport.h index 059b3303e2..ce99ef8b7e 100644 --- a/transport.h +++ b/transport.h @@ -102,6 +102,7 @@ struct transport { #define TRANSPORT_PUSH_PORCELAIN 16 #define TRANSPORT_PUSH_SET_UPSTREAM 32 #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64 +#define TRANSPORT_PUSH_PRUNE 128 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)