diff mbox series

[PATCH/RFC] checkout: print something when checking out paths

Message ID 20181110133525.17538-1-pclouds@gmail.com (mailing list archive)
State New, archived
Headers show
Series [PATCH/RFC] checkout: print something when checking out paths | expand

Commit Message

Duy Nguyen Nov. 10, 2018, 1:35 p.m. UTC
One of the problems with "git checkout" is that it does so many
different things and could confuse people specially when we fail to
handle ambiguation correctly.

One way to help with that is tell the user what sort of operation is
actually carried out. When switching branches, we always print
something [1].  Checking out paths however is silent. Print something
so that if we got the user intention wrong, they won't waste too much
time to find that out.

Since the purpose of printing this is to help disambiguate. Only do it
when "--" is missing (the actual reason though is many tests check
empty stderr to see that no error is raised and I'm too lazy to fix
all the test cases).

[1] Knowing the number of paths updated could also be useful even in
    normal case.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 This is related to another patch in
 
 https://public-inbox.org/git/20181110120707.25846-1-pclouds@gmail.com/T/#u

 While writing that patch I thought printing something would also
 help. But if we get ambiguation right (in that particular case) then
 we don't actually need this. But it still seems a good idea...

 apply.c                  |  3 ++-
 builtin/checkout-index.c |  6 ++++--
 builtin/checkout.c       | 30 ++++++++++++++++++++++--------
 builtin/difftool.c       |  2 +-
 cache.h                  |  4 ++--
 entry.c                  | 10 ++++++----
 unpack-trees.c           |  6 +++---
 7 files changed, 40 insertions(+), 21 deletions(-)

Comments

Junio C Hamano Nov. 12, 2018, 6:21 a.m. UTC | #1
Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> Since the purpose of printing this is to help disambiguate. Only do it
> when "--" is missing (the actual reason though is many tests check
> empty stderr to see that no error is raised and I'm too lazy to fix
> all the test cases).

Heh, honesty is good but in this particular case the official reason
alone would make perfect sense, too ;-)

As with progress output, shouldn't this automatically be turned off
when the standard error stream goes to non tty, as the real purpose
of printing is to help the user sitting in front of the terminal and
interacting with the command?

And by this, I do not mean to say that "When -- is missing" can go
away.  I agree that "--" is a clear sign that the user knows what
s/he is doing---to overwrite the paths in the working tree that
match the pathspec.

> @@ -371,17 +374,27 @@ static int checkout_paths(const struct checkout_opts *opts,
>  		struct cache_entry *ce = active_cache[pos];
>  		if (ce->ce_flags & CE_MATCHED) {
>  			if (!ce_stage(ce)) {
> -				errs |= checkout_entry(ce, &state, NULL);
> +				errs |= checkout_entry(ce, &state,
> +						       NULL, &nr_checkouts);
>  				continue;

As we count inside checkout_entry(), we might not actually write
this out when we know that the working tree file is up to date
already but we do not increment in that case either, so we keep
track of the accurate count of "updates", not path matches (i.e. we
are not reporting "we made sure this many paths are up to date in
the working tree"; instead we are reporting how many paths we needed
to overwrite in the working tree).

>  			}
>  			if (opts->writeout_stage)
> -				errs |= checkout_stage(opts->writeout_stage, ce, pos, &state);
> +				errs |= checkout_stage(opts->writeout_stage,
> +						       ce, pos,
> +						       &state, &nr_checkouts);

Likewike.

>  			else if (opts->merge)
> -				errs |= checkout_merged(pos, &state);
> +				errs |= checkout_merged(pos, &state,
> +							&nr_checkouts);

Likewise.

>  			pos = skip_same_name(ce, pos) - 1;
>  		}
>  	}
> -	errs |= finish_delayed_checkout(&state);
> +	errs |= finish_delayed_checkout(&state, &nr_checkouts);
> +
> +	if (opts->count_checkout_paths)
> +		fprintf_ln(stderr, Q_("%d path has been updated",
> +				      "%d paths have been updated",
> +				      nr_checkouts),
> +			   nr_checkouts);

This one may want to be protected behind isatty(2).  Or the default
value of count_checkout_paths could be tweakedbased on isatty(2).

