1
0
mirror of https://github.com/git/git synced 2024-07-07 19:39:27 +00:00

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:
Junio C Hamano 2012-02-26 23:05:45 -08:00
commit d365a43227
7 changed files with 101 additions and 40 deletions

View File

@ -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

View File

@ -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
View File

@ -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 (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 rs + matching_refs;
else
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;

View File

@ -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 */

View File

@ -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

View File

@ -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)) {

View File

@ -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)