33

I'm working on a project where we have recently started using git. The setup was not perfect from start, so I've set up .gitattributes after people started cloning/working and I'm still making some changes to this file.

Consider the following setup...

Both Alice and Bob have cloned "repo.git" and the repository contains the file /myproj/src/file.ending with \n as line ending, i.e. the file does not contain \r characters.

They also both have .gitattributes with the following setting:

/myproj/src/file.ending -text

This tells git that file.ending should not be considered a text file and thus no line ending conversion should take place.

Accordingly, the files in Alice's and Bob's working tree also have \n as line ending.

Now, Alice makes the following change to .gitattributes:

/myproj/src/file.ending text

Alice would like this change to take effect, both for her and for Bob.

The only way I know of right now is quite intrusive:

git rm --cached -r .
git reset --hard

I would like to avoid two things:

  • Alice has to commit her `.gitattributes` file before she can actually test it (reset above will overwrite her changes).
  • Bob has to wipe his index and working tree to get the update. Bob is not happy.

What is the preferred way of doing this?

jgreen81
  • 705
  • 1
  • 6
  • 14
  • `touch myproj/src/file.ending` – user4003407 Feb 06 '18 at 13:31
  • @PetSerAl - touching the file is supposed to do what, exactly? – Mark Adelsberger Feb 06 '18 at 14:15
  • @MarkAdelsberger That will change stat information for file in working directory, so it does not match cached stat information saved in index. – user4003407 Feb 06 '18 at 14:18
  • @PetSerAl - That's not how git detects change. – Mark Adelsberger Feb 06 '18 at 14:22
  • @MarkAdelsberger Then why do you run `rm myproj/src/file.ending` before `git checkout -- myproj/src/file.ending`? Is `git checkout` alone not enough to update the file? – user4003407 Feb 06 '18 at 14:24
  • @PetSerAl - This also surprises me. It seems that touch followed by checkout actually results in a line ending change. Does this mean that the time stamp on the file makes git re-consider it for checkout? – jgreen81 Feb 06 '18 at 14:27
  • 1
    @MarkAdelsberger - git checkout alone is not enough. – jgreen81 Feb 06 '18 at 14:27
  • @eversceptic Git cache stat information in the index. When cached stat information in the index match stat information in the working directory, then Git assume that file does not changed. Even if `.gitattributes` changed in a way, that you now need to use different smuggle filter (line ending conversion for example). – user4003407 Feb 06 '18 at 14:32
  • @eversceptic - When did I say it was? – Mark Adelsberger Feb 06 '18 at 14:41
  • @PetSerAl - Well, I ran an experiment based on just how dangerous that method would be, expecting to demonstrate that it doesn't hold up; but it appears that it does. Of the shortcuts I've seen git take for quick comparisons, that's the first one I would describe as exploitable. But ok, you win this round. I stand corrected and you *could* substitute `touch` for `rm` in my answer. – Mark Adelsberger Feb 06 '18 at 14:50
  • @MarkAdelsberger You didn't say that / ask whether checkout alone was enough. I messed up your name tags. Sorry. – jgreen81 Feb 06 '18 at 14:51
  • I'm pretty new to git so I've just learned about the smudge and clean filters. Can anyone point me to a good resource with some details on this? Like what has to change to the filters to run? What has to change for git to register a "change", etc? In this case, I was not aware of the time stamp thing. – jgreen81 Feb 06 '18 at 15:05
  • @eversceptic - I don't know of a particularly good resource about the filters, but beyond "clean runs when writing from work tree to index, and smudge runs when writing from index to work tree" there are no special triggers that decide when the filters run. The trick in this case, for example, is coaxing git not to take a "do nothing" shortcut on checkout; as long as it actually writes a new copy of the file from the index to the work tree, the new filter configuration for the path will apply. – Mark Adelsberger Feb 06 '18 at 15:50
  • Possible duplicate of [git: re-checkout files after creating smudge filter](https://stackoverflow.com/questions/21652242/git-re-checkout-files-after-creating-smudge-filter) – Jan Hudec Oct 24 '18 at 12:29
  • There are better solutions in https://stackoverflow.com/questions/21652242/git-re-checkout-files-after-creating-smudge-filter – Jan Hudec Oct 24 '18 at 12:30

4 Answers4

23

You don't have to reset hard (if I understand correctly what you're doing).

My case is similiar. I added a .gitattributes in a running project. I need the files I have and the files in online repo to be ruled by gitattr.

# This will force git to recheck and "reapply" gitattributes changes.
git rm --cached -r .
git add -A

Your commit will re-add all the .ending files you mention and you'll not lose any changes you may have. Of course, Bob will have to pull to get it.

Ratata Tata
  • 2,781
  • 1
  • 34
  • 49
  • 10
    This has a massive problem. It will add files that were not versioned and shouldn't be there. – Jan Hudec Oct 24 '18 at 11:21
  • 2
    Indeed, this solution has to have a huge warning. For any mid to large size repository, this is definitely not a good thing to do – Leonid Usov Jun 11 '20 at 09:44
9

osse on irc://chat.freenode.net/#git gave me this method, and it works reasonably well:

git rm -r :/ && git checkout HEAD -- :/

This will complain if you have uncommitted changes in your tree.

Seems like there should be a better way though.

