Alwaysblue
Alwaysblue

Reputation: 11830

How to push commit after checking out previous commit and doing changes in it

I am sort of afraid that my current working code doesn't get screwed.

So let's say I have following commit

7abd390c269d4abc9f3208d3c1b0a27ef5ae4162 -> Latest commit 

5488da9335eb51eed274aa9b4a0f8f205643a8e0 -> 2nd commit

a31e310304404eb4452e6465e0a92dd472c5329a -> 1st commit

I checked out my first commit using

git checkout a31e310304404eb4452e6465e0a92dd472c5329a

And started doing changes in here. Now I want to commit this and push it to github.

but Things I am trying to avoid Git pull and Git Merge

Because at this point of time I have done so many changes.

Question: Can anyone tell me if it possible to force push this commit and If yes then how?

Upvotes: 0

Views: 1752

Answers (2)

torek
torek

Reputation: 487883

TL;DR

What you probably want to do is to create a new branch name for your latest commit, e.g., git checkout -b newname. You can then use this new name for all your work, or use it to remember your commits while you go back to older names and do work.

Long

I checked out my first commit using

git checkout a31e310304404eb4452e6465e0a92dd472c5329a

Doing this produces what Git calls a detached HEAD. While this may sound scary, it's actually not fatal, it just means that HEAD is not attached.

Normally, the special name HEAD (written in all capitals, just like this1) is attached to some existing branch name. A branch name, in Git, is how Git keeps track of who's doing what work. More specifically, the name itself stores the raw hash ID of these commits. So a branch name like master or develop would store that big ugly hash ID, the a31e310304404eb4452e6465e0a92dd472c5329a thing. That lets you, the human, assign a meaningful name to this otherwise seemingly-random thing.

You're not doing the normal thing, and that means you really need to know what you're doing. Otherwise you'll lose track of your commits, and things will be painful.


1On Windows and MacOS, you can use head in lowercase, but that's something of an an accident / quirk of the system. It's best to stick with the all-uppercase variant, since the lowercase one won't work on Linux. If you don't like typing out HEAD in all caps, you can use @ instead, to mean the same thing.


Commits and branch names

Let's talk a bit more about how Git normally works with branch names, because this part is a little tricky, and yet, is one of the keys to using Git successfully. The first thing to understand is that Git itself does not care very much about these names: they are mostly for humans to use. Git mostly cares about the hash IDs, those big ugly strings of letters and numbers. Those uniquely identify each commit, and commits are the reason that Git exists at all. So they're what Git cares about.

Let's look at a really tiny, almost-new repository, with just three commits in it. These three commits will each have their own unique hash ID, but to save us a lot of headache, I'll use single uppercase letters for them instead. I'll call the first commit ever made A, the second commit B, and the third one C.

Each commit stores a complete snapshot of a source tree, along with some metadata, which is just a fancy word for the stuff git log can print about the commit: who made it and when, and what log message they entered for it. The metadata also includes the raw hash ID of the commit's parent commit, which is the commit that was in place when the person made that commit.

In other words, each commit points back to its previous commit. So let's draw that:

A  <-B  <-C

Commit A, being the very first commit, has no parent, because it can't. But commit B remembers that A was the commit when B was made, so B points back to A. Likewise, commit C remembers for us that B was its parent, so C points back to B.

What we—and Git—need at this point is a way to find the big ugly hash ID for commit C. This is where branch names come in: the branch name master can hold the big ugly unique hash ID for C, like this:

A--B--C   <-- master

We say that the branch name, master, points to the last commit in the chain. We use the branch name to get the hash ID, and that lets us (via Git) get to commit C. Commit has B's hash inside it, so from C we can find B, and from B, we can find A. A has no parent, so we can stop at this point.

Now, we if we only have the one branch name, it's pretty simple. We git checkout master, which gets us commit C. Then we make some changes, git add our changed files, and run git commit. This packages up the new snapshot, adds our own name and the current time, adds our log message, saves C's hash ID, and makes a new commit, which now gets a new, unique, big ugly hash ID, and now the key step happens: Git writes the new hash ID into the name master:

A--B--C--D   <-- master

But what if we have two branch names? Let's create a new branch name now:

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

Right now, both names store the same hash ID, the one for commit D. Now Git needs to know which branch name to update. So to do that, Git attaches HEAD to one of them, whichever one we give to git checkout. For instance:

git checkout master

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

but:

git checkout develop

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

Either way, we're starting from commit D. The difference is which branch name changes when we make new commit E:

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

