0

git-2.16.2 on linux

git novice, so please bear with....

I cloned a repo, changed several files and now I want to push that to the origin master. I do not want to merge anything, I want git to take all the changes I made without merging.

I believe I still have to to the git pull --rebase. And I know there will be conflicts. But is there a way to bypass the merge piece of this and just push all my changes back to the origin master without merging (after git add, git commit of c) ?

daveg
  • 1,051
  • 11
  • 24
  • Do you intend to actually throw away the changes that were pushed by someone else on your remote? If not, you need to either rebase or merge, and resolve the conflicts, there's no way around it. If you do want to discard those changes on the remote, `gut push --force` is indeed what you want. – joanis Mar 30 '20 at 20:34

3 Answers3

2

What you are describing is

git push --force

and everyone will tell you not to do that. That is, if you ever work together with someone on a repository (or actially a branch). This is because you are rewriting history.

If this is your own repository ( i think it is), it will be no problem.

If you want to do it "properly" you would have to do a merge, and then use only changes from local, like here. This way you preserve your history, and achieve the same result.

David van rijn
  • 2,108
  • 9
  • 21
  • So --force will clobber anyone else's changes that I would have otherwise had to merge with ? – daveg Mar 30 '20 at 21:40
  • Yeah that's right. It literally forces the remote branch to be the same as the local branch. No matter what is there now. – David van rijn Mar 31 '20 at 15:30
2

If the state of origin has changed since you cloned (e.g. person B has pushed to it), you can't just push your changes without destroying Person B's changes. Instead you should put your local changes on their own branch "new", pull Person B's changes into your master branch, then rebase your "new" branch onto the end of master. Then push it all back.

symlink
  • 11,984
  • 7
  • 29
  • 50
  • I don't need to do this right now but want to be ready if someone else pushes changes (even though I told them not to and would clobber those changes if they did). – daveg Mar 30 '20 at 21:39
1

Git doesn't push changes, but rather commits. That's because Git doesn't store changes either.

Every commit holds a full, complete snapshot of all of its files. That's the data part of a commit. Every commit also holds some metadata, such as who made it (name and email address), when, and why (the log message). You can view a commit as changes, by comparing the snapshot to the previous commit's snapshot, but it's still a full snapshot.

Every commit has its own unique hash ID. That hash ID means that commit: never any other commit, and no other Git repository anywhere can use that commit hash ID for any other commit. Every Git repository that has this commit uses that hash ID for it. (Which is why commit hash IDs are so big and ugly: they have to be unique!)

Meanwhile, inside the metadata for each commit, a commit holds the raw hash ID of some earlier or parent commit. We say that this commit points to its parent. If we draw a diagram of several commits, we see that they tend to become backwards-looking, linear chains:

... <-F <-G <-H

where H is the latest or last commit. It has some big ugly hash ID, which I've just called H, and commit H contains the actual hash ID of earlier commit G, which contains the hash ID of yet-earlier commit F, and so on. These backwards chains make it easy for Git to show you a commit as a set of changes, which is much smaller (and more useful) than just showing you the entire snapshot every time.

What a branch name does in Git is that it lets you—and Git—quickly find this last commit. I'm going to stop drawing the internal arrows as arrows (because of laziness) but keep drawing branch names as arrows:

...--F--G--H   <-- master

The name master contains the hash ID of the last commit H.

I cloned a repo, changed several files and now I want to push that to the origin master.

Technically, you cloned the repo—which got you all their commits—and made you your own name master. Your clone also remembered the hash ID of the last commit in their master, under your name origin/master, which we can draw like this:

...--G--H   <-- master (HEAD), origin/master

