1

I have the following situation: A coworker has committed a bunch of files with Windows line endings. We have a .gitattribute which removes the Windows line endings during commit.

That means after pull, I have dozens of files that are marked as "locally changed" when they weren't. I can't check them out since Git will take the (broken) file out of the repo (where it has Windows line endings), fix the line endings to end up with a file that's always different to what is currently in the repo.

One member of the team has fixed those files and committed and pushed them.

This question answers my question at the price of losing all local commits (i.e. everything that hasn't been pushed, yet): How do I force "git pull" to overwrite local files?

How do I pull without losing my local commits?

This is my Git repo on the server:

A --> B --> BAD1 --> FIX1(HEAD)

BAD1 contains bad commits, FIX1 fixes them.

This is what I currently have on my disk:

A --> B --> BAD1 --> LC1 --> LC2(HEAD)
              \---> FIX1

I arrived here by git pull --rebase. before that command, I had:

A --> B --> LC1 --> LC2(HEAD)

What I want to end up with is:

A --> B --> BAD1 --> FIX1 --> LC1 --> LC2(HEAD)
Community
  • 1
  • 1
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • 1
    `.gitattributes` doesn't remove Windows line-endings ***on pull and checkout***, it removes them ***on commit***. Depending on what settings you have, Windows line-endings are only ***restored*** upon checkout. –  Mar 31 '14 at 10:44
  • @Cupcake: AHHHHH... now, the behavior starts to make (more) sense. For some reason, the commit contains Windows line endings and I get them with pull/checkout. How can Git notice that such a file is different during `diff` and `status`? Does `diff` apply the attributes as well? – Aaron Digulla Mar 31 '14 at 11:29

1 Answers1

1

The answer you linked to is similar to what you need to do. Since your other coworker already fixed the line-endings and pushed the changes, you can just fetch the new changes from the origin, then merge them into your local work, or rebase your local work on top of them:

git fetch <remote>
git checkout <local-branch>

# Merge changes
git merge <remote>/<remote-branch>

# Or rebase
git rebase <remote>/<remote-branch>

fetch will never overwrite work on you local branches, all it does is update your remote-tracking branches. In fact, pull is just a fetch followed by a merge.

Update

Since your working copy has modified files (only because of the line endings), I guess it's preventing you from rebasing. So you can simply cleanup your working copy, do a hard reset back to a previous good state, merge in the fixed commit, then cherry-pick your local commits back on top (using their sha IDs).

Putting that all together, it looks like this:

git checkout -- .
git reset --hard B
git merge <remote> <branch>
git cherry-pick LC1 LC2

When you do a hard reset, none of your commits are actually deleted by git right away. Git keeps all commits that aren't reachable from a branch or reference around for a set period of time (the default is 2 weeks), before finally garbage-collecting them from the repo. You can initiate an early garbage-collection by running git gc, but obviously you don't want to do that right now in your case.

Because none of the commits are actually deleted right away, you can still reference them using their sha IDs, which is why you can cherry pick them back on top of your branch after you've merged in the fixed commit.

  • How do I fix a local checkout if they already have pulled the bad commit (i.e. when `local-branch` creates the situation above)? – Aaron Digulla Mar 31 '14 at 11:27
  • One option is to `git reset --hard` to get rid of the bad commit, then `merge` or `rebase`, but if you've already done local work on top of the bad commit, you'll have to either just `merge` and fix the conflicts, or you can do the hard reset, merge the good commits, then cherry-pick your old work back on top (which is basically the same as an interactive `rebase`). Is it possible for you to show what the relevant commits from `git log --graph --oneline` look like? –  Mar 31 '14 at 11:38
  • The problem with hard reset is that it will delete all my local commits. I've created some images in my question; do they answer your question? – Aaron Digulla Mar 31 '14 at 12:52
  • Just do another `rebase` like in my answer above. In fact, `git pull --rebase` is equivalent to doing a `fetch` followed by a `rebase`. You'll end up with both of your local commits on top of the fixed commit. –  Mar 31 '14 at 12:59
  • I can't because `pull` says: "You have uncommitted local changes." – Aaron Digulla Mar 31 '14 at 13:00
  • I updated my question, you can fix this by simply doing a hard reset, then cherry-picking your commits back on top. I'll add more explanation about why that's OK. –  Mar 31 '14 at 13:09
  • Just to be clear, you don't actually need any of the working copy modifications, correct? If they contain actual work you need, you might want to commit the changes and keep a branch reference on that commit, just in case you need to work with it later (like with cherry pick, or whatever). –  Mar 31 '14 at 13:15
  • `git status` show a lot of files, `git diff -b -w` doesn't print anything. – Aaron Digulla Mar 31 '14 at 13:20
  • What is `git checkout -- .` supposed to do? Is that the same as `git clean -f .`? – Aaron Digulla Mar 31 '14 at 13:21
  • 1
    It's not the same as `git clean`. Git's `checkout` command is also used to revert changes to your working copy, so `git checkout -- .` means "Revert all changes to my working copy in this path", and the path that is passed is the current directory, `.`. `git clean`, on the other hand, is used to delete files that haven't yet been tracked/added to your git repo history. –  Mar 31 '14 at 13:30
  • 1
    Think of it this way, you revert modifications to files by "checking them out" from their previous state in history again, thereby "overwriting" the local modifications. You can think of `git checkout -- ` as shorthand for `git checkout HEAD -- `. You can even checkout different versions of specific files from different branches or revisions/commits, such as `git checkout -- ` or `git checkout -- `. –  Mar 31 '14 at 13:34
  • It's because `checkout` will change the state of your working copy, but it won't change the state of your branch. Only `reset` will change what commit a branch reference points to. Doing `git checkout B` will switch your working copy to B, but your branch will stay at the bad commit...it's like if you had a bunch of signs posts in the ground for each commit, and you had an extra post for your branch. Right now your branch post is next to the bad commit. `checkout B` would mean you walk over to B, but leave your branch post behind... –  Mar 31 '14 at 13:39
  • 1
    `reset --hard` means you pull the branch post out of the ground, and carry it with you over to the B commit, then plunk it right down next to it. Does my improvised analogy make sense? I like to use another analogy too, a "tree of commits with post-it notes attached", where the post-it notes represent branch references. You can pick up the post-it notes and move them around with `git reset`. –  Mar 31 '14 at 13:40
  • :-/ this is all pretty dangerous. Wouldn't it be better to stash all my local commits, checkout the remote (fixed) head and then reapply them? – Aaron Digulla Mar 31 '14 at 16:11
  • You can't stash commits, you can only stash the state of your working copy, which you said didn't have anything you wanted to keep, right? People do commit manipulations like these all the time, git makes them easy to work with. If you're worried that you might lose your local commits after you hard reset, you can just attach a branch reference to them with `git branch `, and that way they'll never be garbage-collected. Or you could do what you said, checkout the remote head and re-apply your local commits...but the easiest way to re-apply them is with `git cherry-pick` anyways. –  Mar 31 '14 at 19:00