Git - Difference Between 'assume-unchanged' and 'skip-worktree'
Asked Answered
O

3

678

I have local changes to a file that I don't want to commit to my repository. It is a configuration file for building the application on a server, but I want to build locally with different settings. Naturally, the file always shows up when I do 'git status' as something to be staged. I would like to hide this particular change and not commit it. I won't make any other changes to the file.

To clarify, using .gitignore is not what I want since that only prevents new files from being added. I want to ignore changes to a file already in the repository.

After some digging around, I see 2 options: assume-unchanged and skip-worktree. A previous question here talks about them but doesn't really explain their differences.

How are the two commands different? Why would someone use one or the other?

Outsail answered 29/11, 2012 at 17:0 Comment(5)
but can't one remove all and add all so as to "refresh" like explained here? #7076423 @GrigorySuccessful
The file should not be ignored, if I get OP's intention correctly. The file must be in the repository, but these very specific changes he has made should not be committed - not now, at least.Calvary
Googlers: see also I'm going to accept this emailed answer from Junio Hamano (the maintainer of Git) because I think it explains some things more lucidly than the official docs, and it can be taken as "official" adviceMarcus
Interesting patch in progress in Q1 2022: public-inbox.org/git/[email protected]/…Overbite
Well spotted VonC! Quoting from that RFC: "It has been reported that some users might be (ab)using SKIP_WORKTREE as a let-me-modify-but-keep-the-file-in-the-worktree mechanism[13, and a few other similar references]. These users know of multiple caveats and shortcomings in doing so; perhaps not surprising given the "SKIP_WORKTREE expecations" section above. However, these users use git update-index --skip-worktree, and not git sparse-checkout or core.sparseCheckout=true. As such, these users would be unaffected by this change and can continue abusing the system as before."Hypocaust
A
979

You want to use skip-worktree:

git update-index --skip-worktree <file_name>

assume-unchanged is designed for cases where it is expensive to check whether a group of files have been modified; when you set the bit, git (of course) assumes the files corresponding to that portion of the index have not been modified in the working copy. So it avoids a mess of stat calls. This bit is lost whenever the file's entry in the index changes (so, when the file is changed upstream).

skip-worktree is more than that: even where git knows that the file has been modified (or needs to be modified by a reset --hard or the like), it will pretend it has not been, using the version from the index instead. This persists until the index is discarded.

There is a good summary of the ramifications of this difference and the typical use cases here: http://fallengamer.livejournal.com/93321.html.

From that article:

  • --assume-unchanged assumes that a developer shouldn’t change a file. This flag is meant for improving performance for not-changing folders like SDKs.
  • --skip-worktree is useful when you instruct git not to touch a specific file ever because developers should change it. For example, if the main repository upstream hosts some production-ready configuration files and you don’t want to accidentally commit changes to those files, --skip-worktree is exactly what you want.
Abbreviation answered 29/11, 2012 at 17:39 Comment(15)
That makes sense. skip-worktree does indeed seem to be the way to go. Thanks!Outsail
You can find more information about the two commands in the git-update-index(1) Manual Page.Salters
A small note to save few seconds searching and reading. To cancel --skip-worktree effects and unset the flag there is --no-skip-worktree option. Works exactly the same way. This is useful in case a hand had slipped and wrong files were flagged, or if the circumstances had changed and previously skipped files shouldn't be ignored anymore.Spieler
I've found it worthwhile to add aliases for these to my ~/.gitignore file so that they are available globally. If you edit that file, you can add them to the [alias] section. e.g. ignore = update-index --skip-worktreeRighteous
I'm curious about the difference between using --skip-worktree and adding the filenames to the .git/info/exclude file. Both seem appropriate when you only want changes to certain files to be ignored for your local repo. Is there a difference in use cases?Detruncate
To answer my own question above, the difference between using --skip-worktree and the .git/info/exclude file is that the former will work even for files that are currently tracked. .git/info/exclude, like .gitignore, will only prevent accidentally adding untracked files to the index, but not making changes to files that are already tracked.Detruncate
Can this be pushed to the remote and be preserved by all clones?Burley
@Burley If you wanted that, just .gitignore the file and have developers locally copy it from a template once after checkout.Abbreviation
@Burley Seems this is only for the direct repository control command, you should have the console access to the remote repository to make that change. The push would ignore that because it has to use the same local index that already filtered.Durgy
Unfortunately both: --skip-worktree and --assume-unchanged do not allow switch to different branch. I get the error: Your local changes to the following files would be overwritten by checkout. Details hereTripersonal
Just the usage, ma'am: git update-index --skip-worktree <file_name>Trainload
@EugenKonkov that's because you are asking for something that has ill defined behavior and git shouldn't allow you to switch branches without letting it know what to do. The question is do you want to keep the modified file, or have the changes lost and overwritten by the file on the other branch?Veil
@NoviceC: Probably switching to different branch all local changes (including untracked files) should be saved into stash. After user is switched to different branch he can decide to apply his changes or not. This will resolve problem when I lost changes at untracked file when switching to branch where this file was tracked.Tripersonal
I can confirm there's no issue with checking out another branch with skip-worktree and on git version 2.30.0.windows.2Purchasable
This seems useless for config files if every time you need to change branches you need to again run --no-skip-worktree? Am I missing something? Or perhaps not useless, but incredibly inconvenient. Switching branches is a very common task for which you want ignored files to be carried from one branch to the next without thinking about them.Capias
O
156

