42

I've been using git --assume-unchanged yacs/settings/development.py to ignore my local database configuration file in my dev branch. But when I want to switch branches (for deployments), I get an error that I still have changes pending:

% git checkout production
error: Your local changes to the following files would be overwritten by checkout:
    yacs/settings/development.py
Please, commit your changes or stash them before you can switch branches.
Aborting

Which is annoying. The only way I know how to get around this would be to stash it:

% git stash
% git checkout production
% git merge dev
% ./deploy.sh
% git checkout dev
% git stash pop
# On branch dev
# 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)
#
#   modified:   yacs/settings/development.py
#

But now it's back in the index again (ugh)! Is there a better alternative to this workflow?

[I don't particularly care if the local changes stay locally (aka, it's ok if it's the production branch), I just don't want it pushed to a remote repository.]

Jeff
  • 5,013
  • 1
  • 33
  • 30
  • this does not work for me. I add the file with --skip-worktree and git status does not show. However, I am still not able to switch to another branch. What am I missing? – sandyp Jul 14 '17 at 17:29

4 Answers4

22

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.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 8
    Having tried both `--assume-unchanged` and `--skip-worktree`, both continue to complain about uncommitted changes to the file when I try to switch local branches after setting this property. – amoe Sep 13 '13 at 12:43
  • 4
    @amoe and that file was already versioned right? What if you add that file to `your_repo\.git\info\exclude`? – VonC Sep 13 '13 at 12:48
  • 1
    @VonC , script.js file have `--assume-unchanged` and `--skip-worktree` but still getting same error. Any ideas? – Eray Apr 09 '15 at 20:50
  • @Eray not just on top of my head. It is best to make a new question with a link back to this one. – VonC Apr 09 '15 at 20:57
  • @VonC , asked it on here : http://stackoverflow.com/questions/29549318/not-possible-to-switch-branch-after-skip-worktree thank you. – Eray Apr 09 '15 at 21:25
  • @VonC this does not work for me. I add the file with --skip-worktree and git status does not show. However, I am still not able to switch to another branch. What am I missing? – sandyp Jul 14 '17 at 17:30
  • @san not sure: it would be best to ask a separate question with the guy version, and the exact command you typed. – VonC Jul 14 '17 at 17:32
  • @VonC Thanks. It is pretty much what Eray asked here: https://stackoverflow.com/questions/29549318/not-possible-to-switch-branch-after-skip-worktree – sandyp Jul 14 '17 at 18:27
  • @sandyp Then it is best to untrack that file completely (`git rm --cached -- afile`, in both branches, as in https://stackoverflow.com/a/29549545/6309), and if possible generates its content through a content filter driver, as in https://stackoverflow.com/a/45025658/6309. – VonC Jul 14 '17 at 18:31
3

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

Eric Woodruff
  • 6,380
  • 3
  • 36
  • 33
3

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
mlehmeher
  • 197
  • 2
  • 9
1

this issue happens when the skipped file is not same as the one in the branch you are trying to checkout or the one you are trying to pull.

Bruce Zu
  • 507
  • 6
  • 17