Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preserve git --assume-unchanged files between branch checkouts

Tags:

git

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.]

like image 372
Jeff Avatar asked Mar 22 '12 01:03

Jeff


People also ask

What is assume unchanged in git?

When the "assume unchanged" bit is on, the user promises not to change the file and allows Git to assume that the working tree file matches what is recorded in the index. If you want to change the working tree file, you need to unset the bit to tell Git.

Does git checkout change files?

The git checkout command can be used in a commit, or file level scope. A file level checkout will change the file's contents to those of the specific commit.

Does switching branches change files?

So if you switch to a different branch and there are no files in the commit that were added to different commit you can: Merge the other branch into the current. That brings all changes from the branch being merged — new files a re added, updated files updated, removed files removed.


2 Answers

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.

like image 157
VonC Avatar answered Oct 16 '22 15:10

VonC


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

like image 27
Eric Woodruff Avatar answered Oct 16 '22 13:10

Eric Woodruff