Note: fallengamer did some tests in 2011 (so they may be outdated), and here are his findings:

Operations

  • File is changed both in local repository and upstream
    git pull:
    Git preserves local changes anyway.
    Thus you wouldn’t accidentally lose any data that you marked with any of the flags.
    • File with assume-unchanged flag: Git wouldn’t overwrite local file. Instead it would output conflicts and advices how to resolve them
    • File with skip-worktree flag: Git wouldn’t overwrite local file. Instead it would output conflicts and advices how to resolve them

  • File is changed both in local repository and upstream, trying to pull anyway
    git stash
    git pull
    Using skip-worktree results in some extra manual work but at least you wouldn’t lose any data if you had any local changes.
    • File with assume-unchanged flag: Discards all local changes without any possibility to restore them. The effect is like ‘git reset --hard’. ‘git pull’ call will succeed
    • File with skip-worktree flag: Stash wouldn’t work on skip-worktree files. ‘git pull’ will fail with the same error as above. Developer is forced to manually reset skip-worktree flag to be able to stash and complete the failing pull.

  • No local changes, upstream file changed
    git pull
    Both flags wouldn’t prevent you from getting upstream changes. Git detects that you broke assume-unchanged promise and choses to reflect the reality by resetting the flag.
    • File with assume-unchanged flag: Content is updated, flag is lost.
      git ls-files -v’ would show that flag is modified to H (from h).
    • File with skip-worktree flag: Content is updated, flag is preserved.
      git ls-files -v' would show the same S flag as before the pull.

  • With local file changed
    git reset --hard
    Git doesn’t touch skip-worktree file and reflects reality (the file promised to be unchanged actually was changed) for assume-unchanged file.
    • File with assume-unchanged flag: File content is reverted. Flag is reset to H (from h).
    • File with skip-worktree flag: File content is intact. Flag remains the same.

He adds the following analysis:

  • It looks like skip-worktree is trying very hard to preserve your local data. But it doesn’t prevent you to get upstream changes if it is safe. Plus git doesn’t reset the flag on pull.
    But ignoring the ‘reset --hard' command could become a nasty surprise for a developer.

  • Assume-unchanged flag could be lost on the pull operation and the local changes inside such files doesn’t seem to be important to git.

See:

He concludes:

Actually neither of the flags is intuitive enough.

  • assume-unchanged assumes that a developer shouldn’t change a file. If a file was changed – then that change is not important. This flag is meant for improving performance for not-changing folders like SDKs.
    But if the promise is broken and a file is actually changed, git reverts the flag to reflect the reality. Probably it’s ok to have some inconsistent flags in generally not-meant-to-be-changed folders.

  • On the other hand skip-worktree is useful when you instruct git not to touch a specific file ever. That is useful for an already tracked config file.
    Upstream main repository hosts some production-ready config but you would like to change some settings in the config to be able to do some local testing. And you don’t want to accidentally check the changes in such file to affect the production config. In that case skip-worktree makes perfect scene.


With Git 2.25.1 (Feb. 2020), the "Actually neither of the flags is intuitive enough" mentioned above is further clarified:

See commit 7a2dc95, commit 1b13e90 (22 Jan 2020) by brian m. carlson (bk2204).
(Merged by Junio C Hamano -- gitster -- in commit 53a8329, 30 Jan 2020)
(Git Mailing list)

