12

I'm surprised I couldn't find anything about that...

In short, my question is if is there a way to avoid the intermediate file changes between the following two commands, if after the following two commands the file content is exactly as was before?

git checkout dev
git pull

The motivation is (Solution that meets only one of the motivations is also better than nothing):

  • I want to avoid my IDE to reload files if not needed (yes, VS project files...).
  • Avoid stashing files that not really changed.

This is a very common scenario when I'm coming back from a feature branch to master after merging origin/feature into origin/dev using a remote source control server.

I would expect that performing git fetch origin dev before will solve this but it doesn't. Also I found some answers like this which talks about grouping the commands together for easiness, but didn't targetted the file-changes issue.

MaMazav
  • 1,773
  • 3
  • 19
  • 33

2 Answers2

9

I've come to the same scenario and after looking for a simpler alternative I've just found what I needed:

Pull for another branch without switching:

You cannot merge a branch B into branch A without checking out A first if it would result in a non-fast-forward merge. This is because a working copy is needed to resolve any potential conflicts.

This command only works for a fast-forward merge.

git fetch origin master:master

(Replace master with the branch you want to update. In the OP's scenario, it will be git fetch origin dev:dev)

Once the local branch is updated with the remote changes you can switch to the desired branch without causing file changes

References:

Pau Fracés
  • 1,077
  • 10
  • 22
1

There's a way to do this, using git worktree add. Be sure your Git is at least 2.5 (so that it has git worktree) and preferably at least 2.15 (fixes a fairly severe bug with added worktrees; without the fix, an added worktree should not be used for more than about two weeks, in general).

Your IDE may be incompatible with git worktree add; I avoid IDEs so cannot say which ones cooperate with it, and which do not.

Assuming you have a Git version that supports added worktrees, use git worktree add to create your dev branch, and leave the main worktree on the master branch. Alternatively, use added work-tree for the main (master, or whatever) branch, and the main work-tree for the dev branch. Remember that Git enforces that each work-tree have a different branch checked-out.

Stop using git pull. (This is not strictly necessary, but I recommend it.)

To update your repository from the server, use git fetch—with no additional arguments—at any time, from either work-tree. This obtains any new commits they have that you don't, and updates your origin/* remote-tracking names.

To update your master branch, enter the master-branch work-tree and, on the command line, run git merge or git merge origin/master. (Run git fetch first, if you have not done so recently, so as to have an up-to-date origin/master.)

To update your dev branch, enter the dev-branch work-tree and, on the command line, run git merge or git merge origin/dev. As before, if you have not run git fetch recently, you may want to do that first so that your origin/master is up to date.

Your IDE may or may not be able to achieve what the command line does, here. Depending on the IDE, perhaps it will work to run two copies of it, one in each work-tree.

Remember, all git pull does is run git fetch and then git merge (or git fetch and then git rebase). It's the git checkout that is messing with the time-stamps on your work-tree files. Git doesn't really use or need the work-tree: that's for you. Running git checkout replaces the files in the work-tree with those from the other commit, while also replacing the files in the index; the index copies are the ones Git uses and cares about.

Git replaces the work-tree files because it assumes you need the ones from the branch you are switching-to. Switching back, of course, updates those work-tree files yet again. So now various files have been updated twice, and need rebuilding.


When you add a new work-tree, Git actually adds a group of three items:

  • a new HEAD for the new work-tree, to remember what commit and/or branch are the current ones in the new work-tree;
  • an index for the new work-tree, that Git uses to store Git's files for the next commit you might make; and
  • the new work-tree itself, for you to see and work with the files.

This work-tree is simply a directory (or folder) that is outside the main repository work-tree. For instance if your main repository is in ~/work/project you could move it to ~/work/project/main and create ~/work/project/dev to hold the dev-branch work-tree.

Fundamentally, though, the problem is simple: When you use git checkout, git replaces both index and work-tree contents as needed. So you need to stop using git checkout. If you need both work-trees, make two separate work-trees.


If your Git is too old to support work-trees, make two clones. Everything else works the same except, of course, that the two clones do not share branches.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks, I'll try understanding the details later. Just for clarity - what do you mean that the IDE might or might not be compatible with this solution? I mean, I don't care to use command line to run the commands manually instead of the GUI (I usually do it anyway), did you mean that I should command line if it's incompatible? – MaMazav Mar 06 '19 at 08:50
  • I mean that some IDE might not "get" the concept of added work-trees. It might insist that there's only *one* work-tree, i.e., the main one. The new feature has only been in Git for about 3.5 years (and the big bug I mention was only fixed in late 2017), so it might just not support it. – torek Mar 06 '19 at 08:57
  • Thanks for the answer. To me it seems that this solution is too complicated compared to the problem I tried to solve, but it's good to know that concept if I'll need it some day. Thanks! – MaMazav Mar 08 '19 at 12:51
  • The more recent proposed solution is pretty simple.. before you `git checkout dev`, first run `git fetch origin dev:dev` (with the ":" syntax) - it seems to work fine for me, so feel free to accept that guys answer if it does the trick for you as well! Pro-tip: theoretically you could even create your own git alias to run the 2 commands with less typing ^^ – T_D Apr 01 '22 at 10:01
  • @T_D: Note that `git fetch origin br:br` only does a fast-forward (as already noted in that answer), so if you need a real merge, it's not suitable. Moreover, if all you want is the new commit that `br` would name, you can just `git fetch origin` and then use `origin/br` directly. This allows you to not bother to have a `br` branch at all! – torek Apr 01 '22 at 18:27