Merge branch 'fc/push-prune'
* fc/push-prune: push: add '--prune' option remote: refactor code into alloc_delete_ref() remote: reorganize check_pattern_match() remote: use a local variable in match_push_refs() Conflicts: builtin/push.c
This commit is contained in:
commit
d365a43227
@ -10,7 +10,7 @@ SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'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>...]]
|
||||
|
||||
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
|
||||
|
@ -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_BOOL(0, "progress", &progress, "force progress reporting"),
|
||||
OPT_BIT(0, "prune", &flags, "prune locally removed refs",
|
||||
TRANSPORT_PUSH_PRUNE),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
|
107
remote.c
107
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,
|
||||
@ -978,16 +980,20 @@ static void tail_link_ref(struct ref *ref, struct ref ***tail)
|
||||
*tail = &ref->next;
|
||||
}
|
||||
|
||||
static struct ref *alloc_delete_ref(void)
|
||||
{
|
||||
struct ref *ref = alloc_ref("(delete)");
|
||||
hashclr(ref->new_sha1);
|
||||
return ref;
|
||||
}
|
||||
|
||||
static struct ref *try_explicit_object_name(const char *name)
|
||||
{
|
||||
unsigned char sha1[20];
|
||||
struct ref *ref;
|
||||
|
||||
if (!*name) {
|
||||
ref = alloc_ref("(delete)");
|
||||
hashclr(ref->new_sha1);
|
||||
return ref;
|
||||
}
|
||||
if (!*name)
|
||||
return alloc_delete_ref();
|
||||
if (get_sha1(name, sha1))
|
||||
return NULL;
|
||||
ref = alloc_ref(name);
|
||||
@ -1110,10 +1116,11 @@ static int match_explicit_refs(struct ref *src, struct ref *dst,
|
||||
return errs;
|
||||
}
|
||||
|
||||
static const struct refspec *check_pattern_match(const struct refspec *rs,
|
||||
int rs_nr,
|
||||
const struct ref *src)
|
||||
static char *get_ref_match(const struct refspec *rs, int rs_nr, const struct ref *ref,
|
||||
int send_mirror, int direction, const struct refspec **ret_pat)
|
||||
{
|
||||
const struct refspec *pat;
|
||||
char *name;
|
||||
int i;
|
||||
int matching_refs = -1;
|
||||
for (i = 0; i < rs_nr; i++) {
|
||||
@ -1123,14 +1130,36 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rs[i].pattern && match_name_with_pattern(rs[i].src, src->name,
|
||||
NULL, NULL))
|
||||
return rs + i;
|
||||
}
|
||||
if (matching_refs != -1)
|
||||
return rs + matching_refs;
|
||||
if (rs[i].pattern) {
|
||||
const char *dst_side = rs[i].dst ? rs[i].dst : rs[i].src;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (matching_refs == -1)
|
||||
return NULL;
|
||||
|
||||
pat = rs + matching_refs;
|
||||
if (pat->matching) {
|
||||
/*
|
||||
* "matching refs"; traditionally we pushed everything
|
||||
* including refs outside refs/heads/ hierarchy, but
|
||||
* that does not make much sense these days.
|
||||
*/
|
||||
if (!send_mirror && prefixcmp(ref->name, "refs/heads/"))
|
||||
return NULL;
|
||||
name = xstrdup(ref->name);
|
||||
}
|
||||
if (ret_pat)
|
||||
*ret_pat = pat;
|
||||
return name;
|
||||
}
|
||||
|
||||
static struct ref **tail_ref(struct ref **head)
|
||||
@ -1155,9 +1184,10 @@ 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 **dst_tail = tail_ref(dst);
|
||||
struct ref *ref, **dst_tail = tail_ref(dst);
|
||||
|
||||
if (!nr_refspec) {
|
||||
nr_refspec = 1;
|
||||
@ -1167,39 +1197,23 @@ int match_push_refs(struct ref *src, struct ref **dst,
|
||||
errs = match_explicit_refs(src, *dst, &dst_tail, rs, nr_refspec);
|
||||
|
||||
/* pick the remainder */
|
||||
for ( ; src; src = src->next) {
|
||||
for (ref = src; ref; ref = ref->next) {
|
||||
struct ref *dst_peer;
|
||||
const struct refspec *pat = NULL;
|
||||
char *dst_name;
|
||||
if (src->peer_ref)
|
||||
|
||||
if (ref->peer_ref)
|
||||
continue;
|
||||
|
||||
pat = check_pattern_match(rs, nr_refspec, src);
|
||||
if (!pat)
|
||||
dst_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_SRC, &pat);
|
||||
if (!dst_name)
|
||||
continue;
|
||||
|
||||
if (pat->matching) {
|
||||
/*
|
||||
* "matching refs"; traditionally we pushed everything
|
||||
* including refs outside refs/heads/ hierarchy, but
|
||||
* that does not make much sense these days.
|
||||
*/
|
||||
if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
|
||||
continue;
|
||||
dst_name = xstrdup(src->name);
|
||||
|
||||
} else {
|
||||
const char *dst_side = pat->dst ? pat->dst : pat->src;
|
||||
if (!match_name_with_pattern(pat->src, src->name,
|
||||
dst_side, &dst_name))
|
||||
die("Didn't think it matches any more");
|
||||
}
|
||||
dst_peer = find_ref_by_name(*dst, dst_name);
|
||||
if (dst_peer) {
|
||||
if (dst_peer->peer_ref)
|
||||
/* We're already sending something to this ref. */
|
||||
goto free_name;
|
||||
|
||||
} else {
|
||||
if (pat->matching && !(send_all || send_mirror))
|
||||
/*
|
||||
@ -1211,13 +1225,30 @@ int match_push_refs(struct ref *src, struct ref **dst,
|
||||
|
||||
/* Create a new one and link it */
|
||||
dst_peer = make_linked_ref(dst_name, &dst_tail);
|
||||
hashcpy(dst_peer->new_sha1, src->new_sha1);
|
||||
hashcpy(dst_peer->new_sha1, ref->new_sha1);
|
||||
}
|
||||
dst_peer->peer_ref = copy_ref(src);
|
||||
dst_peer->peer_ref = copy_ref(ref);
|
||||
dst_peer->force = pat->force;
|
||||
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;
|
||||
|
3
remote.h
3
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 */
|
||||
|
@ -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
|
||||
|
@ -1032,6 +1032,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)) {
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user