How can I checkout a branch while ensuring that none of my current branch's changes are carried over?

后端 未结 2 2055
再見小時候
再見小時候 2021-01-25 14:14

I want switch branches and have git always set the working directory contents to reflect the state of the branch I\'m switching to. I\'m experiencing the behaviors sta

相关标签:
2条回答
  • 2021-01-25 14:38

    Uncommitted changes remain in your worktree or index until you tell git where to commit them.

    git add, git checkout and git reset all have a --patch option. The workflow is, doing what needs doing and recording it where it needs recording are separate tasks. Often enough you find yourself doing effectively drive-by bugfixing, where you find a complete mess someoneusually me left and the issue is, there's doing the work, and doing the paperwork, but you're staring right straight at the work and know exactly what needs doing.

    The git way1 is, Do It. Get it all correct in your worktree (this may involve a bit of exploratory branching itself)

    • and then construct the commit series you'd have constructed in the first place if you'd known exactly what you were doing all along.

    That's the important part. That's what makes professional, publishable work. What trips people up is, git's meant at least as much for the messy part that comes before that.


    1hahaha he said "the git way", as if there were only onelol

    0 讨论(0)
  • 2021-01-25 14:49

    As jthill said in comments, you will need to figure out what to do with untracked and/or ignored files.

    Moreover, you must not be in the middle of a conflicted merge.

    There is a simple solution to all of this (use a different work-tree, perhaps from a different clone or perhaps from git worktree if your Git is new enough). If that's not desirable for some reason, though, here are some things to consider. Let's take a somewhat hypothetical example, but show some real-world problems. Suppose you're in repo project, currently on branch dev-a:

    $ cd project
    $ git status
    On branch dev-a
    You have unmerged paths.
      (fix conflicts and run "git commit") ...
    

    In this case, you are really quite stuck. Anything you do here will lose your partially-merged state. If there is nothing important in the merged state, you can run git merge --abort to stop merging, and throw out the conflicted index, and now we're back to the previous case. Let's see if Git thinks everything is clean.

    $ git merge --abort
    $ git status
    On branch dev-a
    nothing to commit, working tree clean
    

    Apparently everything is clean. But wait!

    $ cat foo
    I am a foo
    $ git checkout dev-b
    Switched to branch 'dev-b'
    $ git status
    On branch dev-b
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
    
        foo
    
    nothing added to commit but untracked files present (use "git add" to track)
    

    Was everything clean? Well, file foo is ignored in branch dev-a, but not in branch dev-b, where it now shows up as "untracked". We can use git status --ignored from dev-a to see it:

    $ git checkout dev-a
    Switched to branch 'dev-a'
    $ git status --short --ignored
    !! foo
    

    Untracked and/or ignored files will now show up as UU or !! (in the short output—the long output is as already seen).

    If you wish to simply remove ignored files, you can use git clean with the -x option (in addition to any usual options).

    If you want to save them first, you may use git stash -a. The stash code will make three commits, instead of the usual two; the third commit will hold the untracked and ignored files. (Note that git stash -u saves only the untracked, not the ignored, files, in the third commit.) After saving the files, the stash code will remove them, leaving you with a clean (as in git clean -fdx) work-tree.

    Note that a file that is ignored (and therefore not untracked) in one branch, such as dev-a, can be non-ignored, and therefor either untracked or tracked (but not both) in another branch, such as dev-b and dev-c. If file foo is ignored in dev-a and you switch to dev-b it becomes untracked, as we saw. But what if it's ignored in dev-a and tracked in dev-c?

    $ git status --short --ignored
    !! foo
    $ git checkout dev-c
    Switched to branch 'dev-c'
    $ cat foo
    I am a foo
    $ git checkout dev-a
    Switched to branch 'dev-a'
    $ cat foo
    cat: foo: No such file or directory
    

    File foo is tracked in dev-c and ignored in dev-a, so it gets removed when we switch from dev-a to dev-c (because that's what changing from the tip commit of dev-c to dev-a requires):

    $ git diff --name-status dev-c dev-a
    A       .gitignore
    D       foo
    

    There's another very tricky case here. Remember that foo is ignored in (the tip commit of) dev-a, untracked in dev-b, and consists of I am a foo\n in dev-c. As we just saw, switching from dev-a to dev-c extracted the version from dev-c. This is true regardless of what we put in it:

    $ git rev-parse --abbrev-rev HEAD
    dev-a
    $ echo 'If the foo s.its, wear it' > foo
    $ git checkout dev-c
    Switched to branch 'dev-c'
    $ cat foo
    I am a foo
    

    Let's get back to dev-a and put our twisted Foo Bird joke back again, and this time, let's step through branch (and tip commit) dev-b, where file foo is untracked, rather than ignored:

    $ git checkout dev-a
    Switched to branch 'dev-a'
    $ echo 'If the foo s.its, wear it' > foo
    $ git checkout dev-b
    $ git status --short
    ?? foo
    $ git checkout dev-c
    error: The following untracked working tree files would be overwritten by checkout:
        foo
    Please move or remove them before you switch branches.
    Aborting
    

    This is because an ignored file is also a clobberable (clobber-worthy?) file, but an untracked file is not. There is, in Git's little mind, no notion of a path-name that should not be complained-about during git status (i.e., ignored for git status, and skipped over when adding "all" files in some directory), yet is also precious (must never be clobbered by git checkout).

    All of these are complicated even further by files marked, in the index, with the --skip-worktree or --assume-unchanged flags. I have not tested out these additional corner cases—but all are avoided by using a separate work-tree, rather than trying to cram everything into a single work-tree. (The reason is that a separate work-tree also implies a separate index. The git worktree code does this fairly well, or you can simply clone the repository locally, which is smart enough to share files when possible.)

    0 讨论(0)
提交回复
热议问题