Git development
 help / color / mirror / Atom feed
* Re: How de-duplicate similar repositories with alternates
From: Stefan Beller @ 2018-11-29 18:55 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git, git-users, Christian Couder
In-Reply-To: <87zhtsx73l.fsf@evledraar.gmail.com>

On Thu, Nov 29, 2018 at 7:00 AM Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
>
> A co-worker asked me today how space could be saved when you have
> multiple checkouts of the same repository (at different revs) on the
> same machine. I said since these won't block-level de-duplicate well[1]
> one way to do this is with alternates.

Another way is to use git-worktree, which would solve the gc issues
mentioned below?

I view alternates as a historic artefact as the deduping
of objects client side can be done using worktrees, and on the
serverside - I think - most of the git hosters use namespaces
and put a fork network into the same repository and use pack islands.

Can you elaborate on why worktrees would not solve the problem?
(I initially was hesitant to use them as I liked going into .git and tempering
with files such as the config directly. But now I cannot `cd .git` any more;
it turns out the advantages outweigh this corner case that I was attached to)

^ permalink raw reply

* Re: [PATCH 2/2] format-patch: allow for independent diff & range-diff options
From: Johannes Schindelin @ 2018-11-29 19:03 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git, Junio C Hamano, Eric Sunshine
In-Reply-To: <87tvjzyiph.fsf@evledraar.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 4326 bytes --]

Hi Ævar,

On Thu, 29 Nov 2018, Ævar Arnfjörð Bjarmason wrote:

> On Thu, Nov 29 2018, Johannes Schindelin wrote:
> 
> > On Thu, 29 Nov 2018, Ævar Arnfjörð Bjarmason wrote:
> >
> >> On Thu, Nov 29 2018, Johannes Schindelin wrote:
> >>
> >> > On Thu, 29 Nov 2018, Ævar Arnfjörð Bjarmason wrote:
> >> >
> >> >> On Thu, Nov 29 2018, Johannes Schindelin wrote:
> >> >>
> >> >> > On Wed, 28 Nov 2018, Ævar Arnfjörð Bjarmason wrote:
> >> >> >
> >> >> >> Change the semantics of the "--range-diff" option so that the regular
> >> >> >> diff options can be provided separately for the range-diff and the
> >> >> >> patch. This allows for supplying e.g. --range-diff-U0 and -U1 to
> >> >> >> "format-patch" to provide different context for the range-diff and the
> >> >> >> patch. This wasn't possible before.
> >> >> >
> >> >> > I really, really dislike the `--range-diff-<random-thing>`. We have
> >> >> > precedent for passing optional arguments that are passed to some other
> >> >> > command, so a much more logical and consistent convention would be to use
> >> >> > `--range-diff[=<diff-option>..]`, allowing all of the diff options that
> >> >> > you might want to pass to the outer diff in one go rather than having a
> >> >> > lengthy string of `--range-diff-this` and `--range-diff-that` options.
> >> >>
> >> >> Where do we pass those sorts of arguments?
> >> >>
> >> >> Reasons I did it this way:
> >> >>
> >> >>  a) Passing it as one option will require the user to double-quote those
> >> >>     options that take quoted arguments (e.g. --word-diff-regex), which I
> >> >>     thought sucked more than the prefix. On the implementation side we
> >> >>     couldn't leave the parsing of the command-line to the shell anymore.
> >> >>
> >> >>  b) I think people will want to tweak this very rarely, much more rarely
> >> >>     than e.g. -U10 in format-patch itself, so having something long-ish
> >> >>     doesn't sound bad.
> >> >
> >> > Hmm. I still don't like it. It sets a precedent, and we simply do not do
> >> > it that way in other circumstances (most obvious would be the -X merge
> >> > options). The more divergent user interfaces for the same sort of thing
> >> > are, the more brain cycles you force users to spend on navigating said
> >> > interfaces.
> >>
> >> Yeah it sucks, I just think it sucks less than the alternative :)
> >> I.e. I'm not picky about --range-diff-* prefix the name, but I think
> >> doing our own shell parsing would be nasty.
> >
> > What prevents you from using `sq_dequote_to_argv()`?
> 
> I mean not just nasty in terms of implementation, yeah we could do it,
> but also a nasty UX for things like --word-diff-regex. I.e. instead of:
> 
>     --range-diff-word-diff-regex='[0-9"]'
> 
> You need:
> 
>     --range-diff-opts="--word-diff-regex='[0-9\"]'"

Really? I think that would not work. It would pass the single quotes as
part of the regex to the diff machinery.

Or maybe not. But the extra quotes do not strike me as necessary, as there
is no shell script involved (thank deity!) after `git range-diff` parsed
the options.

> Now admittedly that in itself isn't very painful *in this case*, but in
> terms of precedent I really dislike that option, i.e. git having some
> mode where I need to work to escape input to pass to another command.
> 
> Not saying that this --range-diff-* thing is what we should go for, but
> surely we can find some way to do deal with this that doesn't involve
> the user needing to escape stuff like this.
> 
> It also has other downstream effects in the UI, e.g. it's presumably
> easy to teach the bash completion that a --foo=XYZ option is also called
> --some-prefix--foo=XYZ and to enable completion for that, less so for
> making it smart enough to complete "--some-prefix-opts="--foo=<TAB>".

These are all good points, and need proper discussion.

Sadly, all that time needed for a proper discussion is not left before
v2.20.0 is supposed to come out.

Quite honestly, I think what we will have to do is to describe in the
documentation of `format-patch`'s `--range-diff` option that the exact
user interface how to pass diff options down to `range-diff` is in flux
and not final.

That way, we can give your design the proper treatment, and work together
on making a user interface we all can be happy with.

Ciao,
Dscho

^ permalink raw reply

* [PATCH 0/1] Fix built-in rebase regression noticed by Debian's dgit
From: Johannes Schindelin via GitGitGadget @ 2018-11-29 19:09 UTC (permalink / raw)
  To: git; +Cc: Ian Jackson, Junio C Hamano

It has been reported on the Debian bug tracker
[https://bugs.debian.org/914695] that the built-in rebase regresses on the
scripted version, and later details emerged that this has something to do
with the reflog messages: they were different with the built-in rebase than
with the scripted one.

This here patch fixes that.

Johannes Schindelin (1):
  rebase: fix GIT_REFLOG_ACTION regression

 builtin/rebase.c          | 29 ++++++++++++++++++++++++++---
 t/t3406-rebase-message.sh | 26 ++++++++++++++++++++++++++
 2 files changed, 52 insertions(+), 3 deletions(-)


base-commit: 7068cbc4abac53d9c3675dfba81c1e97d25e8eeb
Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-91%2Fdscho%2Ffix-reflog-action-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-91/dscho/fix-reflog-action-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/91
-- 
gitgitgadget

^ permalink raw reply

* [PATCH 1/1] rebase: fix GIT_REFLOG_ACTION regression
From: Johannes Schindelin via GitGitGadget @ 2018-11-29 19:09 UTC (permalink / raw)
  To: git; +Cc: Ian Jackson, Junio C Hamano, Johannes Schindelin
In-Reply-To: <pull.91.git.gitgitgadget@gmail.com>

From: Johannes Schindelin <johannes.schindelin@gmx.de>

The scripted version (partially) heeded the `GIT_REFLOG_ACTION` and when
we converted to a built-in, this regressed.

Fix that, and add a regression test, both with `GIT_REFLOG_ACTION` set
and unset.

Note: the reflog message for "rebase finished" did *not* heed
GIT_REFLOG_ACTION, and as we are very late in the v2.20.0-rcN phase, we
leave that bug for later (as it seems that that bug has been with us
from the very beginning).

Reported by Ian Jackson.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/rebase.c          | 29 ++++++++++++++++++++++++++---
 t/t3406-rebase-message.sh | 26 ++++++++++++++++++++++++++
 2 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 5b3e5baec8..ba0c3c954b 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -776,6 +776,23 @@ static void NORETURN error_on_missing_default_upstream(void)
 	exit(1);
 }
 