doc: dissuade users from trying to ignore tracked files

Signed-off-by: Jeff King
Signed-off-by: brian m. carlson

It is quite common for users to want to ignore the changes to a file that Git tracks.

Common scenarios for this case are IDE settings and configuration files, which should generally not be tracked and possibly generated from tracked files using a templating mechanism.

However, users learn about the assume-unchanged and skip-worktree bits and try to use them to do this anyway.

This is problematic, because when these bits are set, many operations behave as the user expects, but they usually do not help when git checkout needs to replace a file.

There is no sensible behavior in this case, because sometimes the data is precious, such as certain configuration files, and sometimes it is irrelevant data that the user would be happy to discard.

Since this is not a supported configuration and users are prone to misuse the existing features for unintended purposes, causing general sadness and confusion, let's document the existing behavior and the pitfalls in the documentation for git update-index so that users know they should explore alternate solutions.

In addition, let's provide a recommended solution to dealing with the common case of configuration files, since there are well-known approaches used successfully in many environments.

The git update-index man page now includes:

Users often try to use the assume-unchanged and skip-worktree bits to tell Git to ignore changes to files that are tracked. This does not work as expected, since Git may still check working tree files against the index when performing certain operations. In general, Git does not provide a way to ignore changes to tracked files, so alternate solutions are recommended.

For example, if the file you want to change is some sort of config file, the repository can include a sample config file that can then be copied into the ignored name and modified. The repository can even include a script to treat the sample file as a template, modifying and copying it automatically.

That last part is what I describe a typical content filter driver based on smudge/clean scripts.

Overbite answered 22/5, 2014 at 12:36 Comment(9)
If you have skip-worktree on a file and the upstream changes, you get "please commit or stash" when you try to pull, even though git status does not report the file as changed. How can you avoid this, so the local changes can persist while people are mucking around with production settings on origin?Multifaceted
@Multifaceted not sure: that would be a good question on its own (with a link back to this one). I suppose you would get a similar message with assume-unchanged instead of skip-worktree.Overbite
Yes, I can confirm that you do. It means it's still very hard to have a local file that you simply want to maintain differently to the origin.Multifaceted
@Multifaceted Just in case it matters, what version of git are you using?Overbite
git version 1.7.10.4, debianMultifaceted
@Multifaceted than seems ancient. Any chance you could test it with a 2.2.x?Overbite
I'll look into "what version of git should we have on this machine" and see what I can do - it's a virtual machine hosting a production server.Multifaceted
@Overbite how to solve for both assume-changed & skip-worktree for git pull or rebase, both will throws please commit or stash even git status shows nothingPendley
@Pendley Strange. Could you detail that in a new question, with your Git version, OS version and shell?Overbite
S
7

Use skip-worktree as in:

git update-index --skip-worktree changedfile.txt

confirm file status:

 git ls-files -v
 S changedfile.txt

The first letter, S, means the file is marked with the skip-worktree option

Reason:

"git-update-index --assume-unchanged was never meant to ignore changes to tracked files (only to spare some stats). So do not suggest it as a means to achieve that."

SEE: https://github.com/git/git/commit/936d2c9301e41a84a374b98f92777e00d321a2ea

--skip-worktree explained:

This option ignores changes to a file that is already tracked. git always uses the file content and attributes from the index regardless of any modifications made to the file in the working directory. This allows you to make changes to a file that you don't want to be pushed to upstream. Use this option as already shown above.

To unset this option, use --no-skip-worktree as in:

git update-index --no-skip-worktree changedfile.txt

--assume-unchanged explained:

This option was specifically made for use cases where it's resource-intensive to check if certain files have been modified. e.g. to optimize resource usage on a slow filesystem, git doesn't check the files to see if the files in question changed, it assumes the files have not been modified in the working directory. This flag is lost whenever the file's entry in the index changes. e.g. when the file is modified upstream.

It's used as follows:

git update-index --assume-unchanged changedfile.txt

confirm file status:

git ls-files -v
h changedfile.txt

The first letter h means the file is marked with the assumed-unchanged option To unset this option, use –no-assume-unchanged as in:

git update-index --no-assume-unchanged changedfile.txt
Scrubby answered 4/7, 2022 at 11:46 Comment(1)
this only happens for you tho right? Other devs will have to do the same. My use case is an ENV file with dummy values that the dev must fill in for their personal setup.Bisitun

© 2022 - 2024 — McMap. All rights reserved.