Home > other >  Why Git merge not working properly with local branch?
Why Git merge not working properly with local branch?

Time:06-17

I have a local_branch which has local changes , I want my main branch to merge into it. But when I do “git merge origin/main” it shows me already upto date . But I am sure I am not as I can see some changes in main that I don’t have locally. It could also be that I accidentally resolved conflicts to mine and that’s why it is showing already up to date. How can I get out of this situation ? I want my local branch to merge the main branch

CodePudding user response:

You may need to update your local version of the main branch via a git fetch:

# from local_branch
git fetch origin
git merge origin/main

The above git merge would be using the most up-to-date remote tracking branch, which should reflect the latest changes on the main branch from the remote.

CodePudding user response:

I have a local_branch which has local changes, I want my main branch to merge into it. But when I do “git merge origin/main” it shows me already up to date ...

That means exactly what it says: you've already merged origin/main, so there's no work left to do.

It's worth noting a number of things here:

  • First, git merge does not mean make same. If it did that, we would lose work! Instead, it means combine work.

  • Second, the definition of work and combine are tricky. The "combine" part isn't that hard once you figure out what it means to "find work that has been done", but that part is kind of hard.

  • Last, the "direction" of the merge matters somewhat, although less than most people think at first. We'll see this below.

To understand why the definition of "work" is tricky, let's start with some very basic things you need to know about Git.

Git is about commits

Those new to Git often think that Git is about files, and it's not. It's about commits. Each commit holds files—if they didn't, Git would be useless—but what Git really cares about here is the commit. You get all of a commit, or none of it: you never get half a commit.1

Those new to Git often think that Git is about branches, and it's not. It's about commits. Git organizes commits into branches, and we (humans) use branch names to have Git help us find the commits, but again, what Git really cares about here is the commits.

Aside: I also find that branch is a bad word in Git—not bad as in the kind of "four-letter word" profanity, but rather, bad as in nobody knows at first what you mean when you say branch, in the same way that bad word brings up profanity, or how "bad" sometimes means "good". That is, the word branch has multiple meanings in Git, and people will use this word in several sentences—or several times in one sentence—with each occurrence meaning something different!

So, okay, Git is about commits. It's possible to use Git without having any branch names at all (but this is not a good idea, at least not if you're a human). This means we need to know exactly what a commit is and does for you. Let me start with this though:

  • A Git repository is, at its heart, a collection of two databases, one usually much bigger than the other.

  • The usually-bigger database holds Git's commits and other Git objects (supporting objects that let Git hold onto commits and/or make commits work better). Everything in this big database is read-only, and the database itself is mostly (though not 100%) append-only. That is, commits don't normally go away, and no commit, once made, can ever change. Instead, we just add new ones.

  • The objects in this big database are numbered, with big, ugly, random-looking hash IDs or (more formally) Git object IDs or OIDs. These were exclusively SHA-1 hashes at one time (that's no longer true) so old documentation will call these SHAs and the like, too. Git literally needs the hash ID (the OID) to find the object. So every commit has a unique hash ID; that hash ID, in a sense, is the commit.

  • The (usually smaller) second database holds names: branch names, tag names, remote-tracking names, and lots of other kinds of names. Git provides these for us humans, for whom hash IDs are unusable except via cut-and-paste with the mouse or whatever. Instead, we get to use things like branch names: a branch name like main or develop translates into a (one, single) hash ID, which—for a branch name—is defined as the latest commit that is "on" that branch.

Hence, by using these two databases, we can give Git a name (local_branch for instance, or origin/main). Git will use that name to look up a commit hash ID as needed. Git then uses the hash ID to look up the actual commit in the big all-Git-objects database.

When I say the commit's hash ID is unique, I really mean unique: we will "clone" a Git repository with git clone, for instance, and when we do, we copy all the Git objects from the other repository's database. These copies have the same OIDs as the originals. (That's why the objects are read-only: the OIDs are cryptographic checksums of the data. Change the data, and you've changed the checksum and have a different object. Since the objects database is largely append-only, all you have done is add a new object: the old one is still there, under the old ID.)

So that's what we need to know about the databases:

  • The big one holds objects indexed by their hash IDs, including the commits. That's how Git finds any specific commit: you give Git the hash ID and Git simply looks it up. It's either there—the whole thing is there, never just some part of it—or it's not, and because it can't change, if you have a commit with some known hash ID, and someone else has something in some other Git repository with the same hash ID, you both necessarily have the same commit.2

  • The little one means that most of the time, we don't have to use a raw hash ID: we can use a name and Git will look up the raw hash ID for us.

Now let's move on to the commit. A commit, in Git:

  • Is numbered, as we just saw.
  • Contains two things: a full snapshot of every file, and some metadata, or information about the commit itself.

On hearing that every commit has a full snapshot of every file, people often object: Won't that make the repository grossly huge? It would, except that:

  • the files in the snapshot are compressed, sometimes highly compressed; and
  • the files are de-duplicated, which in many ways is even more important.

(Git achieves the de-duplication by storing each file's content as an object in the big objects database. The high-degree-of-compression happens later in the game when objects become packed into a "pack file", which we won't cover at all here, as none of this matters for simply using Git.)

While the files are what we care about when we go to do work, right at the moment, it's actually the metadata we need to care about. The metadata of any one given commit contain, among other things:

  • a user name and email address for the person who made the commit;
  • a date-and-time stamp;
  • a log message, which git log can show or summarize; and
  • crucially for Git's operation, a list of previous commit hash IDs.

This list is usually exactly one entry long. The (single) commit hash ID thus stored in any given commit is called the parent of the commit.

This trick of storing a parent commit hash ID in each commit means that if we can find the last commit in some chain of commits, we can use that to work backwards. Suppose H stands for the hash ID of the last commit in some chain of commits. We say that the hash ID stored in H points to the previous commit—let's call it G for simplicity. We can draw this like so:

        <-G <-H

That is, commit H is literally pointing backwards to commit G. Of course, G is a commit too, so it too has a parent, which we can call F:

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

and since F has a parent, Git can start at H and work back to G and then F and so on. Eventually, all this working-backwards will come to the very first commit ever, which won't have a parent, because it can't. Its list of parents, in its metadata, will be empty, and that gives us the whole chain:

A--B--C--D--E--F--G--H

(our repository apparently has just eight commits in it).

How do we find H? We already know: we use a branch name like main to do that:

A--B--C--D--E--F--G--H   <-- main

Note that I have gotten a bit lazy about drawing the arrows between commits. That's partly because they literally can't change—they're metadata in the commits, and no part of any commit can ever change—and also because of the more complicated graph I'm about to draw.


1A still-being-developed feature called partial clone lets you get "half" (or some fragment of) a commit, with a promise that the rest will appear later if/when needed. This does a lot of damage to the way you should think about Git initially, so let's pretend it doesn't exist.

  • Related