> @@ -1064,6 +1077,7 @@ static int parse_branchname_arg(int argc, const char **argv,
>  		has_dash_dash = 1; /* case (3) or (1) */
>  	else if (dash_dash_pos >= 2)
>  		die(_("only one reference expected, %d given."), dash_dash_pos);
> +	opts->count_checkout_paths = !opts->quiet && !has_dash_dash;

i.e. "&& isatty(2)" as well.

Of course, "--[no-]count-paths" could be added to override this.
Duy Nguyen Nov. 12, 2018, 4:27 p.m. UTC | #2
On Mon, Nov 12, 2018 at 7:21 AM Junio C Hamano <gitster@pobox.com> wrote:
>
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
> > Since the purpose of printing this is to help disambiguate. Only do it
> > when "--" is missing (the actual reason though is many tests check
> > empty stderr to see that no error is raised and I'm too lazy to fix
> > all the test cases).
>
> Heh, honesty is good but in this particular case the official reason
> alone would make perfect sense, too ;-)
>
> As with progress output, shouldn't this automatically be turned off
> when the standard error stream goes to non tty, as the real purpose
> of printing is to help the user sitting in front of the terminal and
> interacting with the command?

I see this at the same level as "Switched to branch 'foo'" which is
also protected by opts->quiet. If we start hiding messages based on
tty it should be done for other messages in update_refs_for_switch()
too, I guess?

> And by this, I do not mean to say that "When -- is missing" can go
> away.  I agree that "--" is a clear sign that the user knows what
> s/he is doing---to overwrite the paths in the working tree that
> match the pathspec.

My other problem with deciding to print based on the presence of "--"
is also with branch switching code: it always prints something (unless
it actually does nothing). So it's a bit strange that the
checkout_paths() behaves slightly different based on "--".


> > @@ -371,17 +374,27 @@ static int checkout_paths(const struct checkout_opts *opts,
> >               struct cache_entry *ce = active_cache[pos];
> >               if (ce->ce_flags & CE_MATCHED) {
> >                       if (!ce_stage(ce)) {
> > -                             errs |= checkout_entry(ce, &state, NULL);
> > +                             errs |= checkout_entry(ce, &state,
> > +                                                    NULL, &nr_checkouts);
> >                               continue;
>
> As we count inside checkout_entry(), we might not actually write
> this out when we know that the working tree file is up to date
> already but we do not increment in that case either, so we keep
> track of the accurate count of "updates", not path matches (i.e. we
> are not reporting "we made sure this many paths are up to date in
> the working tree"; instead we are reporting how many paths we needed
> to overwrite in the working tree).

Yeah. I counted matched paths first, but when you do "git co .", you
get a huge (and meaningless, to me) number. Printing how many files
are updated makes more sense.

> >                       pos = skip_same_name(ce, pos) - 1;
> >               }
> >       }
> > -     errs |= finish_delayed_checkout(&state);
> > +     errs |= finish_delayed_checkout(&state, &nr_checkouts);
> > +
> > +     if (opts->count_checkout_paths)
> > +             fprintf_ln(stderr, Q_("%d path has been updated",
> > +                                   "%d paths have been updated",
> > +                                   nr_checkouts),
> > +                        nr_checkouts);
>
> This one may want to be protected behind isatty(2).  Or the default
> value of count_checkout_paths could be tweakedbased on isatty(2).

Another thing I'm going to change (if this v1 is in the right
direction): print different messages for "git checkout -- foo" and
"git checkout foo -- bar". The first one is "%d paths have been
reverted" but the second one does different things and probably should
say "%d paths have been updated from branch foo" or something like
that.
Junio C Hamano Nov. 12, 2018, 8:15 p.m. UTC | #3
Duy Nguyen <pclouds@gmail.com> writes:

> Another thing I'm going to change (if this v1 is in the right
> direction): print different messages for "git checkout -- foo" and
> "git checkout foo -- bar". The first one is "%d paths have been
> reverted" but the second one does different things and probably should
> say "%d paths have been updated from branch foo" or something like
> that.

I actually think that it is a bad idea to deliberately use such
misleading words like "revert", that will discourage users from
weaning themselves off of SVN.  "checked out N paths out of the
index", "checked out N paths out of the commit X" and "checked out N
paths out of branch B" would be clear enough and won't have such a
problem---otherwise you are encouraging them to make a mistake

	echo garbage >path
	... say oops ...
	git revert path
Ævar Arnfjörð Bjarmason Nov. 19, 2018, 1:08 p.m. UTC | #4
On Mon, Nov 12 2018, Duy Nguyen wrote:

> On Mon, Nov 12, 2018 at 7:21 AM Junio C Hamano <gitster@pobox.com> wrote:
>>
>> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>>
>> > Since the purpose of printing this is to help disambiguate. Only do it
>> > when "--" is missing (the actual reason though is many tests check
>> > empty stderr to see that no error is raised and I'm too lazy to fix
>> > all the test cases).
>>
>> Heh, honesty is good but in this particular case the official reason
>> alone would make perfect sense, too ;-)
>>
>> As with progress output, shouldn't this automatically be turned off
>> when the standard error stream goes to non tty, as the real purpose
>> of printing is to help the user sitting in front of the terminal and
>> interacting with the command?
>
> I see this at the same level as "Switched to branch 'foo'" which is
> also protected by opts->quiet. If we start hiding messages based on
> tty it should be done for other messages in update_refs_for_switch()
> too, I guess?