+static void set_reflog_action(struct rebase_options *options)
+{
+	const char *env;
+	struct strbuf buf = STRBUF_INIT;
+
+	if (!is_interactive(options))
+		return;
+
+	env = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
+	if (env && strcmp("rebase", env))
+		return; /* only override it if it is "rebase" */
+
+	strbuf_addf(&buf, "rebase -i (%s)", options->action);
+	setenv(GIT_REFLOG_ACTION_ENVIRONMENT, buf.buf, 1);
+	strbuf_release(&buf);
+}
+
 int cmd_rebase(int argc, const char **argv, const char *prefix)
 {
 	struct rebase_options options = {
@@ -978,6 +995,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	if (action != NO_ACTION && !in_progress)
 		die(_("No rebase in progress?"));
+	setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
 
 	if (action == ACTION_EDIT_TODO && !is_interactive(&options))
 		die(_("The --edit-todo action can only be used during "
@@ -990,6 +1008,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		int fd;
 
 		options.action = "continue";
+		set_reflog_action(&options);
 
 		/* Sanity check */
 		if (get_oid("HEAD", &head))
@@ -1018,6 +1037,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		struct string_list merge_rr = STRING_LIST_INIT_DUP;
 
 		options.action = "skip";
+		set_reflog_action(&options);
 
 		rerere_clear(&merge_rr);
 		string_list_clear(&merge_rr, 1);
@@ -1033,6 +1053,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	case ACTION_ABORT: {
 		struct string_list merge_rr = STRING_LIST_INIT_DUP;
 		options.action = "abort";
+		set_reflog_action(&options);
 
 		rerere_clear(&merge_rr);
 		string_list_clear(&merge_rr, 1);
@@ -1440,11 +1461,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				}
 
 				strbuf_reset(&buf);
-				strbuf_addf(&buf, "rebase: checkout %s",
+				strbuf_addf(&buf, "%s: checkout %s",
+					    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
 					    options.switch_to);
 				if (reset_head(&oid, "checkout",
 					       options.head_name, 0,
-					       NULL, NULL) < 0) {
+					       NULL, buf.buf) < 0) {
 					ret = !!error(_("could not switch to "
 							"%s"),
 						      options.switch_to);
@@ -1508,7 +1530,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		printf(_("First, rewinding head to replay your work on top of "
 			 "it...\n"));
 
-	strbuf_addf(&msg, "rebase: checkout %s", options.onto_name);
+	strbuf_addf(&msg, "%s: checkout %s",
+		    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
 	if (reset_head(&options.onto->object.oid, "checkout", NULL,
 		       RESET_HEAD_DETACH, NULL, msg.buf))
 		die(_("Could not detach HEAD"));
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index 38bd876cab..db8505eb86 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -91,4 +91,30 @@ test_expect_success 'error out early upon -C<n> or --whitespace=<bad>' '
 	test_i18ngrep "Invalid whitespace option" err
 '
 
+test_expect_success 'GIT_REFLOG_ACTION' '
+	git checkout start &&
+	test_commit reflog-onto &&
+	git checkout -b reflog-topic start &&
+	test_commit reflog-to-rebase &&
+
+	git rebase reflog-onto &&
+	git log -g --format=%gs -3 >actual &&
+	cat >expect <<-\EOF &&
+	rebase finished: returning to refs/heads/reflog-topic
+	rebase: reflog-to-rebase
+	rebase: checkout reflog-onto
+	EOF
+	test_cmp expect actual &&
+
+	git checkout -b reflog-prefix reflog-to-rebase &&
+	GIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto &&
+	git log -g --format=%gs -3 >actual &&
+	cat >expect <<-\EOF &&
+	rebase finished: returning to refs/heads/reflog-prefix
+	change-the-reflog: reflog-to-rebase
+	change-the-reflog: checkout reflog-onto
+	EOF
+	test_cmp expect actual
+'
+
 test_done
-- 
gitgitgadget

^ permalink raw reply related

* Re: [PATCH v2 6/7] checkout: split into switch-branch and checkout-files
From: Stefan Xenos @ 2018-11-29 19:29 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Junio C Hamano, Ævar Arnfjörð Bjarmason, git,
	Stefan Beller, Thomas Gummerer
In-Reply-To: <CACsJy8An2n5yah1UTCJZoC5ucSpCoM0vrXtEXnjg-di7jQZwLA@mail.gmail.com>

>
> Which brings us back to your "git checkout-files <tree-ish>" use case
> above. It should be treat the same way in my opinion, so we either do
>
>  git checkout-files --from=tree-ish :/
>
> or
>
>  git checkout-files --from=tree-ish .
>
> But "git checkout-files --from=tree-ish" alone is rejected.

Agreed. Those arguments are better. The gist of my comment was the
treatment of newly created local files rather than the form of the
arguments, but it sounds like you've got that under control, too.

> > Suggestion:
> > If git checkout-files overwrites or deletes any locally-modified files
> > from the workspace or index, those files could be auto-stashed. That
> > would make it easy to restore them in the event of a mistyped command.
> > Auto-stashing could be suppressed with a command-line argument (with
> > alternate behaviors being fail-if-modified or always-overwrite).
>
> Stashing I think is not the right tool for this. When you stash, you
> plan to retrieve it back later but here you should rarely ever need to
> unstash until the accident. For recovery from accidents like this, I
> have another thing in queue to achieve the same (I'm calling it
> "backup log" now). So we will have the same functionality, just with
> different means.

Yes, this makes sense too. You wouldn't want to pollute the stash list
with autogenerated things the user probably doesn't want.

> This one is tricky because we should deal with submodule autoupdate
> consistently across all porcelain commands (or at least common ones),
> not just checkout-files.

This is also a good point. I'd like it if submodules just behaved like
a single giant repository for most commands, but you're right that
this is something that should be done intentionally for all the
commands at one rather than just for a single command.

I also like your new names "switch-branch" and "restore-files".

^ permalink raw reply

* Re: How de-duplicate similar repositories with alternates
From: Ævar Arnfjörð Bjarmason @ 2018-11-29 20:10 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, git-users, Christian Couder
In-Reply-To: <CAGZ79ka1sjU+rHctRP4SVMP0GQsK2iZghkU46=f96ugqvX5Neg@mail.gmail.com>


On Thu, Nov 29 2018, Stefan Beller wrote:

> On Thu, Nov 29, 2018 at 7:00 AM Ævar Arnfjörð Bjarmason
> <avarab@gmail.com> wrote:
>>
>> A co-worker asked me today how space could be saved when you have
>> multiple checkouts of the same repository (at different revs) on the
>> same machine. I said since these won't block-level de-duplicate well[1]
>> one way to do this is with alternates.
>
> Another way is to use git-worktree, which would solve the gc issues
> mentioned below?
>
> I view alternates as a historic artefact as the deduping
> of objects client side can be done using worktrees, and on the
> serverside - I think - most of the git hosters use namespaces
> and put a fork network into the same repository and use pack islands.
>
> Can you elaborate on why worktrees would not solve the problem?
> (I initially was hesitant to use them as I liked going into .git and tempering
> with files such as the config directly. But now I cannot `cd .git` any more;
> it turns out the advantages outweigh this corner case that I was attached to)

This was discussed recently on-list & I chimed in with details about
that here:
https://public-inbox.org/git/87po1waqyc.fsf@evledraar.gmail.com/ &
https://public-inbox.org/git/87muwzc2kv.fsf@evledraar.gmail.com/

In particular the "multiple devs" use-case described in the latter
E-Mail is what I have in mind. Worktrees are inherently unsuitable for
that.

^ permalink raw reply

* Re: How de-duplicate similar repositories with alternates
From: Duy Nguyen @ 2018-11-29 20:43 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Stefan Beller, Git Mailing List, git-users, Christian Couder
In-Reply-To: <87r2f3y7a9.fsf@evledraar.gmail.com>

On Thu, Nov 29, 2018 at 9:15 PM Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
>
>
> On Thu, Nov 29 2018, Stefan Beller wrote:
>
> > On Thu, Nov 29, 2018 at 7:00 AM Ævar Arnfjörð Bjarmason
> > <avarab@gmail.com> wrote:
> >>
> >> A co-worker asked me today how space could be saved when you have
> >> multiple checkouts of the same repository (at different revs) on the
> >> same machine. I said since these won't block-level de-duplicate well[1]
> >> one way to do this is with alternates.
> >
> > Another way is to use git-worktree, which would solve the gc issues
> > mentioned below?
> >
> > I view alternates as a historic artefact as the deduping
> > of objects client side can be done using worktrees, and on the
> > serverside - I think - most of the git hosters use namespaces
> > and put a fork network into the same repository and use pack islands.
> >
> > Can you elaborate on why worktrees would not solve the problem?
> > (I initially was hesitant to use them as I liked going into .git and tempering
> > with files such as the config directly. But now I cannot `cd .git` any more;
> > it turns out the advantages outweigh this corner case that I was attached to)
>
> This was discussed recently on-list & I chimed in with details about
> that here:
> https://public-inbox.org/git/87po1waqyc.fsf@evledraar.gmail.com/ &
> https://public-inbox.org/git/87muwzc2kv.fsf@evledraar.gmail.com/
>
> In particular the "multiple devs" use-case described in the latter
> E-Mail is what I have in mind. Worktrees are inherently unsuitable for
> that.

Yeah, the separate ref namespace is something I would like to have on
the client side too. I did consider implementing it a couple times but
it's really no small task. Naively, I could achieve that pretty quick
if all refs are loose, but performance would tank once the number of
refs goes over say a hundred.
-- 
Duy

^ permalink raw reply

* [PATCH/RFC v3 00/14] Introduce new commands switch-branch and restore-files
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181127165211.24763-1-pclouds@gmail.com>

v3 sees switch-branch go back to switch-branch (in v2 it was
checkout-branch). checkout-files is also renamed restore-files (v1 was
restore-paths). Hopefully we won't see another rename.

I'll try to summarize the differences between the new commands and
'git checkout' down here, but you're welcome to just head to 07/14 and
read the new man pages.

'git switch-branch'

- does not "do nothing", you have to either switch branch, create a
  new branch, or detach. "git switch-branch" with no arguments is
  rejected.

- implicit detaching is rejected. If you need to detach, you need to
  give --detach. Or stick to 'git checkout'.

- -b/-B is renamed to -c/-C with long option names

- of course does not accept pathspec

'git restore-files'

- takes a ref from --from argument, not as a free ref. As a result,
  '--' is no longer needed. All non-option arguments are pathspec

- pathspec is mandatory, you can't do "git restore-files" without any
  pathspec.

- I just remember -p which is allowed to take no pathspec :( I'll fix
  it later.

- Two more fancy features (the "git checkout --index" being the
  default mode and the backup log for accidental overwrites) are of
  course still missing. But they are coming.

I did not go replace "detached HEAD" with "unnamed branch" (or "no
branch") everywhere because I think a unique term is still good to
refer to this concept. Or maybe "no branch" is good enough. I dunno.

Nguyễn Thái Ngọc Duy (14):
  git-checkout.txt: fix one syntax line
  git-checkout.txt: split detached head section out
  checkout: factor out some code in parse_branchname_arg()
  checkout: make "opts" in cmd_checkout() a pointer
  checkout: move 'confict_style' and 'dwim_..' to checkout_opts
  checkout: split options[] array in three pieces
  checkout: split into switch-branch and restore-files
  switch-branch: better names for -b and -B
  switch-branch: stop accepting pathspec
  switch-branch: reject "do nothing" case
  switch-branch: only allow explicit detached HEAD
  restore-files: take tree-ish from --from option instead
  restore-files: make pathspec mandatory
  doc: promote "git switch-branch" and "git restore-files"

 .gitignore                             |   2 +
 Documentation/config/advice.txt        |  10 +-
 Documentation/config/checkout.txt      |   5 +-
 Documentation/detach-head.txt          | 132 +++++++++
 Documentation/git-branch.txt           |   8 +-
 Documentation/git-check-ref-format.txt |   2 +-
 Documentation/git-checkout.txt         | 140 +--------
 Documentation/git-format-patch.txt     |   2 +-
 Documentation/git-merge-base.txt       |   2 +-
 Documentation/git-rebase.txt           |   2 +-
 Documentation/git-remote.txt           |   2 +-
 Documentation/git-rerere.txt           |  10 +-
 Documentation/git-reset.txt            |  18 +-
 Documentation/git-restore-files.txt    | 167 +++++++++++
 Documentation/git-revert.txt           |   2 +-
 Documentation/git-stash.txt            |   6 +-
 Documentation/git-switch-branch.txt    | 289 +++++++++++++++++++
 Documentation/gitattributes.txt        |   2 +-
 Documentation/gitcli.txt               |   4 +-
 Documentation/gitcore-tutorial.txt     |  18 +-
 Documentation/giteveryday.txt          |  24 +-
 Documentation/githooks.txt             |   5 +-
 Documentation/gittutorial-2.txt        |   2 +-
 Documentation/gittutorial.txt          |   4 +-
 Documentation/revisions.txt            |   2 +-
 Documentation/user-manual.txt          |  54 ++--
 Makefile                               |   2 +
 advice.c                               |  11 +-
 builtin.h                              |   2 +
 builtin/checkout.c                     | 380 ++++++++++++++++++-------
 command-list.txt                       |   4 +-
 git.c                                  |   2 +
 parse-options-cb.c                     |  16 ++
 parse-options.h                        |   3 +-
 sha1-name.c                            |   2 +-
 wt-status.c                            |   2 +-
 36 files changed, 1006 insertions(+), 332 deletions(-)
 create mode 100644 Documentation/detach-head.txt
 create mode 100644 Documentation/git-restore-files.txt
 create mode 100644 Documentation/git-switch-branch.txt

-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply

* [PATCH v3 01/14] git-checkout.txt: fix one syntax line
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

<branch> can be omitted in this syntax, and it's actually documented a
few paragraphs down:

  You could omit <branch>, in which case the command degenerates to
  "check out the current branch", which is a glorified no-op with
  rather expensive side-effects to show only the tracking information,
  if exists, for the current branch.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 801de2f764..65bd1bc50d 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -23,7 +23,7 @@ or the specified tree.  If no paths are given, 'git checkout' will
 also update `HEAD` to set the specified branch as the current
 branch.
 
-'git checkout' <branch>::
+'git checkout' [<branch>]::
 	To prepare for working on <branch>, switch to it by updating
 	the index and the files in the working tree, and by pointing
 	HEAD at the branch. Local modifications to the files in the
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* [PATCH v3 03/14] checkout: factor out some code in parse_branchname_arg()
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

This is in preparation for the new command restore-files, which also
needs to parse opts->source_tree but does not need all the
disambiguation logic.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 51 ++++++++++++++++++++++++++++------------------
 1 file changed, 31 insertions(+), 20 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index acdafc6e4c..1887c996c6 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -990,6 +990,34 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
 	return git_xmerge_config(var, value, NULL);
 }
 
+static void setup_new_branch_info_and_source_tree(
+	struct branch_info *new_branch_info,
+	struct checkout_opts *opts,
+	struct object_id *rev,
+	const char *arg)
+{
+	struct tree **source_tree = &opts->source_tree;
+	struct object_id branch_rev;
+
+	new_branch_info->name = arg;
+	setup_branch_path(new_branch_info);
+
+	if (!check_refname_format(new_branch_info->path, 0) &&
+	    !read_ref(new_branch_info->path, &branch_rev))
+		oidcpy(rev, &branch_rev);
+	else
+		new_branch_info->path = NULL; /* not an existing branch */
+
+	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
+	if (!new_branch_info->commit) {
+		/* not a commit */
+		*source_tree = parse_tree_indirect(rev);
+	} else {
+		parse_commit_or_die(new_branch_info->commit);
+		*source_tree = get_commit_tree(new_branch_info->commit);
+	}
+}
+
 static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new_branch_info,
@@ -997,10 +1025,8 @@ static int parse_branchname_arg(int argc, const char **argv,
 				struct object_id *rev,
 				int *dwim_remotes_matched)
 {
-	struct tree **source_tree = &opts->source_tree;
 	const char **new_branch = &opts->new_branch;
 	int argcount = 0;
-	struct object_id branch_rev;
 	const char *arg;
 	int dash_dash_pos;
 	int has_dash_dash = 0;
@@ -1114,26 +1140,11 @@ static int parse_branchname_arg(int argc, const char **argv,
 	argv++;
 	argc--;
 
-	new_branch_info->name = arg;
-	setup_branch_path(new_branch_info);
-
-	if (!check_refname_format(new_branch_info->path, 0) &&
-	    !read_ref(new_branch_info->path, &branch_rev))
-		oidcpy(rev, &branch_rev);
-	else
-		new_branch_info->path = NULL; /* not an existing branch */
+	setup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);
 
-	new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
-	if (!new_branch_info->commit) {
-		/* not a commit */
-		*source_tree = parse_tree_indirect(rev);
-	} else {
-		parse_commit_or_die(new_branch_info->commit);
-		*source_tree = get_commit_tree(new_branch_info->commit);
-	}
-
-	if (!*source_tree)                   /* case (1): want a tree */
+	if (!opts->source_tree)                   /* case (1): want a tree */
 		die(_("reference is not a tree: %s"), arg);
+
 	if (!has_dash_dash) {	/* case (3).(d) -> (1) */
 		/*
 		 * Do not complain the most common case
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* [PATCH v3 04/14] checkout: make "opts" in cmd_checkout() a pointer
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

"opts" will soon be moved out of cmd_checkout(). To keep changes in
that patch smaller, convert "opts" to a pointer and keep the real
thing behind "real_opts".

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 109 +++++++++++++++++++++++----------------------
 1 file changed, 55 insertions(+), 54 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 1887c996c6..1b19328d0a 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1236,76 +1236,77 @@ static int checkout_branch(struct checkout_opts *opts,
 
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
-	struct checkout_opts opts;
+	struct checkout_opts real_opts;
+	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
 	char *conflict_style = NULL;
 	int dwim_new_local_branch = 1;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
-		OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
-		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
+		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		OPT_STRING('b', NULL, &opts->new_branch, N_("branch"),
 			   N_("create and checkout a new branch")),
-		OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"),
+		OPT_STRING('B', NULL, &opts->new_branch_force, N_("branch"),
 			   N_("create/reset and checkout a branch")),
-		OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
-		OPT_BOOL(0, "detach", &opts.force_detach, N_("detach HEAD at named commit")),
-		OPT_SET_INT('t', "track",  &opts.track, N_("set upstream info for new branch"),
+		OPT_BOOL('l', NULL, &opts->new_branch_log, N_("create reflog for new branch")),
+		OPT_BOOL(0, "detach", &opts->force_detach, N_("detach HEAD at named commit")),
+		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
 			BRANCH_TRACK_EXPLICIT),
-		OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
-		OPT_SET_INT_F('2', "ours", &opts.writeout_stage,
+		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
-		OPT_SET_INT_F('3', "theirs", &opts.writeout_stage,
+		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)"),
+		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts.overwrite_ignore,
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
-		OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
-		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
+		OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")),
+		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree,
 			 N_("do not limit pathspecs to sparse entries only")),
 		OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
 				N_("second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
 			    "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, "progress", &opts->show_progress, N_("force progress reporting")),
 		OPT_END(),
 	};
 
-	memset(&opts, 0, sizeof(opts));
+	memset(opts, 0, sizeof(*opts));
 	memset(&new_branch_info, 0, sizeof(new_branch_info));
-	opts.overwrite_ignore = 1;
-	opts.prefix = prefix;
-	opts.show_progress = -1;
+	opts->overwrite_ignore = 1;
+	opts->prefix = prefix;
+	opts->show_progress = -1;
 
-	git_config(git_checkout_config, &opts);
+	git_config(git_checkout_config, opts);
 
-	opts.track = BRANCH_TRACK_UNSPECIFIED;
+	opts->track = BRANCH_TRACK_UNSPECIFIED;
 
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	if (opts.show_progress < 0) {
-		if (opts.quiet)
-			opts.show_progress = 0;
+	if (opts->show_progress < 0) {
+		if (opts->quiet)
+			opts->show_progress = 0;
 		else
-			opts.show_progress = isatty(2);
+			opts->show_progress = isatty(2);
 	}
 
 	if (conflict_style) {
-		opts.merge = 1; /* implied */
+		opts->merge = 1; /* implied */
 		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
 	}
 
-	if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
+	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
 		die(_("-b, -B and --orphan are mutually exclusive"));
 
 	/*
@@ -1313,14 +1314,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	 * and new_branch_force and new_orphan_branch will tell us which one of
 	 * -b/-B/--orphan is being used.
 	 */
-	if (opts.new_branch_force)
-		opts.new_branch = opts.new_branch_force;
+	if (opts->new_branch_force)
+		opts->new_branch = opts->new_branch_force;
 
-	if (opts.new_orphan_branch)
-		opts.new_branch = opts.new_orphan_branch;
+	if (opts->new_orphan_branch)
+		opts->new_branch = opts->new_orphan_branch;
 
 	/* --track without -b/-B/--orphan should DWIM */
-	if (opts.track != BRANCH_TRACK_UNSPECIFIED && !opts.new_branch) {
+	if (opts->track != BRANCH_TRACK_UNSPECIFIED && !opts->new_branch) {
 		const char *argv0 = argv[0];
 		if (!argc || !strcmp(argv0, "--"))
 			die(_("--track needs a branch name"));
@@ -1329,7 +1330,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		argv0 = strchr(argv0, '/');
 		if (!argv0 || !argv0[1])
 			die(_("missing branch name; try -b"));
-		opts.new_branch = argv0 + 1;
+		opts->new_branch = argv0 + 1;
 	}
 
 	/*
@@ -1348,56 +1349,56 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	if (argc) {
 		struct object_id rev;
 		int dwim_ok =
-			!opts.patch_mode &&
+			!opts->patch_mode &&
 			dwim_new_local_branch &&
-			opts.track == BRANCH_TRACK_UNSPECIFIED &&
-			!opts.new_branch;
+			opts->track == BRANCH_TRACK_UNSPECIFIED &&
+			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
-					     &new_branch_info, &opts, &rev,
+					     &new_branch_info, opts, &rev,
 					     &dwim_remotes_matched);
 		argv += n;
 		argc -= n;
 	}
 
 	if (argc) {
-		parse_pathspec(&opts.pathspec, 0,
-			       opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
+		parse_pathspec(&opts->pathspec, 0,
+			       opts->patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
 			       prefix, argv);
 
-		if (!opts.pathspec.nr)
+		if (!opts->pathspec.nr)
 			die(_("invalid path specification"));
 
 		/*
 		 * Try to give more helpful suggestion.
 		 * new_branch && argc > 1 will be caught later.
 		 */
-		if (opts.new_branch && argc == 1)
+		if (opts->new_branch && argc == 1)
 			die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
-				argv[0], opts.new_branch);
+				argv[0], opts->new_branch);
 
-		if (opts.force_detach)
+		if (opts->force_detach)
 			die(_("git checkout: --detach does not take a path argument '%s'"),
 			    argv[0]);
 
-		if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+		if (1 < !!opts->writeout_stage + !!opts->force + !!opts->merge)
 			die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
 			      "checking out of the index."));
 	}
 
-	if (opts.new_branch) {
+	if (opts->new_branch) {
 		struct strbuf buf = STRBUF_INIT;
 
-		if (opts.new_branch_force)
-			opts.branch_exists = validate_branchname(opts.new_branch, &buf);
+		if (opts->new_branch_force)
+			opts->branch_exists = validate_branchname(opts->new_branch, &buf);
 		else
-			opts.branch_exists =
-				validate_new_branchname(opts.new_branch, &buf, 0);
+			opts->branch_exists =
+				validate_new_branchname(opts->new_branch, &buf, 0);
 		strbuf_release(&buf);
 	}
 
 	UNLEAK(opts);
-	if (opts.patch_mode || opts.pathspec.nr) {
-		int ret = checkout_paths(&opts, new_branch_info.name);
+	if (opts->patch_mode || opts->pathspec.nr) {
+		int ret = checkout_paths(opts, new_branch_info.name);
 		if (ret && dwim_remotes_matched > 1 &&
 		    advice_checkout_ambiguous_remote_branch_name)
 			advise(_("'%s' matched more than one remote tracking branch.\n"
@@ -1416,6 +1417,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			       dwim_remotes_matched);
 		return ret;
 	} else {
-		return checkout_branch(&opts, &new_branch_info);
+		return checkout_branch(opts, &new_branch_info);
 	}
 }
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* [PATCH v3 05/14] checkout: move 'confict_style' and 'dwim_..' to checkout_opts
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

These local variables are referenced by struct option[]. This struct
will soon be broken down, moved away and we can't rely on local
variables anymore. Move these two to struct checkout_opts in
preparation for that.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 1b19328d0a..2423fdbf94 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -44,6 +44,8 @@ struct checkout_opts {
 	int ignore_skipworktree;
 	int ignore_other_worktrees;
 	int show_progress;
+	int dwim_new_local_branch;
+
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
 	 * should be updated accordingly.
@@ -55,6 +57,7 @@ struct checkout_opts {
 	int new_branch_log;
 	enum branch_track track;
 	struct diff_options diff_options;
+	char *conflict_style;
 
 	int branch_exists;
 	const char *prefix;
@@ -1239,8 +1242,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	struct checkout_opts real_opts;
 	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
-	char *conflict_style = NULL;
-	int dwim_new_local_branch = 1;
 	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
@@ -1265,12 +1266,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
 			   N_("update ignored files (default)"),
 			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &conflict_style, N_("style"),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
 			   N_("conflict style (merge or diff3)")),
 		OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")),
 		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree,
 			 N_("do not limit pathspecs to sparse entries only")),
-		OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
+		OPT_HIDDEN_BOOL(0, "guess", &opts->dwim_new_local_branch,
 				N_("second guess 'git checkout <no-such-branch>'")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
@@ -1286,6 +1287,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts->overwrite_ignore = 1;
 	opts->prefix = prefix;
 	opts->show_progress = -1;
+	opts->dwim_new_local_branch = 1;
 
 	git_config(git_checkout_config, opts);
 
@@ -1301,9 +1303,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			opts->show_progress = isatty(2);
 	}
 
-	if (conflict_style) {
+	if (opts->conflict_style) {
 		opts->merge = 1; /* implied */
-		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+		git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL);
 	}
 
 	if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
@@ -1350,7 +1352,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		struct object_id rev;
 		int dwim_ok =
 			!opts->patch_mode &&
-			dwim_new_local_branch &&
+			opts->dwim_new_local_branch &&
 			opts->track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts->new_branch;
 		int n = parse_branchname_arg(argc, argv, dwim_ok,
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* [PATCH v3 06/14] checkout: split options[] array in three pieces
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

This is a preparation step for introducing new commands that do parts
of what checkout does. There will be two new commands, one is about
switching branches, detaching HEAD... one about checking out
paths. These share the a subset of command line options. The rest of
command line options are separate.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 77 +++++++++++++++++++++++++++++++++-------------
 parse-options-cb.c | 16 ++++++++++
 parse-options.h    |  3 +-
 3 files changed, 73 insertions(+), 23 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2423fdbf94..764e1a83a1 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1237,14 +1237,31 @@ static int checkout_branch(struct checkout_opts *opts,
 	return switch_branches(opts, new_branch_info);
 }
 
-int cmd_checkout(int argc, const char **argv, const char *prefix)
+static struct option *add_common_options(struct checkout_opts *opts,
+					 struct option *prevopts)
 {
-	struct checkout_opts real_opts;
-	struct checkout_opts *opts = &real_opts;
-	struct branch_info new_branch_info;
-	int dwim_remotes_matched = 0;
 	struct option options[] = {
 		OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
+			    "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__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
+			   PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
+			   N_("conflict style (merge or diff3)")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_switch_branch_options(struct checkout_opts *opts,
+						struct option *prevopts)
+{
+	struct option options[] = {
 		OPT_STRING('b', NULL, &opts->new_branch, N_("branch"),
 			   N_("create and checkout a new branch")),
 		OPT_STRING('B', NULL, &opts->new_branch_force, N_("branch"),
@@ -1254,33 +1271,44 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
 			BRANCH_TRACK_EXPLICIT),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+		OPT_HIDDEN_BOOL(0, "guess", &opts->dwim_new_local_branch,
+				N_("second guess 'git checkout <no-such-branch>'")),
+		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
+			 N_("do not check if another worktree is holding the given ref")),
+		OPT_END()
+	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+static struct option *add_checkout_path_options(struct checkout_opts *opts,
+						struct option *prevopts)
+{
+	struct option options[] = {
 		OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
 			      N_("checkout our version for unmerged files"),
 			      2, PARSE_OPT_NONEG),
 		OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
 			      N_("checkout their version for unmerged files"),
 			      3, PARSE_OPT_NONEG),
-		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
-		OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
-			   N_("update ignored files (default)"),
-			   PARSE_OPT_NOCOMPLETE),
-		OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
-			   N_("conflict style (merge or diff3)")),
 		OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")),
 		OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree,
 			 N_("do not limit pathspecs to sparse entries only")),
-		OPT_HIDDEN_BOOL(0, "guess", &opts->dwim_new_local_branch,
-				N_("second guess 'git checkout <no-such-branch>'")),
-		OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
-			 N_("do not check if another worktree is holding the given ref")),
-		{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
-			    "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_END(),
+		OPT_END()
 	};
+	struct option *newopts = parse_options_concat(prevopts, options);
+	free(prevopts);
+	return newopts;
+}
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+	struct checkout_opts real_opts;
+	struct checkout_opts *opts = &real_opts;
+	struct branch_info new_branch_info;
+	int dwim_remotes_matched = 0;
+	struct option *options = NULL;
 
 	memset(opts, 0, sizeof(*opts));
 	memset(&new_branch_info, 0, sizeof(new_branch_info));
@@ -1293,6 +1321,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	opts->track = BRANCH_TRACK_UNSPECIFIED;
 
+	options = parse_options_dup(options);
+	options = add_common_options(opts, options);
+	options = add_switch_branch_options(opts, options);
+	options = add_checkout_path_options(opts, options);
+
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 8c9edce52f..f46b3cb0a4 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -121,6 +121,22 @@ int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+struct option *parse_options_dup(const struct option *o)
+{
+	struct option *opts;
+	int nr = 0;
+
+	while (o && o->type != OPTION_END) {
+		nr++;
+		o++;
+	}
+
+	CALLOC_ARRAY(opts, nr + 1);
+	memcpy(opts, o - nr, sizeof(*o) * nr);
+	opts[nr].type = OPTION_END;
+	return opts;
+}
+
 struct option *parse_options_concat(struct option *a, struct option *b)
 {
 	struct option *ret;
diff --git a/parse-options.h b/parse-options.h
index 6c4fe2016d..584cb521f5 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -239,7 +239,8 @@ extern int parse_options_step(struct parse_opt_ctx_t *ctx,
 
 extern int parse_options_end(struct parse_opt_ctx_t *ctx);
 
-extern struct option *parse_options_concat(struct option *a, struct option *b);
+struct option *parse_options_dup(const struct option *a);
+struct option *parse_options_concat(struct option *a, struct option *b);
 
 /*----- some often used options -----*/
 extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* [PATCH v3 08/14] switch-branch: better names for -b and -B
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

The shortcut of these options do not make much sense when used with
switch-branch. And their descriptions are also tied to checkout
out. Move -b/-B to cmd_checkout() and new -c/-C with the same
functionality in cmd_switch_branch()

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 30 +++++++++++++++++++-----------
 1 file changed, 19 insertions(+), 11 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 7dc0f4d3f3..ceb635de36 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1268,14 +1268,10 @@ static struct option *add_common_options(struct checkout_opts *opts,
 	return newopts;
 }
 
-static struct option *add_switch_branch_options(struct checkout_opts *opts,
-						struct option *prevopts)
+static struct option *add_common_switch_branch_options(
+	struct checkout_opts *opts, struct option *prevopts)
 {
 	struct option options[] = {
-		OPT_STRING('b', NULL, &opts->new_branch, N_("branch"),
-			   N_("create and checkout a new branch")),
-		OPT_STRING('B', NULL, &opts->new_branch_force, N_("branch"),
-			   N_("create/reset and checkout a branch")),
 		OPT_BOOL('l', NULL, &opts->new_branch_log, N_("create reflog for new branch")),
 		OPT_BOOL(0, "detach", &opts->force_detach, N_("detach HEAD at named commit")),
 		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
@@ -1461,15 +1457,21 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 int cmd_checkout(int argc, const char **argv, const char *prefix)
 {
 	struct checkout_opts opts;
-	struct option *options = NULL;
+	struct option *options;
+	struct option checkout_options[] = {
+		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
+			   N_("create and checkout a new branch")),
+		OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"),
+			   N_("create/reset and checkout a branch")),
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_options(&opts, options);
+	options = add_common_switch_branch_options(&opts, options);
 	options = add_checkout_path_options(&opts, options);
 
 	ret = checkout_main(argc, argv, prefix, &opts,
@@ -1482,14 +1484,20 @@ int cmd_switch_branch(int argc, const char **argv, const char *prefix)
 {
 	struct checkout_opts opts;
 	struct option *options = NULL;
+	struct option switch_options[] = {
+		OPT_STRING('c', "create", &opts.new_branch, N_("branch"),
+			   N_("create and switch to a new branch")),
+		OPT_STRING('C', "force-create", &opts.new_branch_force, N_("branch"),
+			   N_("create/reset and switch to a new branch")),
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-	options = add_switch_branch_options(&opts, options);
+	options = add_common_switch_branch_options(&opts, options);
 
 	ret = checkout_main(argc, argv, prefix, &opts,
 			    options, switch_branch_usage);
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* [PATCH v3 09/14] switch-branch: stop accepting pathspec
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

This command is about switching branch (or creating a new one) and
should not accept pathspec. This helps simplify ambiguation
handling. The other two ("git checkout" and "git restore-files") of
course do accept pathspec as before.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index ceb635de36..880030e929 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -55,6 +55,7 @@ struct checkout_opts {
 	int ignore_other_worktrees;
 	int show_progress;
 	int dwim_new_local_branch;
+	int accept_pathspec;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -1089,10 +1090,16 @@ static int parse_branchname_arg(int argc, const char **argv,
 	if (!argc)
 		return 0;
 
+	if (!opts->accept_pathspec) {
+		if (argc > 1)
+			die(_("only one reference expected"));
+		has_dash_dash = 1; /* helps disambiguate */
+	}
+
 	arg = argv[0];
 	dash_dash_pos = -1;
 	for (i = 0; i < argc; i++) {
-		if (!strcmp(argv[i], "--")) {
+		if (opts->accept_pathspec && !strcmp(argv[i], "--")) {
 			dash_dash_pos = i;
 			break;
 		}
@@ -1167,7 +1174,7 @@ static int parse_branchname_arg(int argc, const char **argv,
 		 */
 		if (argc)
 			verify_non_filename(opts->prefix, arg);
-	} else {
+	} else if (opts->accept_pathspec) {
 		argcount++;
 		argv++;
 		argc--;
@@ -1468,6 +1475,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
+	opts.accept_pathspec = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1494,6 +1502,7 @@ int cmd_switch_branch(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
+	opts.accept_pathspec = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
@@ -1513,6 +1522,7 @@ int cmd_restore_files(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
+	opts.accept_pathspec = 1;
 
 	options = parse_options_dup(options);
 	options = add_common_options(&opts, options);
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* [PATCH v3 10/14] switch-branch: reject "do nothing" case
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

"git checkout" can be executed without any arguments. What it does is
not exactly great: it switches from HEAD to HEAD and showing worktree
modification as a side effect.

Make switch-branch reject this case. You have to either

- really switch a branch
- (explicitly) detach from the current branch
- create a new branch

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 880030e929..c7ae068d2c 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -56,6 +56,7 @@ struct checkout_opts {
 	int show_progress;
 	int dwim_new_local_branch;
 	int accept_pathspec;
+	int switch_branch_doing_nothing_not_ok;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -1233,6 +1234,13 @@ static int checkout_branch(struct checkout_opts *opts,
 		die(_("Cannot switch branch to a non-commit '%s'"),
 		    new_branch_info->name);
 
+	if (opts->switch_branch_doing_nothing_not_ok &&
+	    !new_branch_info->name &&
+	    !opts->new_branch &&
+	    !opts->new_branch_force &&
+	    !opts->force_detach)
+		die(_("nothing to do"));
+
 	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
 	    !opts->ignore_other_worktrees) {
 		int flag;
@@ -1475,6 +1483,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
+	opts.switch_branch_doing_nothing_not_ok = 0;
 	opts.accept_pathspec = 1;
 
 	options = parse_options_dup(checkout_options);
@@ -1503,6 +1512,7 @@ int cmd_switch_branch(int argc, const char **argv, const char *prefix)
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
 	opts.accept_pathspec = 0;
+	opts.switch_branch_doing_nothing_not_ok = 1;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* [PATCH v3 11/14] switch-branch: only allow explicit detached HEAD
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

"git checkout <commit>" will checkout the commit in question and
detach HEAD from the current branch. It is naturally a right thing to
do once you get git references. But detached HEAD is a scary concept
to new users because we show a lot of warnings and stuff, and it could
be hard to get out of (until you know better).

To keep switch-branch a bit more friendly to new users, we only allow
entering detached HEAD mode when --detach is given. "git
switch-branch" must take a branch (unless you create a new branch,
then of course switch-branch can take any commit-ish)

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index c7ae068d2c..fbfebba2d9 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -49,6 +49,7 @@ struct checkout_opts {
 	int merge;
 	int force;
 	int force_detach;
+	int implicit_detach;
 	int writeout_stage;
 	int overwrite_ignore;
 	int ignore_skipworktree;
@@ -1241,6 +1242,13 @@ static int checkout_branch(struct checkout_opts *opts,
 	    !opts->force_detach)
 		die(_("nothing to do"));
 
+	if (!opts->implicit_detach &&
+	    !opts->new_branch &&
+	    !opts->new_branch_force &&
+	    new_branch_info->name &&
+	    !new_branch_info->path)
+		die(_("a branch is expected, got %s"), new_branch_info->name);
+
 	if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
 	    !opts->ignore_other_worktrees) {
 		int flag;
@@ -1485,6 +1493,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_not_ok = 0;
 	opts.accept_pathspec = 1;
+	opts.implicit_detach = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1513,6 +1522,7 @@ int cmd_switch_branch(int argc, const char **argv, const char *prefix)
 	opts.dwim_new_local_branch = 1;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_not_ok = 1;
+	opts.implicit_detach = 0;
 
 	options = parse_options_dup(switch_options);
 	options = add_common_options(&opts, options);
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* [PATCH v3 12/14] restore-files: take tree-ish from --from option instead
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

This is another departure from 'git checkout' syntax, which uses -- to
separate ref and pathspec. The observation is restore-files (or "git
checkout ,, <pathspec>") is most often used to restore some files from
the index. If this is correct, we can simplify it by taking a way the
ref, so that we can write

    git restore-files some-file

without worrying about some-file being a ref and whether we need to do

    git restore-files -- some-file

for safety. If the source of the restore comes from a tree, it will be
in the form of an option with value, e.g.

    git restore-files --from=this-tree some-file

This is of course longer to type than using "--". But hopefully it
will not be used as often, and it is clearly easier to understand.

dwim_new_local_branch is no longer set (or unset) in cmd_restore_files()
because it's irrelevant because we don't really care about dwim-ing.
With accept_ref being unset, dwim can't happen.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 41 ++++++++++++++++++++++++++++++++++-------
 1 file changed, 34 insertions(+), 7 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index fbfebba2d9..7ff9951818 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -39,7 +39,7 @@ static const char * const switch_branch_usage[] = {
 };
 
 static const char * const restore_files_usage[] = {
-	N_("git restore-files [<options>] [<branch>] -- <file>..."),
+	N_("git restore-files [<options>] [--from=<branch>] <file>..."),
 	NULL,
 };
 
@@ -56,6 +56,7 @@ struct checkout_opts {
 	int ignore_other_worktrees;
 	int show_progress;
 	int dwim_new_local_branch;
+	int accept_ref;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_not_ok;
 
@@ -75,6 +76,7 @@ struct checkout_opts {
 	int branch_exists;
 	const char *prefix;
 	struct pathspec pathspec;
+	const char *from_treeish;
 	struct tree *source_tree;
 };
 
@@ -1337,6 +1339,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 {
 	struct branch_info new_branch_info;
 	int dwim_remotes_matched = 0;
+	int parseopt_flags = 0;
 
 	memset(&new_branch_info, 0, sizeof(new_branch_info));
 	opts->overwrite_ignore = 1;
@@ -1347,8 +1350,13 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 
 	opts->track = BRANCH_TRACK_UNSPECIFIED;
 
-	argc = parse_options(argc, argv, prefix, options, usagestr,
-			     PARSE_OPT_KEEP_DASHDASH);
+	if (!opts->accept_pathspec && !opts->accept_ref)
+		BUG("make up your mind, you need to take _something_");
+	if (opts->accept_pathspec && opts->accept_ref)
+		parseopt_flags = PARSE_OPT_KEEP_DASHDASH;
+
+	argc = parse_options(argc, argv, prefix, options,
+			     usagestr, parseopt_flags);
 
 	if (opts->show_progress < 0) {
 		if (opts->quiet)
@@ -1402,7 +1410,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 	 * including "last branch" syntax and DWIM-ery for names of
 	 * remote branches, erroring out for invalid or ambiguous cases.
 	 */
-	if (argc) {
+	if (argc && opts->accept_ref && opts->accept_pathspec) {
 		struct object_id rev;
 		int dwim_ok =
 			!opts->patch_mode &&
@@ -1414,6 +1422,18 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 					     &dwim_remotes_matched);
 		argv += n;
 		argc -= n;
+	} else if (!opts->accept_ref && opts->from_treeish) {
+		struct object_id rev;
+
+		if (get_oid_mb(opts->from_treeish, &rev))
+			die(_("could not resolve %s"), opts->from_treeish);
+
+		setup_new_branch_info_and_source_tree(&new_branch_info,
+						      opts, &rev,
+						      opts->from_treeish);
+
+		if (!opts->source_tree)
+			die(_("reference is not a tree: %s"), opts->from_treeish);
 	}
 
 	if (argc) {
@@ -1492,6 +1512,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
 	opts.switch_branch_doing_nothing_not_ok = 0;
+	opts.accept_ref = 1;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
 
@@ -1520,6 +1541,7 @@ int cmd_switch_branch(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	opts.dwim_new_local_branch = 1;
+	opts.accept_ref = 1;
 	opts.accept_pathspec = 0;
 	opts.switch_branch_doing_nothing_not_ok = 1;
 	opts.implicit_detach = 0;
@@ -1537,14 +1559,19 @@ int cmd_switch_branch(int argc, const char **argv, const char *prefix)
 int cmd_restore_files(int argc, const char **argv, const char *prefix)
 {
 	struct checkout_opts opts;
-	struct option *options = NULL;
+	struct option *options;
+	struct option restore_options[] = {
+		OPT_STRING(0, "from", &opts.from_treeish, "<tree-ish>",
+			   N_("where the checkout from")),
+		OPT_END()
+	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
-	opts.dwim_new_local_branch = 1;
+	opts.accept_ref = 0;
 	opts.accept_pathspec = 1;
 
-	options = parse_options_dup(options);
+	options = parse_options_dup(restore_options);
 	options = add_common_options(&opts, options);
 	options = add_checkout_path_options(&opts, options);
 
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* [PATCH v3 13/14] restore-files: make pathspec mandatory
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

"git restore-files" without arguments does not make much sense when
it's about restoring files (what files now?). We could default to
either

    git restore-files .

or

    git restore-files :/

Neither is intuitive. Make the user always give pathspec, force the
user to think the scope of restore they want.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/checkout.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 7ff9951818..961a90b1c0 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -59,6 +59,7 @@ struct checkout_opts {
 	int accept_ref;
 	int accept_pathspec;
 	int switch_branch_doing_nothing_not_ok;
+	int empty_pathspec_ok;
 
 	/*
 	 * If new checkout options are added, skip_merge_working_tree
@@ -1436,6 +1437,9 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 			die(_("reference is not a tree: %s"), opts->from_treeish);
 	}
 
+	if (opts->accept_pathspec && !opts->empty_pathspec_ok && !argc)
+		die(_("pathspec is required"));
+
 	if (argc) {
 		parse_pathspec(&opts->pathspec, 0,
 			       opts->patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
@@ -1515,6 +1519,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.accept_ref = 1;
 	opts.accept_pathspec = 1;
 	opts.implicit_detach = 1;
+	opts.empty_pathspec_ok = 1;
 
 	options = parse_options_dup(checkout_options);
 	options = add_common_options(&opts, options);
@@ -1570,6 +1575,7 @@ int cmd_restore_files(int argc, const char **argv, const char *prefix)
 	memset(&opts, 0, sizeof(opts));
 	opts.accept_ref = 0;
 	opts.accept_pathspec = 1;
+	opts.empty_pathspec_ok = 0;
 
 	options = parse_options_dup(restore_options);
 	options = add_common_options(&opts, options);
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* [PATCH v3 14/14] doc: promote "git switch-branch" and "git restore-files"
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

The two new commands "git switch-branch" and "git restore-files" are
added to avoid the confusion of one-command-do-all "git checkout" for
new users. They are also helpful to avoid ambiguation context.

For these reasons, promote them everywhere possible. This includes
documentation, suggestions/advice from other commands...

"git checkout" is also removed from "git help" (i.e. it's no longer
considered a commonly used command)

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config/advice.txt        | 10 +++--
 Documentation/config/checkout.txt      |  5 ++-
 Documentation/git-branch.txt           |  8 ++--
 Documentation/git-check-ref-format.txt |  2 +-
 Documentation/git-format-patch.txt     |  2 +-
 Documentation/git-merge-base.txt       |  2 +-
 Documentation/git-rebase.txt           |  2 +-
 Documentation/git-remote.txt           |  2 +-
 Documentation/git-rerere.txt           | 10 ++---
 Documentation/git-reset.txt            | 18 ++++-----
 Documentation/git-revert.txt           |  2 +-
 Documentation/git-stash.txt            |  6 +--
 Documentation/gitattributes.txt        |  2 +-
 Documentation/gitcli.txt               |  4 +-
 Documentation/gitcore-tutorial.txt     | 18 ++++-----
 Documentation/giteveryday.txt          | 24 ++++++------
 Documentation/githooks.txt             |  5 ++-
 Documentation/gittutorial-2.txt        |  2 +-
 Documentation/gittutorial.txt          |  4 +-
 Documentation/revisions.txt            |  2 +-
 Documentation/user-manual.txt          | 54 +++++++++++++-------------
 advice.c                               | 11 ++++--
 command-list.txt                       |  2 +-
 sha1-name.c                            |  2 +-
 wt-status.c                            |  2 +-
 25 files changed, 104 insertions(+), 97 deletions(-)

diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index 57fcd4c862..bffc503385 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -35,7 +35,8 @@ advice.*::
 		state in the output of linkgit:git-status[1], in
 		the template shown when writing commit messages in
 		linkgit:git-commit[1], and in the help message shown
-		by linkgit:git-checkout[1] when switching branch.
+		by linkgit:git-switch-branch[1] or
+		linkgit:git-checkout[1] when switching branch.
 	statusUoption::
 		Advise to consider using the `-u` option to linkgit:git-status[1]
 		when the command takes more than 2 seconds to enumerate untracked
@@ -55,9 +56,10 @@ advice.*::
 		your information is guessed from the system username and
 		domain name.
 	detachedHead::
-		Advice shown when you used linkgit:git-checkout[1] to
-		move to the detach HEAD state, to instruct how to create
-		a local branch after the fact.
+		Advice shown when you used
+		linkgit:git-switch-branch[1] or linkgit:git-checkout[1]
+		to move to the detach HEAD state, to instruct how to
+		create a local branch after the fact.
 	checkoutAmbiguousRemoteBranchName::
 		Advice shown when the argument to
 		linkgit:git-checkout[1] ambiguously resolves to a
diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt
index c4118fa196..81b0d47ced 100644
--- a/Documentation/config/checkout.txt
+++ b/Documentation/config/checkout.txt
@@ -8,8 +8,9 @@ checkout.defaultRemote::
 	disambiguation. The typical use-case is to set this to
 	`origin`.
 +
-Currently this is used by linkgit:git-checkout[1] when 'git checkout
-<something>' will checkout the '<something>' branch on another remote,
+Currently this is used by linkgit:git-switch-branch[1] and
+linkgit:git-checkout[1] when 'git checkout <something>'
+will checkout the '<something>' branch on another remote,
 and by linkgit:git-worktree[1] when 'git worktree add' refers to a
 remote branch. This setting might be used for other checkout-like
 commands or functionality in the future.
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index bf5316ffa9..1564df47d2 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -48,7 +48,7 @@ The command's second form creates a new branch head named <branchname>
 which points to the current `HEAD`, or <start-point> if given.
 
 Note that this will create the new branch, but it will not switch the
-working tree to it; use "git checkout <newbranch>" to switch to the
+working tree to it; use "git switch-branch <newbranch>" to switch to the
 new branch.
 
 When a local branch is started off a remote-tracking branch, Git sets up the
@@ -194,7 +194,7 @@ This option is only applicable in non-verbose mode.
 +
 This behavior is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
-want `git checkout` and `git branch` to always behave as if `--no-track`
+want `git switch-branch` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
 start-point is either a local or remote-tracking branch.
 
@@ -293,7 +293,7 @@ Start development from a known tag::
 $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
 $ cd my2.6
 $ git branch my2.6.14 v2.6.14   <1>
-$ git checkout my2.6.14
+$ git switch-branch my2.6.14
 ------------
 +
 <1> This step and the next one could be combined into a single step with
@@ -319,7 +319,7 @@ NOTES
 -----
 
 If you are creating a branch that you want to checkout immediately, it is
-easier to use the git checkout command with its `-b` option to create
+easier to use the "git switch-branch" command with its `-b` option to create
 a branch and check it out with a single command.
 
 The options `--contains`, `--no-contains`, `--merged` and `--no-merged`
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
index d9de992585..38c2169d7a 100644
--- a/Documentation/git-check-ref-format.txt
+++ b/Documentation/git-check-ref-format.txt
@@ -88,7 +88,7 @@ but it is explicitly forbidden at the beginning of a branch name).
 When run with `--branch` option in a repository, the input is first
 expanded for the ``previous checkout syntax''
 `@{-n}`.  For example, `@{-1}` is a way to refer the last thing that
-was checked out using "git checkout" operation. This option should be
+was checked out using "git switch-branch" operation. This option should be
 used by porcelains to accept this syntax anywhere a branch name is
 expected, so they can act as if you typed the branch name. As an
 exception note that, the ``previous checkout operation'' might result
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index aba4c5febe..0ceaa1173c 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -416,7 +416,7 @@ One way to test if your MUA is set up correctly is:
 * Apply it:
 
     $ git fetch <project> master:test-apply
-    $ git checkout test-apply
+    $ git switch-branch test-apply
     $ git reset --hard
     $ git am a.patch
 
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
index 9f07f4f6ed..1b25e5d530 100644
--- a/Documentation/git-merge-base.txt
+++ b/Documentation/git-merge-base.txt
@@ -149,7 +149,7 @@ instead.
 Discussion on fork-point mode
 -----------------------------
 
-After working on the `topic` branch created with `git checkout -b
+After working on the `topic` branch created with `git switch-branch -b
 topic origin/master`, the history of remote-tracking branch
 `origin/master` may have been rewound and rebuilt, leading to a
 history of this shape:
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 80793bad8d..fe10880633 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -17,7 +17,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 If <branch> is specified, 'git rebase' will perform an automatic
-`git checkout <branch>` before doing anything else.  Otherwise
+`git switch-branch <branch>` before doing anything else.  Otherwise
 it remains on the current branch.
 
 If <upstream> is not specified, the upstream configured in
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index 0cad37fb81..044bbdb27c 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -230,7 +230,7 @@ $ git branch -r
   staging/master
   staging/staging-linus
   staging/staging-next
-$ git checkout -b staging staging/master
+$ git switch-branch -b staging staging/master
 ...
 ------------
 
diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt
index df310d2a58..fe9d21b395 100644
--- a/Documentation/git-rerere.txt
+++ b/Documentation/git-rerere.txt
@@ -91,7 +91,7 @@ For such a test, you need to merge master and topic somehow.
 One way to do it is to pull master into the topic branch:
 
 ------------
-	$ git checkout topic
+	$ git switch-branch topic
 	$ git merge master
 
               o---*---o---+ topic
@@ -113,10 +113,10 @@ the upstream might have been advanced since the test merge `+`,
 in which case the final commit graph would look like this:
 
 ------------
-	$ git checkout topic
+	$ git switch-branch topic
 	$ git merge master
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch-branch master
 	$ git merge topic
 
               o---*---o---+---o---o topic
@@ -136,11 +136,11 @@ merges, you could blow away the test merge, and keep building on
 top of the tip before the test merge:
 
 ------------
-	$ git checkout topic
+	$ git switch-branch topic
 	$ git merge master
 	$ git reset --hard HEAD^ ;# rewind the test merge
 	$ ... work on both topic and master branches
-	$ git checkout master
+	$ git switch-branch master
 	$ git merge topic
 
               o---*---o-------o---o topic
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 2dac95c71a..ca46b4c967 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -149,9 +149,9 @@ See also the --amend option to linkgit:git-commit[1].
 Undo a commit, making it a topic branch::
 +
 ------------
-$ git branch topic/wip     <1>
-$ git reset --hard HEAD~3  <2>
-$ git checkout topic/wip   <3>
+$ git branch topic/wip          <1>
+$ git reset --hard HEAD~3       <2>
+$ git switch-branch topic/wip   <3>
 ------------
 +
 <1> You have made some commits, but realize they were premature
@@ -232,13 +232,13 @@ working tree are not in any shape to be committed yet, but you
 need to get to the other branch for a quick bugfix.
 +
 ------------
-$ git checkout feature ;# you were working in "feature" branch and
+$ git switch-branch feature ;# you were working in "feature" branch and
 $ work work work       ;# got interrupted
 $ git commit -a -m "snapshot WIP"                 <1>
-$ git checkout master
+$ git switch-branch master
 $ fix fix fix
 $ git commit ;# commit with real log
-$ git checkout feature
+$ git switch-branch feature
 $ git reset --soft HEAD^ ;# go back to WIP state  <2>
 $ git reset                                       <3>
 ------------
@@ -279,18 +279,18 @@ reset it while keeping the changes in your working tree.
 +
 ------------
 $ git tag start
-$ git checkout -b branch1
+$ git switch-branch -b branch1
 $ edit
 $ git commit ...                            <1>
 $ edit
-$ git checkout -b branch2                   <2>
+$ git switch-branch -b branch2              <2>
 $ git reset --keep start                    <3>
 ------------
 +
 <1> This commits your first edits in branch1.
 <2> In the ideal world, you could have realized that the earlier
     commit did not belong to the new topic when you created and switched
-    to branch2 (i.e. "git checkout -b branch2 start"), but nobody is
+    to branch2 (i.e. "git switch-branch -b branch2 start"), but nobody is
     perfect.
 <3> But you can use "reset --keep" to remove the unwanted commit after
     you switched to "branch2".
diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt
index 837707a8fd..07ef83866b 100644
--- a/Documentation/git-revert.txt
+++ b/Documentation/git-revert.txt
@@ -26,7 +26,7 @@ effect of some earlier commits (often only a faulty one).  If you want to
 throw away all uncommitted changes in your working directory, you
 should see linkgit:git-reset[1], particularly the `--hard` option.  If
 you want to extract specific files as they were in another commit, you
-should see linkgit:git-checkout[1], specifically the `git checkout
+should see linkgit:git-checkout[1], specifically the `git restore-files
 <commit> -- <filename>` syntax.  Take care with these alternatives as
 both will discard uncommitted changes in your working directory.
 
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 7ef8c47911..ea226979b1 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -235,12 +235,12 @@ return to your original branch to make the emergency fix, like this:
 +
 ----------------------------------------------------------------
 # ... hack hack hack ...
-$ git checkout -b my_wip
+$ git switch-branch -b my_wip
 $ git commit -a -m "WIP"
-$ git checkout master
+$ git switch-branch master
 $ edit emergency fix
 $ git commit -a -m "Fix in a hurry"
-$ git checkout my_wip
+$ git switch-branch my_wip
 $ git reset --soft HEAD^
 # ... continue hacking ...
 ----------------------------------------------------------------
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index b8392fc330..df62bd8019 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -112,7 +112,7 @@ Checking-out and checking-in
 
 These attributes affect how the contents stored in the
 repository are copied to the working tree files when commands
-such as 'git checkout' and 'git merge' run.  They also affect how
+such as 'git switch-branch' and 'git merge' run.  They also affect how
 Git stores the contents you prepare in the working tree in the
 repository upon 'git add' and 'git commit'.
 
diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt
index 592e06d839..491eb91c2e 100644
--- a/Documentation/gitcli.txt
+++ b/Documentation/gitcli.txt
@@ -47,8 +47,8 @@ disambiguating `--` at appropriate places.
    things:
 +
 --------------------------------
-$ git checkout -- *.c
-$ git checkout -- \*.c
+$ git restore-files -- *.c
+$ git restore-files -- \*.c
 --------------------------------
 +
 The former lets your shell expand the fileglob, and you are asking
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index e29a9effcc..49a8b5aa52 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -741,7 +741,7 @@ used earlier, and create a branch in it. You do that by simply just
 saying that you want to check out a new branch:
 
 ------------
-$ git checkout -b mybranch
+$ git branch mybranch
 ------------
 
 will create a new branch based at the current `HEAD` position, and switch
@@ -755,7 +755,7 @@ just telling 'git checkout' what the base of the checkout would be.
 In other words, if you have an earlier tag or branch, you'd just do
 
 ------------
-$ git checkout -b mybranch earlier-commit
+$ git switch-branch -b mybranch earlier-commit
 ------------
 
 and it would create the new branch `mybranch` at the earlier commit,
@@ -765,7 +765,7 @@ and check out the state at that time.
 You can always just jump back to your original `master` branch by doing
 
 ------------
-$ git checkout master
+$ git switch-branch master
 ------------
 
 (or any other branch-name, for that matter) and if you forget which
@@ -794,7 +794,7 @@ $ git branch <branchname> [startingpoint]
 
 which will simply _create_ the branch, but will not do anything further.
 You can then later -- once you decide that you want to actually develop
-on that branch -- switch to that branch with a regular 'git checkout'
+on that branch -- switch to that branch with a regular 'git switch-branch
 with the branchname as the argument.
 
 
@@ -808,7 +808,7 @@ being the same as the original `master` branch, let's make sure we're in
 that branch, and do some work there.
 
 ------------------------------------------------
-$ git checkout mybranch
+$ git switch-branch mybranch
 $ echo "Work, work, work" >>hello
 $ git commit -m "Some work." -i hello
 ------------------------------------------------
@@ -825,7 +825,7 @@ does some work in the original branch, and simulate that by going back
 to the master branch, and editing the same file differently there:
 
 ------------
-$ git checkout master
+$ git switch-branch master
 ------------
 
 Here, take a moment to look at the contents of `hello`, and notice how they
@@ -958,7 +958,7 @@ to the `master` branch. Let's go back to `mybranch`, and run
 'git merge' to get the "upstream changes" back to your branch.
 
 ------------
-$ git checkout mybranch
+$ git switch-branch mybranch
 $ git merge -m "Merge upstream changes." master
 ------------
 
@@ -1133,9 +1133,9 @@ Remember, before running 'git merge', our `master` head was at
 work." commit.
 
 ------------
-$ git checkout mybranch
+$ git switch-branch mybranch
 $ git reset --hard master^2
-$ git checkout master
+$ git switch-branch master
 $ git reset --hard master^
 ------------
 
diff --git a/Documentation/giteveryday.txt b/Documentation/giteveryday.txt
index 9f2528fc8c..9d64544bb9 100644
--- a/Documentation/giteveryday.txt
+++ b/Documentation/giteveryday.txt
@@ -80,9 +80,9 @@ $ git tag v2.43 <2>
 Create a topic branch and develop.::
 +
 ------------
-$ git checkout -b alsa-audio <1>
+$ git branch alsa-audio <1>
 $ edit/compile/test
-$ git checkout -- curses/ux_audio_oss.c <2>
+$ git restore-files -- curses/ux_audio_oss.c <2>
 $ git add curses/ux_audio_alsa.c <3>
 $ edit/compile/test
 $ git diff HEAD <4>
@@ -90,7 +90,7 @@ $ git commit -a -s <5>
 $ edit/compile/test
 $ git diff HEAD^ <6>
 $ git commit -a --amend <7>
-$ git checkout master <8>
+$ git switch-branch master <8>
 $ git merge alsa-audio <9>
 $ git log --since='3 days ago' <10>
 $ git log v2.43.. curses/ <11>
@@ -148,11 +148,11 @@ Clone the upstream and work on it.  Feed changes to upstream.::
 ------------
 $ git clone git://git.kernel.org/pub/scm/.../torvalds/linux-2.6 my2.6
 $ cd my2.6
-$ git checkout -b mine master <1>
+$ git switch-branch -b mine master <1>
 $ edit/compile/test; git commit -a -s <2>
 $ git format-patch master <3>
 $ git send-email --to="person <email@example.com>" 00*.patch <4>
-$ git checkout master <5>
+$ git switch-branch master <5>
 $ git pull <6>
 $ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <7>
 $ git ls-remote --heads http://git.kernel.org/.../jgarzik/libata-dev.git <8>
@@ -194,7 +194,7 @@ satellite$ edit/compile/test/commit
 satellite$ git push origin <4>
 
 mothership$ cd frotz
-mothership$ git checkout master
+mothership$ git switch-branch master
 mothership$ git merge satellite/master <5>
 ------------
 +
@@ -216,7 +216,7 @@ machine into the master branch.
 Branch off of a specific tag.::
 +
 ------------
-$ git checkout -b private2.6.14 v2.6.14 <1>
+$ git switch-branch -b private2.6.14 v2.6.14 <1>
 $ edit/compile/test; git commit -a
 $ git checkout master
 $ git cherry-pick v2.6.14..private2.6.14 <2>
@@ -274,14 +274,14 @@ $ mailx <3>
 & s 2 3 4 5 ./+to-apply
 & s 7 8 ./+hold-linus
 & q
-$ git checkout -b topic/one master
+$ git switch-branch -b topic/one master
 $ git am -3 -i -s ./+to-apply <4>
 $ compile/test
-$ git checkout -b hold/linus && git am -3 -i -s ./+hold-linus <5>
-$ git checkout topic/one && git rebase master <6>
-$ git checkout pu && git reset --hard next <7>
+$ git switch-branch -b hold/linus && git am -3 -i -s ./+hold-linus <5>
+$ git switch-branch topic/one && git rebase master <6>
+$ git switch-branch pu && git reset --hard next <7>
 $ git merge topic/one topic/two && git merge hold/linus <8>
-$ git checkout maint
+$ git switch-branch maint
 $ git cherry-pick master~4 <9>
 $ compile/test
 $ git tag -s -m "GIT 0.99.9x" v0.99.9x <10>
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 959044347e..3939ec774a 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -166,7 +166,7 @@ worktree.  The hook is given three parameters: the ref of the previous HEAD,
 the ref of the new HEAD (which may or may not have changed), and a flag
 indicating whether the checkout was a branch checkout (changing branches,
 flag=1) or a file checkout (retrieving a file from the index, flag=0).
-This hook cannot affect the outcome of `git checkout`.
+This hook cannot affect the outcome of `git switch-branch` or `git checkout`.
 
 It is also run after linkgit:git-clone[1], unless the `--no-checkout` (`-n`) option is
 used. The first parameter given to the hook is the null-ref, the second the
@@ -402,7 +402,8 @@ exit with a zero status.
 For example, the hook can simply run `git read-tree -u -m HEAD "$1"`
 in order to emulate `git fetch` that is run in the reverse direction
 with `git push`, as the two-tree form of `git read-tree -u -m` is
-essentially the same as `git checkout` that switches branches while
+essentially the same as `git switch-branch` or `git checkout`
+that switches branches while
 keeping the local changes in the working tree that do not interfere
 with the difference between the branches.
 
diff --git a/Documentation/gittutorial-2.txt b/Documentation/gittutorial-2.txt
index e0976f6017..1ec14da6b4 100644
--- a/Documentation/gittutorial-2.txt
+++ b/Documentation/gittutorial-2.txt
@@ -376,7 +376,7 @@ Changes to be committed:
 
 Changes not staged for commit:
   (use "git add <file>..." to update what will be committed)
-  (use "git checkout -- <file>..." to discard changes in working directory)
+  (use "git restore-files -- <file>..." to discard changes in working directory)
 
 	modified:   file.txt
 
diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt
index 242de31cb6..396e55c191 100644
--- a/Documentation/gittutorial.txt
+++ b/Documentation/gittutorial.txt
@@ -207,7 +207,7 @@ automatically.  The asterisk marks the branch you are currently on;
 type
 
 ------------------------------------------------
-$ git checkout experimental
+$ git switch-branch experimental
 ------------------------------------------------
 
 to switch to the experimental branch.  Now edit a file, commit the
@@ -216,7 +216,7 @@ change, and switch back to the master branch:
 ------------------------------------------------
 (edit file)
 $ git commit -a
-$ git checkout master
+$ git switch-branch master
 ------------------------------------------------
 
 Check that the change you made is no longer visible, since it was
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index 72daa20e76..f55502cd50 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -115,7 +115,7 @@ Here's an example to make it more clear:
 ------------------------------
 $ git config push.default current
 $ git config remote.pushdefault myfork
-$ git checkout -b mybranch origin/master
+$ git switch-branch -b mybranch origin/master
 
 $ git rev-parse --symbolic-full-name @{upstream}
 refs/remotes/origin/master
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index eff7890274..e3ff98077d 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -125,7 +125,7 @@ Create a new branch head pointing to one of these versions and check it
 out using linkgit:git-checkout[1]:
 
 ------------------------------------------------
-$ git checkout -b new v2.6.13
+$ git switch-branch -b new v2.6.13
 ------------------------------------------------
 
 The working directory then reflects the contents that the project had
@@ -282,10 +282,10 @@ a summary of the commands:
 	this command will fail with a warning.
 `git branch -D <branch>`::
 	delete the branch `<branch>` irrespective of its merged status.
-`git checkout <branch>`::
+`git switch-branch <branch>`::
 	make the current branch `<branch>`, updating the working
 	directory to reflect the version referenced by `<branch>`.
-`git checkout -b <new> <start-point>`::
+`git switch-branch -b <new> <start-point>`::
 	create a new branch `<new>` referencing `<start-point>`, and
 	check it out.
 
@@ -302,12 +302,12 @@ ref: refs/heads/master
 Examining an old version without creating a new branch
 ------------------------------------------------------
 
-The `git checkout` command normally expects a branch head, but will also
+The `git switch-branch` command normally expects a branch head, but will also
 accept an arbitrary commit; for example, you can check out the commit
 referenced by a tag:
 
 ------------------------------------------------
-$ git checkout v2.6.17
+$ git switch-branch v2.6.17
 Note: checking out 'v2.6.17'.
 
 You are in 'detached HEAD' state. You can look around, make experimental
@@ -317,7 +317,7 @@ state without impacting any branches by performing another checkout.
 If you want to create a new branch to retain commits you create, you may
 do so (now or later) by using -b with the checkout command again. Example:
 
-  git checkout -b new_branch_name
+  git switch-branch -b new_branch_name
 
 HEAD is now at 427abfa Linux v2.6.17
 ------------------------------------------------
@@ -373,7 +373,7 @@ You might want to build on one of these remote-tracking branches
 on a branch of your own, just as you would for a tag:
 
 ------------------------------------------------
-$ git checkout -b my-todo-copy origin/todo
+$ git switch-branch -b my-todo-copy origin/todo
 ------------------------------------------------
 
 You can also check out `origin/todo` directly to examine it or
@@ -1523,12 +1523,12 @@ Checking out an old version of a file
 
 In the process of undoing a previous bad change, you may find it
 useful to check out an older version of a particular file using
-linkgit:git-checkout[1].  We've used `git checkout` before to switch
+linkgit:git-checkout[1].  We've used `git switch-branch` before to switch
 branches, but it has quite different behavior if it is given a path
 name: the command
 
 -------------------------------------------------
-$ git checkout HEAD^ path/to/file
+$ git restore-files HEAD^ path/to/file
 -------------------------------------------------
 
 replaces path/to/file by the contents it had in the commit HEAD^, and
@@ -2211,8 +2211,8 @@ $ git branch --track release origin/master
 These can be easily kept up to date using linkgit:git-pull[1].
 
 -------------------------------------------------
-$ git checkout test && git pull
-$ git checkout release && git pull
+$ git switch-branch test && git pull
+$ git switch-branch release && git pull
 -------------------------------------------------
 
 Important note!  If you have any local changes in these branches, then
@@ -2264,7 +2264,7 @@ tested changes
 2) help future bug hunters that use `git bisect` to find problems
 
 -------------------------------------------------
-$ git checkout -b speed-up-spinlocks v2.6.35
+$ git switch-branch -b speed-up-spinlocks v2.6.35
 -------------------------------------------------
 
 Now you apply the patch(es), run some tests, and commit the change(s).  If
@@ -2279,7 +2279,7 @@ When you are happy with the state of this change, you can merge it into the
 "test" branch in preparation to make it public:
 
 -------------------------------------------------
-$ git checkout test && git merge speed-up-spinlocks
+$ git switch-branch test && git merge speed-up-spinlocks
 -------------------------------------------------
 
 It is unlikely that you would have any conflicts here ... but you might if you
@@ -2291,7 +2291,7 @@ see the value of keeping each patch (or patch series) in its own branch.  It
 means that the patches can be moved into the `release` tree in any order.
 
 -------------------------------------------------
-$ git checkout release && git merge speed-up-spinlocks
+$ git switch-branch release && git merge speed-up-spinlocks
 -------------------------------------------------
 
 After a while, you will have a number of branches, and despite the
@@ -2358,7 +2358,7 @@ Here are some of the scripts that simplify all this even further.
 
 case "$1" in
 test|release)
-	git checkout $1 && git pull . origin
+	git switch-branch $1 && git pull . origin
 	;;
 origin)
 	before=$(git rev-parse refs/remotes/origin/master)
@@ -2400,7 +2400,7 @@ test|release)
 		echo $1 already merged into $2 1>&2
 		exit 1
 	fi
-	git checkout $2 && git pull . $1
+	git switch-branch $2 && git pull . $1
 	;;
 *)
 	usage
@@ -2512,7 +2512,7 @@ Suppose that you create a branch `mywork` on a remote-tracking branch
 `origin`, and create some commits on top of it:
 
 -------------------------------------------------
-$ git checkout -b mywork origin
+$ git switch-branch -b mywork origin
 $ vi file.txt
 $ git commit
 $ vi otherfile.txt
@@ -2552,7 +2552,7 @@ commits without any merges, you may instead choose to use
 linkgit:git-rebase[1]:
 
 -------------------------------------------------
-$ git checkout mywork
+$ git switch-branch mywork
 $ git rebase origin
 -------------------------------------------------
 
@@ -3668,13 +3668,13 @@ change within the submodule, and then update the superproject to reference the
 new commit:
 
 -------------------------------------------------
-$ git checkout master
+$ git switch-branch master
 -------------------------------------------------
 
 or
 
 -------------------------------------------------
-$ git checkout -b fix-up
+$ git switch-branch -b fix-up
 -------------------------------------------------
 
 then
@@ -4194,7 +4194,7 @@ start.
 A good place to start is with the contents of the initial commit, with:
 
 ----------------------------------------------------
-$ git checkout e83c5163
+$ git switch-branch e83c5163
 ----------------------------------------------------
 
 The initial revision lays the foundation for almost everything Git has
@@ -4437,10 +4437,10 @@ Managing branches
 -----------------
 
 -----------------------------------------------
-$ git branch	     # list all local branches in this repo
-$ git checkout test  # switch working directory to branch "test"
-$ git branch new     # create branch "new" starting at current HEAD
-$ git branch -d new  # delete branch "new"
+$ git branch			# list all local branches in this repo
+$ git switch-branch test	# switch working directory to branch "test"
+$ git branch new		# create branch "new" starting at current HEAD
+$ git branch -d new		# delete branch "new"
 -----------------------------------------------
 
 Instead of basing a new branch on current HEAD (the default), use:
@@ -4456,7 +4456,7 @@ $ git branch new test~10 # ten commits before tip of branch "test"
 Create and switch to a new branch at the same time:
 
 -----------------------------------------------
-$ git checkout -b new v2.6.15
+$ git switch-branch -b new v2.6.15
 -----------------------------------------------
 
 Update and examine branches from the repository you cloned from:
@@ -4467,7 +4467,7 @@ $ git branch -r		# list
   origin/master
   origin/next
   ...
-$ git checkout -b masterwork origin/master
+$ git switch-branch -b masterwork origin/master
 -----------------------------------------------
 
 Fetch a branch from a different repository, and give it a new
diff --git a/advice.c b/advice.c
index 5f35656409..578ea31c7e 100644
--- a/advice.c
+++ b/advice.c
@@ -189,13 +189,16 @@ void NORETURN die_conclude_merge(void)
 void detach_advice(const char *new_name)
 {
 	const char *fmt =
-	_("Note: checking out '%s'.\n\n"
+	_("Note: checking out '%s'.\n"
+	"\n"
 	"You are in 'detached HEAD' state. You can look around, make experimental\n"
 	"changes and commit them, and you can discard any commits you make in this\n"
-	"state without impacting any branches by performing another checkout.\n\n"
+	"state without impacting any branches by performing another checkout.\n"
+	"\n"
 	"If you want to create a new branch to retain commits you create, you may\n"
-	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
-	"  git checkout -b <new-branch-name>\n\n");
+	"do so (now or later) by using -b with the checkout command again. Example:\n"
+	"\n"
+	"  git switch-branch -b <new-branch-name>\n\n");
 
 	fprintf(stderr, fmt, new_name);
 }
diff --git a/command-list.txt b/command-list.txt
index 4638802754..d1fb1d551d 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -59,7 +59,7 @@ git-cat-file                            plumbinginterrogators
 git-check-attr                          purehelpers
 git-check-ignore                        purehelpers
 git-check-mailmap                       purehelpers
-git-checkout                            mainporcelain           history
+git-checkout                            mainporcelain
 git-checkout-index                      plumbingmanipulators
 git-check-ref-format                    purehelpers
 git-cherry                              plumbinginterrogators          complete
diff --git a/sha1-name.c b/sha1-name.c
index faa60f69e3..4e4e14a45c 100644
--- a/sha1-name.c
+++ b/sha1-name.c
@@ -771,7 +771,7 @@ static int get_oid_basic(const char *str, int len, struct object_id *oid,
 	"because it will be ignored when you just specify 40-hex. These refs\n"
 	"may be created by mistake. For example,\n"
 	"\n"
-	"  git checkout -b $br $(git rev-parse ...)\n"
+	"  git switch-branch -b $br $(git rev-parse ...)\n"
 	"\n"
 	"where \"$br\" is somehow empty and a 40-hex ref is created. Please\n"
 	"examine these refs and maybe delete them. Turn this message off by\n"
diff --git a/wt-status.c b/wt-status.c
index a24711374c..c615cac607 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -224,7 +224,7 @@ static void wt_longstatus_print_dirty_header(struct wt_status *s,
 		status_printf_ln(s, c, _("  (use \"git add <file>...\" to update what will be committed)"));
 	else
 		status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" to update what will be committed)"));
-	status_printf_ln(s, c, _("  (use \"git checkout -- <file>...\" to discard changes in working directory)"));
+	status_printf_ln(s, c, _("  (use \"git restore-files <file>...\" to discard changes in working directory)"));
 	if (has_dirty_submodules)
 		status_printf_ln(s, c, _("  (commit or discard the untracked or modified content in submodules)"));
 	status_printf_ln(s, c, "%s", "");
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* [PATCH v3 07/14] checkout: split into switch-branch and restore-files
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

"git checkout" doing too many things is a source of confusion for many
users (and it even bites old timers sometimes). To rememdy that, the
command is now split in two: switch-branch and checkout-files. The
good old "git checkout" command is still here and will be until all
(or most of users) are sick of it.

See the new man pages for the final design of these commands. The
actual implementation though is still pretty much the same as "git
checkout". Following patches will adjust their behavior to match the
man pages.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 .gitignore                          |   2 +
 Documentation/git-checkout.txt      |   5 +
 Documentation/git-restore-files.txt | 167 ++++++++++++++++
 Documentation/git-switch-branch.txt | 289 ++++++++++++++++++++++++++++
 Makefile                            |   2 +
 builtin.h                           |   2 +
 builtin/checkout.c                  |  84 ++++++--
 command-list.txt                    |   2 +
 git.c                               |   2 +
 9 files changed, 543 insertions(+), 12 deletions(-)
 create mode 100644 Documentation/git-restore-files.txt
 create mode 100644 Documentation/git-switch-branch.txt

diff --git a/.gitignore b/.gitignore
index 0d77ea5894..c63dcb1427 100644
--- a/.gitignore
+++ b/.gitignore
@@ -143,6 +143,7 @@
 /git-request-pull
 /git-rerere
 /git-reset
+/git-restore-files
 /git-rev-list
 /git-rev-parse
 /git-revert
@@ -167,6 +168,7 @@
 /git-submodule
 /git-submodule--helper
 /git-svn
+/git-switch-branch
 /git-symbolic-ref
 /git-tag
 /git-unpack-file
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 25887a6087..25ec7f508f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -406,6 +406,11 @@ $ edit frotz
 $ git add frotz
 ------------
 
+SEE ALSO
+--------
+linkgit:git-switch-branch[1]
+linkgit:git-restore-files[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Documentation/git-restore-files.txt b/Documentation/git-restore-files.txt
new file mode 100644
index 0000000000..03c1250ad0
--- /dev/null
+++ b/Documentation/git-restore-files.txt
@@ -0,0 +1,167 @@
+git-restore-files(1)
+====================
+
+NAME
+----
+git-restore-files - Restore working tree files
+
+SYNOPSIS
+--------
+[verse]
+'git restore-files' [-f|--ours|--theirs|-m|--conflict=<style>] [--from=<tree-ish>] <pathspec>...
+'git restore-files' [--from=<tree-ish>] <pathspec>...
+'git restore-files' (-p|--patch) [--from=<tree-ish>] [<pathspec>...]
+
+DESCRIPTION
+-----------
+Updates files in the working tree to match the version in the index
+or the specified tree.
+
+'git restore-files' [--from=<tree-ish>] <pathspec>...::
+
+	Overwrite paths in the working tree by replacing with the
+	contents in the index or in the <tree-ish> (most often a
+	commit).  When a <tree-ish> is given, the paths that
+	match the <pathspec> are updated both in the index and in
+	the working tree.
++
+The index may contain unmerged entries because of a previous failed merge.
+By default, if you try to check out such an entry from the index, the
+checkout operation will fail and nothing will be checked out.
+Using `-f` will ignore these unmerged entries.  The contents from a
+specific side of the merge can be checked out of the index by
+using `--ours` or `--theirs`.  With `-m`, changes made to the working tree
+file can be discarded to re-create the original conflicted merge result.
+
+'git restore-files' (-p|--patch) [--from=<tree-ish>] [<pathspec>...]::
+	This is similar to the "check out paths to the working tree
+	from either the index or from a tree-ish" mode described
+	above, but lets you use the interactive interface to show
+	the "diff" output and choose which hunks to use in the
+	result.  See below for the description of `--patch` option.
+
+OPTIONS
+-------
+-q::
+--quiet::
+	Quiet, suppress feedback messages.
+
+--[no-]progress::
+	Progress status is reported on the standard error stream
+	by default when it is attached to a terminal, unless `--quiet`
+	is specified. This flag enables progress reporting even if not
+	attached to a terminal, regardless of `--quiet`.
+
+-f::
+--force::
+	Do not fail upon unmerged entries; instead, unmerged entries
+	are ignored.
+
+--ours::
+--theirs::
+	Check out stage #2 ('ours') or #3 ('theirs') for unmerged
+	paths.
++
+Note that during `git rebase` and `git pull --rebase`, 'ours' and
+'theirs' may appear swapped; `--ours` gives the version from the
+branch the changes are rebased onto, while `--theirs` gives the
+version from the branch that holds your work that is being rebased.
++
+This is because `rebase` is used in a workflow that treats the
+history at the remote as the shared canonical one, and treats the
+work done on the branch you are rebasing as the third-party work to
+be integrated, and you are temporarily assuming the role of the
+keeper of the canonical history during the rebase.  As the keeper of
+the canonical history, you need to view the history from the remote
+as `ours` (i.e. "our shared canonical history"), while what you did
+on your side branch as `theirs` (i.e. "one contributor's work on top
+of it").
+
+--ignore-skip-worktree-bits::
+	In sparse checkout mode, update only entries matched by
+	<paths> and sparse patterns in
+	$GIT_DIR/info/sparse-checkout. This option ignores the sparse
+	patterns and adds back any files in <paths>.
+
+-m::
+--merge::
+	When checking out paths from the index, this option lets you
+	recreate the conflicted merge in the specified paths.
+
+--conflict=<style>::
+	The same as --merge option above, but changes the way the
+	conflicting hunks are presented, overriding the
+	merge.conflictStyle configuration variable.  Possible values are
+	"merge" (default) and "diff3" (in addition to what is shown by
+	"merge" style, shows the original contents).
+
+-p::
+--patch::
+	Interactively select hunks in the difference between the
+	<tree-ish> (or the index, if unspecified) and the working
+	tree.  The chosen hunks are then applied in reverse to the
+	working tree (and if a <tree-ish> was specified, the index).
++
+This means that you can use `git restore-files -p` to selectively
+discard edits from your current working tree. See the ``Interactive
+Mode'' section of linkgit:git-add[1] to learn how to operate the
+`--patch` mode.
+
+--[no-]recurse-submodules::
+	Using --recurse-submodules will update the content of all initialized
+	submodules according to the commit recorded in the superproject. If
+	local modifications in a submodule would be overwritten the checkout
+	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
+	is used, the work trees of submodules will not be updated.
+	Just like linkgit:git-submodule[1], this will detach the
+	submodules HEAD.
+
+<tree-ish>::
+	Tree to checkout from (when paths are given). If not specified,
+	the index will be used.
+
+EXAMPLES
+--------
+
+. The following sequence checks out the `master` branch, reverts
+the `Makefile` to two revisions back, deletes hello.c by
+mistake, and gets it back from the index.
++
+------------
+$ git switch-branch master                    <1>
+$ git restore-files --from master~2 Makefile  <2>
+$ rm -f hello.c
+$ git restore-files hello.c                   <3>
+------------
++
+<1> switch branch
+<2> take a file out of another commit
+<3> restore hello.c from the index
++
+If you want to check out _all_ C source files out of the index,
+you can say
++
+------------
+$ git restore-files '*.c'
+------------
++
+Note the quotes around `*.c`.  The file `hello.c` will also be
+checked out, even though it is no longer in the working tree,
+because the file globbing is used to match entries in the index
+(not in the working tree by the shell).
++
+If you have an unfortunate branch that is named `hello.c`, this
+step would be confused as an instruction to switch to that branch.
+You should instead write:
++
+------------
+$ git restore-files hello.c
+------------
+
+SEE ALSO
+--------
+linkgit:git-checkout[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-switch-branch.txt b/Documentation/git-switch-branch.txt
new file mode 100644
index 0000000000..d5bf5cb37d
--- /dev/null
+++ b/Documentation/git-switch-branch.txt
@@ -0,0 +1,289 @@
+git-switch-branch(1)
+====================
+
+NAME
+----
+git-switch-branch - Switch branches
+
+SYNOPSIS
+--------
+[verse]
+'git switch-branch' [-q] [-f] [-m] <branch>
+'git switch-branch' [-q] [-f] [-m] --detach [<commit>]
+'git switch-branch' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
+
+DESCRIPTION
+-----------
+Switch to a specified branch and update files in the working tree to
+match it.
+
+'git switch-branch' <branch>::
+	To prepare for working on <branch>, switch to it by updating
+	the index and the files in the working tree. Local
+	modifications to the files in the working tree are kept, so
+	that they can be committed to the <branch>.
++
+If <branch> is not found but there does exist a tracking branch in
+exactly one remote (call it <remote>) with a matching name, treat as
+equivalent to
++
+------------
+$ git switch-branch -b <branch> --track <remote>/<branch>
+------------
++
+If the branch exists in multiple remotes and one of them is named by
+the `checkout.defaultRemote` configuration variable, we'll use that
+one for the purposes of disambiguation, even if the `<branch>` isn't
+unique across all remotes. Set it to
+e.g. `checkout.defaultRemote=origin` to always checkout remote
+branches from there if `<branch>` is ambiguous but exists on the
+'origin' remote. See also `checkout.defaultRemote` in
+linkgit:git-config[1].
+
+'git switch-branch' -c|-C <new_branch> [<start_point>]::
+
+	Specifying `-c` causes a new branch to be created as if
+	linkgit:git-branch[1] were called and then switched to. In
+	this case you can use the `--track` or `--no-track` options,
+	which will be passed to 'git branch'.  As a convenience,
+	`--track` without `-c` implies branch creation; see the
+	description of `--track` below.
++
+If `-C` is given, <new_branch> is created if it doesn't exist;
+otherwise, it is reset. This is the transactional equivalent of
++
+------------
+$ git branch -f <branch> [<start_point>]
+$ git switch-branch <branch>
+------------
++
+that is to say, the branch is not reset/created unless "git
+switch-branch" is successful.
+
+'git switch-branch' --detach [<commit>]::
+
+	Prepare to work on a unnamed branch on top of <commit> (see
+	"DETACHED HEAD" section), and updating the index and the files
+	in the working tree.  Local modifications to the files in the
+	working tree are kept, so that the resulting working tree will
+	be the state recorded in the commit plus the local
+	modifications.
++
+When the <commit> argument is a branch name, the `--detach` option can
+be used to detach HEAD at the tip of the branch (`git switch-branch
+<branch>` would check out that branch without detaching HEAD).
++
+Omitting <commit> detaches HEAD at the tip of the current branch.
+
+OPTIONS
+-------
+-q::
+--quiet::
+	Quiet, suppress feedback messages.
+
+--[no-]progress::
+	Progress status is reported on the standard error stream
+	by default when it is attached to a terminal, unless `--quiet`
+	is specified. This flag enables progress reporting even if not
+	attached to a terminal, regardless of `--quiet`.
+
+-f::
+--force::
+	Proceed even if the index or the working tree differs from
+	HEAD.  This is used to throw away local changes.
+
+-c <new_branch>::
+--create <new_branch>::
+	Create a new branch named <new_branch> and start it at
+	<start_point>; see linkgit:git-branch[1] for details.
+
+-C <new_branch>::
+--force-create <new_branch>::
+	Creates the branch <new_branch> and start it at <start_point>;
+	if it already exists, then reset it to <start_point>. This is
+	equivalent to running "git branch" with "-f"; see
+	linkgit:git-branch[1] for details.
+
+-t::
+--track::
+	When creating a new branch, set up "upstream" configuration. See
+	"--track" in linkgit:git-branch[1] for details.
++
+If no `-c` option is given, the name of the new branch will be derived
+from the remote-tracking branch, by looking at the local part of the
+refspec configured for the corresponding remote, and then stripping
+the initial part up to the "*".
+This would tell us to use "hack" as the local branch when branching
+off of "origin/hack" (or "remotes/origin/hack", or even
+"refs/remotes/origin/hack").  If the given name has no slash, or the above
+guessing results in an empty name, the guessing is aborted.  You can
+explicitly give a name with `-c` in such a case.
+
+--no-track::
+	Do not set up "upstream" configuration, even if the
+	branch.autoSetupMerge configuration variable is true.
+
+-l::
+	Create the new branch's reflog; see linkgit:git-branch[1] for
+	details.
+
+--detach::
+	Rather than checking out a branch to work on it, check out a
+	commit for inspection and discardable experiments.
+	This is the default behavior of "git checkout <commit>" when
+	<commit> is not a branch name.  See the "DETACHED HEAD" section
+	below for details.
+
+--orphan <new_branch>::
+	Create a new 'orphan' branch, named <new_branch>, started from
+	<start_point> and switch to it.  The first commit made on this
+	new branch will have no parents and it will be the root of a new
+	history totally disconnected from all the other branches and
+	commits.
++
+The index and the working tree are adjusted as if you had previously run
+"git checkout <start_point>".  This allows you to start a new history
+that records a set of paths similar to <start_point> by easily running
+"git commit -a" to make the root commit.
++
+This can be useful when you want to publish the tree from a commit
+without exposing its full history. You might want to do this to publish
+an open source branch of a project whose current tree is "clean", but
+whose full history contains proprietary or otherwise encumbered bits of
+code.
++
+If you want to start a disconnected history that records a set of paths
+that is totally different from the one of <start_point>, then you should
+clear the index and the working tree right after creating the orphan
+branch by running "git rm -rf ." from the top level of the working tree.
+Afterwards you will be ready to prepare your new files, repopulating the
+working tree, by copying them from elsewhere, extracting a tarball, etc.
+
+-m::
+--merge::
+	If you have local modifications to one or more files that are
+	different between the current branch and the branch to which
+	you are switching, the command refuses to switch branches in
+	order to preserve your modifications in context.  However,
+	with this option, a three-way merge between the current
+	branch, your working tree contents, and the new branch is
+	done, and you will be on the new branch.
++
+When a merge conflict happens, the index entries for conflicting
+paths are left unmerged, and you need to resolve the conflicts
+and mark the resolved paths with `git add` (or `git rm` if the merge
+should result in deletion of the path).
+
+--conflict=<style>::
+	The same as --merge option above, but changes the way the
+	conflicting hunks are presented, overriding the
+	merge.conflictStyle configuration variable.  Possible values are
+	"merge" (default) and "diff3" (in addition to what is shown by
+	"merge" style, shows the original contents).
+
+--ignore-other-worktrees::
+	`git switch-branch` refuses when the wanted ref is already
+	checked out by another worktree. This option makes it check
+	the ref out anyway. In other words, the ref can be held by
+	more than one worktree.
+
+--[no-]recurse-submodules::
+	Using --recurse-submodules will update the content of all initialized
+	submodules according to the commit recorded in the superproject. If
+	local modifications in a submodule would be overwritten the checkout
+	will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
+	is used, the work trees of submodules will not be updated.
+	Just like linkgit:git-submodule[1], this will detach the
+	submodules HEAD.
+
+<branch>::
+	Branch to checkout; if it refers to a branch (i.e., a name that,
+	when prepended with "refs/heads/", is a valid ref), then that
+	branch is checked out. Otherwise, if it refers to a valid
+	commit, your HEAD becomes "detached" and you are no longer on
+	any branch (see below for details).
++
+You can use the `"@{-N}"` syntax to refer to the N-th last
+branch/commit checked out using "git checkout" operation. You may
+also specify `-` which is synonymous to `"@{-1}`.
++
+As a special case, you may use `"A...B"` as a shortcut for the
+merge base of `A` and `B` if there is exactly one merge base. You can
+leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
+
+<new_branch>::
+	Name for the new branch.
+
+<start_point>::
+	The name of a commit at which to start the new branch; see
+	linkgit:git-branch[1] for details. Defaults to HEAD.
+
+DETACHED HEAD
+-------------
+include::detach-head.txt[]
+
+EXAMPLES
+--------
+
+. The following sequence checks out the `master` branch.
++
+------------
+$ git switch-branch master
+------------
++
+
+. After working in the wrong branch, switching to the correct
+branch would be done using:
++
+------------
+$ git switch-branch mytopic
+------------
++
+However, your "wrong" branch and correct "mytopic" branch may
+differ in files that you have modified locally, in which case
+the above checkout would fail like this:
++
+------------
+$ git switch-branch mytopic
+error: You have local changes to 'frotz'; not switching branches.
+------------
++
+You can give the `-m` flag to the command, which would try a
+three-way merge:
++
+------------
+$ git switch-branch -m mytopic
+Auto-merging frotz
+------------
++
+After this three-way merge, the local modifications are _not_
+registered in your index file, so `git diff` would show you what
+changes you made since the tip of the new branch.
+
+. When a merge conflict happens during switching branches with
+the `-m` option, you would see something like this:
++
+------------
+$ git switch-branch -m mytopic
+Auto-merging frotz
+ERROR: Merge conflict in frotz
+fatal: merge program failed
+------------
++
+At this point, `git diff` shows the changes cleanly merged as in
+the previous example, as well as the changes in the conflicted
+files.  Edit and resolve the conflict and mark it resolved with
+`git add` as usual:
++
+------------
+$ edit frotz
+$ git add frotz
+------------
+
+SEE ALSO
+--------
+linkgit:git-checkout[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 1a44c811aa..f035dbab9e 100644
--- a/Makefile
+++ b/Makefile
@@ -777,9 +777,11 @@ BUILT_INS += git-format-patch$X
 BUILT_INS += git-fsck-objects$X
 BUILT_INS += git-init$X
 BUILT_INS += git-merge-subtree$X
+BUILT_INS += git-restore-files$X
 BUILT_INS += git-show$X
 BUILT_INS += git-stage$X
 BUILT_INS += git-status$X
+BUILT_INS += git-switch-branch$X
 BUILT_INS += git-whatchanged$X
 
 # what 'all' will build and 'install' will install in gitexecdir,
diff --git a/builtin.h b/builtin.h
index 6538932e99..01ed43ea69 100644
--- a/builtin.h
+++ b/builtin.h
@@ -214,6 +214,7 @@ extern int cmd_remote_fd(int argc, const char **argv, const char *prefix);
 extern int cmd_repack(int argc, const char **argv, const char *prefix);
 extern int cmd_rerere(int argc, const char **argv, const char *prefix);
 extern int cmd_reset(int argc, const char **argv, const char *prefix);
+extern int cmd_restore_files(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
 extern int cmd_revert(int argc, const char **argv, const char *prefix);
@@ -227,6 +228,7 @@ extern int cmd_show_index(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
+extern int cmd_switch_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 764e1a83a1..7dc0f4d3f3 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -33,6 +33,16 @@ static const char * const checkout_usage[] = {
 	NULL,
 };
 
+static const char * const switch_branch_usage[] = {
+	N_("git switch-branch [<options>] [<branch>]"),
+	NULL,
+};
+
+static const char * const restore_files_usage[] = {
+	N_("git restore-files [<options>] [<branch>] -- <file>..."),
+	NULL,
+};
+
 struct checkout_opts {
 	int patch_mode;
 	int quiet;
@@ -1302,31 +1312,23 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,
 	return newopts;
 }
 
-int cmd_checkout(int argc, const char **argv, const char *prefix)
+static int checkout_main(int argc, const char **argv, const char *prefix,
+			 struct checkout_opts *opts, struct option *options,
+			 const char * const usagestr[])
 {
-	struct checkout_opts real_opts;
-	struct checkout_opts *opts = &real_opts;
 	struct branch_info new_branch_info;
 	int dwim_remotes_matched = 0;
-	struct option *options = NULL;
 
-	memset(opts, 0, sizeof(*opts));
 	memset(&new_branch_info, 0, sizeof(new_branch_info));
 	opts->overwrite_ignore = 1;
 	opts->prefix = prefix;
 	opts->show_progress = -1;
-	opts->dwim_new_local_branch = 1;
 
 	git_config(git_checkout_config, opts);
 
 	opts->track = BRANCH_TRACK_UNSPECIFIED;
 
-	options = parse_options_dup(options);
-	options = add_common_options(opts, options);
-	options = add_switch_branch_options(opts, options);
-	options = add_checkout_path_options(opts, options);
-
-	argc = parse_options(argc, argv, prefix, options, checkout_usage,
+	argc = parse_options(argc, argv, prefix, options, usagestr,
 			     PARSE_OPT_KEEP_DASHDASH);
 
 	if (opts->show_progress < 0) {
@@ -1455,3 +1457,61 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		return checkout_branch(opts, &new_branch_info);
 	}
 }
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+	struct checkout_opts opts;
+	struct option *options = NULL;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.dwim_new_local_branch = 1;
+
+	options = parse_options_dup(options);
+	options = add_common_options(&opts, options);
+	options = add_switch_branch_options(&opts, options);
+	options = add_checkout_path_options(&opts, options);
+
+	ret = checkout_main(argc, argv, prefix, &opts,
+			    options, checkout_usage);
+	FREE_AND_NULL(options);
+	return ret;
+}
+
+int cmd_switch_branch(int argc, const char **argv, const char *prefix)
+{
+	struct checkout_opts opts;
+	struct option *options = NULL;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.dwim_new_local_branch = 1;
+
+	options = parse_options_dup(options);
+	options = add_common_options(&opts, options);
+	options = add_switch_branch_options(&opts, options);
+
+	ret = checkout_main(argc, argv, prefix, &opts,
+			    options, switch_branch_usage);
+	FREE_AND_NULL(options);
+	return ret;
+}
+
+int cmd_restore_files(int argc, const char **argv, const char *prefix)
+{
+	struct checkout_opts opts;
+	struct option *options = NULL;
+	int ret;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.dwim_new_local_branch = 1;
+
+	options = parse_options_dup(options);
+	options = add_common_options(&opts, options);
+	options = add_checkout_path_options(&opts, options);
+
+	ret = checkout_main(argc, argv, prefix, &opts,
+			    options, restore_files_usage);
+	FREE_AND_NULL(options);
+	return ret;
+}
diff --git a/command-list.txt b/command-list.txt
index 3a9af104b5..4638802754 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -151,6 +151,7 @@ git-replace                             ancillarymanipulators           complete
 git-request-pull                        foreignscminterface             complete
 git-rerere                              ancillaryinterrogators
 git-reset                               mainporcelain           worktree
+git-restore-files                       mainporcelain           worktree
 git-revert                              mainporcelain
 git-rev-list                            plumbinginterrogators
 git-rev-parse                           plumbinginterrogators
@@ -171,6 +172,7 @@ git-status                              mainporcelain           info
 git-stripspace                          purehelpers
 git-submodule                           mainporcelain
 git-svn                                 foreignscminterface
+git-switch-branch                       mainporcelain           history
 git-symbolic-ref                        plumbingmanipulators
 git-tag                                 mainporcelain           history
 git-unpack-file                         plumbinginterrogators
diff --git a/git.c b/git.c
index 2f604a41ea..a2be6c3eb5 100644
--- a/git.c
+++ b/git.c
@@ -542,6 +542,7 @@ static struct cmd_struct commands[] = {
 	{ "replace", cmd_replace, RUN_SETUP },
 	{ "rerere", cmd_rerere, RUN_SETUP },
 	{ "reset", cmd_reset, RUN_SETUP },
+	{ "restore-files", cmd_restore_files, RUN_SETUP | NEED_WORK_TREE },
 	{ "rev-list", cmd_rev_list, RUN_SETUP | NO_PARSEOPT },
 	{ "rev-parse", cmd_rev_parse, NO_PARSEOPT },
 	{ "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
@@ -557,6 +558,7 @@ static struct cmd_struct commands[] = {
 	{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 	{ "stripspace", cmd_stripspace },
 	{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
+	{ "switch-branch", cmd_switch_branch, RUN_SETUP | NEED_WORK_TREE },
 	{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
 	{ "tag", cmd_tag, RUN_SETUP | DELAY_PAGER_CONFIG },
 	{ "unpack-file", cmd_unpack_file, RUN_SETUP | NO_PARSEOPT },
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* [PATCH v3 02/14] git-checkout.txt: split detached head section out
From: Nguyễn Thái Ngọc Duy @ 2018-11-29 21:58 UTC (permalink / raw)
  To: pclouds; +Cc: avarab, git, gitster, sbeller, t.gummerer, sxenos
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>

This is to be reused by the coming git-switch-branch.txt man page
which also deals with detached HEAD.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/detach-head.txt  | 132 ++++++++++++++++++++++++++++++++
 Documentation/git-checkout.txt | 133 +--------------------------------
 2 files changed, 133 insertions(+), 132 deletions(-)
 create mode 100644 Documentation/detach-head.txt

diff --git a/Documentation/detach-head.txt b/Documentation/detach-head.txt
new file mode 100644
index 0000000000..bb6f5d7843
--- /dev/null
+++ b/Documentation/detach-head.txt
@@ -0,0 +1,132 @@
+HEAD normally refers to a named branch (e.g. 'master'). Meanwhile, each
+branch refers to a specific commit. Let's look at a repo with three
+commits, one of them tagged, and with branch 'master' checked out:
+
+------------
+           HEAD (refers to branch 'master')
+            |
+            v
+a---b---c  branch 'master' (refers to commit 'c')
+    ^
+    |
+  tag 'v2.0' (refers to commit 'b')
+------------
+
+When a commit is created in this state, the branch is updated to refer to
+the new commit. Specifically, 'git commit' creates a new commit 'd', whose
+parent is commit 'c', and then updates branch 'master' to refer to new
+commit 'd'. HEAD still refers to branch 'master' and so indirectly now refers
+to commit 'd':
+
+------------
+$ edit; git add; git commit
+
+               HEAD (refers to branch 'master')
+                |
+                v
+a---b---c---d  branch 'master' (refers to commit 'd')
+    ^
+    |
+  tag 'v2.0' (refers to commit 'b')
+------------
+
+It is sometimes useful to be able to checkout a commit that is not at
+the tip of any named branch, or even to create a new commit that is not
+referenced by a named branch. Let's look at what happens when we
+checkout commit 'b' (here we show two ways this may be done):
+
+------------
+$ git checkout v2.0  # or
+$ git checkout master^^
+
+   HEAD (refers to commit 'b')
+    |
+    v
+a---b---c---d  branch 'master' (refers to commit 'd')
+    ^
+    |
+  tag 'v2.0' (refers to commit 'b')
+------------
+
+Notice that regardless of which checkout command we use, HEAD now refers
+directly to commit 'b'. This is known as being in detached HEAD state.
+It means simply that HEAD refers to a specific commit, as opposed to
+referring to a named branch. Let's see what happens when we create a commit:
+
+------------
+$ edit; git add; git commit
+
+     HEAD (refers to commit 'e')
+      |
+      v
+      e
+     /
+a---b---c---d  branch 'master' (refers to commit 'd')
+    ^
+    |
+  tag 'v2.0' (refers to commit 'b')
+------------
+
+There is now a new commit 'e', but it is referenced only by HEAD. We can
+of course add yet another commit in this state:
+
+------------
+$ edit; git add; git commit
+
+         HEAD (refers to commit 'f')
+          |
+          v
+      e---f
+     /
+a---b---c---d  branch 'master' (refers to commit 'd')
+    ^
+    |
+  tag 'v2.0' (refers to commit 'b')
+------------
+
+In fact, we can perform all the normal Git operations. But, let's look
+at what happens when we then checkout master:
+
+------------
+$ git checkout master
+
+               HEAD (refers to branch 'master')
+      e---f     |
+     /          v
+a---b---c---d  branch 'master' (refers to commit 'd')
+    ^
+    |
+  tag 'v2.0' (refers to commit 'b')
+------------
+
+It is important to realize that at this point nothing refers to commit
+'f'. Eventually commit 'f' (and by extension commit 'e') will be deleted
+by the routine Git garbage collection process, unless we create a reference
+before that happens. If we have not yet moved away from commit 'f',
+any of these will create a reference to it:
+
+------------
+$ git checkout -b foo   <1>
+$ git branch foo        <2>
+$ git tag foo           <3>
+------------
+
+<1> creates a new branch 'foo', which refers to commit 'f', and then
+updates HEAD to refer to branch 'foo'. In other words, we'll no longer
+be in detached HEAD state after this command.
+
+<2> similarly creates a new branch 'foo', which refers to commit 'f',
+but leaves HEAD detached.
+
+<3> creates a new tag 'foo', which refers to commit 'f',
+leaving HEAD detached.
+
+If we have moved away from commit 'f', then we must first recover its object
+name (typically by using git reflog), and then we can create a reference to
+it. For example, to see the last two commits to which HEAD referred, we
+can use either of these commands:
+
+------------
+$ git reflog -2 HEAD # or
+$ git log -g -2 HEAD
+------------
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 65bd1bc50d..25887a6087 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -306,138 +306,7 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
 DETACHED HEAD
 -------------
-HEAD normally refers to a named branch (e.g. 'master'). Meanwhile, each
-branch refers to a specific commit. Let's look at a repo with three
-commits, one of them tagged, and with branch 'master' checked out:
-
-------------
-           HEAD (refers to branch 'master')
-            |
-            v
-a---b---c  branch 'master' (refers to commit 'c')
-    ^
-    |
-  tag 'v2.0' (refers to commit 'b')
-------------
-
-When a commit is created in this state, the branch is updated to refer to
-the new commit. Specifically, 'git commit' creates a new commit 'd', whose
-parent is commit 'c', and then updates branch 'master' to refer to new
-commit 'd'. HEAD still refers to branch 'master' and so indirectly now refers
-to commit 'd':
-
-------------
-$ edit; git add; git commit
-
-               HEAD (refers to branch 'master')
-                |
-                v
-a---b---c---d  branch 'master' (refers to commit 'd')
-    ^
-    |
-  tag 'v2.0' (refers to commit 'b')
-------------
-
-It is sometimes useful to be able to checkout a commit that is not at
-the tip of any named branch, or even to create a new commit that is not
-referenced by a named branch. Let's look at what happens when we
-checkout commit 'b' (here we show two ways this may be done):
-
-------------
-$ git checkout v2.0  # or
-$ git checkout master^^
-
-   HEAD (refers to commit 'b')
-    |
-    v
-a---b---c---d  branch 'master' (refers to commit 'd')
-    ^
-    |
-  tag 'v2.0' (refers to commit 'b')
-------------
-
-Notice that regardless of which checkout command we use, HEAD now refers
-directly to commit 'b'. This is known as being in detached HEAD state.
-It means simply that HEAD refers to a specific commit, as opposed to
-referring to a named branch. Let's see what happens when we create a commit:
-
-------------
-$ edit; git add; git commit
-
-     HEAD (refers to commit 'e')
-      |
-      v
-      e
-     /
-a---b---c---d  branch 'master' (refers to commit 'd')
-    ^
-    |
-  tag 'v2.0' (refers to commit 'b')
-------------
-
-There is now a new commit 'e', but it is referenced only by HEAD. We can
-of course add yet another commit in this state:
-
-------------
-$ edit; git add; git commit
-
-	 HEAD (refers to commit 'f')
-	  |
-	  v
-      e---f
-     /
-a---b---c---d  branch 'master' (refers to commit 'd')
-    ^
-    |
-  tag 'v2.0' (refers to commit 'b')
-------------
-
-In fact, we can perform all the normal Git operations. But, let's look
-at what happens when we then checkout master:
-
-------------
-$ git checkout master
-
-               HEAD (refers to branch 'master')
-      e---f     |
-     /          v
-a---b---c---d  branch 'master' (refers to commit 'd')
-    ^
-    |
-  tag 'v2.0' (refers to commit 'b')
-------------
-
-It is important to realize that at this point nothing refers to commit
-'f'. Eventually commit 'f' (and by extension commit 'e') will be deleted
-by the routine Git garbage collection process, unless we create a reference
-before that happens. If we have not yet moved away from commit 'f',
-any of these will create a reference to it:
-
-------------
-$ git checkout -b foo   <1>
-$ git branch foo        <2>
-$ git tag foo           <3>
-------------
-
-<1> creates a new branch 'foo', which refers to commit 'f', and then
-updates HEAD to refer to branch 'foo'. In other words, we'll no longer
-be in detached HEAD state after this command.
-
-<2> similarly creates a new branch 'foo', which refers to commit 'f',
-but leaves HEAD detached.
-
-<3> creates a new tag 'foo', which refers to commit 'f',
-leaving HEAD detached.
-
-If we have moved away from commit 'f', then we must first recover its object
-name (typically by using git reflog), and then we can create a reference to
-it. For example, to see the last two commits to which HEAD referred, we
-can use either of these commands:
-
-------------
-$ git reflog -2 HEAD # or
-$ git log -g -2 HEAD
-------------
+include::detach-head.txt[]
 
 ARGUMENT DISAMBIGUATION
 -----------------------
-- 
2.20.0.rc1.380.g3eb999425c.dirty


^ permalink raw reply related

* Re: [PATCH/RFC v3 00/14] Introduce new commands switch-branch and restore-files
From: Ævar Arnfjörð Bjarmason @ 2018-11-29 23:05 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy
  Cc: git, gitster, sbeller, t.gummerer, sxenos, Elijah Newren,
	Dan Fabulich
In-Reply-To: <20181129215850.7278-1-pclouds@gmail.com>


On Thu, Nov 29 2018, Nguyễn Thái Ngọc Duy wrote:

> v3 sees switch-branch go back to switch-branch (in v2 it was
> checkout-branch). checkout-files is also renamed restore-files (v1 was
> restore-paths). Hopefully we won't see another rename.
>
> I'll try to summarize the differences between the new commands and
> 'git checkout' down here, but you're welcome to just head to 07/14 and
> read the new man pages.
>
> 'git switch-branch'
>
> - does not "do nothing", you have to either switch branch, create a
>   new branch, or detach. "git switch-branch" with no arguments is
>   rejected.
>
> - implicit detaching is rejected. If you need to detach, you need to
>   give --detach. Or stick to 'git checkout'.
>
> - -b/-B is renamed to -c/-C with long option names
>
> - of course does not accept pathspec
>
> 'git restore-files'
>
> - takes a ref from --from argument, not as a free ref. As a result,
>   '--' is no longer needed. All non-option arguments are pathspec
>
> - pathspec is mandatory, you can't do "git restore-files" without any
>   pathspec.
>
> - I just remember -p which is allowed to take no pathspec :( I'll fix
>   it later.
>
> - Two more fancy features (the "git checkout --index" being the
>   default mode and the backup log for accidental overwrites) are of
>   course still missing. But they are coming.
>
> I did not go replace "detached HEAD" with "unnamed branch" (or "no
> branch") everywhere because I think a unique term is still good to
> refer to this concept. Or maybe "no branch" is good enough. I dunno.

I finally tracked down
https://redfin.engineering/two-commits-that-wrecked-the-user-experience-of-git-f0075b77eab1
which I'd remembered reading and couldn't find again in these
discussions. Re-reading it while one may not 100% agree with the
author's opinion, it's an interesting rabbit hole.

I also didn't know about EasyGit, or that Elijah Newren had written
it. I haven't seen him chime in on this series, and would be interested
to see what he thinks about it.

Re the naming question in
https://public-inbox.org/git/87o9abzv46.fsf@evledraar.gmail.com/ &
seeing that eg-switch exists, I wonder if just s/switch-branch/switch/
makes more sense.

Assuming greenfield development (which we definitely don't have), I
don't like the "restore-files" name, but the alternative that makes
sense is "checkout". Then this "--from" argument could become "git
checkout-tree <treeish> -- <pathspec>", and we'd have:

    git switch <branchish>
    git checkout <pathspec>
    git checkout-tree <treeish> -- <pathspec>

Or maybe that sucks, anyway what I was going to suggest is *if* others
think that made sense as a "if we designed git today" endgame whether we
could have an opt-in setting where you set e.g. core.uiVersion=3 (in
anticipation of Git 3.0) and you'd get that behavior. There could be
some other setting where core.uiVersion would use the old behavior (or
with another setting, only warn) if we weren't connected to a terminal.

I.e. I'm thinking of this as step #2 in a #3 step series. Where this is
the fully backwards compatible UI improvement, but someone who'd
e.g. use EasyGit or didn't have backwards compatibility concerns could
enable step #3 and opt-in to a mode where we'd fixed a bunch of UI warts
in a backwards-incompatible way.

What would that mode look like? I'd to work on piling that on top of
this :)

^ permalink raw reply

* Re: [PATCH/RFC v3 00/14] Introduce new commands switch-branch and restore-files
From: Ævar Arnfjörð Bjarmason @ 2018-11-29 23:18 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy
  Cc: git, gitster, sbeller, t.gummerer, sxenos, Elijah Newren,
	Dan Fabulich, Santiago Perez De Rosso, Daniel Jackson
In-Reply-To: <87pnunxz5i.fsf@evledraar.gmail.com>


On Thu, Nov 29 2018, Ævar Arnfjörð Bjarmason wrote:

> On Thu, Nov 29 2018, Nguyễn Thái Ngọc Duy wrote:
>
>> v3 sees switch-branch go back to switch-branch (in v2 it was
>> checkout-branch). checkout-files is also renamed restore-files (v1 was
>> restore-paths). Hopefully we won't see another rename.
>>
>> I'll try to summarize the differences between the new commands and
>> 'git checkout' down here, but you're welcome to just head to 07/14 and
>> read the new man pages.
>>
>> 'git switch-branch'
>>
>> - does not "do nothing", you have to either switch branch, create a
>>   new branch, or detach. "git switch-branch" with no arguments is
>>   rejected.
>>
>> - implicit detaching is rejected. If you need to detach, you need to
>>   give --detach. Or stick to 'git checkout'.
>>
>> - -b/-B is renamed to -c/-C with long option names
>>
>> - of course does not accept pathspec
>>
>> 'git restore-files'
>>
>> - takes a ref from --from argument, not as a free ref. As a result,
>>   '--' is no longer needed. All non-option arguments are pathspec
>>
>> - pathspec is mandatory, you can't do "git restore-files" without any
>>   pathspec.
>>
>> - I just remember -p which is allowed to take no pathspec :( I'll fix
>>   it later.
>>
>> - Two more fancy features (the "git checkout --index" being the
>>   default mode and the backup log for accidental overwrites) are of
>>   course still missing. But they are coming.
>>
>> I did not go replace "detached HEAD" with "unnamed branch" (or "no
>> branch") everywhere because I think a unique term is still good to
>> refer to this concept. Or maybe "no branch" is good enough. I dunno.
>
> I finally tracked down
> https://redfin.engineering/two-commits-that-wrecked-the-user-experience-of-git-f0075b77eab1
> which I'd remembered reading and couldn't find again in these
> discussions. Re-reading it while one may not 100% agree with the
> author's opinion, it's an interesting rabbit hole.
>
> I also didn't know about EasyGit, or that Elijah Newren had written
> it. I haven't seen him chime in on this series, and would be interested
> to see what he thinks about it.
>
> Re the naming question in
> https://public-inbox.org/git/87o9abzv46.fsf@evledraar.gmail.com/ &
> seeing that eg-switch exists, I wonder if just s/switch-branch/switch/
> makes more sense.
>
> Assuming greenfield development (which we definitely don't have), I
> don't like the "restore-files" name, but the alternative that makes
> sense is "checkout". Then this "--from" argument could become "git
> checkout-tree <treeish> -- <pathspec>", and we'd have:
>
>     git switch <branchish>
>     git checkout <pathspec>
>     git checkout-tree <treeish> -- <pathspec>
>
> Or maybe that sucks, anyway what I was going to suggest is *if* others
> think that made sense as a "if we designed git today" endgame whether we
> could have an opt-in setting where you set e.g. core.uiVersion=3 (in
> anticipation of Git 3.0) and you'd get that behavior. There could be
> some other setting where core.uiVersion would use the old behavior (or
> with another setting, only warn) if we weren't connected to a terminal.
>
> I.e. I'm thinking of this as step #2 in a #3 step series. Where this is
> the fully backwards compatible UI improvement, but someone who'd
> e.g. use EasyGit or didn't have backwards compatibility concerns could
> enable step #3 and opt-in to a mode where we'd fixed a bunch of UI warts
> in a backwards-incompatible way.
>
> What would that mode look like? I'd to work on piling that on top of
> this :)

(Digging some more)

There's also more interesting prior art at https://gitless.com/ (CC'd
authors) and 2x research papers linked at the bottom of that page which
were briefly discussed on-list before:
https://public-inbox.org/git/20160930191413.002049b94b3908b15881b77f@domain007.com/

The "gitless" UI has a "gl checkout" which just takes paths as I was
musing about above (and that Redfin post also suggests).

^ permalink raw reply

* Re: [PATCH/RFC v3 00/14] Introduce new commands switch-branch and restore-files
From: Dan Fabulich @ 2018-11-29 23:37 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Nguyễn Thái Ngọc Duy, git, gitster, sbeller,
	t.gummerer, sxenos, Elijah Newren
In-Reply-To: <87pnunxz5i.fsf@evledraar.gmail.com>

Assuming the great day has come to think about this, one thing I'd love to do is to unify the name of the index/stage/cache in command-line parameters and the documentation.

The index/stage/cache should have one canonical name, and the documentation should support that consistently. My taste is to call it the "stage," but references to --index, --keep-index, --no-index, etc. are all over the code, as well as legacy references to "--cached".

* You can 'git rm --cached myfile' but you can't 'git rm --staged myfile' or 'git rm --index myfile'.

* You can 'git diff --no-index' or you can 'git diff --cached' with 'git diff --staged' as a synonym, deprioritized in the documentation ("--staged is a synonym of --cached"). But you can't 'git diff --index' or 'git diff --no-stage' or 'git diff --no-cache'.

* You can 'git stage' but 'git help stage' is only one line long, declaring that it's a synonym for 'git add'. 'add' appears in the 'git help' common commands, but not 'git stage'. There's no built-in 'git unstage', and certainly no appetite for 'git index' or 'git cache'.

* Not to mention all of the plumbing commands: checkout-index, diff-index, index-pack, merge-index, show-index, and update-index.

My understanding based on historical threads is that changes like this would be unwelcome, even just to add synonyms and standardize the documentation around "stage" as the term (leaving the plumbing commands alone), but if documentation+synonym patches are welcome here, I'd be very enthusiastic.

-Dan

> On Nov 29, 2018, at 3:05 PM, Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:
> 
> 
> On Thu, Nov 29 2018, Nguyễn Thái Ngọc Duy wrote:
> 
>> v3 sees switch-branch go back to switch-branch (in v2 it was
>> checkout-branch). checkout-files is also renamed restore-files (v1 was
>> restore-paths). Hopefully we won't see another rename.
>> 
>> I'll try to summarize the differences between the new commands and
>> 'git checkout' down here, but you're welcome to just head to 07/14 and
>> read the new man pages.
>> 
>> 'git switch-branch'
>> 
>> - does not "do nothing", you have to either switch branch, create a
>>  new branch, or detach. "git switch-branch" with no arguments is
>>  rejected.
>> 
>> - implicit detaching is rejected. If you need to detach, you need to
>>  give --detach. Or stick to 'git checkout'.
>> 
>> - -b/-B is renamed to -c/-C with long option names
>> 
>> - of course does not accept pathspec
>> 
>> 'git restore-files'
>> 
>> - takes a ref from --from argument, not as a free ref. As a result,
>>  '--' is no longer needed. All non-option arguments are pathspec
>> 
>> - pathspec is mandatory, you can't do "git restore-files" without any
>>  pathspec.
>> 
>> - I just remember -p which is allowed to take no pathspec :( I'll fix
>>  it later.
>> 
>> - Two more fancy features (the "git checkout --index" being the
>>  default mode and the backup log for accidental overwrites) are of
>>  course still missing. But they are coming.
>> 
>> I did not go replace "detached HEAD" with "unnamed branch" (or "no
>> branch") everywhere because I think a unique term is still good to
>> refer to this concept. Or maybe "no branch" is good enough. I dunno.
> 
> I finally tracked down
> https://redfin.engineering/two-commits-that-wrecked-the-user-experience-of-git-f0075b77eab1
> which I'd remembered reading and couldn't find again in these
> discussions. Re-reading it while one may not 100% agree with the
> author's opinion, it's an interesting rabbit hole.
> 
> I also didn't know about EasyGit, or that Elijah Newren had written
> it. I haven't seen him chime in on this series, and would be interested
> to see what he thinks about it.
> 
> Re the naming question in
> https://public-inbox.org/git/87o9abzv46.fsf@evledraar.gmail.com/ &
> seeing that eg-switch exists, I wonder if just s/switch-branch/switch/
> makes more sense.
> 
> Assuming greenfield development (which we definitely don't have), I
> don't like the "restore-files" name, but the alternative that makes
> sense is "checkout". Then this "--from" argument could become "git
> checkout-tree <treeish> -- <pathspec>", and we'd have:
> 
>    git switch <branchish>
>    git checkout <pathspec>
>    git checkout-tree <treeish> -- <pathspec>
> 
> Or maybe that sucks, anyway what I was going to suggest is *if* others
> think that made sense as a "if we designed git today" endgame whether we
> could have an opt-in setting where you set e.g. core.uiVersion=3 (in
> anticipation of Git 3.0) and you'd get that behavior. There could be
> some other setting where core.uiVersion would use the old behavior (or
> with another setting, only warn) if we weren't connected to a terminal.
> 
> I.e. I'm thinking of this as step #2 in a #3 step series. Where this is
> the fully backwards compatible UI improvement, but someone who'd
> e.g. use EasyGit or didn't have backwards compatibility concerns could
> enable step #3 and opt-in to a mode where we'd fixed a bunch of UI warts
> in a backwards-incompatible way.
> 
> What would that mode look like? I'd to work on piling that on top of
> this :)


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox