Proof of Concept: Implementation of beginner to advanced concepts of git and GitHub.
- What is Git?
- What is a Repository?
- Commits & Snapshots
- What is GitHub?
- Git & GitHub Installation
- The Basics of Git
- Making a new git repository (initialization)
- Logical States in Git
- Commits in Git
- Overview of a Git Repository
- Initializing a Git Repository w. Existing Files/Project
- Commit History w. Log and Show
- Express Commits
- Rolling Back Changes
- Creating New Commands - Git Alias
- Renaming & Deleting Files Using Git Bash
- Renaming & Deleting Files Not Using Git Bash, but the OS Commands
- Excluding/Ignoring Unwanted Files in our Git Repository
- Advanced Version Control Using Git
- Basics of GitHub
- SSH Authentication
- GitHub Repository
- Cloning a GitHub Repository
- Fetch & Pull in GitHub
- Updating Remote References of a Repository
- Creating New Files in GitHub on a New Branch
- Synchronizing Changes made in Remote Repository to the Local Repository
- Creating Branches on GitHub
- Creating Branches Locally and then Pushing them to GitHub
- Comparing and Pull Requests in GitHub
- Merging Branches Locally
- Locally Switching to a Branch on GitHub
- Cleaning Up By Deleting Branches and References
- Pull with Rebase
- Changing Default Branch in GitHub
- Dealing with a Conflict while Pulling
- GitHub Tags & Releases
Git is a fast, scalable, distributed revision control system with an unusually rich command set that provides both high-level operations and full access to internals.
In simple words, it is basically a system which allows the user/developer to maintain different versions of their softwares/projects across their cloud service (which is GitHub, bitbucket, etc) or in the local machine (using git bash).
Git is super fast because it supports local operations. It means that we can completely work disconnected from the internet. All of the changes made locally, will be pushed onto the remote repository when there's an active internet connection available, automatically.
Git is Open Source and has an extremely active community thereby, finding help/resources related to Git & GitHub is extremely easy.
Repository is a collection of files managed by Git. These collection of files are also managed with their entire history (version, names, etc).
The repository in the local machine is considered as the Working Directory or the Workspace. The Workspace can contain, both files from git and other files and directories related to the project.
Normally, within the root folder of the repo, there's a pseudo-hidden .git directory which contains its own collection of files and directories that make up the entire internal structure of the repository.
Git works by saving the current state (state: changes made till now, meaning the current version of the software) of all its files it manages, into snapshots called Commits. A commit can contain more than one changes made to the files inside your repository.
Git doesn't version the directories, but only versions the files inside your repository. As we make changes to the state of the repository, the commits are saved onto a timeline known as a Branch.
A git repository will have at least one branch known as the master branch. Additional branches can be created, which we will look into later.
GitHub is a git repository hosting service provided by GitHub, Incorporation (owned by Microsoft) at GitHub.com.
There are other hosting services, and one of the popular ones is bitbucket. But GitHub is the most popular git repository hosting service online. GitHub stands out as a git repo hosting service because it provides unlimited free public repositories to be maintained by an individual/organization. It means that as long as you're comfortable with others being able to look into your code, you can have as many public repositories as you want.
Therefore, in order to have private repositories in your GitHub account, you've to pay for the service. Because of this, most of the open source community has adopted GitHub as their main choice for hosting their git repositories. Even the source code git itself is hosted on GitHub.
GitHub also has some additional features such as Issue Tracking, GitHub Webpages, Source Code Snippet Sharing (in the form of gists), etc to support developer projects hosted on GitHub.
Goto: https://git-scm.com/ and follow the instructions there to download Git for an OS.
After installing Git, open Git Bash and set the username and email using the following command syntax:
git config --global user.name "Your Name"
git config --global user.email "Your Email"
Example:
git config --global user.name "John"
git config --global user.email "[email protected]"
Install P4Merge for Visual Compare and Merge Tool. Download P4Merge from here. Choose the installer that matches your system and download the installer to install P4Merge Visualizer Tool.
After installation, type in the following commands in Git Bash to install P4Merge as the default graphical compare tool.
git config --global diff.tool p4merge
git config --global difftool.p4merge.path "Absolute-Path-To-Where-P4Merge-Is-Installed"
(Example: git config --global difftool.p4merge.path "C:/Program Files/Perforce/p4merge.exe"
)
git config --global difftool.prompt false
The following commands are used to make P4Merge as the default graphical merge tool using git bash:
git config --global merge.tool p4merge
got config --global mergetool.p4merge.path "Absolute-Path-To-Where-P4Merge-Is-Installed"
git config --global mergetool.prompt false
We will learn how to make new repositories, how to get information/status of a repository, basic workflow of a repository, how to apply file operations like renaming, moving & deleting files in the git repository, excluding unwanted files from the git repository and undoing mistakes.
To make a new repository in your system, first create a new directory anywhere in your system using mkdir project
(let's say we use "project" as the name of the directory).
Then simply inside the "project" directory, we run the git init "repo-name"
command, where "repo-name" can be any name you want without the double quotes (ex: git init demo
).
There are 3 local states related to files being managed by git. These three states are:
- Working Directory.
- Staging Area.
- Repository (.git directory) aka Commit History.
The Working Directory contains our application's files and folders, and therefore, git is aware of these files as a logical state known as "Working Directory".
On the other hand, we've the .git folder, where all the commit history is saved and thereby git knows it as the final state (in local machine) which is the "Repository" state. .git folder contains all the committed and saved changes of the repository. Anything in .git folder is a part of git's history.
In between is the git's Staging Area. This state is used to prepare for the next commit. Files are moved from the modified "Working Directory" state, to the Git "Staging Area" and then finally committed into the Git "Repository". These aforementioned 3 states of git are specific to the local git repository.
Now, even in the remote repository (the same repository which is hosted on GitHub), we have these same 3 states. Conceptually, we can think of the Remote repository as a state in itself, because all the changes made inside the local environment is saved in the cloud, at a remote server, thereby, we are accessing the remote repository.
This is how the files go from one state to another:
Working Directory => Staging Area => Repository (.git folder) => Remote Repository.
To get the information about a git repository, we can use the git status
command. We'll see that (for a newly created repository), we'll have the information related to the branch (which will be master) and the commit message (which will be initial commit).
To commit changes made inside a git repository, we first need to make changes inside the repository. After that, we can save the changes and again type in the git status
command, which will give us the commit we are on (which will be 'initial commit'), and there'll be some untracked files (the files that you changed and are not committed in the local repository). Right now, the files are the "Working Directory (modified)" state. Now, if we want to get these modified files in the repository to the "Staging Area" state, we need to type in the git add <file-name>
command (example: git add README.md
).
Now that our file is in the staging area, we can verify that using the git status
command. If we type it, we will see that we are still in the "initial commit" in the master branch, but we'll see that there's some new information which is Changes to be committed and git bash will show all the files which are in the staging area which are ready to be committed.
To make sure that the files in the staging area are committed to the repository (.git folder), we give git commit -m <commit-message>
command in the git bash where the <commit-message> can be any message depending on the changes made to the repository. When we commit the changes to the repository, we get information about the number of file changes and the number of insertions/deletions with a commit ID (which is a unique hashed value like 7dxc32e).
Now when we type in the git status
command, we get the information that we are still on the master branch and we have nothing to commit in the working directory, which is mentioned as: nothing to commit, working directory clean.
Inside our repository, currently, we have one file (say) which README.md which is committed. Whatever our repository's name is (let's say "demo"), our directory, is the working directory of our git repository. The actual git repository is contained within .git folder which inside our working directory. The .git folder is a special directory that git manages internally.
To get into the .git folder, we have to get into the .git folder using cd .git/
command, and when we check the files inside the .git folder using ls -al
, we will see that the we will see certain files which are HEAD, branches, config, description, hooks, index, info, logs, objects, refs, etc. These files are not to be changed without the knowledge of how to do so.
To change our working directory into a normal folder, we can simply remove the .git folder from our working directory, and our directory will simply be a normal folder in our filesystem. To remove the .git folder, we type in rm -rf .git
command to (-rf: recursively and forcefully) delete anything that's contained inside the .git folder. When we return to our prompt, we can see in the git bash that now, it knows the fact that we are no longer inside a git repository, we are inside a normal directory, and therefore, the git bash won't show any branch information with the directory we are inside.
If we simply type in git status
into the git bash now, git bash will respond with the following message:
fatal: Not a git repository (or any of the parent directories): .git
Now that we've deleted our .git folder, our "demo" folder is no longer a git repository, therefore, to make it a git repository, we first navigate the git bash to the "demo" folder, and just type in git init .
command in the git bash, where . means "current folder". It will initialize the git repository inside the current folder.
When we type in ls -la
in the git bash, we can see that now our "demo" folder contains the .git folder and therefore now our current working directory has become a git repository.
Now we can follow the steps 6.3 Commits in Git to make changes and commit the changes in the git repository we created.
Within our "demo" git repository to show all the commits/changes that have been made in our repository (in the order of most recent to least recent), we simply type in git log
command and we will get the information about all the commits we have made so far, regarding our git repository. The commits contain the hash identifier (which is a SHA-1 ID to uniquely commits within a repository) along with the Author (which contains the user.name and user.email), Date followed by the commit message.
We can get a similar information using the git show
command. It will show the last commit we made to the repository and a diff
containing all the changes we made in the commit. To get out of the show
command, we simply press 'q' in the git bash.
Inside git bash, we are in the "demo" git repository. When we type in git status
command, we can see that there will be a message - nothing to commit, working directory clean. Inside our "demo" git repo, it shows that we have a LICENCE.md & README.md file. If we update one of them and again type in the git status
command in the git bash, we can see that git will show us the message - modified: <file-name>.
The difference between previous commits and this commit is that this time, the files are not untracked, they're tracked, but modified. This is how git tracks the difference between tracked and untracked files in a git repository. To know the files that are being tracked by git, we type in the git ls-files
in the git bash. Git will show all the files that are being tracked by it. Now if we add a new file to the "demo" repository and again when we type in git ls-files
command, we can see that git is still not tracking the new file. When we type in the git status
command, we will see that there will be 2 messages: modified: <prev-file> and Untracked files: <new-file>.
We can get rid of the newly created file using rm <new-file-name>
and if we again type in git status
command, we will see that there will only be one message and it will be the modified: <file-name>. Now to commit these changes, we can simply type in git commit -m "commit-message"
into the git bash. But, we can do something much better by committing the changes expressly. The Express Commits rely upon one thing, which are/is the tracked file(s) in the repository. To add the files to the staging area and then commit them in a single command, we type in git commit -am "commit-message"
into the git bash. Here, -am
option => a: add all modified and tracked files to the staging area, and m: give the commit with a message.
When we say we are adding the modified files into the git's staging area, we are basically adding the modifications to the git repository, not the entire modified file.
Now when we type in git log
into the git bash, we can see all the commits we made till now and also the most recent commit that we made earlier at the top of the logs.
Here, we will back out the changes we made previously by unstaging our changes from the git staging area and then finally we will revert our changes entirely.
In our "demo" git repository, when we git bash the command git status
, we can see that there will be nothing to commit and we will see the following message: nothing to commit, working directory clean.
Now, we will modify the README.md file again and we will put some random text inside it. We save and close the README and then we again type in git status
command inside the git bash. We can see that we will have the message - modified: README.md and then we can add the changes into the repository using git add .
command in our git bash. When we type in git status
again, we can see that we see the message - Changes to be committed: modified: README.md in the git bash.
Now, to unstage the changes, or undo the commit, we can type in the command git reset HEAD README.md
where README.md is the file to be unstaged from the user's repository. When we open up our README.md file, the changes that we made, will still be there, but the changes that we made have simply been unstaged, that means, when we type in the command git status
, we can see that the message we get is - Changes not staged for commit: modified: README.md in the git bash.
Now, if we don't want the changes that we made and to discard the changes made to the repository, we can simply revert back to the last known good state (or commit) of the respective file (here, it is README.md) whose details are available inside the .git folder, we simply type in git checkout -- <file-name>
(ex: git checkout -- README.md
). Now, we can check the status of the repository with git status
command, which will respond with the message - nothing to commit, working directory clean in the master branch of our "demo" repository. Now if we open README.md, we can see that the changes we made are also gone.
We will create a git alias to shorten a command into a smaller command. In our "demo" repository, in the master branch, when we type in git status
, we can see that we have nothing to commit, working directory clean as the message. Now to see the options available with the git log
command, we can simply type in git help log
command.
Out of all the options available, we can type in the log command as git log --oneline --graph --decorate --all
where --online: provides a simplified commit entry providing the log information in a single line instead of multiple lines, --graph: provides an asterisk(*) based graph denoting our branching hierarchy, --decorate: tells us which commits are part of which branches, --all: provides the history for all the branches that are available in the repository. We will see an output in git bash which will show us the entire history of our significant commits. Now, whenever we want to get details in this manner, we always have to type in git log --oneline --graph --decorate --all
command every time. Instead of typing it all the time, we can use git aliases.
To create a git alias (which is basically a new command in git bash and a shortened representation of an existing combination of longer command(s)) we will type in git config --global alias.hist "log --oneline --graph --decorate --all"
command in the git bash and we have created an alias for the above command called "hist". So whenever we want to call the longer command, we can simply type in git hist
. To check our list of aliases that we have configured in git configuration, we simply type in git config --global --list
command in the git bash.
Using aliases, we can also add more options to the underlying command, it simply replaces the alias command with the actual command and concatenates the options that we added. For example, the command git hist -- LICENCE.md
, will convert into the actual command which is git log --oneline --graph --decorate --all -- LICENCE.md
, where the command will give us all the commit logs related to LICENCE.md file in the repository.
In our demo repository, we will create a file called example.txt and insert some random text into the file and save and return back to the git bash. We type in git status
and check the status of our git repository and we will see the message Untracked files: example.txt. We will add the new file using git add example.txt
command in git bash. Finally, we commit the changes made into the repo using git commit -m 'adding example.txt'
. When we list the files in the git bash using ls -la
command, we will see that we have the three files LICENCE.md, README.md & example.txt in our repository.
Now, if we want to change the name of the file we just made, we can change the name in the git bash using the git mv example.txt demo.txt
command. When we again check the status of the repository (using git status
command), we will see the message - Changes to be committed: renamed: example.txt -> demo.txt. This change is in the staged state. The renaming takes effect properly, only when we commit. When we list the files of the repository (using ls -la
command) in git bash, we can see that the file has already been renamed on the Operating System. But to finish the task, we need to actually commit the changes and let our changes be in the .git directory, in the final state, the Committed State. For that, we type in git commit -m "renamed example.txt to demo.txt"
. We will see a message - rename example.txt => demo.txt (100%) where the 100% inside the parentheses is the confidence level given by git that this file's content is a 100% similar to the previous file name, i.e., example.txt's context hasn't been changed, only the name of the file has been changed to demo.txt. If we would've made modifications to demo.txt before committing the state of the repository, then the confidence level would've been lower than 100%.
If we wanted to delete a file that we made, we can remove it using the rm
command in the git bash. To remove it using the git bash (instead of the facilities provided by the OS), we type in git rm demo.txt
command in the git bash. If we delete/rename/modify files using the git commands (instead of the commands provided by the OS), we will get the additional benefit of git tracking the changes that we made in our repository. And so, we will have more power with us using the version control system. Now, if we simply list the files in our repository, we will see that we no longer have demo.txt file in our repository. Now, when we check the status of our repository (using git status
command), we can see the message - Changes to be committed: deleted: demo.txt, meaning, our deletion is just staged, it hasn't been committed. Therefore, we have to commit to completely reflect the changes in the .git directory of our repository using git commit -m "deleting demo.txt file"
. When we check the status of our repo, we should see the message nothing to commit, working directory clean.
We will create a new file in the demo repository using the OS commands (notepad file-name
command for Windows Systems and touch file-name
command for Linux/Mac Systems). We will list the files using the terminal (dir
for Windows systems and ls
for Linux/Mac systems). We will rename LICENCE.md to LICENCE.txt using the OS' terminal (Windows: rename LICENCE.md LICENCE.txt
, Linux/Mac: mv LICENCE.md LICENCE.txt
). Note that we are trying to make new files and rename them using the respective OS' commands and therefore, git sees these changes differently. When we check the status of our repository (using git status
command), we will see that we have the message -
Changes not staged for commit: deleted: LICENCE.md Untracked files: LICENCE.txt myfile.txt
We can see that we see myfile.txt as an untracked file. But git sees the renaming of the file using the terminal as a deletion and a new file addition to the repository. Therefore, we need to tell git about our recent changes. To stage the deletions in our repository, we type in git add -u
where -u stands for "update". When we check the status of the repository (using git status
command), we can see that we have the message -
Changes to be committed: deleted: LICENCE.md Untracked files: LICENCE.txt myfile.txt
Now, we can see that only the deletions are taken care of. In order to include both additions and deletions in our repository, we have to type in git add -A
in the cmd/terminal where -A covers all types of modifications made to the repository (both addition and deletions) properly. Now when we check the status of our repository (using git status
command), we will see the message -
Changes to be committed: renamed: LICENCE.md -> LICENCE.txt new file: myfile.txt
At this point, these changes are only staged. We have to commit the changes using git commit -m "rename and add"
command in the cmd/terminal of our pwd. We can check the status after we commit using the git status
command.
Now, if we want to delete the myfile.txt, we can type in the respective OS' command from the cmd/terminal (Windows: del myfile.txt
, Linux/Mac: rm myfile.txt
). Now when we check the status of our repository, we will see the message -
Changes not staged for commit: deleted: myfile.txt
Now we stage our deletion using git add -u
command in the cmd/terminal. Check the status of our repository, and we will see the message -
Changes to be committed: deleted: myfile.txt
Now we commit the changes made to the repository using git commit -m "removed myfile.txt"
command. When we check the status again, we will see the message - nothing to commit, working directory clean.
Assume that we create a new log file inside our demo repository (Windows: notepad app.log
& Linux/Mac: touch app.log
). When we check the status of the git repository, we will see that the newly created log file is untracked, and the message we should get is -
Untracked files: app.log
In general, in a repository, we don't want anything other than the source code of the app that we are developing. Therefore, log files, images, videos, etc are not supposed to be in our git repository. For this, our version control, i.e., git, has to ignore these files. To exclude files/folders that we don't want in our git repository, we create a .gitignore file inside which we write the relative paths of the files that we don't want to be tracked in our repository.
Therefore, we create a .gitignore file in cmd/terminal (Windows: notepad .gitignore
& Linux/Mac: touch .gitignore
) and then type in the relative path of the file/folder that we want to completely exclude from being tracked by the git version control. In our case, we want to exclude app.log file, and therefore, in our .gitignore file, we can type in the following -
.gitignore
app.log
Otherwise, if we want to exclude all the log files, we can type in the following - .gitignore
*.log
Now, any file that ends with .log extension will be excluded from being tracked in our repository. We can save the .gitignore file and then close the file and in the terminal, we can again check the status of the repository (using git status
) and we will see the following message -
Untracked files: .gitignore
We can see that app.log has already been ignored from the repository, but the .gitignore file is being tracked now. If we want the changes to take place properly, we need to commit the repository using git commit -am "Adding .gitignore file"
in the git bash or terminal (Otherwise, first do git add .
and then type in git commit -m "Adding .gitignore file"
). When we check the status of the repository, we will see that we are back to a clean working directory.
We can check the files in our repository by listing the files in the terminal (Windows: dir
& Linux/Mac: ls -la
) and we can see that app.log is still in the repository, but it is not being tracked by git i.e., it is being ignored by git, because it is mentioned inside .gitignore file.
For now, we can simply go ahead and remove app.log entirely, from the terminal (Windows: del app.log
& Linux/Mac: rm app.log
).
In this section, we will learn more advanced concepts and techniques in git that apply in local repository. Some of the topics covered in this section include: the following topics:
- Comparing Differences,
- Branching, Merging & Conflict Resolution,
- Marking Milestones w. Tagging,
- How to save Work in Progress,
- How to Time Travel, Other, etc.
We will know how to compare differences using the diff and the difftool command in Git. The difftool
command will only work if P4Merge is properly setup as the difftool, shown in Git & GitHub Installations (section 5).
In our demo repository, we are on the master branch with nothing to commit and with a clean working directory. When we type in our git command alias that we created earlier which is git hist
, we will see all the commits that we made till now (i.e., the commit history of our repository this far). If we want to see the differences between two commit points, we can simply copy the hash-key (SHA1 Key) for the respective commit, and check it against the latest commit (which is the commit pointed by the HEAD pointer) using git diff c904e33 HEAD
command in the terminal. We will get the difference between the HEAD's commit (which is the latest commit on the current (master) branch) and the respective commit that we mentioned earlier (which is c904e33 in our case).
Note: Instead of "c904e33", we can use any SHA1 key which is actually a hash key associated to a commit.
If we have the difftool configured as P4Merge, then we can type in almost the same command with a few changes as follows: git difftool c904e33 HEAD
. We will have multiple files that are associated with the respective diff, therefore, to cycle through each one of them by quitting P4Merge with Command + Q
(Windows: Ctrl + Q
). After cycling through all the files in P4Merge difftool, git returns us back to the terminal.
Now, if we modify the LICENCE.md file, and we want to know the changes that we made to the repository compared to the HEAD's commit (i.e., latest commit), we simply type in git diff
command in the terminal and we will see the latest uncommitted changes that are/were made to the repository's files.
In the same fashion as above, we can do the same thing, but instead, we will use the P4Merge difftool. For that, we type in git difftool
command in the terminal. After we are done looking into the changes/modifications to the files in the git repository, we can quit the difftool (P4Merge) using Command + Q
(Windows: Ctrl + Q
).
To check all the options available with the diff command, we can always type in git help diff
command in the terminal and get the manual page (documentation) on the diff command.
For the most part, anything that can be passed into the diff command, can also be passed into the difftool command (iff, difftool is configured properly).
Branching and Merging are very important concepts in Git.
Branch is just a timeline of Commits, more accurately, branches are the names/labels we give to timelines in Git. We can create/delete branches w/o affecting the timeline, all we are doing is creating/deleting labels of commits in Git. So far, we've been working on the default branch, which is the master branch. Now, we can create a new branch (say feature branch) to develop a new feature of our application in that new branch and then rejoin/merge that new branch to the master branch by merging in any changes that occurred on the new branch.
While Merging, Git tries it's best to automatically merge when possible, which leads to several types of merge scenarios. These types of merge scenarios are:
- Fast-forward Merge: This merge happens in the simplest of cases when no additional work has been detected on the parent branch (by default, the master branch). Git will simply apply all commits from the other branch(s) directly onto the parent branch (as if we never branched off to begin with). We can manually disable the fast-forward merges if we don't desire them for some reason.
- Automatic Merge: This happens when git detects non-conflicting changes in the parent branch. Git is able to automatically resolve any conflicts. In doing so, the old branch's timeline is preserved and a new merge commit is created to show the merging of the two branches.
- Manual Merge: This happens when git is unable to automatically resolve any conflicts. Git enters a special conflicting merge state, which means that all merge conflicts must be resolved prior to moving forward with a commit. Once all conflicts have been resolved, those changes are saved as a merge commit.
In addition branches, tags and other labels for commits, Git has special markers (or pointers). One such popular marker is called HEAD which points to the Last Commit of Current Branch, it means that, as we switch branches, the location of HEAD pointer moves to match the last commit location of that branch.
While HEAD automatically points to the last commit of the current branch, it is also possible to move the HEAD pointer manually to a commit we wish to move it to. The details on how to manually move the HEAD pointer to a commit of our choice, will be covered later.
For now, we just need to remember that HEAD points to the last commit of the current branch.
We will create and manage branches (other than master) in our git repository in this section. Currently we are in the demo repository where, if we check the status of the repository, we will see the following message:
Changes not staged for commit: modified: README.md
Now assume that we intended that README.md file was to be modified experimentally as a feature that we are testing for our app for the next update. Either way, we can use feature/topic branches in order to separate out changes that we want to make off of the master branch. If we use the git branch
command, we will see the following response in the terminal:
* master
which basically means that we are currently working on a single branch which is the master branch. Now, we can create another branch by using the branch command and then separately switch to that branch. But we can accomplish both actions (i.e., creating a new branch and switching to that new branch) by using the checkout command with -b option. Syntax: git checkout -b branch-name
. Example usage: git checkout -b updates
. Here, "updates" is the name of the new branch. When we type in the git checkout command, we will see the following response in the terminal:
M README.md Switched to a new branch 'updates'
Now here, a couple of things have happened. First, it created a new branch called 'updates'. Second, git automatically switched to that new branch. And, since there were modifications pending in the working directory (related to README.md), it carried those modifications forward, into that new branch "updates". This is a technique that we can use when we start working on the master branch, and we decide later that before committing, these changes should really be isolated into their own feature/topic branch.
Now we can modify the README.md file as we want and then add the changes to the staging area using git add .
command. After that, we can simply commit the changes into the "updates" branch using git commit -m "Adding new updates from master branch to 'updates' branch"
command, and we will see the following response in the terminal:
[updates 9394c33] Adding new updates from master branch to 'updates' branch 1 file changed, 40 insertions(+), 5 deletions(-)
When we type in git status
in our repository now, we will see the following response in the terminal:
On branch updates nothing to commit, working directory clean
Now when we type in our git alias i.e., git hist
, we will see something similar to the following response in the terminal:
* 9394c33 (HEAD -> updates) Adding new updates from master branch to 'updates' branch * 6b79701 (master) README Update * 20c7c63 Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e README Update * 6bf863a README Update * 634793a README Update * 8af524a README & LICENCE Update * 231b4cd README Update
We can see that the latest commit is on the "updates" branch and the previous commit is on "master" branch. We can see what the changes are using the git diff updates master
command. Note that the diff command allows us to pass in the names of the branches instead of the commit ID's (which are the SHA1 Keys). The diff command above will show the differences in both the branches (mainly, the modifications made to the files in the repository w.r.t the branches).
In order to integrate any changes made in "updates" branch, we need to first switch to the parent branch, which is the "master" branch. To switch branches, we use the checkout command. Syntax: git checkout branch-name
. Example usage: git checkout master
. Now, we will look into the log/history from master branch's perspective by typing in git hist
command in the terminal. We should get a response which is somewhat similar to the following:
* 9394c33 (updates) Adding new updates from master branch to 'updates' branch * 6b79701 (HEAD -> master) README Update * 20c7c63 Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e README Update * 6bf863a README Update * 634793a README Update * 8af524a README & LICENCE Update * 231b4cd README Update
We can see that HEAD is pointing to the master branch, and HEAD typically points to the last commit on the current branch, and therefore, the current branch is master and the last commit on the current branch is the top one in the log as shown above, and therefore, HEAD and master are sharing the same commit ID.
Now we can go ahead and merge in the changes made in the updates branch into the master branch using git merge updates
command in the terminal. Note that HEAD is currently pointing the master branch's latest commit and therefore, in git merge branch-name
, we wrote "updates" for the "branch-name".
Now, after we merge, we would see a message that should be somewhat similar to the following:
Updating 6b79701...9394c33 Fast-forward README.md | 45 ++++-- 1 file changed, 40 insertions(+), 5 deletions(-)
This merge that we did just now, was the most simple merge, where we simply did a fast forward type merge, which means that we are going to pretend that we never really switched away from the master branch and we merge the changes from the updates branch into the master branch. Therefore, we are going to apply these commits directly to the master branch.
Now, if we type in git hist
(our git alias) command, we should see some message that looks as follows:
* 9394c33 (HEAD -> master, updates) Adding new updates from master branch to 'updates' branch * 6b79701 README Update * 20c7c63 Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e README Update * 6bf863a README Update * 634793a README Update * 8af524a README & LICENCE Update * 231b4cd README Update
We can see that HEAD, master and updates branch's pointers, all point to the same commit ID (which is 9394c33 in this case) and that's the affect of a fast-forward merge. There are options to disable the fast-forward merges from happening, but most of the time, we'll want this behaviour to be intact.
Once we have completed merging in our changes from the updates branch to the master branch, we no longer need the updates branch to be present in our git repository, because effectively, branches in Git are just labels of timelines, and once they've been integrated into the main timeline, there's no need for them anymore. Therefore, we delete the branch(s) that we do not need anymore using git branch -d updates
command in the terminal, where updates is the branch-name we want to delete. We should get some message similar to the following:
Deleted branch updates (was 9394c33)
Again, when we type in git branch
, we will see the following response:
* master
Now, if we use our git log alias which is git hist
, we will be seeing something of the following sort:
* 9394c33 (HEAD -> master) Adding new updates from master branch to 'updates' branch * 6b79701 README Update * 20c7c63 Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e README Update * 6bf863a README Update * 634793a README Update * 8af524a README & LICENCE Update * 231b4cd README Update
We can see that we no longer have the updates branch associated with the respective commit ID (in our case, it is 9394c33). Also note that the actual log/history did NOT vanish/go-away, just the label i.e., updates was removed.
This time, we will deliberately cause a conflict in our git repository and then resolve that conflict when working with branches. We will cause a conflict in the LICENCE.md file.
We are currently in the demo git repository from where, if we check the status of our git repository (using git status
), we will see that we are on branch master and the working directory is clean with nothing to commit.
From the master branch, we will open the LICENCE.md file and then make changes to it. We will add the 3rd line with the string "This is some change!!" as seen below:
LICENCE.md
# LICENCE ## [Licence Name] - [Some Text], just for this tutorial. This is some change!!
We will save and get back to the terminal and then create a branch, say very-bad branch using git checkout -b very-bad
command in the terminal. We should see the following message as a response from the terminal:
Switched to a new branch 'very-bad'
Now if we type in git branch -a
in the terminal, we should a response that is somewhat similar to the following message:
master * very-bad
which means that we are currently working with the very-bad branch (or timeline).
Now we will modify our LICENCE.md file such that it will cause a conflict. To do that, we have to update the same part of the file on both the branches (viz. master and very-bad). Therefore, we will update the 3rd line of LICENCE.md file with the string "This is bound to cause trouble!" as seen below:
LICENCE.md
# LICENCE ## [Licence Name] - [Some Text], just for this tutorial. This is bound to cause trouble!
We will save and close that change and then commit using git commit -am "very bad update in LICENCE.md"
command in the terminal. We should see something of the following sort in our terminal:
[very-bad 809b141] very bad update in LICENCE.md 1 file changed, 1 insertion(+), 1 deletion(-)
When we use our pre-defined git alias for the log, which is git hist
, we should something of the similar sort in our terminal:
* 809b141 (HEAD -> very-bad) * 9394c33 (master) Adding new updates from master branch to 'updates' branch * 6b79701 README Update * 20c7c63 Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e README Update * 6bf863a README Update * 634793a README Update * 8af524a README & LICENCE Update * 231b4cd README Update
Now, we will switch to master branch using git checkout master
command in the terminal. We should see the following message in the terminal:
Switched to branch 'master'
Before we merge in the changes, we are going to think about this scenario as us being another developer who didn't know about the changes made to the LICENCE.md file at the 3rd line, and therefore, we will make another change at the 3rd line of the LICENCE.md file with the string "I hope this is not much of a problem!!!". The changes made should look as the file shown below:
LICENCE.md
# LICENCE ## [Licence Name] - [Some Text], just for this tutorial. I hope this is not much of a problem!!!
Save and close the LICENCE.md file and in the terminal, we will again do the express commit using git commit -am "LICENCE.md Update (Merge Conflict Possible)"
. Now we will merge the very-bad branch into the master branch using git merge very-bad
command in the terminal, and we should see a message of the following sort:
Auto-merging LICENCE.md CONFLICT (content): Merge conflict in LICENCE.md Automatic merge failed; fix conflicts and then commit the result.
Therefore as we can see, due to the multiple changes of the same file at the same location of the two branches, the auto-merge functionality, when merging the branches, doesn't know which version of the LICENCE.md to keep in the repository. Therefore, there's a conflict here, which places us in a MERGING state which is denoted by our git bash as the following:
(master|MERGING) demo $
Now, when we see the contents of the LICENCE.md file (because LICENCE.md file is the file that's implicated in the merge conflict) using cat LICENCE.md
in git bash, we should the contents of the file somewhat similar to the following:
LICENCE.md
# LICENCE ## [Licence Name] - [Some Text], just for this tutorial. <<<<<<<< HEAD I hope this is not much of a problem!!! ======== This is bound to case trouble! >>>>>>>> very-bad
Now, the current version of the file has these weird caret symbol(s) [< and >] which denote the part(s) of the file that is/are conflicted. We can see that the conflict is between the content of either HEAD (which is master in this case) or very-bad and since this happens to be a very simple case of the merge conflict, we could manually modify the LICENCE.md file using our merge tool (which is P4Merge Helix) that we configured earlier in section 5 (Git & GitHub Installation).
Therefore, we use the merge tool using git mergetool
while in the MERGING state and we will see that P4Merge launches with a 3-way merge in progress. We can select any one of the 3 versions of LICENCE.md file and then save that version. Once we've done that, we have no further conflicts to resolve and therefore, we can quit P4Merge altogether. To complete the merge, we need to commit what we've saved and so, we type in git commit -m "Resolving Conflict in LICENCE.md"
in the git bash. If the message in response is the following:
[master a78ed77] Resolving Conflict in LICENCE.md
then the merge conflict has definitely been resolved and we will return back to the git bash in the normal state from the MERGING state, which will look as follows:
(master) demo $
We check the status of our repository using git status
and we can see the following message in our terminal:
Untracked files: LICENCE.md.orig
We can see that we have a LICENCE.md.orig file which is untracked. A file with .orig extension means that it is the original version of the that respective file. Now, we don't need the .orig files to be tracked by our git repository, and therefore, we can add all .orig files to be excluded from our repository in the .gitignore file as follows:
.gitignore
*.log *.orig
When we check the status of our repository, we will see the following message:
On branch master Changes not staged for commit: modified: .gitignore
We will express commit the current state of our repository using git commit -am "Updating .gitignore to exclude .orig files"
command in our terminal and we should see the following response in the terminal:
[master df3d921] Updating .gitignore to exclude .orig files 1 file changed, 2 insertions(+), 1 deletion(-)
Now, all the files with .orig extension will be excluded from being tracked in our git repository and therefore, we can go ahead and delete LICENCE.md.orig (Windows: del LICENCE.md.orig
& Linux/Mac: rm LICENCE.md.orig
).
We are currently in the demo repository on the master branch. If we use our alias i.e., git hist
, we should get something similar to the following response in the terminal:
* 3c5ec7d (HEAD -> master) Updating .gitignore to exclude .orig files * afb7058 Resolving Conflict in LICENCE.md |\ | * 7c48e4c (very-bad) very bad update in LICENCE.md * | cb652a3 LICENCE.md Update (Merge Conflict Possible) |/ * 85a1d27 README Update * 593fe03 Adding new updates from master branch to 'updates' branch * 6b79701 README Update * 20c7c63 Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e README Update * 6bf863a README Update * 634793a README Update * 8af524a README & LICENCE Update * 231b4cd README Update
At this point, we have been working with our demo repository for a really long time. So, we can mark this point in the repository with some type of a milestone. To do that, Git supports a notion of tags, which are just labels that we can put at any arbitrary commit point. By default, if we don't specify a commit (i.e., commit ID), it will be the current commit or HEAD. Now, there are two type of tags and they're:
- Lightweight Tags: It is simply a tag which just has a name associated to it. We create a lightweight tag using the
git tag tag-name
command (ex:git tag mytag
). Now, if we type ingit tag --list
, we will see the following response in the terminal:
mytag
which is essentially a list of tags we have made. And, if we check all the logs using our alias that we created i.e., git hist
, we should something similar to the following response in the terminal:
* 3c5ec7d (HEAD -> master, tag: mytag) Updating .gitignore to exclude .orig files * afb7058 Resolving Conflict in LICENCE.md |\ | * 7c48e4c (very-bad) very bad update in LICENCE.md * | cb652a3 LICENCE.md Update (Merge Conflict Possible) |/ * 85a1d27 README Update * 593fe03 Adding new updates from master branch to 'updates' branch * 6b79701 README Update * 20c7c63 Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e README Update * 6bf863a README Update * 634793a README Update * 8af524a README & LICENCE Update * 231b4cd README Update
Now, this is a lightweight tag and so there's no associated information with it.
- Annotated Tags: These tags should be preferred over lightweight tags when we have several releases of the app we are working on, and also, each release/version of the app has some changelog associated with itself. Annotated tags have extra information associated with the tag. Before we create an annotated tag, we will delete the lightweight tag we created earlier (i.e., mytag) using
git tag -d tag-name
(ex:git tag -d mytag
) command in the terminal and we should see the following response in the output:
Deleted tag 'mytag' (was 3c5ec7d)
Now, to create an annotated tag, we use the command syntax as git tag -a tag-name -m "commit-message"
(ex: git tag -a v1.0 -m "Release 1.0"
) in the terminal. We will see the list of tags we have using git tag --list
command in the terminal, and we should get the following output:
v1.0
When we type in git hist
, we will see the following output in the terminal:
* 3c5ec7d (HEAD -> master, tag: v1.0) Updating .gitignore to exclude .orig files * afb7058 Resolving Conflict in LICENCE.md |\ | * 7c48e4c (very-bad) very bad update in LICENCE.md * | cb652a3 LICENCE.md Update (Merge Conflict Possible) |/ * 85a1d27 README Update * 593fe03 Adding new updates from master branch to 'updates' branch * 6b79701 README Update * 20c7c63 Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e README Update * 6bf863a README Update * 634793a README Update * 8af524a README & LICENCE Update * 231b4cd README Update
So far, we haven't seen any difference between a lightweight tag and an annotated tag. The question remains, where does the annotation (i.e., the commit message that we typed in) come in? To view the annotations of an annotated tag, we type in git show tag-name
(ex: git show v1.0
), we should see something similar to the following output in the terminal:
tag v1.0 Tagger: Chandrabhatta Sriram <[email protected]> Date: Sun Mar 15 19:23:33 2020 +0530 Release 1.0 commit 3c5ec7d224122896467386d56b6b12dd6336698c (HEAD -> master, tag: v1.0) Author: Chandrabhatta Sriram <[email protected]> Date: Sun Mar 15 12:24:56 2020 +0530 Updating .gitignore to exclude .orig files diff --git a/.gitignore b/.gitignore index bf0824e..490a1a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -*.log \ No newline at end of file +*.log +*.orig \ No newline at end of file
We can see that it has the information about the Tagger, Date that the respective version of the repository was tagged, the commit message (in this case "Release 1.0") followed by the rest of the information for the commit that's associated with that tag. These annotated tags are really useful when we are trying to note major milestones/versions/releases of our repository, and we generally, might want to associate some information with those major releases/versions and that could be achieved through tagging.
We are currently in the demo repository on the master branch in a clean working directory. We will modify the README.md file and we check the status of our repository using git status
and we should see the following output:
On branch master Changes not staged for commit: modified: README.md
But, what if decide we are really supposed to be working on something other than the README.md file. Therefore, what we can do is, we can stash the changes made in README.md using git stash
command in the terminal and we should see something similar to the following output:
Saved working directory and index state WIP on master: 057fdf2 README Update
We can now check the status of the repository using git status
, we will see the following output in the terminal:
On branch master nothing to commit, working tree clean
Now, if we type in git stash list
, it will show us the list of things we stashed, i.e., the following output in the terminal:
stash@{0}: WIP on master: 057fdf2 README Update
Now, we stashed the previous modification, therefore, we can make changes to something other than the README.md file. Let's say we make changes to the LICENCE.md file and apply those changes using git's express commits i.e., git commit -am "Updating LICENCE.md"
and we should some output similar to the following in the terminal:
[master 2bbc636] Updating LICENCE.md 1 file changed, 1 insertion(+), 1 deletion(-)
When we check the status of our repository using git status
, we should see the following output:
On branch master nothing to commit, working tree clean
Now, from the stash, we will apply our stash using git stash pop
command, which will do 2 actions at once which are the same as git stash apply
and then git stash drop
at the same time, which translates to apply the last stash in the stash stack (for git stash apply
[In this case, we put the changes that we made in the README.md file, back into the README.md file]) and drop/delete that stash from the top of the stash stack, which was just applied (for git stash drop
[In this case, it will drop/delete the stash@{0} which was the stash for the Commit ID 057fdf2 "README Update"]). After git stash pop
, we should see the following output in the terminal:
On branch master Changes not staged for commit: modified: README.md
Now if we type in git stash list
we would get no result in the terminal. And if we check the README.md file, we can see that README.md file was updated like it was updated before the stash ever took place. Now we can commit the README.md's update using express commit i.e., using git commit -am "README update after stash"
command in the terminal and we should something similar to the following output:
[master 78c2353] README update after stash 1 file changed, 1 insertion(+), 1 deletion(-)
When we check the status of our repository using git status
command in the terminal, we should see the following output:
On branch master nothing to commit, working tree clean
We are currently in the demo repository, on the master branch and the working directory is clean.
We will update our README.md file and then add the changes to the staging area, and so when we see the status of our git repository using git status
, we should see a response in the terminal that's somewhat similar to the following:
On branch master Changes to be committed: modified: README.md
We again, add some new content into README.md and check the status again. This time, we should see the following output in the terminal:
On branch master: Changes to be committed: modified: README.md Changes not staged for commit: modified: README.md
We can see that we've our modifications detected in both the staging area and in our working directory.
Now, there maybe times when we need to go to a different commit point. Example: If we made a mistake in the last commit that we shouldn't have committed in the first place, then we would want to rollback to a previous commit before that last commit. To check all the commits we ever made, we will use our git alias i.e, using git hist
(which is same as git log --all --oneline --graph --decorate
) command and we should see something similar to the following output in our terminal:
* 184db4d (HEAD -> master) README.md updated after it was stashed * 445064a LICENCE.md updated when README.md's update was stashed * 057fdf2 README Update * 3c5ec7d (tag: v1.0) Updating .gitignore to exclude .orig files * afb7058 Resolving Conflict in LICENCE.md |\ | * 7c48e4c (very-bad) very bad update in LICENCE.md * | cb652a3 LICENCE.md Update (Merge Conflict Possible) |/ * 85a1d27 README Update * 593fe03 Adding new updates from master branch to 'updates' branch * 6b79701 README Update * 20c7c63 Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e README Update * 6bf863a README Update * 634793a README Update * 8af524a README & LICENCE Update * 231b4cd README Update
We can see that we've several commits to choose from. So we can select a commit point (or version of our app) and reset our git repository to that commit point. For that, we use git reset commit-ID --reset-type
command (ex: git reset 3c5ec7d --soft
). For the --reset-type, we actually have 3 distinct ways of resetting:
- Soft Reset: This is the least destructive out of all the rest type we have. It means that, soft reset only changes where the HEAD pointer points to, to the commit point we specified. Now, if we see where HEAD is pointing to, we can see that it is pointing to 184db4d (which is the master branch for now). Now, if we type in
git reset 3c5ec7d --soft
and then we type ingit hist
, we should see something similar to the following output in the terminal:
* 3c5ec7d (HEAD -> master, tag: v1.0) Updating .gitignore to exclude .orig files * afb7058 Resolving Conflict in LICENCE.md |\ | * 7c48e4c (very-bad) very bad update in LICENCE.md * | cb652a3 LICENCE.md Update (Merge Conflict Possible) |/ * 85a1d27 README Update * 593fe03 Adding new updates from master branch to 'updates' branch * 6b79701 README Update * 20c7c63 Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e README Update * 6bf863a README Update * 634793a README Update * 8af524a README & LICENCE Update * 231b4cd README Update
Now we can see that the HEAD is pointing to 3c5ec7d which is commit ID that we passed in earlier to the reset command. If we check our repository's status using git status
, we can see something similar to the following output in the terminal:
On branch master Changes to be committed: modified: LICENCE.md modified: README.md Changes not staged for commit: modified: README.md
We can see that we have files that are modified in the working directory and in our staging area and this is what the Soft Reset precisely allows us to do and it is simply changing the Commit ID that the HEAD pointer is pointing to, to the respective Commit ID we pass in as the parameter when we use the reset command. Therefore a soft reset preserves the Git staging area and our working directory, which effectively means that, we can back out our changes, make minor modifications to them, and then commit where HEAD is currently pointing to.
- Mixed/Default Reset: Let's say we will reset to the Commit ID - 20c7c63 ("Adding .gitignore file") using
git reset 20c7c63 --mixed
(Note that by default,git reset 20c7c63 --mixed
means the same asgit reset 20c7c63
. It means that if no option is given to the reset command, by default, --mixed option is applied) command in the terminal, and instantly, we would some message of the following sort:
Unstaged changes after reset: M .gitignore M LICENCE.md M README.md
We can see that some of the changes made till now have been unstaged. If we use our log alias i.e., git hist
, we should some output of the following sort:
* 3c5ec7d (tag: v1.0) Updating .gitignore to exclude .orig files * afb7058 Resolving Conflict in LICENCE.md |\ | * 7c48e4c (very-bad) very bad update in LICENCE.md * | cb652a3 LICENCE.md Update (Merge Conflict Possible) |/ * 85a1d27 README Update * 593fe03 Adding new updates from master branch to 'updates' branch * 6b79701 README Update * 20c7c63 (HEAD -> master) Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e README Update * 6bf863a README Update * 634793a README Update * 8af524a README & LICENCE Update * 231b4cd README Update
We can see that the HEAD is pointing to 20c7c63. And if we check the status using git status
command, we should some output which is similar to the following:
On branch master Changes not staged for commit: modified: .gitignore modified: LICENCE.md modified: README.md
We can see that we have several files that have been unstaged and have been placed into our working directory; there's nothing in our staging area.
- Hard Reset: This type of reset is the most destructive of all the reset modes. To apply this reset, we will simply apply the reset command to a particular Commit ID using the --hard option i.e., using the
git reset commit-id reset-type
command (ex:git reset 8af524a --hard
) and we should get an output that's similar to the following:
HEAD is now at 8af524a README & LICENCE Update
If we now check the status of our repository using git status
, we will see the following message:
On branch master nothing to commit, working directory clean
By the output above, we can see that any changes that were pending till now, have been wiped out (along with anything that was in the staging area) when we did a hard reset. Apart from a handful of changes we have made at the very beginning, we really haven't lost a whole lot. Now, if we type in git hist
, we will see the following output:
* 3c5ec7d (tag: v1.0) Updating .gitignore to exclude .orig files * afb7058 Resolving Conflict in LICENCE.md |\ | * 7c48e4c (very-bad) very bad update in LICENCE.md * | cb652a3 LICENCE.md Update (Merge Conflict Possible) |/ * 85a1d27 README Update * 593fe03 Adding new updates from master branch to 'updates' branch * 6b79701 README Update * 20c7c63 Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e README Update * 6bf863a README Update * 634793a README Update * 8af524a (HEAD -> master) README & LICENCE Update * 231b4cd README Update
We can see that our HEAD is now pointing to the Commit ID - 8af524a ("README & LICENCE Update"). If we just type in git log --oneline
command, we will see the following output in the terminal:
8af524a README & LICENCE Update 231b4cd README Update
We can see that we are not able to get all the commits we have made so far and that's because we did a hard reset. To check all the commits we've made so far, we use git log --all
command. We can also use another command in conjunction with reset command, which is git reflog
. Using reflog command is different when compared to log in the sense that git log
shows us our commit IDs along with the timeline of our commits whereas git reflog
shows us all the different actions we've taken while in the respective repository which will allow us to get all the way back to a specific commit ID, if we need to. The output in the terminal we get should resemble somewhat to the following (when we type in git reflog
):
8af524a HEAD@{0}: reset: moving to 8af524a 20c7c63 HEAD@{1}: reset: moving to 20c7c63 3c5ec7d HEAD@{2}: reset: moving to 3c5ec7d 184db4d (HEAD -> master) HEAD@{3}: commit: README.md updated after it was stashed 445064a HEAD@{4}: commit: LICENCE.md updated when README.md's update was stashed 057fdf2 HEAD@{5}: reset: moving to HEAD 057fdf2 HEAD@{6}: reset: moving to HEAD 057fdf2 HEAD@{7}: reset: moving to HEAD 057fdf2 HEAD@{8}: commit: README Update 3c5ec7d (tag: v1.0) HEAD@{9}: commit: Updating .gitignore to exclude .orig files afb7058 HEAD@{10}: commit (merge): Resolving Conflict in LICENCE.md cb652a3 HEAD@{11}: commit: LICENCE.md Update (Merge Conflict Possible) 85a1d27 HEAD@{12}: checkout: moving from very-bad to master 7c48e4c (very-bad) HEAD@{13}: commit: very bad update in LICENCE.md 85a1d27 HEAD@{14}: checkout: moving from master to very-bad 85a1d27 HEAD@{15}: commit: README Update 593fe03 HEAD@{16}: merge updates: Fast-forward 6b79701 HEAD@{17}: checkout: moving from updates to master 593fe03 HEAD@{18}: checkout: moving from master to updates 6b79701 HEAD@{19}: checkout: moving from updates to master 593fe03 HEAD@{20}: commit: Adding new updates from master branch to 'updates' branch 6b79701 HEAD@{21}: checkout: moving from master to updates 6b79701 HEAD@{22}: commit: README Update 20c7c63 HEAD@{23}: commit: Adding .gitignore file eb94f26 HEAD@{24}: commit: Adding .gitignore file 801a434 HEAD@{25}: commit: README Update cd82d85 HEAD@{26}: commit: README Updated 1f28d75 HEAD@{27}: commit: deleted demo.txt file 64f7486 HEAD@{28}: commit: renamed example.txt to demo.txt 53f8779 HEAD@{29}: commit: adding example.txt d741b7e HEAD@{30}: commit: README Update 6bf863a HEAD@{31}: commit: README Update 634793a HEAD@{32}: commit: README Update 8af524a HEAD@{33}: commit: README & LICENCE Update 231b4cd HEAD@{34}: commit (initial): README Update
Now, if we need to reset to HEAD@{3}, we can simply type in the commit ID associated with HEAD@{3} along with the reset command and the reset type. Now we are going to HEAD@{3} because HEAD@{3} is the last commit before we did any of the resets. Therefore, we type in git reset 184db4d --hard
in the terminal and we should see the following output in the terminal:
HEAD is now at 184db4d README.md updated after it was stashed
We've now moved our Commit ID back to where it was in the beginning and therefore, we will type in git log --oneline
and we should get the following output in the terminal:
184db4d (HEAD -> master) README.md updated after it was stashed 445064a LICENCE.md updated when README.md's update was stashed 057fdf2 README Update 3c5ec7d (tag: v1.0) Updating .gitignore to exclude .orig files afb7058 Resolving Conflict in LICENCE.md cb652a3 LICENCE.md Update (Merge Conflict Possible) 7c48e4c (very-bad) very bad update in LICENCE.md 85a1d27 README Update 593fe03 Adding new updates from master branch to 'updates' branch 6b79701 README Update 20c7c63 Adding .gitignore file eb94f26 Adding .gitignore file 801a434 README Update cd82d85 README Updated 1f28d75 deleted demo.txt file 64f7486 renamed example.txt to demo.txt 53f8779 adding example.txt d741b7e README Update 6bf863a README Update 634793a README Update 8af524a README & LICENCE Update 231b4cd README Update
It looks like we have all our commits that we made till now, back again to us. Now if we use our git hist
alias command, we should see the output as the following (which will look the same as applying git hist
prior to applying any resets to the repository):
* 184db4d (HEAD -> master) README.md updated after it was stashed * 445064a LICENCE.md updated when README.md's update was stashed * 057fdf2 README Update * 3c5ec7d (tag: v1.0) Updating .gitignore to exclude .orig files * afb7058 Resolving Conflict in LICENCE.md |\ | * 7c48e4c (very-bad) very bad update in LICENCE.md * | cb652a3 LICENCE.md Update (Merge Conflict Possible) |/ * 85a1d27 README Update * 593fe03 Adding new updates from master branch to 'updates' branch * 6b79701 README Update * 20c7c63 Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e README Update * 6bf863a README Update * 634793a README Update * 8af524a README & LICENCE Update * 231b4cd README Update
GitHub is a git repository hosting service where we can host unlimited public/private repositories. In GitHub, we can create an account here and sign-in after creating an account here.
After creating a GitHub account, we are all set to push our changes we made in our repository till now onto our Git Repository. We can create a repository in GitHub by following the steps provided here.
In our demo repository our status is that there nothing to commit and the working directory is clean on the master branch.
To connect our local repository to the remote repository (which is hosted by GitHub), we us the remote command. If we issue the git remote -v
command, git responds without any result, that means that there's no remote repository connected to the respective local repository.
To connect to a remote repository, we first create a repository after following the steps present here. We can link the local repository to our remote repository using the add sub-command in the remote command, i.e., using the syntax git remote add origin https://github.com/username/repo-name.git
(Ex: git remote add origin https://github.com/Ch-sriram/Just-Git-In.git
) where the add sub-command takes in two parameters which is the name of the remote reference we want to create (in this case it is 'origin') and the second parameter which is full URL to the remote repository. In this case we can refer to our remote repository with any name in place of 'origin', but by convention, the first and primary remote repository is named and refered by the name 'origin'. Now if we type in git remote -v
, git responds with the following message in the terminal:
origin https://github.com/Ch-sriram/Just-Git-In.git (fetch) origin https://github.com/Ch-sriram/Just-Git-In.git (push)
Git tells us that we've our origin point towards the remote git repository we mentioned earlier. Now there might be a confusion about why the remote repository's entry (URL) is listed twice (one for fetch and one for push)? That's because technically, git will allow to have two different URLs one for fetching the changes from the remote repository to the local repository and one for pushing the changes made in the local repository to the remote repository. The URL for fetching can be different compared to the URL for pushing the repository, but in most cases, the URLs for fetching and pushing will be the same.
We are currently in demo git repository in a clean working directory on master branch with nothing to commit.
Now that we have connected our local repository with the remote repository, it is time to push our local repository to the remote repository. In order to synchronize all our changes between the local git repository and the remote git repository, we are going to the use the push command i.e, git push -u origin master [--tags]
command where -u option sets up a tracking branch relationship between the master branch on the local repository and the master on the remote repository. Since the remote repository we have is named 'origin', that's the value we used. After the name/reference of the remote repository, we provide the name of the branch (we are going to push our changes from the local repository to the remote repository) which in our case is 'master'. After that, we have --tags flag to send all the tags that we currently have in our local git repository, up to GitHub (The --tags flag is optional. For the --tags flag to be considered, we type in git push -u origin master --tags
command in the git bash). After we type in the command, we will be prompted for our GitHub Username and Password as seen below:
Username for 'https://github.com': Ch-sriram Password for 'https://[email protected]':
After that, Git will do some house-keeping (which synchronizes the local and remote repositories) and if everything is successful, we should see an output on the terminal which is similar to the following:
To https://github.com/Ch-sriram/Just-Git-In.git * [new branch] master -> master * [new tag] v1.0 -> v1.0 Branch master set up to track remote branch master from origin.
We can see that we have two lines with [new branch] and [new tag] information, which means that, when we pushed our local repository to the remote repository, we created our one and only branch in the remote repository which is the 'master' branch.
We also see that we have the statement - Branch master set up to track remote branch master from origin. This statement came up because we used the -u option when we pushed the local repository to the remote repository. In the future pushes we make to the remote repo, we need not use the -u option.
After we have pushed our local repository to the remote repository, we can check and verify our changes in the remote repository using the GitHub remote repository URL we gave earlier.
Now when we type in git status
in the terminal, we see the following output from git:
On branch master Your branch is up to date with 'origin/master'. nothing to commit, working tree clean
Till now we used HTTPS (Hyper-Text Transfer Protocol Secure) [which is used for secure transfer communication over a computer network and is widely used in the internet] authentication with GitHub.
This time, we will set-up another authentication method known as SSH (Secure SHell is a cryptographic network protocol for operating network services securely over an unsecured network). It is recommended to use SSH when we are on a system/computer we own or a system that we use on a regular basis. SSH takes a short setup process, but once it is done, it saves a lot more time each time we make a remote change on our local repository, when compared to the HTTPS authentication.
We will setup our SSH Key for our respective system and then save it on GitHub, so now, our account is linked via SSH authentication.
The first thing we need to in order to communicate via SSH in GitHub is that we've to create an SSH key on our local system. We are currently outside the demo repository and we create a .ssh directory using mkdir .ssh
. We will need a tool called ssh-keygen to generate both public and private SSH keys and for that, we navigate to the .ssh directory and we type in ssh-keygen -t rsa -C "[email protected]"
command where, -t option is the type of the cryptographic algorithm we are going to use, "rsa" stands Rivest-Shamir-Adleman algorithm in cryptography, -C option is for common name for which the input is the email address as shown in the command. After we press enter, we will get be prompted about where to save the file? as shown below:
Generating public/private rsa key pair. Enter file in which to save the key (C:/Users/srira/Desktop/.ssh/id_rsa):
We press enter to accept the default and then we will be asked for a passphrase as shown below:
Generating public/private rsa key pair. Enter file in which to save the key (C:/Users/srira/Desktop/.ssh/id_rsa): Enter passphrase (empty for no passphrase):
We press enter to have no passphrase (it is recommended to enter the passphrase). No passphrase means that we would be asked the passphrase when we are actually linking our GitHub account to our Git repsoitory using SSH.
Now, after we enter the passphrase (or leave it empty), we will have our SSH Keys generated as the following output:
Your identification has been saved in C:/Users/srira/Desktop/.ssh/id_rsa Your public key has been saved in C:/Users/srira/Desktop/.ssh/id_rsa.pub The key fingerprint is: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx [email protected] The key's randomart image is: +--[ RSA 2048 ]--+ | .. | |.. | |o . | | o . | | . . . S | | . E o . | | = = = | | = *.= | | .o.*o=. | +----------------+
When we check the files in the .ssh directory, we should definitely have two files and they're:
id_rsa id_rsa.pub
id_rsa is the private key and id_rsa.pub is our private key. We open the id_rsa.pub file in our text editor (Windows: notepad id_rsa.pub
& Linux/Mac: mate id_rsa.pub
) and then copy all the content of the file and then close our editor. Now we have the contents of id_rsa.pub file in our clipboard.
Now, in our internet browser, we open our GitHub Profile and go to Settings. In the Settings, we go to the menu item SSH Keys. In there, we will see that we have a functionality to add an SSH Key using the Add SSH Key button. After we press the button, we will be asked to give a Title and the Key. In the Title field, we will give a description of the SSH Key we are using as an SSH Key is usually associated with some system/computer (example: My Lenovo YogaPad [PC]) and then we will paste the contents of the clipboard into the Key field and then press Add key button. After that, GitHub prompts us for password of the GitHub account so that we can get into super user mode. Once we have successfully authenticate using our GitHub password, we will be redirected to the SSH keys sub-menu in Personal settings menu where we will see our SSH key with the title we gave alonf with the public key. We can always delete the SSH Key we have from that same menu using the Delete button.
Now to confirm whether the machine we generated the SSH Key on is able to communicate with GitHub via SSH, we are going to use the SSH command to try to login. For that we type in ssh -T [email protected]
in the terminal inside the .ssh directory, and we should something similar to the following output in the terminal:
The authenticity of host 'github.com (192.30.234.66)' can't be established RSA key fingerprint is xxxxxxxxxxxxxxxxxxxxxxxx. Are you sure you want to continue (yes/no)?
We will be given a prompt as shown above and we should type in yes and we will be prompted again for our SSH Key Password. We have the option to remember the SSH Key Password depending on the Operating System we are using. We type in the SSH Key Password and then click "OK" and we should see the following output in the terminal:
Identity added: C:/users/srira/Desktop/.ssh/id_rsa (C:/users/srira/Desktop/.ssh/id_rsa) Hi Ch-sriram! You've successfully authenticated, but GitHub does not provide shell access.
We will know how to apply local operations in a git repository, but by using the GitHub Interface. We will view, edit, create & delete files all directly on the GitHub interface.
We will also go over the unique aspects of the GitHub's remote repository and how it relates to our local repositories back on our system.
While we can make smal changes in our project using the GitHub Interface, but the more significant changes are recommended to be done using the Git CLI (or the git bash).
We can create a repository on GitHub from the steps given here.
We can clone a GitHub Repository easily from the repository link (link can be an SSH or HTTPS link) itself. Whenever we open a GitHub repository in the github's website in the web browser, we can see an option in the repository which has information on the clone URL by pressing the Clone or download button. We have two types of URLs to choose from, one is HTTPS, another is SSH. We can choose either. We will choose SSH URL. We will copy the SSH URL of the the respective GitHub Repository and in our terminal, we type in the clone command with the following syntax: git clone clone-url [directory-name]
(example: If we use the command w/o giving the optional [directory-name] option, then we will clone the directory from GitHub to our local machine with the same name as the repo's name and so the command would be the following: git clone [email protected]:Ch-sriram/Practice-CPP.git
. Otherwise, if we want a custom directory name, we would give the optional [directory-name] option which is git clone [email protected]:Ch-sriram/Practice-CPP.git CPP
).
After we clone the repository, we can see that the contents inside the clones repository is the same as that of the files list we saw inside the remote repository hosted on GitHub. Inside the cloned repository, whenever we type in git remote -v
, we will see that the git repository is automatically connected to GitHub via SSH with both the fetch and pull links having the same URL as seen below:
origin [email protected]:Ch-sriram/Practice-CPP.git (fetch) origin [email protected]:Ch-sriram/Practice-CPP.git (push)
Right now, we are in our CPP repository (which we cloned earlier) in the web browser, and while we are in the repository, we are going to make a quick change in the repository directly through the github website. We are going to open the README.md file in the repository and then edit that file (Note that the CPP repository should be a part of our repositories in our respective GitHub account to edit on GitHub). After editing/adding some content in README.md, we can provide the Commit Message and the provide optional extended description and then we can choose if we want to Commit directly to the master
branch or create a new branch for the commit and start a pull request. We will simply make the commit on the master branch, and then press the Commit changes button.
Now we just updated our remote repository, a change of which our local repository is currently unaware of. In our local repository, in the terminal, when we check the status of our repository (which is CPP repo) using git status
, we will see the following output in the terminal:
On branch master Your branch is up-to-date with 'origin/master' nothing to commit, working directory clean
While we are in the CPP repository, we can make some changes to the README.md file there, and then commit our changes using the express commit, i.e., using git commit -am "README Update"
command and we should see house-keeping output. Now when we check the status of CPP repository using git status
command in the terminal, we would see the following output:
On branch master Your branch is ahead of 'origin/master' by 1 commit. (use "git push" to publish your local commits) nothing to commit, working directory clean
Now according to the message seen above, we can see that we are ahead of the remote repository by 1 commit, however, we know that we have made a commit in CPP remote repository through the GitHub website and therefore, if we now try to publish the changes made in the local repository to the remote repository using git push
, we'd get the following output in the terminal:
To github.com:Ch-sriram/Practice-CPP.git ! [rejected] master -> master (fetch first) error: failed to push some refs to '[email protected]:Ch-sriram/Practice-CPP.git' hint: Updates were rejected because the remote contains work that you do hint: not have locally. This is usually caused by another repository pushing hint: to the same ref. You may want to first integrate the remote changes hint: (e.g., 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
When we tried to publish our changes in the local repository to the remote repository, git has rejected the push because we have made a commit in the remote repository and we have to first fetch the changes we made in our remote repository and then merge those changes to our local repository, otherwise, we can simply pull the changes from the remote repository to the local repository which will fetch + merge at the same time. But when pulling (or merging in general) there's obviously a possibility that the pull might fail due to a merge conflict (in that case, we go to a MERGING state and then use our mergetool to resolve the merge conflict), but most oftenly, the pull request goes in automatically (due to a fast-forward merge).
Now, if we are not sure whether there'll be merge conflict or not, we generally go for fetching and then merging the remote repo changes into our local repo manually rather than going for the pull command, which can be a destructive command if we have changes that are not compatible with what currently is in the remote repository.
Therefore, we alleviate the usage of pull by using the fetch command, i.e., using git fetch
command in the terminal, and we should see something similar to the following output:
remote: Enumerating objects: 5, done. remote: Counting objects: 100% (5/5), done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), 717 bytes | 4.00 KiB/s, done. From github.com:Ch-sriram/Practice-CPP 56b8dac..fdcf788 master -> origin/master
Now when we check the status using git status
command, git responds with the following output in the terminal:
On branch master Your branch and 'origin/master' have diverged, and have 1 and 1 different commits each, respectively. (use "git pull" to merge the remote branch into yours) nothing to commit, working tree clean
We can see that the message depicts that we have 1 commit from local and 1 commit from the remote repository. Also, the message says that we can merge the changes automatically using git pull
command in the terminal. Now when we type in git pull
command in the terminal, since the master branch on both sides (remote and local) are different, what occurs is a merge commit and so, we will see a MERGE_MSG file open where we type in the commit message we want. By default, the commit message we will see is - Merge branch 'master' of github.com:Ch-sriram/Practice-CPP which is a perfectly fine commit message. Therefore, we leave the message as that and then save and close the editor where the MERGE_MSG file was opened and then we publish the changes to GitHub using git push
and we should get an output which is house-keeping output.
Doing a pull or a fetch prior to any pushes is the best practice when trying to publish the changes made in the local repository to our remote repository.
We can verify our changes in GitHub in our CPP repository.
We will be in need of updating our remote references of the local repository when we rename the repository, because the SSH/HTTPS URL changes with respect to the name of the repository. Please know how to rename a repository here. Assume that we renamed Practice-CPP to CPP.
After renaming our repository, we generally have to change our remote reference to the local repository, because that particular remote reference is not functional anymore. When we check the remote reference our local repository is connected to using git remote -v
command, we get the following output:
origin [email protected]:Ch-sriram/Practice-CPP.git (fetch) origin [email protected]:Ch-sriram/Practice-CPP.git (push)
Now, that we have renamed our repository, we need to update the remote reference to origin by copying the clone URL (SSH or HTTPS, depending on what you want) and then using git remote set-url origin [email protected]:Ch-sriram/CPP.git
command in the terminal and then we check the remote reference again using git remote -v
command in the terminal and we should see the following output:
origin [email protected]:Ch-sriram/CPP.git (fetch) origin [email protected]:Ch-sriram/CPP.git (push)
We can get additional information about our remote reference by typing in git remote show origin
command (where 'origin' is the name of the remote reference) in the terminal and we should get a similar output as shown below:
* remote origin Fetch URL: [email protected]:Ch-sriram/CPP.git Push URL: [email protected]:Ch-sriram/CPP.git HEAD branch: master Remote branch: master tracked Local branch configured for 'git pull': master merges with remote master Local ref configured for 'git push': master pushes to master (up to date)
We can see more information related to the remote reference and we see that the Remote branch is master and it is tracked, which means whenever we do a git push
or git pull
, git will automatically synchronize between the master branches on both sides (viz. remote and local).
In our CPP repository on GitHub (which is here), we will create a new file by clicking on the Create new file button in the repository page in the web browser. We can go into any sub-directory inside our GitHub repository and create a new file by giving a name to the file with the appropriate extension and then typing in the content of the file in the Edit new file section. After we fill the content inside the file, we can also preview the file before committing the changes by clicking on the Preview tab beside the Edit new file tab in the web browser.
After we fill the content in the new file, we scroll to the bottom of the webpage and then put in our commit message's title along with any additional optional description (if we want, we can put in the description in markdown syntax) and then we can commit directly to the master
branch or we can Create a new branch for this commit and start a pull request. We will create a new branch for the commit and start a pull request. We will name our branch feature-branch and we click on Propose new file button.
We will be redirected to Open a pull request page where we are effectively making a pull request into our master branch in the remote repository itself. We can edit the commit message we gave earlier while committing the creation of the new file, or leave it as it is. After the pull request commit message we have a comment that will go along with it where we can drop-in images and use markdown there too. After we finish with the commit message and the comment, we can simply press the Crete pull request button.
We will see a message which says This branch is up-to-date with the base branch and since we are the only contributor of the repository, we will click the Merge pull request button below the message. We will again have the opportunity to modify the commit message prior to clicking on Confirm merge button. Once we've clicked the button, we will be shown one more message which says Pull request successfully merged and closed and we will have a button beside it which is Delete branch and we can click that button to delete the branch ("feature-branch") that we created earlier. We click Delete branch button and we delete the branch we created earlier.
To verify the changes made, we go to CPP repository home and we check that the newly created file is there in the remote repository.
After we made the changes in the remote repository as we have seen in the section above (in 10.4), now we have to synchronize the changes that we made in the remote repository into our local repository. For that, we type in git fetch
in the terminal, and we will see an output which is of the following sort:
remote: Enumerating objects: 5, done. remote: Counting objects: 100% (5/5), done. remote: Compressing objects: 100% (4/4), done. remote: Total 4 (delta 1), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (4/4), 1.64 KiB | 6.00 KiB/s, done. From https://github.com/Ch-sriram/Just-Git-In 05dd1ca..1a54600 master -> origin/master
Now if we check the status using git status
command in our demo repository, we will see the following output:
On branch master Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded. (use "git pull" to update your local branch) nothing to commit, working tree clean
Now we can simply type in a git pull
in the terminal, and we shall an output that's similar to the following:
Updating 05dd1ca..1a54600 Fast-forward lorem.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 lorem.txt
Now when we check the status (using git status
command) of our demo repository, we will see the following output:
On branch master Your branch is up to date with 'origin/master'. nothing to commit, working tree clean
Now when we type in ls
in our terminal, we will see that lorem.txt is in our local repository as seen below:
app.log LICENCE.txt lorem.txt README.md
Before making any new branches in GitHub, we should note that we are on the master branch which is the default branch when we are on a newly made repository.
So now, we will create a new branch using GitHub. We will press on the button Branch: master in our repository and we will see the list of branches we have in our repository on GitHub's remote repository (which should only be master). So now, to create a new branch, we simply type inside the text-field that has the placeholder Find or create a branch and so we type in the name of the branch we want to create in that text-field, let's say we create the new branch called example.
After we create the new branch (example), we can see that we will instantly be switching to the example branch and we can see that everything in that branch is th same as that of the master branch. We can make a quick edit inside the example branch, so that we can understand what will happen when we branch.
Click on lorem.txt file and then click on the pencil button to edit the file and make some changes say "This is some change in the example branhc". Once we edited, we scroll down and type in a commit message and then commit the changes directly on the example branch. We will return to the main page for the repository which is the demo repository and it will reset the repository to the master branch. To get back to the example branch, we will simply click the button Branch: master and select the example branch.
Once we switch to the example branch, we can see that we have our latest commit which we made on the example branch. If we notice the branches tab in GitHub, it tells us that we have 2 branches and when we click on the tab, we will see that we have our Default
branch as master and we will see other branches under Your branches section.
Previously, we created a branch directly on GitHub in the remote repository and then made some changes directly on the file inside the remote repository hosted by GitHub. However, modifying files directly on GitHub is not exactly a best practice. What we'll end up doing most of the time is creating the branches locally and then pushing them up to GitHub. So let's do that now.
We are going to create a branch called remove-lorem using git checkout -b remove-lorem
command in the git bash and we should see the following output in the git bash:
Switched to a new branch 'remove-lorem'
When we list out the files inside our text file, we ca see that we have lorem.txt, which is just a text file which has some filler text inside it. Therefore, we can delete/remove the file as it is not providing any tangible benefit to the demo repository directly. Therefore, we will delete it using git rm lorem.txt
in the git bash. Now when we check the status of our repository (using git status
), we can see that we have staged the fact that we are doing a delete as seen below:
On branch remove-lorem Changes to be committed: (use "git reset HEAD <file> to unstage) deleted: lorem.txt
In order to finalize this change, we need to commit using git commit -m "Removing lorem.txt file"
in the git bash, and we shall se the following output:
1 file changed, 5 deletions(-) delete mode 100644 lorem.txt
We have successfully deleted lorem.txt from the repository and we are back to a clean working directory. However, these changes are only reflected in the local repository. In order to make sure that these changes reflect in the remote repository, we should use the command git push -u origin remove-lorem
where the -u sets up a tracking relation between the remove-lorem branch in the local repository and the remote repository (and so, whenever we push using git push
or git pull
next time, we would automatically push/pull into/from remove-lorem branch), origin is the name of the remote reference and remove-lorem is the branch we are trying to push into the remote repository. When we type in the command, we will see some house-keeping output, and along with that, we shall see something similar to the following:
To [email protected]:Ch-sriram/Just-Git-In.git * [new branch] remove-lorem -> remove-lorem Branch remove-lorem set up to track remote branch remove-lorem from origin.
Therefore, our local branch has been pushed up to GitHub. Now if we refresh our remote repository page in GitHub, we will see that GitHub already detected that remove-lorem is a new branch. When we browse the remove-lorem branch, we will see that we no longer have the lorem.txt file inside our remove-lorem branch of the repository.
We will integrate one of the branches we made in GitHub, into the master branch. We are currently logged into GitHub and we are currently on the demo git repository hosted on GitHub (in master branch). We have created 2 branches fairly recently and the GitHub interface notices that we have recent branches named example and remove-lorem which is shown above below the description of the repository in the default branch (which is the master branch) of the repository and that's because a lot of workflow that GitHub has is around comparing and pull requests.
Therefore, beside our example branch, there will be abutton which is Compare & pull request which we are going to click on (We can go into the Pull request tab and do the same). After that, we will be redirected to a page where we will be asked to Open a pull request and by default (if there are no forks involved), GitHub will automatically do a compare and prepare for a pull request based on the default branch (which in this case is master). We will also have a commit messag which will be by default, the previous commit message we made in the branch we are applying the pull request to. We can also comment on the commit, and when we scroll to the bottom, we can see the difference between what we started with and what all files were actually edited/added in the example branch (in unified/split view). We can also see the number of commits, number of files changed, number of commit comments and the number of contributors associated with the example branch.
Once we are done with the commit message and the rest of the pull request changes, we can go ahead and click on the button Create pull request and that will land us on the main pull request page where the heading of the page will be the commit message we gave in the previous page (which was the Open pull request page). For this particular repository (the demo repository), this is the second pull request, and therefore, we can see that we have the title of the main pull request page appended with #2 with the commit message.
Now, one thing to note here is that, for public repositories, anyone can submit a pull request, however, only the people who have access to commit to the repository will have to ability to merge the pull request. Therefore, when we landed on this main pull request page, we can see that we have button that says Merge pull request. Before we press the button, we can check the comments in the Conversation tab or, we can check the Commits tab where all the commits for the branch will be listed, and we will also be able to check all the differences made, in the Files changed tab. We can go to the individual (modified) files and then leave comment, at the line level (GitHub provides a fine level of granularity). We can comment using emoticons, which are accessible using the colon operator (:) i.e., :+1
converts to a thumbs-up. On GitHub, whenever we type in the colon operator, we will be able to see a preview of emoticons we can use and simply press the icon to get the code related to it typed in automatically into the comment.
Now, we can simply press the Merge pull request button (note that the button is only visible if there are no merge conflicts between the default branch and the branch we are merging into the default branch) and then we are given the opportunity to revise the commit message, and so, we are going to click on Confirm merge button and we should see a message saying - Pull request was successfully merged and close, and beside that button, we have the Delete branch button which we will press to delete our branch. Therefore, the example branch is deleted.
To verify, we can go to the repository's master branch, and then click the Branch: master button where we can check the branches we have in our repository. We will see that the example branch is deleted. Also, we will have a commit message that says Merge pull request #2 from Ch-sriram/example (which is the commit message that we get automatically when GItHub does the merge), where #2 is a hotlink directly to the pull request (in case if we want to review our pull request later). When we got to the hotlink #2, we will see the status of Merged (which we can always revert whenever we want to).
To double check, we can go back to the master branch and when we check the lorem.txt file, we will see that the edit we made in the example branch is now reflecting in the master branch.
If we are on remove-lorem branch (use git status
to check), then we checkout from remove-lorem branch into the master branch using git checkout master
command in the git bash, and we should some output of the following sort:
Switched to branch 'master' Your branch is up-to-date with 'origin/master'.
Now, to make sure that our local repository is in sync with the remote repository, we are just going to apply a git pull
in the git bash and we shall see some git house-keeping output and alongside that, we should also see some output similar to the following:
From github.com:Ch-sriram/demo dbcd07..f1dcf21 Fast-forward README.md | 2 ++ 1 file changed, 2 insertions(+)
Now we've integrated all the changes into master from our remote GitHub repository. Now, we are ready to integrate the changes that we have in our local remove-lorem branch using git merge remove-lorem
command in the git bash and we shall see the following output:
Removing lorem.txt
And we should also see the default text editor opened so that we can write our commit merge message (by default, the merge commit message is Merge branch 'remove-lorem'). We will leave the default commit message, save and exit the default text editor, and we shall see some output which is of the following sort:
Removing lorem.txt Merge made by the 'recursive' strategy. lorem.txt | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 lorem.txt
Now if we check the status of our repository (using git status
), we shall see something of the following sort:
On branch master Your branch is ahead of 'origin/master' by 2 commits. (use "git push" to publish your local commits) nothing to commit, working directory clean
Now, we can synchronize our local changes to the remote repository into the GitHub repository using git push
, and we shall see some git house-keeping output and in the end, we shall see the following output:
To [email protected]:Ch-sriram/demo.git dbcd07..e86646b master -> master
Now, we have successfully published out local repository's changes into the remote repository's master hosted on GitHub. We can verify it on GitHub by opening the demo repository page on GitHub using a web-browser.
Now, when we check our remote repository on GitHub using the web browser, we will see that there are still 2 branches, but we see that, since we merged the remove-lorem branch with the master branch, we can see that we do NOT have lorem.txt file in our master branch also. When we switch to remove-lorem branch, we can see the message as a sub-title that This branch is 3 commits behind master and that's because remove-lorem doesn't have the changes from the example branch that was integrated into the master branch already. The remove-lorem branch also doesn't have the merge commits associated with merging back into master.
We can delete the remove-lorem branch by going into the "branches" tab and then clicking on the trash-can icon beside the remove-lorem branch. Until e refresh the page, we have the option to Restore the branch that we just deleted, but it is always better to delete the branch iff we are completely done implementing the feature we wanted to integrate with our master branch.
Now, going back to the local repository, when we type in git branch -a
in the git bash, we should see some output of the following sort:
* master remove-lorem remotes/origin/HEAD -> origin/master remotes/origin/master remotes/origin/remove-lorem
We can see that remove-lorem is still a branch that we have on GitHub, or at least the reference of it is still there in the local repository which is under remotes/origin/remove-lorem. Therefore, we will delete our branch in the local repository using git branch -d remove-lorem
command in the git bash and we shall see an output that's similar to the following:
Deleted branch remove-lorem (was 30278cb)
Note that git would've warned us if we wouldn't have had integrated the changes made inside the remove-lorem branch into the master branch. Now, we can check the branches we have in our repository using git branch -a
, and we see the following output:
* master remotes/origin/HEAD -> origin/master remotes/origin/master remotes/origin/remove-lorem
We can see that the remove-lorem branch which was the local repository's branch, has been removed. But we still have the reference to the remove-lorem branch which is present in the remote repository on GitHub referenced at remotes/origin/remove-lorem. However, we know that that branch doesn't exist on GitHub and therefore, it is just a stale reference. To update those stale references, we use the fetch command, i.e., git fetch -p
where "-p" is the prune option, which means that the fetch is going to look for any dead branches and remove those references between the local and the remote repositories. We use that command, and we shall see some output of the following sort:
From github.com:Ch-sriram/demo x [deleted] (none) -> origin/remove-lorem
Now when we type in git branch -a
in the git bash, we see the following output in the terminal:
* master remotes/origin/master remotes/origin/HEAD
Now, in the local repository, we only have the branch references to the remote repository's master branch and the special pointer called HEAD. And we can see that remove-lorem branch is no longer available locally or is available as a reference in the remote repository on GitHub.
In addition to starting a branch in the local repository and then pushing the changes into the remote repository hosted on GitHub, we can also work from the other direction, where we create a branch on GitHub and then checkout that particular branch on the local repository using the git bash.
Therefore, we will create a new branch in the remote repository named update-licence on GitHub (instructions to create a new branch can be found here at 10.6). Now, after we create the new branch, we will go into the LICENCE.md file and edit it on GitHub and then commit the changes on the update-licence branch.
Now, let's say we want to continue making changes to the update-licence branch, but we will do that on our local system, in our local repository, and so, we will switch back over to our terminal and we are currently in the demo git repository in the master branch where we have a clean working directory. Now, if we type in git branch -a
, we should see the following output:
* master remotes/origin/master
Now, since we have made some changes in our remote repository hosted at GitHub, so we need to update those changes made into the local repository using git fetch
command, which will update our remotes, which will include any new branches that git/github might be aware of. Now when we check all of our branches using git branch -a
, we will see the following output:
* master remotes/origin/master remotes/origin/update-licence
Now, we can see that we have a new branch update-licence within the origin reference as part of the remotes from the .git directory. There are several ways in which we can link the local branch with the update-licence branch present at the remote repository on GitHub. But the simples way is to use the checkout command, i.e., using the git checkout update-licence
command, even though the update-licence branch isn't available in the local repository. Now this only works if we have a single remote repository, and the reason why this works is because git will figure out what we mean by the name of the branch that we are specifying. Git is first going to look for a local branch called update-licence and when it fails to find it, it will look for the name of that branch among the remotes. As long as the branch name (the git is looking for) is unique among our remotes, git will find and it will be able to checkout that branch -- however, if we have a scenario where we have multiple remotes pointing to similar repositories, where there are overlaps with the names of the branches, git is not going to be able to figure that out, and thus, we'll need to handle that manually to link a local branch with the specific branch on our remote that we want to link. However, in this case, git was able to find update-licence branch in the remotes since we only have single remote named origin and in doing so, git made the assumption that we want to track the branch, so that my local version (local repository) of update-licence is tracking to the GitHub version (remote repository) of it. And then git automatically switches to that new branch, which it is a new local branch created automatically by git.
Now when we use git branch -a
, we shall see an output of the following sort:
master * update-licence remotes/origin/master remotes/origin/update-licence
We can see that we have new local branch called update-licence and technically, they are two different distinct branches because we are working with two different repositories - a local repository & a remote repository.
Now we will open up our LICENCE.md file and make some more changes into it, and then save and close it. We will commit the changes and then we will publish the changes made in our local repository to the remote repository hosted on GitHub using git push
command (we don't need to specify the remote reference or the branch since the tracking relationship has already been established by git previously) and git figures out which branch these changes need to be pushed, and it pushed to the corresponding update-licence branch on GitHub.
we can verify the published changes from the local repository to the remote repository from our remote repository hosted at GitHub using the web browser.
This time, we will make few more changes to the update-licence branch and then sync them back to my local repository, then synchronize all our changes to tracking branches locally, and then walk through the process of merging locally, then deleting the branch both locally and remotely from the command line.
In our demo GitHub remote repository, we will switch to the update-licence branch and then we will open the LICENCE.txt file and then make a quick tweak to the file and then commit the changes directly to the update-licence branch.
Now, we will switch back to the terminal on the demo's local repository, where we will check the status of our repository using git status
, and we shall see the following output:
On branch update-licence Your branch is up-to-date with 'origin/update-licence'. nothing to commit, working directory clean
If we are not in the update-licence branch, we would switch to the the branch using git checkout update-licence
command in the terminal.
Now, we shall synchronize our remote repository's changes into the local repository, and for that, we can easily do it using git pull
and get the latest update that's on the remote repository on GitHub, but we will see another way to synchronize the remote repo with the local repo.
Now, eventually, we will be merging with the master branch, so we will switch to the master branch using git checkout master
, and we shall see the following output:
Switched to branch 'master' Your branch is up-to-date with 'origin/master'.
Now, in the master branch, if we use git pull
, we will fetch and merge from the master branch of the remote repo, into the master branch of the local repo. We can check the configuration using git config --global -e
command, and we will see the following output on vim:
[filter "lfs"] required = true lean = git-lfs clean -- %f smudge = git-lfs smudge -- %f process = git-lfs filter-process [user] name = Chandrabhatta Sriram email = [email protected] [winUpdater] recentlySeenVersion = 2.25.0.windows.1 [diff] tool = p4merge [difftool "p4merge"] path = C:/Program Files/Perforce/p4merge.exe [difftool] prompt = false [merge] tool = prmerge [mergetool "p4merge"] path = C:\\Program Files\\Perforce\\p4merge.exe [mergetool] prompt = false [alias] hist = log --oneline --graph --decorate --all [push] default = simple
At the bottom, we can see the the push command has default = simple which smply means that whatever branch we are currently on, push and pull will only apply to that particular branch. However, if we want to override that, we can use an option when we do a pull, i.e., by using git pull --all
, even though, we are currently on the master branch, Git will update all tracking branches at the same time, i.e., all branches in the local repositories will merge into all branches of the remote repositories viz. master to master and update-licence to update-licence.
And now, we will integrate our changes using the merge command using git merge update-licence
command and we shall some output of the following sort:
Updating e86646b..9cg9c0f Fast-forward README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
Now we have integrated the changes into the local repository, and therefore, now we can check the status of our local repository, and we will see that we are on master branch and we will be ahead by some number of commits from 'origin/master'.
We will publish the changes in our local repository, into our remote repository using git push
command and we shall see some output of the following sort:
To [email protected]:Ch-sriram/demo.git e86646b..9cg9c0f master -> master
Now that we have published the changes, we no longer need the update-licence branch and by using git branch -d update-licence
command, we can delete the update-licence branch. Now, by typing in git branch -a
, we can see the following output:
* master remotes/origin/master remotes/origin/update-licence
We can see that update-licence branch is deleted from the local repository, but not from the remote repository. We can delete the update-licence branch remotely on GitHub, but we can do the same at the local repository also. We can use git push origin :update-licence
command [where : (colon) is used to mention to GitHub to delete the branch that's followed by the : (colon)] and we shall see the following output:
To [email protected]:Ch-sriram/demo.git - [deleted] update-licence
When we check our remote repository hosted on GitHub, we can see that it got instantly updated that the update-licence branch is deleted. We can see that we only have 1 branch in the remote repository on GitHub.
Using the command line, we will type in git branch -a
to check the branches we have in our local git repository, and we shall see the following output:
* master remotes/origin/master
When we removed the update-licence branch in the remote repository also, git automatically updated our remote branch references in the local repository as well and therefore, we don't need to apply a fetch command with the prune option.
When we apply a pull with the --rebase option, git rewinds the current commits that are on our branch to a point to where the branch we're merging in originally diverged from. Then, playing back the commits that happened on that branch that we are wanting to bring in; and then, after that, playing on top of that any commits that have happened on the branch that we are currently on, thus making any changes that we have including the changes that happened on the remote branch, but with our changes made ahead of them (that were local).
This is a good strategy when we are working on something, and we want to make sure that our changes are ahead of whatever's going on from the remote side.
If all of the explaination feels confusing, we can always apply the concept practically to understand what the explaination was. To start things off, we're in our local repository on the demo git repository on the master branch and at this point, the working directory is clean with nothing to commit. We can verify that by applying a git pull
and we see the following output:
Already up-to-date.
Now, if we jump over to the remote repository on GitHub, we can edit the LICENCE.txt file there, and then commit the changes made directly to the master branch. For ease in understanding, we can have a commit message that say something as follows - "LICENCE Updated on master on GitHub before rebase".
Now, we will switch back to our local repository back in our terminal. Before we pull any changes from GitHub, we are going to make some changes on the local side first (a malice change so that there's a definite merge conflict). Let's tweak the LICENCE.txt file first. After that tweak, we can commit the changes locally with the commit message git commit -am "Updating LICENCE locally before rebase"
. Now, if we do a git fetch
, we will update our local git repository with whatever changed in our remote side. And now if we apply a git status
command, we can see the following output:
On branch master Your branch and 'origin/master' have diverged, and have 1 and 1 different commit each, respectively. (use "git pull" to merge the remote branch into yours) nothing to commit, working directory clean
In this case, we can just merge in the changes from or remote repository into the local repository, and in doing so, it will create an automatic merge, which would result in a merge commit, and that's okay.
BUT, what if want to make sure that whatever we are currently working on, stays ahead of whatever's currently in GitHub. To do that, we can use the rebase command. Therefore, we type in git pull --rebase
, where --rebase option first rewinds the HEAD to replay our work from the remote repository, into the local repository and then apply the latest local repository changes (commits) on top of it. We can check the re-winding process using our alias (we created here in 6.9) git hist
command and we can see some output of the following sort:
* b18gb1c (HEAD -> master) Updating LICENCE locally before rebase * 2eb9c33 (origin/master) LICENCE Updated on master on GitHub before rebase * e6d2589 Merge conflict resolved: LICENCE Updated |\ | * bfe9739 Updating LICENCE.txt * | 3ba7730 LICENCE Update |/ * 9a98e53 README Update: 10.11 Cleaning Up By Deleting Branches and References && Link Update in Table of Contents * 7191c89 LICENCE Update * 0e5906a LICENCE.md Update * 56c1db4 README Update: 10.10 Table of Contents Link Integrated * a3a9e1d README Update: 10.10 Locally Switching to a Branch on GitHub * 0abc089 README Update: Minor Changes * 866f8dc lorem.txt removed * 8226e97 Merge branch remove-lorem && README Update |\ | * e9353c4 Removed lorem.txt file * | 3aafe1a README Update: 10.9 Link Integrated in Table of Contents * | 87e1467 README Update: 10.9 Merging Branches Locally * | 905a778 README Update: 10.8 Link Update in Table of Contents * | 426bfcb Merge branch 'master' of https://github.com/Ch-sriram/Just-Git-In |\ \ | * \ f984afd Merge pull request #2 from Ch-sriram/example | |\ \ | | * | 3033da1 Updating lorem.txt in the example branch | |/ / * | | 981e9fe README Update: 10.8 Comparing and Pull Requests in GitHub : ...
We can see that the commit 2eb9c33 is what 'origin/master' is currently pointing to, which is our last commit from GitHub on 'origin/master' followed by the commit we just made on our local copy on master, which is updating the LICENCE file before the rebase. However, that commit did not include any of the updates from GitHub, and therefore, that's an interesting distinction.
Before, when we had applied the local changes on master, it only had the changes that we included while we were in the local repository, it did not include anything that we changed on GitHub. We can verify that the changes we made in our remote repository, made it back into our local repo by simply checking the LICENCE.txt file. When we check the status of our repository (using git status
), we can see that the local repo is ahead of the remote repo by 1 commit, which is the commit made in the local repo. Now, we will publish the changes that we made locally, to the remote repository on GitHub using git push
command, and we shall see the following output:
To [email protected]:Ch-sriram/demo.git 2eb9c33..b18gb1c
In the demo remote repository on GitHub, on the master branch. In a lot of workflows, the master branch represents whatever is currently in production. Active development should not happen directly on the master branch. Instead, there should be another branch which is also long-lived where all the development effort goes, and then periodically, those changes are merged back into the master branch for production.
So from now on, we will make a branch called develop which will be the default branch of our repository. To do that, we will create a new branch in GitHub using the instructions present here in 10.6. After that, we navigate to the Repository Settings in GitHub's webpage and then goto the Branches tab under Options, where we will see the Default branch settings through which we change the repository to develop branch and then press the Update button to make sure that the changes stick to the repository. We've successfully updated the default branch for the repository.
Now when we get to the main page of our repository, we can see that the repository's default branch is now the develop branch. Now, whenever we try to make a Pull Request, we will see that the base destination of the pull request is now the develop branch (and not the master branch). Another thing is, whenever we try to clone the repository using either the HTTPS/SSH clone URL in the terminal in the local system, using the git clone [email protected]:Ch-sriram/demo.git
command, we will see that the repository is cloned inside the local system, and when we type in git branch -a
command in the terminal, we will see the following output:
* develop remotes/origin/HEAD -> origin/develop remotes/origin/develop remotes/origin/master
We can see that master is not present in the local repository, it is only present in the remote repository, and so, we have to make a new master branch inside the local repository using git checkout master
as we've seen in section 10.10, where the remote master is merged with the local master branch (after a new local master branch is created automatically). So now we've both master and develop branches in our local repository and remote repository, but the develop branch is the default branch. Now when we type in git branch -a
command, we shall see the following output:
develop * master remotes/origin/HEAD -> origin/develop remotes/origin/develop remotes/origin/master
We will now look into resolving conflicts between our local repository and our remote repository hosted on GitHub. In the remote repository on GitHub, we are in our demo repository. While we are here, we are going to make a quick change that we know will lead to a conflict when pulling to the local repo. We will make a change in LICENCE.txt file and add some text at the end of the file, and then type in the Commit message, let's say "LICENCE Update: Problematic changes from GitHub", and then commit the changes directly to the develop branch.
Now, we will go to our local repo - in the terminal, we will switch over to the develop branch using git checkout develop
command and we shall see the following output:
Switched to branch 'develop' Your branch is up-to-date with 'origin/develop'.
Now that we are on the develop branch, we can simply fetch the changes made in the remote repository using the git fetch
command, and we shall see some house-keeping output after which, we would see the following output:
From github.com:Ch-sriram/demo.git b18gb1d..5bc44f7 develop -> origin/develop
Now that we've fetched the changes from the remote repo at GitHub, when we check the status of the local repo using git status
, we will an output similar to the following:
On branch develop Your branch is behind 'origin/develop' by 1 commit, and can be fast-forwarded (use "git pull" to update your local branch) nothing to commit, working directory clean
Now, we will update our LICENCE.txt file before we issue the pull request (so as to cause a merge conflict when we pull from the remote repo to the local repo). We will again, update our LICENCE.txt file in the local repo, and add some content at the end of the LICENCE.txt file. We will commit the changes using git commit -am "LICENCE Update with malice on local side"
. Now when we check the status of our local repository, we shall the following output:
On branch develop Your branch and 'origin/develop' have diverged, and have 1 and 1 different commit each, respectively. (use "git pull" to merge the remote branch into yours) nothing to commit, working directory clean
Now if we try to pull (i.e., fetch + merge) the changes from the remote to the local side using git pull
, we would get the following output:
Auto-merging LICENCE.txt CONFLICT (content): Merge conflict in LICENCE.txt Automatic merge failed; fix conflicts and then commit the result. (develop|MERGING) demo $
We can see that we're having a Merge Conflict and we're in the MERGING State. Like before, as we tried to hande a Merge Conflict in section 7.5, we can do the same here by either resolving the merge conflict directly, or by using the mergetool. Since we have a mergetool configured as shown in section 5, we can use the mergetool to resolve our merge conflict.
In the MERGING State, we will type in git mergetool
, and the Helix P4Merge tool will open, where we will resolve the conflict and then save the changes we made. After that, we will the changes in this state using git commit -m "Resolving the merge conflict"
command in the terminal, and if everything went well, we should exit the MERGING State, back to the normal state. Now, when we check the status of our repository using git status
, we will see that we have the following output:
On branch develop Your branch is ahead of 'origin/develop' by 2 commits. (use "git push" to publish your local commits ) Untracked files: (use "git add <file>..." to include in what will be committed) LICENCE.txt.orig nothing added to commit but untracked files present (use "git add" to track)
We can remove the .orig file using rm *.orig
command. Now, if check the status of our repository, we will see the following output in the terminal:
On branch develop Your branch is ahead of 'origin/develop' by 2 commits. (use "git push" to publish your local commits) nothing to commit, working directory clean
We will now simply publish the changes into the remote repository hosted at GitHub by using the git push
command, and we shall see some house-keeping output, after which, we will see the following output:
To [email protected]/Ch-sriram/demo.git 6bc44f7..470c943 develop -> develop
We can verify the publish on GitHub by checking out the LICENCE.txt file.
Tags are often a forgotten aspect of GitHub, but with our project hosted on GitHub, tags can play a larger role in the life of our project.
Locally, we use tags to mark important events, while that remains the same on GitHub, it extends the tagging concepts with Releases, allowing us to attach release notes and provide links to download archives of our project to the end-user(s)/other-programmer(s).
Now, most of the information about local tags is given in section 7.6, apart from that, we have additional things we can do with tags in GitHub.
To list all the tags associated with our local repository, we can use the command git tag --list
or else, we can just type in git tag
, which will also give us a list of tags in our repository.
We can associate a lightweight tag to a specific commit-ID using the command git tag tag-name commit-ID
(ex: git tag v0.1-alpha a3c4edv
, where tag-name is v0.1-aplha and the commit-ID is a3c4edv) where tag-name is any name that can be given to the tag and the commit-ID is the SHA-1 hash ID given to the tag and till that commit point (snapshot), the tag associated, will act as a release on GitHub. Instead of the commit-ID being an actual ID, we can also give a reference as the commit-ID, i.e., for example, we can type in the command git tag v2.0 HEAD
in the terminal, and the tag v2.0 will be associated with the latest commit point (NOTE: if we use the name of a branch as a commit-ID say git tag v2.0 master
, then the tag will be associated to the latest commit of the respective branch).
We can do the same with annotated tags also, the only difference is, for annotated tags, we use the following syntax for the command - git tag -a tag-name -m "tag-information" commit-ID
where -a stands for annotated, tag-information can be though of as release notes for the tag associated to the specific commit-ID. Example: git tag -a v0.2-alpha -m "Release v0.2 (Aplha)" a3c4edv
.
We can create a simple/lightweight tag for the master branch of our demo repository by typing in git tag stable master
in our terminal, and then we can create a simple tag for the develop branch of our demo repository by typing in git tag unstable develop
in our terminal.
We can also create a release/annotated tag for a specific commit ID in our demo repository by typing in git tag -a v0.1-alpha -m "Release v0.1 (Alpha)" d741b7e
in our terminal.
When we type in git tag
command in our terminal, we should see the following output:
stable unstable v0.1-alpha v1.0
Note that tag v1.0 was created in section 7.6. We can see that all our created tags are listed in the output above.
To check which tag is associated with what commit, we simply type in our alias created in section 6.9 which is git hist
command, in our terminal and we shall an output similar to the following log:
* a70158c (HEAD -> develop, tag: unstable, origin/develop, origin/HEAD) README Link Update: Section 11 * 63eca36 README Update: Section 11 - GitHub Tags & Releases * 1e98ed8 Resolved the merge conflict in LICENCE |\ | * 4b15d00 LICENCE update in remote on GitHub * | 46b999e LICENCE Update with malice on local side |/ * bd43565 README Update: Section 10.14 Minor Edits * 117c07e README Update: 10.14 Dealing with a Conflict while Pulling * 14b6ce1 README Update: Section 10.13 Edited * cf01906 (tag: stable, origin/master, master) README: Section 10.13 edited * 96781e9 README: Section 10.13 updated w. more content * 9445fa7 README Update: 10.13 Changing Default Branch in GitHub . . . . * 3c5ec7d (tag: v1.0) Updating .gitignore to exclude .orig files * afb7058 Resolving Conflict in LICENCE.md |\ | * 7c48e4c very bad update in LICENCE.md * | cb652a3 LICENCE.md Update (Merge Conflict Possible) |/ * 85a1d27 README Update * 593fe03 Adding new updates from master branch to 'updates' branch * 6b79701 README Update * 20c7c63 Adding .gitignore file * eb94f26 Adding .gitignore file * 801a434 README Update * cd82d85 README Updated * 1f28d75 deleted demo.txt file * 64f7486 renamed example.txt to demo.txt * 53f8779 adding example.txt * d741b7e (tag: v0.1-alpha) README Update * 6bf863a README Update * 634793a README Update * 8af524a README & LICENCE Update * 231b4cd README Update
To show information about a specific tag, we can simply type in git show tag-name
command in our terminal. For instance, if we type in git show v0.1-aplha
, we would get an output similar to the following:
tag v0.1-alpha Tagger: Chandrabhatta Sriram <[email protected]> Date: Tue Mar 31 15:00:20 2020 +0530 Release v0.1 (Alpha) commit d741b7eb68a7b30d4fc11f55a0ad5ca5e919ad83 (tag: v0.1-alpha) Author: Chandrabhatta Sriram <[email protected]> Date: Mon Mar 2 13:26:27 2020 +0530 README Update diff --git a/README.md b/README.md index 5e4a1a0..54aaecd 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Resources 1. [Official Git Documentation](https://git-scm.com/docs) 2. [Jason Taylor's Online Git & GitHub Course](https://www.udemy.com/course/github-ultimate/) + ## Table Of Contents 1. [What is Git?]() 2. [What is a Repository?]() @@ -19,6 +20,9 @@ Resources 5. [Initializing a Git Repository w. Existing Files/Project]() 6. [Commit History w. Log and Show]() 7. [Express Commits]() + 8. [Rolling Back Changes]() + + ## 1. What is Git? @@ -174,4 +178,20 @@ We can get rid of the newly created file using <code>rm <new-file-name></code> When we say we are adding the modified files into the git's staging area, we are basically adding the modifications to the git repository, not the entire modified file. -Now when we type in <code>git log</code> into the git bash, we can see all the commits we made till now and also the most recent commit that we made earlier at the top of the logs. \ No newline at end of file +Now when we type in <code>git log</code> into the git bash, we can see all the commits we made till now and also the most recent commit that we made earlier at the top of the logs. + + +### 6.8 Rolling Back Changes +Here, we will back out the changes we made previously by unstaging our changes from the git staging area and then finally we will revert our changes entirely. + +In our "demo" git repository, when we git bash the command <code>git status</code>, we can see that there will be nothing to commit and we will see the following message: <em>nothing to commit, working directory clean</em>. + +Now, we will modify the README.md file again and we will put some random text inside it. We save and close the README and then we again type in <code>git status</code> command inside the git bash. We can see that we will have the message - <strong>modified: README.md</strong> and then we can add the changes into the repository using <code>git add .</code> command in our git bash. When we type in <code>git status</code> again, we can see that we see the message - <strong>Changes to be committed: modified: README.md</strong> in the git bash. + +Now, to unstage the changes, or undo the commit, we can type in the command <code>git reset HEAD README.md</code> where README.md is the file to be unstaged from the user's repository. When we open up our README.md file, the changes that we made, will still be there, but the changes that we made have simply been unstaged, that means, when we type in the command <code>git status</code>, we can see that the message we get is - <strong>Changes not staged for commit: modified: README.md</strong> in the git bash. + +Now, if we don't want the changes that we made and to discard the changes made to the repository, we can simply revert back to the last known good state (or commit) of the respective file (here, it is README.md) whose details are available inside the .git folder, we simply type in <code>git checkout -- <file-name></code> (ex: <code>git checkout -- README.md</code>). Now, we can check the status of the repository with <code>git status</code> command, which will respond with the message - <em>nothing to commit, working directory clean</em> in the master branch of our "demo" repository. Now if we open README.md, we can see that the changes we made are also gone. + + +### 6.9 Creating New Commands - Git Alias +We will create a git alias to shorten a command into a smaller command. In our "demo" repository, in the master branch, when we type in <code>git status</code>, we can see that we have <em>nothing to commit, working directory clean</em> as the message. \ No newline at end of file (END)
We created 3 tags in section 11.1 and when we simply apply a push from the local repository using git push
command in our terminal, we can see that in our remote repository hosted on GitHub, we don't have any new releases on either the develop branch or the master branch when we check the Releases tab.
Note: In GitHub, Releases and Tags are almost synonymous to each other, although there's a minor distinction between the two, which we will get into later.
Before moving forward, we'll merge in the changes that were made in our remote repository, into our local repository using git pull
command. After that, if we type in git push
, we shall get the following output:
Everything up-to-date
which means that there's nothing to push. Now, we can see that, by default, tag are not sent to GitHub using the standard git's push command. Now, to actually push the tags to the remote repository hosted on GitHub, we type in the push command with the following syntax - git push remote-reference tag-name
. For instance, if we want to push the stable tag (which is the name of the tag associated to the master branch) to remote, we type in git push origin stable
, and we shall see the following output:
To [email protected]:Ch-sriram/demo.git * [new tag] stable -> stable
We can see that git has given a response saying that it has pushed a new tag stable (of the local repository) to a newly created tag stable (in the remote repository). We can verify this on GitHub (remote side), and we will see a new tag named stable along with its associated commit-ID in the References tab of our demo remote repository. We also have the options for downloading the source code associated with that tag on GitHub in .zip
or .tar.gz
formats.
From our terminal in the local side, we just sent one specific tag to our remote side, we could keep on just sending all our tags individually, one-by-one, however we have several tags, and we want to publish all of them to the remote repository on GitHub. To push all our tags in the local side onto the remote side, we use git's push command along with the --tags option, i.e., we type in git push --tags
command in our terminal, and we shall see the following output:
To [email protected]:Ch-sriram/demo.git * [new tag] unstable -> unstable * [new tag] v0.1-alpha -> v0.1-alpha
We can see that git pushed rest of our tags from the local side on to the remote side on GitHub. We can verify that tag releases on GitHub under the References tab of our remote repository.
We are going to delete some tags both directly on GitHub (remote repository) and on the local system (local repository). In our demo GitHub remote repository, we will go to the releases tab and we will be re-directed to github.com/username/repo-name/tags page in Github. In there, we've two tabs which are Releases and Tags. We click on the Tags tab to see the tags we've created till now. We click on v0.1-alpha tag and we get re-directed to the specific page related to v0.1-alpha tag where we have an option to delete the tag using the Delete button. We will click that button and delete the tag. We will see that the deleted tag is no longer listed inside our releases tab of the remote GitHub repository. However, the deleted tag still exists on our local side.
In our local terminal, on the develop branch with a clean working directory, if we type in git tag
, we should see a similar output as the following below:
stable unstable v0.1-alpha v1.0
We can see that the tag that we deleted on GitHub (which is v0.1-alpha) is still listed as a tag in our local repository. Before deleting the tag on our local repository, we will apply a fetch command to sync our remote repository on GitHub with the local local repository. Therefore, we type in git fetch -p
(where -p is a prune option) command for the reason mentioned earlier (syncing GitHub's remote repo with the local repo). We will now delete the v0.1-alpha tag on our local system by typing in git tag -d v0.1-alpha
command in our terminal and we'll see the following output:
Deleted tag 'v0.1-alpha' (was d741b7e)
Now if we type in git tag
command in our terminal, we will see the following output:
stable unstable v1.0
We can notice that we are missing the tag that we deleted just now, from the list of tags on our local repository.
Now, this is how we delete tags starting on the remote side (on GitHub) and syncing the deletion on the local side (in the local repository).
Now, we will delete the tag on the local system (local repository) and then sync the remote repository on GitHub by deleting the same tag on GitHub using the local side's git command(s).
This time, we'll delete the tag v1.0 from the local side, and then delete it on the remote side. If we open our repository's releases/tags on GitHub, we will see that we currently have 3 tags. Therefore, we will delete the tag using the command git tag -d v1.0
where -d option is the --delete variant and v1.0 is the name of the tag. We will see the following output when we delete the tag:
Deleted tag 'v1.0' (was 3c5ec7d)
After that, we can delete the tag on the remote side by pushing to the remote using no tag name when we use the push command with the remote reference. We will do that using git push origin :v1.0
command (note that the command uses colon ':' before the tag-name) in our terminal and we shall see the following output:
To [email protected]/Ch-sriram/demo.git - [deleted] v1.0
We can verify if the push was successful by going to the tags page of the demo's remote repository on GitHub. We will see that tag v1.0 is not listed in the references tab of our remote repository.
We are going to update our existing tags in the local environment, to point to a new commit. Then, we will update the same in our remote repository on GitHub to reflect that.
Before beginning, we will make a new annotated tag by following the steps in section 11.1 and let's say we create the annotated tag v0.9-beta. We're in the demo remote GitHub repository in the tags/releases page, where we should see 3 tags listed (which are v0.9-beta, unstable & stable).
Heading over to our local environment, we'll edit our dummy LICENCE.txt file (meaning that, we'll add some text to the file, not remove the existing text) on the develop branch. We'll commit those changes and then publish (push) them on to the remote repository on GitHub.
When we check our log using the alias we created in section 6.9 which is git hist
, we should see some output similar to the following:
* eg64f8b (HEAD -> develop, origin/develop, origin/HEAD) LICENCE Update: Adding the Purpose Section * df53e7a README Update: Section 11.3 - Deleting Tags on GitHub * 4c92f48 README Update: Section - 11.2 Pushing Local Tags to GitHub * 93e8951 README Link Update: Section 11.1 * 157ff7c README Update: Section 11.1 - Local Tags (Review) * a70158c (tag: unstable) README Link Update: Section 11 * 63eca36 README Update: Section 11 - GitHub Tags & Releases * 1e98ed8 Resolved the merge conflict in LICENCE |\ | * 4b15d00 LICENCE update in remote on GitHub * | 46b999e LICENCE Update with malice on local side |/ * bd43565 README Update: Section 10.14 Minor Edits :
We can see that the tag unstable is associated to the commit with the ID of a70158c. We will update our tag to point to the latest commit (the commit ID which is pointed by HEAD). In order update an existing tag (associated to some commit ID) to now associate with a new commit ID, we use the --force option (abbreviation: -f) in the git's tag command, i.e., we type in git tag -f unstable HEAD
command (unstable is the tag-name and HEAD is the optional reference to the commit ID. The commit ID is optional as if we don't mention the commit ID, whatever commit ID is being pointed by the HEAD pointer of the current branch will be the commit ID that will be associated with the newly created tag by default) in our terminal, and we should see the following output:
Updated tag 'unstable' (was a70158c)
We can see that git gives us the information about the updated tag's name and along with that, we get the information about which commit ID it was previously associated with. Now, if we type in our alias which is git hist
, we shall see the following output in our terminal:
* eg64f8b (HEAD -> develop, tag: unstable, origin/develop, origin/HEAD) LICENCE Update: Adding the Purpose Section * df53e7a README Update: Section 11.3 - Deleting Tags on GitHub * 4c92f48 README Update: Section - 11.2 Pushing Local Tags to GitHub * 93e8951 README Link Update: Section 11.1 * 157ff7c README Update: Section 11.1 - Local Tags (Review) * a70158c README Link Update: Section 11 * 63eca36 README Update: Section 11 - GitHub Tags & Releases * 1e98ed8 Resolved the merge conflict in LICENCE |\ | * 4b15d00 LICENCE update in remote on GitHub * | 46b999e LICENCE Update with malice on local side |/ * bd43565 README Update: Section 10.14 Minor Edits :
We can see that the tag unstable in the develop branch has moved up to the latest commit. Before we check out GitHub, let's push our commits that we have made till now, onto GitHub by typing in git push
, then before pushing the tag, we type in git pull
(to sync the local repo with the remote repo), and finally, we type in git push origin develop
in our terminal.
We will open our remote repository on GitHub and see that all our commits we made were pushed onto the remote side, but the in the references tab, the tag unstable hasn't changed at all, i.e., unstable tag is still pointing to the older commit ID which means that we didn't push our tag changes made in the local side, onto the remote side on GitHub. Therefore, let's push our tag change(s) using git push origin unstable
command in our terminal and we shall see the following output:
To [email protected]/Ch-sriram/demo.git ! [rejected] unstable -> unstable (already exists) error: failed to push some refs to '[email protected]/Ch-sriram/demo.git' hint: Updates were rejected because the tag already exists in the remote.
We can see that the tag push (update) gets rejected as mentioned in the reason given in the output aforementioned. Therefore, we can delete that existing tag (which is unstable tag) on the remote side at GitHub using the instructions given in section 11.3 and then push the updated unstable tag using the same command as given above (which is git push origin unstable
) and that would work.
There's another way to make it work which is by using the --force (or -f) option, i.e., using the command git push --force origin unstable
in our local terminal and it would push the tag changes in unstable to the remote on GitHub forcefully and we shall see an output of the following sort:
To [email protected]/Ch-sriram/demo.git + a70158c...eg64f8b unstable -> unstable (forced update)
We can see the git lets us know that the push was a forced update. In general, we have to be careful with the --force option because we can cause a lot of grief (to fellow developers), since git repositories are distributed. We can verify the update back in the remote side on GitHub's tags/references tab.
We will know the differences between tags and releases in this section. We will also create a new release on GitHub.
We are currently on the demo remote github repository in the releases (tags) tab, and by default, out of the two sub-tabs which are References and Tags tabs, GitHub's website routes/lands us on the Releases sub-tab. When we go over to the Tags sub-tab, we will notice almost no difference between the previous sub-tab (which was Releases sub-tab) and the current sub-tab. The only difference we'd notice is that, in the Tags sub-tab, we have the option to Add release notes (As of 2020, the button has been removed and has been replaced with 3 horizontal dots, which when clicked on, shows a button called Create release) and this is the only vital difference between releases and tags.
In the Tags sub-tab, we will click on v0.9-beta tag's Add release notes (or Create release button) hyperlink which re-routes us back onto the Releases sub-tab where we can add the release notes in a mark-down format (along with a Release title, let's say we named our release as Beta Release v0.9). In the mark-down, we can also reference the specific issues using the pound symbol (#)[i.e., Pull Request #2].
After writing down our release notes, we can scroll down, and see that there's a checkbox available for making this particular release a pre-release by checking the This is a pre-release checkbox. We can either check that checkbox or we can leave it depending on whether the release is actually a pre-release or not and then click Publish release button to puclish the release in our repository.
We can verify our release in the Releases sub-tab in the releases tab of our demo github repository and we will see that the release is mentioned as Pre-release along with the release notes in the markdown format.
Now, if we compare the differences between tags and releases, we can see that the tags page is just a simply list of tags, and the releases page is the tag, along with a whole lot of information that is associated to the tag, where the release also have assets for production ready use.
In this section, we are going to look into the Releases page on GitHub (Learn how to start a release in section 11.5) and also, we'll know how to delete our release.
In our demo remote github repository, we are specifically within the resources tab and we just created the release - Beta Release v0.9 in section 11.5. If we click on the release title, we'll be re-directed to that respective release's release page on GitHub where we will be able to check the release notes, associated tag and the commit ID associated to the specific release, along with whether the release is a full-release or a pre-release. We will also see an option to edit the release using the Edit release button, and also, we have the option to delete the release using the Delete button.
One thing to keep in mind is that, if we delete a release, it doesn't mean that we also delete the underlying tag associated to that particular release. Therefore now, we will delete our release associated to the tag v0.9-beta which is released and titled as Beta Release v0.9 using the Delete button. We click on the Delete button and we will delete the release. We will be re-directed to the releases page of our github remote repository and we will see that the v0.9-beta tag is still listed in releases and that's because by default, the releases page will simply list out all the tags that are in our remote github repository. At this point, the releases page and the tags page of our remote github repository should look similar.
In order to fully get rif of both the release and the tag associated with it, in the tag page, we click on the v0.9-beta tag, and then we click on the Delete button, which will delete the tag. When we refresh our tags/releases page, we will see that we do NOT see the v0.9-beta tag and also the release associated to it.
In this section, we'll know how to create a new release and tag directly in the remote repository hosted on GitHub.
In our demo GitHub remote repository, within the releases page, in order to create a new release (along with the tag), we click on the Draft a new release button. We will be re-directed towards the release notes form where we have some extra options now. We can create a new tag by giving it a Tag version (let's say we used the tag version as v1.0) in a particular branch which has already been created, or in a new branch, or we can associate the release with a specific commit also (let's say we associated the tag with the master branch), and then as usual, give the Release a Release title (let's say the title for our release is Release 1.0)and then we can Describe the release (which are the release notes). After which, we can check whether the release is a pre-release or a full release by checking or unchecking the This is a pre-release checkbox. After that, we can simply click on the Publish release button to publish the release on GitHub.
We can verify that the release, along with the tag in the releases page of our remote github repository was successfull or not.
Going back to our local environment, in the master branch with the working directory clean (if we aren't on the master branch, then we use the git checkout master
command to checkout to the master branch), we can check the tags we have using the git tag
command in our terminal and we shall see the following output:
stable unstable v0.9-beta
We can see that the tag v0.9-beta that we deleted in section 11.6 is still available in our local repository as we never synced our remote repository changes to our local repository. In this case, we'd leave the local repository as it is with its tags as they're. Now, in order to get all the updates from the remote repository on GitHub, type in git pull
command in the terminal, and we shall see the following output:
From github.com:Ch-sriram/demo.git * [new tag] v1.0 -> v1.0 Already up-to-date
Now, if we check the list of tags using git tag
command, we'll see the v1.0 tag listed in our list of tags, as shown below:
stable unstable v0.9-beta v1.0
We can inspect our tag locally using git show v1.0
command, and we shall see the following output:
commit cf01906ac509b965e762b36d1b04b4be46300018 (HEAD -> master, tag: v1.0, tag: stable, origin/master) Author: Chandrabhatta Sriram Date: Sat Mar 28 15:30:52 2020 +0530 README: Section 10.13 edited diff --git a/README.md b/README.md index 14ff086..3a5d398 100644 --- a/README.md +++ b/README.md @@ -1925,7 +1925,7 @@ To [email protected]:Ch-sriram/demo.git In the demo remote repository on GitHub, on the master branch. In a lot of workflows, the __master__ branch represents whatever is currently in production. Active development should not happen directly on the __master__ branch. Instead, there should be another branch which is also long-lived where all the development effort goes, and then periodically, those changes are merged back into the __master__ branch for production. -So from now on, we will make a branch called __develop__ which will be the __default__ branch of our repository. To do that, we will create a new branch in GitHub using the instructions present __[here in 10.6](https://github.com/Ch-sriram/Just-Git-In#106-creating-branches-on-github)__. After that, we navigate to the __Repository Settings__ in GitHub's webpage and then goto the __Options__ tab, where we will see the __Default branch__ option, where we change the repository to __develop__ branch and then press the __Update__ button to make sure that the changes stick to the repository. We've successfully updated the default branch for the repository. +So from now on, we will make a branch called __develop__ which will be the __default__ branch of our repository. To do that, we will create a new branch in GitHub using the instructions present __[here in 10.6](https://github.com/Ch-sriram/Just-Git-In#106-creating-branches-on-github)__. After that, we navigate to the __Repository Settings__ in GitHub's webpage and then goto the __Branches__ tab under Options, where we will see the __Default branch__ settings through which we change the repository to __develop__ branch and then press the __Update__ button to make sure that the changes stick to the repository. We've successfully updated the default branch for the repository. Now when we get to the main page of our repository, we can see that the repository's default branch is now the __develop__ branch. Now, whenever we try to make a __Pull Request__, we will see that the base destination of the pull request is now the __develop__ branch (and not the __master__ branch). Another thing is, whenever we try to clone the repository using either the HTTPS/SSH clone URL in the terminal in the local system, using thegit clone [email protected]:Ch-sriram/demo.git
command, we will see that the repository is cloned inside the local system, and when we type ingit branch -a
command in the terminal, we will see the following output:
By inspecting about the tag details, we can see that we do NOT have any annotations of the tag listed in the tag's information. Therefore, when we create a tag on GitHub (remote side) using the GitHub interface, GitHub internally creates a lightweight/simple tag.