Skip to content

Latest commit

 

History

History

Git-tutorial

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Quick Tutorial of Git

Git is the version control tool that almost every programmer needs to know and use it proficiently. I always have problems using git when collaborating with other people. Therefore, here comes a brief quick tutorial of common git commands and workflows with github. Some may also come with cute illustrations.

Our tutorial will split into two parts: git local operation and git remote operation.


Git Local

  1. Check the Version

First of all, we want to make sure Git is properly installed on the system.

$ git --version
git version 2.34.1
  1. Configure Git

We typically configure git to use the username/email/password to the ones we register at github. The global keyword setup the config for every repository on the system. We can remove the global keyword to setup only for current repository.

// how to setup config
$ git config --global user.name "Tommy"
$ git config --global user.email "[email protected]"
$ git config --global user.password "hello123"
// how to retrieve config
$ git config user.name
Tommy
$ git config user.email
[email protected]
$ git conig user.password
hello123
  1. Initialize Git

We can create a new repo and initialize git to track a folder as follows:

$ mkdir myproject && cd myproject
$ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint: 
hint: 	git config --global init.defaultBranch <name>
hint: 
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint: 
hint: 	git branch -m <name>
Initialized empty Git repository in ./Git-tutorial/myproject/.git/

We can observe that the hidden folder /.git/ is where git store all the trackings it does for us.

  1. Check Status

We create a new file called index.html in our project repo. We can see the git spots this new file:

$ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	index.html

nothing added to commit but untracked files present (use "git add" to track)

There is shorthand git status --short to show a brief summary of status. The flags are ?? for untracked file, A for files added to stage, M for modified files and D for deleted file.

  1. Stage New Changes

We saw git says we have some untracked files.

In git, every file can be in one of the two stages:

  1. Tracked - git is actively monitoring any changes on that as part of the repo
  2. Untracked - although the file exist, git "ignore" its existence and change

When we hit a milestone working on a file, we can stage the file. It will be ready to be commit later on.

$ git add index.html
$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   index.html

We can see after we stage this file and check the git status, the status goes from untracked to to be committed.

There are shortcuts for staging multiple files all at once. Either git add --all or git add -A would work to stage any changes in current repo.

In the meantime, if we accidentally stage a file we don't really want to stage, we can unstage it through the command:

$ git restore --staged [filename]
  1. Commit Changes

If we are to make a metaphor, staging is we tell git to "please keep an eye on my files in this repo" and commit is we tell git to "please take a photo snapshot of this repo now".

Commit is a "save point" that we may later return back to if we find any bugs in development in later on. As a good practice, we should also try to leave a meaningful message for every commit, so that future-us could know what's exactly included in this commit. The command is as follows:

$ git commit -m "First release of Git Helloworld Project"
[master (root-commit) 48d7a59] First release of Git Helloworld Project
 3 files changed, 26 insertions(+)
 create mode 100644 README.md
 create mode 100644 bluestyle.css
 create mode 100644 index.html

Although it's not recommended, we could also bring a change into commit in one step, skipping the staging. The shortcut command is git commit -a -m [commit message].

  1. Commit History

We can view the commit history so far on this repo by using the log command:

$ git log
commit 2daa2870f5b893fd8c89dedcfb9c0f9151b4413d (HEAD -> master)
Author: Tommy <[email protected]>
Date:   Mon Aug 29 19:56:48 2022 -0400

    Another changes

commit 48d7a59c0e8b12f9a4467b9f156d9c1af488d866
Author: Tommy <[email protected]>
Date:   Mon Aug 29 19:50:34 2022 -0400

    First release of Git Helloworld Project
  1. Git Help

There are so many flag options and commands that we are not expected to memorize.

git help --all will show all available git commands.

git [command] -help will show all the available flags to be set with this command, and git [command] --help will open up the manual for this command. (single slash v.s. double slash)

  1. Git Branch

Branches in git allow us to work on different part of a repo without affecting the main branch.

We can create a new branch:

$ git branch hello-world-image

check all available branches on this repo:

$ git branch
  hello-world-image
* master

and then we will move from the master branch to the newly created branch through checkout command:

$ git checkout hello-world-image
Switched to branch 'hello-world-image'

(The creation and switch to of a new branch could be done in one step by shortcut git checkout -b hello-world-image)

Now let's add a new image img_hello_world.png to the repo and commit it.

$ git add --all
$ git commit -m "Added image to Hello World"

We can see now that there is 4 files in the folder:

$ ls
README.md		img_hello_world.png
bluestyle.css		index.html

and if we switch back to master branch, the new image is not part of it. Our new changes in another branch doesn't impact the master branch.

