From 1624333ec1486378c44ce38e4f8ae9d02c07d15a Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Tue, 11 Jan 2022 18:04:59 +0000 Subject: reset: reorder wildcard pathspec conditions Rearrange conditions in method determining whether index expansion is necessary when a pathspec is specified for `git reset`, placing less expensive condition first. Additionally, add details & examples to related code comments to help with readability. Helped-by: Elijah Newren Signed-off-by: Victoria Dye Reviewed-by: Elijah Newren Signed-off-by: Junio C Hamano --- builtin/reset.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'builtin') diff --git a/builtin/reset.c b/builtin/reset.c index b1ff699b43..79b40385b9 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -204,10 +204,16 @@ static int pathspec_needs_expanded_index(const struct pathspec *pathspec) /* * Special case: if the pattern is a path inside the cone * followed by only wildcards, the pattern cannot match - * partial sparse directories, so we don't expand the index. + * partial sparse directories, so we know we don't need to + * expand the index. + * + * Examples: + * - in-cone/foo***: doesn't need expanded index + * - not-in-cone/bar*: may need expanded index + * - **.c: may need expanded index */ - if (path_in_cone_mode_sparse_checkout(item.original, &the_index) && - strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len) + if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len && + path_in_cone_mode_sparse_checkout(item.original, &the_index)) continue; for (pos = 0; pos < active_nr; pos++) { -- cgit v1.3-5-g9baa From 1e9e10e04891a13e5ccd52b36cfadc55dfaa5066 Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Tue, 11 Jan 2022 18:05:00 +0000 Subject: clean: integrate with sparse index Remove full index requirement for `git clean` and test to ensure the index is not expanded in `git clean`. Add to existing test for `git clean` to verify cleanup of untracked files in sparse directories is consistent between sparse index and non-sparse index checkouts. Signed-off-by: Victoria Dye Reviewed-by: Elijah Newren Signed-off-by: Junio C Hamano --- builtin/clean.c | 3 +++ t/t1092-sparse-checkout-compatibility.sh | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) (limited to 'builtin') diff --git a/builtin/clean.c b/builtin/clean.c index 98a2860409..5628fc7103 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -983,6 +983,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS; } + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + if (read_cache() < 0) die(_("index file corrupt")); diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index d5167e7ed6..0558736145 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -764,23 +764,42 @@ test_expect_success 'clean' ' test_all_match git commit -m "ignore bogus files" && run_on_sparse mkdir folder1 && + run_on_all mkdir -p deep/untracked-deep && run_on_all touch folder1/bogus && + run_on_all touch folder1/untracked && + run_on_all touch deep/untracked-deep/bogus && + run_on_all touch deep/untracked-deep/untracked && test_all_match git status --porcelain=v2 && test_all_match git clean -f && test_all_match git status --porcelain=v2 && test_sparse_match ls && test_sparse_match ls folder1 && + run_on_all test_path_exists folder1/bogus && + run_on_all test_path_is_missing folder1/untracked && + run_on_all test_path_exists deep/untracked-deep/bogus && + run_on_all test_path_exists deep/untracked-deep/untracked && + + test_all_match git clean -fd && + test_all_match git status --porcelain=v2 && + test_sparse_match ls && + test_sparse_match ls folder1 && + run_on_all test_path_exists folder1/bogus && + run_on_all test_path_exists deep/untracked-deep/bogus && + run_on_all test_path_is_missing deep/untracked-deep/untracked && test_all_match git clean -xf && test_all_match git status --porcelain=v2 && test_sparse_match ls && test_sparse_match ls folder1 && + run_on_all test_path_is_missing folder1/bogus && + run_on_all test_path_exists deep/untracked-deep/bogus && test_all_match git clean -xdf && test_all_match git status --porcelain=v2 && test_sparse_match ls && test_sparse_match ls folder1 && + run_on_all test_path_is_missing deep/untracked-deep/bogus && test_sparse_match test_path_is_dir folder1 ' @@ -920,6 +939,8 @@ test_expect_success 'sparse-index is not expanded' ' # Wildcard identifies only full sparse directories, no index expansion ensure_not_expanded reset deepest -- folder\* && + ensure_not_expanded clean -fd && + ensure_not_expanded checkout -f update-deep && test_config -C sparse-index pull.twohead ort && ( -- cgit v1.3-5-g9baa From 88078f543b769dc13ae9796372651178584a25a0 Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Tue, 11 Jan 2022 18:05:02 +0000 Subject: checkout-index: add --ignore-skip-worktree-bits option Update `checkout-index` to no longer refresh files that have the `skip-worktree` bit set, exiting with an error if `skip-worktree` filenames are directly provided to `checkout-index`. The newly-added `--ignore-skip-worktree-bits` option provides a mechanism to replicate the old behavior, checking out *all* files specified (even those with `skip-worktree` enabled). The ability to toggle whether files should be checked-out based on `skip-worktree` already exists in `git checkout` and `git restore` (both of which have an `--ignore-skip-worktree-bits` option). The change to, by default, ignore `skip-worktree` files is especially helpful for sparse-checkout; it prevents inadvertent creation of files outside the sparse definition on disk and eliminates the need to expand a sparse index when using the `--all` option. Internal usage of `checkout-index` in `git stash` and `git filter-branch` do not make explicit use of files with `skip-worktree` enabled, so `--ignore-skip-worktree-bits` is not added to them. Helped-by: Elijah Newren Signed-off-by: Victoria Dye Reviewed-by: Elijah Newren Signed-off-by: Junio C Hamano --- Documentation/git-checkout-index.txt | 10 ++++++++-- builtin/checkout-index.c | 13 +++++++++++++ t/t1092-sparse-checkout-compatibility.sh | 27 +++++++++++++++++---------- 3 files changed, 38 insertions(+), 12 deletions(-) (limited to 'builtin') diff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt index 4d33e7be0f..01dbd5cbf5 100644 --- a/Documentation/git-checkout-index.txt +++ b/Documentation/git-checkout-index.txt @@ -12,6 +12,7 @@ SYNOPSIS 'git checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=] [--stage=|all] [--temp] + [--ignore-skip-worktree-bits] [-z] [--stdin] [--] [...] @@ -37,8 +38,9 @@ OPTIONS -a:: --all:: - checks out all files in the index. Cannot be used - together with explicit filenames. + checks out all files in the index except for those with the + skip-worktree bit set (see `--ignore-skip-worktree-bits`). + Cannot be used together with explicit filenames. -n:: --no-create:: @@ -59,6 +61,10 @@ OPTIONS write the content to temporary files. The temporary name associations will be written to stdout. +--ignore-skip-worktree-bits:: + Check out all files, including those with the skip-worktree bit + set. + --stdin:: Instead of taking list of paths from the command line, read list of paths from the standard input. Paths are diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index e21620d964..615a118e2f 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -7,6 +7,7 @@ #define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "config.h" +#include "dir.h" #include "lockfile.h" #include "quote.h" #include "cache-tree.h" @@ -17,6 +18,7 @@ #define CHECKOUT_ALL 4 static int nul_term_line; static int checkout_stage; /* default to checkout stage0 */ +static int ignore_skip_worktree; /* default to 0 */ static int to_tempfile; static char topath[4][TEMPORARY_FILENAME_LENGTH + 1]; @@ -65,6 +67,7 @@ static int checkout_file(const char *name, const char *prefix) int namelen = strlen(name); int pos = cache_name_pos(name, namelen); int has_same_name = 0; + int is_skipped = 1; int did_checkout = 0; int errs = 0; @@ -78,6 +81,9 @@ static int checkout_file(const char *name, const char *prefix) break; has_same_name = 1; pos++; + if (!ignore_skip_worktree && ce_skip_worktree(ce)) + break; + is_skipped = 0; if (ce_stage(ce) != checkout_stage && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) continue; @@ -106,6 +112,9 @@ static int checkout_file(const char *name, const char *prefix) fprintf(stderr, "git checkout-index: %s ", name); if (!has_same_name) fprintf(stderr, "is not in the cache"); + else if (is_skipped) + fprintf(stderr, "has skip-worktree enabled; " + "use '--ignore-skip-worktree-bits' to checkout"); else if (checkout_stage) fprintf(stderr, "does not exist at stage %d", checkout_stage); @@ -125,6 +134,8 @@ static int checkout_all(const char *prefix, int prefix_length) ensure_full_index(&the_index); for (i = 0; i < active_nr ; i++) { struct cache_entry *ce = active_cache[i]; + if (!ignore_skip_worktree && ce_skip_worktree(ce)) + continue; if (ce_stage(ce) != checkout_stage && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) continue; @@ -185,6 +196,8 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) struct option builtin_checkout_index_options[] = { OPT_BOOL('a', "all", &all, N_("check out all files in the index")), + OPT_BOOL(0, "ignore-skip-worktree-bits", &ignore_skip_worktree, + N_("do not skip files with skip-worktree set")), OPT__FORCE(&force, N_("force overwrite of existing files"), 0), OPT__QUIET(&quiet, N_("no warning for existing files and files not in index")), diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index db7ad41109..434ef0433c 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -772,9 +772,14 @@ test_expect_success 'checkout-index inside sparse definition' ' test_expect_success 'checkout-index outside sparse definition' ' init_repos && - # File does not exist on disk yet for sparse checkouts, so checkout-index - # succeeds without -f - test_sparse_match git checkout-index -- folder1/a && + # Without --ignore-skip-worktree-bits, outside-of-cone files will trigger + # an error + test_sparse_match test_must_fail git checkout-index -- folder1/a && + test_i18ngrep "folder1/a has skip-worktree enabled" sparse-checkout-err && + test_path_is_missing folder1/a && + + # With --ignore-skip-worktree-bits, outside-of-cone files are checked out + test_sparse_match git checkout-index --ignore-skip-worktree-bits -- folder1/a && test_cmp sparse-checkout/folder1/a sparse-index/folder1/a && test_cmp sparse-checkout/folder1/a full-checkout/folder1/a && @@ -783,8 +788,8 @@ test_expect_success 'checkout-index outside sparse definition' ' run_on_sparse mkdir -p folder1 && run_on_all cp ../new-a folder1/a && - test_all_match test_must_fail git checkout-index -- folder1/a && - test_all_match git checkout-index -f -- folder1/a && + test_all_match test_must_fail git checkout-index --ignore-skip-worktree-bits -- folder1/a && + test_all_match git checkout-index -f --ignore-skip-worktree-bits -- folder1/a && test_cmp sparse-checkout/folder1/a sparse-index/folder1/a && test_cmp sparse-checkout/folder1/a full-checkout/folder1/a ' @@ -799,14 +804,16 @@ test_expect_success 'checkout-index with folders' ' test_all_match test_must_fail git checkout-index -f -- folder1/ ' -# NEEDSWORK: even in sparse checkouts, checkout-index --all will create all -# files (even those outside the sparse definition) on disk. However, these files -# don't appear in the percentage of tracked files in git status. -test_expect_failure 'checkout-index --all' ' +test_expect_success 'checkout-index --all' ' init_repos && test_all_match git checkout-index --all && - test_sparse_match test_path_is_missing folder1 + test_sparse_match test_path_is_missing folder1 && + + # --ignore-skip-worktree-bits will cause `skip-worktree` files to be + # checked out, causing the outside-of-cone `folder1` to exist on-disk + test_all_match git checkout-index --ignore-skip-worktree-bits --all && + test_all_match test_path_exists folder1 ' test_expect_success 'clean' ' -- cgit v1.3-5-g9baa From 35682ada44554e136677649ac3da8c92342cdae2 Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Tue, 11 Jan 2022 18:05:03 +0000 Subject: checkout-index: integrate with sparse index Add repository settings to allow usage of the sparse index. When using the `--all` option, sparse directories are ignored by default due to the `skip-worktree` flag, so there is no need to expand the index. If `--ignore-skip-worktree-bits` is specified, the index is expanded in order to check out all files. When checking out individual files, existing behavior in a full index is to exit with an error if a directory is specified (as the directory name will not match an index entry). However, it is possible in a sparse index to match a directory name to a sparse directory index entry, but checking out that sparse directory still results in an error on checkout. To reduce some potential confusion for users, `checkout_file(...)` explicitly exits with an informative error if provided with a sparse directory name. The test corresponding to this scenario verifies the error message, which now differs between sparse index and non-sparse index checkouts. Signed-off-by: Victoria Dye Reviewed-by: Elijah Newren Signed-off-by: Junio C Hamano --- builtin/checkout-index.c | 28 ++++++++++++++++++++++++++-- t/t1092-sparse-checkout-compatibility.sh | 11 ++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) (limited to 'builtin') diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 615a118e2f..97e06e8c52 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -67,6 +67,7 @@ static int checkout_file(const char *name, const char *prefix) int namelen = strlen(name); int pos = cache_name_pos(name, namelen); int has_same_name = 0; + int is_file = 0; int is_skipped = 1; int did_checkout = 0; int errs = 0; @@ -81,6 +82,9 @@ static int checkout_file(const char *name, const char *prefix) break; has_same_name = 1; pos++; + if (S_ISSPARSEDIR(ce->ce_mode)) + break; + is_file = 1; if (!ignore_skip_worktree && ce_skip_worktree(ce)) break; is_skipped = 0; @@ -112,6 +116,8 @@ static int checkout_file(const char *name, const char *prefix) fprintf(stderr, "git checkout-index: %s ", name); if (!has_same_name) fprintf(stderr, "is not in the cache"); + else if (!is_file) + fprintf(stderr, "is a sparse directory"); else if (is_skipped) fprintf(stderr, "has skip-worktree enabled; " "use '--ignore-skip-worktree-bits' to checkout"); @@ -130,10 +136,25 @@ static int checkout_all(const char *prefix, int prefix_length) int i, errs = 0; struct cache_entry *last_ce = NULL; - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); for (i = 0; i < active_nr ; i++) { struct cache_entry *ce = active_cache[i]; + + if (S_ISSPARSEDIR(ce->ce_mode)) { + if (!ce_skip_worktree(ce)) + BUG("sparse directory '%s' does not have skip-worktree set", ce->name); + + /* + * If the current entry is a sparse directory and skip-worktree + * entries are being checked out, expand the index and continue + * the loop on the current index position (now pointing to the + * first entry inside the expanded sparse directory). + */ + if (ignore_skip_worktree) { + ensure_full_index(&the_index); + ce = active_cache[i]; + } + } + if (!ignore_skip_worktree && ce_skip_worktree(ce)) continue; if (ce_stage(ce) != checkout_stage @@ -225,6 +246,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); prefix_length = prefix ? strlen(prefix) : 0; + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + if (read_cache() < 0) { die("invalid cache"); } diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index 434ef0433c..0c72c854d8 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -801,7 +801,14 @@ test_expect_success 'checkout-index with folders' ' test_all_match test_must_fail git checkout-index -f -- deep/ && # Outside checkout definition - test_all_match test_must_fail git checkout-index -f -- folder1/ + # Note: although all tests fail (as expected), the messaging differs. For + # non-sparse index checkouts, the error is that the "file" does not appear + # in the index; for sparse checkouts, the error is explicitly that the + # entry is a sparse directory. + run_on_all test_must_fail git checkout-index -f -- folder1/ && + test_cmp full-checkout-err sparse-checkout-err && + ! test_cmp full-checkout-err sparse-index-err && + grep "is a sparse directory" sparse-index-err ' test_expect_success 'checkout-index --all' ' @@ -972,6 +979,8 @@ test_expect_success 'sparse-index is not expanded' ' echo >>sparse-index/untracked.txt && ensure_not_expanded add . && + ensure_not_expanded checkout-index -f a && + ensure_not_expanded checkout-index -f --all && for ref in update-deep update-folder1 update-folder2 update-deep do echo >>sparse-index/README.md && -- cgit v1.3-5-g9baa From c35e9f5ecd00f0c003dc9120d3c68e95e2ba3bd7 Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Tue, 11 Jan 2022 18:05:05 +0000 Subject: update-index: integrate with sparse index Enable use of the sparse index with `update-index`. Most variations of `update-index` work without explicitly expanding the index or making any other updates in or outside of `update-index.c`. The one usage requiring additional changes is `--cacheinfo`; if a file inside a sparse directory was specified, the index would not be expanded until after the cache tree is invalidated, leading to a mismatch between the index and cache tree. This scenario is handled by rearranging `add_index_entry_with_check`, allowing `index_name_stage_pos` to expand the index *before* attempting to invalidate the relevant cache tree path, avoiding cache tree/index corruption. Signed-off-by: Victoria Dye Reviewed-by: Elijah Newren Signed-off-by: Junio C Hamano --- builtin/update-index.c | 3 +++ read-cache.c | 10 +++++++--- t/t1092-sparse-checkout-compatibility.sh | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) (limited to 'builtin') diff --git a/builtin/update-index.c b/builtin/update-index.c index 187203e8bb..605cc693bb 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -1077,6 +1077,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); + prepare_repo_settings(r); + the_repository->settings.command_requires_full_index = 0; + /* we will diagnose later if it turns out that we need to update it */ newfd = hold_locked_index(&lock_file, 0); if (newfd < 0) diff --git a/read-cache.c b/read-cache.c index cbe73f14e5..b4600e954b 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1339,9 +1339,6 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK; int new_only = option & ADD_CACHE_NEW_ONLY; - if (!(option & ADD_CACHE_KEEP_CACHE_TREE)) - cache_tree_invalidate_path(istate, ce->name); - /* * If this entry's path sorts after the last entry in the index, * we can avoid searching for it. @@ -1352,6 +1349,13 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e else pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce), EXPAND_SPARSE); + /* + * Cache tree path should be invalidated only after index_name_stage_pos, + * in case it expands a sparse index. + */ + if (!(option & ADD_CACHE_KEEP_CACHE_TREE)) + cache_tree_invalidate_path(istate, ce->name); + /* existing match? Just replace it. */ if (pos >= 0) { if (!new_only) diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index 91f849f541..fceaba7101 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -1253,6 +1253,21 @@ test_expect_success 'sparse index is not expanded: diff' ' ensure_not_expanded diff --cached ' +test_expect_success 'sparse index is not expanded: update-index' ' + init_repos && + + deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) && + ensure_not_expanded update-index --cacheinfo 100644 $deep_a_oid deep/a && + + echo "test" >sparse-index/README.md && + echo "test2" >sparse-index/a && + rm -f sparse-index/deep/a && + + ensure_not_expanded update-index --add README.md && + ensure_not_expanded update-index a && + ensure_not_expanded update-index --remove deep/a +' + test_expect_success 'sparse index is not expanded: blame' ' init_repos && -- cgit v1.3-5-g9baa From b9ca5e26579ceb820103b49648c01187a4a0dddd Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Tue, 11 Jan 2022 18:05:06 +0000 Subject: update-index: reduce scope of index expansion in do_reupdate Replace unconditional index expansion in 'do_reupdate()' with one scoped to only where a full index is needed. A full index is only required in 'do_reupdate()' when a sparse directory in the index differs from HEAD; in that case, the index is expanded and the operation restarted. Because the index should only be expanded if a sparse directory is modified, add a test ensuring the index is not expanded when differences only exist within the sparse cone. Signed-off-by: Victoria Dye Reviewed-by: Elijah Newren Signed-off-by: Junio C Hamano --- builtin/update-index.c | 14 +++++++++++--- t/t1092-sparse-checkout-compatibility.sh | 5 ++++- 2 files changed, 15 insertions(+), 4 deletions(-) (limited to 'builtin') diff --git a/builtin/update-index.c b/builtin/update-index.c index 605cc693bb..52ecc714d9 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -606,7 +606,7 @@ static struct cache_entry *read_one_ent(const char *which, error("%s: not in %s branch.", path, which); return NULL; } - if (mode == S_IFDIR) { + if (!the_index.sparse_index && mode == S_IFDIR) { if (which) error("%s: not a blob in %s branch.", path, which); return NULL; @@ -743,8 +743,6 @@ static int do_reupdate(int ac, const char **av, */ has_head = 0; redo: - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); for (pos = 0; pos < active_nr; pos++) { const struct cache_entry *ce = active_cache[pos]; struct cache_entry *old = NULL; @@ -761,6 +759,16 @@ static int do_reupdate(int ac, const char **av, discard_cache_entry(old); continue; /* unchanged */ } + + /* At this point, we know the contents of the sparse directory are + * modified with respect to HEAD, so we expand the index and restart + * to process each path individually + */ + if (S_ISSPARSEDIR(ce->ce_mode)) { + ensure_full_index(&the_index); + goto redo; + } + /* Be careful. The working tree may not have the * path anymore, in which case, under 'allow_remove', * or worse yet 'allow_replace', active_nr may decrease. diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index fceaba7101..53f84881de 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -1265,7 +1265,10 @@ test_expect_success 'sparse index is not expanded: update-index' ' ensure_not_expanded update-index --add README.md && ensure_not_expanded update-index a && - ensure_not_expanded update-index --remove deep/a + ensure_not_expanded update-index --remove deep/a && + + ensure_not_expanded reset --soft update-deep && + ensure_not_expanded update-index --add --remove --again ' test_expect_success 'sparse index is not expanded: blame' ' -- cgit v1.3-5-g9baa From ae42fa4c03ef0ffe36468f8918b3d404a277da50 Mon Sep 17 00:00:00 2001 From: Phillip Wood Date: Wed, 26 Jan 2022 13:05:36 +0000 Subject: rebase: factor out checkout for up to date branch This code is heavily indented and it will be convenient later in the series to have it in its own function. Signed-off-by: Phillip Wood Signed-off-by: Junio C Hamano --- builtin/rebase.c | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) (limited to 'builtin') diff --git a/builtin/rebase.c b/builtin/rebase.c index 34b4744e5f..f5c37b7d4a 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -812,6 +812,23 @@ static int rebase_config(const char *var, const char *value, void *data) return git_default_config(var, value, data); } +static int checkout_up_to_date(struct rebase_options *options) +{ + struct strbuf buf = STRBUF_INIT; + int ret = 0; + + strbuf_addf(&buf, "%s: checkout %s", + getenv(GIT_REFLOG_ACTION_ENVIRONMENT), + options->switch_to); + if (reset_head(the_repository, &options->orig_head, "checkout", + options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK, + NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0) + ret = error(_("could not switch to %s"), options->switch_to); + strbuf_release(&buf); + + return ret; +} + /* * Determines whether the commits in from..to are linear, i.e. contain * no merge commits. This function *expects* `from` to be an ancestor of @@ -1673,21 +1690,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!(options.flags & REBASE_FORCE)) { /* Lazily switch to the target branch if needed... */ if (options.switch_to) { - strbuf_reset(&buf); - strbuf_addf(&buf, "%s: checkout %s", - getenv(GIT_REFLOG_ACTION_ENVIRONMENT), - options.switch_to); - if (reset_head(the_repository, - &options.orig_head, "checkout", - options.head_name, - RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, buf.buf, - DEFAULT_REFLOG_ACTION) < 0) { - ret = error(_("could not switch to " - "%s"), - options.switch_to); + ret = checkout_up_to_date(&options); + if (ret) goto cleanup; - } } if (!(options.flags & REBASE_NO_QUIET)) -- cgit v1.3-5-g9baa From 1946d45844c65ede4e3a514a5decf16612ad79f0 Mon Sep 17 00:00:00 2001 From: Phillip Wood Date: Wed, 26 Jan 2022 13:05:41 +0000 Subject: reset_head(): remove action parameter The only use of the action parameter is to setup the error messages for unpack_trees(). All but two cases pass either "checkout" or "reset". The case that passes "reset --hard" would be better passing "reset" so that the error messages match the builtin reset command like all the other callers that are doing a reset. The case that passes "Fast-forwarded" is only updating HEAD and so the parameter is unused in that case as it does not call unpack_trees(). The value to pass to setup_unpack_trees_porcelain() can be determined by checking whether flags contains RESET_HEAD_HARD without the caller having to specify it. Signed-off-by: Phillip Wood Signed-off-by: Junio C Hamano --- builtin/rebase.c | 14 +++++++------- reset.c | 5 +++-- reset.h | 2 +- sequencer.c | 3 +-- 4 files changed, 12 insertions(+), 12 deletions(-) (limited to 'builtin') diff --git a/builtin/rebase.c b/builtin/rebase.c index f5c37b7d4a..2e5a535b54 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -583,7 +583,7 @@ static int move_to_original_branch(struct rebase_options *opts) opts->head_name, oid_to_hex(&opts->onto->object.oid)); strbuf_addf(&head_reflog, "rebase finished: returning to %s", opts->head_name); - ret = reset_head(the_repository, NULL, "", opts->head_name, + ret = reset_head(the_repository, NULL, opts->head_name, RESET_HEAD_REFS_ONLY, orig_head_reflog.buf, head_reflog.buf, DEFAULT_REFLOG_ACTION); @@ -674,7 +674,7 @@ static int run_am(struct rebase_options *opts) free(rebased_patches); strvec_clear(&am.args); - reset_head(the_repository, &opts->orig_head, "checkout", + reset_head(the_repository, &opts->orig_head, opts->head_name, 0, "HEAD", NULL, DEFAULT_REFLOG_ACTION); error(_("\ngit encountered an error while preparing the " @@ -820,7 +820,7 @@ static int checkout_up_to_date(struct rebase_options *options) strbuf_addf(&buf, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options->switch_to); - if (reset_head(the_repository, &options->orig_head, "checkout", + if (reset_head(the_repository, &options->orig_head, options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK, NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0) ret = error(_("could not switch to %s"), options->switch_to); @@ -1272,7 +1272,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) rerere_clear(the_repository, &merge_rr); string_list_clear(&merge_rr, 1); - if (reset_head(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD, + if (reset_head(the_repository, NULL, NULL, RESET_HEAD_HARD, NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) die(_("could not discard worktree changes")); remove_branch_state(the_repository, 0); @@ -1290,7 +1290,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (read_basic_state(&options)) exit(1); - if (reset_head(the_repository, &options.orig_head, "reset", + if (reset_head(the_repository, &options.orig_head, options.head_name, RESET_HEAD_HARD, NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) die(_("could not move back to %s"), @@ -1759,7 +1759,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); - if (reset_head(the_repository, &options.onto->object.oid, "checkout", NULL, + if (reset_head(the_repository, &options.onto->object.oid, NULL, RESET_HEAD_DETACH | RESET_ORIG_HEAD | RESET_HEAD_RUN_POST_CHECKOUT_HOOK, NULL, msg.buf, DEFAULT_REFLOG_ACTION)) @@ -1777,7 +1777,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "rebase finished: %s onto %s", options.head_name ? options.head_name : "detached HEAD", oid_to_hex(&options.onto->object.oid)); - reset_head(the_repository, NULL, "Fast-forwarded", options.head_name, + reset_head(the_repository, NULL, options.head_name, RESET_HEAD_REFS_ONLY, "HEAD", msg.buf, DEFAULT_REFLOG_ACTION); strbuf_release(&msg); diff --git a/reset.c b/reset.c index 3537de91f6..7841b2b2a0 100644 --- a/reset.c +++ b/reset.c @@ -8,7 +8,7 @@ #include "tree.h" #include "unpack-trees.h" -int reset_head(struct repository *r, struct object_id *oid, const char *action, +int reset_head(struct repository *r, struct object_id *oid, const char *switch_to_branch, unsigned flags, const char *reflog_orig_head, const char *reflog_head, const char *default_reflog_action) @@ -23,7 +23,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action, struct lock_file lock = LOCK_INIT; struct unpack_trees_options unpack_tree_opts = { 0 }; struct tree *tree; - const char *reflog_action; + const char *action, *reflog_action; struct strbuf msg = STRBUF_INIT; size_t prefix_len; struct object_id *old_orig = NULL, oid_old_orig; @@ -50,6 +50,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action, if (refs_only) goto reset_head_refs; + action = reset_hard ? "reset" : "checkout"; setup_unpack_trees_porcelain(&unpack_tree_opts, action); unpack_tree_opts.head_idx = 1; unpack_tree_opts.src_index = r->index; diff --git a/reset.h b/reset.h index 12f83c78e2..2daec80425 100644 --- a/reset.h +++ b/reset.h @@ -12,7 +12,7 @@ #define RESET_HEAD_REFS_ONLY (1<<3) #define RESET_ORIG_HEAD (1<<4) -int reset_head(struct repository *r, struct object_id *oid, const char *action, +int reset_head(struct repository *r, struct object_id *oid, const char *switch_to_branch, unsigned flags, const char *reflog_orig_head, const char *reflog_head, const char *default_reflog_action); diff --git a/sequencer.c b/sequencer.c index b4135a78c9..e18329a399 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4137,8 +4137,7 @@ void create_autostash(struct repository *r, const char *path, path); write_file(path, "%s", oid_to_hex(&oid)); printf(_("Created autostash: %s\n"), buf.buf); - if (reset_head(r, NULL, "reset --hard", - NULL, RESET_HEAD_HARD, NULL, NULL, + if (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL, default_reflog_action) < 0) die(_("could not reset --hard")); -- cgit v1.3-5-g9baa From 1526d0fcfd20efca24bc96a4bc14c8d5459ec470 Mon Sep 17 00:00:00 2001 From: Phillip Wood Date: Wed, 26 Jan 2022 13:05:43 +0000 Subject: reset_head(): make default_reflog_action optional This parameter is only needed when a ref is going to be updated and the caller does not pass an explicit reflog message. Callers that are only discarding uncommitted changes in the working tree such as such as "rebase --skip" or create_autostash() do not update any refs so should not have to worry about passing this parameter. This change is not intended to have any user visible changes. The pointer comparison between `oid` and `&head_oid` checks that the caller did not pass an oid to be checked out. As no callers pass RESET_HEAD_RUN_POST_CHECKOUT_HOOK without passing an oid there are no changes to when the post-checkout hook is run. As update_ref() only updates the ref if the oid passed to it differs from the current ref there are no changes to when HEAD is updated. Signed-off-by: Phillip Wood Signed-off-by: Junio C Hamano --- builtin/rebase.c | 10 ++++------ reset.c | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) (limited to 'builtin') diff --git a/builtin/rebase.c b/builtin/rebase.c index 2e5a535b54..82be965915 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -585,8 +585,7 @@ static int move_to_original_branch(struct rebase_options *opts) opts->head_name); ret = reset_head(the_repository, NULL, opts->head_name, RESET_HEAD_REFS_ONLY, - orig_head_reflog.buf, head_reflog.buf, - DEFAULT_REFLOG_ACTION); + orig_head_reflog.buf, head_reflog.buf, NULL); strbuf_release(&orig_head_reflog); strbuf_release(&head_reflog); @@ -822,7 +821,7 @@ static int checkout_up_to_date(struct rebase_options *options) options->switch_to); if (reset_head(the_repository, &options->orig_head, options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0) + NULL, buf.buf, NULL) < 0) ret = error(_("could not switch to %s"), options->switch_to); strbuf_release(&buf); @@ -1273,7 +1272,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) string_list_clear(&merge_rr, 1); if (reset_head(the_repository, NULL, NULL, RESET_HEAD_HARD, - NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) + NULL, NULL, NULL) < 0) die(_("could not discard worktree changes")); remove_branch_state(the_repository, 0); if (read_basic_state(&options)) @@ -1778,8 +1777,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.head_name ? options.head_name : "detached HEAD", oid_to_hex(&options.onto->object.oid)); reset_head(the_repository, NULL, options.head_name, - RESET_HEAD_REFS_ONLY, "HEAD", msg.buf, - DEFAULT_REFLOG_ACTION); + RESET_HEAD_REFS_ONLY, "HEAD", msg.buf, NULL); strbuf_release(&msg); ret = finish_rebase(&options); goto cleanup; diff --git a/reset.c b/reset.c index 56d6e2a06d..4a92e4bc30 100644 --- a/reset.c +++ b/reset.c @@ -22,8 +22,13 @@ static int update_refs(const struct object_id *oid, const char *switch_to_branch size_t prefix_len; int ret; - reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); - strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : default_reflog_action); + if ((update_orig_head && !reflog_orig_head) || !reflog_head) { + if (!default_reflog_action) + BUG("default_reflog_action must be given when reflog messages are omitted"); + reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); + strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : + default_reflog_action); + } prefix_len = msg.len; if (update_orig_head) { @@ -71,6 +76,7 @@ int reset_head(struct repository *r, struct object_id *oid, { unsigned reset_hard = flags & RESET_HEAD_HARD; unsigned refs_only = flags & RESET_HEAD_REFS_ONLY; + unsigned update_orig_head = flags & RESET_ORIG_HEAD; struct object_id *head = NULL, head_oid; struct tree_desc desc[2] = { { NULL }, { NULL } }; struct lock_file lock = LOCK_INIT; @@ -144,8 +150,10 @@ int reset_head(struct repository *r, struct object_id *oid, goto leave_reset_head; } - ret = update_refs(oid, switch_to_branch, head, reflog_head, - reflog_orig_head, default_reflog_action, flags); + if (oid != &head_oid || update_orig_head || switch_to_branch) + ret = update_refs(oid, switch_to_branch, head, reflog_head, + reflog_orig_head, default_reflog_action, + flags); leave_reset_head: rollback_lock_file(&lock); -- cgit v1.3-5-g9baa From b7de153bd9332a992baa6f937372f0b1833f61e5 Mon Sep 17 00:00:00 2001 From: Phillip Wood Date: Wed, 26 Jan 2022 13:05:44 +0000 Subject: create_autostash(): remove unneeded parameter The default_reflog parameter of create_autostash() is passed to reset_head(). However as creating a stash does not involve updating any refs the parameter is not used by reset_head(). Removing the parameter from create_autostash() simplifies the callers. Signed-off-by: Phillip Wood Signed-off-by: Junio C Hamano --- builtin/merge.c | 6 ++---- builtin/rebase.c | 8 ++++---- sequencer.c | 5 ++--- sequencer.h | 3 +-- 4 files changed, 9 insertions(+), 13 deletions(-) (limited to 'builtin') diff --git a/builtin/merge.c b/builtin/merge.c index ea3112e0c0..cb0e4e2225 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -1565,8 +1565,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (autostash) create_autostash(the_repository, - git_path_merge_autostash(the_repository), - "merge"); + git_path_merge_autostash(the_repository)); if (checkout_fast_forward(the_repository, &head_commit->object.oid, &commit->object.oid, @@ -1637,8 +1636,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (autostash) create_autostash(the_repository, - git_path_merge_autostash(the_repository), - "merge"); + git_path_merge_autostash(the_repository)); /* We are going to make a new commit. */ git_committer_info(IDENT_STRICT); diff --git a/builtin/rebase.c b/builtin/rebase.c index 82be965915..3d78b5c8be 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1657,10 +1657,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (repo_read_index(the_repository) < 0) die(_("could not read index")); - if (options.autostash) { - create_autostash(the_repository, state_dir_path("autostash", &options), - DEFAULT_REFLOG_ACTION); - } + if (options.autostash) + create_autostash(the_repository, + state_dir_path("autostash", &options)); + if (require_clean_work_tree(the_repository, "rebase", _("Please commit or stash them."), 1, 1)) { diff --git a/sequencer.c b/sequencer.c index e18329a399..119564f435 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4100,8 +4100,7 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset) return -1; } -void create_autostash(struct repository *r, const char *path, - const char *default_reflog_action) +void create_autostash(struct repository *r, const char *path) { struct strbuf buf = STRBUF_INIT; struct lock_file lock_file = LOCK_INIT; @@ -4138,7 +4137,7 @@ void create_autostash(struct repository *r, const char *path, write_file(path, "%s", oid_to_hex(&oid)); printf(_("Created autostash: %s\n"), buf.buf); if (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL, - default_reflog_action) < 0) + NULL) < 0) die(_("could not reset --hard")); if (discard_index(r->index) < 0 || diff --git a/sequencer.h b/sequencer.h index 05a7d2ba6b..da64473636 100644 --- a/sequencer.h +++ b/sequencer.h @@ -197,8 +197,7 @@ void commit_post_rewrite(struct repository *r, const struct commit *current_head, const struct object_id *new_head); -void create_autostash(struct repository *r, const char *path, - const char *default_reflog_action); +void create_autostash(struct repository *r, const char *path); int save_autostash(const char *path); int apply_autostash(const char *path); int apply_autostash_oid(const char *stash_oid); -- cgit v1.3-5-g9baa From ee464c4e37c7f34c4e5ba2fce35df4149083e5ea Mon Sep 17 00:00:00 2001 From: Phillip Wood Date: Wed, 26 Jan 2022 13:05:45 +0000 Subject: rebase: cleanup reset_head() calls If ORIG_HEAD is not set by passing RESET_ORIG_HEAD then there is no need to pass anything for reflog_orig_head. In addition to the callers fixed in this commit move_to_original_branch() also passes reflog_orig_head without setting ORIG_HEAD. That caller is mistakenly passing the message it wants to put in the branch reflog which is not currently possible so we delay fixing that caller until we can pass the message as the branch reflog. A later commit will make it a BUG() to pass reflog_orig_head without RESET_ORIG_HEAD, that changes cannot be done here as it needs to wait for move_to_original_branch() to be fixed first. Signed-off-by: Phillip Wood Signed-off-by: Junio C Hamano --- builtin/rebase.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'builtin') diff --git a/builtin/rebase.c b/builtin/rebase.c index 3d78b5c8be..fdd822c470 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -675,7 +675,7 @@ static int run_am(struct rebase_options *opts) reset_head(the_repository, &opts->orig_head, opts->head_name, 0, - "HEAD", NULL, DEFAULT_REFLOG_ACTION); + NULL, NULL, DEFAULT_REFLOG_ACTION); error(_("\ngit encountered an error while preparing the " "patches to replay\n" "these revisions:\n" @@ -1777,7 +1777,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.head_name ? options.head_name : "detached HEAD", oid_to_hex(&options.onto->object.oid)); reset_head(the_repository, NULL, options.head_name, - RESET_HEAD_REFS_ONLY, "HEAD", msg.buf, NULL); + RESET_HEAD_REFS_ONLY, NULL, msg.buf, NULL); strbuf_release(&msg); ret = finish_rebase(&options); goto cleanup; -- cgit v1.3-5-g9baa From 6ae8086161d81a707ff36dfdc07f57e4f473e0fd Mon Sep 17 00:00:00 2001 From: Phillip Wood Date: Wed, 26 Jan 2022 13:05:46 +0000 Subject: reset_head(): take struct rebase_head_opts This function takes a confusingly large number of parameters which makes it difficult to remember which order to pass them in. The following commits will add a couple more parameters which makes the problem worse. To address this change the function to take a struct of options. Using a struct means that it is no longer necessary to remember which order to pass the parameters in and anyone reading the code can easily see which value is passed to each parameter. Signed-off-by: Phillip Wood Signed-off-by: Junio C Hamano --- builtin/rebase.c | 57 +++++++++++++++++++++++++++++++++++--------------------- reset.c | 38 ++++++++++++++++++------------------- reset.h | 40 +++++++++++++++++++++++++++++++++++---- sequencer.c | 5 ++--- 4 files changed, 92 insertions(+), 48 deletions(-) (limited to 'builtin') diff --git a/builtin/rebase.c b/builtin/rebase.c index fdd822c470..ecc368dd4f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -571,6 +571,7 @@ static int finish_rebase(struct rebase_options *opts) static int move_to_original_branch(struct rebase_options *opts) { struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; + struct reset_head_opts ropts = { 0 }; int ret; if (!opts->head_name) @@ -583,9 +584,11 @@ static int move_to_original_branch(struct rebase_options *opts) opts->head_name, oid_to_hex(&opts->onto->object.oid)); strbuf_addf(&head_reflog, "rebase finished: returning to %s", opts->head_name); - ret = reset_head(the_repository, NULL, opts->head_name, - RESET_HEAD_REFS_ONLY, - orig_head_reflog.buf, head_reflog.buf, NULL); + ropts.branch = opts->head_name; + ropts.flags = RESET_HEAD_REFS_ONLY; + ropts.orig_head_msg = orig_head_reflog.buf; + ropts.head_msg = head_reflog.buf; + ret = reset_head(the_repository, &ropts); strbuf_release(&orig_head_reflog); strbuf_release(&head_reflog); @@ -669,13 +672,15 @@ static int run_am(struct rebase_options *opts) status = run_command(&format_patch); if (status) { + struct reset_head_opts ropts = { 0 }; unlink(rebased_patches); free(rebased_patches); strvec_clear(&am.args); - reset_head(the_repository, &opts->orig_head, - opts->head_name, 0, - NULL, NULL, DEFAULT_REFLOG_ACTION); + ropts.oid = &opts->orig_head; + ropts.branch = opts->head_name; + ropts.default_reflog_action = DEFAULT_REFLOG_ACTION; + reset_head(the_repository, &ropts); error(_("\ngit encountered an error while preparing the " "patches to replay\n" "these revisions:\n" @@ -814,14 +819,17 @@ static int rebase_config(const char *var, const char *value, void *data) static int checkout_up_to_date(struct rebase_options *options) { struct strbuf buf = STRBUF_INIT; + struct reset_head_opts ropts = { 0 }; int ret = 0; strbuf_addf(&buf, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options->switch_to); - if (reset_head(the_repository, &options->orig_head, - options->head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, buf.buf, NULL) < 0) + ropts.oid = &options->orig_head; + ropts.branch = options->head_name; + ropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK; + ropts.head_msg = buf.buf; + if (reset_head(the_repository, &ropts) < 0) ret = error(_("could not switch to %s"), options->switch_to); strbuf_release(&buf); @@ -1033,6 +1041,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int reschedule_failed_exec = -1; int allow_preemptive_ff = 1; int preserve_merges_selected = 0; + struct reset_head_opts ropts = { 0 }; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -1270,9 +1279,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) rerere_clear(the_repository, &merge_rr); string_list_clear(&merge_rr, 1); - - if (reset_head(the_repository, NULL, NULL, RESET_HEAD_HARD, - NULL, NULL, NULL) < 0) + ropts.flags = RESET_HEAD_HARD; + if (reset_head(the_repository, &ropts) < 0) die(_("could not discard worktree changes")); remove_branch_state(the_repository, 0); if (read_basic_state(&options)) @@ -1289,9 +1297,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (read_basic_state(&options)) exit(1); - if (reset_head(the_repository, &options.orig_head, - options.head_name, RESET_HEAD_HARD, - NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) + ropts.oid = &options.orig_head; + ropts.branch = options.head_name; + ropts.flags = RESET_HEAD_HARD; + ropts.default_reflog_action = DEFAULT_REFLOG_ACTION; + if (reset_head(the_repository, &ropts) < 0) die(_("could not move back to %s"), oid_to_hex(&options.orig_head)); remove_branch_state(the_repository, 0); @@ -1758,10 +1768,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); - if (reset_head(the_repository, &options.onto->object.oid, NULL, - RESET_HEAD_DETACH | RESET_ORIG_HEAD | - RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, msg.buf, DEFAULT_REFLOG_ACTION)) + ropts.oid = &options.onto->object.oid; + ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD | + RESET_HEAD_RUN_POST_CHECKOUT_HOOK; + ropts.head_msg = msg.buf; + ropts.default_reflog_action = DEFAULT_REFLOG_ACTION; + if (reset_head(the_repository, &ropts)) die(_("Could not detach HEAD")); strbuf_release(&msg); @@ -1776,8 +1788,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "rebase finished: %s onto %s", options.head_name ? options.head_name : "detached HEAD", oid_to_hex(&options.onto->object.oid)); - reset_head(the_repository, NULL, options.head_name, - RESET_HEAD_REFS_ONLY, NULL, msg.buf, NULL); + memset(&ropts, 0, sizeof(ropts)); + ropts.branch = options.head_name; + ropts.flags = RESET_HEAD_REFS_ONLY; + ropts.head_msg = msg.buf; + reset_head(the_repository, &ropts); strbuf_release(&msg); ret = finish_rebase(&options); goto cleanup; diff --git a/reset.c b/reset.c index 4a92e4bc30..78145d5c45 100644 --- a/reset.c +++ b/reset.c @@ -8,14 +8,17 @@ #include "tree.h" #include "unpack-trees.h" -static int update_refs(const struct object_id *oid, const char *switch_to_branch, - const struct object_id *head, const char *reflog_head, - const char *reflog_orig_head, - const char *default_reflog_action, unsigned flags) +static int update_refs(const struct reset_head_opts *opts, + const struct object_id *oid, + const struct object_id *head) { - unsigned detach_head = flags & RESET_HEAD_DETACH; - unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK; - unsigned update_orig_head = flags & RESET_ORIG_HEAD; + unsigned detach_head = opts->flags & RESET_HEAD_DETACH; + unsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK; + unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD; + const char *switch_to_branch = opts->branch; + const char *reflog_head = opts->head_msg; + const char *reflog_orig_head = opts->orig_head_msg; + const char *default_reflog_action = opts->default_reflog_action; struct object_id *old_orig = NULL, oid_old_orig; struct strbuf msg = STRBUF_INIT; const char *reflog_action; @@ -69,14 +72,13 @@ static int update_refs(const struct object_id *oid, const char *switch_to_branch return ret; } -int reset_head(struct repository *r, struct object_id *oid, - const char *switch_to_branch, unsigned flags, - const char *reflog_orig_head, const char *reflog_head, - const char *default_reflog_action) +int reset_head(struct repository *r, const struct reset_head_opts *opts) { - unsigned reset_hard = flags & RESET_HEAD_HARD; - unsigned refs_only = flags & RESET_HEAD_REFS_ONLY; - unsigned update_orig_head = flags & RESET_ORIG_HEAD; + const struct object_id *oid = opts->oid; + const char *switch_to_branch = opts->branch; + unsigned reset_hard = opts->flags & RESET_HEAD_HARD; + unsigned refs_only = opts->flags & RESET_HEAD_REFS_ONLY; + unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD; struct object_id *head = NULL, head_oid; struct tree_desc desc[2] = { { NULL }, { NULL } }; struct lock_file lock = LOCK_INIT; @@ -104,9 +106,7 @@ int reset_head(struct repository *r, struct object_id *oid, oid = &head_oid; if (refs_only) - return update_refs(oid, switch_to_branch, head, reflog_head, - reflog_orig_head, default_reflog_action, - flags); + return update_refs(opts, oid, head); action = reset_hard ? "reset" : "checkout"; setup_unpack_trees_porcelain(&unpack_tree_opts, action); @@ -151,9 +151,7 @@ int reset_head(struct repository *r, struct object_id *oid, } if (oid != &head_oid || update_orig_head || switch_to_branch) - ret = update_refs(oid, switch_to_branch, head, reflog_head, - reflog_orig_head, default_reflog_action, - flags); + ret = update_refs(opts, oid, head); leave_reset_head: rollback_lock_file(&lock); diff --git a/reset.h b/reset.h index 2daec80425..a205be2fb8 100644 --- a/reset.h +++ b/reset.h @@ -6,15 +6,47 @@ #define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" +/* Request a detached checkout */ #define RESET_HEAD_DETACH (1<<0) +/* Request a reset rather than a checkout */ #define RESET_HEAD_HARD (1<<1) +/* Run the post-checkout hook */ #define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2) +/* Only update refs, do not touch the worktree */ #define RESET_HEAD_REFS_ONLY (1<<3) +/* Update ORIG_HEAD as well as HEAD */ #define RESET_ORIG_HEAD (1<<4) -int reset_head(struct repository *r, struct object_id *oid, - const char *switch_to_branch, unsigned flags, - const char *reflog_orig_head, const char *reflog_head, - const char *default_reflog_action); +struct reset_head_opts { + /* + * The commit to checkout/reset to. Defaults to HEAD. + */ + const struct object_id *oid; + /* + * Optional branch to switch to. + */ + const char *branch; + /* + * Flags defined above. + */ + unsigned flags; + /* + * Optional reflog message for HEAD, if this omitted but oid or branch + * are given then default_reflog_action must be given. + */ + const char *head_msg; + /* + * Optional reflog message for ORIG_HEAD, if this omitted and flags + * contains RESET_ORIG_HEAD then default_reflog_action must be given. + */ + const char *orig_head_msg; + /* + * Action to use in default reflog messages, only required if a ref is + * being updated and the reflog messages above are omitted. + */ + const char *default_reflog_action; +}; + +int reset_head(struct repository *r, const struct reset_head_opts *opts); #endif diff --git a/sequencer.c b/sequencer.c index 119564f435..55ed074ae7 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4115,6 +4115,7 @@ void create_autostash(struct repository *r, const char *path) if (has_unstaged_changes(r, 1) || has_uncommitted_changes(r, 1)) { struct child_process stash = CHILD_PROCESS_INIT; + struct reset_head_opts ropts = { .flags = RESET_HEAD_HARD }; struct object_id oid; strvec_pushl(&stash.args, @@ -4136,10 +4137,8 @@ void create_autostash(struct repository *r, const char *path) path); write_file(path, "%s", oid_to_hex(&oid)); printf(_("Created autostash: %s\n"), buf.buf); - if (reset_head(r, NULL, NULL, RESET_HEAD_HARD, NULL, NULL, - NULL) < 0) + if (reset_head(r, &ropts) < 0) die(_("could not reset --hard")); - if (discard_index(r->index) < 0 || repo_read_index(r) < 0) die(_("could not read index")); -- cgit v1.3-5-g9baa From 7700ab087b82f71d19134141045b95063e407344 Mon Sep 17 00:00:00 2001 From: Phillip Wood Date: Wed, 26 Jan 2022 13:05:47 +0000 Subject: rebase --apply: fix reflog move_to_original_branch() passes the message intended for the branch reflog as `orig_head_msg`. Fix this by adding a `branch_msg` member to struct reset_head_opts and add a regression test. Note that these reflog messages do not respect GIT_REFLOG_ACTION. They are not alone in that and will be fixed in a future series. The "merge" backend already has tests that check both the branch and HEAD reflogs. Signed-off-by: Phillip Wood Signed-off-by: Junio C Hamano --- builtin/rebase.c | 8 ++++---- reset.c | 12 ++++++++++-- reset.h | 4 ++++ t/t3406-rebase-message.sh | 23 +++++++++++++++++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) (limited to 'builtin') diff --git a/builtin/rebase.c b/builtin/rebase.c index ecc368dd4f..b55a9cff05 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -570,7 +570,7 @@ static int finish_rebase(struct rebase_options *opts) static int move_to_original_branch(struct rebase_options *opts) { - struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; + struct strbuf branch_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; struct reset_head_opts ropts = { 0 }; int ret; @@ -580,17 +580,17 @@ static int move_to_original_branch(struct rebase_options *opts) if (!opts->onto) BUG("move_to_original_branch without onto"); - strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s", + strbuf_addf(&branch_reflog, "rebase finished: %s onto %s", opts->head_name, oid_to_hex(&opts->onto->object.oid)); strbuf_addf(&head_reflog, "rebase finished: returning to %s", opts->head_name); ropts.branch = opts->head_name; ropts.flags = RESET_HEAD_REFS_ONLY; - ropts.orig_head_msg = orig_head_reflog.buf; + ropts.branch_msg = branch_reflog.buf; ropts.head_msg = head_reflog.buf; ret = reset_head(the_repository, &ropts); - strbuf_release(&orig_head_reflog); + strbuf_release(&branch_reflog); strbuf_release(&head_reflog); return ret; } diff --git a/reset.c b/reset.c index 78145d5c45..e02915c0f6 100644 --- a/reset.c +++ b/reset.c @@ -16,6 +16,7 @@ static int update_refs(const struct reset_head_opts *opts, unsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK; unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD; const char *switch_to_branch = opts->branch; + const char *reflog_branch = opts->branch_msg; const char *reflog_head = opts->head_msg; const char *reflog_orig_head = opts->orig_head_msg; const char *default_reflog_action = opts->default_reflog_action; @@ -58,8 +59,9 @@ static int update_refs(const struct reset_head_opts *opts, detach_head ? REF_NO_DEREF : 0, UPDATE_REFS_MSG_ON_ERR); else { - ret = update_ref(reflog_head, switch_to_branch, oid, - NULL, 0, UPDATE_REFS_MSG_ON_ERR); + ret = update_ref(reflog_branch ? reflog_branch : reflog_head, + switch_to_branch, oid, NULL, 0, + UPDATE_REFS_MSG_ON_ERR); if (!ret) ret = create_symref("HEAD", switch_to_branch, reflog_head); @@ -90,6 +92,12 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts) if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) BUG("Not a fully qualified branch: '%s'", switch_to_branch); + if (opts->orig_head_msg && !update_orig_head) + BUG("ORIG_HEAD reflog message given without updating ORIG_HEAD"); + + if (opts->branch_msg && !opts->branch) + BUG("branch reflog message given without a branch"); + if (!refs_only && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) { ret = -1; goto leave_reset_head; diff --git a/reset.h b/reset.h index a205be2fb8..7ef7e43ea8 100644 --- a/reset.h +++ b/reset.h @@ -30,6 +30,10 @@ struct reset_head_opts { * Flags defined above. */ unsigned flags; + /* + * Optional reflog message for branch, defaults to head_msg. + */ + const char *branch_msg; /* * Optional reflog message for HEAD, if this omitted but oid or branch * are given then default_reflog_action must be given. diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh index 77a313f62e..d17b450e81 100755 --- a/t/t3406-rebase-message.sh +++ b/t/t3406-rebase-message.sh @@ -105,6 +105,29 @@ test_expect_success 'GIT_REFLOG_ACTION' ' test_cmp expect actual ' +test_expect_success 'rebase --apply reflog' ' + git checkout -b reflog-apply start && + old_head_reflog="$(git log -g --format=%gs -1 HEAD)" && + + git rebase --apply Y && + + git log -g --format=%gs -4 HEAD >actual && + cat >expect <<-EOF && + rebase finished: returning to refs/heads/reflog-apply + rebase: Z + rebase: checkout Y + $old_head_reflog + EOF + test_cmp expect actual && + + git log -g --format=%gs -2 reflog-apply >actual && + cat >expect <<-EOF && + rebase finished: refs/heads/reflog-apply onto $(git rev-parse Y) + branch: Created from start + EOF + test_cmp expect actual +' + test_expect_success 'rebase -i onto unrelated history' ' git init unrelated && test_commit -C unrelated 1 && -- cgit v1.3-5-g9baa From cd1528ef8ef9847fc27cff4016bf073f04729504 Mon Sep 17 00:00:00 2001 From: Phillip Wood Date: Wed, 26 Jan 2022 13:05:48 +0000 Subject: rebase --apply: set ORIG_HEAD correctly At the start of a rebase, ORIG_HEAD is updated to the tip of the branch being rebased. Unfortunately reset_head() always uses the current value of HEAD for this which is incorrect if the rebase is started with "git rebase " as in that case ORIG_HEAD should be updated to . This only affects the "apply" backend as the "merge" backend does not yet use reset_head() for the initial checkout. Fix this by passing in orig_head when calling reset_head() and add some regression tests. Signed-off-by: Phillip Wood Signed-off-by: Junio C Hamano --- builtin/rebase.c | 1 + reset.c | 4 +++- reset.h | 4 ++++ t/t3418-rebase-continue.sh | 26 ++++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) (limited to 'builtin') diff --git a/builtin/rebase.c b/builtin/rebase.c index b55a9cff05..e942c300f8 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1769,6 +1769,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); ropts.oid = &options.onto->object.oid; + ropts.orig_head = &options.orig_head, ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD | RESET_HEAD_RUN_POST_CHECKOUT_HOOK; ropts.head_msg = msg.buf; diff --git a/reset.c b/reset.c index e02915c0f6..448cb3fd78 100644 --- a/reset.c +++ b/reset.c @@ -15,6 +15,7 @@ static int update_refs(const struct reset_head_opts *opts, unsigned detach_head = opts->flags & RESET_HEAD_DETACH; unsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK; unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD; + const struct object_id *orig_head = opts->orig_head; const char *switch_to_branch = opts->branch; const char *reflog_branch = opts->branch_msg; const char *reflog_head = opts->head_msg; @@ -43,7 +44,8 @@ static int update_refs(const struct reset_head_opts *opts, strbuf_addstr(&msg, "updating ORIG_HEAD"); reflog_orig_head = msg.buf; } - update_ref(reflog_orig_head, "ORIG_HEAD", head, + update_ref(reflog_orig_head, "ORIG_HEAD", + orig_head ? orig_head : head, old_orig, 0, UPDATE_REFS_MSG_ON_ERR); } else if (old_orig) delete_ref(NULL, "ORIG_HEAD", old_orig, 0); diff --git a/reset.h b/reset.h index 7ef7e43ea8..a28f81829d 100644 --- a/reset.h +++ b/reset.h @@ -22,6 +22,10 @@ struct reset_head_opts { * The commit to checkout/reset to. Defaults to HEAD. */ const struct object_id *oid; + /* + * Optional value to set ORIG_HEAD. Defaults to HEAD. + */ + const struct object_id *orig_head; /* * Optional branch to switch to. */ diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 22eca73aa3..130e2f9b55 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -308,4 +308,30 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas test_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec ' +test_orig_head_helper () { + test_when_finished 'git rebase --abort && + git checkout topic && + git reset --hard commit-new-file-F2-on-topic-branch' && + git update-ref -d ORIG_HEAD && + test_must_fail git rebase "$@" && + test_cmp_rev ORIG_HEAD commit-new-file-F2-on-topic-branch +} + +test_orig_head () { + type=$1 + test_expect_success "rebase $type sets ORIG_HEAD correctly" ' + git checkout topic && + git reset --hard commit-new-file-F2-on-topic-branch && + test_orig_head_helper $type main + ' + + test_expect_success "rebase $type sets ORIG_HEAD correctly" ' + git checkout main && + test_orig_head_helper $type main topic + ' +} + +test_orig_head --apply +test_orig_head --merge + test_done -- cgit v1.3-5-g9baa From e89f151db13684924feb0cd0a0ca3a13c1d71516 Mon Sep 17 00:00:00 2001 From: Glen Choo Date: Fri, 28 Jan 2022 16:04:41 -0800 Subject: branch: move --set-upstream-to behavior to dwim_and_setup_tracking() This commit is preparation for a future commit that will simplify create_branch() so that it always creates a branch. This will allow create_branch() to accept a dry_run parameter (which is needed for "git branch --recurse-submodules"). create_branch() used to always create a branch, but 4fc5006676 (Add branch --set-upstream, 2010-01-18) changed it to also be able to set tracking information without creating a branch. Refactor the code that sets tracking information into its own functions dwim_branch_start() and dwim_and_setup_tracking(). Also change an invocation of create_branch() in cmd_branch() in builtin/branch.c to use dwim_and_setup_tracking(), since that invocation is only for setting tracking information (in "git branch --set-upstream-to"). As of this commit, create_branch() is no longer invoked in a way that does not create branches. Helped-by: Jonathan Tan Signed-off-by: Glen Choo Reviewed-by: Jonathan Tan Signed-off-by: Junio C Hamano --- branch.c | 87 +++++++++++++++++++++++++++++++++++++++++++------------- branch.h | 22 ++++++++++++++ builtin/branch.c | 9 ++---- 3 files changed, 92 insertions(+), 26 deletions(-) (limited to 'builtin') diff --git a/branch.c b/branch.c index a4e4631ef1..f3a31930fb 100644 --- a/branch.c +++ b/branch.c @@ -218,9 +218,11 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref) } /* - * This is called when new_ref is branched off of orig_ref, and tries - * to infer the settings for branch..{remote,merge} from the - * config. + * Used internally to set the branch..{remote,merge} config + * settings so that branch 'new_ref' tracks 'orig_ref'. Unlike + * dwim_and_setup_tracking(), this does not do DWIM, i.e. "origin/main" + * will not be expanded to "refs/remotes/origin/main", so it is not safe + * for 'orig_ref' to be raw user input. */ static void setup_tracking(const char *new_ref, const char *orig_ref, enum branch_track track, int quiet) @@ -341,31 +343,37 @@ N_("\n" "will track its remote counterpart, you may want to use\n" "\"git push -u\" to set the upstream config as you push."); -void create_branch(struct repository *r, - const char *name, const char *start_name, - int force, int clobber_head_ok, int reflog, - int quiet, enum branch_track track) +/** + * DWIMs a user-provided ref to determine the starting point for a + * branch and validates it, where: + * + * - r is the repository to validate the branch for + * + * - start_name is the ref that we would like to test. This is + * expanded with DWIM and assigned to out_real_ref. + * + * - track is the tracking mode of the new branch. If tracking is + * explicitly requested, start_name must be a branch (because + * otherwise start_name cannot be tracked) + * + * - out_oid is an out parameter containing the object_id of start_name + * + * - out_real_ref is an out parameter containing the full, 'real' form + * of start_name e.g. refs/heads/main instead of main + * + */ +static void dwim_branch_start(struct repository *r, const char *start_name, + enum branch_track track, char **out_real_ref, + struct object_id *out_oid) { struct commit *commit; struct object_id oid; char *real_ref; - struct strbuf ref = STRBUF_INIT; - int forcing = 0; - int dont_change_ref = 0; int explicit_tracking = 0; if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE) explicit_tracking = 1; - if ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok) - ? validate_branchname(name, &ref) - : validate_new_branchname(name, &ref, force)) { - if (!force) - dont_change_ref = 1; - else - forcing = 1; - } - real_ref = NULL; if (get_oid_mb(start_name, &oid)) { if (explicit_tracking) { @@ -402,7 +410,37 @@ void create_branch(struct repository *r, if ((commit = lookup_commit_reference(r, &oid)) == NULL) die(_("Not a valid branch point: '%s'."), start_name); - oidcpy(&oid, &commit->object.oid); + if (out_real_ref) { + *out_real_ref = real_ref; + real_ref = NULL; + } + if (out_oid) + oidcpy(out_oid, &commit->object.oid); + + FREE_AND_NULL(real_ref); +} + +void create_branch(struct repository *r, + const char *name, const char *start_name, + int force, int clobber_head_ok, int reflog, + int quiet, enum branch_track track) +{ + struct object_id oid; + char *real_ref; + struct strbuf ref = STRBUF_INIT; + int forcing = 0; + int dont_change_ref = 0; + + if ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok) + ? validate_branchname(name, &ref) + : validate_new_branchname(name, &ref, force)) { + if (!force) + dont_change_ref = 1; + else + forcing = 1; + } + + dwim_branch_start(r, start_name, track, &real_ref, &oid); if (reflog) log_all_ref_updates = LOG_REFS_NORMAL; @@ -436,6 +474,15 @@ void create_branch(struct repository *r, free(real_ref); } +void dwim_and_setup_tracking(struct repository *r, const char *new_ref, + const char *orig_ref, enum branch_track track, + int quiet) +{ + char *real_orig_ref; + dwim_branch_start(r, orig_ref, track, &real_orig_ref, NULL); + setup_tracking(new_ref, real_orig_ref, track, quiet); +} + void remove_merge_branch_state(struct repository *r) { unlink(git_path_merge_head(r)); diff --git a/branch.h b/branch.h index 815dcd40c0..ab2315c611 100644 --- a/branch.h +++ b/branch.h @@ -18,6 +18,28 @@ extern enum branch_track git_branch_track; /* Functions for acting on the information about branches. */ +/** + * Sets branch..{remote,merge} config settings such that + * new_ref tracks orig_ref according to the specified tracking mode. + * + * - new_ref is the name of the branch that we are setting tracking + * for. + * + * - orig_ref is the name of the ref that is 'upstream' of new_ref. + * orig_ref will be expanded with DWIM so that the config settings + * are in the correct format e.g. "refs/remotes/origin/main" instead + * of "origin/main". + * + * - track is the tracking mode e.g. BRANCH_TRACK_REMOTE causes + * new_ref to track orig_ref directly, whereas BRANCH_TRACK_INHERIT + * causes new_ref to track whatever orig_ref tracks. + * + * - quiet suppresses tracking information. + */ +void dwim_and_setup_tracking(struct repository *r, const char *new_ref, + const char *orig_ref, enum branch_track track, + int quiet); + /* * Creates a new branch, where: * diff --git a/builtin/branch.c b/builtin/branch.c index a77c4ad7ba..676825242a 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -823,12 +823,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!ref_exists(branch->refname)) die(_("branch '%s' does not exist"), branch->name); - /* - * create_branch takes care of setting up the tracking - * info and making sure new_upstream is correct - */ - create_branch(the_repository, branch->name, new_upstream, - 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE); + dwim_and_setup_tracking(the_repository, branch->name, + new_upstream, BRANCH_TRACK_OVERRIDE, + quiet); } else if (unset_upstream) { struct branch *branch = branch_get(argv[0]); struct strbuf buf = STRBUF_INIT; -- cgit v1.3-5-g9baa From 3f3e76082bc29ff647dff16de9f0145a4d582825 Mon Sep 17 00:00:00 2001 From: Glen Choo Date: Fri, 28 Jan 2022 16:04:43 -0800 Subject: branch: add a dry_run parameter to create_branch() Add a dry_run parameter to create_branch() such that dry_run = 1 will validate a new branch without trying to create it. This will be used in `git branch --recurse-submodules` to ensure that the new branch can be created in all submodules. Signed-off-by: Glen Choo Reviewed-by: Jonathan Tan Signed-off-by: Junio C Hamano --- branch.c | 5 ++++- branch.h | 5 ++++- builtin/branch.c | 2 +- builtin/checkout.c | 3 ++- 4 files changed, 11 insertions(+), 4 deletions(-) (limited to 'builtin') diff --git a/branch.c b/branch.c index df24021f27..02d46a69b8 100644 --- a/branch.c +++ b/branch.c @@ -423,7 +423,7 @@ static void dwim_branch_start(struct repository *r, const char *start_name, void create_branch(struct repository *r, const char *name, const char *start_name, int force, int clobber_head_ok, int reflog, - int quiet, enum branch_track track) + int quiet, enum branch_track track, int dry_run) { struct object_id oid; char *real_ref; @@ -445,6 +445,8 @@ void create_branch(struct repository *r, } dwim_branch_start(r, start_name, track, &real_ref, &oid); + if (dry_run) + goto cleanup; if (reflog) log_all_ref_updates = LOG_REFS_NORMAL; @@ -467,6 +469,7 @@ void create_branch(struct repository *r, if (real_ref && track) setup_tracking(ref.buf + 11, real_ref, track, quiet); +cleanup: strbuf_release(&ref); free(real_ref); } diff --git a/branch.h b/branch.h index cf3a4d3ff3..509cfcc34e 100644 --- a/branch.h +++ b/branch.h @@ -62,11 +62,14 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref, * - track causes the new branch to be configured to merge the remote branch * that start_name is a tracking branch for (if any). * + * - dry_run causes the branch to be validated but not created. + * */ void create_branch(struct repository *r, const char *name, const char *start_name, int force, int clobber_head_ok, - int reflog, int quiet, enum branch_track track); + int reflog, int quiet, enum branch_track track, + int dry_run); /* * Check if 'name' can be a valid name for a branch; die otherwise. diff --git a/builtin/branch.c b/builtin/branch.c index 676825242a..0a49de0281 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -859,7 +859,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) create_branch(the_repository, argv[0], (argc == 2) ? argv[1] : head, - force, 0, reflog, quiet, track); + force, 0, reflog, quiet, track, 0); } else usage_with_options(builtin_branch_usage, options); diff --git a/builtin/checkout.c b/builtin/checkout.c index 1809ac12df..8600860629 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -893,7 +893,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts, opts->new_branch_force ? 1 : 0, opts->new_branch_log, opts->quiet, - opts->track); + opts->track, + 0); new_branch_info->name = opts->new_branch; setup_branch_path(new_branch_info); } -- cgit v1.3-5-g9baa From 6e0a2ca0277e6010cd403c70d8f15b66af345d33 Mon Sep 17 00:00:00 2001 From: Glen Choo Date: Fri, 28 Jan 2022 16:04:44 -0800 Subject: builtin/branch: consolidate action-picking logic in cmd_branch() Consolidate the logic for deciding when to create a new branch in cmd_branch(), and save the result for reuse. Besides making the function more explicit, this allows us to validate options that can only be used when creating a branch. Such an option does not exist yet, but one will be introduced in a subsequent commit. Helped-by: Jonathan Tan Signed-off-by: Glen Choo Reviewed-by: Jonathan Tan Signed-off-by: Junio C Hamano --- builtin/branch.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'builtin') diff --git a/builtin/branch.c b/builtin/branch.c index 0a49de0281..209b1cc442 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -616,14 +616,15 @@ static int edit_branch_description(const char *branch_name) int cmd_branch(int argc, const char **argv, const char *prefix) { - int delete = 0, rename = 0, copy = 0, force = 0, list = 0; - int show_current = 0; - int reflog = 0, edit_description = 0; - int quiet = 0, unset_upstream = 0; + /* 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 */ + int reflog = 0, quiet = 0, icase = 0, force = 0; enum branch_track track; struct ref_filter filter; - int icase = 0; static struct ref_sorting *sorting; struct string_list sorting_options = STRING_LIST_INIT_DUP; struct ref_format format = REF_FORMAT_INIT; @@ -708,8 +709,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) filter.reachable_from || filter.unreachable_from || filter.points_at.nr) list = 1; - if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current + - list + edit_description + unset_upstream > 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); if (filter.abbrev == -1) @@ -849,7 +852,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) 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 (argc > 0 && argc <= 2) { + } else if (!noncreate_actions && argc > 0 && argc <= 2) { 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 ?")); -- cgit v1.3-5-g9baa From 540776406974dfcacb77e94a42f4bdfd4b15b4fe Mon Sep 17 00:00:00 2001 From: Chen Bojun Date: Sat, 29 Jan 2022 14:35:38 +0800 Subject: receive-pack: purge temporary data if no command is ready to run When pushing a hidden ref, e.g.: $ git push origin HEAD:refs/hidden/foo "receive-pack" will reject our request with an error message like this: ! [remote rejected] HEAD -> refs/hidden/foo (deny updating a hidden ref) The remote side ("git-receive-pack") will not create the hidden ref as expected, but the pack file sent by "git-send-pack" is left inside the remote repository. I.e. the quarantine directory is not purged as it should be. Add a checkpoint before calling "tmp_objdir_migrate()" and after calling the "pre-receive" hook to purge that temporary data in the quarantine area when there is no command ready to run. The reason we do not add the checkpoint before the "pre-receive" hook, but after it, is that the "pre-receive" hook is called with a switch-off "skip_broken" flag, and all commands, even broken ones, should be fed by calling "feed_receive_hook()". Add a new test case in t5516 as well. Helped-by: Jiang Xin Helped-by: Teng Long Signed-off-by: Chen Bojun Signed-off-by: Junio C Hamano --- builtin/receive-pack.c | 9 +++++++++ t/t5516-fetch-push.sh | 8 ++++++++ 2 files changed, 17 insertions(+) (limited to 'builtin') diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 9f4a0b816c..a0b193ab3f 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1971,6 +1971,15 @@ static void execute_commands(struct command *commands, return; } + /* + * If there is no command ready to run, should return directly to destroy + * temporary data in the quarantine area. + */ + for (cmd = commands; cmd && cmd->error_string; cmd = cmd->next) + ; /* nothing */ + if (!cmd) + return; + /* * Now we'll start writing out refs, which means the objects need * to be in their final positions so that other processes can see them. diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 2f04cf9a1c..da70c45857 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -1809,4 +1809,12 @@ test_expect_success 'refuse fetch to current branch of bare repository worktree' git -C bare.git fetch -u .. HEAD:wt ' +test_expect_success 'refuse to push a hidden ref, and make sure do not pollute the repository' ' + mk_empty testrepo && + git -C testrepo config receive.hiderefs refs/hidden && + git -C testrepo config receive.unpackLimit 1 && + test_must_fail git push testrepo HEAD:refs/hidden/foo && + test_dir_is_empty testrepo/.git/objects/pack +' + test_done -- cgit v1.3-5-g9baa From 757e75c81e4332e363b90a0534a657b1bb6546fa Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Tue, 1 Feb 2022 20:19:45 -0800 Subject: patch-id: fix scan_hunk_header on diffs with 1 line of before/after Normally diffs will contain a hunk header of the format "@@ -2,2 +2,15 @@ code". However when there is only 1 line of change, the unified diff format allows for the second comma separated value to be omitted in either before or after line counts. This can produce hunk headers that look like "@@ -2 +2,18 @@ code" or "@@ -2,2 +2 @@ code". As a result, scan_hunk_header mistakenly returns the line number as line count, which then results in unpredictable parsing errors with the rest of the patch, including giving multiple lines of output for a single commit. Fix by explicitly setting line count to 1 when there is no comma, and add a test. apply.c contains this same logic except it is correct. A worthwhile future project might be to unify these two diff parsers so they both benefit from fixes. Signed-off-by: Jerry Zhang Signed-off-by: Junio C Hamano --- builtin/patch-id.c | 9 +++++++-- t/t4204-patch-id.sh | 31 ++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) (limited to 'builtin') diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 822ffff51f..881fcf3273 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -32,8 +32,12 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) n = strspn(q, digits); if (q[n] == ',') { q += n + 1; + *p_before = atoi(q); n = strspn(q, digits); + } else { + *p_before = 1; } + if (n == 0 || q[n] != ' ' || q[n+1] != '+') return 0; @@ -41,13 +45,14 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) n = strspn(r, digits); if (r[n] == ',') { r += n + 1; + *p_after = atoi(r); n = strspn(r, digits); + } else { + *p_after = 1; } if (n == 0) return 0; - *p_before = atoi(q); - *p_after = atoi(r); return 1; } diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh index 2bc940a07e..a730c0db98 100755 --- a/t/t4204-patch-id.sh +++ b/t/t4204-patch-id.sh @@ -38,7 +38,7 @@ calc_patch_id () { shift git patch-id "$@" >patch-id.output && sed "s/ .*//" patch-id.output >patch-id_"$patch_name" && - test_line_count -gt 0 patch-id_"$patch_name" + test_line_count -eq 1 patch-id_"$patch_name" } get_top_diff () { @@ -200,4 +200,33 @@ test_expect_success 'patch-id handles no-nl-at-eof markers' ' calc_patch_id withnl diffu1 <<-\EOF && + diff --git a/bar b/bar + index bdaf90f..31051f6 100644 + --- a/bar + +++ b/bar + @@ -2 +2,2 @@ + b + +c + diff --git a/car b/car + index 00750ed..2ae5e34 100644 + --- a/car + +++ b/car + @@ -1 +1,2 @@ + 3 + +d + diff --git a/foo b/foo + index e439850..7146eb8 100644 + --- a/foo + +++ b/foo + @@ -2 +2,2 @@ + a + +e + EOF + calc_patch_id diffu1 Date: Fri, 28 Jan 2022 16:04:45 -0800 Subject: 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 Signed-off-by: Glen Choo Reviewed-by: Jonathan Tan Signed-off-by: Junio C Hamano --- Documentation/config/advice.txt | 3 + Documentation/config/submodule.txt | 37 +++-- Documentation/git-branch.txt | 19 ++- advice.c | 1 + advice.h | 1 + branch.c | 141 ++++++++++++++++++ branch.h | 29 ++++ builtin/branch.c | 44 +++++- builtin/submodule--helper.c | 38 +++++ submodule-config.c | 61 ++++++++ submodule-config.h | 34 +++++ submodule.c | 11 +- submodule.h | 3 + t/t3207-branch-submodule.sh | 292 +++++++++++++++++++++++++++++++++++++ 14 files changed, 694 insertions(+), 20 deletions(-) create mode 100755 t/t3207-branch-submodule.sh (limited to 'builtin') diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt index 063eec2511..adee26fbbb 100644 --- a/Documentation/config/advice.txt +++ b/Documentation/config/advice.txt @@ -116,6 +116,9 @@ advice.*:: submoduleAlternateErrorStrategyDie:: Advice shown when a submodule.alternateErrorStrategy option configured to "die" causes a fatal error. + submodulesNotUpdated:: + Advice shown when a user runs a submodule command that fails + because `git submodule update --init` was not run. addIgnoredFile:: Advice shown if a user attempts to add an ignored file to the index. diff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt index ee454f8126..6490527b45 100644 --- a/Documentation/config/submodule.txt +++ b/Documentation/config/submodule.txt @@ -59,18 +59,33 @@ submodule.active:: submodule.recurse:: A boolean indicating if commands should enable the `--recurse-submodules` - option by default. - Applies to all commands that support this option - (`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`, - `restore` and `switch`) except `clone` and `ls-files`. + option by default. Defaults to false. ++ +When set to true, it can be deactivated via the +`--no-recurse-submodules` option. Note that some Git commands +lacking this option may call some of the above commands affected by +`submodule.recurse`; for instance `git remote update` will call +`git fetch` but does not have a `--no-recurse-submodules` option. +For these commands a workaround is to temporarily change the +configuration value by using `git -c submodule.recurse=0`. ++ +The following list shows the commands that accept +`--recurse-submodules` and whether they are supported by this +setting. + +* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, +`reset`, `restore` and `switch` are always supported. +* `clone` and `ls-files` are not supported. +* `branch` is supported only if `submodule.propagateBranches` is +enabled + +submodule.propagateBranches:: + [EXPERIMENTAL] A boolean that enables branching support when + using `--recurse-submodules` or `submodule.recurse=true`. + Enabling this will allow certain commands to accept + `--recurse-submodules` and certain commands that already accept + `--recurse-submodules` will now consider branches. Defaults to false. - When set to true, it can be deactivated via the - `--no-recurse-submodules` option. Note that some Git commands - lacking this option may call some of the above commands affected by - `submodule.recurse`; for instance `git remote update` will call - `git fetch` but does not have a `--no-recurse-submodules` option. - For these commands a workaround is to temporarily change the - configuration value by using `git -c submodule.recurse=0`. submodule.fetchJobs:: Specifies how many submodules are fetched/cloned at the same time. diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 731e340cbc..c8b4f9ce3c 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -16,7 +16,8 @@ SYNOPSIS [--points-at ] [--format=] [(-r | --remotes) | (-a | --all)] [--list] [...] -'git branch' [--track[=(direct|inherit)] | --no-track] [-f] [] +'git branch' [--track[=(direct|inherit)] | --no-track] [-f] + [--recurse-submodules] [] 'git branch' (--set-upstream-to= | -u ) [] 'git branch' --unset-upstream [] 'git branch' (-m | -M) [] @@ -235,6 +236,22 @@ how the `branch..remote` and `branch..merge` options are used. Do not set up "upstream" configuration, even if the branch.autoSetupMerge configuration variable is set. +--recurse-submodules:: + THIS OPTION IS EXPERIMENTAL! Causes the current command to + recurse into submodules if `submodule.propagateBranches` is + enabled. See `submodule.propagateBranches` in + linkgit:git-config[1]. Currently, only branch creation is + supported. ++ +When used in branch creation, a new branch will be created +in the superproject and all of the submodules in the superproject's +. In submodules, the branch will point to the submodule +commit in the superproject's but the branch's tracking +information will be set up based on the submodule's branches and remotes +e.g. `git branch --recurse-submodules topic origin/main` will create the +submodule branch "topic" that points to the submodule commit in the +superproject's "origin/main", but tracks the submodule's "origin/main". + --set-upstream:: As this option had confusing syntax, it is no longer supported. Please use `--track` or `--set-upstream-to` instead. diff --git a/advice.c b/advice.c index 1dfc91d176..e00d30254c 100644 --- a/advice.c +++ b/advice.c @@ -70,6 +70,7 @@ static struct { [ADVICE_STATUS_HINTS] = { "statusHints", 1 }, [ADVICE_STATUS_U_OPTION] = { "statusUoption", 1 }, [ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 }, + [ADVICE_SUBMODULES_NOT_UPDATED] = { "submodulesNotUpdated", 1 }, [ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath", 1 }, [ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor", 1 }, }; diff --git a/advice.h b/advice.h index 601265fd10..a7521d6087 100644 --- a/advice.h +++ b/advice.h @@ -44,6 +44,7 @@ struct string_list; ADVICE_STATUS_HINTS, ADVICE_STATUS_U_OPTION, ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE, + ADVICE_SUBMODULES_NOT_UPDATED, ADVICE_UPDATE_SPARSE_PATH, ADVICE_WAITING_FOR_EDITOR, ADVICE_SKIPPED_CHERRY_PICKS, diff --git a/branch.c b/branch.c index 02d46a69b8..70026b3c79 100644 --- a/branch.c +++ b/branch.c @@ -8,6 +8,8 @@ #include "sequencer.h" #include "commit.h" #include "worktree.h" +#include "submodule-config.h" +#include "run-command.h" struct tracking { struct refspec_item spec; @@ -483,6 +485,145 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref, setup_tracking(new_ref, real_orig_ref, track, quiet); } +/** + * Creates a branch in a submodule by calling + * create_branches_recursively() in a child process. The child process + * is necessary because install_branch_config_multiple_remotes() (which + * is called by setup_tracking()) does not support writing configs to + * submodules. + */ +static int submodule_create_branch(struct repository *r, + const struct submodule *submodule, + const char *name, const char *start_oid, + const char *tracking_name, int force, + int reflog, int quiet, + enum branch_track track, int dry_run) +{ + int ret = 0; + struct child_process child = CHILD_PROCESS_INIT; + struct strbuf child_err = STRBUF_INIT; + struct strbuf out_buf = STRBUF_INIT; + char *out_prefix = xstrfmt("submodule '%s': ", submodule->name); + child.git_cmd = 1; + child.err = -1; + child.stdout_to_stderr = 1; + + prepare_other_repo_env(&child.env_array, r->gitdir); + /* + * submodule_create_branch() is indirectly invoked by "git + * branch", but we cannot invoke "git branch" in the child + * process. "git branch" accepts a branch name and start point, + * where the start point is assumed to provide both the OID + * (start_oid) and the branch to use for tracking + * (tracking_name). But when recursing through submodules, + * start_oid and tracking name need to be specified separately + * (see create_branches_recursively()). + */ + strvec_pushl(&child.args, "submodule--helper", "create-branch", NULL); + if (dry_run) + strvec_push(&child.args, "--dry-run"); + if (force) + strvec_push(&child.args, "--force"); + if (quiet) + strvec_push(&child.args, "--quiet"); + if (reflog) + strvec_push(&child.args, "--create-reflog"); + if (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT) + strvec_push(&child.args, "--track"); + + strvec_pushl(&child.args, name, start_oid, tracking_name, NULL); + + if ((ret = start_command(&child))) + return ret; + ret = finish_command(&child); + strbuf_read(&child_err, child.err, 0); + strbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len); + + if (ret) + fprintf(stderr, "%s", out_buf.buf); + else + printf("%s", out_buf.buf); + + strbuf_release(&child_err); + strbuf_release(&out_buf); + return ret; +} + +void create_branches_recursively(struct repository *r, const char *name, + const char *start_commitish, + const char *tracking_name, int force, + int reflog, int quiet, enum branch_track track, + int dry_run) +{ + int i = 0; + char *branch_point = NULL; + struct object_id super_oid; + struct submodule_entry_list submodule_entry_list; + + /* Perform dwim on start_commitish to get super_oid and branch_point. */ + dwim_branch_start(r, start_commitish, BRANCH_TRACK_NEVER, + &branch_point, &super_oid); + + /* + * If we were not given an explicit name to track, then assume we are at + * the top level and, just like the non-recursive case, the tracking + * name is the branch point. + */ + if (!tracking_name) + tracking_name = branch_point; + + submodules_of_tree(r, &super_oid, &submodule_entry_list); + /* + * Before creating any branches, first check that the branch can + * be created in every submodule. + */ + for (i = 0; i < submodule_entry_list.entry_nr; i++) { + if (submodule_entry_list.entries[i].repo == NULL) { + if (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED)) + advise(_("You may try updating the submodules using 'git checkout %s && git submodule update --init'"), + start_commitish); + die(_("submodule '%s': unable to find submodule"), + submodule_entry_list.entries[i].submodule->name); + } + + if (submodule_create_branch( + submodule_entry_list.entries[i].repo, + submodule_entry_list.entries[i].submodule, name, + oid_to_hex(&submodule_entry_list.entries[i] + .name_entry->oid), + tracking_name, force, reflog, quiet, track, 1)) + die(_("submodule '%s': cannot create branch '%s'"), + submodule_entry_list.entries[i].submodule->name, + name); + } + + create_branch(the_repository, name, start_commitish, force, 0, reflog, quiet, + BRANCH_TRACK_NEVER, dry_run); + if (dry_run) + return; + /* + * NEEDSWORK If tracking was set up in the superproject but not the + * submodule, users might expect "git branch --recurse-submodules" to + * fail or give a warning, but this is not yet implemented because it is + * tedious to determine whether or not tracking was set up in the + * superproject. + */ + setup_tracking(name, tracking_name, track, quiet); + + for (i = 0; i < submodule_entry_list.entry_nr; i++) { + if (submodule_create_branch( + submodule_entry_list.entries[i].repo, + submodule_entry_list.entries[i].submodule, name, + oid_to_hex(&submodule_entry_list.entries[i] + .name_entry->oid), + tracking_name, force, reflog, quiet, track, 0)) + die(_("submodule '%s': cannot create branch '%s'"), + submodule_entry_list.entries[i].submodule->name, + name); + repo_clear(submodule_entry_list.entries[i].repo); + } +} + void remove_merge_branch_state(struct repository *r) { unlink(git_path_merge_head(r)); diff --git a/branch.h b/branch.h index 509cfcc34e..04df2aa5b5 100644 --- a/branch.h +++ b/branch.h @@ -71,6 +71,35 @@ void create_branch(struct repository *r, int reflog, int quiet, enum branch_track track, int dry_run); +/* + * Creates a new branch in a repository and its submodules (and its + * submodules, recursively). The parameters are mostly analogous to + * those of create_branch() except for start_name, which is represented + * by two different parameters: + * + * - start_commitish is the commit-ish, in repository r, that determines + * which commits the branches will point to. The superproject branch + * will point to the commit of start_commitish and the submodule + * branches will point to the gitlink commit oids in start_commitish's + * tree. + * + * - tracking_name is the name of the ref, in repository r, that will be + * used to set up tracking information. This value is propagated to + * all submodules, which will evaluate the ref using their own ref + * stores. If NULL, this defaults to start_commitish. + * + * When this function is called on the superproject, start_commitish + * can be any user-provided ref and tracking_name can be NULL (similar + * to create_branches()). But when recursing through submodules, + * start_commitish is the plain gitlink commit oid. Since the oid cannot + * be used for tracking information, tracking_name is propagated and + * used for tracking instead. + */ +void create_branches_recursively(struct repository *r, const char *name, + const char *start_commitish, + const char *tracking_name, int force, + int reflog, int quiet, enum branch_track track, + int dry_run); /* * Check if 'name' can be a valid name for a branch; die otherwise. * Return 1 if the named branch already exists; return 0 otherwise. diff --git a/builtin/branch.c b/builtin/branch.c index 209b1cc442..d9a3ad53dd 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -27,7 +27,8 @@ static const char * const builtin_branch_usage[] = { N_("git branch [] [-r | -a] [--merged] [--no-merged]"), - N_("git branch [] [-l] [-f] []"), + N_("git branch [] [-f] [--recurse-submodules] []"), + N_("git branch [] [-l] [...]"), N_("git branch [] [-r] (-d | -D) ..."), N_("git branch [] (-m | -M) [] "), N_("git branch [] (-c | -C) [] "), @@ -38,6 +39,8 @@ static const char * const builtin_branch_usage[] = { static const char *head; static struct object_id head_oid; +static int recurse_submodules = 0; +static int submodule_propagate_branches = 0; static int branch_use_color = -1; static char branch_colors[][COLOR_MAXLEN] = { @@ -99,6 +102,15 @@ static int git_branch_config(const char *var, const char *value, void *cb) return config_error_nonbool(var); return color_parse(value, branch_colors[slot]); } + 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; + } + return git_color_default_config(var, value, cb); } @@ -622,7 +634,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) const char *new_upstream = NULL; int noncreate_actions = 0; /* possible options */ - int reflog = 0, quiet = 0, icase = 0, force = 0; + int reflog = 0, quiet = 0, icase = 0, force = 0, + recurse_submodules_explicit = 0; enum branch_track track; struct ref_filter filter; static struct ref_sorting *sorting; @@ -673,6 +686,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) 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")), + 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(), }; @@ -715,6 +729,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (noncreate_actions > 1) usage_with_options(builtin_branch_usage, options); + 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; @@ -853,6 +878,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); strbuf_release(&buf); } else if (!noncreate_actions && argc > 0 && argc <= 2) { + 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 ?")); @@ -860,10 +888,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (track == BRANCH_TRACK_OVERRIDE) die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.")); - create_branch(the_repository, - argv[0], (argc == 2) ? argv[1] : head, - force, 0, reflog, quiet, track, 0); - + 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); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index e630f0c730..44b6283c08 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -20,6 +20,7 @@ #include "diff.h" #include "object-store.h" #include "advice.h" +#include "branch.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -2983,6 +2984,42 @@ static int module_set_branch(int argc, const char **argv, const char *prefix) return !!ret; } +static int module_create_branch(int argc, const char **argv, const char *prefix) +{ + enum branch_track track; + int quiet = 0, force = 0, reflog = 0, dry_run = 0; + + struct option options[] = { + OPT__QUIET(&quiet, N_("print only error messages")), + OPT__FORCE(&force, N_("force creation"), 0), + OPT_BOOL(0, "create-reflog", &reflog, + N_("create the branch's reflog")), + OPT_SET_INT('t', "track", &track, + N_("set up tracking mode (see git-pull(1))"), + BRANCH_TRACK_EXPLICIT), + OPT__DRY_RUN(&dry_run, + N_("show whether the branch would be created")), + OPT_END() + }; + const char *const usage[] = { + N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] "), + NULL + }; + + git_config(git_default_config, NULL); + track = git_branch_track; + argc = parse_options(argc, argv, prefix, options, usage, 0); + + if (argc != 3) + usage_with_options(usage, options); + + if (!quiet && !dry_run) + printf_ln(_("creating branch '%s'"), argv[0]); + + create_branches_recursively(the_repository, argv[0], argv[1], argv[2], + force, reflog, quiet, track, dry_run); + return 0; +} struct add_data { const char *prefix; const char *branch; @@ -3389,6 +3426,7 @@ static struct cmd_struct commands[] = { {"config", module_config, 0}, {"set-url", module_set_url, 0}, {"set-branch", module_set_branch, 0}, + {"create-branch", module_create_branch, 0}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) diff --git a/submodule-config.c b/submodule-config.c index f95344028b..c9f54bc72d 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -7,6 +7,7 @@ #include "strbuf.h" #include "object-store.h" #include "parse-options.h" +#include "tree-walk.h" /* * submodule cache lookup structure @@ -726,6 +727,66 @@ const struct submodule *submodule_from_path(struct repository *r, return config_from(r->submodule_cache, treeish_name, path, lookup_path); } +/** + * Used internally by submodules_of_tree(). Recurses into 'treeish_name' + * and appends submodule entries to 'out'. The submodule_cache expects + * a root-level treeish_name and paths, so keep track of these values + * with 'root_tree' and 'prefix'. + */ +static void traverse_tree_submodules(struct repository *r, + const struct object_id *root_tree, + char *prefix, + const struct object_id *treeish_name, + struct submodule_entry_list *out) +{ + struct tree_desc tree; + struct submodule_tree_entry *st_entry; + struct name_entry *name_entry; + char *tree_path = NULL; + + name_entry = xmalloc(sizeof(*name_entry)); + + fill_tree_descriptor(r, &tree, treeish_name); + while (tree_entry(&tree, name_entry)) { + if (prefix) + tree_path = + mkpathdup("%s/%s", prefix, name_entry->path); + else + tree_path = xstrdup(name_entry->path); + + if (S_ISGITLINK(name_entry->mode) && + is_tree_submodule_active(r, root_tree, tree_path)) { + st_entry = xmalloc(sizeof(*st_entry)); + st_entry->name_entry = xmalloc(sizeof(*st_entry->name_entry)); + *st_entry->name_entry = *name_entry; + st_entry->submodule = + submodule_from_path(r, root_tree, tree_path); + st_entry->repo = xmalloc(sizeof(*st_entry->repo)); + if (repo_submodule_init(st_entry->repo, r, tree_path, + root_tree)) + FREE_AND_NULL(st_entry->repo); + + ALLOC_GROW(out->entries, out->entry_nr + 1, + out->entry_alloc); + out->entries[out->entry_nr++] = *st_entry; + } else if (S_ISDIR(name_entry->mode)) + traverse_tree_submodules(r, root_tree, tree_path, + &name_entry->oid, out); + free(tree_path); + } +} + +void submodules_of_tree(struct repository *r, + const struct object_id *treeish_name, + struct submodule_entry_list *out) +{ + CALLOC_ARRAY(out->entries, 0); + out->entry_nr = 0; + out->entry_alloc = 0; + + traverse_tree_submodules(r, treeish_name, NULL, treeish_name, out); +} + void submodule_free(struct repository *r) { if (r->submodule_cache) diff --git a/submodule-config.h b/submodule-config.h index 65875b94ea..fa229a8b97 100644 --- a/submodule-config.h +++ b/submodule-config.h @@ -6,6 +6,7 @@ #include "hashmap.h" #include "submodule.h" #include "strbuf.h" +#include "tree-walk.h" /** * The submodule config cache API allows to read submodule @@ -101,4 +102,37 @@ int check_submodule_name(const char *name); void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules); void update_clone_config_from_gitmodules(int *max_jobs); +/* + * Submodule entry that contains relevant information about a + * submodule in a tree. + */ +struct submodule_tree_entry { + /* The submodule's tree entry. */ + struct name_entry *name_entry; + /* + * A struct repository corresponding to the submodule. May be + * NULL if the submodule has not been updated. + */ + struct repository *repo; + /* + * A struct submodule containing the submodule config in the + * tree's .gitmodules. + */ + const struct submodule *submodule; +}; + +struct submodule_entry_list { + struct submodule_tree_entry *entries; + int entry_nr; + int entry_alloc; +}; + +/** + * Given a treeish, return all submodules in the tree and its subtrees, + * but excluding nested submodules. Callers that require nested + * submodules are expected to recurse into the submodules themselves. + */ +void submodules_of_tree(struct repository *r, + const struct object_id *treeish_name, + struct submodule_entry_list *ret); #endif /* SUBMODULE_CONFIG_H */ diff --git a/submodule.c b/submodule.c index c689070524..5ace18a7d9 100644 --- a/submodule.c +++ b/submodule.c @@ -267,7 +267,9 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt, * ie, the config looks like: "[submodule] active\n". * Since that is an invalid pathspec, we should inform the user. */ -int is_submodule_active(struct repository *repo, const char *path) +int is_tree_submodule_active(struct repository *repo, + const struct object_id *treeish_name, + const char *path) { int ret = 0; char *key = NULL; @@ -275,7 +277,7 @@ int is_submodule_active(struct repository *repo, const char *path) const struct string_list *sl; const struct submodule *module; - module = submodule_from_path(repo, null_oid(), path); + module = submodule_from_path(repo, treeish_name, path); /* early return if there isn't a path->module mapping */ if (!module) @@ -317,6 +319,11 @@ int is_submodule_active(struct repository *repo, const char *path) return ret; } +int is_submodule_active(struct repository *repo, const char *path) +{ + return is_tree_submodule_active(repo, null_oid(), path); +} + int is_submodule_populated_gently(const char *path, int *return_error_code) { int ret = 0; diff --git a/submodule.h b/submodule.h index 6bd2c99fd9..784ceffc0e 100644 --- a/submodule.h +++ b/submodule.h @@ -54,6 +54,9 @@ int git_default_submodule_config(const char *var, const char *value, void *cb); struct option; int option_parse_recurse_submodules_worktree_updater(const struct option *opt, const char *arg, int unset); +int is_tree_submodule_active(struct repository *repo, + const struct object_id *treeish_name, + const char *path); int is_submodule_active(struct repository *repo, const char *path); /* * Determine if a submodule has been populated at a given 'path' by checking if diff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh new file mode 100755 index 0000000000..6ef2733396 --- /dev/null +++ b/t/t3207-branch-submodule.sh @@ -0,0 +1,292 @@ +#!/bin/sh + +test_description='git branch submodule tests' + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + +pwd=$(pwd) + +# Creates a clean test environment in "pwd" by copying the repo setup +# from test_dirs. +reset_test () { + rm -fr super && + rm -fr sub-sub-upstream && + rm -fr sub-upstream && + cp -r test_dirs/* . +} + +# Tests that the expected branch does not exist +test_no_branch () { + DIR=$1 && + BRANCH_NAME=$2 && + test_must_fail git -C "$DIR" rev-parse "$BRANCH_NAME" 2>err && + grep "ambiguous argument .$BRANCH_NAME." err +} + +test_expect_success 'setup superproject and submodule' ' + mkdir test_dirs && + ( + cd test_dirs && + git init super && + test_commit -C super foo && + git init sub-sub-upstream && + test_commit -C sub-sub-upstream foo && + git init sub-upstream && + # Submodule in a submodule + git -C sub-upstream submodule add "${pwd}/test_dirs/sub-sub-upstream" sub-sub && + git -C sub-upstream commit -m "add submodule" && + # Regular submodule + git -C super submodule add "${pwd}/test_dirs/sub-upstream" sub && + # Submodule in a subdirectory + git -C super submodule add "${pwd}/test_dirs/sub-sub-upstream" second/sub && + git -C super commit -m "add submodule" && + git -C super config submodule.propagateBranches true && + git -C super/sub submodule update --init + ) && + reset_test +' + +# Test the argument parsing +test_expect_success '--recurse-submodules should create branches' ' + test_when_finished "reset_test" && + ( + cd super && + git branch --recurse-submodules branch-a && + git rev-parse branch-a && + git -C sub rev-parse branch-a && + git -C sub/sub-sub rev-parse branch-a && + git -C second/sub rev-parse branch-a + ) +' + +test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' ' + test_when_finished "reset_test" && + ( + cd super && + echo "fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled" >expected && + test_must_fail git -c submodule.propagateBranches=false branch --recurse-submodules branch-a 2>actual && + test_cmp expected actual + ) +' + +test_expect_success '--recurse-submodules should fail when not creating branches' ' + test_when_finished "reset_test" && + ( + cd super && + git branch --recurse-submodules branch-a && + echo "fatal: --recurse-submodules can only be used to create branches" >expected && + test_must_fail git branch --recurse-submodules -D branch-a 2>actual && + test_cmp expected actual && + # Assert that the branches were not deleted + git rev-parse branch-a && + git -C sub rev-parse branch-a + ) +' + +test_expect_success 'should respect submodule.recurse when creating branches' ' + test_when_finished "reset_test" && + ( + cd super && + git -c submodule.recurse=true branch branch-a && + git rev-parse branch-a && + git -C sub rev-parse branch-a + ) +' + +test_expect_success 'should ignore submodule.recurse when not creating branches' ' + test_when_finished "reset_test" && + ( + cd super && + git branch --recurse-submodules branch-a && + git -c submodule.recurse=true branch -D branch-a && + test_no_branch . branch-a && + git -C sub rev-parse branch-a + ) +' + +# Test branch creation behavior +test_expect_success 'should create branches based off commit id in superproject' ' + test_when_finished "reset_test" && + ( + cd super && + git branch --recurse-submodules branch-a && + git checkout --recurse-submodules branch-a && + git -C sub rev-parse HEAD >expected && + # Move the tip of sub:branch-a so that it no longer matches the commit in super:branch-a + git -C sub checkout branch-a && + test_commit -C sub bar && + # Create a new branch-b branch with start-point=branch-a + git branch --recurse-submodules branch-b branch-a && + git rev-parse branch-b && + git -C sub rev-parse branch-b >actual && + # Assert that the commit id of sub:second-branch matches super:branch-a and not sub:branch-a + test_cmp expected actual + ) +' + +test_expect_success 'should not create any branches if branch is not valid for all repos' ' + test_when_finished "reset_test" && + ( + cd super && + git -C sub branch branch-a && + test_must_fail git branch --recurse-submodules branch-a 2>actual && + test_no_branch . branch-a && + grep "submodule .sub.: fatal: A branch named .branch-a. already exists" actual + ) +' + +test_expect_success 'should create branches if branch exists and --force is given' ' + test_when_finished "reset_test" && + ( + cd super && + git -C sub rev-parse HEAD >expected && + test_commit -C sub baz && + # branch-a in sub now points to a newer commit. + git -C sub branch branch-a HEAD && + git -C sub rev-parse branch-a >actual-old-branch-a && + git branch --recurse-submodules --force branch-a && + git rev-parse branch-a && + git -C sub rev-parse branch-a >actual-new-branch-a && + test_cmp expected actual-new-branch-a && + # assert that branch --force actually moved the sub + # branch + ! test_cmp expected actual-old-branch-a + ) +' + +test_expect_success 'should create branch when submodule is not in HEAD:.gitmodules' ' + test_when_finished "reset_test" && + ( + cd super && + git branch branch-a && + git checkout -b branch-b && + git submodule add ../sub-upstream sub2 && + git -C sub2 submodule update --init && + # branch-b now has a committed submodule not in branch-a + git commit -m "add second submodule" && + git checkout branch-a && + git branch --recurse-submodules branch-c branch-b && + git checkout --recurse-submodules branch-c && + git -C sub2 rev-parse branch-c && + git -C sub2/sub-sub rev-parse branch-c + ) +' + +test_expect_success 'should not create branches in inactive submodules' ' + test_when_finished "reset_test" && + test_config -C super submodule.sub.active false && + ( + cd super && + git branch --recurse-submodules branch-a && + git rev-parse branch-a && + test_no_branch sub branch-a + ) +' + +test_expect_success 'should set up tracking of local branches with track=always' ' + test_when_finished "reset_test" && + ( + cd super && + git -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main && + git -C sub rev-parse main && + test_cmp_config -C sub . branch.branch-a.remote && + test_cmp_config -C sub refs/heads/main branch.branch-a.merge + ) +' + +test_expect_success 'should set up tracking of local branches with explicit track' ' + test_when_finished "reset_test" && + ( + cd super && + git branch --track --recurse-submodules branch-a main && + git -C sub rev-parse main && + test_cmp_config -C sub . branch.branch-a.remote && + test_cmp_config -C sub refs/heads/main branch.branch-a.merge + ) +' + +test_expect_success 'should not set up unnecessary tracking of local branches' ' + test_when_finished "reset_test" && + ( + cd super && + git branch --recurse-submodules branch-a main && + git -C sub rev-parse main && + test_cmp_config -C sub "" --default "" branch.branch-a.remote && + test_cmp_config -C sub "" --default "" branch.branch-a.merge + ) +' + +reset_remote_test () { + rm -fr super-clone && + reset_test +} + +test_expect_success 'setup tests with remotes' ' + ( + cd test_dirs && + ( + cd super && + git branch branch-a && + git checkout -b branch-b && + git submodule add ../sub-upstream sub2 && + # branch-b now has a committed submodule not in branch-a + git commit -m "add second submodule" + ) && + git clone --branch main --recurse-submodules super super-clone && + git -C super-clone config submodule.propagateBranches true + ) && + reset_remote_test +' + +test_expect_success 'should get fatal error upon branch creation when submodule is not in .git/modules' ' + test_when_finished "reset_remote_test" && + ( + cd super-clone && + # This should succeed because super-clone has sub in .git/modules + git branch --recurse-submodules branch-a origin/branch-a && + # This should fail because super-clone does not have sub2 .git/modules + test_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual && + grep "fatal: submodule .sub2.: unable to find submodule" actual && + test_no_branch . branch-b && + test_no_branch sub branch-b && + # User can fix themselves by initializing the submodule + git checkout origin/branch-b && + git submodule update --init --recursive && + git branch --recurse-submodules branch-b origin/branch-b + ) +' + +test_expect_success 'should set up tracking of remote-tracking branches' ' + test_when_finished "reset_remote_test" && + ( + cd super-clone && + git branch --recurse-submodules branch-a origin/branch-a && + test_cmp_config origin branch.branch-a.remote && + test_cmp_config refs/heads/branch-a branch.branch-a.merge && + # "origin/branch-a" does not exist for "sub", but it matches the refspec + # so tracking should be set up + test_cmp_config -C sub origin branch.branch-a.remote && + test_cmp_config -C sub refs/heads/branch-a branch.branch-a.merge && + test_cmp_config -C sub/sub-sub origin branch.branch-a.remote && + test_cmp_config -C sub/sub-sub refs/heads/branch-a branch.branch-a.merge + ) +' + +test_expect_success 'should not fail when unable to set up tracking in submodule' ' + test_when_finished "reset_remote_test" && + ( + cd super-clone && + git remote rename origin ex-origin && + git branch --recurse-submodules branch-a ex-origin/branch-a && + test_cmp_config ex-origin branch.branch-a.remote && + test_cmp_config refs/heads/branch-a branch.branch-a.merge && + test_cmp_config -C sub "" --default "" branch.branch-a.remote && + test_cmp_config -C sub "" --default "" branch.branch-a.merge + ) +' + +test_done -- cgit v1.3-5-g9baa From a699367bb8749a338aefb092c5d7ac75c69d61e1 Mon Sep 17 00:00:00 2001 From: Jean-Noël Avila Date: Mon, 31 Jan 2022 22:07:46 +0000 Subject: i18n: factorize more 'incompatible options' messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Find more incompatible options to factorize. When more than two options are mutually exclusive, print the ones which are actually on the command line. Signed-off-by: Jean-Noël Avila Signed-off-by: Junio C Hamano --- builtin/commit.c | 35 +++++++++++++------------------ builtin/difftool.c | 5 +++-- builtin/grep.c | 8 +++---- builtin/log.c | 5 +++-- builtin/merge-base.c | 6 ++++-- parse-options.c | 34 ++++++++++++++++++++++++++++++ parse-options.h | 16 ++++++++++++++ t/t7500-commit-template-squash-signoff.sh | 2 +- 8 files changed, 79 insertions(+), 32 deletions(-) (limited to 'builtin') diff --git a/builtin/commit.c b/builtin/commit.c index b9ed0374e3..33ca9e99c8 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1242,8 +1242,6 @@ static int parse_and_validate_options(int argc, const char *argv[], struct commit *current_head, struct wt_status *s) { - int f = 0; - argc = parse_options(argc, argv, prefix, options, usage, 0); finalize_deferred_config(s); @@ -1251,7 +1249,7 @@ static int parse_and_validate_options(int argc, const char *argv[], force_author = find_author_by_nickname(force_author); if (force_author && renew_authorship) - die(_("Using both --reset-author and --author does not make sense")); + die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); if (logfile || have_option_m || use_message) use_editor = 0; @@ -1268,20 +1266,16 @@ static int parse_and_validate_options(int argc, const char *argv[], die(_("You are in the middle of a rebase -- cannot amend.")); } if (fixup_message && squash_message) - die(_("Options --squash and --fixup cannot be used together")); - if (use_message) - f++; - if (edit_message) - f++; - if (fixup_message) - f++; - if (logfile) - f++; - if (f > 1) - die(_("Only one of -c/-C/-F/--fixup can be used.")); - if (have_option_m && (edit_message || use_message || logfile)) - die((_("Option -m cannot be combined with -c/-C/-F."))); - if (f || have_option_m) + die(_("options '%s' and '%s' cannot be used together"), "--squash", "--fixup"); + die_for_incompatible_opt4(!!use_message, "-C", + !!edit_message, "-c", + !!logfile, "-F", + !!fixup_message, "--fixup"); + die_for_incompatible_opt4(have_option_m, "-m", + !!edit_message, "-c", + !!use_message, "-C", + !!logfile, "-F"); + if (use_message || edit_message || logfile ||fixup_message || have_option_m) template_file = NULL; if (edit_message) use_message = edit_message; @@ -1306,9 +1300,10 @@ static int parse_and_validate_options(int argc, const char *argv[], if (patch_interactive) interactive = 1; - if (also + only + all + interactive > 1) - die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); - + die_for_incompatible_opt4(also, "-i/--include", + only, "-o/--only", + all, "-a/--all", + interactive, "--interactive/-p/--patch"); if (fixup_message) { /* * We limit --fixup's suboptions to only alpha characters. diff --git a/builtin/difftool.c b/builtin/difftool.c index c79fbbf67e..faa3507369 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -732,8 +732,9 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) } else if (dir_diff) die(_("options '%s' and '%s' cannot be used together"), "--dir-diff", "--no-index"); - if (use_gui_tool + !!difftool_cmd + !!extcmd > 1) - die(_("options '%s', '%s', and '%s' cannot be used together"), "--gui", "--tool", "--extcmd"); + die_for_incompatible_opt3(use_gui_tool, "--gui", + !!difftool_cmd, "--tool", + !!extcmd, "--extcmd"); if (use_gui_tool) setenv("GIT_MERGETOOL_GUI", "true", 1); diff --git a/builtin/grep.c b/builtin/grep.c index 9e34a820ad..88144f0630 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -1167,11 +1167,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!show_in_pager && !opt.status_only) setup_pager(); - if (!use_index && (untracked || cached)) - die(_("--cached or --untracked cannot be used with --no-index")); - - if (untracked && cached) - die(_("--untracked cannot be used with --cached")); + die_for_incompatible_opt3(!use_index, "--no-index", + untracked, "--untracked", + cached, "--cached"); if (!use_index || untracked) { int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; diff --git a/builtin/log.c b/builtin/log.c index 4b493408cc..970aa3483c 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1978,8 +1978,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (rev.show_notes) load_display_notes(&rev.notes_opt); - if (use_stdout + rev.diffopt.close_file + !!output_directory > 1) - die(_("options '%s', '%s', and '%s' cannot be used together"), "--stdout", "--output", "--output-directory"); + die_for_incompatible_opt3(use_stdout, "--stdout", + rev.diffopt.close_file, "--output", + !!output_directory, "--output-directory"); if (use_stdout) { setup_pager(); diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 6719ac198d..26b84980db 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -159,12 +159,14 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix) if (argc < 2) usage_with_options(merge_base_usage, options); if (show_all) - die("--is-ancestor cannot be used with --all"); + die(_("options '%s' and '%s' cannot be used together"), + "--is-ancestor", "--all"); return handle_is_ancestor(argc, argv); } if (cmdmode == 'r' && show_all) - die("--independent cannot be used with --all"); + die(_("options '%s' and '%s' cannot be used together"), + "--independent", "--all"); if (cmdmode == 'o') return handle_octopus(argc, argv, show_all); diff --git a/parse-options.c b/parse-options.c index a8283037be..276e3911a7 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1079,3 +1079,37 @@ void NORETURN usage_msg_opt(const char *msg, die_message("%s\n", msg); /* The extra \n is intentional */ usage_with_options(usagestr, options); } + +void die_for_incompatible_opt4(int opt1, const char *opt1_name, + int opt2, const char *opt2_name, + int opt3, const char *opt3_name, + int opt4, const char *opt4_name) +{ + int count = 0; + const char *options[4]; + + if (opt1) + options[count++] = opt1_name; + if (opt2) + options[count++] = opt2_name; + if (opt3) + options[count++] = opt3_name; + if (opt4) + options[count++] = opt4_name; + switch (count) { + case 4: + die(_("options '%s', '%s', '%s', and '%s' cannot be used together"), + opt1_name, opt2_name, opt3_name, opt4_name); + break; + case 3: + die(_("options '%s', '%s', and '%s' cannot be used together"), + options[0], options[1], options[2]); + break; + case 2: + die(_("options '%s' and '%s' cannot be used together"), + options[0], options[1]); + break; + default: + break; + } +} diff --git a/parse-options.h b/parse-options.h index e22846d3b7..f773cc7859 100644 --- a/parse-options.h +++ b/parse-options.h @@ -225,6 +225,22 @@ NORETURN void usage_msg_opt(const char *msg, const char * const *usagestr, const struct option *options); +void die_for_incompatible_opt4(int opt1, const char *opt1_name, + int opt2, const char *opt2_name, + int opt3, const char *opt3_name, + int opt4, const char *opt4_name); + + +static inline void die_for_incompatible_opt3(int opt1, const char *opt1_name, + int opt2, const char *opt2_name, + int opt3, const char *opt3_name) +{ + die_for_incompatible_opt4(opt1, opt1_name, + opt2, opt2_name, + opt3, opt3_name, + 0, ""); +} + /* * Use these assertions for callbacks that expect to be called with NONEG and * NOARG respectively, and do not otherwise handle the "unset" and "arg" diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh index 91964653a0..5fcaa0b4f2 100755 --- a/t/t7500-commit-template-squash-signoff.sh +++ b/t/t7500-commit-template-squash-signoff.sh @@ -442,7 +442,7 @@ test_expect_success '--fixup=reword: give error with pathsec' ' ' test_expect_success '--fixup=reword: -F give error message' ' - echo "fatal: Only one of -c/-C/-F/--fixup can be used." >expect && + echo "fatal: options '\''-F'\'' and '\''--fixup'\'' cannot be used together" >expect && test_must_fail git commit --fixup=reword:HEAD~ -F msg 2>actual && test_cmp expect actual ' -- cgit v1.3-5-g9baa From 1a8aea857e4225a9d35a531869fd47777f3063d6 Mon Sep 17 00:00:00 2001 From: Jean-Noël Avila Date: Mon, 31 Jan 2022 22:07:47 +0000 Subject: i18n: factorize "invalid value" messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the same message when an invalid value is passed to a command line option or a configuration variable. Signed-off-by: Jean-Noël Avila Signed-off-by: Junio C Hamano --- builtin/am.c | 8 +++++--- builtin/blame.c | 7 ++++--- builtin/fetch.c | 4 ++-- builtin/pack-objects.c | 2 +- builtin/pull.c | 6 +++--- builtin/push.c | 2 +- builtin/send-pack.c | 2 +- diff-merges.c | 2 +- gpg-interface.c | 6 +++--- ls-refs.c | 3 ++- parallel-checkout.c | 4 ++-- sequencer.c | 2 +- setup.c | 3 ++- submodule-config.c | 2 +- t/t4150-am.sh | 2 +- 15 files changed, 30 insertions(+), 25 deletions(-) (limited to 'builtin') diff --git a/builtin/am.c b/builtin/am.c index b6be1f1cb1..97dbeb8e49 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -199,7 +199,7 @@ static int am_option_parse_empty(const struct option *opt, else if (!strcmp(arg, "keep")) *opt_value = KEEP_EMPTY_COMMIT; else - return error(_("Invalid value for --empty: %s"), arg); + return error(_("invalid value for '%s': '%s'"), "--empty", arg); return 0; } @@ -2239,7 +2239,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int * when you add new options */ else - return error(_("Invalid value for --patch-format: %s"), arg); + return error(_("invalid value for '%s': '%s'"), + "--patch-format", arg); return 0; } @@ -2282,7 +2283,8 @@ static int parse_opt_show_current_patch(const struct option *opt, const char *ar break; } if (new_value >= ARRAY_SIZE(valid_modes)) - return error(_("Invalid value for --show-current-patch: %s"), arg); + return error(_("invalid value for '%s': '%s'"), + "--show-current-patch", arg); } if (resume->mode == RESUME_SHOW_PATCH && new_value != resume->sub_mode) diff --git a/builtin/blame.c b/builtin/blame.c index 7fafeac408..9c142f17a2 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -721,8 +721,8 @@ static int git_blame_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "color.blame.repeatedlines")) { if (color_parse_mem(value, strlen(value), repeated_meta_color)) - warning(_("invalid color '%s' in color.blame.repeatedLines"), - value); + warning(_("invalid value for '%s': '%s'"), + "color.blame.repeatedLines", value); return 0; } if (!strcmp(var, "color.blame.highlightrecent")) { @@ -739,7 +739,8 @@ static int git_blame_config(const char *var, const char *value, void *cb) coloring_mode &= ~(OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR); } else { - warning(_("invalid value for blame.coloring")); + warning(_("invalid value for '%s': '%s'"), + "blame.coloring", value); return 0; } } diff --git a/builtin/fetch.c b/builtin/fetch.c index 5f06b21f8e..8be19bb879 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -763,8 +763,8 @@ static void prepare_format_display(struct ref *ref_map) else if (!strcasecmp(format, "compact")) compact_format = 1; else - die(_("configuration fetch.output contains invalid value %s"), - format); + die(_("invalid value for '%s': '%s'"), + "fetch.output", format); for (rm = ref_map; rm; rm = rm->next) { if (rm->status == REF_STATUS_REJECT_SHALLOW || diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index ba2006f221..192c3ca305 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -3504,7 +3504,7 @@ static int option_parse_missing_action(const struct option *opt, return 0; } - die(_("invalid value for --missing")); + die(_("invalid value for '%s': '%s'"), "--missing", arg); return 0; } diff --git a/builtin/pull.c b/builtin/pull.c index 100cbf9fb8..e54a0ccadc 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -42,9 +42,9 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value, return v; if (fatal) - die(_("Invalid value for %s: %s"), key, value); + die(_("invalid value for '%s': '%s'"), key, value); else - error(_("Invalid value for %s: %s"), key, value); + error(_("invalid value for '%s': '%s'"), key, value); return REBASE_INVALID; } @@ -318,7 +318,7 @@ static const char *config_get_ff(void) if (!strcmp(value, "only")) return "--ff-only"; - die(_("Invalid value for pull.ff: %s"), value); + die(_("invalid value for '%s': '%s'"), "pull.ff", value); } /** diff --git a/builtin/push.c b/builtin/push.c index 359db90321..cad997965a 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -486,7 +486,7 @@ static int git_push_config(const char *k, const char *v, void *cb) if (value && !strcasecmp(value, "if-asked")) set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_IF_ASKED); else - return error("Invalid value for '%s'", k); + return error(_("invalid value for '%s'"), k); } } } else if (!strcmp(k, "push.recursesubmodules")) { diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 69c432ef1a..64962be016 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -145,7 +145,7 @@ static int send_pack_config(const char *k, const char *v, void *cb) if (value && !strcasecmp(value, "if-asked")) args.push_cert = SEND_PACK_PUSH_CERT_IF_ASKED; else - return error("Invalid value for '%s'", k); + return error(_("invalid value for '%s'"), k); } } } diff --git a/diff-merges.c b/diff-merges.c index 5060ccd890..cd6c102a0d 100644 --- a/diff-merges.c +++ b/diff-merges.c @@ -67,7 +67,7 @@ static void set_diff_merges(struct rev_info *revs, const char *optarg) diff_merges_setup_func_t func = func_by_opt(optarg); if (!func) - die(_("unknown value for --diff-merges: %s"), optarg); + die(_("invalid value for '%s': '%s'"), "--diff-merges", optarg); func(revs); diff --git a/gpg-interface.c b/gpg-interface.c index b52eb0e2e0..4e084b08f6 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -702,7 +702,7 @@ int git_gpg_config(const char *var, const char *value, void *cb) return config_error_nonbool(var); fmt = get_format_by_name(value); if (!fmt) - return error("unsupported value for %s: %s", + return error(_("invalid value for '%s': '%s'"), var, value); use_format = fmt; return 0; @@ -717,8 +717,8 @@ int git_gpg_config(const char *var, const char *value, void *cb) free(trust); if (ret) - return error("unsupported value for %s: %s", var, - value); + return error(_("invalid value for '%s': '%s'"), + var, value); return 0; } diff --git a/ls-refs.c b/ls-refs.c index 54078323dc..98e69373c8 100644 --- a/ls-refs.c +++ b/ls-refs.c @@ -34,7 +34,8 @@ static void ensure_config_read(void) } else if (!strcmp(str, "ignore")) { /* do nothing */ } else { - die(_("invalid value '%s' for lsrefs.unborn"), str); + die(_("invalid value for '%s': '%s'"), + "lsrefs.unborn", str); } } config_read = 1; diff --git a/parallel-checkout.c b/parallel-checkout.c index 8dd7e7bad4..31a3d0ee1b 100644 --- a/parallel-checkout.c +++ b/parallel-checkout.c @@ -39,8 +39,8 @@ void get_parallel_checkout_configs(int *num_workers, int *threshold) if (env_workers && *env_workers) { if (strtol_i(env_workers, 10, num_workers)) { - die("invalid value for GIT_TEST_CHECKOUT_WORKERS: '%s'", - env_workers); + die(_("invalid value for '%s': '%s'"), + "GIT_TEST_CHECKOUT_WORKERS", env_workers); } if (*num_workers < 1) *num_workers = online_cpus(); diff --git a/sequencer.c b/sequencer.c index 5213d16e97..683f5392eb 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2806,7 +2806,7 @@ static int populate_opts_cb(const char *key, const char *value, void *data) return error(_("invalid key: %s"), key); if (!error_flag) - return error(_("invalid value for %s: %s"), key, value); + return error(_("invalid value for '%s': '%s'"), key, value); return 0; } diff --git a/setup.c b/setup.c index af3b8c09ab..04ce33cdcd 100644 --- a/setup.c +++ b/setup.c @@ -559,7 +559,8 @@ static enum extension_result handle_extension(const char *var, return config_error_nonbool(var); format = hash_algo_by_name(value); if (format == GIT_HASH_UNKNOWN) - return error("invalid value for 'extensions.objectformat'"); + return error(_("invalid value for '%s': '%s'"), + "extensions.objectformat", value); data->hash_algo = format; return EXTENSION_OK; } diff --git a/submodule-config.c b/submodule-config.c index f95344028b..fb95a026f4 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -496,7 +496,7 @@ static int parse_config(const char *var, const char *value, void *data) else if (parse_submodule_update_strategy(value, &submodule->update_strategy) < 0 || submodule->update_strategy.type == SM_UPDATE_COMMAND) - die(_("invalid value for %s"), var); + die(_("invalid value for '%s'"), var); } else if (!strcmp(item.buf, "shallow")) { if (!me->overwrite && submodule->recommend_shallow != -1) warn_multiple_config(me->treeish_name, submodule->name, diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 6caff0ca39..159fae8d01 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -1169,7 +1169,7 @@ test_expect_success 'invalid when passing the --empty option alone' ' test_when_finished "git am --abort || :" && git checkout empty-commit^ && test_must_fail git am --empty empty-commit.patch 2>err && - echo "error: Invalid value for --empty: empty-commit.patch" >expected && + echo "error: invalid value for '\''--empty'\'': '\''empty-commit.patch'\''" >expected && test_cmp expected err ' -- cgit v1.3-5-g9baa From 959d670d1a42af282a55551a3f03642592f64eb6 Mon Sep 17 00:00:00 2001 From: Jean-Noël Avila Date: Mon, 31 Jan 2022 22:07:48 +0000 Subject: i18n: remove from i18n strings that do not hold translatable parts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jean-Noël Avila Signed-off-by: Junio C Hamano --- archive.c | 2 +- builtin/bisect--helper.c | 6 +++--- builtin/count-objects.c | 2 +- builtin/hash-object.c | 2 +- builtin/help.c | 4 ++-- builtin/mktag.c | 2 +- builtin/mktree.c | 2 +- builtin/notes.c | 6 +++--- builtin/prune-packed.c | 2 +- builtin/rebase.c | 2 +- builtin/reflog.c | 2 +- builtin/remote.c | 2 +- builtin/replace.c | 2 +- builtin/sparse-checkout.c | 8 ++++---- builtin/stripspace.c | 4 ++-- builtin/submodule--helper.c | 2 +- builtin/update-server-info.c | 2 +- 17 files changed, 26 insertions(+), 26 deletions(-) (limited to 'builtin') diff --git a/archive.c b/archive.c index d571249cf3..e29d0e00f6 100644 --- a/archive.c +++ b/archive.c @@ -12,7 +12,7 @@ static char const * const archive_usage[] = { N_("git archive [] [...]"), - N_("git archive --list"), + "git archive --list", N_("git archive --remote [--exec ] [] [...]"), N_("git archive --remote [--exec ] --list"), NULL diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 28a2e6a575..f962dbd430 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -22,15 +22,15 @@ static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN") static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --bisect-reset []"), - N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), + "git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]", N_("git bisect--helper --bisect-start [--term-{new,bad}= --term-{old,good}=]" " [--no-checkout] [--first-parent] [ [...]] [--] [...]"), - N_("git bisect--helper --bisect-next"), + "git bisect--helper --bisect-next", N_("git bisect--helper --bisect-state (bad|new) []"), N_("git bisect--helper --bisect-state (good|old) [...]"), N_("git bisect--helper --bisect-replay "), N_("git bisect--helper --bisect-skip [(|)...]"), - N_("git bisect--helper --bisect-visualize"), + "git bisect--helper --bisect-visualize", N_("git bisect--helper --bisect-run ..."), NULL }; diff --git a/builtin/count-objects.c b/builtin/count-objects.c index 3fae474f6f..07b9419596 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -87,7 +87,7 @@ static int print_alternate(struct object_directory *odb, void *data) } static char const * const count_objects_usage[] = { - N_("git count-objects [-v] [-H | --human-readable]"), + "git count-objects [-v] [-H | --human-readable]", NULL }; diff --git a/builtin/hash-object.c b/builtin/hash-object.c index c7b3ad74c6..04e2442ec7 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -81,7 +81,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) { static const char * const hash_object_usage[] = { N_("git hash-object [-t ] [-w] [--path= | --no-filters] [--stdin] [--] ..."), - N_("git hash-object --stdin-paths"), + "git hash-object --stdin-paths", NULL }; const char *type = blob_type; diff --git a/builtin/help.c b/builtin/help.c index d387131dd8..b4f2ad3f94 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -77,8 +77,8 @@ static struct option builtin_help_options[] = { static const char * const builtin_help_usage[] = { N_("git help [-a|--all] [--[no-]verbose]]\n" " [[-i|--info] [-m|--man] [-w|--web]] []"), - N_("git help [-g|--guides]"), - N_("git help [-c|--config]"), + "git help [-g|--guides]", + "git help [-c|--config]", NULL }; diff --git a/builtin/mktag.c b/builtin/mktag.c index 3b2dbbb37e..c7b905c614 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -7,7 +7,7 @@ #include "config.h" static char const * const builtin_mktag_usage[] = { - N_("git mktag"), + "git mktag", NULL }; static int option_strict = 1; diff --git a/builtin/mktree.c b/builtin/mktree.c index ae78ca1c02..8bdaada922 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -63,7 +63,7 @@ static void write_tree(struct object_id *oid) } static const char *mktree_usage[] = { - N_("git mktree [-z] [--missing] [--batch]"), + "git mktree [-z] [--missing] [--batch]", NULL }; diff --git a/builtin/notes.c b/builtin/notes.c index 05d60483e8..f99593a185 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -32,8 +32,8 @@ static const char * const git_notes_usage[] = { N_("git notes [--ref ] edit [--allow-empty] []"), N_("git notes [--ref ] show []"), N_("git notes [--ref ] merge [-v | -q] [-s ] "), - N_("git notes merge --commit [-v | -q]"), - N_("git notes merge --abort [-v | -q]"), + "git notes merge --commit [-v | -q]", + "git notes merge --abort [-v | -q]", N_("git notes [--ref ] remove [...]"), N_("git notes [--ref ] prune [-n] [-v]"), N_("git notes [--ref ] get-ref"), @@ -89,7 +89,7 @@ static const char * const git_notes_prune_usage[] = { }; static const char * const git_notes_get_ref_usage[] = { - N_("git notes get-ref"), + "git notes get-ref", NULL }; diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c index b7b9281a8c..da3273a268 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -3,7 +3,7 @@ #include "prune-packed.h" static const char * const prune_packed_usage[] = { - N_("git prune-packed [-n | --dry-run] [-q | --quiet]"), + "git prune-packed [-n | --dry-run] [-q | --quiet]", NULL }; diff --git a/builtin/rebase.c b/builtin/rebase.c index 36490d06c8..64796c6a78 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -36,7 +36,7 @@ static char const * const builtin_rebase_usage[] = { "[--onto | --keep-base] [ []]"), N_("git rebase [-i] [options] [--exec ] [--onto ] " "--root []"), - N_("git rebase --continue | --abort | --skip | --edit-todo"), + "git rebase --continue | --abort | --skip | --edit-todo", NULL }; diff --git a/builtin/reflog.c b/builtin/reflog.c index a4b1dd27e1..ee5ee8d8cf 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -800,7 +800,7 @@ static int cmd_reflog_exists(int argc, const char **argv, const char *prefix) */ static const char reflog_usage[] = -N_("git reflog [ show | expire | delete | exists ]"); +"git reflog [ show | expire | delete | exists ]"; int cmd_reflog(int argc, const char **argv, const char *prefix) { diff --git a/builtin/remote.c b/builtin/remote.c index 299c466116..6f27ddc47b 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -14,7 +14,7 @@ #include "commit-reach.h" static const char * const builtin_remote_usage[] = { - N_("git remote [-v | --verbose]"), + "git remote [-v | --verbose]", N_("git remote add [-t ] [-m ] [-f] [--tags | --no-tags] [--mirror=] "), N_("git remote rename "), N_("git remote remove "), diff --git a/builtin/replace.c b/builtin/replace.c index 6ff1734d58..ac92337c0e 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -22,7 +22,7 @@ static const char * const git_replace_usage[] = { N_("git replace [-f] "), N_("git replace [-f] --edit "), N_("git replace [-f] --graft [...]"), - N_("git replace [-f] --convert-graft-file"), + "git replace [-f] --convert-graft-file", N_("git replace -d ..."), N_("git replace [--format=] [-l []]"), NULL diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 679c107036..771c9869a1 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -43,7 +43,7 @@ static void write_patterns_to_file(FILE *fp, struct pattern_list *pl) } static char const * const builtin_sparse_checkout_list_usage[] = { - N_("git sparse-checkout list"), + "git sparse-checkout list", NULL }; @@ -419,7 +419,7 @@ static int update_modes(int *cone_mode, int *sparse_index) } static char const * const builtin_sparse_checkout_init_usage[] = { - N_("git sparse-checkout init [--cone] [--[no-]sparse-index]"), + "git sparse-checkout init [--cone] [--[no-]sparse-index]", NULL }; @@ -762,7 +762,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix) } static char const * const builtin_sparse_checkout_reapply_usage[] = { - N_("git sparse-checkout reapply [--[no-]cone] [--[no-]sparse-index]"), + "git sparse-checkout reapply [--[no-]cone] [--[no-]sparse-index]", NULL }; @@ -800,7 +800,7 @@ static int sparse_checkout_reapply(int argc, const char **argv) } static char const * const builtin_sparse_checkout_disable_usage[] = { - N_("git sparse-checkout disable"), + "git sparse-checkout disable", NULL }; diff --git a/builtin/stripspace.c b/builtin/stripspace.c index be33eb83c1..1e34cf2beb 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -15,8 +15,8 @@ static void comment_lines(struct strbuf *buf) } static const char * const stripspace_usage[] = { - N_("git stripspace [-s | --strip-comments]"), - N_("git stripspace [-c | --comment-lines]"), + "git stripspace [-s | --strip-comments]", + "git stripspace [-c | --comment-lines]", NULL }; diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index c5d3fc3817..b80aa9898a 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -2883,7 +2883,7 @@ static int module_config(int argc, const char **argv, const char *prefix) const char *const git_submodule_helper_usage[] = { N_("git submodule--helper config []"), N_("git submodule--helper config --unset "), - N_("git submodule--helper config --check-writeable"), + "git submodule--helper config --check-writeable", NULL }; diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c index 4321a34456..880fffec58 100644 --- a/builtin/update-server-info.c +++ b/builtin/update-server-info.c @@ -4,7 +4,7 @@ #include "parse-options.h" static const char * const update_server_info_usage[] = { - N_("git update-server-info [--force]"), + "git update-server-info [--force]", NULL }; -- cgit v1.3-5-g9baa From 9164d97a63b31614a52571d708f1ef151b97db71 Mon Sep 17 00:00:00 2001 From: Jean-Noël Avila Date: Mon, 31 Jan 2022 22:07:49 +0000 Subject: i18n: fix some misformated placeholders in command synopsis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add '<>' around arguments where missing * convert plurals into '...' forms This applies the style guide for documentation. Signed-off-by: Jean-Noël Avila Reviewed-by: Phillip Wood Signed-off-by: Junio C Hamano --- builtin/fast-export.c | 2 +- builtin/reflog.c | 4 ++-- builtin/rev-list.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'builtin') diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 9f1c730e58..510139e9b5 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -26,7 +26,7 @@ #include "commit-slab.h" static const char *fast_export_usage[] = { - N_("git fast-export [rev-list-opts]"), + N_("git fast-export []"), NULL }; diff --git a/builtin/reflog.c b/builtin/reflog.c index ee5ee8d8cf..343a10d371 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -17,10 +17,10 @@ static const char reflog_expire_usage[] = N_("git reflog expire [--expire=