* pw/add-p-select, was Re: What's cooking in git.git (Dec 2018, #01; Sun, 9)
From: Johannes Schindelin @ 2018-12-09 20:31 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <xmqq8t0z3xcc.fsf@gitster-ct.c.googlers.com>
Hi Junio,
On Sun, 9 Dec 2018, Junio C Hamano wrote:
> * pw/add-p-select (2018-07-26) 4 commits
> - add -p: optimize line selection for short hunks
> - add -p: allow line selection to be inverted
> - add -p: select modified lines correctly
> - add -p: select individual hunk lines
>
> "git add -p" interactive interface learned to let users choose
> individual added/removed lines to be used in the operation, instead
> of accepting or rejecting a whole hunk.
>
> Will discard.
> No further feedbacks on the topic for quite some time.
That is not quite true. I did comment that this feature (which I take as
being inspired by Git GUI's "Stage Selected Line"), and thought that it
would be useful.
I could imagine, however, that it would make sense for `git add -p` to
imitate that feature more closely: by allowing to stage a single line and
then presenting the current hunk (re-computed) again.
Ciao,
Dscho
^ permalink raw reply
* [PATCH 8/8] stash: use git checkout --no-overlay
From: Thomas Gummerer @ 2018-12-09 20:04 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy,
Elijah Newren, Thomas Gummerer
In-Reply-To: <20181209200449.16342-1-t.gummerer@gmail.com>
Now that we have 'git checkout --no-overlay', we can use it in git
stash, making the codepaths for 'git stash push' with and without
pathspec more similar, and thus easier to follow.
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
As mentioned in the cover letter, not sure if we want to apply this
now. There are two reasons I did this:
- Showing the new functionality of git checkout
- Increased test coverage, as we are running the new code with all git
stash tests for free, which helped look at some cases that I was
missing initially.
git-stash.sh | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/git-stash.sh b/git-stash.sh
index 94793c1a91..67be04d996 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -314,19 +314,15 @@ push_stash () {
if test -z "$patch_mode"
then
- test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
- if test -n "$untracked" && test $# = 0
+ test "$untracked" = "all" && CLEAN_X_OPTION=-X || CLEAN_X_OPTION=
+ if test -n "$untracked"
then
- git clean --force --quiet -d $CLEAN_X_OPTION
+ git clean --force --quiet -d $CLEAN_X_OPTION -- "$@"
fi
if test $# != 0
then
- test -z "$untracked" && UPDATE_OPTION="-u" || UPDATE_OPTION=
- test "$untracked" = "all" && FORCE_OPTION="--force" || FORCE_OPTION=
- git add $UPDATE_OPTION $FORCE_OPTION -- "$@"
- git diff-index -p --cached --binary HEAD -- "$@" |
- git apply --index -R
+ git checkout --quiet --no-overlay --ignore-unmatched HEAD -- "$@"
else
git reset --hard -q
fi
--
2.20.0.405.gbc1bbc6f85
^ permalink raw reply related
* [PATCH 7/8] checkout: allow ignoring unmatched pathspec
From: Thomas Gummerer @ 2018-12-09 20:04 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy,
Elijah Newren, Thomas Gummerer
In-Reply-To: <20181209200449.16342-1-t.gummerer@gmail.com>
Currently when 'git checkout -- <pathspec>...' is invoked with
multiple pathspecs, where one or more of the pathspecs don't match
anything, checkout errors out.
This can be inconvenient in some cases, such as when using git
checkout from a script. Introduce a new --ignore-unmatched option,
which which allows us to ignore a non-matching pathspec instead of
erroring out.
In a subsequent commit we're going to start using 'git checkout' in
'git stash' and are going to make use of this feature.
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
builtin/checkout.c | 10 +++++++++-
t/t2022-checkout-paths.sh | 9 +++++++++
t/t9902-completion.sh | 1 +
3 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 6ba85e9de5..7e7b5cd1d3 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -46,6 +46,7 @@ struct checkout_opts {
int show_progress;
int overlay_mode;
int cached;
+ int ignore_unmatched;
/*
* If new checkout options are added, skip_merge_working_tree
* should be updated accordingly.
@@ -358,7 +359,8 @@ static int checkout_paths(const struct checkout_opts *opts,
ce->ce_flags |= CE_MATCHED;
}
- if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) {
+ if (!opts->ignore_unmatched &&
+ report_path_error(ps_matched, &opts->pathspec, opts->prefix)) {
free(ps_matched);
return 1;
}
@@ -586,6 +588,11 @@ static int skip_merge_working_tree(const struct checkout_opts *opts,
* not tested here
*/
+ /*
+ * opts->ignore_unmatched cannot be used with switching branches so is
+ * not tested here
+ */
+
/*
* If we aren't creating a new branch any changes or updates will
* happen in the existing branch. Since that could only be updating
@@ -1320,6 +1327,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode")),
OPT_BOOL(0, "cached", &opts.cached, N_("work on the index only")),
+ OPT_BOOL(0, "ignore-unmatched", &opts.ignore_unmatched, N_("don't error on unmatched pathspecs")),
OPT_END(),
};
diff --git a/t/t2022-checkout-paths.sh b/t/t2022-checkout-paths.sh
index fc3eb43b89..b44cdf7b63 100755
--- a/t/t2022-checkout-paths.sh
+++ b/t/t2022-checkout-paths.sh
@@ -78,4 +78,13 @@ test_expect_success 'do not touch files that are already up-to-date' '
test_cmp expect actual
'
+test_expect_success 'checkout --ignore-unmatched' '
+ test_commit file1 &&
+ echo changed >file1.t &&
+ git checkout --ignore-unmatched -- file1.t unknown-file &&
+ echo file1 >expect &&
+ test_cmp expect file1.t
+
+'
+
test_done
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index cbc304ace8..475debcf95 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1438,6 +1438,7 @@ test_expect_success 'double dash "git checkout"' '
--no-... Z
--overlay Z
--cached Z
+ --ignore-unmatched Z
EOF
'
--
2.20.0.405.gbc1bbc6f85
^ permalink raw reply related
* [PATCH 6/8] checkout: add --cached option
From: Thomas Gummerer @ 2018-12-09 20:04 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy,
Elijah Newren, Thomas Gummerer
In-Reply-To: <20181209200449.16342-1-t.gummerer@gmail.com>
Add a new --cached option to git checkout, which works only on the
index, but not the working tree, similar to what 'git reset <tree-ish>
-- <pathspec>... does. Indeed the tests are adapted from the 'git
reset' tests.
In the longer term the idea is to potentially deprecate 'git reset
<tree-ish> -- <pathspec>...', so the 'git reset' command becomes only
about re-pointing the HEAD, and not also about copying entries from
<tree-ish> to the index.
Note that 'git checkout' by default works in overlay mode, meaning
files that match the pathspec that don't exist in <tree-ish>, but
exist in the index would not be removed. 'git checkout --no-overlay
--cached' can be used to get the same behaviour as 'git reset
<tree-ish> -- <pathspec>'.
One thing this patch doesn't currently deal with is conflicts.
Currently 'git checkout --{ours,theirs} -- <file-with-conflicts>'
doesn't do anything with the index, so the --cached option just
mirrors that behaviour. But given it doesn't even deal with
conflicts, the '--cached' option doesn't make much sense when no
<tree-ish> is given. As it operates only on the index, it's always a
no-op if no tree-ish is given.
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
Maybe we can just disallow --cached without <tree-ish> given for now,
and possibly later allow it with some different behaviour for
conflicts, not sure what the best way forward here is. We can also
just make it update the index as appropriate, and have it behave
different than 'git checkout' curerntly does when handling conflicts?
builtin/checkout.c | 26 ++++++++--
t/t2016-checkout-patch.sh | 8 +++
t/t2026-checkout-cached.sh | 103 +++++++++++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
4 files changed, 135 insertions(+), 3 deletions(-)
create mode 100755 t/t2026-checkout-cached.sh
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 0aef35bbc4..6ba85e9de5 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -45,6 +45,7 @@ struct checkout_opts {
int ignore_other_worktrees;
int show_progress;
int overlay_mode;
+ int cached;
/*
* If new checkout options are added, skip_merge_working_tree
* should be updated accordingly.
@@ -288,6 +289,10 @@ static int checkout_paths(const struct checkout_opts *opts,
die(_("Cannot update paths and switch to branch '%s' at the same time."),
opts->new_branch);
+ if (opts->patch_mode && opts->cached)
+ return run_add_interactive(revision, "--patch=reset",
+ &opts->pathspec);
+
if (opts->patch_mode)
return run_add_interactive(revision, "--patch=checkout",
&opts->pathspec);
@@ -319,7 +324,9 @@ static int checkout_paths(const struct checkout_opts *opts,
* the current index, which means that it should
* be removed.
*/
- ce->ce_flags |= CE_MATCHED | CE_REMOVE | CE_WT_REMOVE;
+ ce->ce_flags |= CE_MATCHED | CE_REMOVE;
+ if (!opts->cached)
+ ce->ce_flags |= CE_WT_REMOVE;
continue;
} else {
/*
@@ -392,6 +399,9 @@ static int checkout_paths(const struct checkout_opts *opts,
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
if (ce->ce_flags & CE_MATCHED) {
+ if (opts->cached) {
+ continue;
+ }
if (!ce_stage(ce)) {
errs |= checkout_entry(ce, &state, NULL);
continue;
@@ -571,6 +581,11 @@ static int skip_merge_working_tree(const struct checkout_opts *opts,
* not tested here
*/
+ /*
+ * opts->cached cannot be used with switching branches so is
+ * not tested here
+ */
+
/*
* If we aren't creating a new branch any changes or updates will
* happen in the existing branch. Since that could only be updating
@@ -1207,9 +1222,13 @@ static int checkout_branch(struct checkout_opts *opts,
die(_("'%s' cannot be used with switching branches"),
"--patch");
- if (!opts->overlay_mode)
+ if (opts->overlay_mode != -1)
+ die(_("'%s' cannot be used with switching branches"),
+ "--overlay/--no-overlay");
+
+ if (opts->cached)
die(_("'%s' cannot be used with switching branches"),
- "--no-overlay");
+ "--cached");
if (opts->writeout_stage)
die(_("'%s' cannot be used with switching branches"),
@@ -1300,6 +1319,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode")),
+ OPT_BOOL(0, "cached", &opts.cached, N_("work on the index only")),
OPT_END(),
};
diff --git a/t/t2016-checkout-patch.sh b/t/t2016-checkout-patch.sh
index 47aeb0b167..e8774046e0 100755
--- a/t/t2016-checkout-patch.sh
+++ b/t/t2016-checkout-patch.sh
@@ -108,6 +108,14 @@ test_expect_success PERL 'path limiting works: foo inside dir' '
verify_state dir/foo head head
'
+test_expect_success PERL 'git checkout --cached -p' '
+ set_and_save_state dir/foo work work &&
+ test_write_lines n y | git checkout --cached -p >output &&
+ verify_state dir/foo work head &&
+ verify_saved_state bar &&
+ test_i18ngrep "Unstage" output
+'
+
test_expect_success PERL 'none of this moved HEAD' '
verify_saved_head
'
diff --git a/t/t2026-checkout-cached.sh b/t/t2026-checkout-cached.sh
new file mode 100755
index 0000000000..1b66192727
--- /dev/null
+++ b/t/t2026-checkout-cached.sh
@@ -0,0 +1,103 @@
+#!/bin/sh
+
+test_description='checkout --cached <pathspec>'
+
+. ./test-lib.sh
+
+test_expect_success 'checkout --cached <pathspec>' '
+ echo 1 >file1 &&
+ echo 2 >file2 &&
+ git add file1 file2 &&
+ test_tick &&
+ git commit -m files &&
+ git rm file2 &&
+ echo 3 >file3 &&
+ echo 4 >file1 &&
+ git add file1 file3 &&
+ git checkout --cached HEAD -- file1 file2 &&
+ test_must_fail git diff --quiet &&
+
+ cat >expect <<-\EOF &&
+ diff --git a/file1 b/file1
+ index d00491f..b8626c4 100644
+ --- a/file1
+ +++ b/file1
+ @@ -1 +1 @@
+ -1
+ +4
+ diff --git a/file2 b/file2
+ deleted file mode 100644
+ index 0cfbf08..0000000
+ --- a/file2
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -2
+ EOF
+ git diff >actual &&
+ test_cmp expect actual &&
+
+ cat >expect <<-\EOF &&
+ diff --git a/file3 b/file3
+ new file mode 100644
+ index 0000000..00750ed
+ --- /dev/null
+ +++ b/file3
+ @@ -0,0 +1 @@
+ +3
+ EOF
+ git diff --cached >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'checking out an unmodified path is a no-op' '
+ git reset --hard &&
+ git checkout --cached HEAD -- file1 &&
+ git diff-files --exit-code &&
+ git diff-index --cached --exit-code HEAD
+'
+
+test_expect_success 'checking out specific path that is unmerged' '
+ test_commit file3 file3 &&
+ git rm --cached file2 &&
+ echo 1234 >file2 &&
+ F1=$(git rev-parse HEAD:file1) &&
+ F2=$(git rev-parse HEAD:file2) &&
+ F3=$(git rev-parse HEAD:file3) &&
+ {
+ echo "100644 $F1 1 file2" &&
+ echo "100644 $F2 2 file2" &&
+ echo "100644 $F3 3 file2"
+ } | git update-index --index-info &&
+ git ls-files -u &&
+ git checkout --cached HEAD file2 &&
+ test_must_fail git diff --quiet &&
+ git diff-index --exit-code --cached HEAD
+'
+
+test_expect_success '--cached without --no-overlay does not remove entry from index' '
+ test_must_fail git checkout --cached HEAD^ file3 &&
+ git ls-files --error-unmatch -- file3
+'
+
+test_expect_success 'file is removed from the index with --no-overlay' '
+ git checkout --cached --no-overlay HEAD^ file3 &&
+ test_path_is_file file3 &&
+ test_must_fail git ls-files --error-unmatch -- file3
+'
+
+test_expect_success 'test checkout --cached --no-overlay at given paths' '
+ mkdir sub &&
+ >sub/file1 &&
+ >sub/file2 &&
+ git update-index --add sub/file1 sub/file2 &&
+ T=$(git write-tree) &&
+ git checkout --cached --no-overlay HEAD sub/file2 &&
+ test_must_fail git diff --quiet &&
+ U=$(git write-tree) &&
+ echo "$T" &&
+ echo "$U" &&
+ test_must_fail git diff-index --cached --exit-code "$T" &&
+ test "$T" != "$U"
+'
+
+test_done
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index a3fd9a9630..cbc304ace8 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1437,6 +1437,7 @@ test_expect_success 'double dash "git checkout"' '
--no-quiet Z
--no-... Z
--overlay Z
+ --cached Z
EOF
'
--
2.20.0.405.gbc1bbc6f85
^ permalink raw reply related
* [PATCH 5/8] checkout: introduce --{,no-}overlay option
From: Thomas Gummerer @ 2018-12-09 20:04 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy,
Elijah Newren, Thomas Gummerer
In-Reply-To: <20181209200449.16342-1-t.gummerer@gmail.com>
Currently 'git checkout' is defined as an overlay operation, which
means that if in 'git checkout <tree-ish> -- [<pathspec>]' we have an
entry in the index that matches <pathspec>, but that doesn't exist in
<tree-ish>, that entry will not be removed from the index or the
working tree.
Introduce a new --{,no-}overlay option, which allows using 'git
checkout' in non-overlay mode, thus removing files from the working
tree if they do not exist in <tree-ish> but match <pathspec>.
Note that 'git checkout -p <tree-ish> -- [<pathspec>]' already works
this way, so no changes are needed for the patch mode. We disallow
'git checkout --overlay -p' to avoid confusing users who would expect
to be able to force overlay mode in 'git checkout -p' this way.
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
builtin/checkout.c | 64 +++++++++++++++++++++++++++-------
t/t2025-checkout-no-overlay.sh | 47 +++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
3 files changed, 99 insertions(+), 13 deletions(-)
create mode 100755 t/t2025-checkout-no-overlay.sh
diff --git a/builtin/checkout.c b/builtin/checkout.c
index acdafc6e4c..0aef35bbc4 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -44,6 +44,7 @@ struct checkout_opts {
int ignore_skipworktree;
int ignore_other_worktrees;
int show_progress;
+ int overlay_mode;
/*
* If new checkout options are added, skip_merge_working_tree
* should be updated accordingly.
@@ -132,7 +133,8 @@ static int skip_same_name(const struct cache_entry *ce, int pos)
return pos;
}
-static int check_stage(int stage, const struct cache_entry *ce, int pos)
+static int check_stage(int stage, const struct cache_entry *ce, int pos,
+ int overlay_mode)
{
while (pos < active_nr &&
!strcmp(active_cache[pos]->name, ce->name)) {
@@ -140,6 +142,8 @@ static int check_stage(int stage, const struct cache_entry *ce, int pos)
return 0;
pos++;
}
+ if (!overlay_mode)
+ return 0;
if (stage == 2)
return error(_("path '%s' does not have our version"), ce->name);
else
@@ -165,7 +169,7 @@ static int check_stages(unsigned stages, const struct cache_entry *ce, int pos)
}
static int checkout_stage(int stage, const struct cache_entry *ce, int pos,
- const struct checkout *state)
+ const struct checkout *state, int overlay_mode)
{
while (pos < active_nr &&
!strcmp(active_cache[pos]->name, ce->name)) {
@@ -173,6 +177,10 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos,
return checkout_entry(active_cache[pos], state, NULL);
pos++;
}
+ if (!overlay_mode) {
+ unlink_entry(ce);
+ return 0;
+ }
if (stage == 2)
return error(_("path '%s' does not have our version"), ce->name);
else
@@ -302,15 +310,29 @@ static int checkout_paths(const struct checkout_opts *opts,
ce->ce_flags &= ~CE_MATCHED;
if (!opts->ignore_skipworktree && ce_skip_worktree(ce))
continue;
- if (opts->source_tree && !(ce->ce_flags & CE_UPDATE))
- /*
- * "git checkout tree-ish -- path", but this entry
- * is in the original index; it will not be checked
- * out to the working tree and it does not matter
- * if pathspec matched this entry. We will not do
- * anything to this entry at all.
- */
- continue;
+ if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) {
+ if (!opts->overlay_mode &&
+ ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) {
+ /*
+ * "git checkout --no-overlay <tree-ish> -- path",
+ * and the path is not in tree-ish, but is in
+ * the current index, which means that it should
+ * be removed.
+ */
+ ce->ce_flags |= CE_MATCHED | CE_REMOVE | CE_WT_REMOVE;
+ continue;
+ } else {
+ /*
+ * "git checkout tree-ish -- path", but this
+ * entry is in the original index; it will not
+ * be checked out to the working tree and it
+ * does not matter if pathspec matched this
+ * entry. We will not do anything to this entry
+ * at all.
+ */
+ continue;
+ }
+ }
/*
* Either this entry came from the tree-ish we are
* checking the paths out of, or we are checking out
@@ -348,7 +370,7 @@ static int checkout_paths(const struct checkout_opts *opts,
if (opts->force) {
warning(_("path '%s' is unmerged"), ce->name);
} else if (opts->writeout_stage) {
- errs |= check_stage(opts->writeout_stage, ce, pos);
+ errs |= check_stage(opts->writeout_stage, ce, pos, opts->overlay_mode);
} else if (opts->merge) {
errs |= check_stages((1<<2) | (1<<3), ce, pos);
} else {
@@ -375,12 +397,14 @@ static int checkout_paths(const struct checkout_opts *opts,
continue;
}
if (opts->writeout_stage)
- errs |= checkout_stage(opts->writeout_stage, ce, pos, &state);
+ errs |= checkout_stage(opts->writeout_stage, ce, pos, &state, opts->overlay_mode);
else if (opts->merge)
errs |= checkout_merged(pos, &state);
pos = skip_same_name(ce, pos) - 1;
}
}
+ remove_marked_cache_entries(&the_index, 1);
+ remove_scheduled_dirs();
errs |= finish_delayed_checkout(&state);
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
@@ -542,6 +566,11 @@ static int skip_merge_working_tree(const struct checkout_opts *opts,
* opts->show_progress only impacts output so doesn't require a merge
*/
+ /*
+ * opts->overlay_mode cannot be used with switching branches so is
+ * not tested here
+ */
+
/*
* If we aren't creating a new branch any changes or updates will
* happen in the existing branch. Since that could only be updating
@@ -1178,6 +1207,10 @@ static int checkout_branch(struct checkout_opts *opts,
die(_("'%s' cannot be used with switching branches"),
"--patch");
+ if (!opts->overlay_mode)
+ die(_("'%s' cannot be used with switching branches"),
+ "--no-overlay");
+
if (opts->writeout_stage)
die(_("'%s' cannot be used with switching branches"),
"--ours/--theirs");
@@ -1266,6 +1299,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
"checkout", "control recursive updating of submodules",
PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
+ OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode")),
OPT_END(),
};
@@ -1274,6 +1308,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
opts.overwrite_ignore = 1;
opts.prefix = prefix;
opts.show_progress = -1;
+ opts.overlay_mode = -1;
git_config(git_checkout_config, &opts);
@@ -1297,6 +1332,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
die(_("-b, -B and --orphan are mutually exclusive"));
+ if (opts.overlay_mode == 1 && opts.patch_mode)
+ die(_("-p and --overlay are mutually exclusive"));
+
/*
* From here on, new_branch will contain the branch to be checked out,
* and new_branch_force and new_orphan_branch will tell us which one of
diff --git a/t/t2025-checkout-no-overlay.sh b/t/t2025-checkout-no-overlay.sh
new file mode 100755
index 0000000000..3575321382
--- /dev/null
+++ b/t/t2025-checkout-no-overlay.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+test_description='checkout --no-overlay <tree-ish> -- <pathspec>'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ git commit --allow-empty -m "initial"
+'
+
+test_expect_success 'checkout --no-overlay deletes files not in <tree>' '
+ >file &&
+ mkdir dir &&
+ >dir/file1 &&
+ git add file dir/file1 &&
+ git checkout --no-overlay HEAD -- file &&
+ test_path_is_missing file &&
+ test_path_is_file dir/file1
+'
+
+test_expect_success 'checkout --no-overlay removing last file from directory' '
+ git checkout --no-overlay HEAD -- dir/file1 &&
+ test_path_is_missing dir
+'
+
+test_expect_success 'checkout -p --overlay is disallowed' '
+ test_must_fail git checkout -p --overlay HEAD 2>actual &&
+ test_i18ngrep "fatal: -p and --overlay are mutually exclusive" actual
+'
+
+test_expect_success '--no-overlay --theirs with M/D conflict deletes file' '
+ test_commit file1 file1 &&
+ test_commit file2 file2 &&
+ git rm --cached file1 &&
+ echo 1234 >file1 &&
+ F1=$(git rev-parse HEAD:file1) &&
+ F2=$(git rev-parse HEAD:file2) &&
+ {
+ echo "100644 $F1 1 file1" &&
+ echo "100644 $F2 2 file1"
+ } | git update-index --index-info &&
+ test_path_is_file file1 &&
+ git checkout --theirs --no-overlay -- file1 &&
+ test_path_is_missing file1
+'
+
+test_done
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 175f83d704..a3fd9a9630 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1436,6 +1436,7 @@ test_expect_success 'double dash "git checkout"' '
--progress Z
--no-quiet Z
--no-... Z
+ --overlay Z
EOF
'
--
2.20.0.405.gbc1bbc6f85
^ permalink raw reply related
* [PATCH 4/8] read-cache: add invalidate parameter to remove_marked_cache_entries
From: Thomas Gummerer @ 2018-12-09 20:04 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy,
Elijah Newren, Thomas Gummerer
In-Reply-To: <20181209200449.16342-1-t.gummerer@gmail.com>
When marking cache entries for removal, and later removing them all at
once using 'remove_marked_cache_entries()', cache entries currently
have to be invalidated manually in the cache tree and in the untracked
cache.
Add an invalidate flag to the function. With the flag set, the
function will take care of invalidating the path in the cache tree and
in the untracked cache.
This will be useful in a subsequent commit.
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
For the two current callsites, unpack-trees seems to do this
invalidation itself internally. I don't quite understand why we don't
need it in split-index mode though. I assume it's because the cache
tree in the main index would already have been invalidated? I didn't
have much time to dig, but couldn't produce any failures with it
either, so I assume not invalidating paths is the right thing to do
here.
cache.h | 2 +-
read-cache.c | 8 +++++++-
split-index.c | 2 +-
unpack-trees.c | 2 +-
4 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/cache.h b/cache.h
index c1c953e810..1deee48f5b 100644
--- a/cache.h
+++ b/cache.h
@@ -751,7 +751,7 @@ extern void rename_index_entry_at(struct index_state *, int pos, const char *new
/* Remove entry, return true if there are more entries to go. */
extern int remove_index_entry_at(struct index_state *, int pos);
-extern void remove_marked_cache_entries(struct index_state *istate);
+extern void remove_marked_cache_entries(struct index_state *istate, int invalidate);
extern int remove_file_from_index(struct index_state *, const char *path);
#define ADD_CACHE_VERBOSE 1
#define ADD_CACHE_PRETEND 2
diff --git a/read-cache.c b/read-cache.c
index 4ca81286c0..d86a06acba 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -590,13 +590,19 @@ int remove_index_entry_at(struct index_state *istate, int pos)
* CE_REMOVE is set in ce_flags. This is much more effective than
* calling remove_index_entry_at() for each entry to be removed.
*/
-void remove_marked_cache_entries(struct index_state *istate)
+void remove_marked_cache_entries(struct index_state *istate, int invalidate)
{
struct cache_entry **ce_array = istate->cache;
unsigned int i, j;
for (i = j = 0; i < istate->cache_nr; i++) {
if (ce_array[i]->ce_flags & CE_REMOVE) {
+ if (invalidate) {
+ cache_tree_invalidate_path(istate,
+ ce_array[i]->name);
+ untracked_cache_remove_from_index(istate,
+ ce_array[i]->name);
+ }
remove_name_hash(istate, ce_array[i]);
save_or_free_index_entry(istate, ce_array[i]);
}
diff --git a/split-index.c b/split-index.c
index 5820412dc5..8aebc3661b 100644
--- a/split-index.c
+++ b/split-index.c
@@ -162,7 +162,7 @@ void merge_base_index(struct index_state *istate)
ewah_each_bit(si->replace_bitmap, replace_entry, istate);
ewah_each_bit(si->delete_bitmap, mark_entry_for_delete, istate);
if (si->nr_deletions)
- remove_marked_cache_entries(istate);
+ remove_marked_cache_entries(istate, 0);
for (i = si->nr_replacements; i < si->saved_cache_nr; i++) {
if (!ce_namelen(si->saved_cache[i]))
diff --git a/unpack-trees.c b/unpack-trees.c
index e8d1a6ac50..8e6afa924d 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -392,7 +392,7 @@ static int check_updates(struct unpack_trees_options *o)
unlink_entry(ce);
}
}
- remove_marked_cache_entries(index);
+ remove_marked_cache_entries(index, 0);
remove_scheduled_dirs();
if (should_update_submodules() && o->update && !o->dry_run)
--
2.20.0.405.gbc1bbc6f85
^ permalink raw reply related
* [PATCH 2/8] entry: factor out unlink_entry function
From: Thomas Gummerer @ 2018-12-09 20:04 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy,
Elijah Newren, Thomas Gummerer
In-Reply-To: <20181209200449.16342-1-t.gummerer@gmail.com>
Factor out the 'unlink_entry()' function from unpack-trees.c to
entry.c. It will be used in other places as well in subsequent
steps.
As it's no longer a static function, also move the documentation to
the header file to make it more discoverable.
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
cache.h | 5 +++++
entry.c | 15 +++++++++++++++
unpack-trees.c | 19 -------------------
3 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/cache.h b/cache.h
index ca36b44ee0..c1c953e810 100644
--- a/cache.h
+++ b/cache.h
@@ -1542,6 +1542,11 @@ struct checkout {
extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
extern void enable_delayed_checkout(struct checkout *state);
extern int finish_delayed_checkout(struct checkout *state);
+/*
+ * Unlink the last component and schedule the leading directories for
+ * removal, such that empty directories get removed.
+ */
+extern void unlink_entry(const struct cache_entry *ce);
struct cache_def {
struct strbuf path;
diff --git a/entry.c b/entry.c
index 5d136c5d55..3ec148ceee 100644
--- a/entry.c
+++ b/entry.c
@@ -508,3 +508,18 @@ int checkout_entry(struct cache_entry *ce,
create_directories(path.buf, path.len, state);
return write_entry(ce, path.buf, state, 0);
}
+
+void unlink_entry(const struct cache_entry *ce)
+{
+ const struct submodule *sub = submodule_from_ce(ce);
+ if (sub) {
+ /* state.force is set at the caller. */
+ submodule_move_head(ce->name, "HEAD", NULL,
+ SUBMODULE_MOVE_HEAD_FORCE);
+ }
+ if (!check_leading_path(ce->name, ce_namelen(ce)))
+ return;
+ if (remove_or_warn(ce->ce_mode, ce->name))
+ return;
+ schedule_dir_for_removal(ce->name, ce_namelen(ce));
+}
diff --git a/unpack-trees.c b/unpack-trees.c
index 7570df481b..e8d1a6ac50 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -300,25 +300,6 @@ static void load_gitmodules_file(struct index_state *index,
}
}
-/*
- * Unlink the last component and schedule the leading directories for
- * removal, such that empty directories get removed.
- */
-static void unlink_entry(const struct cache_entry *ce)
-{
- const struct submodule *sub = submodule_from_ce(ce);
- if (sub) {
- /* state.force is set at the caller. */
- submodule_move_head(ce->name, "HEAD", NULL,
- SUBMODULE_MOVE_HEAD_FORCE);
- }
- if (!check_leading_path(ce->name, ce_namelen(ce)))
- return;
- if (remove_or_warn(ce->ce_mode, ce->name))
- return;
- schedule_dir_for_removal(ce->name, ce_namelen(ce));
-}
-
static struct progress *get_progress(struct unpack_trees_options *o)
{
unsigned cnt = 0, total = 0;
--
2.20.0.405.gbc1bbc6f85
^ permalink raw reply related
* [PATCH 3/8] entry: support CE_WT_REMOVE flag in checkout_entry
From: Thomas Gummerer @ 2018-12-09 20:04 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy,
Elijah Newren, Thomas Gummerer
In-Reply-To: <20181209200449.16342-1-t.gummerer@gmail.com>
'checkout_entry()' currently only supports creating new entries in the
working tree, but not deleting them. Add the ability to remove
entries at the same time if the entry is marked with the CE_WT_REMOVE
flag.
Currently this doesn't have any effect, as the CE_WT_REMOVE flag is
only used in unpack-tree, however we will make use of this in a
subsequent step in the series.
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
entry.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/entry.c b/entry.c
index 3ec148ceee..cd1c6601b6 100644
--- a/entry.c
+++ b/entry.c
@@ -441,6 +441,13 @@ int checkout_entry(struct cache_entry *ce,
static struct strbuf path = STRBUF_INIT;
struct stat st;
+ if (ce->ce_flags & CE_WT_REMOVE) {
+ if (topath)
+ BUG("Can't remove entry to a path");
+ unlink_entry(ce);
+ return 0;
+ }
+
if (topath)
return write_entry(ce, topath, state, 1);
--
2.20.0.405.gbc1bbc6f85
^ permalink raw reply related
* [PATCH 1/8] move worktree tests to t24*
From: Thomas Gummerer @ 2018-12-09 20:04 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy,
Elijah Newren, Thomas Gummerer
In-Reply-To: <20181209200449.16342-1-t.gummerer@gmail.com>
The 'git worktree' command used to be just another mode in 'git
checkout', namely 'git checkout --to'. When the tests for the latter
were retrofitted for the former, the test name was adjusted, but the
test number was kept, even though the test is testing a different
command now. t/README states: "Second digit tells the particular
command we are testing.", so 'git worktree' should have a separate
number just for itself.
Move the worktree tests to t24* to adhere to that guideline. We're
going to make use of the free'd up numbers in a subsequent commit.
Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
---
t/{t2025-worktree-add.sh => t2400-worktree-add.sh} | 0
t/{t2026-worktree-prune.sh => t2401-worktree-prune.sh} | 0
t/{t2027-worktree-list.sh => t2402-worktree-list.sh} | 0
3 files changed, 0 insertions(+), 0 deletions(-)
rename t/{t2025-worktree-add.sh => t2400-worktree-add.sh} (100%)
rename t/{t2026-worktree-prune.sh => t2401-worktree-prune.sh} (100%)
rename t/{t2027-worktree-list.sh => t2402-worktree-list.sh} (100%)
diff --git a/t/t2025-worktree-add.sh b/t/t2400-worktree-add.sh
similarity index 100%
rename from t/t2025-worktree-add.sh
rename to t/t2400-worktree-add.sh
diff --git a/t/t2026-worktree-prune.sh b/t/t2401-worktree-prune.sh
similarity index 100%
rename from t/t2026-worktree-prune.sh
rename to t/t2401-worktree-prune.sh
diff --git a/t/t2027-worktree-list.sh b/t/t2402-worktree-list.sh
similarity index 100%
rename from t/t2027-worktree-list.sh
rename to t/t2402-worktree-list.sh
--
2.20.0.405.gbc1bbc6f85
^ permalink raw reply
* [PATCH 0/8] introduce no-overlay and cached mode in git checkout
From: Thomas Gummerer @ 2018-12-09 20:04 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy,
Elijah Newren, Thomas Gummerer
Here's the series I mentioned a couple of times on the list already,
introducing a no-overlay mode in 'git checkout'. The inspiration for
this came from Junios message in [*1*].
Basically the idea is to also delete files when the match <pathspec>
in 'git checkout <tree-ish> -- <pathspec>' in the current tree, but
don't match <pathspec> in <tree-ish>. The rest of the cases are
already properly taken care of by 'git checkout'.
The final step in the series is to actually make use of this in 'git
stash', which simplifies the code there a bit. I am however happy to
hold off on this step until the stash-in-C series is merged, so we
don't delay that further.
In addition to the no-overlay mode, we also add a --cached mode, which
works only on the index, thus similar to 'git reset <tree-ish> -- <pathspec>'.
Actually deprecating 'git reset <tree-ish> -- <pathspec>' should come
later, probably not before Duy's restore-files command lands, as 'git
checkout --no-overlay <tree-ish> -- <pathspec>' is a bit cumbersome to
type compared to 'git reset <tree-ish> -- <pathspec>'.
My hope is also that the no-overlay mode could become the new default
in the restore-files command Duy is currently working on.
No documentation yet, as I wanted to get this out for review first.
I'm not familiar with most of the code I touched here, so there may
well be much better ways to implement some of this, that I wasn't able
to figure out. I'd be very happy with some feedback around that.
Another thing I'm not sure about is how to deal with conflicts. In
the cached mode this patch series is not dealing with it at all, as
'git checkout -- <pathspec>' when pathspec matches a file with
conflicts doesn't update the index. For the no-overlay mode, the file
is removed if the corresponding stage is not found in the index. I'm
however not sure this is the right thing to do in all cases?
*1*: <xmqq4loqplou.fsf@gitster.mtv.corp.google.com>
Thomas Gummerer (8):
move worktree tests to t24*
entry: factor out unlink_entry function
entry: support CE_WT_REMOVE flag in checkout_entry
read-cache: add invalidate parameter to remove_marked_cache_entries
checkout: introduce --{,no-}overlay option
checkout: add --cached option
checkout: add allow ignoring unmatched pathspec
stash: use git checkout --index
builtin/checkout.c | 66 +++++++++--
cache.h | 7 +-
entry.c | 22 ++++
git-stash.sh | 12 +-
read-cache.c | 8 +-
split-index.c | 2 +-
t/t2016-checkout-patch.sh | 8 ++
t/t2022-checkout-paths.sh | 9 ++
t/t2025-checkout-no-overlay.sh | 31 ++++++
t/t2026-checkout-cached.sh | 103 ++++++++++++++++++
...-worktree-add.sh => t2400-worktree-add.sh} | 0
...ktree-prune.sh => t2401-worktree-prune.sh} | 0
...orktree-list.sh => t2402-worktree-list.sh} | 0
t/t9902-completion.sh | 3 +
unpack-trees.c | 21 +---
15 files changed, 251 insertions(+), 41 deletions(-)
create mode 100755 t/t2025-checkout-no-overlay.sh
create mode 100755 t/t2026-checkout-cached.sh
rename t/{t2025-worktree-add.sh => t2400-worktree-add.sh} (100%)
rename t/{t2026-worktree-prune.sh => t2401-worktree-prune.sh} (100%)
rename t/{t2027-worktree-list.sh => t2402-worktree-list.sh} (100%)
--
2.20.0.rc2.411.g8f28e744c2
^ permalink raw reply
* Re: [PATCH] rebase docs: drop stray word in merge command description
From: Johannes Schindelin @ 2018-12-09 19:41 UTC (permalink / raw)
To: Kyle Meyer; +Cc: git
In-Reply-To: <20181208231541.1341999-1-kyle@kyleam.com>
Hi Kyle,
On Sat, 8 Dec 2018, Kyle Meyer wrote:
> Delete a misplaced word introduced by caafecfcf1 (rebase
> --rebase-merges: adjust man page for octopus support, 2018-03-09).
>
> Signed-off-by: Kyle Meyer <kyle@kyleam.com>
ACK.
Too bad this did not make it into v2.20.0, but at least it can make it
into a future version.
Thanks,
Johannes
> ---
> Documentation/git-rebase.txt | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
> index dff17b3178..2ee535fb23 100644
> --- a/Documentation/git-rebase.txt
> +++ b/Documentation/git-rebase.txt
> @@ -979,7 +979,7 @@ when the merge operation did not even start), it is rescheduled immediately.
>
> At this time, the `merge` command will *always* use the `recursive`
> merge strategy for regular merges, and `octopus` for octopus merges,
> -strategy, with no way to choose a different one. To work around
> +with no way to choose a different one. To work around
> this, an `exec` command can be used to call `git merge` explicitly,
> using the fact that the labels are worktree-local refs (the ref
> `refs/rewritten/onto` would correspond to the label `onto`, for example).
> --
> 2.19.2
>
>
^ permalink raw reply
* Announcing Pro Git Second Edition Reedited
From: Jon Forrest @ 2018-12-09 18:42 UTC (permalink / raw)
To: git
Several years ago I released what I called Pro Git Reedited. This was an
attempt to tighten up the text of the excellent Pro Git book written by
Scott Chacon. Since then, Scott and Ben Straub released the second
edition of Pro Git so once again I'm releasing a reedited version of
what they wrote. I hope you enjoy it.
The PDF of my version is at
https://drive.google.com/file/d/18wGebSU0dyYU1L_bfyoDQtZRF1Vo1H3p/view?usp=sharing
If there's enough interest, I'll try to put up an HTML version of
the book.
Cordially,
Jon Forrest
^ permalink raw reply
* Git Test Coverage Report (Sunday, Dec 9)
From: Derrick Stolee @ 2018-12-09 18:04 UTC (permalink / raw)
To: git@vger.kernel.org
Here is today's coverage report.
Thanks,
-Stolee
[1] https://dev.azure.com/git/git/_build/results?buildId=287
---
pu: dfcf84ebfa17eb0bb3b57806fa530e87d8c8f1b8
jch: dd824ca506dbdf198282714b0bd21665c5825b4d
next: bc1bbc6f855c3b5ef7fcbd0f688f647c4e5b208b
master: 5d826e972970a784bd7a7bdf587512510097b8c7
master@{1}: 7068cbc4abac53d9c3675dfba81c1e97d25e8eeb
Uncovered code in 'pu' not in 'jch'
--------------------------------------
builtin/blame.c
74e8221b52 builtin/blame.c 928) blame_date_width = sizeof("Thu Oct 19
16:00");
74e8221b52 builtin/blame.c 929) break;
builtin/remote.c
b7f4e371e7 builtin/remote.c 1551) die(_("--save-to-push cannot be used
with other options"));
b7f4e371e7 builtin/remote.c 1575) die(_("--save-to-push can only be used
when only one url is defined"));
commit-graph.c
721351787e 128) return NULL;
721351787e 131) return NULL;
721351787e 187) free(graph);
721351787e 188) return NULL;
721351787e 223) free(graph);
721351787e 224) return NULL;
date.c
74e8221b52 113) die("Timestamp too large for this system: %"PRItime, time);
74e8221b52 216) if (tm->tm_mon == human_tm->tm_mon) {
74e8221b52 217) if (tm->tm_mday > human_tm->tm_mday) {
74e8221b52 219) } else if (tm->tm_mday == human_tm->tm_mday) {
74e8221b52 220) hide.date = hide.wday = 1;
74e8221b52 221) } else if (tm->tm_mday + 5 > human_tm->tm_mday) {
74e8221b52 223) hide.date = 1;
74e8221b52 231) gettimeofday(&now, NULL);
74e8221b52 232) show_date_relative(time, tz, &now, buf);
74e8221b52 233) return;
74e8221b52 246) hide.seconds = 1;
74e8221b52 247) hide.tz |= !hide.date;
74e8221b52 248) hide.wday = hide.time = !hide.year;
74e8221b52 262) strbuf_rtrim(buf);
74e8221b52 287) gettimeofday(&now, NULL);
74e8221b52 290) human_tz = local_time_tzoffset(now.tv_sec, &human_tm);
74e8221b52 886) static int auto_date_style(void)
74e8221b52 888) return (isatty(1) || pager_in_use()) ? DATE_HUMAN :
DATE_NORMAL;
74e8221b52 909) return DATE_HUMAN;
74e8221b52 911) return auto_date_style();
pretty.c
4681fe38e1 1069) return 0;
b755bf6f83 1107) match_placeholder_arg(p, "=on", end) ||
b755bf6f83 1108) match_placeholder_arg(p, "=true", end)) {
protocol.c
24c10f7473 37) die(_("Unrecognized protocol version"));
24c10f7473 39) die(_("Unrecognized protocol_version"));
remote-curl.c
24c10f7473 403) return 0;
strbuf.c
18f8e81091 397) return 0;
submodule.c
26f80ccfc1 1396) strbuf_release(&gitdir);
be76c21282 1519) struct fetch_task *task = task_cb;
be76c21282 1523) fetch_task_release(task);
wrapper.c
5efde212fc 70) die("Out of memory, malloc failed (tried to allocate %"
PRIuMAX " bytes)",
5efde212fc 73) error("Out of memory, malloc failed (tried to allocate
%" PRIuMAX " bytes)",
Commits introducing uncovered code:
Anders Waldenborg 18f8e8109: strbuf: separate callback for
strbuf_expand:ing literals
Anders Waldenborg 4681fe38e: pretty: allow showing specific trailers
Anders Waldenborg b755bf6f8: pretty: allow %(trailers) options with
explicit value
Denton Liu b7f4e371e: remote: add --save-to-push option to git
remote set-url
Josh Steadmon 24c10f747: protocol: advertise multiple supported
versions
Josh Steadmon 721351787: commit-graph, fuzz: add fuzzer for
commit-graph
Linus Torvalds 74e8221b5: Add 'human' date format
Martin Koegler 5efde212f: zlib.c: use size_t for size
Stefan Beller 26f80ccfc: submodule: migrate get_next_submodule to
use repository structs
Stefan Beller be76c2128: fetch: ensure submodule objects fetched
Uncovered code in 'jch' not in 'next'
----------------------------------------
apply.c
0f086e6dca 3355) if (checkout_entry(ce, &costate, NULL, NULL) ||
0f086e6dca 3356) lstat(ce->name, st))
builtin/branch.c
0ecb1fc726 builtin/branch.c 456) die(_("could not resolve HEAD"));
0ecb1fc726 builtin/branch.c 462) die(_("HEAD (%s) points outside of
refs/heads/"), refname);
hex.c
47edb64997 93) char *sha1_to_hex_r(char *buffer, const unsigned char *sha1)
47edb64997 95) return hash_to_hex_algop_r(buffer, sha1,
&hash_algos[GIT_HASH_SHA1]);
47edb64997 116) char *hash_to_hex(const unsigned char *hash)
47edb64997 118) return hash_to_hex_algop(hash, the_hash_algo);
pathspec.c
22af33bece 671) name = to_free = xmemdupz(name, namelen);
read-cache.c
ee70c12820 1730) if (advice_unknown_index_extension) {
ee70c12820 1731) warning(_("ignoring optional %.4s index extension"), ext);
ee70c12820 1732) advise(_("This is likely due to the file having been
written by a newer\n"
ec36c42a63 3508) const char *index = NULL;
ec36c42a63 3514) if (!offset)
ec36c42a63 3515) return NULL;
ec36c42a63 3516) while (offset <= mmap_size - the_hash_algo->rawsz - 8) {
ec36c42a63 3517) extsize = get_be32(mmap + offset + 4);
ec36c42a63 3518) if (CACHE_EXT((mmap + offset)) ==
CACHE_EXT_INDEXENTRYOFFSETTABLE) {
ec36c42a63 3519) index = mmap + offset + 4 + 4;
ec36c42a63 3520) break;
ec36c42a63 3522) offset += 8;
ec36c42a63 3523) offset += extsize;
ec36c42a63 3525) if (!index)
ec36c42a63 3526) return NULL;
ec36c42a63 3529) ext_version = get_be32(index);
ec36c42a63 3530) if (ext_version != IEOT_VERSION) {
ec36c42a63 3531) error("invalid IEOT version %d", ext_version);
ec36c42a63 3532) return NULL;
ec36c42a63 3534) index += sizeof(uint32_t);
ec36c42a63 3537) nr = (extsize - sizeof(uint32_t)) / (sizeof(uint32_t) +
sizeof(uint32_t));
ec36c42a63 3538) if (!nr) {
ec36c42a63 3539) error("invalid number of IEOT entries %d", nr);
ec36c42a63 3540) return NULL;
ec36c42a63 3542) ieot = xmalloc(sizeof(struct index_entry_offset_table)
ec36c42a63 3543) + (nr * sizeof(struct index_entry_offset)));
ec36c42a63 3544) ieot->nr = nr;
ec36c42a63 3545) for (i = 0; i < nr; i++) {
ec36c42a63 3546) ieot->entries[i].offset = get_be32(index);
ec36c42a63 3547) index += sizeof(uint32_t);
ec36c42a63 3548) ieot->entries[i].nr = get_be32(index);
ec36c42a63 3549) index += sizeof(uint32_t);
ec36c42a63 3552) return ieot;
sequencer.c
18e711a162 2387) opts->quiet = 1;
sha1-file.c
2f90b9d9b4 sha1-file.c 172) int hash_algo_by_name(const char *name)
2f90b9d9b4 sha1-file.c 175) if (!name)
2f90b9d9b4 sha1-file.c 176) return GIT_HASH_UNKNOWN;
2f90b9d9b4 sha1-file.c 177) for (i = 1; i < GIT_HASH_NALGOS; i++)
2f90b9d9b4 sha1-file.c 178) if (!strcmp(name, hash_algos[i].name))
2f90b9d9b4 sha1-file.c 179) return i;
2f90b9d9b4 sha1-file.c 180) return GIT_HASH_UNKNOWN;
2f90b9d9b4 sha1-file.c 183) int hash_algo_by_id(uint32_t format_id)
2f90b9d9b4 sha1-file.c 186) for (i = 1; i < GIT_HASH_NALGOS; i++)
2f90b9d9b4 sha1-file.c 187) if (format_id == hash_algos[i].format_id)
2f90b9d9b4 sha1-file.c 188) return i;
2f90b9d9b4 sha1-file.c 189) return GIT_HASH_UNKNOWN;
tree.c
e092073d64 104) commit = lookup_commit(r, entry.oid);
Commits introducing uncovered code:
brian m. carlson 2f90b9d9b: sha1-file: provide functions to look up
hash algorithms
brian m. carlson 47edb6499: hex: introduce functions to print
arbitrary hashes
Daniels Umanovskis 0ecb1fc72: branch: introduce --show-current
display option
Elijah Newren 18e711a16: git-rebase, sequencer: extend --quiet
option for the interactive machinery
Jonathan Nieder ee70c1282: index: offer advice for unknown index
extensions
Nguyễn Thái Ngọc Duy 0f086e6dc: checkout: print something when
checking out paths
Nguyễn Thái Ngọc Duy 22af33bec: dir.c: move, rename and export
match_attrs()
Nguyễn Thái Ngọc Duy e092073d6: tree.c: make read_tree*() take
'struct repository *'
Nguyễn Thái Ngọc Duy ec36c42a6: Indent code with TABs
Uncovered code in 'next' not in 'master'
--------------------------------------------
archive.c
c6e7965ddf 399) die(_("not a valid object name: %s"), name);
c6e7965ddf 412) die(_("not a tree object: %s"), oid_to_hex(&oid));
c6e7965ddf 422) die(_("current working directory is untracked"));
attr.c
ad8f8f4aed 369) fprintf_ln(stderr, _("%s not allowed: %s:%d"),
blame.c
fb998eae6c 1717) obj = deref_tag(revs->repo, obj, NULL, 0);
fb998eae6c 1724) head_commit = lookup_commit_reference_gently(revs->repo,
builtin/bundle.c
74ae4b638d builtin/bundle.c 64) return !!unbundle(the_repository,
&header, bundle_fd, 0) ||
builtin/fast-export.c
b93b81e799 builtin/fast-export.c 52) signed_tag_mode = SIGNED_TAG_ABORT;
b93b81e799 builtin/fast-export.c 70) tag_of_filtered_mode =
TAG_FILTERING_ABORT;
f129c4275c builtin/fast-export.c 202) if (!p->parents)
f129c4275c builtin/fast-export.c 203) return NULL;
f129c4275c builtin/fast-export.c 204) p = p->parents->item;
f129c4275c builtin/fast-export.c 205) }
843b9e6d48 builtin/fast-export.c 265) die("oid mismatch in blob %s",
oid_to_hex(oid));
a965bb3116 builtin/fast-export.c 277) printf("original-oid %s\n",
oid_to_hex(oid));
843b9e6d48 builtin/fast-export.c 356) const unsigned hashsz =
the_hash_algo->rawsz;
843b9e6d48 builtin/fast-export.c 357) unsigned char *out =
xcalloc(hashsz, 1);
843b9e6d48 builtin/fast-export.c 358) put_be32(out + hashsz - 4,
counter++);
843b9e6d48 builtin/fast-export.c 362) static const struct object_id
*anonymize_oid(const struct object_id *oid)
843b9e6d48 builtin/fast-export.c 365) size_t len = the_hash_algo->rawsz;
843b9e6d48 builtin/fast-export.c 366) return anonymize_mem(&objs,
generate_fake_oid, oid, &len);
843b9e6d48 builtin/fast-export.c 426) anonymize_oid(&spec->oid) :
a965bb3116 builtin/fast-export.c 644) printf("original-oid %s\n",
oid_to_hex(&commit->object.oid));
530ca19c02 builtin/fast-export.c 668) printf("%s\n", oid_to_hex(anonymize ?
530ca19c02 builtin/fast-export.c 669) anonymize_oid(&obj->oid) :
f129c4275c builtin/fast-export.c 810) p = rewrite_commit((struct commit
*)tagged);
f129c4275c builtin/fast-export.c 811) if (!p) {
f129c4275c builtin/fast-export.c 812) printf("reset %s\nfrom %s\n\n",
f129c4275c builtin/fast-export.c 814) free(buf);
f129c4275c builtin/fast-export.c 815) return;
a965bb3116 builtin/fast-export.c 825) printf("original-oid %s\n",
oid_to_hex(&tag->object.oid));
cd13762d8f builtin/fast-export.c 943) printf("reset %s\nfrom %s\n\n",
cd13762d8f builtin/fast-export.c 945) continue;
530ca19c02 builtin/fast-export.c 960) if (!reference_excluded_commits) {
530ca19c02 builtin/fast-export.c 962) printf("reset %s\nfrom %s\n\n",
530ca19c02 builtin/fast-export.c 964) continue;
530ca19c02 builtin/fast-export.c 967) printf("reset %s\nfrom %s\n\n", name,
530ca19c02 builtin/fast-export.c 968) oid_to_hex(&commit->object.oid));
fdf31b6369 builtin/fast-export.c 969) continue;
builtin/fsck.c
674ba34038 builtin/fsck.c 87) ret = _("unknown");
674ba34038 builtin/fsck.c 167) objerror(parent, _("wrong object type in
link"));
674ba34038 builtin/fsck.c 278) printf_ln(_("unreachable %s %s"),
printable_type(obj),
674ba34038 builtin/fsck.c 306) error(_("could not create lost-found"));
674ba34038 builtin/fsck.c 313) die_errno(_("could not write '%s'"),
filename);
674ba34038 builtin/fsck.c 317) die_errno(_("could not finish '%s'"),
674ba34038 builtin/fsck.c 334) fprintf_ln(stderr, _("Checking %s"),
describe_object(obj));
674ba34038 builtin/fsck.c 352) fprintf_ln(stderr, _("Checking
connectivity (%d objects)"), max);
674ba34038 builtin/fsck.c 371) fprintf_ln(stderr, _("Checking %s %s"),
674ba34038 builtin/fsck.c 384) printf_ln(_("root %s"),
674ba34038 builtin/fsck.c 421) return error(_("%s: object corrupt or
missing"),
674ba34038 builtin/fsck.c 460) fprintf_ln(stderr, _("Checking reflog
%s->%s"),
674ba34038 builtin/fsck.c 584) error(_("%s: object could not be parsed:
%s"),
674ba34038 builtin/fsck.c 619) fprintf_ln(stderr, _("Checking object
directory"));
5215bd2f7d builtin/fsck.c 637) fprintf_ln(stderr, _("Checking %s link"),
head_ref_name);
5215bd2f7d builtin/fsck.c 642) return error(_("invalid %s"), head_ref_name);
674ba34038 builtin/fsck.c 671) fprintf_ln(stderr, _("Checking cache tree"));
674ba34038 builtin/fsck.c 687) err |= objerror(obj, _("non-tree in
cache-tree"));
builtin/merge.c
9440b831ad builtin/merge.c 135) return error(_("option `%s' requires a
value"), opt->long_name);
builtin/pull.c
b19eee9066 647) argv_array_push(&args, opt_cleanup);
builtin/rebase--interactive.c
005af339c9 builtin/rebase--interactive.c 262) ret =
rearrange_squash(the_repository);
005af339c9 builtin/rebase--interactive.c 265) ret =
sequencer_add_exec_commands(the_repository, cmd);
builtin/reflog.c
dd509db342 builtin/reflog.c 592) usage(_(reflog_expire_usage));
dd509db342 builtin/reflog.c 643) status |= error(_("%s points
nowhere!"), argv[i]);
dd509db342 builtin/reflog.c 689) usage(_(reflog_delete_usage));
dd509db342 builtin/reflog.c 695) return error(_("no reflog specified to
delete"));
dd509db342 builtin/reflog.c 704) status |= error(_("not a reflog: %s"),
argv[i]);
dd509db342 builtin/reflog.c 709) status |= error(_("no reflog for
'%s'"), argv[i]);
dd509db342 builtin/reflog.c 744) usage(_(reflog_exists_usage));
dd509db342 builtin/reflog.c 752) usage(_(reflog_exists_usage));
dd509db342 builtin/reflog.c 755) die(_("invalid ref format: %s"),
argv[start]);
builtin/repack.c
c83d950e59 200) die(_("could not start pack-objects to repack promisor
objects"));
5215bd2f7d 239) die(_("repack: Expecting full hex object ID lines only
from pack-objects."));
c83d950e59 250) die_errno(_("unable to create '%s'"), promisor_name);
5215bd2f7d 411) die(_("repack: Expecting full hex object ID lines only
from pack-objects."));
bundle.c
74ae4b638d 394) struct commit *one = lookup_commit_reference(revs->repo,
&oid);
delta-islands.c
385cb64ff3 216) parse_object(r, &obj->oid);
fast-import.c
a965bb3116 1821) read_next_command();
git.c
8aa8c14097 341) die_errno(_("while expanding alias '%s': '%s'"),
8aa8c14097 350) die(_("alias '%s' changes environment variables.\n"
8aa8c14097 358) die(_("empty alias for %s"), alias_command);
8aa8c14097 361) die(_("recursive alias: %s"), alias_command);
8aa8c14097 412) die(_("%s doesn't support --super-prefix"), p->cmd);
8aa8c14097 436) die_errno(_("write failure on standard output"));
8aa8c14097 438) die(_("unknown write failure on standard output"));
8aa8c14097 440) die_errno(_("close failed on standard output"));
8aa8c14097 657) die(_("%s doesn't support --super-prefix"), argv[0]);
8aa8c14097 769) die(_("cannot handle %s as a builtin"), cmd);
http-walker.c
b69fb867b4 http-walker.c 550) loose_object_path(the_repository, &buf,
req->sha1);
http.c
d73019feb4 289) return git_config_string(&curl_http_version, var, value);
d73019feb4 797) static int get_curl_http_version_opt(const char
*version_string, long *opt)
d73019feb4 808) for (i = 0; i < ARRAY_SIZE(choice); i++) {
d73019feb4 809) if (!strcmp(version_string, choice[i].name)) {
d73019feb4 810) *opt = choice[i].opt_token;
d73019feb4 811) return 0;
d73019feb4 815) warning("unknown value given to http.version: '%s'",
version_string);
d73019feb4 816) return -1; /* not found */
d73019feb4 841) if (!get_curl_http_version_opt(curl_http_version, &opt)) {
d73019feb4 843) curl_easy_setopt(result, CURLOPT_HTTP_VERSION, opt);
merge-recursive.c
37b65ce36b 1584) return -1;
37b65ce36b 1587) return -1;
37b65ce36b 1593) return -1;
37b65ce36b 1596) return -1;
37b65ce36b 1663) return -1;
37b65ce36b 1666) return -1;
37b65ce36b 1669) return -1;
7f8671656f 1702) return -1;
48c9cb9d6d 1758) return -1;
48c9cb9d6d 1806) return -1;
48c9cb9d6d 1812) return -1;
48c9cb9d6d 1815) return -1;
48c9cb9d6d 1825) return -1;
48c9cb9d6d 1831) return -1;
48c9cb9d6d 1834) return -1;
parse-options-cb.c
9440b831ad 21) return error(_("option `%s' expects a numerical value"),
9440b831ad 51) return error(_("option `%s' expects \"always\",
\"auto\", or \"never\""),
parse-options.c
9440b831ad 88) return error(_("%s takes no value"), optname(opt, flags));
9440b831ad 90) return error(_("%s isn't available"), optname(opt, flags));
9440b831ad 92) return error(_("%s takes no value"), optname(opt, flags));
9440b831ad 178) return error(_("%s expects a numerical value"),
9440b831ad 194) return error(_("%s expects a non-negative integer value"
8900342628 356) error(_("did you mean `--%s` (with two dashes ?)"), arg);
8900342628 651) error(_("unknown non-ascii option in string: `%s'"),
9440b831ad 785) strbuf_addf(&sb, "option `no-%s'", opt->long_name);
read-cache.c
9d0a9e9089 675) die(_("will not add file alias '%s' ('%s' already
exists in index)"),
9d0a9e9089 676) ce->name, alias->name);
9d0a9e9089 691) die(_("cannot create an empty blob in the object
database"));
9d0a9e9089 712) return error(_("%s: can only add regular files,
symbolic links or git-directories"), path);
9d0a9e9089 786) return error(_("unable to add '%s' to index"), path);
9d0a9e9089 822) error(_("invalid path '%s'"), path);
9d0a9e9089 848) error(_("invalid path '%s'"), path);
9d0a9e9089 1686) return error(_("bad signature 0x%08x"),
hdr->hdr_signature);
9d0a9e9089 1689) return error(_("bad index version %d"), hdr_version);
9d0a9e9089 1728) return error(_("index uses %.4s extension, which we do
not understand"),
9d0a9e9089 1730) fprintf_ln(stderr, _("ignoring %.4s extension"), ext);
9d0a9e9089 1777) die(_("unknown index entry format 0x%08x"),
extended_flags);
9d0a9e9089 1848) die(_("unordered stage entries in index"));
9d0a9e9089 1851) die(_("multiple stage entries for merged file '%s'"),
9d0a9e9089 1854) die(_("unordered stage entries for '%s'"),
9d0a9e9089 2148) die_errno(_("%s: index file open failed"), path);
9d0a9e9089 2152) die_errno(_("%s: cannot stat the open index"), path);
9d0a9e9089 2156) die(_("%s: index file smaller than expected"), path);
9d0a9e9089 2160) die_errno(_("%s: unable to map index file"), path);
9d0a9e9089 2251) warning(_("could not freshen shared index '%s'"),
shared_index);
9d0a9e9089 2286) die(_("broken index, expect %s in %s, got %s"),
9d0a9e9089 3100) error(_("cannot fix permission bits on '%s'"),
get_tempfile_path(*temp));
9d0a9e9089 3247) return error(_("%s: cannot drop to stage #0"),
ref-filter.c
bbfc042ef9 236) oi_deref.info.sizep = &oi_deref.size;
bbfc042ef9 245) return strbuf_addf_ret(err, -1, _("unrecognized
%%(objectsize) argument: %s"), arg);
ab0e367154 253) return strbuf_addf_ret(err, -1, _("%%(deltabase) does
not take arguments"));
9440b831ad 2353) return error(_("option `%s' is incompatible with
--no-merged"),
remote-curl.c
afa5d74929 359) die("invalid server response; expected service, got
flush packet");
remote.c
0b9c3afdbf 363) warning(_("config remote shorthand cannot begin with
'/': %s"),
0b9c3afdbf 418) error(_("more than one uploadpack given, using the
first"));
0b9c3afdbf 684) die(_("key '%s' of pattern had no '*'"), key);
0b9c3afdbf 694) die(_("value '%s' of pattern has no '*'"), value);
0b9c3afdbf 1102) error(_("unable to delete '%s': remote ref does not
exist"),
0b9c3afdbf 1121) return error(_("dst ref %s receives from more than one
src"),
0b9c3afdbf 1840) die(_("couldn't find remote ref %s"), name);
0b9c3afdbf 1853) error(_("* Ignoring funny ref '%s' locally"),
0b9c3afdbf 1948) die(_("revision walk setup failed"));
0b9c3afdbf 2221) return error(_("cannot parse expected object name '%s'"),
sequencer.c
f11c958054 593) istate->cache_tree = cache_tree();
f11c958054 3974) res = error_dirty_index(r->index, opts);
sha1-file.c
f0eaf63819 sha1-file.c 2145) return r;
Commits introducing uncovered code:
Denton Liu b19eee906: merge: add scissors line on merge conflict
Elijah Newren 37b65ce36: merge-recursive: new function for better
colliding conflict resolutions
Elijah Newren 48c9cb9d6: merge-recursive: improve
rename/rename(1to2)/add[/add] handling
Elijah Newren 530ca19c0: fast-export: add
--reference-excluded-parents option
Elijah Newren 7f8671656: merge-recursive: fix rename/add conflict
handling
Elijah Newren 843b9e6d4: fast-export: convert sha1 to oid
Elijah Newren a965bb311: fast-export: add a --show-original-ids
option to show original names
Elijah Newren b93b81e79: fast-export: use value from correct enum
Elijah Newren cd13762d8: fast-export: when using paths, avoid
corrupt stream with non-existent mark
Elijah Newren f129c4275: fast-export: move commit rewriting logic
into a function for reuse
Elijah Newren fdf31b636: fast-export: ensure we export requested refs
Force Charlie d73019feb: http: add support selecting http version
Jeff King afa5d7492: remote-curl: refactor smart-http discovery
Jeff King b69fb867b: sha1_file_name(): overwrite buffer instead of
appending
Jeff King f0eaf6381: sha1-file: use an object_directory for the
main object dir
Junio C Hamano 5215bd2f7: Merge branch 'nd/i18n' into next
Nguyễn Thái Ngọc Duy 005af339c: sequencer.c: remove implicit
dependency on the_repository
Nguyễn Thái Ngọc Duy 0b9c3afdb: remote.c: mark messages for translation
Nguyễn Thái Ngọc Duy 385cb64ff: delta-islands.c: remove
the_repository references
Nguyễn Thái Ngọc Duy 674ba3403: fsck: mark strings for translation
Nguyễn Thái Ngọc Duy 74ae4b638: bundle.c: remove the_repository
references
Nguyễn Thái Ngọc Duy 890034262: parse-options.c: mark more strings
for translation
Nguyễn Thái Ngọc Duy 8aa8c1409: git.c: mark more strings for
translation
Nguyễn Thái Ngọc Duy 9440b831a: parse-options: replace opterror()
with optname()
Nguyễn Thái Ngọc Duy 9d0a9e908: read-cache.c: mark more strings for
translation
Nguyễn Thái Ngọc Duy ad8f8f4ae: attr.c: mark more string for
translation
Nguyễn Thái Ngọc Duy c6e7965dd: archive.c: mark more strings for
translation
Nguyễn Thái Ngọc Duy c83d950e5: repack: mark more strings for
translation
Nguyễn Thái Ngọc Duy dd509db34: reflog: mark strings for translation
Nguyễn Thái Ngọc Duy f11c95805: sequencer.c: remove implicit
dependency on the_index
Nguyễn Thái Ngọc Duy fb998eae6: blame.c: remove implicit dependency
the_repository
Olga Telezhnaya ab0e36715: ref-filter: add deltabase option
Olga Telezhnaya bbfc042ef: ref-filter: add objectsize:disk option
Uncovered code in 'master' not in 'master@{1}'
----------------------------------------------------
builtin/rebase.c
13a5a9f0fd 789) return; /* only override it if it is "rebase" */
range-diff.c
d8981c3f88 466) diff_setup(&opts);
Commits introducing uncovered code:
Johannes Schindelin 13a5a9f0f: rebase: fix GIT_REFLOG_ACTION regression
Junio C Hamano d8981c3f8: format-patch: do not let its diff-options
affect --range-diff
^ permalink raw reply
* [PATCH 13/24] backup-log: add prune command
From: Nguyễn Thái Ngọc Duy @ 2018-12-09 10:44 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <20181209104419.12639-1-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
backup-log.c | 71 ++++++++++++++++++++++++++++++++++++++++++++
backup-log.h | 3 ++
builtin/backup-log.c | 17 +++++++++++
3 files changed, 91 insertions(+)
diff --git a/backup-log.c b/backup-log.c
index 49f2ce68fe..5e38725981 100644
--- a/backup-log.c
+++ b/backup-log.c
@@ -1,6 +1,8 @@
#include "cache.h"
#include "backup-log.h"
+#include "blob.h"
#include "lockfile.h"
+#include "object-store.h"
#include "strbuf.h"
void bkl_append(struct strbuf *output, const char *path,
@@ -217,3 +219,72 @@ int bkl_parse_file(const char *path,
strbuf_release(&sb);
return ret;
}
+
+struct prune_options {
+ struct repository *repo;
+ FILE *fp;
+ timestamp_t expire;
+ struct strbuf copy;
+};
+
+static int good_oid(struct repository *r, const struct object_id *oid)
+{
+ if (is_null_oid(oid))
+ return 1;
+
+ return oid_object_info(r, oid, NULL) == OBJ_BLOB;
+}
+
+static int prune_parse(struct strbuf *line, void *data)
+{
+ struct prune_options *opts = data;
+ struct bkl_entry entry;
+
+ strbuf_reset(&opts->copy);
+ strbuf_addbuf(&opts->copy, line);
+
+ if (bkl_parse_entry(line, &entry))
+ return -1;
+
+ if (entry.timestamp < opts->expire)
+ return 0;
+
+ if (oideq(&entry.old_oid, &entry.new_oid))
+ return 0;
+
+ if (!good_oid(opts->repo, &entry.old_oid) ||
+ !good_oid(opts->repo, &entry.new_oid))
+ return 0;
+
+ if (!opts->fp)
+ return -1;
+
+ fputs(opts->copy.buf, opts->fp);
+ return 0;
+}
+
+int bkl_prune(struct repository *r, const char *path, timestamp_t expire)
+{
+ struct lock_file lk;
+ struct prune_options opts;
+ int ret;
+
+ ret = hold_lock_file_for_update(&lk, path, 0);
+ if (ret == -1) {
+ if (errno == ENOTDIR || errno == ENOENT)
+ return 0;
+ return error(_("failed to lock '%s'"), path);
+ }
+ opts.repo = r;
+ opts.expire = expire;
+ opts.fp = fdopen_lock_file(&lk, "w");
+ strbuf_init(&opts.copy, 0);
+
+ ret = bkl_parse_file(path, prune_parse, &opts);
+ if (ret < 0)
+ rollback_lock_file(&lk);
+ else
+ ret = commit_lock_file(&lk);
+ strbuf_release(&opts.copy);
+ return ret;
+}
diff --git a/backup-log.h b/backup-log.h
index c9de9c687c..06fe706f81 100644
--- a/backup-log.h
+++ b/backup-log.h
@@ -3,6 +3,7 @@
#include "cache.h"
+struct repository;
struct strbuf;
struct bkl_entry
@@ -29,4 +30,6 @@ int bkl_parse_file(const char *path,
int (*parse)(struct strbuf *line, void *data),
void *data);
+int bkl_prune(struct repository *r, const char *id, timestamp_t expire);
+
#endif
diff --git a/builtin/backup-log.c b/builtin/backup-log.c
index 2496d73ba5..2291124c38 100644
--- a/builtin/backup-log.c
+++ b/builtin/backup-log.c
@@ -301,6 +301,21 @@ static int log_(int argc, const char **argv,
return ret;
}
+static int prune(int argc, const char **argv,
+ const char *prefix, const char *log_path)
+{
+ timestamp_t expire = time(NULL) - 90 * 24 * 3600;
+ struct option options[] = {
+ OPT_EXPIRY_DATE(0, "expire", &expire,
+ N_("expire objects older than <time>")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options, backup_log_usage, 0);
+
+ return bkl_prune(the_repository, log_path, expire);
+}
+
static char *log_id_to_path(const char *id)
{
if (!strcmp(id, "index"))
@@ -346,6 +361,8 @@ int cmd_backup_log(int argc, const char **argv, const char *prefix)
return diff(argc, argv, prefix, log_path);
else if (!strcmp(argv[0], "log"))
return log_(argc, argv, prefix, log_path);
+ else if (!strcmp(argv[0], "prune"))
+ return prune(argc, argv, prefix, log_path);
else
die(_("unknown subcommand: %s"), argv[0]);
--
2.20.0.rc2.486.g9832c05c3d
^ permalink raw reply related
* [PATCH 16/24] sha1-file.c: let index_path() accept NULL istate
From: Nguyễn Thái Ngọc Duy @ 2018-12-09 10:44 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <20181209104419.12639-1-pclouds@gmail.com>
istate is needed for content conversion. Allow to pass NULL istate
(which implies that the caller does not care at all about file content
conversion).
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
sha1-file.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/sha1-file.c b/sha1-file.c
index 5bd11c85bc..214d62f3b5 100644
--- a/sha1-file.c
+++ b/sha1-file.c
@@ -1829,7 +1829,8 @@ static int index_mem(struct index_state *istate,
*/
if ((type == OBJ_BLOB) && path) {
struct strbuf nbuf = STRBUF_INIT;
- if (convert_to_git(istate, path, buf, size, &nbuf,
+ if (istate &&
+ convert_to_git(istate, path, buf, size, &nbuf,
get_conv_flags(flags))) {
buf = strbuf_detach(&nbuf, &size);
re_allocated = 1;
@@ -1957,12 +1958,13 @@ int index_fd(struct index_state *istate, struct object_id *oid,
* Call xsize_t() only when needed to avoid potentially unnecessary
* die() for large files.
*/
- if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(istate, path))
+ if (type == OBJ_BLOB && path && istate &&
+ would_convert_to_git_filter_fd(istate, path))
ret = index_stream_convert_blob(istate, oid, fd, path, flags);
else if (!S_ISREG(st->st_mode))
ret = index_pipe(istate, oid, fd, type, path, flags);
else if (st->st_size <= big_file_threshold || type != OBJ_BLOB ||
- (path && would_convert_to_git(istate, path)))
+ (path && istate && would_convert_to_git(istate, path)))
ret = index_core(istate, oid, fd, xsize_t(st->st_size),
type, path, flags);
else
--
2.20.0.rc2.486.g9832c05c3d
^ permalink raw reply related
* [PATCH 20/24] reset --hard: keep backup of overwritten files
From: Nguyễn Thái Ngọc Duy @ 2018-12-09 10:44 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <20181209104419.12639-1-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
builtin/reset.c | 2 ++
merge-recursive.c | 2 +-
t/t2080-backup-log.sh | 14 +++++++++
unpack-trees.c | 70 +++++++++++++++++++++++++++++++++----------
unpack-trees.h | 3 +-
5 files changed, 74 insertions(+), 17 deletions(-)
diff --git a/builtin/reset.c b/builtin/reset.c
index 58166964f8..517a27dce5 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -67,6 +67,8 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet)
break;
case HARD:
opts.update = 1;
+ repo_config_get_bool(the_repository, "core.backupLog",
+ &opts.keep_backup);
/* fallthrough */
default:
opts.reset = 1;
diff --git a/merge-recursive.c b/merge-recursive.c
index acc2f64a4e..10a9d3180a 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -896,7 +896,7 @@ static int was_dirty(struct merge_options *o, const char *path)
ce = index_file_exists(o->unpack_opts.src_index,
path, strlen(path), ignore_case);
- dirty = verify_uptodate(ce, &o->unpack_opts) != 0;
+ dirty = verify_uptodate(ce, &o->unpack_opts, NULL) != 0;
return dirty;
}
diff --git a/t/t2080-backup-log.sh b/t/t2080-backup-log.sh
index a283528912..901755ce93 100755
--- a/t/t2080-backup-log.sh
+++ b/t/t2080-backup-log.sh
@@ -211,4 +211,18 @@ test_expect_success 'overwritten ignored file is backed up' '
)
'
+test_expect_success 'overwritten out-of-date file is backed up' '
+ git init overwrite-outofdate &&
+ (
+ cd overwrite-outofdate &&
+ test_commit haha &&
+ NEW=$(git hash-object haha.t) &&
+ echo bad >>haha.t &&
+ OLD=$(git hash-object haha.t) &&
+ git -c core.backupLog reset --hard &&
+ echo "$OLD $NEW $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $test_tick -0700 haha.t" >expected &&
+ test_cmp expected .git/worktree.bkl
+ )
+'
+
test_done
diff --git a/unpack-trees.c b/unpack-trees.c
index 8d7273af2b..221869b47c 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -1715,7 +1715,8 @@ static int same(const struct cache_entry *a, const struct cache_entry *b)
*/
static int verify_uptodate_1(const struct cache_entry *ce,
struct unpack_trees_options *o,
- enum unpack_trees_error_types error_type)
+ enum unpack_trees_error_types error_type,
+ struct object_id *old_hash)
{
struct stat st;
@@ -1727,10 +1728,16 @@ static int verify_uptodate_1(const struct cache_entry *ce,
* if this entry is truly up-to-date because this file may be
* overwritten.
*/
- if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))
+ if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) {
; /* keep checking */
- else if (o->reset || ce_uptodate(ce))
+ } else if (o->reset) {
+ if (o->keep_backup && old_hash && !lstat(ce->name, &st))
+ index_path(NULL, old_hash, ce->name, &st,
+ HASH_WRITE_OBJECT);
+ return 0;
+ } else if (ce_uptodate(ce)) {
return 0;
+ }
if (!lstat(ce->name, &st)) {
int flags = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE;
@@ -1764,17 +1771,20 @@ static int verify_uptodate_1(const struct cache_entry *ce,
}
int verify_uptodate(const struct cache_entry *ce,
- struct unpack_trees_options *o)
+ struct unpack_trees_options *o,
+ struct object_id *old_hash)
{
+ if (o->keep_backup && old_hash)
+ oidclr(old_hash);
if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
return 0;
- return verify_uptodate_1(ce, o, ERROR_NOT_UPTODATE_FILE);
+ return verify_uptodate_1(ce, o, ERROR_NOT_UPTODATE_FILE, old_hash);
}
static int verify_uptodate_sparse(const struct cache_entry *ce,
struct unpack_trees_options *o)
{
- return verify_uptodate_1(ce, o, ERROR_SPARSE_NOT_UPTODATE_FILE);
+ return verify_uptodate_1(ce, o, ERROR_SPARSE_NOT_UPTODATE_FILE, NULL);
}
/*
@@ -1862,8 +1872,11 @@ static int verify_clean_subdirectory(const struct cache_entry *ce,
* removed.
*/
if (!ce_stage(ce2)) {
- if (verify_uptodate(ce2, o))
+ struct object_id old_hash;
+
+ if (verify_uptodate(ce2, o, &old_hash))
return -1;
+ make_backup(ce2, &old_hash, NULL, o);
add_entry(o, ce2, CE_REMOVE, 0);
invalidate_ce_path(ce, o);
mark_ce_used(ce2, o);
@@ -1973,8 +1986,13 @@ static int verify_absent_1(const struct cache_entry *ce,
int len;
struct stat st;
- if (o->index_only || o->reset || !o->update)
+ if (o->index_only || o->reset || !o->update) {
+ if (o->reset && o->keep_backup &&
+ old_hash && !lstat(ce->name, &st))
+ index_path(NULL, old_hash, ce->name, &st,
+ HASH_WRITE_OBJECT);
return 0;
+ }
len = check_leading_path(ce->name, ce_namelen(ce));
if (!len)
@@ -2092,10 +2110,12 @@ static int merged_entry(const struct cache_entry *ce,
copy_cache_entry(merge, old);
update = 0;
} else {
- if (verify_uptodate(old, o)) {
+ struct object_id old_hash;
+ if (verify_uptodate(old, o, &old_hash)) {
discard_cache_entry(merge);
return -1;
}
+ make_backup(old, &old_hash, &merge->oid, o);
/* Migrate old flags over */
update |= old->ce_flags & (CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
invalidate_ce_path(old, o);
@@ -2124,18 +2144,20 @@ static int deleted_entry(const struct cache_entry *ce,
const struct cache_entry *old,
struct unpack_trees_options *o)
{
+ struct object_id old_hash;
+
/* Did it exist in the index? */
if (!old) {
- struct object_id old_hash;
-
if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED,
o, &old_hash))
return -1;
make_backup(ce, &old_hash, NULL, o);
return 0;
}
- if (!(old->ce_flags & CE_CONFLICTED) && verify_uptodate(old, o))
+ if (!(old->ce_flags & CE_CONFLICTED) &&
+ verify_uptodate(old, o, &old_hash))
return -1;
+ make_backup(ce, &old_hash, NULL, o);
add_entry(o, ce, CE_REMOVE, 0);
invalidate_ce_path(ce, o);
return 1;
@@ -2305,8 +2327,16 @@ int threeway_merge(const struct cache_entry * const *stages,
* conflict resolution files.
*/
if (index) {
- if (verify_uptodate(index, o))
+ struct object_id old_hash;
+
+ if (verify_uptodate(index, o, &old_hash))
return -1;
+ /*
+ * A new conflict appears. We could make a backup from
+ * worktree version to stage 2 or 3. But neither makes much
+ * sense. Make a deletion backup instead.
+ */
+ make_backup(index, &old_hash, NULL, o);
}
o->nontrivial_merge = 1;
@@ -2447,16 +2477,26 @@ int oneway_merge(const struct cache_entry * const *src,
return deleted_entry(old, old, o);
if (old && same(old, a)) {
+ struct object_id old_hash;
int update = 0;
+
+ oidclr(&old_hash);
if (o->reset && o->update && !ce_uptodate(old) && !ce_skip_worktree(old)) {
struct stat st;
+
if (lstat(old->name, &st) ||
- ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE))
+ ie_match_stat(o->src_index, old, &st,
+ CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE))
update |= CE_UPDATE;
+
+ if (update & CE_UPDATE && o->keep_backup)
+ index_path(NULL, &old_hash, old->name, &st,
+ HASH_WRITE_OBJECT);
}
if (o->update && S_ISGITLINK(old->ce_mode) &&
- should_update_submodules() && !verify_uptodate(old, o))
+ should_update_submodules() && !verify_uptodate(old, o, NULL))
update |= CE_UPDATE;
+ make_backup(old, &old_hash, &old->oid, o);
add_entry(o, old, update, 0);
return 0;
}
diff --git a/unpack-trees.h b/unpack-trees.h
index e2a64e2401..a453def564 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -93,7 +93,8 @@ int unpack_trees(unsigned n, struct tree_desc *t,
struct unpack_trees_options *options);
int verify_uptodate(const struct cache_entry *ce,
- struct unpack_trees_options *o);
+ struct unpack_trees_options *o,
+ struct object_id *old_hash);
int threeway_merge(const struct cache_entry * const *stages,
struct unpack_trees_options *o);
--
2.20.0.rc2.486.g9832c05c3d
^ permalink raw reply related
* [PATCH 24/24] FIXME
From: Nguyễn Thái Ngọc Duy @ 2018-12-09 10:44 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <20181209104419.12639-1-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
Documentation/git-backup-log.txt | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/git-backup-log.txt b/Documentation/git-backup-log.txt
index 98998156c1..fe03726337 100644
--- a/Documentation/git-backup-log.txt
+++ b/Documentation/git-backup-log.txt
@@ -41,6 +41,8 @@ following commands will save backups:
`--hard` or linkgit:git-am[1] and linkgit:git-rebase[1] with
`--skip` or `--abort` will make a backup before overwriting non
up-to-date files.
+- FIXME perhaps `git checkout <paths>` only makes backups on
+ "precious" paths only?
Backups are split in three groups, changes related in the index, in
working directory or in $GIT_DIR. These can be selected with `--id`
--
2.20.0.rc2.486.g9832c05c3d
^ permalink raw reply related
* [PATCH 23/24] rebase: keep backup of overwritten files on --skip or --abort
From: Nguyễn Thái Ngọc Duy @ 2018-12-09 10:44 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <20181209104419.12639-1-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
builtin/rebase.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/builtin/rebase.c b/builtin/rebase.c
index b5c99ec10c..5c7b223843 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -573,8 +573,12 @@ static int reset_head(struct object_id *oid, const char *action,
unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
unpack_tree_opts.update = 1;
unpack_tree_opts.merge = 1;
- if (!detach_head)
+ if (!detach_head) {
unpack_tree_opts.reset = 1;
+ repo_config_get_bool(the_repository, "core.backupLog",
+ &unpack_tree_opts.keep_backup);
+ }
+
if (read_index_unmerged(the_repository->index) < 0) {
ret = error(_("could not read index"));
--
2.20.0.rc2.486.g9832c05c3d
^ permalink raw reply related
* [PATCH 14/24] gc: prune backup logs
From: Nguyễn Thái Ngọc Duy @ 2018-12-09 10:44 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <20181209104419.12639-1-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
backup-log.c | 33 +++++++++++++++++++++++++++++++++
backup-log.h | 1 +
builtin/gc.c | 3 +++
3 files changed, 37 insertions(+)
diff --git a/backup-log.c b/backup-log.c
index 5e38725981..dbb6d5487e 100644
--- a/backup-log.c
+++ b/backup-log.c
@@ -4,6 +4,7 @@
#include "lockfile.h"
#include "object-store.h"
#include "strbuf.h"
+#include "worktree.h"
void bkl_append(struct strbuf *output, const char *path,
const struct object_id *from,
@@ -288,3 +289,35 @@ int bkl_prune(struct repository *r, const char *path, timestamp_t expire)
strbuf_release(&opts.copy);
return ret;
}
+
+void bkl_prune_all_or_die(struct repository *r, timestamp_t expire)
+{
+ struct worktree **worktrees, **p;
+ char *bkl_path;
+
+ bkl_path = repo_git_path(r, "common/gitdir.bkl");
+ if (bkl_prune(r, bkl_path, expire))
+ die(_("failed to prune %s"), "gitdir.bkl");
+ free(bkl_path);
+
+ worktrees = get_worktrees(0);
+ for (p = worktrees; *p; p++) {
+ struct worktree *wt = *p;
+
+ if (bkl_prune(r, worktree_git_path(wt, "index.bkl"), expire)) {
+ if (wt->id)
+ die(_("failed to prune %s on working tree '%s'"),
+ "index.bkl", wt->id);
+ else
+ die(_("failed to prune %s"), "index.bkl");
+ }
+ if (bkl_prune(r, worktree_git_path(wt, "worktree.bkl"), expire)) {
+ if (wt->id)
+ die(_("failed to prune %s on working tree '%s'"),
+ "worktree.bkl", wt->id);
+ else
+ die(_("failed to prune %s"), "worktree.bkl");
+ }
+ }
+ free_worktrees(worktrees);
+}
diff --git a/backup-log.h b/backup-log.h
index 06fe706f81..6572ce9c93 100644
--- a/backup-log.h
+++ b/backup-log.h
@@ -31,5 +31,6 @@ int bkl_parse_file(const char *path,
void *data);
int bkl_prune(struct repository *r, const char *id, timestamp_t expire);
+void bkl_prune_all_or_die(struct repository *r, timestamp_t expire);
#endif
diff --git a/builtin/gc.c b/builtin/gc.c
index 871a56f1c5..50a5d46abb 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -27,6 +27,7 @@
#include "pack-objects.h"
#include "blob.h"
#include "tree.h"
+#include "backup-log.h"
#define FAILED_RUN "failed to run %s"
@@ -657,6 +658,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
die(FAILED_RUN, rerere.argv[0]);
+ bkl_prune_all_or_die(the_repository, time(NULL) - 90 * 24 * 3600);
+
report_garbage = report_pack_garbage;
reprepare_packed_git(the_repository);
if (pack_garbage.nr > 0)
--
2.20.0.rc2.486.g9832c05c3d
^ permalink raw reply related
* [PATCH 21/24] checkout -f: keep backup of overwritten files
From: Nguyễn Thái Ngọc Duy @ 2018-12-09 10:44 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <20181209104419.12639-1-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
builtin/checkout.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b5e27a5f6d..3ae001ae35 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -438,6 +438,8 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
opts.verbose_update = o->show_progress;
opts.src_index = &the_index;
opts.dst_index = &the_index;
+ repo_config_get_bool(the_repository, "core.backupLog",
+ &opts.keep_backup);
parse_tree(tree);
init_tree_desc(&tree_desc, tree->buffer, tree->size);
switch (unpack_trees(1, &tree_desc, &opts)) {
--
2.20.0.rc2.486.g9832c05c3d
^ permalink raw reply related
* [PATCH 22/24] am: keep backup of overwritten files on --skip or --abort
From: Nguyễn Thái Ngọc Duy @ 2018-12-09 10:44 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <20181209104419.12639-1-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
builtin/am.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/builtin/am.c b/builtin/am.c
index 8f27f3375b..098bbaab39 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1886,6 +1886,9 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
opts.update = 1;
opts.merge = 1;
opts.reset = reset;
+ if (opts.reset)
+ repo_config_get_bool(the_repository, "core.backupLog",
+ &opts.keep_backup);
opts.fn = twoway_merge;
init_tree_desc(&t[0], head->buffer, head->size);
init_tree_desc(&t[1], remote->buffer, remote->size);
--
2.20.0.rc2.486.g9832c05c3d
^ permalink raw reply related
* [PATCH 19/24] unpack-trees.c: keep backup of ignored files being overwritten
From: Nguyễn Thái Ngọc Duy @ 2018-12-09 10:44 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <20181209104419.12639-1-pclouds@gmail.com>
Ignored files are usually machine generated (e.g. *.o) and not worth
keeping, so when a merge or branch switch happens and need to
overwrite them, we just go ahead and do it. Occasionally though
ignored files _can_ have valuable content.
We will likely have a separate mechanism to protect these "precious"
ignored files, but a surprise is still a surprise. Keep a backup of
ignored files when they are overwritten (until we are explicitly told
"rubbish" by said mechanism). This lets the user recover the content
and start telling git that the file is precious so it won't happen
again.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
builtin/checkout.c | 2 ++
merge.c | 2 ++
t/t2080-backup-log.sh | 21 ++++++++++++
unpack-trees.c | 77 +++++++++++++++++++++++++++++++++++--------
unpack-trees.h | 3 ++
5 files changed, 92 insertions(+), 13 deletions(-)
diff --git a/builtin/checkout.c b/builtin/checkout.c
index acdafc6e4c..b5e27a5f6d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -620,6 +620,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
topts.dir = xcalloc(1, sizeof(*topts.dir));
topts.dir->flags |= DIR_SHOW_IGNORED;
setup_standard_excludes(topts.dir);
+ repo_config_get_bool(the_repository, "core.backupLog",
+ &topts.keep_backup);
}
tree = parse_tree_indirect(old_branch_info->commit ?
&old_branch_info->commit->object.oid :
diff --git a/merge.c b/merge.c
index 91008f7602..9f20305d7a 100644
--- a/merge.c
+++ b/merge.c
@@ -1,4 +1,5 @@
#include "cache.h"
+#include "config.h"
#include "diff.h"
#include "diffcore.h"
#include "lockfile.h"
@@ -85,6 +86,7 @@ int checkout_fast_forward(struct repository *r,
dir.flags |= DIR_SHOW_IGNORED;
setup_standard_excludes(&dir);
opts.dir = &dir;
+ repo_config_get_bool(r, "core.backupLog", &opts.keep_backup);
}
opts.head_idx = 1;
diff --git a/t/t2080-backup-log.sh b/t/t2080-backup-log.sh
index 710df1ec8b..a283528912 100755
--- a/t/t2080-backup-log.sh
+++ b/t/t2080-backup-log.sh
@@ -190,4 +190,25 @@ test_expect_success 'deleted reflog is kept' '
grep ^$OLD .git/common/gitdir.bkl
'
+test_expect_success 'overwritten ignored file is backed up' '
+ git init overwrite-ignore &&
+ (
+ cd overwrite-ignore &&
+ echo ignored-overwritten >ignored &&
+ NEW=$(git hash-object ignored) &&
+ git add ignored &&
+ git commit -m ignored &&
+ git rm --cached ignored &&
+ echo /ignored >.gitignore &&
+ git add .gitignore &&
+ git commit -m first-commit-no-ignored &&
+ echo precious >ignored &&
+ OLD=$(git hash-object ignored) &&
+ test_tick &&
+ git -c core.backupLog=true checkout --detach HEAD^ &&
+ echo "$OLD $NEW $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $test_tick -0700 ignored" >expected &&
+ test_cmp expected .git/worktree.bkl
+ )
+'
+
test_done
diff --git a/unpack-trees.c b/unpack-trees.c
index 7570df481b..8d7273af2b 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -1,6 +1,7 @@
#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "argv-array.h"
+#include "backup-log.h"
#include "repository.h"
#include "config.h"
#include "dir.h"
@@ -191,6 +192,23 @@ void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)
memset(opts->msgs, 0, sizeof(opts->msgs));
}
+static void make_backup(const struct cache_entry *ce,
+ const struct object_id *old_hash,
+ const struct object_id *new_hash,
+ struct unpack_trees_options *o)
+{
+ struct object_id null_hash;
+
+ if (!o->keep_backup || is_null_oid(old_hash))
+ return;
+
+ if (!new_hash) {
+ oidclr(&null_hash);
+ new_hash = &null_hash;
+ }
+ bkl_append(&o->backup_log, ce->name, old_hash, new_hash);
+}
+
static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
unsigned int set, unsigned int clear)
{
@@ -462,6 +480,9 @@ static int check_updates(struct unpack_trees_options *o)
if (o->clone)
report_collided_checkout(index);
+ if (o->backup_log.len)
+ bkl_write(git_path("worktree.bkl"), &o->backup_log);
+
trace_performance_leave("check_updates");
return errs != 0;
}
@@ -1460,7 +1481,8 @@ static void mark_new_skip_worktree(struct exclude_list *el,
static int verify_absent(const struct cache_entry *,
enum unpack_trees_error_types,
- struct unpack_trees_options *);
+ struct unpack_trees_options *,
+ struct object_id *);
/*
* N-way merge "len" trees. Returns 0 on success, -1 on failure to manipulate the
* resulting index, -2 on failure to reflect the changes to the work tree.
@@ -1489,6 +1511,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
free(sparse);
}
+ strbuf_init(&o->backup_log, 0);
+ if (!o->update)
+ o->keep_backup = 0;
+
memset(&o->result, 0, sizeof(o->result));
o->result.initialized = 1;
o->result.timestamp.sec = o->src_index->timestamp.sec;
@@ -1596,7 +1622,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
* correct CE_NEW_SKIP_WORKTREE
*/
if (ce->ce_flags & CE_ADDED &&
- verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) {
+ verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o, NULL)) {
if (!o->show_all_errors)
goto return_failed;
ret = -1;
@@ -1646,6 +1672,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
o->src_index = NULL;
done:
+ strbuf_release(&o->backup_log);
trace_performance_leave("unpack_trees");
clear_exclude_list(&el);
return ret;
@@ -1880,7 +1907,8 @@ static int icase_exists(struct unpack_trees_options *o, const char *name, int le
static int check_ok_to_remove(const char *name, int len, int dtype,
const struct cache_entry *ce, struct stat *st,
enum unpack_trees_error_types error_type,
- struct unpack_trees_options *o)
+ struct unpack_trees_options *o,
+ struct object_id *old_hash)
{
const struct cache_entry *result;
@@ -1895,12 +1923,16 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
return 0;
if (o->dir &&
- is_excluded(o->dir, o->src_index, name, &dtype))
+ is_excluded(o->dir, o->src_index, name, &dtype)) {
+ if (o->keep_backup && old_hash)
+ index_path(NULL, old_hash, name, st,
+ HASH_WRITE_OBJECT);
/*
* ce->name is explicitly excluded, so it is Ok to
* overwrite it.
*/
return 0;
+ }
if (S_ISDIR(st->st_mode)) {
/*
* We are checking out path "foo" and
@@ -1935,7 +1967,8 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
*/
static int verify_absent_1(const struct cache_entry *ce,
enum unpack_trees_error_types error_type,
- struct unpack_trees_options *o)
+ struct unpack_trees_options *o,
+ struct object_id *old_hash)
{
int len;
struct stat st;
@@ -1960,7 +1993,7 @@ static int verify_absent_1(const struct cache_entry *ce,
NULL, o);
else
ret = check_ok_to_remove(path, len, DT_UNKNOWN, NULL,
- &st, error_type, o);
+ &st, error_type, o, old_hash);
}
free(path);
return ret;
@@ -1975,17 +2008,20 @@ static int verify_absent_1(const struct cache_entry *ce,
return check_ok_to_remove(ce->name, ce_namelen(ce),
ce_to_dtype(ce), ce, &st,
- error_type, o);
+ error_type, o, old_hash);
}
}
static int verify_absent(const struct cache_entry *ce,
enum unpack_trees_error_types error_type,
- struct unpack_trees_options *o)
+ struct unpack_trees_options *o,
+ struct object_id *old_hash)
{
+ if (o->keep_backup && old_hash)
+ oidclr(old_hash);
if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
return 0;
- return verify_absent_1(ce, error_type, o);
+ return verify_absent_1(ce, error_type, o, old_hash);
}
static int verify_absent_sparse(const struct cache_entry *ce,
@@ -1996,7 +2032,7 @@ static int verify_absent_sparse(const struct cache_entry *ce,
if (orphaned_error == ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN)
orphaned_error = ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN;
- return verify_absent_1(ce, orphaned_error, o);
+ return verify_absent_1(ce, orphaned_error, o, NULL);
}
static int merged_entry(const struct cache_entry *ce,
@@ -2007,6 +2043,8 @@ static int merged_entry(const struct cache_entry *ce,
struct cache_entry *merge = dup_cache_entry(ce, &o->result);
if (!old) {
+ struct object_id old_hash;
+
/*
* New index entries. In sparse checkout, the following
* verify_absent() will be delayed until after
@@ -2023,10 +2061,15 @@ static int merged_entry(const struct cache_entry *ce,
merge->ce_flags |= CE_NEW_SKIP_WORKTREE;
if (verify_absent(merge,
- ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) {
+ ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN,
+ o, &old_hash)) {
discard_cache_entry(merge);
return -1;
}
+ if (o->keep_backup)
+ bkl_append(&o->backup_log, merge->name,
+ &old_hash, &merge->oid);
+
invalidate_ce_path(merge, o);
if (submodule_from_ce(ce)) {
@@ -2083,8 +2126,12 @@ static int deleted_entry(const struct cache_entry *ce,
{
/* Did it exist in the index? */
if (!old) {
- if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o))
+ struct object_id old_hash;
+
+ if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED,
+ o, &old_hash))
return -1;
+ make_backup(ce, &old_hash, NULL, o);
return 0;
}
if (!(old->ce_flags & CE_CONFLICTED) && verify_uptodate(old, o))
@@ -2236,8 +2283,12 @@ int threeway_merge(const struct cache_entry * const *stages,
if (index)
return deleted_entry(index, index, o);
if (ce && !head_deleted) {
- if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o))
+ struct object_id old_hash;
+
+ if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED,
+ o, &old_hash))
return -1;
+ make_backup(ce, &old_hash, NULL, o);
}
return 0;
}
diff --git a/unpack-trees.h b/unpack-trees.h
index 0135080a7b..e2a64e2401 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -60,6 +60,7 @@ struct unpack_trees_options {
exiting_early,
show_all_errors,
dry_run;
+ int keep_backup;
const char *prefix;
int cache_bottom;
struct dir_struct *dir;
@@ -83,6 +84,8 @@ struct unpack_trees_options {
struct index_state *src_index;
struct index_state result;
+ struct strbuf backup_log;
+
struct exclude_list *el; /* for internal use */
};
--
2.20.0.rc2.486.g9832c05c3d
^ permalink raw reply related
* [PATCH 15/24] backup-log: keep all blob references around
From: Nguyễn Thái Ngọc Duy @ 2018-12-09 10:44 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <20181209104419.12639-1-pclouds@gmail.com>
The four commands prune, rev-list, pack-objects and repack are updated
to not consider backup-log's blobs as unreachable and either delete
them outright or not repack them.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
backup-log.c | 65 ++++++++++++++++++++++++++++++++++++++++++
backup-log.h | 2 ++
builtin/pack-objects.c | 9 +++++-
builtin/repack.c | 1 +
reachable.c | 3 ++
revision.c | 3 ++
6 files changed, 82 insertions(+), 1 deletion(-)
diff --git a/backup-log.c b/backup-log.c
index dbb6d5487e..37fa71e4e0 100644
--- a/backup-log.c
+++ b/backup-log.c
@@ -3,6 +3,7 @@
#include "blob.h"
#include "lockfile.h"
#include "object-store.h"
+#include "revision.h"
#include "strbuf.h"
#include "worktree.h"
@@ -321,3 +322,67 @@ void bkl_prune_all_or_die(struct repository *r, timestamp_t expire)
}
free_worktrees(worktrees);
}
+
+struct pending_cb {
+ struct rev_info *revs;
+ unsigned flags;
+};
+
+static void add_blob_to_pending(const struct object_id *oid,
+ const char *path,
+ struct pending_cb *cb)
+{
+ struct blob *blob;
+
+ if (!good_oid(cb->revs->repo, oid))
+ return;
+
+ blob = lookup_blob(cb->revs->repo, oid);
+ blob->object.flags |= cb->flags;
+ add_pending_object(cb->revs, &blob->object, path);
+}
+
+static int add_pending(struct strbuf *line, void *cb)
+{
+ struct bkl_entry entry;
+
+ if (bkl_parse_entry(line, &entry))
+ return -1;
+
+ add_blob_to_pending(&entry.old_oid, entry.path, cb);
+ add_blob_to_pending(&entry.new_oid, entry.path, cb);
+ return 0;
+}
+
+static void add_backup_log_to_pending(const char *path, struct pending_cb *cb)
+{
+ bkl_parse_file(path, add_pending, cb);
+}
+
+void add_backup_logs_to_pending(struct rev_info *revs, unsigned flags)
+{
+ struct worktree **worktrees, **p;
+ char *path;
+ struct pending_cb cb;
+
+ cb.revs = revs;
+ cb.flags = flags;
+
+ worktrees = get_worktrees(0);
+ for (p = worktrees; *p; p++) {
+ struct worktree *wt = *p;
+
+ path = xstrdup(worktree_git_path(wt, "index.bkl"));
+ add_backup_log_to_pending(path, &cb);
+ free(path);
+
+ path = xstrdup(worktree_git_path(wt, "worktree.bkl"));
+ add_backup_log_to_pending(path, &cb);
+ free(path);
+ }
+ free_worktrees(worktrees);
+
+ path = git_pathdup("common/gitdir.bkl");
+ add_backup_log_to_pending(path, &cb);
+ free(path);
+}
diff --git a/backup-log.h b/backup-log.h
index 6572ce9c93..aaa76b7fe2 100644
--- a/backup-log.h
+++ b/backup-log.h
@@ -4,6 +4,7 @@
#include "cache.h"
struct repository;
+struct rev_info;
struct strbuf;
struct bkl_entry
@@ -30,6 +31,7 @@ int bkl_parse_file(const char *path,
int (*parse)(struct strbuf *line, void *data),
void *data);
+void add_backup_logs_to_pending(struct rev_info *revs, unsigned flags);
int bkl_prune(struct repository *r, const char *id, timestamp_t expire);
void bkl_prune_all_or_die(struct repository *r, timestamp_t expire);
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 411aefd687..940eb0c768 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -3230,7 +3230,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
int all_progress_implied = 0;
struct argv_array rp = ARGV_ARRAY_INIT;
int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0;
- int rev_list_index = 0;
+ int rev_list_index = 0, rev_list_backuplog = 0;
struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
struct option pack_objects_options[] = {
OPT_SET_INT('q', "quiet", &progress,
@@ -3278,6 +3278,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
OPT_SET_INT_F(0, "reflog", &rev_list_reflog,
N_("include objects referred by reflog entries"),
1, PARSE_OPT_NONEG),
+ OPT_SET_INT_F(0, "backup-log", &rev_list_backuplog,
+ N_("include objects referred by backup-log entries"),
+ 1, PARSE_OPT_NONEG),
OPT_SET_INT_F(0, "indexed-objects", &rev_list_index,
N_("include objects referred to by the index"),
1, PARSE_OPT_NONEG),
@@ -3366,6 +3369,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
use_internal_rev_list = 1;
argv_array_push(&rp, "--reflog");
}
+ if (rev_list_backuplog) {
+ use_internal_rev_list = 1;
+ argv_array_push(&rp, "--backup-log");
+ }
if (rev_list_index) {
use_internal_rev_list = 1;
argv_array_push(&rp, "--indexed-objects");
diff --git a/builtin/repack.c b/builtin/repack.c
index 45583683ee..a8a9fad9c6 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -365,6 +365,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
argv_array_push(&cmd.args, "--non-empty");
argv_array_push(&cmd.args, "--all");
argv_array_push(&cmd.args, "--reflog");
+ argv_array_push(&cmd.args, "--backup-log");
argv_array_push(&cmd.args, "--indexed-objects");
if (repository_format_partial_clone)
argv_array_push(&cmd.args, "--exclude-promisor-objects");
diff --git a/reachable.c b/reachable.c
index 6e9b810d2a..61f6560b54 100644
--- a/reachable.c
+++ b/reachable.c
@@ -12,6 +12,7 @@
#include "packfile.h"
#include "worktree.h"
#include "object-store.h"
+#include "backup-log.h"
struct connectivity_progress {
struct progress *progress;
@@ -185,6 +186,8 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog,
if (mark_reflog)
add_reflogs_to_pending(revs, 0);
+ add_backup_logs_to_pending(revs, 0);
+
cp.progress = progress;
cp.count = 0;
diff --git a/revision.c b/revision.c
index 13e0519c02..755edea61e 100644
--- a/revision.c
+++ b/revision.c
@@ -27,6 +27,7 @@
#include "commit-reach.h"
#include "commit-graph.h"
#include "prio-queue.h"
+#include "backup-log.h"
volatile show_early_output_fn_t show_early_output;
@@ -2286,6 +2287,8 @@ static int handle_revision_pseudo_opt(const char *submodule,
clear_ref_exclusion(&revs->ref_excludes);
} else if (!strcmp(arg, "--reflog")) {
add_reflogs_to_pending(revs, *flags);
+ } else if (!strcmp(arg, "--backup-log")) {
+ add_backup_logs_to_pending(revs, *flags);
} else if (!strcmp(arg, "--indexed-objects")) {
add_index_objects_to_pending(revs, *flags);
} else if (!strcmp(arg, "--not")) {
--
2.20.0.rc2.486.g9832c05c3d
^ permalink raw reply related
* [PATCH 18/24] refs: keep backup of deleted reflog
From: Nguyễn Thái Ngọc Duy @ 2018-12-09 10:44 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <20181209104419.12639-1-pclouds@gmail.com>
As noted in git-backup-log.txt a long time ago, this is mostly meant
for recovering a branch immediately after an accidental deletion.
References from the deleted reflog will not be included in
reachability tests and they will be deleted at the next gc. At that
point, this deleted reflog becomes useless.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
refs/files-backend.c | 32 ++++++++++++++++++++++++++++++++
t/t2080-backup-log.sh | 12 ++++++++++++
2 files changed, 44 insertions(+)
diff --git a/refs/files-backend.c b/refs/files-backend.c
index dd8abe9185..9afb274075 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -11,6 +11,7 @@
#include "../dir.h"
#include "../chdir-notify.h"
#include "worktree.h"
+#include "backup-log.h"
/*
* This backend uses the following flags in `ref_update::flags` for
@@ -1873,6 +1874,35 @@ static int files_reflog_exists(struct ref_store *ref_store,
return ret;
}
+static void backup_reflog(struct files_ref_store *refs,
+ const char *reflog_path,
+ const char *refname)
+{
+ int core_backup_log = 0;
+ struct stat st;
+ struct strbuf line = STRBUF_INIT;
+ struct strbuf path = STRBUF_INIT;
+ struct object_id old, new;
+
+ repo_config_get_bool(the_repository, "core.backuplog",
+ &core_backup_log);
+ if (!core_backup_log)
+ return;
+ if (ref_type(refname) != REF_TYPE_NORMAL)
+ return;
+ if (lstat(reflog_path, &st) ||
+ index_path(NULL, &old, reflog_path, &st, HASH_WRITE_OBJECT))
+ return;
+
+ strbuf_addf(&path, "logs/%s", refname);
+ oidclr(&new);
+ bkl_append(&line, path.buf, &old, &new);
+ strbuf_release(&path);
+ mkdir_in_gitdir(git_path("common"));
+ bkl_write(git_path("common/gitdir.bkl"), &line);
+ strbuf_release(&line);
+}
+
static int files_delete_reflog(struct ref_store *ref_store,
const char *refname)
{
@@ -1882,6 +1912,7 @@ static int files_delete_reflog(struct ref_store *ref_store,
int ret;
files_reflog_path(refs, &sb, refname);
+ backup_reflog(refs, sb.buf, refname);
ret = remove_path(sb.buf);
strbuf_release(&sb);
return ret;
@@ -2797,6 +2828,7 @@ static int files_transaction_finish(struct ref_store *ref_store,
!(update->flags & REF_IS_PRUNING)) {
strbuf_reset(&sb);
files_reflog_path(refs, &sb, update->refname);
+ backup_reflog(refs, sb.buf, update->refname);
if (!unlink_or_warn(sb.buf))
try_remove_empty_parents(refs, update->refname,
REMOVE_EMPTY_PARENTS_REFLOG);
diff --git a/t/t2080-backup-log.sh b/t/t2080-backup-log.sh
index dbd19db757..710df1ec8b 100755
--- a/t/t2080-backup-log.sh
+++ b/t/t2080-backup-log.sh
@@ -178,4 +178,16 @@ test_expect_success 'config --edit makes a backup' '
grep $NEW .git/common/gitdir.bkl
'
+test_expect_success 'deleted reflog is kept' '
+ git checkout -b foo &&
+ git commit -am everything-else &&
+ test_commit two &&
+ test_commit three &&
+ git checkout -f master &&
+ OLD=$(git hash-object .git/logs/refs/heads/foo) &&
+ git -c core.backupLog=true branch -D foo &&
+ grep logs/refs/heads/foo .git/common/gitdir.bkl &&
+ grep ^$OLD .git/common/gitdir.bkl
+'
+
test_done
--
2.20.0.rc2.486.g9832c05c3d
^ permalink raw reply related
* [PATCH 17/24] config --edit: support backup log
From: Nguyễn Thái Ngọc Duy @ 2018-12-09 10:44 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <20181209104419.12639-1-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
builtin/config.c | 27 ++++++++++++++++++++++++++-
t/t2080-backup-log.sh | 14 ++++++++++++++
2 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/builtin/config.c b/builtin/config.c
index 84385ef165..a42044d03e 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -1,5 +1,6 @@
#include "builtin.h"
#include "cache.h"
+#include "backup-log.h"
#include "config.h"
#include "color.h"
#include "parse-options.h"
@@ -593,6 +594,15 @@ static char *default_user_config(void)
return strbuf_detach(&buf, NULL);
}
+static void hash_one_path(const char *path, struct object_id *oid)
+{
+ struct stat st;
+
+ if (lstat(path, &st) ||
+ index_path(NULL, oid, path, &st, HASH_WRITE_OBJECT))
+ oidclr(oid);
+}
+
int cmd_config(int argc, const char **argv, const char *prefix)
{
int nongit = !startup_info->have_repository;
@@ -735,6 +745,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
else if (actions == ACTION_EDIT) {
char *config_file;
+ int core_backup_log = 0;
+ struct object_id old, new;
check_argc(argc, 0, 0);
if (!given_config_source.file && nongit)
@@ -747,6 +759,9 @@ int cmd_config(int argc, const char **argv, const char *prefix)
config_file = given_config_source.file ?
xstrdup(given_config_source.file) :
git_pathdup("config");
+ repo_config_get_bool(the_repository, "core.backuplog",
+ &core_backup_log);
+ oidclr(&old);
if (use_global_config) {
int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666);
if (fd >= 0) {
@@ -757,9 +772,19 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
else if (errno != EEXIST)
die_errno(_("cannot create configuration file %s"), config_file);
- }
+ } else if (!given_config_source.file && core_backup_log)
+ hash_one_path(git_path("config"), &old);
launch_editor(config_file, NULL, NULL);
free(config_file);
+ if (!is_null_oid(&old)) {
+ struct strbuf sb = STRBUF_INIT;
+
+ hash_one_path(git_path("config"), &new);
+ bkl_append(&sb, "config", &old, &new);
+ mkdir(git_path("common"), 0777);
+ bkl_write(git_path("common/gitdir.bkl"), &sb);
+ strbuf_release(&sb);
+ }
}
else if (actions == ACTION_SET) {
int ret;
diff --git a/t/t2080-backup-log.sh b/t/t2080-backup-log.sh
index 3cdabc2ba2..dbd19db757 100755
--- a/t/t2080-backup-log.sh
+++ b/t/t2080-backup-log.sh
@@ -164,4 +164,18 @@ test_expect_success 'backup-log log' '
test_cmp expected actual
'
+test_expect_success 'config --edit makes a backup' '
+ cat >edit.sh <<-EOF &&
+ #!$SHELL_PATH
+ echo ";Edited" >>"\$1"
+ EOF
+ chmod +x edit.sh &&
+ OLD=$(git hash-object .git/config) &&
+ EDITOR=./edit.sh git -c core.backupLog=true config --edit &&
+ NEW=$(git hash-object .git/config) &&
+ grep config .git/common/gitdir.bkl &&
+ grep ^$OLD .git/common/gitdir.bkl &&
+ grep $NEW .git/common/gitdir.bkl
+'
+
test_done
--
2.20.0.rc2.486.g9832c05c3d
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox