git/builtin/branch.c

993 lines
30 KiB
C
Raw Normal View History

/*
* Builtin "git branch"
*
* Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
* Based on git-branch.sh by Junio C Hamano.
*/
#include "builtin.h"
#include "config.h"
#include "color.h"
#include "editor.h"
#include "environment.h"
#include "refs.h"
#include "commit.h"
#include "gettext.h"
#include "object-name.h"
#include "remote.h"
#include "parse-options.h"
#include "branch.h"
builtin-branch.c: optimize --merged and --no-merged "git branch --no-merged $commit" used to compute the merge base between the tip of each and every branch with the named $commit, but this was wasteful when you have many branches. Inside append_ref() we literally ran has_commit() between the tip of the branch and the merge_filter_ref. Instead, we can let the revision machinery traverse the history as if we are running: $ git rev-list --branches --not $commit by queueing the tips of branches we encounter as positive refs (this mimicks the "--branches" option in the above command line) and then appending the merge_filter_ref commit as a negative one, and finally calling prepare_revision_walk() to limit the list.. After the traversal is done, branch tips that are reachable from $commit are painted UNINTERESTING; they are already fully contained in $commit (i.e. --merged). Tips that are not painted UNINTERESTING still have commits that are not reachable from $commit, thus "--no-merged" will show them. With an artificial repository that has "master" and 1000 test-$i branches where they were created by "git branch test-$i master~$i": (with patch) $ /usr/bin/time git-branch --no-merged master >/dev/null 0.12user 0.02system 0:00.15elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+1588minor)pagefaults 0swaps $ /usr/bin/time git-branch --no-merged test-200 >/dev/null 0.15user 0.03system 0:00.18elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+1711minor)pagefaults 0swaps (without patch) $ /usr/bin/time git-branch --no-merged master >/dev/null 0.69user 0.03system 0:00.72elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+2229minor)pagefaults 0swaps $ /usr/bin/time git-branch --no-merged test-200 >/dev/null 0.58user 0.03system 0:00.61elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+2248minor)pagefaults 0swaps Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-07-24 06:13:41 +08:00
#include "diff.h"
#include "path.h"
builtin-branch.c: optimize --merged and --no-merged "git branch --no-merged $commit" used to compute the merge base between the tip of each and every branch with the named $commit, but this was wasteful when you have many branches. Inside append_ref() we literally ran has_commit() between the tip of the branch and the merge_filter_ref. Instead, we can let the revision machinery traverse the history as if we are running: $ git rev-list --branches --not $commit by queueing the tips of branches we encounter as positive refs (this mimicks the "--branches" option in the above command line) and then appending the merge_filter_ref commit as a negative one, and finally calling prepare_revision_walk() to limit the list.. After the traversal is done, branch tips that are reachable from $commit are painted UNINTERESTING; they are already fully contained in $commit (i.e. --merged). Tips that are not painted UNINTERESTING still have commits that are not reachable from $commit, thus "--no-merged" will show them. With an artificial repository that has "master" and 1000 test-$i branches where they were created by "git branch test-$i master~$i": (with patch) $ /usr/bin/time git-branch --no-merged master >/dev/null 0.12user 0.02system 0:00.15elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+1588minor)pagefaults 0swaps $ /usr/bin/time git-branch --no-merged test-200 >/dev/null 0.15user 0.03system 0:00.18elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+1711minor)pagefaults 0swaps (without patch) $ /usr/bin/time git-branch --no-merged master >/dev/null 0.69user 0.03system 0:00.72elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+2229minor)pagefaults 0swaps $ /usr/bin/time git-branch --no-merged test-200 >/dev/null 0.58user 0.03system 0:00.61elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+2248minor)pagefaults 0swaps Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-07-24 06:13:41 +08:00
#include "revision.h"
#include "string-list.h"
#include "column.h"
#include "utf8.h"
#include "wt-status.h"
#include "ref-filter.h"
#include "worktree.h"
#include "help.h"
#include "commit-reach.h"
static const char * const builtin_branch_usage[] = {
N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"),
branch: add --recurse-submodules option for branch creation To improve the submodules UX, we would like to teach Git to handle branches in submodules. Start this process by teaching "git branch" the --recurse-submodules option so that "git branch --recurse-submodules topic" will create the `topic` branch in the superproject and its submodules. Although this commit does not introduce breaking changes, it does not work well with existing --recurse-submodules commands because "git branch --recurse-submodules" writes to the submodule ref store, but most commands only consider the superproject gitlink and ignore the submodule ref store. For example, "git checkout --recurse-submodules" will check out the commits in the superproject gitlinks (and put the submodules in detached HEAD) instead of checking out the submodule branches. Because of this, this commit introduces a new configuration value, `submodule.propagateBranches`. The plan is for Git commands to prioritize submodule ref store information over superproject gitlinks if this value is true. Because "git branch --recurse-submodules" writes to submodule ref stores, for the sake of clarity, it will not function unless this configuration value is set. This commit also includes changes that support working with submodules from a superproject commit because "branch --recurse-submodules" (and future commands) need to read .gitmodules and gitlinks from the superproject commit, but submodules are typically read from the filesystem's .gitmodules and the index's gitlinks. These changes are: * add a submodules_of_tree() helper that gives the relevant information of an in-tree submodule (e.g. path and oid) and initializes the repository * add is_tree_submodule_active() by adding a treeish_name parameter to is_submodule_active() * add the "submoduleNotUpdated" advice to advise users to update the submodules in their trees Incidentally, fix an incorrect usage string that combined the 'list' usage of git branch (-l) with the 'create' usage; this string has been incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use parse_options., 2007-10-07). Helped-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Glen Choo <chooglen@google.com> Reviewed-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-01-29 08:04:45 +08:00
N_("git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]"),
N_("git branch [<options>] [-l] [<pattern>...]"),
N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
branch: add a --copy (-c) option to go with --move (-m) Add the ability to --copy a branch and its reflog and configuration, this uses the same underlying machinery as the --move (-m) option except the reflog and configuration is copied instead of being moved. This is useful for e.g. copying a topic branch to a new version, e.g. work to work-2 after submitting the work topic to the list, while preserving all the tracking info and other configuration that goes with the branch, and unlike --move keeping the other already-submitted branch around for reference. Like --move, when the source branch is the currently checked out branch the HEAD is moved to the destination branch. In the case of --move we don't really have a choice (other than remaining on a detached HEAD) and in order to keep the functionality consistent, we are doing it in similar way for --copy too. The most common usage of this feature is expected to be moving to a new topic branch which is a copy of the current one, in that case moving to the target branch is what the user wants, and doesn't unexpectedly behave differently than --move would. One outstanding caveat of this implementation is that: git checkout maint && git checkout master && git branch -c topic && git checkout - Will check out 'maint' instead of 'master'. This is because the @{-N} feature (or its -1 shorthand "-") relies on HEAD reflogs created by the checkout command, so in this case we'll checkout maint instead of master, as the user might expect. What to do about that is left to a future change. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Sahil Dua <sahildua2305@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-06-19 05:19:16 +08:00
N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"),
N_("git branch [<options>] [-r | -a] [--points-at]"),
N_("git branch [<options>] [-r | -a] [--format]"),
NULL
};
static const char *head;
static struct object_id head_oid;
branch: add --recurse-submodules option for branch creation To improve the submodules UX, we would like to teach Git to handle branches in submodules. Start this process by teaching "git branch" the --recurse-submodules option so that "git branch --recurse-submodules topic" will create the `topic` branch in the superproject and its submodules. Although this commit does not introduce breaking changes, it does not work well with existing --recurse-submodules commands because "git branch --recurse-submodules" writes to the submodule ref store, but most commands only consider the superproject gitlink and ignore the submodule ref store. For example, "git checkout --recurse-submodules" will check out the commits in the superproject gitlinks (and put the submodules in detached HEAD) instead of checking out the submodule branches. Because of this, this commit introduces a new configuration value, `submodule.propagateBranches`. The plan is for Git commands to prioritize submodule ref store information over superproject gitlinks if this value is true. Because "git branch --recurse-submodules" writes to submodule ref stores, for the sake of clarity, it will not function unless this configuration value is set. This commit also includes changes that support working with submodules from a superproject commit because "branch --recurse-submodules" (and future commands) need to read .gitmodules and gitlinks from the superproject commit, but submodules are typically read from the filesystem's .gitmodules and the index's gitlinks. These changes are: * add a submodules_of_tree() helper that gives the relevant information of an in-tree submodule (e.g. path and oid) and initializes the repository * add is_tree_submodule_active() by adding a treeish_name parameter to is_submodule_active() * add the "submoduleNotUpdated" advice to advise users to update the submodules in their trees Incidentally, fix an incorrect usage string that combined the 'list' usage of git branch (-l) with the 'create' usage; this string has been incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use parse_options., 2007-10-07). Helped-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Glen Choo <chooglen@google.com> Reviewed-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-01-29 08:04:45 +08:00
static int recurse_submodules = 0;
static int submodule_propagate_branches = 0;
static int branch_use_color = -1;
static char branch_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RESET,
2017-01-10 16:49:52 +08:00
GIT_COLOR_NORMAL, /* PLAIN */
GIT_COLOR_RED, /* REMOTE */
GIT_COLOR_NORMAL, /* LOCAL */
GIT_COLOR_GREEN, /* CURRENT */
GIT_COLOR_BLUE, /* UPSTREAM */
GIT_COLOR_CYAN, /* WORKTREE */
};
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,
BRANCH_COLOR_WORKTREE = 6
};
static const char *color_branch_slots[] = {
[BRANCH_COLOR_RESET] = "reset",
[BRANCH_COLOR_PLAIN] = "plain",
[BRANCH_COLOR_REMOTE] = "remote",
[BRANCH_COLOR_LOCAL] = "local",
[BRANCH_COLOR_CURRENT] = "current",
[BRANCH_COLOR_UPSTREAM] = "upstream",
[BRANCH_COLOR_WORKTREE] = "worktree",
};
static struct string_list output = STRING_LIST_INIT_DUP;
static unsigned int colopts;
define_list_config_array(color_branch_slots);
config: add ctx arg to config_fn_t Add a new "const struct config_context *ctx" arg to config_fn_t to hold additional information about the config iteration operation. config_context has a "struct key_value_info kvi" member that holds metadata about the config source being read (e.g. what kind of config source it is, the filename, etc). In this series, we're only interested in .kvi, so we could have just used "struct key_value_info" as an arg, but config_context makes it possible to add/adjust members in the future without changing the config_fn_t signature. We could also consider other ways of organizing the args (e.g. moving the config name and value into config_context or key_value_info), but in my experiments, the incremental benefit doesn't justify the added complexity (e.g. a config_fn_t will sometimes invoke another config_fn_t but with a different config value). In subsequent commits, the .kvi member will replace the global "struct config_reader" in config.c, making config iteration a global-free operation. It requires much more work for the machinery to provide meaningful values of .kvi, so for now, merely change the signature and call sites, pass NULL as a placeholder value, and don't rely on the arg in any meaningful way. Most of the changes are performed by contrib/coccinelle/config_fn_ctx.pending.cocci, which, for every config_fn_t: - Modifies the signature to accept "const struct config_context *ctx" - Passes "ctx" to any inner config_fn_t, if needed - Adds UNUSED attributes to "ctx", if needed Most config_fn_t instances are easily identified by seeing if they are called by the various config functions. Most of the remaining ones are manually named in the .cocci patch. Manual cleanups are still needed, but the majority of it is trivial; it's either adjusting config_fn_t that the .cocci patch didn't catch, or adding forward declarations of "struct config_context ctx" to make the signatures make sense. The non-trivial changes are in cases where we are invoking a config_fn_t outside of config machinery, and we now need to decide what value of "ctx" to pass. These cases are: - trace2/tr2_cfg.c:tr2_cfg_set_fl() This is indirectly called by git_config_set() so that the trace2 machinery can notice the new config values and update its settings using the tr2 config parsing function, i.e. tr2_cfg_cb(). - builtin/checkout.c:checkout_main() This calls git_xmerge_config() as a shorthand for parsing a CLI arg. This might be worth refactoring away in the future, since git_xmerge_config() can call git_default_config(), which can do much more than just parsing. Handle them by creating a KVI_INIT macro that initializes "struct key_value_info" to a reasonable default, and use that to construct the "ctx" arg. Signed-off-by: Glen Choo <chooglen@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-06-29 03:26:22 +08:00
static int git_branch_config(const char *var, const char *value,
const struct config_context *ctx, void *cb)
{
const char *slot_name;
if (!strcmp(var, "branch.sort")) {
if (!value)
return config_error_nonbool(var);
for-each-ref: delay parsing of --sort=<atom> options The for-each-ref family of commands invoke parsers immediately when it sees each --sort=<atom> option, and die before even seeing the other options on the command line when the <atom> is unrecognised. Instead, accumulate them in a string list, and have them parsed into a ref_sorting structure after the command line parsing is done. As a consequence, "git branch --sort=bogus -h" used to fail to give the brief help, which arguably may have been a feature, now does so, which is more consistent with how other options work. The patch is smaller than the actual extent of the "damage" to the codebase, thanks to the fact that the original code consistently used OPT_REF_SORT() macro to handle command line options. We only needed to replace the variable used for the list, and implementation of the callback function used in the macro. The old rule was for the users of the API to: - Declare ref_sorting and ref_sorting_tail variables; - OPT_REF_SORT() macro will instantiate ref_sorting instance (which may barf and die) and append it to the tail; - Append to the tail each ref_sorting read from the configuration by parsing in the config callback (which may barf and die); - See if ref_sorting is null and use ref_sorting_default() instead. Now the rule is not all that different but is simpler: - Declare ref_sorting_options string list. - OPT_REF_SORT() macro will append it to the string list; - Append to the string list the sort key read from the configuration; - call ref_sorting_options() to turn the string list to ref_sorting structure (which also deals with the default value). As side effects, this change also cleans up a few issues: - 95be717c (parse_opt_ref_sorting: always use with NONEG flag, 2019-03-20) muses that "git for-each-ref --no-sort" should simply clear the sort keys accumulated so far; it now does. - The implementation detail of "struct ref_sorting" and the helper function parse_ref_sorting() can now be private to the ref-filter API implementation. - If you set branch.sort to a bogus value, the any "git branch" invocation, not only the listing mode, would abort with the original code; now it doesn't Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-10-21 03:23:53 +08:00
string_list_append(cb, value);
return 0;
}
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 = LOOKUP_CONFIG(color_branch_slots, slot_name);
2009-12-12 20:25:24 +08:00
if (slot < 0)
return 0;
if (!value)
return config_error_nonbool(var);
return color_parse(value, branch_colors[slot]);
}
branch: add --recurse-submodules option for branch creation To improve the submodules UX, we would like to teach Git to handle branches in submodules. Start this process by teaching "git branch" the --recurse-submodules option so that "git branch --recurse-submodules topic" will create the `topic` branch in the superproject and its submodules. Although this commit does not introduce breaking changes, it does not work well with existing --recurse-submodules commands because "git branch --recurse-submodules" writes to the submodule ref store, but most commands only consider the superproject gitlink and ignore the submodule ref store. For example, "git checkout --recurse-submodules" will check out the commits in the superproject gitlinks (and put the submodules in detached HEAD) instead of checking out the submodule branches. Because of this, this commit introduces a new configuration value, `submodule.propagateBranches`. The plan is for Git commands to prioritize submodule ref store information over superproject gitlinks if this value is true. Because "git branch --recurse-submodules" writes to submodule ref stores, for the sake of clarity, it will not function unless this configuration value is set. This commit also includes changes that support working with submodules from a superproject commit because "branch --recurse-submodules" (and future commands) need to read .gitmodules and gitlinks from the superproject commit, but submodules are typically read from the filesystem's .gitmodules and the index's gitlinks. These changes are: * add a submodules_of_tree() helper that gives the relevant information of an in-tree submodule (e.g. path and oid) and initializes the repository * add is_tree_submodule_active() by adding a treeish_name parameter to is_submodule_active() * add the "submoduleNotUpdated" advice to advise users to update the submodules in their trees Incidentally, fix an incorrect usage string that combined the 'list' usage of git branch (-l) with the 'create' usage; this string has been incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use parse_options., 2007-10-07). Helped-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Glen Choo <chooglen@google.com> Reviewed-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-01-29 08:04:45 +08:00
if (!strcmp(var, "submodule.recurse")) {
recurse_submodules = git_config_bool(var, value);
return 0;
}
if (!strcasecmp(var, "submodule.propagateBranches")) {
submodule_propagate_branches = git_config_bool(var, value);
return 0;
}
if (git_color_config(var, value, cb) < 0)
return -1;
config: add ctx arg to config_fn_t Add a new "const struct config_context *ctx" arg to config_fn_t to hold additional information about the config iteration operation. config_context has a "struct key_value_info kvi" member that holds metadata about the config source being read (e.g. what kind of config source it is, the filename, etc). In this series, we're only interested in .kvi, so we could have just used "struct key_value_info" as an arg, but config_context makes it possible to add/adjust members in the future without changing the config_fn_t signature. We could also consider other ways of organizing the args (e.g. moving the config name and value into config_context or key_value_info), but in my experiments, the incremental benefit doesn't justify the added complexity (e.g. a config_fn_t will sometimes invoke another config_fn_t but with a different config value). In subsequent commits, the .kvi member will replace the global "struct config_reader" in config.c, making config iteration a global-free operation. It requires much more work for the machinery to provide meaningful values of .kvi, so for now, merely change the signature and call sites, pass NULL as a placeholder value, and don't rely on the arg in any meaningful way. Most of the changes are performed by contrib/coccinelle/config_fn_ctx.pending.cocci, which, for every config_fn_t: - Modifies the signature to accept "const struct config_context *ctx" - Passes "ctx" to any inner config_fn_t, if needed - Adds UNUSED attributes to "ctx", if needed Most config_fn_t instances are easily identified by seeing if they are called by the various config functions. Most of the remaining ones are manually named in the .cocci patch. Manual cleanups are still needed, but the majority of it is trivial; it's either adjusting config_fn_t that the .cocci patch didn't catch, or adding forward declarations of "struct config_context ctx" to make the signatures make sense. The non-trivial changes are in cases where we are invoking a config_fn_t outside of config machinery, and we now need to decide what value of "ctx" to pass. These cases are: - trace2/tr2_cfg.c:tr2_cfg_set_fl() This is indirectly called by git_config_set() so that the trace2 machinery can notice the new config values and update its settings using the tr2 config parsing function, i.e. tr2_cfg_cb(). - builtin/checkout.c:checkout_main() This calls git_xmerge_config() as a shorthand for parsing a CLI arg. This might be worth refactoring away in the future, since git_xmerge_config() can call git_default_config(), which can do much more than just parsing. Handle them by creating a KVI_INIT macro that initializes "struct key_value_info" to a reasonable default, and use that to construct the "ctx" arg. Signed-off-by: Glen Choo <chooglen@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-06-29 03:26:22 +08:00
return git_default_config(var, value, ctx, cb);
}
static const char *branch_get_color(enum color_branch ix)
{
color: delay auto-color decision until point of use When we read a color value either from a config file or from the command line, we use git_config_colorbool to convert it from the tristate always/never/auto into a single yes/no boolean value. This has some timing implications with respect to starting a pager. If we start (or decide not to start) the pager before checking the colorbool, everything is fine. Either isatty(1) will give us the right information, or we will properly check for pager_in_use(). However, if we decide to start a pager after we have checked the colorbool, things are not so simple. If stdout is a tty, then we will have already decided to use color. However, the user may also have configured color.pager not to use color with the pager. In this case, we need to actually turn off color. Unfortunately, the pager code has no idea which color variables were turned on (and there are many of them throughout the code, and they may even have been manipulated after the colorbool selection by something like "--color" on the command line). This bug can be seen any time a pager is started after config and command line options are checked. This has affected "git diff" since 89d07f7 (diff: don't run pager if user asked for a diff style exit code, 2007-08-12). It has also affect the log family since 1fda91b (Fix 'git log' early pager startup error case, 2010-08-24). This patch splits the notion of parsing a colorbool and actually checking the configuration. The "use_color" variables now have an additional possible value, GIT_COLOR_AUTO. Users of the variable should use the new "want_color()" wrapper, which will lazily determine and cache the auto-color decision. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-18 13:04:23 +08:00
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 == FILTER_REFS_BRANCHES) {
struct branch *branch = branch_get(name);
const char *upstream = branch_get_upstream(branch, NULL);
struct object_id oid;
if (upstream &&
(reference_name = reference_name_to_free =
resolve_refdup(upstream, RESOLVE_REF_READING,
&oid, NULL)) != NULL)
reference_rev = lookup_commit_reference(the_repository,
&oid);
}
if (!reference_rev)
reference_rev = head_rev;
merged = reference_rev ? repo_in_merge_bases(the_repository, rev,
reference_rev) : 0;
/*
* After the safety valve is fully redefined to "check with
* upstream, if any, otherwise with HEAD", we should just
* return the result of the repo_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) &&
(head_rev ? repo_in_merge_bases(the_repository, rev, head_rev) : 0) != 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,
const struct object_id *oid, struct commit *head_rev,
int kinds, int force)
{
struct commit *rev = lookup_commit_reference(the_repository, oid);
if (!force && !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"
"If you are sure you want to delete it, "
"run 'git branch -D %s'"), branchname, branchname);
return -1;
}
return 0;
}
static void delete_branch_config(const char *branchname)
{
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "branch.%s", branchname);
if (git_config_rename_section(buf.buf, NULL) < 0)
warning(_("update of config-file failed"));
strbuf_release(&buf);
}
static int delete_branches(int argc, const char **argv, int force, int kinds,
int quiet)
{
struct commit *head_rev = NULL;
struct object_id oid;
char *name = NULL;
const char *fmt;
int i;
int ret = 0;
int remote_branch = 0;
struct strbuf bname = STRBUF_INIT;
unsigned allowed_interpret;
struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
struct string_list_item *item;
int branch_name_pos;
const char *fmt_remotes = "refs/remotes/%s";
switch (kinds) {
case FILTER_REFS_REMOTES:
fmt = fmt_remotes;
/* For subsequent UI messages */
remote_branch = 1;
allowed_interpret = INTERPRET_BRANCH_REMOTE;
force = 1;
break;
case FILTER_REFS_BRANCHES:
fmt = "refs/heads/%s";
allowed_interpret = INTERPRET_BRANCH_LOCAL;
break;
default:
die(_("cannot use -a with -d"));
}
branch_name_pos = strcspn(fmt, "%");
branch: gracefully handle '-d' on orphan HEAD When deleting a branch, "git branch -d" has a safety check that ensures the branch is merged to its upstream (if any), or to HEAD. To do that, naturally we try to resolve HEAD to a commit object. If we're on an orphan branch (i.e., HEAD points to a branch that does not yet exist), that will fail, and we'll bail with an error: $ git branch -d to-delete fatal: Couldn't look up commit object for HEAD This usually isn't that big of a deal. The deletion would fail anyway, since the branch isn't merged to HEAD, and you'd need to use "-D" (or "-f"). And doing so skips the HEAD resolution, courtesy of 67affd5173 (git-branch -D: make it work even when on a yet-to-be-born branch, 2006-11-24). But there are still two problems: 1. The error message isn't very helpful. We should give the usual "not fully merged" message, which points the user at "branch -D". That was a problem even back in 67affd5173. 2. Even without a HEAD, these days it's still possible for the deletion to succeed. After 67affd5173, commit 99c419c915 (branch -d: base the "already-merged" safety on the branch it merges with, 2009-12-29) made it OK to delete a branch if it is merged to its upstream. We can fix both by removing the die() in delete_branches() completely, leaving head_rev NULL in this case. It's tempting to stop there, as it appears at first glance that the rest of the code does the right thing with a NULL. But sadly, it's not quite true. We end up feeding the NULL to repo_is_descendant_of(). In the traditional code path there, we call repo_in_merge_bases_many(). It feeds the NULL to repo_parse_commit(), which is smart enough to return an error, and we immediately return "no, it's not a descendant". But there's an alternate code path: if we have a commit graph with generation numbers, we end up in can_all_from_reach(), which does eventually try to set a flag on the NULL commit and segfaults. So instead, we'll teach the local branch_merged() helper to treat a NULL as "not merged". This would be a little more elegant in in_merge_bases() itself, but that function is called in a lot of places, and it's not clear that quietly returning "not merged" is the right thing everywhere (I'd expect in many cases, feeding a NULL is a sign of a bug). There are four tests here: a. The first one confirms that deletion succeeds with an orphaned HEAD when the branch is merged to its upstream. This is case (2) above. b. Same, but with commit graphs enabled. Even if it is merged to upstream, we still check head_rev so that we can say "deleting because it's merged to upstream, even though it's not merged to HEAD". Without the second hunk in branch_merged(), this test would segfault in can_all_from_reach(). c. The third one confirms that we correctly say "not merged to HEAD" when we can't resolve HEAD, and reject the deletion. d. Same, but with commit graphs enabled. Without the first hunk in branch_merged(), this one would segfault. Reported-by: Martin von Zweigbergk <martinvonz@google.com> Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Taylor Blau <me@ttaylorr.com>
2022-11-02 13:27:49 +08:00
if (!force)
head_rev = lookup_commit_reference(the_repository, &head_oid);
for (i = 0; i < argc; i++, strbuf_reset(&bname)) {
char *target = NULL;
int flags = 0;
strbuf_branchname(&bname, argv[i], allowed_interpret);
Avoid unnecessary "if-before-free" tests. This change removes all obvious useless if-before-free tests. E.g., it replaces code like this: if (some_expression) free (some_expression); with the now-equivalent: free (some_expression); It is equivalent not just because POSIX has required free(NULL) to work for a long time, but simply because it has worked for so long that no reasonable porting target fails the test. Here's some evidence from nearly 1.5 years ago: http://www.winehq.org/pipermail/wine-patches/2006-October/031544.html FYI, the change below was prepared by running the following: git ls-files -z | xargs -0 \ perl -0x3b -pi -e \ 's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*NULL)?\s*\)\s+(free\s*\(\s*\1\s*\))/$2/s' Note however, that it doesn't handle brace-enclosed blocks like "if (x) { free (x); }". But that's ok, since there were none like that in git sources. Beware: if you do use the above snippet, note that it can produce syntactically invalid C code. That happens when the affected "if"-statement has a matching "else". E.g., it would transform this if (x) free (x); else foo (); into this: free (x); else foo (); There were none of those here, either. If you're interested in automating detection of the useless tests, you might like the useless-if-before-free script in gnulib: [it *does* detect brace-enclosed free statements, and has a --name=S option to make it detect free-like functions with different names] http://git.sv.gnu.org/gitweb/?p=gnulib.git;a=blob;f=build-aux/useless-if-before-free Addendum: Remove one more (in imap-send.c), spotted by Jean-Luc Herren <jlh@gmx.ch>. Signed-off-by: Jim Meyering <meyering@redhat.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-02-01 01:26:32 +08:00
free(name);
name = mkpathdup(fmt, bname.buf);
if (kinds == FILTER_REFS_BRANCHES) {
const char *path;
if ((path = branch_checked_out(name))) {
error(_("cannot delete branch '%s' "
"used by worktree at '%s'"),
bname.buf, path);
ret = 1;
continue;
}
}
target = resolve_refdup(name,
RESOLVE_REF_READING
| RESOLVE_REF_NO_RECURSE
| RESOLVE_REF_ALLOW_BAD_NAME,
&oid, &flags);
if (!target) {
if (remote_branch) {
error(_("remote-tracking branch '%s' not found"), bname.buf);
} else {
char *virtual_name = mkpathdup(fmt_remotes, bname.buf);
char *virtual_target = resolve_refdup(virtual_name,
RESOLVE_REF_READING
| RESOLVE_REF_NO_RECURSE
| RESOLVE_REF_ALLOW_BAD_NAME,
&oid, &flags);
FREE_AND_NULL(virtual_name);
if (virtual_target)
error(_("branch '%s' not found.\n"
"Did you forget --remote?"),
bname.buf);
else
error(_("branch '%s' not found"), bname.buf);
FREE_AND_NULL(virtual_target);
}
ret = 1;
continue;
}
refs.c: allow listing and deleting badly named refs We currently do not handle badly named refs well: $ cp .git/refs/heads/master .git/refs/heads/master.....@\*@\\. $ git branch fatal: Reference has invalid format: 'refs/heads/master.....@*@\.' $ git branch -D master.....@\*@\\. error: branch 'master.....@*@\.' not found. Users cannot recover from a badly named ref without manually finding and deleting the loose ref file or appropriate line in packed-refs. Making that easier will make it easier to tweak the ref naming rules in the future, for example to forbid shell metacharacters like '`' and '"', without putting people in a state that is hard to get out of. So allow "branch --list" to show these refs and allow "branch -d/-D" and "update-ref -d" to delete them. Other commands (for example to rename refs) will continue to not handle these refs but can be changed in later patches. Details: In resolving functions, refuse to resolve refs that don't pass the git-check-ref-format(1) check unless the new RESOLVE_REF_ALLOW_BAD_NAME flag is passed. Even with RESOLVE_REF_ALLOW_BAD_NAME, refuse to resolve refs that escape the refs/ directory and do not match the pattern [A-Z_]* (think "HEAD" and "MERGE_HEAD"). In locking functions, refuse to act on badly named refs unless they are being deleted and either are in the refs/ directory or match [A-Z_]*. Just like other invalid refs, flag resolved, badly named refs with the REF_ISBROKEN flag, treat them as resolving to null_sha1, and skip them in all iteration functions except for for_each_rawref. Flag badly named refs (but not symrefs pointing to badly named refs) with a REF_BAD_NAME flag to make it easier for future callers to notice and handle them specially. For example, in a later patch for-each-ref will use this flag to detect refs whose names can confuse callers parsing for-each-ref output. In the transaction API, refuse to create or update badly named refs, but allow deleting them (unless they try to escape refs/ and don't match [A-Z_]*). Signed-off-by: Ronnie Sahlberg <sahlberg@google.com> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-09-04 02:45:43 +08:00
if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&
check_branch_commit(bname.buf, name, &oid, head_rev, kinds,
force)) {
ret = 1;
goto next;
}
item = string_list_append(&refs_to_delete, name);
item->util = xstrdup((flags & REF_ISBROKEN) ? "broken"
: (flags & REF_ISSYMREF) ? target
: repo_find_unique_abbrev(the_repository, &oid, DEFAULT_ABBREV));
next:
free(target);
}
if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF))
ret = 1;
for_each_string_list_item(item, &refs_to_delete) {
char *describe_ref = item->util;
char *name = item->string;
if (!ref_exists(name)) {
char *refname = name + branch_name_pos;
if (!quiet)
printf(remote_branch
? _("Deleted remote-tracking branch %s (was %s).\n")
: _("Deleted branch %s (was %s).\n"),
name + branch_name_pos, describe_ref);
delete_branch_config(refname);
}
free(describe_ref);
}
string_list_clear(&refs_to_delete, 0);
Avoid unnecessary "if-before-free" tests. This change removes all obvious useless if-before-free tests. E.g., it replaces code like this: if (some_expression) free (some_expression); with the now-equivalent: free (some_expression); It is equivalent not just because POSIX has required free(NULL) to work for a long time, but simply because it has worked for so long that no reasonable porting target fails the test. Here's some evidence from nearly 1.5 years ago: http://www.winehq.org/pipermail/wine-patches/2006-October/031544.html FYI, the change below was prepared by running the following: git ls-files -z | xargs -0 \ perl -0x3b -pi -e \ 's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*NULL)?\s*\)\s+(free\s*\(\s*\1\s*\))/$2/s' Note however, that it doesn't handle brace-enclosed blocks like "if (x) { free (x); }". But that's ok, since there were none like that in git sources. Beware: if you do use the above snippet, note that it can produce syntactically invalid C code. That happens when the affected "if"-statement has a matching "else". E.g., it would transform this if (x) free (x); else foo (); into this: free (x); else foo (); There were none of those here, either. If you're interested in automating detection of the useless tests, you might like the useless-if-before-free script in gnulib: [it *does* detect brace-enclosed free statements, and has a --name=S option to make it detect free-like functions with different names] http://git.sv.gnu.org/gitweb/?p=gnulib.git;a=blob;f=build-aux/useless-if-before-free Addendum: Remove one more (in imap-send.c), spotted by Jean-Luc Herren <jlh@gmx.ch>. Signed-off-by: Jim Meyering <meyering@redhat.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2008-02-01 01:26:32 +08:00
free(name);
strbuf_release(&bname);
return ret;
}
2017-01-10 16:49:52 +08:00
static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
{
2017-01-10 16:49:52 +08:00
int i, max = 0;
for (i = 0; i < refs->nr; i++) {
struct ref_array_item *it = refs->items[i];
const char *desc = it->refname;
int w;
2017-01-10 16:49:52 +08:00
skip_prefix(it->refname, "refs/heads/", &desc);
skip_prefix(it->refname, "refs/remotes/", &desc);
if (it->kind == FILTER_REFS_DETACHED_HEAD) {
char *head_desc = get_head_description();
w = utf8_strwidth(head_desc);
free(head_desc);
} else
w = utf8_strwidth(desc);
2017-01-10 16:49:52 +08:00
if (it->kind == FILTER_REFS_REMOTES)
w += remote_bonus;
if (w > max)
max = w;
}
2017-01-10 16:49:52 +08:00
return max;
}
2017-01-10 16:49:52 +08:00
static const char *quote_literal_for_format(const char *s)
{
2017-01-10 16:49:52 +08:00
static struct strbuf buf = STRBUF_INIT;
strbuf_reset(&buf);
while (strbuf_expand_step(&buf, &s))
strbuf_addstr(&buf, "%%");
2017-01-10 16:49:52 +08:00
return buf.buf;
}
2017-01-10 16:49:52 +08:00
static char *build_format(struct ref_filter *filter, int maxwidth, const char *remote_prefix)
{
2017-01-10 16:49:52 +08:00
struct strbuf fmt = STRBUF_INIT;
struct strbuf local = STRBUF_INIT;
struct strbuf remote = STRBUF_INIT;
strbuf_addf(&local, "%%(if)%%(HEAD)%%(then)* %s%%(else)%%(if)%%(worktreepath)%%(then)+ %s%%(else) %s%%(end)%%(end)",
branch_get_color(BRANCH_COLOR_CURRENT),
branch_get_color(BRANCH_COLOR_WORKTREE),
branch_get_color(BRANCH_COLOR_LOCAL));
strbuf_addf(&remote, " %s",
branch_get_color(BRANCH_COLOR_REMOTE));
if (filter->verbose) {
struct strbuf obname = STRBUF_INIT;
if (filter->abbrev < 0)
strbuf_addf(&obname, "%%(objectname:short)");
else if (!filter->abbrev)
strbuf_addf(&obname, "%%(objectname)");
else
strbuf_addf(&obname, "%%(objectname:short=%d)", filter->abbrev);
2017-01-10 16:49:52 +08:00
strbuf_addf(&local, "%%(align:%d,left)%%(refname:lstrip=2)%%(end)", maxwidth);
strbuf_addstr(&local, branch_get_color(BRANCH_COLOR_RESET));
strbuf_addf(&local, " %s ", obname.buf);
2017-01-10 16:49:52 +08:00
if (filter->verbose > 1)
{
strbuf_addf(&local, "%%(if:notequals=*)%%(HEAD)%%(then)%%(if)%%(worktreepath)%%(then)(%s%%(worktreepath)%s) %%(end)%%(end)",
branch_get_color(BRANCH_COLOR_WORKTREE), branch_get_color(BRANCH_COLOR_RESET));
2017-01-10 16:49:52 +08:00
strbuf_addf(&local, "%%(if)%%(upstream)%%(then)[%s%%(upstream:short)%s%%(if)%%(upstream:track)"
"%%(then): %%(upstream:track,nobracket)%%(end)] %%(end)%%(contents:subject)",
branch_get_color(BRANCH_COLOR_UPSTREAM), branch_get_color(BRANCH_COLOR_RESET));
}
2017-01-10 16:49:52 +08:00
else
strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)");
strbuf_addf(&remote, "%%(align:%d,left)%s%%(refname:lstrip=2)%%(end)%s"
"%%(if)%%(symref)%%(then) -> %%(symref:short)"
"%%(else) %s %%(contents:subject)%%(end)",
maxwidth, quote_literal_for_format(remote_prefix),
branch_get_color(BRANCH_COLOR_RESET), obname.buf);
strbuf_release(&obname);
} else {
2017-01-10 16:49:52 +08:00
strbuf_addf(&local, "%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
branch_get_color(BRANCH_COLOR_RESET));
strbuf_addf(&remote, "%s%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
quote_literal_for_format(remote_prefix),
2017-01-10 16:49:52 +08:00
branch_get_color(BRANCH_COLOR_RESET));
}
2017-01-10 16:49:52 +08:00
strbuf_addf(&fmt, "%%(if:notequals=refs/remotes)%%(refname:rstrip=-2)%%(then)%s%%(else)%s%%(end)", local.buf, remote.buf);
2017-01-10 16:49:52 +08:00
strbuf_release(&local);
strbuf_release(&remote);
return strbuf_detach(&fmt, NULL);
}
static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting,
struct ref_format *format, struct string_list *output)
{
int i;
struct ref_array array;
int maxwidth = 0;
const char *remote_prefix = "";
char *to_free = NULL;
/*
* If we are listing more than just remote branches,
* then remote branches will have a "remotes/" prefix.
* We need to account for this in the width.
*/
if (filter->kind != FILTER_REFS_REMOTES)
remote_prefix = "remotes/";
branch: clean up commit flags after merge-filter walk When we run `branch --merged`, we use prepare_revision_walk with the merge-filter marked as UNINTERESTING. Any branch tips that are marked UNINTERESTING after it returns must be ancestors of that commit. As we iterate through the list of refs to show, we check item->commit->object.flags to see whether it was marked. This interacts badly with --verbose, which will do a separate walk to find the ahead/behind information for each branch. There are two bad things that can happen: 1. The ahead/behind walk may get the wrong results, because it can see a bogus UNINTERESTING flag leftover from the merge-filter walk. 2. We may omit some branches if their tips are involved in the ahead/behind traversal of a branch shown earlier. The ahead/behind walk carefully cleans up its commit flags, meaning it may also erase the UNINTERESTING flag that we expect to check later. We can solve this by moving the merge-filter state for each ref into its "struct ref_item" as soon as we finish the merge-filter walk. That fixes (2). Then we are free to clear the commit flags we used in the walk, fixing (1). Note that we actually do away with the matches_merge_filter helper entirely here, and inline it between the revision walk and the flag-clearing. This ensures that nobody accidentally calls it at the wrong time (it is only safe to check in that instant between the setting and clearing of the global flag). Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-09-18 18:49:43 +08:00
memset(&array, 0, sizeof(array));
branch: clean up commit flags after merge-filter walk When we run `branch --merged`, we use prepare_revision_walk with the merge-filter marked as UNINTERESTING. Any branch tips that are marked UNINTERESTING after it returns must be ancestors of that commit. As we iterate through the list of refs to show, we check item->commit->object.flags to see whether it was marked. This interacts badly with --verbose, which will do a separate walk to find the ahead/behind information for each branch. There are two bad things that can happen: 1. The ahead/behind walk may get the wrong results, because it can see a bogus UNINTERESTING flag leftover from the merge-filter walk. 2. We may omit some branches if their tips are involved in the ahead/behind traversal of a branch shown earlier. The ahead/behind walk carefully cleans up its commit flags, meaning it may also erase the UNINTERESTING flag that we expect to check later. We can solve this by moving the merge-filter state for each ref into its "struct ref_item" as soon as we finish the merge-filter walk. That fixes (2). Then we are free to clear the commit flags we used in the walk, fixing (1). Note that we actually do away with the matches_merge_filter helper entirely here, and inline it between the revision walk and the flag-clearing. This ensures that nobody accidentally calls it at the wrong time (it is only safe to check in that instant between the setting and clearing of the global flag). Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-09-18 18:49:43 +08:00
ref-filter: stop setting FILTER_REFS_INCLUDE_BROKEN Of the ref-filter callers, for-each-ref and git-branch both set the INCLUDE_BROKEN flag (but git-tag does not, which is a weird inconsistency). But now that GIT_REF_PARANOIA is on by default, that produces almost the same outcome for all three. The one exception is that GIT_REF_PARANOIA will omit dangling symrefs. That's a better behavior for these tools, as they would never include such a symref in the main output anyway (they can't, as it doesn't point to an object). Instead they issue a warning to stderr. But that warning is somewhat useless; a dangling symref is a perfectly reasonable thing to have in your repository, and is not a sign of corruption. It's much friendlier to just quietly ignore it. And in terms of robustness, the warning gains us little. It does not impact the exit code of either tool. So while the warning _might_ clue in a user that they have an unexpected broken symref, it would not help any kind of scripted use. This patch converts for-each-ref and git-branch to stop using the INCLUDE_BROKEN flag. That gives them more reasonable behavior, and harmonizes them with git-tag. We have to change one test to adapt to the situation. t1430 tries to trigger all of the REF_ISBROKEN behaviors from the underlying ref code. It uses for-each-ref to do so (because there isn't any other mechanism). That will no longer issue a warning about the symref which points to an invalid name, as it's considered dangling (and we can instead be sure that it's _not_ mentioned on stderr). Note that we do still complain about the illegally named "broken..symref"; its problem is not that it's dangling, but the name of the symref itself is illegal. Signed-off-by: Jeff King <peff@peff.net> Reviewed-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-09-25 02:48:05 +08:00
filter_refs(&array, filter, filter->kind);
if (filter->verbose)
maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
if (!format->format)
format->format = to_free = build_format(filter, maxwidth, remote_prefix);
format->use_color = branch_use_color;
if (verify_ref_format(format))
die(_("unable to parse format string"));
2017-01-10 16:49:52 +08:00
for-each-ref: add ahead-behind format atom The previous change implemented the ahead_behind() method, including an algorithm to compute the ahead/behind values for a number of commit tips relative to a number of commit bases. Now, integrate that algorithm as part of 'git for-each-ref' hidden behind a new format atom, ahead-behind. This naturally extends to 'git branch' and 'git tag' builtins, as well. This format allows specifying multiple bases, if so desired, and all matching references are compared against all of those bases. For this reason, failing to read a reference provided from these atoms results in an error. In order to translate the ahead_behind() method information to the format output code in ref-filter.c, we must populate arrays of ahead_behind_count structs. In struct ref_array, we store the full array that will be passed to ahead_behind(). In struct ref_array_item, we store an array of pointers that point to the relvant items within the full array. In this way, we can pull all relevant ahead/behind values directly when formatting output for a specific item. It also ensures the lifetime of the ahead_behind_count structs matches the time that the array is being used. Add specific tests of the ahead/behind counts in t6600-test-reach.sh, as it has an interesting repository shape. In particular, its merging strategy and its use of different commit-graphs would demonstrate over- counting if the ahead_behind() method did not already account for that possibility. Also add tests for the specific for-each-ref, branch, and tag builtins. In the case of 'git tag', there are intersting cases that happen when some of the selected tips are not commits. This requires careful logic around commits_nr in the second loop of filter_ahead_behind(). Also, the test in t7004 is carefully located to avoid being dependent on the GPG prereq. It also avoids using the test_commit helper, as that will add ticks to the time and disrupt the expected timestamps in later tag tests. Also add performance tests in a new p1300-graph-walks.sh script. This will be useful for more uses in the future, but for now compare the ahead-behind counting algorithm in 'git for-each-ref' to the naive implementation by running 'git rev-list --count' processes for each input. For the Git source code repository, the improvement is already obvious: Test this tree --------------------------------------------------------------- 1500.2: ahead-behind counts: git for-each-ref 0.07(0.07+0.00) 1500.3: ahead-behind counts: git branch 0.07(0.06+0.00) 1500.4: ahead-behind counts: git tag 0.07(0.06+0.00) 1500.5: ahead-behind counts: git rev-list 1.32(1.04+0.27) But the standard performance benchmark is the Linux kernel repository, which demosntrates a significant improvement: Test this tree --------------------------------------------------------------- 1500.2: ahead-behind counts: git for-each-ref 0.27(0.24+0.02) 1500.3: ahead-behind counts: git branch 0.27(0.24+0.03) 1500.4: ahead-behind counts: git tag 0.28(0.27+0.01) 1500.5: ahead-behind counts: git rev-list 4.57(4.03+0.54) The 'git rev-list' test exists in this change as a demonstration, but it will be removed in the next change to avoid wasting time on this comparison. Signed-off-by: Derrick Stolee <derrickstolee@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-03-20 19:26:54 +08:00
filter_ahead_behind(the_repository, format, &array);
ref_array_sort(sorting, &array);
if (column_active(colopts)) {
struct strbuf out = STRBUF_INIT, err = STRBUF_INIT;
assert(!filter->verbose && "--column and --verbose are incompatible");
for (i = 0; i < array.nr; i++) {
strbuf_reset(&err);
strbuf_reset(&out);
if (format_ref_array_item(array.items[i], format, &out, &err))
die("%s", err.buf);
/* format to a string_list to let print_columns() do its job */
string_list_append(output, out.buf);
2017-01-10 16:49:52 +08:00
}
strbuf_release(&err);
strbuf_release(&out);
} else {
print_formatted_ref_array(&array, format);
2017-01-10 16:49:52 +08:00
}
ref_array_clear(&array);
free(to_free);
}
static void print_current_branch_name(void)
{
int flags;
const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
const char *shortname;
if (!refname)
die(_("could not resolve HEAD"));
else if (!(flags & REF_ISSYMREF))
return;
else if (skip_prefix(refname, "refs/heads/", &shortname))
puts(shortname);
else
die(_("HEAD (%s) points outside of refs/heads/"), refname);
}
static void reject_rebase_or_bisect_branch(struct worktree **worktrees,
const char *target)
{
int i;
for (i = 0; worktrees[i]; i++) {
struct worktree *wt = worktrees[i];
if (!wt->is_detached)
continue;
if (is_worktree_being_rebased(wt, target))
die(_("branch %s is being rebased at %s"),
target, wt->path);
if (is_worktree_being_bisected(wt, target))
die(_("branch %s is being bisected at %s"),
target, wt->path);
}
}
/*
* Update all per-worktree HEADs pointing at the old ref to point the new ref.
* This will be used when renaming a branch. Returns 0 if successful, non-zero
* otherwise.
*/
static int replace_each_worktree_head_symref(struct worktree **worktrees,
const char *oldref, const char *newref,
const char *logmsg)
{
int ret = 0;
int i;
for (i = 0; worktrees[i]; i++) {
struct ref_store *refs;
if (worktrees[i]->is_detached)
continue;
if (!worktrees[i]->head_ref)
continue;
if (strcmp(oldref, worktrees[i]->head_ref))
continue;
refs = get_worktree_ref_store(worktrees[i]);
if (refs_create_symref(refs, "HEAD", newref, logmsg))
ret = error(_("HEAD of working tree %s is not updated"),
worktrees[i]->path);
}
return ret;
}
#define IS_HEAD 1
#define IS_ORPHAN 2
branch: add a --copy (-c) option to go with --move (-m) Add the ability to --copy a branch and its reflog and configuration, this uses the same underlying machinery as the --move (-m) option except the reflog and configuration is copied instead of being moved. This is useful for e.g. copying a topic branch to a new version, e.g. work to work-2 after submitting the work topic to the list, while preserving all the tracking info and other configuration that goes with the branch, and unlike --move keeping the other already-submitted branch around for reference. Like --move, when the source branch is the currently checked out branch the HEAD is moved to the destination branch. In the case of --move we don't really have a choice (other than remaining on a detached HEAD) and in order to keep the functionality consistent, we are doing it in similar way for --copy too. The most common usage of this feature is expected to be moving to a new topic branch which is a copy of the current one, in that case moving to the target branch is what the user wants, and doesn't unexpectedly behave differently than --move would. One outstanding caveat of this implementation is that: git checkout maint && git checkout master && git branch -c topic && git checkout - Will check out 'maint' instead of 'master'. This is because the @{-N} feature (or its -1 shorthand "-") relies on HEAD reflogs created by the checkout command, so in this case we'll checkout maint instead of master, as the user might expect. What to do about that is left to a future change. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Sahil Dua <sahildua2305@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-06-19 05:19:16 +08:00
static void copy_or_rename_branch(const char *oldname, const char *newname, int copy, int force)
{
struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
const char *interpreted_oldname = NULL;
const char *interpreted_newname = NULL;
int recovery = 0, oldref_usage = 0;
struct worktree **worktrees = get_worktrees();
check_ref_format(): tighten refname rules This changes the rules for refnames to forbid: (1) a refname that contains "@{" in it. Some people and foreign SCM converter may have named their branches as frotz@24 and we still want to keep supporting it. However, "git branch frotz@{24}" is a disaster. It cannot even checked out because "git checkout frotz@{24}" will interpret it as "detach the HEAD at twenty-fourth reflog entry of the frotz branch". (2) a refname that ends with a dot. We already reject a path component that begins with a dot, primarily to avoid ambiguous range interpretation. If we allowed ".B" as a valid ref, it is unclear if "A...B" means "in dot-B but not in A" or "either in A or B but not in both". But for this to be complete, we need also to forbid "A." to avoid "in B but not in A-dot". This was not a problem in the original range notation, but we should have added this restriction when three-dot notation was introduced. Unlike "no dot at the beginning of any path component" rule, this rule does not have to be "no dot at the end of any path component", because you cannot abbreviate the tail end away, similar to you can say "dot-B" to mean "refs/heads/dot-B". For these reasons, it is not likely people created branches with these names on purpose, but we have allowed such names to be used for quite some time, and it is possible that people created such branches by mistake or by accident. To help people with branches with such unfortunate names to recover, we still allow "branch -d 'bad.'" to delete such branches, and also allow "branch -m bad. good" to rename them. Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-03-22 04:27:31 +08:00
if (strbuf_check_branch_ref(&oldref, oldname)) {
/*
* Bad name --- this could be an attempt to rename a
* ref that we used to allow to be created by accident.
*/
if (ref_exists(oldref.buf))
check_ref_format(): tighten refname rules This changes the rules for refnames to forbid: (1) a refname that contains "@{" in it. Some people and foreign SCM converter may have named their branches as frotz@24 and we still want to keep supporting it. However, "git branch frotz@{24}" is a disaster. It cannot even checked out because "git checkout frotz@{24}" will interpret it as "detach the HEAD at twenty-fourth reflog entry of the frotz branch". (2) a refname that ends with a dot. We already reject a path component that begins with a dot, primarily to avoid ambiguous range interpretation. If we allowed ".B" as a valid ref, it is unclear if "A...B" means "in dot-B but not in A" or "either in A or B but not in both". But for this to be complete, we need also to forbid "A." to avoid "in B but not in A-dot". This was not a problem in the original range notation, but we should have added this restriction when three-dot notation was introduced. Unlike "no dot at the beginning of any path component" rule, this rule does not have to be "no dot at the end of any path component", because you cannot abbreviate the tail end away, similar to you can say "dot-B" to mean "refs/heads/dot-B". For these reasons, it is not likely people created branches with these names on purpose, but we have allowed such names to be used for quite some time, and it is possible that people created such branches by mistake or by accident. To help people with branches with such unfortunate names to recover, we still allow "branch -d 'bad.'" to delete such branches, and also allow "branch -m bad. good" to rename them. Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-03-22 04:27:31 +08:00
recovery = 1;
else
die(_("invalid branch name: '%s'"), oldname);
check_ref_format(): tighten refname rules This changes the rules for refnames to forbid: (1) a refname that contains "@{" in it. Some people and foreign SCM converter may have named their branches as frotz@24 and we still want to keep supporting it. However, "git branch frotz@{24}" is a disaster. It cannot even checked out because "git checkout frotz@{24}" will interpret it as "detach the HEAD at twenty-fourth reflog entry of the frotz branch". (2) a refname that ends with a dot. We already reject a path component that begins with a dot, primarily to avoid ambiguous range interpretation. If we allowed ".B" as a valid ref, it is unclear if "A...B" means "in dot-B but not in A" or "either in A or B but not in both". But for this to be complete, we need also to forbid "A." to avoid "in B but not in A-dot". This was not a problem in the original range notation, but we should have added this restriction when three-dot notation was introduced. Unlike "no dot at the beginning of any path component" rule, this rule does not have to be "no dot at the end of any path component", because you cannot abbreviate the tail end away, similar to you can say "dot-B" to mean "refs/heads/dot-B". For these reasons, it is not likely people created branches with these names on purpose, but we have allowed such names to be used for quite some time, and it is possible that people created such branches by mistake or by accident. To help people with branches with such unfortunate names to recover, we still allow "branch -d 'bad.'" to delete such branches, and also allow "branch -m bad. good" to rename them. Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-03-22 04:27:31 +08:00
}
for (int i = 0; worktrees[i]; i++) {
struct worktree *wt = worktrees[i];
if (wt->head_ref && !strcmp(oldref.buf, wt->head_ref)) {
oldref_usage |= IS_HEAD;
if (is_null_oid(&wt->head_oid))
oldref_usage |= IS_ORPHAN;
break;
}
}
if ((copy || !(oldref_usage & IS_HEAD)) && !ref_exists(oldref.buf)) {
if (oldref_usage & IS_HEAD)
die(_("no commit on branch '%s' yet"), oldname);
branch: description for non-existent branch errors When the repository does not yet have commits, some errors describe that there is no branch: $ git init -b first $ git branch --edit-description first error: No branch named 'first'. $ git branch --set-upstream-to=upstream fatal: branch 'first' does not exist $ git branch -c second error: refname refs/heads/first not found fatal: Branch copy failed That "first" branch is unborn but to say it doesn't exists is confusing. Options "-c" (copy) and "-m" (rename) show the same error when the origin branch doesn't exists: $ git branch -c non-existent-branch second error: refname refs/heads/non-existent-branch not found fatal: Branch copy failed $ git branch -m non-existent-branch second error: refname refs/heads/non-existent-branch not found fatal: Branch rename failed Note that "--edit-description" without an explicit argument is already considering the _empty repository_ circumstance in its error. Also note that "-m" on the initial branch it is an allowed operation. Make the error descriptions for those branch operations with unborn or non-existent branches, more informative. This is the result of the change: $ git init -b first $ git branch --edit-description first error: No commit on branch 'first' yet. $ git branch --set-upstream-to=upstream fatal: No commit on branch 'first' yet. $ git branch -c second fatal: No commit on branch 'first' yet. $ git branch [-c/-m] non-existent-branch second fatal: No branch named 'non-existent-branch'. Signed-off-by: Rubén Justo <rjusto@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-10-08 08:39:43 +08:00
else
die(_("no branch named '%s'"), oldname);
branch: description for non-existent branch errors When the repository does not yet have commits, some errors describe that there is no branch: $ git init -b first $ git branch --edit-description first error: No branch named 'first'. $ git branch --set-upstream-to=upstream fatal: branch 'first' does not exist $ git branch -c second error: refname refs/heads/first not found fatal: Branch copy failed That "first" branch is unborn but to say it doesn't exists is confusing. Options "-c" (copy) and "-m" (rename) show the same error when the origin branch doesn't exists: $ git branch -c non-existent-branch second error: refname refs/heads/non-existent-branch not found fatal: Branch copy failed $ git branch -m non-existent-branch second error: refname refs/heads/non-existent-branch not found fatal: Branch rename failed Note that "--edit-description" without an explicit argument is already considering the _empty repository_ circumstance in its error. Also note that "-m" on the initial branch it is an allowed operation. Make the error descriptions for those branch operations with unborn or non-existent branches, more informative. This is the result of the change: $ git init -b first $ git branch --edit-description first error: No commit on branch 'first' yet. $ git branch --set-upstream-to=upstream fatal: No commit on branch 'first' yet. $ git branch -c second fatal: No commit on branch 'first' yet. $ git branch [-c/-m] non-existent-branch second fatal: No branch named 'non-existent-branch'. Signed-off-by: Rubén Justo <rjusto@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-10-08 08:39:43 +08:00
}
/*
* A command like "git branch -M currentbranch currentbranch" cannot
* cause the worktree to become inconsistent with HEAD, so allow it.
*/
if (!strcmp(oldname, newname))
validate_branchname(newname, &newref);
else
validate_new_branchname(newname, &newref, force);
reject_rebase_or_bisect_branch(worktrees, oldref.buf);
if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) ||
!skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) {
BUG("expected prefix missing for refs");
}
branch: add a --copy (-c) option to go with --move (-m) Add the ability to --copy a branch and its reflog and configuration, this uses the same underlying machinery as the --move (-m) option except the reflog and configuration is copied instead of being moved. This is useful for e.g. copying a topic branch to a new version, e.g. work to work-2 after submitting the work topic to the list, while preserving all the tracking info and other configuration that goes with the branch, and unlike --move keeping the other already-submitted branch around for reference. Like --move, when the source branch is the currently checked out branch the HEAD is moved to the destination branch. In the case of --move we don't really have a choice (other than remaining on a detached HEAD) and in order to keep the functionality consistent, we are doing it in similar way for --copy too. The most common usage of this feature is expected to be moving to a new topic branch which is a copy of the current one, in that case moving to the target branch is what the user wants, and doesn't unexpectedly behave differently than --move would. One outstanding caveat of this implementation is that: git checkout maint && git checkout master && git branch -c topic && git checkout - Will check out 'maint' instead of 'master'. This is because the @{-N} feature (or its -1 shorthand "-") relies on HEAD reflogs created by the checkout command, so in this case we'll checkout maint instead of master, as the user might expect. What to do about that is left to a future change. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Sahil Dua <sahildua2305@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-06-19 05:19:16 +08:00
if (copy)
strbuf_addf(&logmsg, "Branch: copied %s to %s",
oldref.buf, newref.buf);
else
strbuf_addf(&logmsg, "Branch: renamed %s to %s",
oldref.buf, newref.buf);
if (!copy && !(oldref_usage & IS_ORPHAN) &&
rename_ref(oldref.buf, newref.buf, logmsg.buf))
die(_("branch rename failed"));
branch: add a --copy (-c) option to go with --move (-m) Add the ability to --copy a branch and its reflog and configuration, this uses the same underlying machinery as the --move (-m) option except the reflog and configuration is copied instead of being moved. This is useful for e.g. copying a topic branch to a new version, e.g. work to work-2 after submitting the work topic to the list, while preserving all the tracking info and other configuration that goes with the branch, and unlike --move keeping the other already-submitted branch around for reference. Like --move, when the source branch is the currently checked out branch the HEAD is moved to the destination branch. In the case of --move we don't really have a choice (other than remaining on a detached HEAD) and in order to keep the functionality consistent, we are doing it in similar way for --copy too. The most common usage of this feature is expected to be moving to a new topic branch which is a copy of the current one, in that case moving to the target branch is what the user wants, and doesn't unexpectedly behave differently than --move would. One outstanding caveat of this implementation is that: git checkout maint && git checkout master && git branch -c topic && git checkout - Will check out 'maint' instead of 'master'. This is because the @{-N} feature (or its -1 shorthand "-") relies on HEAD reflogs created by the checkout command, so in this case we'll checkout maint instead of master, as the user might expect. What to do about that is left to a future change. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Sahil Dua <sahildua2305@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-06-19 05:19:16 +08:00
if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf))
die(_("branch copy failed"));
branch: add a --copy (-c) option to go with --move (-m) Add the ability to --copy a branch and its reflog and configuration, this uses the same underlying machinery as the --move (-m) option except the reflog and configuration is copied instead of being moved. This is useful for e.g. copying a topic branch to a new version, e.g. work to work-2 after submitting the work topic to the list, while preserving all the tracking info and other configuration that goes with the branch, and unlike --move keeping the other already-submitted branch around for reference. Like --move, when the source branch is the currently checked out branch the HEAD is moved to the destination branch. In the case of --move we don't really have a choice (other than remaining on a detached HEAD) and in order to keep the functionality consistent, we are doing it in similar way for --copy too. The most common usage of this feature is expected to be moving to a new topic branch which is a copy of the current one, in that case moving to the target branch is what the user wants, and doesn't unexpectedly behave differently than --move would. One outstanding caveat of this implementation is that: git checkout maint && git checkout master && git branch -c topic && git checkout - Will check out 'maint' instead of 'master'. This is because the @{-N} feature (or its -1 shorthand "-") relies on HEAD reflogs created by the checkout command, so in this case we'll checkout maint instead of master, as the user might expect. What to do about that is left to a future change. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Sahil Dua <sahildua2305@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-06-19 05:19:16 +08:00
if (recovery) {
if (copy)
warning(_("created a copy of a misnamed branch '%s'"),
interpreted_oldname);
branch: add a --copy (-c) option to go with --move (-m) Add the ability to --copy a branch and its reflog and configuration, this uses the same underlying machinery as the --move (-m) option except the reflog and configuration is copied instead of being moved. This is useful for e.g. copying a topic branch to a new version, e.g. work to work-2 after submitting the work topic to the list, while preserving all the tracking info and other configuration that goes with the branch, and unlike --move keeping the other already-submitted branch around for reference. Like --move, when the source branch is the currently checked out branch the HEAD is moved to the destination branch. In the case of --move we don't really have a choice (other than remaining on a detached HEAD) and in order to keep the functionality consistent, we are doing it in similar way for --copy too. The most common usage of this feature is expected to be moving to a new topic branch which is a copy of the current one, in that case moving to the target branch is what the user wants, and doesn't unexpectedly behave differently than --move would. One outstanding caveat of this implementation is that: git checkout maint && git checkout master && git branch -c topic && git checkout - Will check out 'maint' instead of 'master'. This is because the @{-N} feature (or its -1 shorthand "-") relies on HEAD reflogs created by the checkout command, so in this case we'll checkout maint instead of master, as the user might expect. What to do about that is left to a future change. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Sahil Dua <sahildua2305@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-06-19 05:19:16 +08:00
else
warning(_("renamed a misnamed branch '%s' away"),
interpreted_oldname);
branch: add a --copy (-c) option to go with --move (-m) Add the ability to --copy a branch and its reflog and configuration, this uses the same underlying machinery as the --move (-m) option except the reflog and configuration is copied instead of being moved. This is useful for e.g. copying a topic branch to a new version, e.g. work to work-2 after submitting the work topic to the list, while preserving all the tracking info and other configuration that goes with the branch, and unlike --move keeping the other already-submitted branch around for reference. Like --move, when the source branch is the currently checked out branch the HEAD is moved to the destination branch. In the case of --move we don't really have a choice (other than remaining on a detached HEAD) and in order to keep the functionality consistent, we are doing it in similar way for --copy too. The most common usage of this feature is expected to be moving to a new topic branch which is a copy of the current one, in that case moving to the target branch is what the user wants, and doesn't unexpectedly behave differently than --move would. One outstanding caveat of this implementation is that: git checkout maint && git checkout master && git branch -c topic && git checkout - Will check out 'maint' instead of 'master'. This is because the @{-N} feature (or its -1 shorthand "-") relies on HEAD reflogs created by the checkout command, so in this case we'll checkout maint instead of master, as the user might expect. What to do about that is left to a future change. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Sahil Dua <sahildua2305@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-06-19 05:19:16 +08:00
}
check_ref_format(): tighten refname rules This changes the rules for refnames to forbid: (1) a refname that contains "@{" in it. Some people and foreign SCM converter may have named their branches as frotz@24 and we still want to keep supporting it. However, "git branch frotz@{24}" is a disaster. It cannot even checked out because "git checkout frotz@{24}" will interpret it as "detach the HEAD at twenty-fourth reflog entry of the frotz branch". (2) a refname that ends with a dot. We already reject a path component that begins with a dot, primarily to avoid ambiguous range interpretation. If we allowed ".B" as a valid ref, it is unclear if "A...B" means "in dot-B but not in A" or "either in A or B but not in both". But for this to be complete, we need also to forbid "A." to avoid "in B but not in A-dot". This was not a problem in the original range notation, but we should have added this restriction when three-dot notation was introduced. Unlike "no dot at the beginning of any path component" rule, this rule does not have to be "no dot at the end of any path component", because you cannot abbreviate the tail end away, similar to you can say "dot-B" to mean "refs/heads/dot-B". For these reasons, it is not likely people created branches with these names on purpose, but we have allowed such names to be used for quite some time, and it is possible that people created such branches by mistake or by accident. To help people with branches with such unfortunate names to recover, we still allow "branch -d 'bad.'" to delete such branches, and also allow "branch -m bad. good" to rename them. Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-03-22 04:27:31 +08:00
if (!copy && (oldref_usage & IS_HEAD) &&
replace_each_worktree_head_symref(worktrees, oldref.buf, newref.buf,
logmsg.buf))
die(_("branch renamed to %s, but HEAD is not updated"), newname);
strbuf_release(&logmsg);
strbuf_addf(&oldsection, "branch.%s", interpreted_oldname);
strbuf_addf(&newsection, "branch.%s", interpreted_newname);
branch: add a --copy (-c) option to go with --move (-m) Add the ability to --copy a branch and its reflog and configuration, this uses the same underlying machinery as the --move (-m) option except the reflog and configuration is copied instead of being moved. This is useful for e.g. copying a topic branch to a new version, e.g. work to work-2 after submitting the work topic to the list, while preserving all the tracking info and other configuration that goes with the branch, and unlike --move keeping the other already-submitted branch around for reference. Like --move, when the source branch is the currently checked out branch the HEAD is moved to the destination branch. In the case of --move we don't really have a choice (other than remaining on a detached HEAD) and in order to keep the functionality consistent, we are doing it in similar way for --copy too. The most common usage of this feature is expected to be moving to a new topic branch which is a copy of the current one, in that case moving to the target branch is what the user wants, and doesn't unexpectedly behave differently than --move would. One outstanding caveat of this implementation is that: git checkout maint && git checkout master && git branch -c topic && git checkout - Will check out 'maint' instead of 'master'. This is because the @{-N} feature (or its -1 shorthand "-") relies on HEAD reflogs created by the checkout command, so in this case we'll checkout maint instead of master, as the user might expect. What to do about that is left to a future change. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Sahil Dua <sahildua2305@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-06-19 05:19:16 +08:00
if (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0)
die(_("branch is renamed, but update of config-file failed"));
branch: force-copy a branch to itself via @{-1} is a no-op Since 52d59cc645 (branch: add a --copy (-c) option to go with --move (-m), 2017-06-18) we can copy a branch to make a new branch with the '-c' (copy) option or to overwrite an existing branch using the '-C' (force copy) option. A no-op possibility is considered when we are asked to copy a branch to itself, to follow the same no-op introduced for the rename (-M) operation in 3f59481e33 (branch: allow a no-op "branch -M <current-branch> HEAD", 2011-11-25). To check for this, in 52d59cc645 we compared the branch names provided by the user, source (HEAD if omitted) and destination, and a match is considered as this no-op. Since ae5a6c3684 (checkout: implement "@{-N}" shortcut name for N-th last branch, 2009-01-17) a branch can be specified using shortcuts like @{-1}. This allows this usage: $ git checkout -b test $ git checkout - $ git branch -C test test # no-op $ git branch -C test @{-1} # oops $ git branch -C @{-1} test # oops As we are using the branch name provided by the user to do the comparison, if one of the branches is provided using a shortcut we are not going to have a match and a call to git_config_copy_section() will happen. This will make a duplicate of the configuration for that branch, and with this progression the second call will produce four copies of the configuration, and so on. Let's use the interpreted branch name instead for this comparison. The rename operation is not affected. Signed-off-by: Rubén Justo <rjusto@gmail.com> Signed-off-by: Taylor Blau <me@ttaylorr.com>
2022-11-17 09:36:52 +08:00
if (copy && strcmp(interpreted_oldname, interpreted_newname) && git_config_copy_section(oldsection.buf, newsection.buf) < 0)
die(_("branch is copied, but update of config-file failed"));
branch: force-copy a branch to itself via @{-1} is a no-op Since 52d59cc645 (branch: add a --copy (-c) option to go with --move (-m), 2017-06-18) we can copy a branch to make a new branch with the '-c' (copy) option or to overwrite an existing branch using the '-C' (force copy) option. A no-op possibility is considered when we are asked to copy a branch to itself, to follow the same no-op introduced for the rename (-M) operation in 3f59481e33 (branch: allow a no-op "branch -M <current-branch> HEAD", 2011-11-25). To check for this, in 52d59cc645 we compared the branch names provided by the user, source (HEAD if omitted) and destination, and a match is considered as this no-op. Since ae5a6c3684 (checkout: implement "@{-N}" shortcut name for N-th last branch, 2009-01-17) a branch can be specified using shortcuts like @{-1}. This allows this usage: $ git checkout -b test $ git checkout - $ git branch -C test test # no-op $ git branch -C test @{-1} # oops $ git branch -C @{-1} test # oops As we are using the branch name provided by the user to do the comparison, if one of the branches is provided using a shortcut we are not going to have a match and a call to git_config_copy_section() will happen. This will make a duplicate of the configuration for that branch, and with this progression the second call will produce four copies of the configuration, and so on. Let's use the interpreted branch name instead for this comparison. The rename operation is not affected. Signed-off-by: Rubén Justo <rjusto@gmail.com> Signed-off-by: Taylor Blau <me@ttaylorr.com>
2022-11-17 09:36:52 +08:00
strbuf_release(&oldref);
strbuf_release(&newref);
strbuf_release(&oldsection);
strbuf_release(&newsection);
free_worktrees(worktrees);
}
static GIT_PATH_FUNC(edit_description, "EDIT_DESCRIPTION")
static int edit_branch_description(const char *branch_name)
{
int exists;
struct strbuf buf = STRBUF_INIT;
struct strbuf name = STRBUF_INIT;
exists = !read_branch_desc(&buf, branch_name);
if (!buf.len || buf.buf[buf.len-1] != '\n')
strbuf_addch(&buf, '\n');
strbuf_commented_addf(&buf, comment_line_char,
_("Please edit the description for the branch\n"
" %s\n"
"Lines starting with '%c' will be stripped.\n"),
branch_name, comment_line_char);
write_file_buf(edit_description(), buf.buf, buf.len);
strbuf_reset(&buf);
if (launch_editor(edit_description(), &buf, NULL)) {
strbuf_release(&buf);
return -1;
}
strbuf_stripspace(&buf, comment_line_char);
strbuf_addf(&name, "branch.%s.description", branch_name);
if (buf.len || exists)
git_config_set(name.buf, buf.len ? buf.buf : NULL);
strbuf_release(&name);
strbuf_release(&buf);
return 0;
}
int cmd_branch(int argc, const char **argv, const char *prefix)
{
/* possible actions */
int delete = 0, rename = 0, copy = 0, list = 0,
unset_upstream = 0, show_current = 0, edit_description = 0;
const char *new_upstream = NULL;
int noncreate_actions = 0;
/* possible options */
branch: add --recurse-submodules option for branch creation To improve the submodules UX, we would like to teach Git to handle branches in submodules. Start this process by teaching "git branch" the --recurse-submodules option so that "git branch --recurse-submodules topic" will create the `topic` branch in the superproject and its submodules. Although this commit does not introduce breaking changes, it does not work well with existing --recurse-submodules commands because "git branch --recurse-submodules" writes to the submodule ref store, but most commands only consider the superproject gitlink and ignore the submodule ref store. For example, "git checkout --recurse-submodules" will check out the commits in the superproject gitlinks (and put the submodules in detached HEAD) instead of checking out the submodule branches. Because of this, this commit introduces a new configuration value, `submodule.propagateBranches`. The plan is for Git commands to prioritize submodule ref store information over superproject gitlinks if this value is true. Because "git branch --recurse-submodules" writes to submodule ref stores, for the sake of clarity, it will not function unless this configuration value is set. This commit also includes changes that support working with submodules from a superproject commit because "branch --recurse-submodules" (and future commands) need to read .gitmodules and gitlinks from the superproject commit, but submodules are typically read from the filesystem's .gitmodules and the index's gitlinks. These changes are: * add a submodules_of_tree() helper that gives the relevant information of an in-tree submodule (e.g. path and oid) and initializes the repository * add is_tree_submodule_active() by adding a treeish_name parameter to is_submodule_active() * add the "submoduleNotUpdated" advice to advise users to update the submodules in their trees Incidentally, fix an incorrect usage string that combined the 'list' usage of git branch (-l) with the 'create' usage; this string has been incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use parse_options., 2007-10-07). Helped-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Glen Choo <chooglen@google.com> Reviewed-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-01-29 08:04:45 +08:00
int reflog = 0, quiet = 0, icase = 0, force = 0,
recurse_submodules_explicit = 0;
enum branch_track track;
struct ref_filter filter = REF_FILTER_INIT;
for-each-ref: delay parsing of --sort=<atom> options The for-each-ref family of commands invoke parsers immediately when it sees each --sort=<atom> option, and die before even seeing the other options on the command line when the <atom> is unrecognised. Instead, accumulate them in a string list, and have them parsed into a ref_sorting structure after the command line parsing is done. As a consequence, "git branch --sort=bogus -h" used to fail to give the brief help, which arguably may have been a feature, now does so, which is more consistent with how other options work. The patch is smaller than the actual extent of the "damage" to the codebase, thanks to the fact that the original code consistently used OPT_REF_SORT() macro to handle command line options. We only needed to replace the variable used for the list, and implementation of the callback function used in the macro. The old rule was for the users of the API to: - Declare ref_sorting and ref_sorting_tail variables; - OPT_REF_SORT() macro will instantiate ref_sorting instance (which may barf and die) and append it to the tail; - Append to the tail each ref_sorting read from the configuration by parsing in the config callback (which may barf and die); - See if ref_sorting is null and use ref_sorting_default() instead. Now the rule is not all that different but is simpler: - Declare ref_sorting_options string list. - OPT_REF_SORT() macro will append it to the string list; - Append to the string list the sort key read from the configuration; - call ref_sorting_options() to turn the string list to ref_sorting structure (which also deals with the default value). As side effects, this change also cleans up a few issues: - 95be717c (parse_opt_ref_sorting: always use with NONEG flag, 2019-03-20) muses that "git for-each-ref --no-sort" should simply clear the sort keys accumulated so far; it now does. - The implementation detail of "struct ref_sorting" and the helper function parse_ref_sorting() can now be private to the ref-filter API implementation. - If you set branch.sort to a bogus value, the any "git branch" invocation, not only the listing mode, would abort with the original code; now it doesn't Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-10-21 03:23:53 +08:00
static struct ref_sorting *sorting;
struct string_list sorting_options = STRING_LIST_INIT_DUP;
struct ref_format format = REF_FORMAT_INIT;
struct option options[] = {
OPT_GROUP(N_("Generic options")),
OPT__VERBOSE(&filter.verbose,
N_("show hash and subject, give twice for upstream branch")),
OPT__QUIET(&quiet, N_("suppress informational messages")),
OPT_CALLBACK_F('t', "track", &track, "(direct|inherit)",
N_("set branch tracking configuration"),
PARSE_OPT_OPTARG,
parse_opt_tracking_mode),
OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("unset the upstream info")),
OPT__COLOR(&branch_use_color, N_("use colored output")),
OPT_SET_INT_F('r', "remotes", &filter.kind, N_("act on remote-tracking branches"),
FILTER_REFS_REMOTES,
PARSE_OPT_NONEG),
OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")),
ref-filter: add --no-contains option to tag/branch/for-each-ref Change the tag, branch & for-each-ref commands to have a --no-contains option in addition to their longstanding --contains options. This allows for finding the last-good rollout tag given a known-bad <commit>. Given a hypothetically bad commit cf5c7253e0, the git version to revert to can be found with this hacky two-liner: (git tag -l 'v[0-9]*'; git tag -l --contains cf5c7253e0 'v[0-9]*') | sort | uniq -c | grep -E '^ *1 ' | awk '{print $2}' | tail -n 10 With this new --no-contains option the same can be achieved with: git tag -l --no-contains cf5c7253e0 'v[0-9]*' | sort | tail -n 10 As the filtering machinery is shared between the tag, branch & for-each-ref commands, implement this for those commands too. A practical use for this with "branch" is e.g. finding branches which were branched off between v2.8.0 and v2.10.0: git branch --contains v2.8.0 --no-contains v2.10.0 The "describe" command also has a --contains option, but its semantics are unrelated to what tag/branch/for-each-ref use --contains for. A --no-contains option for "describe" wouldn't make any sense, other than being exactly equivalent to not supplying --contains at all, which would be confusing at best. Add a --without option to "tag" as an alias for --no-contains, for consistency with --with and --contains. The --with option is undocumented, and possibly the only user of it is Junio (<xmqqefy71iej.fsf@gitster.mtv.corp.google.com>). But it's trivial to support, so let's do that. The additions to the the test suite are inverse copies of the corresponding --contains tests. With this change --no-contains for tag, branch & for-each-ref is just as well tested as the existing --contains option. In addition to those tests, add a test for "tag" which asserts that --no-contains won't find tree/blob tags, which is slightly unintuitive, but consistent with how --contains works & is documented. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-03-25 02:40:57 +08:00
OPT_NO_CONTAINS(&filter.no_commit, N_("print only branches that don't contain the commit")),
OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")),
ref-filter: add --no-contains option to tag/branch/for-each-ref Change the tag, branch & for-each-ref commands to have a --no-contains option in addition to their longstanding --contains options. This allows for finding the last-good rollout tag given a known-bad <commit>. Given a hypothetically bad commit cf5c7253e0, the git version to revert to can be found with this hacky two-liner: (git tag -l 'v[0-9]*'; git tag -l --contains cf5c7253e0 'v[0-9]*') | sort | uniq -c | grep -E '^ *1 ' | awk '{print $2}' | tail -n 10 With this new --no-contains option the same can be achieved with: git tag -l --no-contains cf5c7253e0 'v[0-9]*' | sort | tail -n 10 As the filtering machinery is shared between the tag, branch & for-each-ref commands, implement this for those commands too. A practical use for this with "branch" is e.g. finding branches which were branched off between v2.8.0 and v2.10.0: git branch --contains v2.8.0 --no-contains v2.10.0 The "describe" command also has a --contains option, but its semantics are unrelated to what tag/branch/for-each-ref use --contains for. A --no-contains option for "describe" wouldn't make any sense, other than being exactly equivalent to not supplying --contains at all, which would be confusing at best. Add a --without option to "tag" as an alias for --no-contains, for consistency with --with and --contains. The --with option is undocumented, and possibly the only user of it is Junio (<xmqqefy71iej.fsf@gitster.mtv.corp.google.com>). But it's trivial to support, so let's do that. The additions to the the test suite are inverse copies of the corresponding --contains tests. With this change --no-contains for tag, branch & for-each-ref is just as well tested as the existing --contains option. In addition to those tests, add a test for "tag" which asserts that --no-contains won't find tree/blob tags, which is slightly unintuitive, but consistent with how --contains works & is documented. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-03-25 02:40:57 +08:00
OPT_WITHOUT(&filter.no_commit, N_("print only branches that don't contain the commit")),
OPT__ABBREV(&filter.abbrev),
OPT_GROUP(N_("Specific git-branch actions:")),
OPT_SET_INT_F('a', "all", &filter.kind, N_("list both remote-tracking and local branches"),
FILTER_REFS_REMOTES | FILTER_REFS_BRANCHES,
PARSE_OPT_NONEG),
OPT_BIT('d', "delete", &delete, N_("delete fully merged branch"), 1),
OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2),
OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1),
OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2),
OPT_BOOL(0, "omit-empty", &format.array_opts.omit_empty,
N_("do not output a newline after empty formatted refs")),
branch: add a --copy (-c) option to go with --move (-m) Add the ability to --copy a branch and its reflog and configuration, this uses the same underlying machinery as the --move (-m) option except the reflog and configuration is copied instead of being moved. This is useful for e.g. copying a topic branch to a new version, e.g. work to work-2 after submitting the work topic to the list, while preserving all the tracking info and other configuration that goes with the branch, and unlike --move keeping the other already-submitted branch around for reference. Like --move, when the source branch is the currently checked out branch the HEAD is moved to the destination branch. In the case of --move we don't really have a choice (other than remaining on a detached HEAD) and in order to keep the functionality consistent, we are doing it in similar way for --copy too. The most common usage of this feature is expected to be moving to a new topic branch which is a copy of the current one, in that case moving to the target branch is what the user wants, and doesn't unexpectedly behave differently than --move would. One outstanding caveat of this implementation is that: git checkout maint && git checkout master && git branch -c topic && git checkout - Will check out 'maint' instead of 'master'. This is because the @{-N} feature (or its -1 shorthand "-") relies on HEAD reflogs created by the checkout command, so in this case we'll checkout maint instead of master, as the user might expect. What to do about that is left to a future change. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Sahil Dua <sahildua2305@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-06-19 05:19:16 +08:00
OPT_BIT('c', "copy", &copy, N_("copy a branch and its reflog"), 1),
OPT_BIT('C', NULL, &copy, N_("copy a branch, even if target exists"), 2),
OPT_BOOL('l', "list", &list, N_("list branch names")),
OPT_BOOL(0, "show-current", &show_current, N_("show current branch name")),
branch: deprecate "-l" option The "-l" option is short for "--create-reflog". This has caused much confusion over the years. Most people expect it to work as "--list", because that would match the other "mode" options like -d/--delete and -m/--move, as well as the similar -l/--list option of git-tag. Adding to the confusion, using "-l" _appears_ to work as "--list" in some cases: $ git branch -l * master because the branch command defaults to listing (so even trying to specify --list in the command above is redundant). But that may bite the user later when they add a pattern, like: $ git branch -l foo which does not return an empty list, but in fact creates a new branch (with a reflog, naturally) called "foo". It's also probably quite uncommon for people to actually use "-l" to create a reflog. Since 0bee591869 (Enable reflogs by default in any repository with a working directory., 2006-12-14), this is the default in non-bare repositories. So it's rather unfortunate that the feature squats on the short-and-sweet "-l" (which was only added in 3a4b3f269c (Create/delete branch ref logs., 2006-05-19), meaning there were only 7 months where it was actually useful). Let's deprecate "-l" in hopes of eventually re-purposing it to "--list". Note that we issue the warning only when we're not in list mode. This means that people for whom it works as a happy accident, namely: $ git branch -l master won't see the warning at all. And when we eventually switch to it meaning "--list", that will just continue to work. We do the issue the warning for these important cases: - when we are actually creating a branch, in case the user really did mean it as "--create-reflog" - when we are in some _other_ mode, like deletion. There the "-l" is a noop for now, but it will eventually conflict with any other mode request, and the user should be told that this is changing. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-06-22 17:24:14 +08:00
OPT_BOOL(0, "create-reflog", &reflog, N_("create the branch's reflog")),
OPT_BOOL(0, "edit-description", &edit_description,
N_("edit the description for the branch")),
OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
OPT_MERGED(&filter, N_("print only branches that are merged")),
OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
for-each-ref: delay parsing of --sort=<atom> options The for-each-ref family of commands invoke parsers immediately when it sees each --sort=<atom> option, and die before even seeing the other options on the command line when the <atom> is unrecognised. Instead, accumulate them in a string list, and have them parsed into a ref_sorting structure after the command line parsing is done. As a consequence, "git branch --sort=bogus -h" used to fail to give the brief help, which arguably may have been a feature, now does so, which is more consistent with how other options work. The patch is smaller than the actual extent of the "damage" to the codebase, thanks to the fact that the original code consistently used OPT_REF_SORT() macro to handle command line options. We only needed to replace the variable used for the list, and implementation of the callback function used in the macro. The old rule was for the users of the API to: - Declare ref_sorting and ref_sorting_tail variables; - OPT_REF_SORT() macro will instantiate ref_sorting instance (which may barf and die) and append it to the tail; - Append to the tail each ref_sorting read from the configuration by parsing in the config callback (which may barf and die); - See if ref_sorting is null and use ref_sorting_default() instead. Now the rule is not all that different but is simpler: - Declare ref_sorting_options string list. - OPT_REF_SORT() macro will append it to the string list; - Append to the string list the sort key read from the configuration; - call ref_sorting_options() to turn the string list to ref_sorting structure (which also deals with the default value). As side effects, this change also cleans up a few issues: - 95be717c (parse_opt_ref_sorting: always use with NONEG flag, 2019-03-20) muses that "git for-each-ref --no-sort" should simply clear the sort keys accumulated so far; it now does. - The implementation detail of "struct ref_sorting" and the helper function parse_ref_sorting() can now be private to the ref-filter API implementation. - If you set branch.sort to a bogus value, the any "git branch" invocation, not only the listing mode, would abort with the original code; now it doesn't Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-10-21 03:23:53 +08:00
OPT_REF_SORT(&sorting_options),
OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"),
N_("print only branches of the object"), parse_opt_object_name),
OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
branch: add --recurse-submodules option for branch creation To improve the submodules UX, we would like to teach Git to handle branches in submodules. Start this process by teaching "git branch" the --recurse-submodules option so that "git branch --recurse-submodules topic" will create the `topic` branch in the superproject and its submodules. Although this commit does not introduce breaking changes, it does not work well with existing --recurse-submodules commands because "git branch --recurse-submodules" writes to the submodule ref store, but most commands only consider the superproject gitlink and ignore the submodule ref store. For example, "git checkout --recurse-submodules" will check out the commits in the superproject gitlinks (and put the submodules in detached HEAD) instead of checking out the submodule branches. Because of this, this commit introduces a new configuration value, `submodule.propagateBranches`. The plan is for Git commands to prioritize submodule ref store information over superproject gitlinks if this value is true. Because "git branch --recurse-submodules" writes to submodule ref stores, for the sake of clarity, it will not function unless this configuration value is set. This commit also includes changes that support working with submodules from a superproject commit because "branch --recurse-submodules" (and future commands) need to read .gitmodules and gitlinks from the superproject commit, but submodules are typically read from the filesystem's .gitmodules and the index's gitlinks. These changes are: * add a submodules_of_tree() helper that gives the relevant information of an in-tree submodule (e.g. path and oid) and initializes the repository * add is_tree_submodule_active() by adding a treeish_name parameter to is_submodule_active() * add the "submoduleNotUpdated" advice to advise users to update the submodules in their trees Incidentally, fix an incorrect usage string that combined the 'list' usage of git branch (-l) with the 'create' usage; this string has been incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use parse_options., 2007-10-07). Helped-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Glen Choo <chooglen@google.com> Reviewed-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-01-29 08:04:45 +08:00
OPT_BOOL(0, "recurse-submodules", &recurse_submodules_explicit, N_("recurse through submodules")),
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
OPT_END(),
};
setup_ref_filter_porcelain_msg();
filter.kind = FILTER_REFS_BRANCHES;
filter.abbrev = -1;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_branch_usage, options);
ref-filter.c: really don't sort when using --no-sort When '--no-sort' is passed to 'for-each-ref', 'tag', and 'branch', the printed refs are still sorted by ascending refname. Change the handling of sort options in these commands so that '--no-sort' to truly disables sorting. '--no-sort' does not disable sorting in these commands is because their option parsing does not distinguish between "the absence of '--sort'" (and/or values for tag.sort & branch.sort) and '--no-sort'. Both result in an empty 'sorting_options' string list, which is parsed by 'ref_sorting_options()' to create the 'struct ref_sorting *' for the command. If the string list is empty, 'ref_sorting_options()' interprets that as "the absence of '--sort'" and returns the default ref sorting structure (equivalent to "refname" sort). To handle '--no-sort' properly while preserving the "refname" sort in the "absence of --sort'" case, first explicitly add "refname" to the string list *before* parsing options. This alone doesn't actually change any behavior, since 'compare_refs()' already falls back on comparing refnames if two refs are equal w.r.t all other sort keys. Now that the string list is populated by default, '--no-sort' is the only way to empty the 'sorting_options' string list. Update 'ref_sorting_options()' to return a NULL 'struct ref_sorting *' if the string list is empty, and add a condition to 'ref_array_sort()' to skip the sort altogether if the sort structure is NULL. Note that other functions using 'struct ref_sorting *' do not need any changes because they already ignore NULL values. Finally, remove the condition around sorting in 'ls-remote', since it's no longer necessary. Unlike 'for-each-ref' et. al., it does *not* do any sorting by default. This default is preserved by simply leaving its sort key string list empty before parsing options; if no additional sort keys are set, 'struct ref_sorting *' is NULL and sorting is skipped. Signed-off-by: Victoria Dye <vdye@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-11-15 03:53:49 +08:00
/*
* Try to set sort keys from config. If config does not set any,
* fall back on default (refname) sorting.
*/
for-each-ref: delay parsing of --sort=<atom> options The for-each-ref family of commands invoke parsers immediately when it sees each --sort=<atom> option, and die before even seeing the other options on the command line when the <atom> is unrecognised. Instead, accumulate them in a string list, and have them parsed into a ref_sorting structure after the command line parsing is done. As a consequence, "git branch --sort=bogus -h" used to fail to give the brief help, which arguably may have been a feature, now does so, which is more consistent with how other options work. The patch is smaller than the actual extent of the "damage" to the codebase, thanks to the fact that the original code consistently used OPT_REF_SORT() macro to handle command line options. We only needed to replace the variable used for the list, and implementation of the callback function used in the macro. The old rule was for the users of the API to: - Declare ref_sorting and ref_sorting_tail variables; - OPT_REF_SORT() macro will instantiate ref_sorting instance (which may barf and die) and append it to the tail; - Append to the tail each ref_sorting read from the configuration by parsing in the config callback (which may barf and die); - See if ref_sorting is null and use ref_sorting_default() instead. Now the rule is not all that different but is simpler: - Declare ref_sorting_options string list. - OPT_REF_SORT() macro will append it to the string list; - Append to the string list the sort key read from the configuration; - call ref_sorting_options() to turn the string list to ref_sorting structure (which also deals with the default value). As side effects, this change also cleans up a few issues: - 95be717c (parse_opt_ref_sorting: always use with NONEG flag, 2019-03-20) muses that "git for-each-ref --no-sort" should simply clear the sort keys accumulated so far; it now does. - The implementation detail of "struct ref_sorting" and the helper function parse_ref_sorting() can now be private to the ref-filter API implementation. - If you set branch.sort to a bogus value, the any "git branch" invocation, not only the listing mode, would abort with the original code; now it doesn't Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-10-21 03:23:53 +08:00
git_config(git_branch_config, &sorting_options);
ref-filter.c: really don't sort when using --no-sort When '--no-sort' is passed to 'for-each-ref', 'tag', and 'branch', the printed refs are still sorted by ascending refname. Change the handling of sort options in these commands so that '--no-sort' to truly disables sorting. '--no-sort' does not disable sorting in these commands is because their option parsing does not distinguish between "the absence of '--sort'" (and/or values for tag.sort & branch.sort) and '--no-sort'. Both result in an empty 'sorting_options' string list, which is parsed by 'ref_sorting_options()' to create the 'struct ref_sorting *' for the command. If the string list is empty, 'ref_sorting_options()' interprets that as "the absence of '--sort'" and returns the default ref sorting structure (equivalent to "refname" sort). To handle '--no-sort' properly while preserving the "refname" sort in the "absence of --sort'" case, first explicitly add "refname" to the string list *before* parsing options. This alone doesn't actually change any behavior, since 'compare_refs()' already falls back on comparing refnames if two refs are equal w.r.t all other sort keys. Now that the string list is populated by default, '--no-sort' is the only way to empty the 'sorting_options' string list. Update 'ref_sorting_options()' to return a NULL 'struct ref_sorting *' if the string list is empty, and add a condition to 'ref_array_sort()' to skip the sort altogether if the sort structure is NULL. Note that other functions using 'struct ref_sorting *' do not need any changes because they already ignore NULL values. Finally, remove the condition around sorting in 'ls-remote', since it's no longer necessary. Unlike 'for-each-ref' et. al., it does *not* do any sorting by default. This default is preserved by simply leaving its sort key string list empty before parsing options; if no additional sort keys are set, 'struct ref_sorting *' is NULL and sorting is skipped. Signed-off-by: Victoria Dye <vdye@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-11-15 03:53:49 +08:00
if (!sorting_options.nr)
string_list_append(&sorting_options, "refname");
track = git_branch_track;
head = resolve_refdup("HEAD", 0, &head_oid, NULL);
if (!head)
die(_("failed to resolve HEAD as a valid ref"));
if (!strcmp(head, "HEAD"))
filter.detached = 1;
else if (!skip_prefix(head, "refs/heads/", &head))
die(_("HEAD not found below refs/heads!"));
argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
0);
if (!delete && !rename && !copy && !edit_description && !new_upstream &&
!show_current && !unset_upstream && argc == 0)
list = 1;
if (filter.with_commit || filter.no_commit ||
filter.reachable_from || filter.unreachable_from || filter.points_at.nr)
list = 1;
noncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +
!!show_current + !!list + !!edit_description +
!!unset_upstream;
if (noncreate_actions > 1)
usage_with_options(builtin_branch_usage, options);
branch: add --recurse-submodules option for branch creation To improve the submodules UX, we would like to teach Git to handle branches in submodules. Start this process by teaching "git branch" the --recurse-submodules option so that "git branch --recurse-submodules topic" will create the `topic` branch in the superproject and its submodules. Although this commit does not introduce breaking changes, it does not work well with existing --recurse-submodules commands because "git branch --recurse-submodules" writes to the submodule ref store, but most commands only consider the superproject gitlink and ignore the submodule ref store. For example, "git checkout --recurse-submodules" will check out the commits in the superproject gitlinks (and put the submodules in detached HEAD) instead of checking out the submodule branches. Because of this, this commit introduces a new configuration value, `submodule.propagateBranches`. The plan is for Git commands to prioritize submodule ref store information over superproject gitlinks if this value is true. Because "git branch --recurse-submodules" writes to submodule ref stores, for the sake of clarity, it will not function unless this configuration value is set. This commit also includes changes that support working with submodules from a superproject commit because "branch --recurse-submodules" (and future commands) need to read .gitmodules and gitlinks from the superproject commit, but submodules are typically read from the filesystem's .gitmodules and the index's gitlinks. These changes are: * add a submodules_of_tree() helper that gives the relevant information of an in-tree submodule (e.g. path and oid) and initializes the repository * add is_tree_submodule_active() by adding a treeish_name parameter to is_submodule_active() * add the "submoduleNotUpdated" advice to advise users to update the submodules in their trees Incidentally, fix an incorrect usage string that combined the 'list' usage of git branch (-l) with the 'create' usage; this string has been incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use parse_options., 2007-10-07). Helped-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Glen Choo <chooglen@google.com> Reviewed-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-01-29 08:04:45 +08:00
if (recurse_submodules_explicit) {
if (!submodule_propagate_branches)
die(_("branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled"));
if (noncreate_actions)
die(_("--recurse-submodules can only be used to create branches"));
}
recurse_submodules =
(recurse_submodules || recurse_submodules_explicit) &&
submodule_propagate_branches;
if (filter.abbrev == -1)
filter.abbrev = DEFAULT_ABBREV;
filter.ignore_case = icase;
finalize_colopts(&colopts, -1);
if (filter.verbose) {
if (explicitly_enable_column(colopts))
die(_("options '%s' and '%s' cannot be used together"), "--column", "--verbose");
colopts = 0;
}
if (force) {
delete *= 2;
rename *= 2;
branch: add a --copy (-c) option to go with --move (-m) Add the ability to --copy a branch and its reflog and configuration, this uses the same underlying machinery as the --move (-m) option except the reflog and configuration is copied instead of being moved. This is useful for e.g. copying a topic branch to a new version, e.g. work to work-2 after submitting the work topic to the list, while preserving all the tracking info and other configuration that goes with the branch, and unlike --move keeping the other already-submitted branch around for reference. Like --move, when the source branch is the currently checked out branch the HEAD is moved to the destination branch. In the case of --move we don't really have a choice (other than remaining on a detached HEAD) and in order to keep the functionality consistent, we are doing it in similar way for --copy too. The most common usage of this feature is expected to be moving to a new topic branch which is a copy of the current one, in that case moving to the target branch is what the user wants, and doesn't unexpectedly behave differently than --move would. One outstanding caveat of this implementation is that: git checkout maint && git checkout master && git branch -c topic && git checkout - Will check out 'maint' instead of 'master'. This is because the @{-N} feature (or its -1 shorthand "-") relies on HEAD reflogs created by the checkout command, so in this case we'll checkout maint instead of master, as the user might expect. What to do about that is left to a future change. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Sahil Dua <sahildua2305@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-06-19 05:19:16 +08:00
copy *= 2;
}
if (list)
setup_auto_pager("branch", 1);
branch: fix a leak in cmd_branch In 98e7ab6d42 (for-each-ref: delay parsing of --sort=<atom> options, 2021-10-20) a new string_list was introduced to accumulate any "branch.sort" setting. That string_list is cleared in ref_sorting_options(), which is only called when processing the "--list" sub-command. Therefore, with other sub-command, while having any sort option set, a leak is produced, e.g.: $ git config branch.sort invalid_sort_option $ git branch --edit-description Direct leak of 384 byte(s) in 1 object(s) allocated from: ... in xrealloc wrapper.c ... in string_list_append_nodup string-list.c ... in string_list_append string-list.c ... in git_branch_config builtin/branch.c ... in configset_iter config.c ... in repo_config config.c ... in git_config config.c ... in cmd_branch builtin/branch.c ... in run_builtin git.c Indirect leak of 20 byte(s) in 1 object(s) allocated from: ... in xstrdup wrapper.c ... in string_list_append string-list.c ... in git_branch_config builtin/branch.c ... in configset_iter config.c ... in repo_config config.c ... in git_config config.c ... in cmd_branch builtin/branch.c ... in run_builtin git.c We don't have a common clean-up section in cmd_branch(). To avoid refactoring, keep the fix simple, and while we find a better solution which hopefuly will avoid entirely that string_list, when no sort options are needed; let's squelch the leak sanitizer using UNLEAK(). Signed-off-by: Rubén Justo <rjusto@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-06-17 14:41:22 +08:00
UNLEAK(sorting_options);
if (delete) {
if (!argc)
die(_("branch name required"));
return delete_branches(argc, argv, delete > 1, filter.kind, quiet);
} else if (show_current) {
print_current_branch_name();
return 0;
} else if (list) {
/* git branch --list also shows HEAD when it is detached */
if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)
filter.kind |= FILTER_REFS_DETACHED_HEAD;
filter.name_patterns = argv;
/*
* If no sorting parameter is given then we default to sorting
* by 'refname'. This would give us an alphabetically sorted
* array with the 'HEAD' ref at the beginning followed by
* local branches 'refs/heads/...' and finally remote-tracking
* branches 'refs/remotes/...'.
*/
for-each-ref: delay parsing of --sort=<atom> options The for-each-ref family of commands invoke parsers immediately when it sees each --sort=<atom> option, and die before even seeing the other options on the command line when the <atom> is unrecognised. Instead, accumulate them in a string list, and have them parsed into a ref_sorting structure after the command line parsing is done. As a consequence, "git branch --sort=bogus -h" used to fail to give the brief help, which arguably may have been a feature, now does so, which is more consistent with how other options work. The patch is smaller than the actual extent of the "damage" to the codebase, thanks to the fact that the original code consistently used OPT_REF_SORT() macro to handle command line options. We only needed to replace the variable used for the list, and implementation of the callback function used in the macro. The old rule was for the users of the API to: - Declare ref_sorting and ref_sorting_tail variables; - OPT_REF_SORT() macro will instantiate ref_sorting instance (which may barf and die) and append it to the tail; - Append to the tail each ref_sorting read from the configuration by parsing in the config callback (which may barf and die); - See if ref_sorting is null and use ref_sorting_default() instead. Now the rule is not all that different but is simpler: - Declare ref_sorting_options string list. - OPT_REF_SORT() macro will append it to the string list; - Append to the string list the sort key read from the configuration; - call ref_sorting_options() to turn the string list to ref_sorting structure (which also deals with the default value). As side effects, this change also cleans up a few issues: - 95be717c (parse_opt_ref_sorting: always use with NONEG flag, 2019-03-20) muses that "git for-each-ref --no-sort" should simply clear the sort keys accumulated so far; it now does. - The implementation detail of "struct ref_sorting" and the helper function parse_ref_sorting() can now be private to the ref-filter API implementation. - If you set branch.sort to a bogus value, the any "git branch" invocation, not only the listing mode, would abort with the original code; now it doesn't Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-10-21 03:23:53 +08:00
sorting = ref_sorting_options(&sorting_options);
ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
branch: sort detached HEAD based on a flag Change the ref-filter sorting of detached HEAD to check the FILTER_REFS_DETACHED_HEAD flag, instead of relying on the ref description filled-in by get_head_description() to start with "(", which in turn we expect to ASCII-sort before any other reference. For context, we'd like the detached line to appear first at the start of "git branch -l", e.g.: $ git branch -l * (HEAD detached at <hash>) master This doesn't change that, but improves on a fix made in 28438e84e04 (ref-filter: sort detached HEAD lines firstly, 2019-06-18) and gives the Chinese translation the ability to use its preferred punctuation marks again. In Chinese the fullwidth versions of punctuation like "()" are typically written as (U+FF08 fullwidth left parenthesis), (U+FF09 fullwidth right parenthesis) instead[1]. This form is used in both po/zh_{CN,TW}.po in most cases where "()" is translated in a string. Aside from that improvement to the Chinese translation, it also just makes for cleaner code that we mark any special cases in the ref_array we're sorting with flags and make the sort function aware of them, instead of piggy-backing on the general-case of strcmp() doing the right thing. As seen in the amended tests this made reverse sorting a bit more consistent. Before this we'd sometimes sort this message in the middle, now it's consistently at the beginning or end, depending on whether we're doing a normal or reverse sort. Having it at the end doesn't make much sense either, but at least it behaves consistently now. A follow-up commit will make this behavior under reverse sorting even better. I'm removing the "TRANSLATORS" comments that were in the old code while I'm at it. Those were added in d4919bb288e (ref-filter: move get_head_description() from branch.c, 2017-01-10). I think it's obvious from context, string and translation memory in typical translation tools that these are the same or similar string. 1. https://en.wikipedia.org/wiki/Chinese_punctuation#Marks_similar_to_European_punctuation Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-01-07 17:51:52 +08:00
ref_sorting_set_sort_flags_all(
sorting, REF_SORTING_DETACHED_HEAD_FIRST, 1);
print_ref_list(&filter, sorting, &format, &output);
print_columns(&output, colopts, NULL);
string_list_clear(&output, 0);
ref_sorting_release(sorting);
ref_filter_clear(&filter);
return 0;
} else if (edit_description) {
const char *branch_name;
struct strbuf branch_ref = STRBUF_INIT;
struct strbuf buf = STRBUF_INIT;
int ret = 1; /* assume failure */
if (!argc) {
if (filter.detached)
die(_("cannot give description to detached HEAD"));
branch_name = head;
} else if (argc == 1) {
strbuf_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);
branch_name = buf.buf;
} else {
die(_("cannot edit description of more than one branch"));
}
strbuf_addf(&branch_ref, "refs/heads/%s", branch_name);
if (!ref_exists(branch_ref.buf))
error((!argc || branch_checked_out(branch_ref.buf))
? _("no commit on branch '%s' yet")
: _("no branch named '%s'"),
branch_name);
else if (!edit_branch_description(branch_name))
ret = 0; /* happy */
strbuf_release(&branch_ref);
strbuf_release(&buf);
return ret;
} else if (copy || rename) {
if (!argc)
die(_("branch name required"));
else if ((argc == 1) && filter.detached)
die(copy? _("cannot copy the current branch while not on any")
: _("cannot rename the current branch while not on any"));
else if (argc == 1)
copy_or_rename_branch(head, argv[0], copy, copy + rename > 1);
else if (argc == 2)
copy_or_rename_branch(argv[0], argv[1], copy, copy + rename > 1);
else
die(copy? _("too many branches for a copy operation")
: _("too many arguments for a rename operation"));
} else if (new_upstream) {
struct branch *branch;
struct strbuf buf = STRBUF_INIT;
if (!argc)
branch = branch_get(NULL);
else if (argc == 1) {
strbuf_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);
branch = branch_get(buf.buf);
} else
die(_("too many arguments to set new upstream"));
if (!branch) {
if (!argc || !strcmp(argv[0], "HEAD"))
die(_("could not set upstream of HEAD to %s when "
"it does not point to any branch"),
new_upstream);
die(_("no such branch '%s'"), argv[0]);
}
branch: description for non-existent branch errors When the repository does not yet have commits, some errors describe that there is no branch: $ git init -b first $ git branch --edit-description first error: No branch named 'first'. $ git branch --set-upstream-to=upstream fatal: branch 'first' does not exist $ git branch -c second error: refname refs/heads/first not found fatal: Branch copy failed That "first" branch is unborn but to say it doesn't exists is confusing. Options "-c" (copy) and "-m" (rename) show the same error when the origin branch doesn't exists: $ git branch -c non-existent-branch second error: refname refs/heads/non-existent-branch not found fatal: Branch copy failed $ git branch -m non-existent-branch second error: refname refs/heads/non-existent-branch not found fatal: Branch rename failed Note that "--edit-description" without an explicit argument is already considering the _empty repository_ circumstance in its error. Also note that "-m" on the initial branch it is an allowed operation. Make the error descriptions for those branch operations with unborn or non-existent branches, more informative. This is the result of the change: $ git init -b first $ git branch --edit-description first error: No commit on branch 'first' yet. $ git branch --set-upstream-to=upstream fatal: No commit on branch 'first' yet. $ git branch -c second fatal: No commit on branch 'first' yet. $ git branch [-c/-m] non-existent-branch second fatal: No branch named 'non-existent-branch'. Signed-off-by: Rubén Justo <rjusto@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-10-08 08:39:43 +08:00
if (!ref_exists(branch->refname)) {
if (!argc || branch_checked_out(branch->refname))
die(_("no commit on branch '%s' yet"), branch->name);
die(_("branch '%s' does not exist"), branch->name);
branch: description for non-existent branch errors When the repository does not yet have commits, some errors describe that there is no branch: $ git init -b first $ git branch --edit-description first error: No branch named 'first'. $ git branch --set-upstream-to=upstream fatal: branch 'first' does not exist $ git branch -c second error: refname refs/heads/first not found fatal: Branch copy failed That "first" branch is unborn but to say it doesn't exists is confusing. Options "-c" (copy) and "-m" (rename) show the same error when the origin branch doesn't exists: $ git branch -c non-existent-branch second error: refname refs/heads/non-existent-branch not found fatal: Branch copy failed $ git branch -m non-existent-branch second error: refname refs/heads/non-existent-branch not found fatal: Branch rename failed Note that "--edit-description" without an explicit argument is already considering the _empty repository_ circumstance in its error. Also note that "-m" on the initial branch it is an allowed operation. Make the error descriptions for those branch operations with unborn or non-existent branches, more informative. This is the result of the change: $ git init -b first $ git branch --edit-description first error: No commit on branch 'first' yet. $ git branch --set-upstream-to=upstream fatal: No commit on branch 'first' yet. $ git branch -c second fatal: No commit on branch 'first' yet. $ git branch [-c/-m] non-existent-branch second fatal: No branch named 'non-existent-branch'. Signed-off-by: Rubén Justo <rjusto@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-10-08 08:39:43 +08:00
}
dwim_and_setup_tracking(the_repository, branch->name,
new_upstream, BRANCH_TRACK_OVERRIDE,
quiet);
strbuf_release(&buf);
} else if (unset_upstream) {
struct branch *branch;
struct strbuf buf = STRBUF_INIT;
if (!argc)
branch = branch_get(NULL);
else if (argc == 1) {
strbuf_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);
branch = branch_get(buf.buf);
} else
die(_("too many arguments to unset upstream"));
if (!branch) {
if (!argc || !strcmp(argv[0], "HEAD"))
die(_("could not unset upstream of HEAD when "
"it does not point to any branch"));
die(_("no such branch '%s'"), argv[0]);
}
if (!branch_has_merge_config(branch))
die(_("branch '%s' has no upstream information"), branch->name);
strbuf_reset(&buf);
strbuf_addf(&buf, "branch.%s.remote", branch->name);
git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
strbuf_reset(&buf);
strbuf_addf(&buf, "branch.%s.merge", branch->name);
git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
strbuf_release(&buf);
} else if (!noncreate_actions && argc > 0 && argc <= 2) {
branch: add --recurse-submodules option for branch creation To improve the submodules UX, we would like to teach Git to handle branches in submodules. Start this process by teaching "git branch" the --recurse-submodules option so that "git branch --recurse-submodules topic" will create the `topic` branch in the superproject and its submodules. Although this commit does not introduce breaking changes, it does not work well with existing --recurse-submodules commands because "git branch --recurse-submodules" writes to the submodule ref store, but most commands only consider the superproject gitlink and ignore the submodule ref store. For example, "git checkout --recurse-submodules" will check out the commits in the superproject gitlinks (and put the submodules in detached HEAD) instead of checking out the submodule branches. Because of this, this commit introduces a new configuration value, `submodule.propagateBranches`. The plan is for Git commands to prioritize submodule ref store information over superproject gitlinks if this value is true. Because "git branch --recurse-submodules" writes to submodule ref stores, for the sake of clarity, it will not function unless this configuration value is set. This commit also includes changes that support working with submodules from a superproject commit because "branch --recurse-submodules" (and future commands) need to read .gitmodules and gitlinks from the superproject commit, but submodules are typically read from the filesystem's .gitmodules and the index's gitlinks. These changes are: * add a submodules_of_tree() helper that gives the relevant information of an in-tree submodule (e.g. path and oid) and initializes the repository * add is_tree_submodule_active() by adding a treeish_name parameter to is_submodule_active() * add the "submoduleNotUpdated" advice to advise users to update the submodules in their trees Incidentally, fix an incorrect usage string that combined the 'list' usage of git branch (-l) with the 'create' usage; this string has been incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use parse_options., 2007-10-07). Helped-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Glen Choo <chooglen@google.com> Reviewed-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-01-29 08:04:45 +08:00
const char *branch_name = argv[0];
const char *start_name = argc == 2 ? argv[1] : head;
if (filter.kind != FILTER_REFS_BRANCHES)
die(_("the -a, and -r, options to 'git branch' do not take a branch name.\n"
"Did you mean to use: -a|-r --list <pattern>?"));
if (track == BRANCH_TRACK_OVERRIDE)
die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead"));
branch: add --recurse-submodules option for branch creation To improve the submodules UX, we would like to teach Git to handle branches in submodules. Start this process by teaching "git branch" the --recurse-submodules option so that "git branch --recurse-submodules topic" will create the `topic` branch in the superproject and its submodules. Although this commit does not introduce breaking changes, it does not work well with existing --recurse-submodules commands because "git branch --recurse-submodules" writes to the submodule ref store, but most commands only consider the superproject gitlink and ignore the submodule ref store. For example, "git checkout --recurse-submodules" will check out the commits in the superproject gitlinks (and put the submodules in detached HEAD) instead of checking out the submodule branches. Because of this, this commit introduces a new configuration value, `submodule.propagateBranches`. The plan is for Git commands to prioritize submodule ref store information over superproject gitlinks if this value is true. Because "git branch --recurse-submodules" writes to submodule ref stores, for the sake of clarity, it will not function unless this configuration value is set. This commit also includes changes that support working with submodules from a superproject commit because "branch --recurse-submodules" (and future commands) need to read .gitmodules and gitlinks from the superproject commit, but submodules are typically read from the filesystem's .gitmodules and the index's gitlinks. These changes are: * add a submodules_of_tree() helper that gives the relevant information of an in-tree submodule (e.g. path and oid) and initializes the repository * add is_tree_submodule_active() by adding a treeish_name parameter to is_submodule_active() * add the "submoduleNotUpdated" advice to advise users to update the submodules in their trees Incidentally, fix an incorrect usage string that combined the 'list' usage of git branch (-l) with the 'create' usage; this string has been incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use parse_options., 2007-10-07). Helped-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Glen Choo <chooglen@google.com> Reviewed-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-01-29 08:04:45 +08:00
if (recurse_submodules) {
create_branches_recursively(the_repository, branch_name,
start_name, NULL, force,
reflog, quiet, track, 0);
return 0;
}
create_branch(the_repository, branch_name, start_name, force, 0,
reflog, quiet, track, 0);
} else
usage_with_options(builtin_branch_usage, options);
return 0;
}