$ git checkout master
Switched to branch 'master'
$ ls
README.md	bluestyle.css	index.html

10.1 Merge Branch

Suppose now we are very happy with our new development in the hello-world-image branch and decide to merge it back to the master branch.

Since we are making the merge change to master branch, we need to firt make sure we stand on the master branch:

$ git checkout master
Switched to branch 'master'

and then we apply the merge command:

$ git merge hello-world-image
Updating 2daa287..c31a2c0
Fast-forward
 img_hello_world.png | Bin 0 -> 48630 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 img_hello_world.png

Success! We see the git says this merge is "Fast-forward" since it didn't see any confict with these two branches and merge happens smoothly.

However, life is not always so easy. Sometimes merge conflict happens.

Suppose now we go back to hello-world-image and add a new line in index.html. Meanwhile we delete a line in index.html in the master branch, and commit changes in both branches.

and now when we try to merge this commit with the master branch, a conflict happens:

$ git merge hello-world-image
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

we can open index.html and see git has left some marks about why and how conflicts happen there:

<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
<link rel="stylesheet" href="bluestyle.css">
</head>
<body>

<h1>Hello world!</h1>
<p>This is the first file in my new Git Repo.</p>
<<<<<<< HEAD
=======

<p>A old line in our file!</p>
>>>>>>> hello-world-image
</body>
</html>

We can choose to keep everything between <<<<<<< HEAD to =======, or everything between ======= to >>>>>>> hello-world-image.

After we make our choices in the conflict parts in index.html, we re-stage the file and commit it to conclude this merge operation.

$ git add index.html
$ git commit -m "after fix the conflict"
[master bdfc2b1] after fix the conflict

This concludes the merge of two conflicting branches, and by passing the flag -d, we can delete the merged branch to keep a clean workspace:

$ git branch -d hello-world-images
Deleted branch hello-world-image (was 9e7a8ee).

10.2 Rebase Branch

rebase is an alternative to merge. What they do in common is combine the different commits in two branches into one. However, rebase creates a linear track of commits, while merge merges all commits as a single commit, and leave a complete detailed history.

In a word, merge preserves history and rebase rewrite history.

The git operation for rebase is:

// at your feature branch
$ git rebase <branch name>

if any conflict occurs, we must solve comflict as the case mentioned before, and continue the rebase by

$ git rebase --continue

Or, we can run with --skip to skip those commits that introduce problem.

$ git rebase --skip

Additionally, if we want to throw away a conflicted rebase operation, we could do

$ git rebase --abort
  1. Revert Previous Commit

revert is the command to take off a previous commit and make the deletion into a new commit, without modifying with the log.

suppose we dont' want to add that line in the index.html. The change is included in the commit "add a line". We want to remove it.

first of all, we want to check which commit is the one we want go back to: (use --oneline keyword to display a concise git log)

$ git log --oneline
bdfc2b1 (HEAD -> master) fix conflicts:
9e7a8ee add a line
17fd1f5 remove a line
5ec2abb a small change
c31a2c0 Added image to Hello World
2daa287 Another changes
48d7a59 First release of Git Helloworld Project

We can just revert back to the latest commit where we fix the merge conflcits. With --no-edit flag, we will get the default revert message in the log:

$ git revert 9e7a8ee --no-edit

There are some shortcuts for revert. git revert HEAD will revert the latest commit, while git revert HEAD~x will revert the x-th latest commit (index start with 0, so HEAD is actually HEAD~0)

  1. Reset

To compare with the revert command we mentioned above, reset will move the repo all the way back to a previous commit, essentially removing all the changes in between that commit and latest version. revert only remove changes in one previous commit but does keep changes after that commit.

Now, suppose we add two files file1.txt, file2.txt and file3.txt and include them in two separate commits:

$ touch file1.txt file2.txt file3.txt
$ git add file1.txt && git commit -m "Add file1"
$ git add file2.txt && git commit -m "Add file2"
$ git add file3.txt && git commit -m "Add file3"
$ git log --oneline
327ae72 (HEAD -> master) Add file3
8b159b4 Add file2
cf9f3bf Add file1
...(more)...

And now we don't want the file2 and file3, but we do want to retain file1. We could just revert the 2 recent commits separately, or we will reset back to the commit where we add the file1.

$ git reset cf9f3bf
$ git log
cf9f3bf (HEAD -> master) Add file1
...(more)...

Warning it's generally dangerous to mess around with the commit history of a repo, especially when working in collaboration with others.

There is way to undo reset if we know the git commit hash. In previous example, even if we reset back to file1's commit, we can still go back since we know the last commit's hash where file 3 is added is 327ae72.

