Browse Source

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>
maint
Felipe Contreras 13 years ago committed by Junio C Hamano
parent
commit
6ddba5e241
  1. 10
      Documentation/git-push.txt
  2. 2
      builtin/push.c
  3. 31
      remote.c
  4. 3
      remote.h
  5. 16
      t/t5516-fetch-push.sh
  6. 2
      transport.c
  7. 1
      transport.h

10
Documentation/git-push.txt

@ -10,7 +10,7 @@ SYNOPSIS
-------- --------
[verse] [verse]
'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>] 'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [-v | --verbose] [-u | --set-upstream] [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
[<repository> [<refspec>...]] [<repository> [<refspec>...]]


DESCRIPTION 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 Instead of naming each ref to push, specifies that all
refs under `refs/heads/` be pushed. 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:: --mirror::
Instead of naming each ref to push, specifies that all Instead of naming each ref to push, specifies that all
refs under `refs/` (which includes but is not refs under `refs/` (which includes but is not

2
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", OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
TRANSPORT_PUSH_SET_UPSTREAM), TRANSPORT_PUSH_SET_UPSTREAM),
OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"), OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
OPT_BIT(0, "prune", &flags, "prune locally removed refs",
TRANSPORT_PUSH_PRUNE),
OPT_END() OPT_END()
}; };



31
remote.c

@ -8,6 +8,8 @@
#include "tag.h" #include "tag.h"
#include "string-list.h" #include "string-list.h"


enum map_direction { FROM_SRC, FROM_DST };

static struct refspec s_tag_refspec = { static struct refspec s_tag_refspec = {
0, 0,
1, 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, 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; const struct refspec *pat;
char *name; 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) { if (rs[i].pattern) {
const char *dst_side = rs[i].dst ? rs[i].dst : rs[i].src; 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; matching_refs = i;
break; break;
} }
@ -1177,6 +1184,7 @@ int match_push_refs(struct ref *src, struct ref **dst,
struct refspec *rs; struct refspec *rs;
int send_all = flags & MATCH_REFS_ALL; int send_all = flags & MATCH_REFS_ALL;
int send_mirror = flags & MATCH_REFS_MIRROR; int send_mirror = flags & MATCH_REFS_MIRROR;
int send_prune = flags & MATCH_REFS_PRUNE;
int errs; int errs;
static const char *default_refspec[] = { ":", NULL }; static const char *default_refspec[] = { ":", NULL };
struct ref *ref, **dst_tail = tail_ref(dst); 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) if (ref->peer_ref)
continue; 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) if (!dst_name)
continue; continue;


@ -1224,6 +1232,23 @@ int match_push_refs(struct ref *src, struct ref **dst,
free_name: free_name:
free(dst_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) if (errs)
return -1; return -1;
return 0; return 0;

3
remote.h

@ -145,7 +145,8 @@ int branch_merge_matches(struct branch *, int n, const char *);
enum match_refs_flags { enum match_refs_flags {
MATCH_REFS_NONE = 0, MATCH_REFS_NONE = 0,
MATCH_REFS_ALL = (1 << 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 */ /* Reporting of tracking info */

16
t/t5516-fetch-push.sh

@ -979,4 +979,20 @@ test_expect_success 'push --porcelain --dry-run rejected' '
test_cmp .git/foo .git/bar 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 test_done

2
transport.c

@ -1028,6 +1028,8 @@ int transport_push(struct transport *transport,
match_flags |= MATCH_REFS_ALL; match_flags |= MATCH_REFS_ALL;
if (flags & TRANSPORT_PUSH_MIRROR) if (flags & TRANSPORT_PUSH_MIRROR)
match_flags |= MATCH_REFS_MIRROR; match_flags |= MATCH_REFS_MIRROR;
if (flags & TRANSPORT_PUSH_PRUNE)
match_flags |= MATCH_REFS_PRUNE;


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

1
transport.h

@ -102,6 +102,7 @@ struct transport {
#define TRANSPORT_PUSH_PORCELAIN 16 #define TRANSPORT_PUSH_PORCELAIN 16
#define TRANSPORT_PUSH_SET_UPSTREAM 32 #define TRANSPORT_PUSH_SET_UPSTREAM 32
#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64 #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
#define TRANSPORT_PUSH_PRUNE 128


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



Loading…
Cancel
Save