Preserve git --assume-unchanged files between branch checkouts

You can try (git update-index man page):

git update-index --skip-worktree -- path

Skip-worktree bit can be defined in one (long) sentence: When reading an entry, if it is marked as skip-worktree, then Git pretends its working directory version is up to date and read the index version instead.

However, as mentioned in "git assume unchanged vs skip worktree":

Both options have problems. --assume-unchanged resets itself whenever the index gets discarded (e.g. git reset), so that will probably trip you up sooner or later. Same goes for --skip-worktree.


Plus, make sure to use Git 2.24 (Q4 2014).
Since 2012 (the OP's question), git stash has been ported to C (it is no longer a shell script) but it had (in its new implementation) to (re-)learn to write refreshed index back to disk.

See commit 34933d0 (11 Sep 2019) by Thomas Gummerer (tgummerer).
(Merged by Thomas Gummerer -- tgummerer -- in commit 34933d0, 20 Sep 2019)

stash: make sure to write refreshed cache

When converting stash into C, calls to 'git update-index --refresh' were replaced with the 'refresh_cache()' function.
That is fine as long as the index is only needed in-core, and not re-read from disk.

However in many cases we do actually need the refreshed index to be written to disk, for example 'merge_recursive_generic()' discards the in-core index before re-reading it from disk, and in the case of 'apply --quiet', the 'refresh_cache()' we currently have is pointless without writing the index to disk.

Always write the index after refreshing it to ensure there are no regressions in this compared to the scripted stash.
In the future we can consider avoiding the write where possible after making sure none of the subsequent calls actually need the refreshed cache, and it is not expected to be refreshed after stash exits or it is written somewhere else already.


Warning, that index might not be correctly rewritten when git stash is used with --quiet: With Git 2.25 (Q1 2020), Recent update to "git stash pop" made the command empty the index when run with the "--quiet" option, which has been corrected.

See commit df53c80 (13 Nov 2019) by Thomas Gummerer (tgummerer).
(Merged by Junio C Hamano -- gitster -- in commit 3c3e5d0, 01 Dec 2019)

stash: make sure we have a valid index before writing it

Reported-by: Grzegorz Rajchman
Signed-off-by: Thomas Gummerer

In 'do_apply_stash()' we refresh the index in the end.

Since 34933d0eff ("stash: make sure to write refreshed cache", 2019-09-11, Git v2.24.0-rc0 -- merge listed in batch #6), we also write that refreshed index when --quiet is given to 'git stash apply'.

However if '--index' is not given to 'git stash apply', we also discard the index in the else clause just before.

We need to do so because we use an external 'git update-index --add --stdin', which leads to an out of date in-core index.

Later we call 'refresh_and_write_cache', which now leads to writing the discarded index, which means we essentially write an empty index file.

This is obviously not correct, or the behaviour the user wanted.

We should not modify the users index without being asked to do so.

Make sure to re-read the index after discarding the current in-core index, to avoid dealing with outdated information.

Instead we could also drop the 'discard_cache()' + 'read_cache()', however that would make it easy to fall into the same trap as 34933d0eff did, so it's better to avoid that.

We can also drop the 'refresh_and_write_cache' completely in the quiet case.

Previously in legacy stash we relied on 'git status' to refresh the index after calling 'git read-tree' when '--index' was passed to 'git apply'.

However the 'reset_tree()' call that replaced 'git read-tree' always passes options that are equivalent to '-m', making the refresh of the index unnecessary.


What I've started doing is creating a branch off master called private that has my local changes; think of it as a proxy branch between my work branch and master. I can rebase my current work branch against private when I need my local-only changes and I have a couple of aliases in my .gitconfig that automate keeping private up-to-date with master. When I need to merge to master, my aliases make sure to rebase --onto master private my work branch first.

I posted a blog entry about this in more detail here http://blog.ericwoodruff.me/2013/02/git-private-branch-pattern.html


The solution that worked for me was to use --skip-worktree. However, like some above, I struggled with being able to switch between a ticketed branch and the main branch without git complaining even after I had set the --skip-worktree flag on the file whose changes I wanted to remain local.

It seems like you'll run into this issue if you make changes to the local-only file before running --skip-worktree, which is what happened to me.

One suggested workaround, above, is to add the file to your_repo/.git/info/exclude. But I didn't want to add the file to the exclude list, so I did the following from within my working tree directory:

  1. cp <local-only_file> ~/
    • copy file that has your local-only changes to somewhere safe on the filesystem
  2. git checkout <local-only_file>
    • in working tree, checkout file so that it matches master branch file
  3. git update-index --skip-worktree -- <local-only_file>
  4. cp ~/<local-only_file> .
    • copy file in question from safe location back into working tree
  5. git diff
    • no changes should be shown; if you push to the main repo, no changes in <local-only_file> are included in the push