Ignore specific changes to a file in git, but not the entire file
Asked Answered
W

10

67

I have a file in a git repository that has a local change on it. I want to have git ignore the local change forever, but not the file. In particular,

  • If the file isn't touched besides this change, git add . should never stage it.
  • Likewise, git commit -a shouldn't commit it.
  • If I ever make an additional change to the file, I should be able to stage and commit that change - but the change I'm ignoring should not be staged and committed.

Is there a way to do this? Doing some research, I read about "smudge/clean cycles," where, if I read correctly,

  1. the file would be marked as unchanged,
  2. the change I made would be overwritten when I checkout,
  3. and then a script would automatically reapply the change and then mark the file as unchanged again.

I am very new to git and scripting, though (I'm an intern with C# and Java experience), so if that's what I need to do, can you please post detailed directions or a link to a tutorial on how to set a smudge/clean cycle up?

Background: I want my working branch to be out of sync with the trunk. There is a low priority bug that only affects development machines, so rather than fix it, we're just commenting out the offending code. Obviously, we don't want this code to be removed from production, where it works just fine.

Wachter answered 16/5, 2013 at 22:5 Comment(3)
This isn't an answer to your question, but you can stage changes by hunks using git add -p. It's not automated, but you can add and commit changes to this file without a full blown solution.Underdog
I'm not sure on the particulars here, but it sounds like you want some hooks. Read git help hooks. There are lots of actions that you can hook where you could put your backup/restore commands.Quintero
possible duplicate of With git, temporary exclude a changed tracked file from commit in command lineBib
G
0

As I see it, the situation you are describing is this: you want to maintain a working directory that is different from the local repository.

This is not advisable; because these changes are not committed you will have little recourse if a file is accidentally deleted or changed in a way you do not want.

Therefore it is recommended that you in fact commit all your changes. If you would like to separate these changes you can easily do that using a branch. Example

git checkout -b new-branch
# now you can change the file without affecting master
echo bar >> foo
git add
git commit
# and back to master
git checkout master
Gondar answered 17/5, 2013 at 3:57 Comment(4)
@sleske: That makes a lot of sense. If you add that as an answer, I'll upvote it and consider accepting it. Also, thanks for editing my comment into my question!Wachter
I asked another developer how he deals with the issue and used his work-around. It involved setting up a config file in our .gitignore list. Thanks for the help, @Steven Penny and @sleske!Wachter
I have a properties file with my user specific properties in it, as the other guys in the team, and we must not push these changes. But I don't want to ignore the file as it also contains general properties that might need to be subject to pushed changes.Crosley
What is the suggested recourse for people working on a team with code signing? My team mate can’t use my code signing identification.Glazing
B
64

You can use the skip-worktree bit. Turn it on with:

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

After that, git will never stage local changes for <file> and will fail (loudly) if git itself has to write to <file> (say, in a merge or a checkout).

If you ever want to stage a future change, you can turn it off, stage the new change, and then turn it back on:

git update-index --no-skip-worktree <file>
git add -p <file>
git update-index --skip-worktree <file>

While not perfect, this might be good enough. It will be up to you to notice that <file> has unstaged changes, since git will no longer tell you that

Note: My original suggestion was to use assume-unchanged. As explained in Git - Difference Between 'assume-unchanged' and 'skip-worktree', it is really skip-worktree that you want. In particular, assume-unchanged is a promise to Git that you won't change the file, and if you violate that promise Git is allowed to erase your changes or commit them! In contrast, Git will not erase or commit your skip-worktree changes.

Bib answered 17/5, 2013 at 4:14 Comment(8)
-1 Incorrect. This will make git ignore all changes to the file. OP requested "If I ever make an additional change to the file, I should be able to stage and commit that change - but the change I'm ignoring should not be staged and committed.".Louisville
I'm seriously considering this answer. As long as I don't need to update the file very often, I wouldn't mind running these commands and manually changing the code I'm excluding each time, especially since git will remind me each time I try to commit the file without preparing it first.Wachter
Thanks @sleske, I updated my comment to explain how to make further changes.Bib
@MarkLodato: Ok, like that it makes sense.Louisville
Does --assume-unchanged have any advantages over --skip-worktree in this case?Monet
@TannerSwett, I never knew about --skip-worktree. It looks like that flag is the correct one to use. Thanks!Bib
@MarkLodato the comments "and will fail (loudly) if it has to write to <file> (say, in a merge or a checkout)." and "This is not perfect, since you will not be notified of any changes to <file>" seem to be at odds with one another. If I understand correctly, if the remote has a change to this file, and I do a checkout or pull, git will fail and point out the the essentially locked file. Then I could use --no-skip-worktree and then try the pull again and re set --skip-worktree after, Is that correct?Forward
@DelightedD0D, both statements are correct. The former is about git itself writing to the file, while the second is if you write to the file and have unstaged changes. I reworded my answer to hopefully clarify. And to your final question, yes, your understanding is correct. Hope this helps!Bib
H
32

These answers are good, but may not best solve @Kevin's problem. I had a similar concern, often editing a config file so the app I was working on would access my own private development database instead of the production one. It's only a matter of time before I accidentally check in and push those config changes! I just needed a light weight way to ignore a file. Here's what I learned:

  • First, make your needed change to your file. I'll call it my_config.

  • Make a patch file of that change with git diff >../somewhere-else/my_config.patch

  • Now tell git to ignore that file (without having to change the checked-in .gitignore): git update-index --assume-unchanged my_config

Now, as long as you don't make changes to my_config that you do want to check in, you can work freely. To stop ignoring my_config, do git update-index --no-assume-unchanged my_config. After pulling in somebody else's changes to my_config, you can easily restore your private change with git apply ../somewhere-else/my_config.patch, then ...assume-unchanged again, as above, and get back to work!

Here are some helpful aliases you can put in your ~/.gitconfig:

[alias]
    unchanged = update-index --assume-unchanged
    changed = update-index --no-assume-unchanged
    show-unchanged = !"git ls-files -v | sed -e 's/^[a-z] //p; d'"
Hillyer answered 19/3, 2018 at 18:30 Comment(2)
Thanks not only for the answer, but the workflow afterwards! I think many people were shying away, due to not knowing what to do after. Your explanation and patch file is underrated.Essence
This is great! I'm writing html on my local machine for a server, and I need to adapt local html references like "/theme.css" to "../theme.css" without affecting the repo. This is a great workflow, thanks! *commenting because, if I'm royally screwing up I wouldn't mind being told so:)Garvey
P
8

Git's "patch mode" is perfect for adding only certain changes from a file to your commit.

To start it, type git add -p, git commit -p (straight to commit message when done), or git add --interactive (more prompts).

It essentially takes you through each section of code shown in git diff and asks you whether you want to stage it or not.

When you reach the change, either answer no, or e to open the patch in your $EDITOR.

Preparative answered 17/5, 2013 at 4:25 Comment(0)
L
2

Yes, this could probably be done using smudge/clean filters. However, I'd strongly advise against doing this, because it would be rather complex and error-prone (e.g. it would confuse many tools building on git, it would make problems hard to debug etc.).

Also, it is generally not a good idea to have permanent local changes. One important point of using a SCM is that you can checkout a version and have it work immediately. That means that everything that you need should be checked in.

I don't think there is any "nice" way to do this, neither with git nor probably with most other SCMs. I would recommend you reconsider your requirements.

The problem seems to be that you have a file with "mixed content": Some content that is always the same, and some that needs to be changed locally (machine-specific options?). The recommended way to handle this is not to check in the problematic file, but instead check in a "template" file, then generate the real file at build time. Try investigating this. It will be more work now (possibly), but will make things easier, especially if you need to support more variation or if other people want to work on the same project.

Edit (based on info in comment)

You write that you want to ignore a local code change that is only necessary for the development machines, but not in production.

In that case, instead of commenting out, wrap the code in a condition of some sort, so that it only runs on the dev machines (e.g. using conditional compilation, or reading a config file or some environment property, or ...(1)). Then you can check in the code normally.

Even better, just fix the bug. If a bug makes development difficult, then IMHO it is high-priority.

Just commenting out code is not a good idea. It is error-prone and must be done each time you check out. Above all, running different code in development and in production is asking for trouble and should be avoided whenever possible.

(1) As an example: In our company, we have an environment variable specifically for cases like this. It indicates whether the code is running in development, in beta test, or in production, and is set by our build and deployment scripts. However, it is usually only used for simple things like choosing different file paths. Changing program logic based on the environment is discouraged, as explained above.

Louisville answered 17/5, 2013 at 7:37 Comment(1)
This makes sense. The bug is that we try and access a SaaS service that local copies of our servers can't get to, so it would make sense to have some simple logic to replace the service with a simple mock controlled by an environment variable.Wachter
M
2

Note: other people have said that keeping around persistent local changes is a bad idea, and I don't have any reason to disagree. Consider other options.

Here's the method that I like:

  1. Make some changes that you want to keep on your local machine.
  2. Use git branch local and git checkout local to make a new branch for your local changes. (If I'm not mistaken, these commands won't set up a remote branch automatically, so the new branch won't get pushed.)
  3. Use git commit to commit your changes.
  4. Make the changes that you want to push upstream.
  5. Use git checkout parent to go back to the branch that you do want to push.
  6. Use git commit to commit to this branch.
  7. Use git push to push your changes.
  8. Use git checkout local to get back to your local branch.
  9. Use git rebase parent to bring the changes from parent in to local. Using rebase will make it so that your local changes remain on top of the changes in parent.
  10. Go back to step 4 and repeat ad nauseam.

With this, you don't have to manually tell git which changes are local and which ones aren't every time you want to make a non-local change.

Monet answered 21/1, 2015 at 21:23 Comment(0)
O
1

As none of the answers fully satisfied me and IMHO did not exactly match all of OPs requirements (which are similar to mine) I would like to share my solution.

Preliminary

For me it is daily business to have some changes in my local repository, which shall not be pushed to the remote (database connections, adaptions for my local testing setup, etc.). Those changes always appeared in the status message as changed and previously, I had to to be careful what to check-in (as I am used to use git add --all), such that those changes where not pushed to remote.

It is no option for me to put those files into .gitignore because there will be the day far in the future, where I change another part in those files and would have forgotten about the .gitignore and would mess things up then.

Solution

I created a patch file with the changes, revert them before I commit and then re-apply them.

Here's my step-by-step guide for this

  1. I assume you have just made changes to the files in question. Those are not yet staged. Run the command below to create a patch file with the diffs you want to ignore in the future.

    git diff fileA FileB FileC > ../somePathOutsideYourGit/001-debugSetupChanges.patch

  2. Create a git alias for adding all files to the staging area. Add the following line to your git config file in the [alias] section.

    aa = "!git apply -R /home/user/somePathOutsideYourGit/001-debugSetupChanges.patch && git add --all && git apply /home/user/somePathOutsideYourGit/001-debugSetupChanges.patch"

You can access to config with git config --global --edit

This enables you now to use git aa to add all files you have changed to the staging area except the changes you want to ignore. If you have made additional changes in those files, they will be still added and pushed to the remote.

With the steps above, you will still see those changes every time in your git status message. If you wish to make them disappear there as well you can just add an alias for status in the same manner as above, masking the changes with git apply -R && git status && git apply .

`st = "!git apply -R /home/user/somePathOutsideYourGit/001-debugSetupChanges.patch && git status && git apply /home/user/somePathOutsideYourGit/001-debugSetupChanges.patch"`

This is no bullet-proof solution as it will fail as soon as git cannot revert the patch on the files because of your additional changes. In this case the git aa command will fail and you need to solve things manually.

Overhead answered 20/2, 2023 at 8:46 Comment(0)
G
0

As I see it, the situation you are describing is this: you want to maintain a working directory that is different from the local repository.

This is not advisable; because these changes are not committed you will have little recourse if a file is accidentally deleted or changed in a way you do not want.

Therefore it is recommended that you in fact commit all your changes. If you would like to separate these changes you can easily do that using a branch. Example

git checkout -b new-branch
# now you can change the file without affecting master
echo bar >> foo
git add
git commit
# and back to master
git checkout master
Gondar answered 17/5, 2013 at 3:57 Comment(4)
@sleske: That makes a lot of sense. If you add that as an answer, I'll upvote it and consider accepting it. Also, thanks for editing my comment into my question!Wachter
I asked another developer how he deals with the issue and used his work-around. It involved setting up a config file in our .gitignore list. Thanks for the help, @Steven Penny and @sleske!Wachter
I have a properties file with my user specific properties in it, as the other guys in the team, and we must not push these changes. But I don't want to ignore the file as it also contains general properties that might need to be subject to pushed changes.Crosley
What is the suggested recourse for people working on a team with code signing? My team mate can’t use my code signing identification.Glazing
C
0

If you can't use .gitignore as you need to make parallel changes in the same file(as found in your comments) then one option is git add -p Using this you can add or skip accordingly.

The problem using the above git command here is, it will be more of a manual process. I guess you may not find any other automated approach for you problem.

Cellulosic answered 17/5, 2013 at 5:59 Comment(0)
S
0

As of Git 2.5 (July 2015), you can use git worktree:

git worktree add -b patch-1 ../patch-1

This is essentially just creating a branch, but it puts the new branch into a new folder alongside the parent repo. This is nice because you can do work on this branch, even without committing, then switch back to master with a clean working directory.

Sartre answered 24/1, 2016 at 3:2 Comment(0)
B
0

If you happen to be using intellij as your IDE, then it has a neat feature (changelists) that I believe does exactly what OP is requesting.

You can mark and name a specific part of your local diff. If memory serves, this will keep those changes separate from all other local modifications and will only be staged for commit if you actively choose to do so - so long as you do your committing from intellij.

Boneyard answered 19/2, 2018 at 10:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.