user10727653
user10727653

Reputation:

git is Already up to date without any reason

git beginner here, i have been reading about this but still did not get it, could somebody clarify this. My question is i have git 'master' which is this :

const list = [ 'h', 'e', 'l', 'l', 'o'];
list.map((currElement, index) => {

  console.log("muutettu : " + index);
  console.log("The curre:  " + currElement);
  console.log("\n");
  return currElement; //equivalent to list[index]
});

and here i have my other branch 'kolmas' and its code :

const list = [ 'h', 'e', 'l', 'l', 'o'];
list.map((currElement, index) => {

  console.log("The current : " + index);
  console.log("The curre:  " + currElement);
  console.log("\n");
  return currElement; //equivalent to list[index]
});

i'm switching to branch master 'git checkout master ' then trying to merge branch 'kolmas' into this master branch(git merge kolmas), and i'm getting: Already up to date. But its not up to date, it did not change anything.

Upvotes: 0

Views: 137

Answers (2)

torek
torek

Reputation: 489828

If Git says "already up to date", it is. That doesn't mean you can't get what you want, just that git merge won't do it.

Git's merge process works by the commit graph

Before we can really talk about merging, we have to talk about the commit graph. But, before we can talk about the commit graph, we have to talk about commits themselves. People who are new to Git may think that Git is about files, or branches. These are not completely wrong, but they're more wrong than right, because Git is really all about commits.

What's in a commit

First, every commit is numbered. These aren't simple counting numbers though—we don't start with commit #1, go on to #2, and from there to #3 and so on. Instead, the unique number for a commit is a random-looking hash ID, like 4f0a8be78499454eac3985b6e7e144b8376ab0a5. It isn't actually random, but it's not useful to humans, who tend to just tune them out. That makes sense: we have a computer, why not let it remember the numbers for us? But they do have numbers, and this matters.

Meanwhile, each commit stores two things:

  • Each commit has a snapshot of all of the files that Git knows about. This snapshot is frozen in time: none of the files in the commit—no part of any commit, or of any Git object at all really—can ever be changed. Not even Git itself can do this.

    The files inside each commit are stored in a special, Git-only, compressed and de-duplicated form. The de-duplication is important since most commits mostly just reuse the previous commit's files. So no matter how many commits you make, if you just keep re-using the previous version of your README.md file, the new commit doesn't need any space to hold the file. (There might be a small amount of space for an internal tree object, but those too can be re-used, under the right conditions.)

  • Separate from this data, each commit has some metadata, or information about the commit itself: who made it—their name and email address—and when (date and time stamp), and so on. In this metadata, Git saves something that Git itself wants: the commit number—the hash ID—of this commit's parent commit.

The parent commit number, as saved in each commit, lets Git work backwards. We'll see how this works in just a moment.

The commit graph

Let's draw a tiny repository with just three commits in it. Each commit will have some big ugly unique hash ID, but let's just use uppercase letters to stand in for these three hash IDs: let's call the first one commit A, the second commit B, and the third commit C.

To draw them, we'll put in their names, and draw an arrow from each child commit back towards its parent:

A <-B <-C

The newest commit is commit C. Inside commit C we'll find all of your files, frozen forever in the form they had when you committed them, plus some metadata showing that you made the commit, when you made the commit, why you made the commit—your log message—and the raw hash ID of earlier commit B.

We say that commit C points to commit B. Git uses the actual hash ID of C to find the contents, which contain the actual hash ID of commit B. So now Git can find the contents of commit B.

Inside commit B, we have the earlier snapshot: all of your files as of whatever they looked like when you made commit B. We also have the raw hash ID of earlier commit A. Git can use that to locate commit A.

Inside commit A, we have all of your files as they were in the first commit. This time, though, there's no parent hash ID, because A was the very first commit. This is how Git knows to stop going backwards: history ends at this point, as there is no earlier commit.

Note that the arrows connecting commits can't ever change: they are part of the child commit itself, and no part of any commit can ever be changed. So we don't really need to draw them as arrows, as long as we remember that they point backwards. That's a little easier, and is what I will do now:

A--B--C

The one tricky part here is this: How do we, or Git, know or find the actual hash ID for commit C? This is where branch names come in.

A branch name just points to the last commit

Let's add a branch name master to this tiny repository:

A--B--C   <-- master

The name master literally just holds C's hash ID. That gives Git an easy way to look it up. That lets Git find commit C, in its big database of every Git object. From there, Git can extract all the saved files in commit C, or look up B's hash ID, or whatever it needs to do.

To add a new branch name, we just tell Git: make a new branch name. We must pick some existing commit and have this branch name point to this same commit. The obvious commit to pick now is C:

A--B--C   <-- master, develop

We've made a new name develop that also points to commit C. All three commits are now on both branches. We do, however, need to add one more thing to our drawing: which name are we using?

The special name HEAD selects the current branch name

If we use git checkout develop or git switch develop, we are telling Git: extract commit C. Both names select commit C, so in this sense it does not matter which name we use, but Git wants to remember the name. So we'll add HEAD in parentheses after the right name:

A--B--C   <-- master, develop (HEAD)

Now let's make a new commit D on develop. We'll use the usual method—modify some files, run git add, and run git commit—without explaining everything, and just assume that we've done it and made a new commit D. Git will (somehow) assign the right hash ID to commit D, and will make D point back to existing commit C as its parent, like this:

A--B--C
       \
        D

Now we need to add the branch names again. Here is the trick: Git writes D's hash ID into the current branch name, so that develop now points to D. The name master keeps pointing to earlier commit C:

A--B--C   <-- master
       \
        D   <-- develop (HEAD)