Iwan Aucamp
  • 1,469
  • 20
  • 21
  • 2
    This answer doesn't work on Windows Powershell, which doesn't have `&&` ([ref](https://stackoverflow.com/questions/15101705/how-to-run-multiple-commands-on-success)). On Powershell, use `git rm -r :/; git checkout HEAD -- :/`. – Jason R. Coombs Jul 30 '21 at 13:49
  • One (big) improvement would be to only apply `git rm` to the files that are affected by the `.gitattributes` change. In this case `*.ending`. So: `find . -iname "*.ending" | xargs -n 1 git rm` (maybe the `-n 1` isn't needed but it gives you an output line for each input line and won't touch files that aren't known to git). – Matt Chambers Mar 15 '22 at 15:27
6

By "would like this change to take effect", do you mean that Alice wants the working copies to switch to Windows-style line endings for both her and Bob? Then the first problem is, why is Alice taking responsibility for what's in Bob's working tree?

If the file is better described by the new attributes, so be it; the .gitattributes file can be edited, tested, and committed just like any other.

The procedure you suggest for getting the new attributes to take effect doesn't make a lot of sense, for two reasons:

First, why are you wiping the index? The text attribute affects the relationship between the index and the working copy. In your example it seems it's the working copy you need to change, not the index.

Second, why are you wiping everything from the index? Only the paths whose attributes have changed need to be addressed.

So in your example, if Alice wants to locally reflect the new attributes, the most that should be necessary is

rm myproj/src/file.ending
git checkout -- myproj/src/file.ending

Since this procedure doesn't overwrite the .gitattributes file, there's no need to prematurely commit it.

It's not clear to me what exactly makes Bob unhappy about your original procedure, so I don't know if this one makes him any happier. Perhaps he just wants the update to be automatic when he pulls; while it's not unreasonable to expect that, I'm not sure it's in the cards as git works.

The problem is how changes are detected. In almost every situation, if git's updating the working tree at the end of a merge or fast-forward (e.g. completing a pull), it need only compare the hashes of the indexed objects for the old commit and the new commit to tell if there's a change to apply.

The exception is if attributes (or filter definitions) change - as noted above, that doesn't change the index. But those conditions are relatively rare, and the checks for them are much more expensive than hash check that's right almost every time, so rather than burden every comparison with mostly-pointless costs git allows that when you know you've done certain things, you have to take an extra step.

So if this is going to happen once, just let the team communicate. "The attributes for this path are changing; you may want to refresh your working copies of the affected files."

If it's going to happen repeatedly, my best advice is to figure out why this keeps happening and fix it. You could try to set up some kind of scripted automation, maybe even with hooks, to detect and address attribute changes; but it's a lot of complication and will likely cause more trouble than it fixes.

Mark Adelsberger
  • 42,148
  • 4
  • 35
  • 52
  • 2
    Regarding the synchronization between Alice and Bob, I don't understand you are wondering why Bob wants these changes. As you state it, the .gitattributes change better describe the file so naturally, Bob wants to make use of this description. I'm wiping the whole index because using one specific file was just an example. It seems that rm alone (instead of git rm) is also useful. So you could use that instead. Was not aware. As stated in a comment to my original text, "touch" also seems to work and may be considered less intrusive. – jgreen81 Feb 06 '18 at 14:46
  • @eversceptic - It's not a question of whether Bob wants the changes. It's a question of who determines if Bob wants the changes. If I change attributes on a file, it isn't up to me to decide that your work tree needs to be changed; it's up to you. (As one example, Bob could be running on a Mac or a unix box, in which case the change won't even affect his work tree.) All Alice needs to do is make sure Bob is aware that the attributes changed; for her to "want the changes to take effect for Bob" is a bad requirement. – Mark Adelsberger Feb 06 '18 at 15:24
  • @eversceptic - Even if the "single file" is just an example, that still doesn't mean it makes sense to wipe out *everything*. You know what paths' attributes have changed and there's no reason to mess with files at other paths. In particular, one of your two stated problems in the original question relates directly to using a procedure that messes up the .gitattributes file itself. – Mark Adelsberger Feb 06 '18 at 15:26
  • @eversceptic - If you prefer to use `touch` instead of `rm`, use `touch`. I will continue to use `rm`. When the purpose of the command is to cause the file to be overwritten from the index, neither one is "less invasive". – Mark Adelsberger Feb 06 '18 at 15:27
  • Regarding Alice's relationship to Bob, I see your point :) In our case, we had some problems that affected colleages and it was a fix to these problems. Saying that Bob wants the fix is a better statement than Alice wants to give Bob the fix. – jgreen81 Feb 06 '18 at 17:17
  • Regarding messing up files, I also agree. To the extent possible, we should only affect the files that needs to be affected. – jgreen81 Feb 06 '18 at 17:18
  • Regarding the intrusiveness of the methods, I also agree. From a certain perspective, e.g. Bob having changes in his working tree, "touch", "rm" and "git rm --cached" are equally intrusive. We'll have to describe how to make this update and then they can do it at their leisure. – jgreen81 Feb 06 '18 at 17:18
  • FYI: For some reason, I cannot tag Mark in these comments. I can above in the comments to my original post. – jgreen81 Feb 06 '18 at 17:20
  • @eversceptic Now you can. – user4003407 Feb 06 '18 at 17:33
  • There are two reasons to use @eversceptic: notification and clarity to whom you are talking. Post author always get notification of new comments, so you do not need it for notification. And you was the only person, besides post author, who are talking, so you do not need it for clarity. As result, Stack Overflow do not allow you to use it. – user4003407 Feb 06 '18 at 18:03
2

After you have committed the changes for .gitattributes run the following to apply the changes

git rm --cached -r .
git reset --hard
Ian
  • 51
  • 1
  • 2