$ git reset 327ae72
$ git log
327ae72 (HEAD -> master) Add file3
8b159b4 Add file2
cf9f3bf Add file1
...(more)...
  1. Amend

commit --amend could be used to modify the recent commit and swap change its commit message.

It combines the changes in staging area with the latest commit and create a new commit out of it, replacing the latest commit.

For example, we change the README.md a little bit and want to make a commit, but type in quite a few typos there in the commit messages.

$ git add README.md
$ git commit -m "Upated: RMEADE.md (ugly typos)"
$ git log --oneline
b0dfb07 (HEAD -> master) Upated: RMEADE.md (ugly typos)
327ae72 Add file3
...(more)...

Oh No! The git history looks bad with our typos. No worries! amend comes to help.

$ git commit --amend -m "Update: README.md (beautiful)"
$ git log --oneline
d4bf700 (HEAD -> master) Update: README.md (beautiful)
327ae72 Add file3
...(more)...

We cam see the previous typo-commit is replaced by our newly-amended one.

  1. Stash

There are situations where we have to work on some features urgently, but we don't want to just delete all the changes we are working at hand. git stash command saves the previously written code and then goes back to the last commit for a fresh start.

By default, git stash saves the modifications on tracked files and reverts the working directory to match the HEAD commit. and we can provide a message when creating stash by git stash save "some message".

We can create multiple stashes and view them by git stash list. Each stash is given an identifier as stash@{a number}. and git stash show will display the summary of operations done on a particular stash.

We can get back the previously stashed changed by git stash {pop, apply}. The difference is that git stash pop will remove the changes from stash, while git stash apply retains the stash. Both will reapply the stashed modification into the working copy.

We can create and checkout a new branch starting from the commit where a stash is made by git stash branch new_branch_name previous_stash_name. git stash drop stash_name will delete any particular stash, and git stash clear delete all existing stashes all at once.


Git Remote

So far we have only been working on a repo with git locally. However, when we need to collaborate with other people across the world, we need a way to "communicate" our progress through the network. Also it's safer to always backup the development progress on the remote git repo, in case if our computers break or are lost. (Hopefully not)

We need to first create a new github repo and upload the local one we have been working on so far, to sync.

We name the remote repo also myproject for convenience.

  1. Sync with Remote Repo

In our local project repo, we need to add a remote tracking link:

$ git remote add https://github.com/[username]/myproject.git

and we check if we are remotely sync with the repo on github through git remote -v

$ git remote -v
origin	https://github.com/[username]/myproject.git (fetch)
origin	https://github.com/[username]/myproject.git (push)

And then we could push all our local developments to the remote repo through:

$ git push --set-upstream origin master
 
Enumerating objects: 34, done.
Counting objects: 100% (34/34), done.
Delta compression using up to 8 threads
Compressing objects: 100% (33/33), done.
Writing objects: 100% (34/34), 49.37 KiB | 12.34 MiB/s, done.
Total 34 (delta 17), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (17/17), done.
To https://github.com/[username]/myproject.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

Now we have uploaded all the developments to the remote repo.

  1. Grab New Stuff

Now suppose something new happens on the remote repo. Here we simulate that by remove a line from the README.md directly on the github and commit there.

How could we update the local repo to include the changes?

We have two choices:

  • fetch + merge
  • pull

Let's start with the first combination. We need to first download all the new changes from remote using fetch

$ git fetch origin
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 2), reused 2 (delta 2), pack-reused 0
Unpacking objects: 100% (3/3), 689 bytes | 344.00 KiB/s, done.
From https://github.com/[username]/myproject
   d4bf700..6d4ad42  master     -> origin/master

now we have the newly changes from upstream, we check the git status:

$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

Clearly it shows we see that local repo is 1 commit behind the upstream master, the one line change on README.md. We could double check the exact difference using the git diff command

$ git diff origin/master
diff --git a/README.md b/README.md
index f3fa9a9..cf28200 100644
--- a/README.md
+++ b/README.md
@@ -3,3 +3,5 @@ Hello World repository for Git tutorial
 This is an example repository for the Git tutoial on https://www.w3schools.com
 
 This repository is built step by step in the tutorial.
+
+a new line

This looks align with our expectation. Now we can merge the commit:

$ git merge origin/master
Updating d4bf700..6d4ad42
Fast-forward
 README.md | 2 --
 1 file changed, 2 deletions(-)

$ git status
On branch master
Your branch is up to date with 'origin/master'.

There we go! Now our local git is up to date.

The above approach works, but a bit tedious. We could actually just do this update in one step by pull command, which is the combination of fetch and merge.

Let's add back the new line deleted from README.md on the github remotely and try to pull the changes to local git.