Note that commits A-B-C are still on both branches. New commit D is only on develop. If we now git checkout master and make a new commit there, we will get another new commit E, which we can draw like this:

        E   <-- master (HEAD)
       /
A--B--C
       \
        D   <-- develop

Putting commit E on its own row helps emphasize that E is only on master now, while A-B-C are shared; but we could also draw this as:

A--B--C--E   <-- master (HEAD)
       \
        D   <-- develop

which represents exactly the same state.

Summary so far, and the definition of the commit graph

  • Git stores commits.
  • Commits are numbered by unique hash IDs.
  • Commits store data (a snapshot of all files) and metadata (information about the commit, including a parent hash ID).
  • Branch names store the commit number of the last commit that is on the branch.
  • The special name HEAD remembers the current branch name.
  • Git works backwards from this last commit to earlier ones. That's what git log does, for instance.
  • The parent/child relationship between each pair of parent-and-child commits, like C vs D and E above, makes up the commit graph.

Now we can talk about git merge

What git merge does is in a way pretty simple. Suppose we have this graph:

          I--J   <-- branch1 (HEAD)
         /
...--G--H
         \
          K--L   <-- branch2

If we run git merge branch2 now, while branch1—and therefore commit J—is current, Git will:

  • locate commit J via the HEAD / branch1 combination;
  • locate commit L via the name branch2;
  • work backwards from both commits to find which commits are shared on both branches; and
  • pick the best shared commit as the place from which the merge will work.

In this case, commits G and H (and earlier commits not shown) are all shared. The best shared commit is commit H, because it's the latest one.1

To make this merge happen, Git will compare the saved snapshot in commit H—the merge base—to the saved snapshots in commits J and L. Ignoring all the actual comparison mechanism and how conflicts work and so on, this big picture should be clear enough: comparing H vs J will show what we changed, and comparing H vs L will show what they changed. To merge these, Git needs to combine these two sets of changes.


1Latest here refers to graph order, rather than date-and-time-stamp. If the timestamps on the commits are in the right order, commit H will have a later date than G too, but the timestamps inside commits depend on users having the right time on their computers, among other things, and that's not always correct.


The graph explains why you are up to date

Suppose that instead of the above, we have a graph that looks like this:

...--D------G--H   <-- master (HEAD)
      \    /
       E--F   <-- kolmas

That is, we're using commit H, through branch name master. Our current work-tree has the files from commit H. We now run:

git merge kolmas

and Git locates commit F, as pointed to by the name kolmas.

Now Git must work backwards. Starting from commit H, we go back one step, to commit G. Commit G is a merge commit, with not just one, but two parents. Its two parents are commits D and F. Commit F has parent E and E has parent D, so that the history we see by starting at H and working backwards splits apart after visiting G, visits all three commits F and E and D, and joins up again at D.

Meanwhile, starting at commit F ... we don't have to work backwards at all. Commit F is part of the history that we find, starting at H and working backwards. So the best shared commit, between F and H, is F itself.

Git would now need to:

  • compare F vs H to see what we changed;
  • compare F vs F to see what they changed; and
  • combine these two sets of changes.

But comparing F vs itself will show no changes. There's nothing to merge! Branch master is already up to date.

What you can do instead

It does not matter that the snapshot in F is different from the snapshot in H. What matters here is that F is an ancestor of H. If you want to get code from F into commit H, you'll have to just take the code out of commit F, put it in your work-tree, use git add, and run git commit to make a new commit.

You can make the new commit on a new branch, if you like. First, let's make a new branch name that also selects commit H:

...--D------G--H   <-- master, retry (HEAD)
      \    /
       E--F   <-- kolmas

Now let's copy something out of kolmas's last commit F—in whatever way you want to do this copying—and make a new commit I:

                 I   <-- retry (HEAD)
                /
...--D------G--H   <-- master
      \    /
       E--F   <-- kolmas

You can now git checkout master; git merge retry, because commit I is ahead of commit H, rather than behind it.

Note, however, that this time, the merge base of H and I is commit H itself. (Walk back one step from I. What commit did you reach? Start with the name master. What commit does it select?)

Once again, the merge operation will be trivial:

  • compare H vs H, to see what we changed;
  • compare H vs I, to see what they changed;
  • combine the two sets of changes.

Obviously, combining nothing (H-vs-H) with something (H-vs-I) will just produce the something, so if you allow Git to do so, it will take a short-cut here. Instead of doing a real merge, Git will perform what it calls a fast-forward. This is not a merge at all: Git just checks out commit I and drags the name master forward:

                 I   <-- master (HEAD), retry (HEAD)
                /
...--D------G--H
      \    /
       E--F   <-- kolmas

(and now there's no real reason to draw I on its own row).

You can force Git to make a real merge, if you like. After running git checkout master, you can git merge --no-ff retry, and Git will go ahead and do the H-vs-itself comparison. The final combination will produce a new snapshot-commit J, which will be a merge commit with two parents:

                 I   <-- retry
                / \
...--D------G--H---J   <-- master (HEAD)
      \    /
       E--F   <-- kolmas

This time we need to keep commit I on its own row so that we can draw the name retry pointing to it, while drawing the name master pointing to new commit J. (If you're doing all this on paper or a whiteboard, or with a more flexible way of drawing than text in StackOverflow, you can put the names wherever you want and draw curved arrows, if you prefer.)

Upvotes: 2

Kassian Sun
Kassian Sun

Reputation: 809

  1. Use git checkout master; git diff kolmas to check whether master and kolmas are same
  2. If they're same and you did change the code, use git status to check whether they're committed.
  3. If you can see the changes with git status, git checkout kolmas then use git add and git commit to commit it.
  4. After committed the changes, you can git checkout master and do git merge now

Upvotes: 1

Related Questions