I have no real opinion either way, but whatever we can do about
"checkout" being so confusing since it does so many things is most
welcome.

Just an alternative: Maybe we can start this out as advice() output
that's either opt-in via config (not on by default) to start with, or
have some advice_tty() that only emits it in the same circumstances we
emit the progress output?
Duy Nguyen Nov. 19, 2018, 3:19 p.m. UTC | #5
On Mon, Nov 19, 2018 at 2:08 PM Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
>
>
> On Mon, Nov 12 2018, Duy Nguyen wrote:
>
> > On Mon, Nov 12, 2018 at 7:21 AM Junio C Hamano <gitster@pobox.com> wrote:
> >>
> >> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
> >>
> >> > Since the purpose of printing this is to help disambiguate. Only do it
> >> > when "--" is missing (the actual reason though is many tests check
> >> > empty stderr to see that no error is raised and I'm too lazy to fix
> >> > all the test cases).
> >>
> >> Heh, honesty is good but in this particular case the official reason
> >> alone would make perfect sense, too ;-)
> >>
> >> As with progress output, shouldn't this automatically be turned off
> >> when the standard error stream goes to non tty, as the real purpose
> >> of printing is to help the user sitting in front of the terminal and
> >> interacting with the command?
> >
> > I see this at the same level as "Switched to branch 'foo'" which is
> > also protected by opts->quiet. If we start hiding messages based on
> > tty it should be done for other messages in update_refs_for_switch()
> > too, I guess?
>
> I have no real opinion either way, but whatever we can do about
> "checkout" being so confusing since it does so many things is most
> welcome.
>
> Just an alternative: Maybe we can start this out as advice() output
> that's either opt-in via config (not on by default) to start with, or
> have some advice_tty() that only emits it in the same circumstances we
> emit the progress output?

No let's drop this for now. I promise to come back with something
better (at least it still sounds better in my mind). If that idea does
not work out, we can come back and see if we can improve this.
Junio C Hamano Nov. 20, 2018, 2:53 a.m. UTC | #6
Duy Nguyen <pclouds@gmail.com> writes:

>> > I see this at the same level as "Switched to branch 'foo'" which is
>> > also protected by opts->quiet. If we start hiding messages based on
>> > tty it should be done for other messages in update_refs_for_switch()
>> > too, I guess?
>
> No let's drop this for now. I promise to come back with something
> better (at least it still sounds better in my mind). If that idea does
> not work out, we can come back and see if we can improve this.

Let's leave it in 'pu'.

I do agree that this is similar to existing messages that talk about
checkout out a branch to work on etc., and I think giving feedback
when checkout paths out _is_ a good thing to do for interactive
users.

If we were to squelch such "notice" output for non-interactive use,
we should do so to the "notice" messages for checking out a branch,
as well, and also to other subcommands that report what they did
with these "notice" output.  And that is a separate topic.

The primary reason why I was annoyed was because "make test" (I
think I have DEFAULT_TEST_TARGET=prove, if it matters) output was
littered with these "checked out N paths", even though I am not
asking for verbose output.  

It could be that the real cause of that is perhaps because we have
too many "git checkout" that is outside test_expect_* block, in
which case we should fix these tests to perform the checkout inside
a test_expect_success block for test set-up, and my annoyance was
only shooting at the messenger.

For example, the attached patch illustrates the right problem but
addresses it in a wrong way.  This checkout_files() helper does too
many things outside (and before) the test_expect_success block it
has (other helpers like commit_chk_wrnNNO share the same problem),
and making "git checkout" noisy will reveal that as a problem by
leaking the noisy output directly to the tester.  But the real fix
is to enclose the set-up step inside a test_expect_success block,
which is not done by the following illustration patch, which instead
just squelches the message.

 t/t0027-auto-crlf.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh
