/*
* Builtin "git branch"
*
* Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
* Based on git-branch.sh by Junio C Hamano.
*/
#include "cache.h"
#include "color.h"
#include "refs.h"
#include "commit.h"
#include "builtin.h"
#include "remote.h"
#include "parse-options.h"
#include "branch.h"
#include "diff.h"
#include "revision.h"
#include "string-list.h"
#include "column.h"
#include "utf8.h"
#include "wt-status.h"
static const char * const builtin_branch_usage[] = {
N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
NULL
};
#define REF_LOCAL_BRANCH 0x01
#define REF_REMOTE_BRANCH 0x02
static const char *head;
static unsigned char head_sha1[20];
static int branch_use_color = -1;
static char branch_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RESET,
GIT_COLOR_NORMAL, /* PLAIN */
GIT_COLOR_RED, /* REMOTE */
GIT_COLOR_NORMAL, /* LOCAL */
GIT_COLOR_GREEN, /* CURRENT */
GIT_COLOR_BLUE, /* UPSTREAM */
};
enum color_branch {
BRANCH_COLOR_RESET = 0,
BRANCH_COLOR_PLAIN = 1,
BRANCH_COLOR_REMOTE = 2,
BRANCH_COLOR_LOCAL = 3,
BRANCH_COLOR_CURRENT = 4,
BRANCH_COLOR_UPSTREAM = 5
};
static enum merge_filter {
NO_FILTER = 0,
SHOW_NOT_MERGED,
SHOW_MERGED
} merge_filter;
static unsigned char merge_filter_ref[20];
static struct string_list output = STRING_LIST_INIT_DUP;
static unsigned int colopts;
static int parse_branch_color_slot(const char *slot)
{
if (!strcasecmp(slot, "plain"))
return BRANCH_COLOR_PLAIN;
if (!strcasecmp(slot, "reset"))
return BRANCH_COLOR_RESET;
if (!strcasecmp(slot, "remote"))
return BRANCH_COLOR_REMOTE;
if (!strcasecmp(slot, "local"))
return BRANCH_COLOR_LOCAL;
if (!strcasecmp(slot, "current"))
return BRANCH_COLOR_CURRENT;
if (!strcasecmp(slot, "upstream"))
return BRANCH_COLOR_UPSTREAM;
return -1;
}
static int git_branch_config(const char *var, const char *value, void *cb)
{
const char *slot_name;
if (starts_with(var, "column."))
return git_column_config(var, value, "branch", &colopts);
if (!strcmp(var, "color.branch")) {
branch_use_color = git_config_colorbool(var, value);
return 0;
}
if (skip_prefix(var, "color.branch.", &slot_name)) {
int slot = parse_branch_color_slot(slot_name);
if (slot < 0)
return 0;
if (!value)
return config_error_nonbool(var);
return color_parse(value, branch_colors[slot]);
}
return git_color_default_config(var, value, cb);
}
static const char *branch_get_color(enum color_branch ix)
{
if (want_color(branch_use_color))
return branch_colors[ix];
return "";
}
static int branch_merged(int kind, const char *name,
struct commit *rev, struct commit *head_rev)
{
/*
* This checks whether the merge bases of branch and HEAD (or
* the other branch this branch builds upon) contains the
* branch, which means that the branch has already been merged
* safely to HEAD (or the other branch).
*/
struct commit *reference_rev = NULL;
const char *reference_name = NULL;
void *reference_name_to_free = NULL;
int merged;
if (kind == REF_LOCAL_BRANCH) {
struct branch *branch = branch_get(name);
unsigned char sha1[20];
if (branch &&
branch->merge &&
branch->merge[0] &&
branch->merge[0]->dst &&
(reference_name = reference_name_to_free =
resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING,
sha1, NULL)) != NULL)
reference_rev = lookup_commit_reference(sha1);
}
if (!reference_rev)
reference_rev = head_rev;
merged = in_merge_bases(rev, reference_rev);
/*
* After the safety valve is fully redefined to "check with
* upstream, if any, otherwise with HEAD", we should just
* return the result of the in_merge_bases() above without
* any of the following code, but during the transition period,
* a gentle reminder is in order.
*/
if ((head_rev != reference_rev) &&
in_merge_bases(rev, head_rev) != merged) {
if (merged)
warning(_("deleting branch '%s' that has been merged to\n"
" '%s', but not yet merged to HEAD."),
name, reference_name);
else
warning(_("not deleting branch '%s' that is not yet merged to\n"
" '%s', even though it is merged to HEAD."),
name, reference_name);
}
free(reference_name_to_free);
return merged;
}
static int check_branch_commit(const char *branchname, const char *refname,
unsigned char *sha1, struct commit *head_rev,
int kinds, int force)
{
struct commit *rev = lookup_commit_reference(sha1);
if (!rev) {
error(_("Couldn't look up commit object for '%s'"), refname);
return -1;
}
if (!force && !branch_merged(kinds, branchname, rev, head_rev)) {
error(_("The branch '%s' is not fully merged.\n"
|