(This HEAD thing is how Git remembers which branch name you're using.)

When you made a new commit, it got a new, unique hash ID. Let's just call this I for short:

...--G--H   <-- origin/master
         \
          I   <-- master (HEAD)

Note how your master now points to commit I, rather than H. But you can still find H in two ways: I points back to H, and origin/master points directly to H.

Meanwhile, in their repository, perhaps someone has added a new commit or two:

...--G--H--J--K   <-- master

Remember, this is their repository (and their master), not yours.

When you run git push origin master, your Git calls up their Git. Your Git tells them you have commit I and you'd like to give it to them. Your Git tells them I's parent is H and they say ok, I already have that commit, just give me I. You send over I to them:

          I   [your commit]
         /
...--G--H--J--K   <-- master

It doesn't matter whether we draw I above or below, or draw it more branch-ily as:

          I   [your commit]
         /
...--G--H
         \
          J--K   <-- master

as long as we draw in all the relevant commits and their connections.

Now your Git asks them—their Git—to set their master to point to commit I, which you both now have. If they obey this polite request, they will have:

          I   <-- master
         /
...--G--H--J--K

They won't be able to find commits J-K any more, because their Git finds the commits using their branch name. That finds I, which leads back to H and then G and so on, but never forwards.

If you use git push --force, you change your polite request into a command: Set your master! If they obey, they'll lose commits J-K.

What should you do about this?

What you will need, if they have these commits, is some way of sending your commit(s) so that they don't lose theirs.

You have several options. One is to use git merge origin/master to combine your work with theirs. The other is to use git rebase, which is more complicated.

Merging

To merge, use git fetch to make sure your own Git is up to date with theirs, then use git merge origin/master.1 This makes a new merge commit, which I'll draw in as M:

          I-----M   <-- master (HEAD)
         /     /
...--G--H--J--K   <-- origin/master

What makes a merge commit a merge is that it points back to more than one parent. The two parents here are commits I and K. If you now have your Git call up their Git and send your master, your Git will offer them commit M. They'll take M. Since M has two parents, your Git will offer I and K. They have K but not I,2 so they'll take I too, and your Git will offer H. They have H so they'll just take M and I in the end.

Then, your Git will ask them to set their master to point to M. That won't lose anything, so they'll probably accept this request. Your own Git will now update your own origin/master—your memory of their master—accordingly.


1If you really like git pull, you can use git pull to combine the git fetch step with the git merge step. All git pull does is run both commands. This robs you of the opportunity to insert Git commands between the two commands, and for Git newbies, I find this makes things more confusing, so I prefer to keep these as separate commands.

2They might still have I from your earlier attempt, or not, depending on their Git version ... and whether you made an attempt after all. If they do have I, that's fine: I's hash ID is unique to that commit.


Rebase

What git rebase does, in a nutshell, is to copy some commits to new-and-improved versions, and then move a branch name.

Nothing—not you and not Git itself—can change any existing commit. All commits are frozen for all time. That's what makes the hash IDs work. But you can take a commit out, and work on it, and make a new commit that's just as good as the original in terms of what changes it makes, and better than the new commit in that it goes in the right place.

That is, suppose you have:

          I   <-- master (HEAD)
         /
...--G--H--J--K   <-- origin/master

What if there is an easy way to have Git copy existing commit I to a new-and-improved commit that comes after commit K?

There is, and it's git rebase. We run:

git rebase origin/master

and Git lists out all the commits on our master (I, H, G, ... on back) and then strikes out from this list all the commits that are on origin/master (K, J, H, G, ...). That leaves commit I. (If we had more commits, such as I-L, we'd have that list here, and the next step would copy two commits instead of one.)

Our Git then uses detached HEAD mode (which we'll just gloss over) to copy each commit, one at a time, to come after commit K, the one we named with origin/master:

          I   <-- master
         /
...--G--H--J--K   <-- origin/master
               \
                I'  <-- HEAD

Having copied all the commits, our Git then yanks our name master over here, to the last-copied commit, and re-attaches HEAD to master:

          I   [abandoned]
         /
...--G--H--J--K   <-- origin/master
               \
                I'  <-- master (HEAD)

Since commit I does not have a name, we won't find it directly, and since I' points to K which points to J which points to H which points to G, we won't find it that way either. It will seem as though we somehow altered existing commit I.

We didn't—I' has a totally different hash ID from I—but since we're the only Git that had I, nobody else needs to know this. We can now run:

git push origin master

to send commit I' to the other Git, and ask them to set their master to point to I'. As long as their master still points to K right now, this will succeed.

torek
  • 448,244
  • 59
  • 642
  • 775