index beb5927f77..3587e454f1 100755
--- a/t/t0027-auto-crlf.sh
+++ b/t/t0027-auto-crlf.sh
@@ -293,9 +293,9 @@ checkout_files () {
 	do
 		rm crlf_false_attr__$f.txt &&
 		if test -z "$ceol"; then
-			git checkout crlf_false_attr__$f.txt
+			git checkout -- crlf_false_attr__$f.txt
 		else
-			git -c core.eol=$ceol checkout crlf_false_attr__$f.txt
+			git -c core.eol=$ceol checkout -- crlf_false_attr__$f.txt
 		fi
 	done
diff mbox series

Patch

diff --git a/apply.c b/apply.c
index 073d5f0451..5876b02197 100644
--- a/apply.c
+++ b/apply.c
@@ -3352,7 +3352,8 @@  static int checkout_target(struct index_state *istate,
 
 	costate.refresh_cache = 1;
 	costate.istate = istate;
-	if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st))
+	if (checkout_entry(ce, &costate, NULL, NULL) ||
+	    lstat(ce->name, st))
 		return error(_("cannot checkout %s"), ce->name);
 	return 0;
 }
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
index 88b86c8d9f..bada491f58 100644
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
@@ -67,7 +67,8 @@  static int checkout_file(const char *name, const char *prefix)
 			continue;
 		did_checkout = 1;
 		if (checkout_entry(ce, &state,
-		    to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
+				   to_tempfile ? topath[ce_stage(ce)] : NULL,
+				   NULL) < 0)
 			errs++;
 	}
 
@@ -111,7 +112,8 @@  static void checkout_all(const char *prefix, int prefix_length)
 				write_tempfile_record(last_ce->name, prefix);
 		}
 		if (checkout_entry(ce, &state,
-		    to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
+				   to_tempfile ? topath[ce_stage(ce)] : NULL,
+				   NULL) < 0)
 			errs++;
 		last_ce = ce;
 	}
diff --git a/builtin/checkout.c b/builtin/checkout.c
index acdafc6e4c..13ed041dc1 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 count_checkout_paths;
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
 	 * should be updated accordingly.