The thing to remember here is this: Whenever we add a new commit, Git makes the new commit have the previously-current commit as its parent. Then Git writes the new commit's ID into the branch name to which HEAD is attached.

Note that the git checkout step does two things:

  • it selects the commit that the branch name points-to; and
  • it attaches HEAD to that branch name.

Here, the first point did not matter only because both master and develop pointed to the same commit.

A detached HEAD is almost the same, but there's no branch name involved

If you git checkout a commit by its raw hash ID, then Git still selects that commit to be the current commit, but this time, it doesn't attach HEAD to a branch name. Let's say, for instance, that you pick the hash ID of commit C. There is no branch name pointing to C, so Git has to do this instead:

          E   <-- develop
         /
        D   <-- master
       /
A--B--C   <-- HEAD

All we did here was to swing D and E up out of the way so that we can draw HEAD in pointing directly to commit C. What Git did here was to write the raw hash ID into the name HEAD, rather than having the branch name in HEAD. (If you want, you can look at the file .git/HEAD: it either contains a raw hash ID like this, or, if it's attached to a branch, contains the text ref: refs/heads/branch.)

Now we can make a new commit as usual, by editing files, running git add as needed, and running git commit. Git collects our commit message as usual, and makes a new commit as usual. The new commit's parent commit is the current commit—commit C—whose hash ID is in the HEAD file. Then, instead of writing the new commit's hash ID to the branch name in the HEAD file, Git just writes the new commit's ID into HEAD directly:

          E   <-- develop
         /
        D   <-- master
       /
A--B--C--F   <-- HEAD

If you go on to make two more commits, the process simply repeats:

          E   <-- develop
         /
        D   <-- master
       /
A--B--C--F--G--H   <-- HEAD

Note that Git can easily find commit H right now, because its hash ID is in the HEAD file. From H, Git can find G and F (and then C and so on too). But if you git checkout master or git checkout develop, for instance, Git will make commit D or E the current commit and will write the branch name into the HEAD file, losing H's hash ID.

If you save the hash ID somewhere, you can still work with it, for a while. Git tries not to throw out un-findable commits like this for a while.2 But it becomes hard to find, lost in a sea of nearly-identical commits distinguished mainly by their seemingly-random hash IDs. The best thing to do is to give it a name.


2This lasts at least 30 days by default, using the reflog for HEAD. The reflog contains all the hash IDs that HEAD named over the last 30 to 90 days, or longer, depending on a number of details that I won't go into here as this is already too long. :-)


Giving a name to a commit hash

What you probably want to do now, then, is to assign a name to remember the hash ID H. This is what branch names—and tag names, for that matter—are for. If you create a new branch name right now, by default, it will point to the current commit:

git branch xyzzy

          E   <-- develop
         /
        D   <-- master
       /
A--B--C--F--G--H   <-- HEAD, xyzzy

Note that this didn't attach HEAD to the name xyzzy. After creating the name, you can git checkout xyzzy to tell Git to switch from commit H, where HEAD points now, to commit H, where xyzzy points. That switch does nothing, but now the second part of git checkout applies, and Git attaches HEAD to the name xyzzy:

          E   <-- develop
         /
        D   <-- master
       /
A--B--C--F--G--H   <-- xyzzy (HEAD)

You can do both of these at once using git checkout -b xyzzy: that creates xyzzy, pointing to the current commit, and at the same time, attaches HEAD to the new name xyzzy.

Once you have done this, you now have a name for your commits. It's now possible to use git push to send the commits to another Git repository, telling that other Git repository to set its xyzzy to point to commit H (whatever H's actual hash ID is).

When the other Git repository gets H, it also gets G and F and, if required, C and B and even A (assuming this particular graph drawing is correct). Essentially, git push sends all the commits that are contained in—or reachable from (see Think Like (a) Git)—the branch, unless the other Git already has them. Then your Git has their Git set some name, usually the same name, to remember the last commit, the way branches do.

If you don't wish to save the two F and G snapshots and their commit metadata, it's now time to use git rebase or similar to rewrite some commits, but that's a topic for another question (and there are lots of answers on StackOverflow already about this).

Upvotes: 3

Matthieu Brucher
Matthieu Brucher

Reputation: 22023

If you force push your new commit, you will loose the other 2 commits that are online (I assume that you ave a new local commit, no reason why not).

Create a new local branch if you don't want to merge just now. But you will "have to" pull what there is online, and then merge.

Pulling and merging won't destroy your local merge, but you have to merge if you stay on the same branch because there can be only one head commit per named branch with git.

Upvotes: 0

Related Questions