$ git pull origin
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), 696 bytes | 232.00 KiB/s, done.
From https://github.com/[username]/myproject
   6d4ad42..b2bb9ae  master     -> origin/master
Updating 6d4ad42..b2bb9ae
Fast-forward
 README.md | 2 ++
 1 file changed, 2 insertions(+)

and we can see we are updated with the remote master branch:

$ git log --oneline
b2bb9ae (HEAD -> master, origin/master) Add back a new line from github directly
6d4ad42 Remove the new line README.md  GitHub directly
d4bf700 Update: README.md (beautiful)
327ae72 Add file3
8b159b4 Add file2
cf9f3bf Add file1
...(more)...
  1. Push New Stuff

In last section, we learned how to download and keep in sync with remote repo. Now we are going to make our contribution to the remote repo through push command.

Let's just add another line in the README.md and try to update the remote repo.

$ git commit -a -m "Update readme locally and try push"
[master f8986b8] Update readme locally and try push
 1 file changed, 1 insertion(+)

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Git shows that we are 1 commit ahead of the remote master branch. Let's push the change to remote repo.

$ git push origin
  1. Remote Branch Pull

We can also directly create a new branch on the remote repo through the GUI. We create a new branch called secondary from master branch and made some changes on the README.md, committed on github directly.

We can pull the new branch to local git and check it:

$ git pull
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), 697 bytes | 232.00 KiB/s, done.
From https://github.com/[username]/myproject
 * [new branch]      secondary  -> origin/secondary
Already up to date.

We can check the new branch as usual through git branch. But by default it only displays local branches. We need the -a flag for all local and remote branches, or -r flag for only remote branches.

$ git branch // only local branches
* master

$ git branch -a
* master
  remotes/origin/master
  remotes/origin/secondary

$ git branch -r 
  origin/master
  origin/secondary
  1. Push Local Branch to Remote

We can push a local new branch with changes to remote repoo as well. Let's do it

$ git checkout -b local-new-branch

...
(do some changes to README.md)
...
$ git commit -a -m "Update from local-new-branch"
[local-new-branch 40e9ee3] Update: local-new-branch
 1 file changed, 2 insertions(+)
$ git push origin local-new-brancch
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 334 bytes | 334.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
remote: 
remote: Create a pull request for 'local-new-branch' on GitHub by visiting:
remote:      https://github.com/[username]/myproject/pull/new/local-new-branch
remote: 
To https://github.com/[username]/myproject.git
 * [new branch]      local-new-branch -> local-new-branch

Now if we go to the github remote repo, we will see there is a newly pushed branch called local-new-branch

  1. Merge Into Master

From the above, on the github repo page, we want to merge our changes in the local-new-branch into the main master branch. The webpage has the option Compare & Pull Request.

We can go click it and create a pull request from it.

Since this is our own repo, we are the "authority" and can directly click Merge pull request to faciliate the changes merge into master branch.

In real life, however, typically a pull request needs to go through code review and test process, verified by your collaborators. If approved, then will the pull request go through and get merged.

  1. Fork & Clone Others' Repo

Technically speaking, fork is not a git command. But rather it's a functionality provided by github to directly copy another public repo and make it into your own git repo. It's widely used in open-source project, where people fork the original repo and later try to make pull request to merge their changes into "THE repo" from their own forked copy of the repo.

Typically, once we fork the repo, we want to download a local copy to our computer and start working on that. Given the url of the repo [the repo url], we could use the clone command:

$ git clone [the repo url] [the folder path we want to clone into]

clone will preserve all logging and versions of files from upstream.

Typically, we want to name the original repo as upstream and our fork copy as origin.

We can check all the remote tracking status by git remote -v. And we now will rename the origin to upstream:

$ git remote rename origin upstream

And we can add tracking of a remote repo as origin with [The url] by the following command:

$ git remote add origin [The url]

Now we have two remote trackings. If we want to make a contribution to the original repo, we could commit and push our changes to the forked repo we have, and on the github page create a pull request to the original repo.

If the changes look good to code reviewers of the original repo, they will approve the pull request and merge it.

  1. Git Ignore

We don't necessarily want git to track very single file in the local repo. There might be some log files, temporary files or personal files that shouldn't be included in the git workflow.

To solve this, we can create a .gitignore file in our git repo, and files specified in it will be ignored by our git. However, the .gitignore file itself is tracked by git.

Some common usages include:

command Description
blank lines are ignored
# something lines start with # is comment and ignored
name All name files and folders
name/ All folders called name
name.file All name.file in repo
*.file any file with extension .file
!*.file negate any previous ignore on this file