@@ -165,12 +166,13 @@  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 *nr_checkouts)
 {
 	while (pos < active_nr &&
 	       !strcmp(active_cache[pos]->name, ce->name)) {
 		if (ce_stage(active_cache[pos]) == stage)
-			return checkout_entry(active_cache[pos], state, NULL);
+			return checkout_entry(active_cache[pos], state,
+					      NULL, nr_checkouts);
 		pos++;
 	}
 	if (stage == 2)
@@ -179,7 +181,7 @@  static int checkout_stage(int stage, const struct cache_entry *ce, int pos,
 		return error(_("path '%s' does not have their version"), ce->name);
 }
 
-static int checkout_merged(int pos, const struct checkout *state)
+static int checkout_merged(int pos, const struct checkout *state, int *nr_checkouts)
 {
 	struct cache_entry *ce = active_cache[pos];
 	const char *path = ce->name;
@@ -242,7 +244,7 @@  static int checkout_merged(int pos, const struct checkout *state)
 	ce = make_transient_cache_entry(mode, &oid, path, 2);
 	if (!ce)
 		die(_("make_cache_entry failed for path '%s'"), path);
-	status = checkout_entry(ce, state, NULL);
+	status = checkout_entry(ce, state, NULL, nr_checkouts);
 	discard_cache_entry(ce);
 	return status;
 }
@@ -257,6 +259,7 @@  static int checkout_paths(const struct checkout_opts *opts,
 	struct commit *head;
 	int errs = 0;
 	struct lock_file lock_file = LOCK_INIT;
+	int nr_checkouts = 0;
 
 	if (opts->track != BRANCH_TRACK_UNSPECIFIED)
 		die(_("'%s' cannot be used with updating paths"), "--track");
@@ -371,17 +374,27 @@  static int checkout_paths(const struct checkout_opts *opts,
 		struct cache_entry *ce = active_cache[pos];
 		if (ce->ce_flags & CE_MATCHED) {
 			if (!ce_stage(ce)) {
-				errs |= checkout_entry(ce, &state, NULL);
+				errs |= checkout_entry(ce, &state,
+						       NULL, &nr_checkouts);
 				continue;
 			}
 			if (opts->writeout_stage)
-				errs |= checkout_stage(opts->writeout_stage, ce, pos, &state);
+				errs |= checkout_stage(opts->writeout_stage,
+						       ce, pos,
+						       &state, &nr_checkouts);
 			else if (opts->merge)
-				errs |= checkout_merged(pos, &state);
+				errs |= checkout_merged(pos, &state,
+							&nr_checkouts);
 			pos = skip_same_name(ce, pos) - 1;
 		}
 	}
-	errs |= finish_delayed_checkout(&state);
+	errs |= finish_delayed_checkout(&state, &nr_checkouts);
+
+	if (opts->count_checkout_paths)
+		fprintf_ln(stderr, Q_("%d path has been updated",
+				      "%d paths have been updated",
+				      nr_checkouts),
+			   nr_checkouts);
 
 	if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
 		die(_("unable to write new index file"));
@@ -1064,6 +1077,7 @@  static int parse_branchname_arg(int argc, const char **argv,
 		has_dash_dash = 1; /* case (3) or (1) */
 	else if (dash_dash_pos >= 2)
 		die(_("only one reference expected, %d given."), dash_dash_pos);
+	opts->count_checkout_paths = !opts->quiet && !has_dash_dash;
 
 	if (!strcmp(arg, "-"))
 		arg = "@{-1}";
diff --git a/builtin/difftool.c b/builtin/difftool.c
index 544b0e8639..71318c26e1 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -323,7 +323,7 @@  static int checkout_path(unsigned mode, struct object_id *oid,
 	int ret;
 
 	ce = make_transient_cache_entry(mode, oid, path, 0);
-	ret = checkout_entry(ce, state, NULL);
+	ret = checkout_entry(ce, state, NULL, NULL);
 
 	discard_cache_entry(ce);
 	return ret;
diff --git a/cache.h b/cache.h
index 8b1ee42ae9..52fb6ba148 100644
--- a/cache.h
+++ b/cache.h
@@ -1540,9 +1540,9 @@  struct checkout {
 #define CHECKOUT_INIT { NULL, "" }
 
 #define TEMPORARY_FILENAME_LENGTH 25
-extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
+extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath, int *nr_checkouts);
 extern void enable_delayed_checkout(struct checkout *state);
-extern int finish_delayed_checkout(struct checkout *state);
+extern int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);
 
 struct cache_def {
 	struct strbuf path;
diff --git a/entry.c b/entry.c
index 5d136c5d55..5f213c30fe 100644
--- a/entry.c
+++ b/entry.c
@@ -161,7 +161,7 @@  static int remove_available_paths(struct string_list_item *item, void *cb_data)
 	return !available;
 }
 
-int finish_delayed_checkout(struct checkout *state)
+int finish_delayed_checkout(struct checkout *state, int *nr_checkouts)
 {
 	int errs = 0;
 	unsigned delayed_object_count;
@@ -226,7 +226,7 @@  int finish_delayed_checkout(struct checkout *state)
 				ce = index_file_exists(state->istate, path->string,
 						       strlen(path->string), 0);
 				if (ce) {
-					errs |= checkout_entry(ce, state, NULL);
+					errs |= checkout_entry(ce, state, NULL, nr_checkouts);
 					filtered_bytes += ce->ce_stat_data.sd_size;
 					display_throughput(progress, filtered_bytes);
 				} else
@@ -435,8 +435,8 @@  static void mark_colliding_entries(const struct checkout *state,
  * its name is returned in topath[], which must be able to hold at
  * least TEMPORARY_FILENAME_LENGTH bytes long.
  */
-int checkout_entry(struct cache_entry *ce,
-		   const struct checkout *state, char *topath)
+int checkout_entry(struct cache_entry *ce, const struct checkout *state,
+		   char *topath, int *nr_checkouts)
 {
 	static struct strbuf path = STRBUF_INIT;
 	struct stat st;
@@ -506,5 +506,7 @@  int checkout_entry(struct cache_entry *ce,
 		return 0;
 
 	create_directories(path.buf, path.len, state);
+	if (nr_checkouts)
+		(*nr_checkouts)++;
 	return write_entry(ce, path.buf, state, 0);
 }
diff --git a/unpack-trees.c b/unpack-trees.c
index 7570df481b..17f1e601da 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -294,7 +294,7 @@  static void load_gitmodules_file(struct index_state *index,
 			repo_read_gitmodules(the_repository);
 		} else if (state && (ce->ce_flags & CE_UPDATE)) {
 			submodule_free(the_repository);
-			checkout_entry(ce, state, NULL);
+			checkout_entry(ce, state, NULL, NULL);
 			repo_read_gitmodules(the_repository);
 		}
 	}
@@ -450,12 +450,12 @@  static int check_updates(struct unpack_trees_options *o)
 			display_progress(progress, ++cnt);
 			ce->ce_flags &= ~CE_UPDATE;
 			if (o->update && !o->dry_run) {
-				errs |= checkout_entry(ce, &state, NULL);
+				errs |= checkout_entry(ce, &state, NULL, NULL);
 			}
 		}
 	}
 	stop_progress(&progress);
-	errs |= finish_delayed_checkout(&state);
+	errs |= finish_delayed_checkout(&state, NULL);
 	if (o->update)
 		git_attr_set_direction(GIT_ATTR_CHECKIN);