😁Git and Github
All you need to know about Git and Github
Last updated
All you need to know about Git and Github
Last updated
When we first initialize git, using git init, our working folder turns into a working directory where .git folder is created. Not only this, but when you use Mac, for some reason, .DS folder gets created. You can do git status and you see this folder. We can remove this as shown below.
A general idea -> We have 3 areas
Areas
Working Area (Not staged area) -> When ever any files are changed/modified
Staging Area -> We can add working area files to staging area using git add -A
command
Committed Aread -> We can add all the files in staging area into committed area as a commit.
A folder can be turned into a working directory by having .git file in it. This can be done by using command git init
Whenever we modify any file, that file will become untracked file. Meaning
When file initially gets created and before adding it to staging area, the file/files will be untracked
git status
gives the following information
branch info
Whether or what files are in working area (not yet in staging area) Or files in staging area (not yet committed)
git log gives the details of all the commits:
Commit ID (unique Id for each commit)
Head commit pointer (tells which commit is the head)
Author of the commit
Date of commit
Commit messge
Branch is a unique set of code changes that holds commits
Default branch is master
If we want to add some features, we might not want to spoil the code of the existing branch by adding unwanted commits. Adding unwanted commits and then reverting back will be a big headache. In some cases we might not have access to add commits to the master branch (for example open source projects). In such case we better create a replica of the master or some other branch we would like to add our commits to. This replica is called a sub-branch or feature branch which we can name anything. To this branch, we start adding commits and then once our code works properly we can merge it back to master branch/ to branch which we branched out from.
This process is called merging branches
The number of branches in our project and the current branch we are working with can be checked using
First Way
To create a new branch from master and still be in the master branch
To switch to second-branch
Second Way
In the above process, we first create a branch with git branch second-branch and then switch to that branch with git checkout second-branch. But we can do both of these Creating and Swtiching in one single command.
Note : The above command can create two branches with same name, so I suggest you to run this command if the branch with that name doesn't exist
Note : In git world, checkout means switching the existing branches or creating a new branch and then switching to it.
Third Way (Git version 2.23+)
The word checkout can be confusing so git introduced new word switch to switch branches and create new branches and then switch to them.
Based on above commands we now know how to create a branch from one branch. Let's say we did that and added some changes to the second-branch. And now, we need to merge the second-branch into the master branch.
Let's say I need to merge second branch into master branch. The source is second-branch and the destination is master branch. I need to be in the destination to receive the source.
Meaning, I need to be located in the master branch inroder to merge second-branch into master
Head is the latest commit of a particular branch. Head points to the latest commit in a branch. That's the reason when we switch from one branch to other branch the head might change as the switched branch would have the latest commit as shown in the fig above.
In the below example, we created two branches from master
branchOne
branchTwo
We added two commits to branch two and one commit to branch one. Now, when we log, each branch shows different head as the heads are pointing to the latest commits of that particular branch.
If we checkout branches, that means we are switching from one branch to another as explained above which inturn means we are pointing to the latest commit of that particular branch.
But, can we checkout a particular commit? A commit could belong to multiple branches.
Yes, we can checkout a particular commit. Checking out a particular commit is called Detached Head. We are detaching a commit and our head points to that particular commit, hence it's called detached head.
When the head points to particular commit then this means that head is not pointing to latest commit of any particular branch. The head is pointing to a commit that can be present in multiple branches. We can do lot of things detached heads like
Changing content of that commit and adding back to a particular branch
Many more which we will explore later
Let's look at 3 stages.
Before checking out the commit
Checking out the commit
Switching back or checking out back to master
Before Checking out the commit
We are in the master branch. Let's see some of the commits in the master branch and then checkout a commit
2. Checking out the commit
Checking out this commit is done by command
Now we have the head pointing out to this commit and not any branch's latest commit. That is why we have detached head.
Now, on doing git branch we get this commit (head current pointer) listed as a branch, but this is actually not a branch as we know.
Considering above explanation we know detached head is some random commit that might belong to one or more branches but that itself is not a branch. So what's the difference between a detached head and branch and why we need detatched head.
The first difference is "Detached head" is a commit that doesn't belong to a particular branch so it is not a part of branch. Where as branch is a commit that is linked to a particular branch.
When we switch back to other branch, the detached head dissappears if we don't merge it into a branch.
3. Switching back to master
Why we need detached heads? We can do many things with detached head. For example we can modify something in that commit and merge it back to some branch.
Let's see how to delete
Working directory files (part of previous commit)
Unstage changes
Staged changes
Latest commits
Branches
Before we understand how we delete files let's see how staging area handles the files.
The above command gives info about all files present in staging area. Meaning, everything that was a part of previous commit.
Let's now say I delete a file from working directory (remember all the files were previously committed and all are in staging area now). Having said that, even though I delete a file from working directory, it would still be present in staging area.
For next commit, we need to remove this file. For that we first need to delete this file from staging area. So once we delete the file from working directory like shown above, we need to add the changes of file removal in staging area like below.
We know that whatever is in staging area will be committed for next commit if not already done. So to see what will be committed next, we need to see what files are in staging area.
Staging area files --------> Next committable files
To see those files
Tracked file - The file that is already a part of staging area. (add command used atleast once on this file)
Untracked file - The file that is added newly but never added to staging area with add command
Let's say we have committed everything and now our staging area files is equal to committed files. Let's now add some changes to an existing file.
After some time, now, we want to get rid of those changes. Meaning, we need to get rid of the files that were not added to staging area.
How to get rid of changes made? In this case we need to restore back to a state where it should be exactly after committed. Meaning, we need to get rid of line number 5 which was added after committing.
This line 5 will now be deleted with the command
What is the meaning of this command?
Well, we know checkout is used to switch branches. When we switch to a branch, it means we get all the files in STAGING AREA OF THAT BRANCH into picture.
So when we do git checkout
, it means we are showing the staging area of a particular branch.
So the above command git checkout -- initial-commit.txt
means that, show me the staging area of this branch only (--) for the file initial-commit.txt. That means what ever file content of initial-commit.txt
is in staging, that will be the initial-commit.txt.
In the above command -- is optional. So we can write
Let's say we need to revert multiple files, then we can say
From git 2.23+ version
What if I need to delete a newly created file which is not yet staged (not yet added to staging )?
Let's say I just created a file and not yet run add command. I need to delete that now.
First list the files that need to be deleted. Then you can delete them
The above command just lists the files that could be deleted which are untracked files.
To delete them use the command below.
We saw how to undo unstaged changes (where if the content is modified but not added to staged area even once then we can simply checkout all the files in staging area to bring it to focus so that all the changes made will be lost).
But let's say I make some changes to a file and add it to staging area. Now, in this case how to revert back. We can not do like we did above because, above, we just brought staging area files into focus where staging area was clean. But in this case, the staging area got polluted as we added a file into staging area.
If we think logically the solution would be
First unpollute or restore staging area back to normal. How? The lastest commit is what the expected staging area to look like correct? So we can make the latest commit as our staging area. This is possible by command
Now latest commit is what we have in staging area. Meaning, the file changes are unstaged at this point. Now we can remove unstaged file as learned in our previous content above
1st way / old way (less than git version 2.23) of removing staged files.
2nd way (git 2.23+)
git reset
helps to not only reset the staging area (makes staging area equal to latest commit) but also helps in deleting the commits by resetting the head from latest commit to old commit.
We have different types of deleting the commits.
soft reset
normal reset
hard reset
Undoing the commmit. It's like not doing commit at all. The general process we follow is
Creating a file + Adding files to staging area + Committing the files in staging area
But in soft reset we just get rid of last part and it's undoing the last part. The files get back to staging area and the commit gets deleted
Committing a file -> File changes comes to staging area -> File changes now in working directory
This can also be used to change or modify the commit message.
Undoing a commit + removing the changes from staging area. It looks like this
Committing a file -> File changes comes to staging area -> File changes now in working directory
Undoing a commit + removing the changes from staging area + working directory file changes. Basically, we remove everything until last commit. So the working directory becomes equal to previous commit. It's like we never wrote any code after previous commit.
Committing a file -> File changes comes to staging area -> File changes now in working directory
To delete a branch that is already merged into another branch
To delete a branch even though that is not merged (kind of force)
Let's say I did a commit. Now, if I check the status, everything will be clean in the working directory. Now I will introduce some code, but suddenly I want to save these local changes and see how the work space was before when I didn't yet start the code after the commit.
One of the options to make the working directory equal to previous commit is by using the command git checkout .
With this command I would get back my clean working directory like how it was after the commit and before starting to code locally but the biggest problem is I would lose the local changes which was not added to the staging yet.
Git stash solves this problem. It makes our working directory clean and resets back the working directory to how it was after the commit and before starting the work locally + it saves the local changes made in stashed area.
Let's do these steps
Now the working directory is clean and nothing is changed to add to staging area
Do some changes to file.txt
Now I want to see the workspace how it was after the previous commit, but at the same time I don't want to lose this above change to file.txt. So I can stash here
Now the file.txt has no content and the working directory is clean (nothing to be staged)
Let's say I want to bring back this stash to working directory
Let's add another feature to the file.txt
Let's stash this also
Now we have two stash (1 with first line in file.txt and the lastest stash with both the lines in file.txt)
Let's see all the stash
Note that you can't apply a stash when you are already in a stash. You either commit the current stash and then apply another stash OR you stash again and then apply the required stash.
Now let's apply stash 3. Note that stash 0 is always the latest stash
How to apply the latest stash and at the same time delete that stash
How to delete a single stash
How to delete all the stash
How to add meaningful message to stash
Two types of merges
Fast-forward merge
Non-fast-forward recursive merge
The feature branch is branched out from master. We then don't add commits to master until we merge back the feature.
Created two commits m1 and m2 in master branch
Create a new branch called feature and switch to feature branch
Now let's switch back to master and merge feature into master
The above stuff is explained in a digramatic way
Now you see, when we merge feature branch back to master, we get all the commits of feature branch (f1 and f2 in this case) displayed in master. We might not want to know all the incremental changes happend in feature and we need all the commits of feature branch to be combined into one single commit in master when merged.
This squash flag gets the latest commit of feature branch and put it to staging area of master. This let's you commit with your own message in master branch.
f1+f2 will be present in f2 commit. Merging with squash will add f2 to staging area of master. We can then commit that as we need into master as a normal commit. This keeps the master branch clean.
This is generally used when, after creating a branch from master we would have additional commits in both master and the new branch.
An additional commit is created in master after the merge of feature branch into master branch.
The latest merge commit holds all other commits below, and if you reset the head 1 time, then all the below commits will be gone.
We have seen two types of merge
Fast forward merge - If we create a branch from master -> add commits to feature --> Merge back to master (No changes was done on master until merged back.
Non Fast forward merge - If we create a branch from master -> add commits to master -> add commits to feature -> Merge back to master (There was a change in master after feature was created)
Let's consider Non-ff case where we have created a feature branch, then we create a new commit in master as well.
We notice that we need this change done in master in our feature as well before we merge back feature into master. Then we can think of this rebase. In rebase we re-base (change the base of commit of master to one commit ahead) and then merge the copy of commits (not the original commits - so you should be careful before using it) from feature. This would be a fast-forward merge.
What actually happend here?
Git will say "I want to base latest commits of feature branch on the latest commit of master branch."
Master branch
Feature Branch
Note that, in feature branch, it branched out based on m2. Once we rebase, the feature branch would have rebased (will have m3 - latest commit of master) in feature branch. In the master, f1 will be the copy (hash will be different) of f1 feature.
Now, let's reabse
Now let's merge feature into master. Since the latest commit in master (m3) is now the base commit in feature, the merge type would be Fast-Foward merge.
Let's say I am working on master and make a typo in a commit. My colleague will create a branch out of master and start working, but then notices the typo caused. He will first correct the typo in the feature branch and commit it (single commit used to fix the typo). Then he does additional commits on his feature.
Later from master, I can cherry pick that particular commit that fixes the typo. Meaning, I can merge a single commit into my master which has the typo fixed. Note that, in cherry pick, the commit Id in master changes (it's a copy of the commit and not the same commit).
Now switch to master and type the command
The below command
is used to connect git and github (let git and github know about each other). But to bring the content of git to github, we use git push
and for getting github content to local we use git pull
Origin - Remote repo URL we would like to access
master / main - Remote branch we would like to push to
With git remote add origin command, we establish a connection between local and remote repository. This connection can't be established until we push something to remote repo. The process involves some more steps.
Command git branch
gives local branches. But git branch -a
gives an extra entry of remote-tracking-branch. Remote-tracking-branch as said, gets created when we first type git push origin master
command.
Now that we know what is origin (remote), remote-tracking branch and so on, let's see
How to create a branch locally and push it to origin
How to create a branch in origin(remotely) and then pull it to local
Let's create a feature branch locally and then push it to remote.
To see remote branch
To have the remote changes updated locally (in this case we need to get feature-remote-branch to local
Now with git fetch
, the Remote tracking branch (Read only remote branch in local) gets updated but let' s say you add another commit remotely and then fetch again with git fetch,
the local branch (which is local version of same branch) would not get updated, but the git pull updates local branch as well. Let's understand how.
To just use git push and git pull, without origin and branch name, we need to be on local tracking branch. To create local tracking branch below is the command. We don't need to worry about local and remote tracking branches but it's good to know how git and github gets connected using local and remote tracking branches. The name of local tracking branch and remote tracking branch must be same.
When we do a git clone, the local-tracking-branch is also setup by default so that we can use git pull and git push.
Until now we manually created the local-tracking-branch and remote-tracking-branch which are what connecting the git and git-hub.
By using -u (upstream) while pushing, the local-tracking-branch and remote-tracking-branch gets created automatically.
For deleting branch and commit on github please watch the video 82. Not making note of it as it's not so important