Tech Notes

Git Tutorial

A tutorial about git commands and some advanced features which we can use in git to help with day-to-day job. Tutorial starts with basic git commands, followed by git branching, git stashing, details about GitHub, rebasing and tags. Many of the topics we face everyday with respect to git are covered here. It’s not a comprehesive list.

GIT BASICS

Let’s start with setting up git username and email. This can be changed later. Its a global change:

#git config --global user.name "Manoj Kaushal"
#git config --global user.email "blah blah"
Verify:
git config user.name
Manoj Kaushal

Initialise Git: We can initialise a new repository using command “git init” which enables git in the particular project. We can verify using git status:

#git init
Initialized empty Git repository in <directory path>/testgit/.git/

#git status
On branch main
No commits yet
nothing to commit (create/copy files and use "git add" to track)

Initialising git creates .git hidden directory and files inside it.

GIT staging and commit: We should keep git commit atomic which means a commit should include a feature and related data. We first stage the commit using “git add <filename>” or “git add <file1> <file2>” and then do commit using “git commit” command.

do staging and commit in one command: git commit -a -m “message”

Amending Git commit: We can amend the previous commit’s message or stage a forgotten file using:

git commit --amend
example:
manojkumar@EMEA-M-4641 testgit % touch test1.json
manojkumar@EMEA-M-4641 testgit % touch test2.json
manojkumar@EMEA-M-4641 testgit % git status
On branch main

No commits yet

adding test1 and test2 files
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	test1.json
	test2.json

nothing added to commit but untracked files present (use "git add" to track)
manojkumar@EMEA-M-4641 testgit % git add test1.json
manojkumar@EMEA-M-4641 testgit % git commit -m "adding test1 and test2 files"
[main (root-commit) 7647d62] adding test1 and test2 files
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test1.json
manojkumar@EMEA-M-4641 testgit % git status
On branch main
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	test2.json

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

manojk@EMEA-M-4641 testgit % git add test2.json

manojk@EMEA-M-4641 testgit % git commit --amend
[main 161f5b3] adding test1 and test2 files
 Date: Thu Dec 21 10:47:55 2023 +0000
 2 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test1.json
 create mode 100644 test2.json

manojk@EMEA-M-4641 testgit % git status
On branch main
nothing to commit, working tree clean
manojk@EMEA-M-4641 testgit %

git log:
#git log --oneline
fc61965 (HEAD -> main, origin/main) Initial commit
1226a64 Initial commit

git ignore: Any file/directory which is present in “.gitignore” file is ignored by git and is not tracked. visit: gitignore.io and select your language(example: python), to get common files which we should ignore.

GIT BRANCHES

git branches helps us with having separate context and timelines for new feature/bugs etc.

To understand git branches, first we have to understand how git commit works. A git commit is point in time checkpoint along with the a hash. A commit also points to the previous commit.

We can use git branches to work on a bug , a new feature in isolation without impacting the main codebase.

Master Branch: The default branch name for new repository. Many treat master branch as the main codebase which has the master copy of the project. But master branch has nothing special.

GitHub has changed the branch name from master to main in 2020.

HEAD->MASTER : HEAD is simply a pointer that refers to the current “location” in your repository. It points to a particular branch reference. Analogically, think of bookmarks in a book and bookmarks are the branches. Since we can only open one bookmark and start reading from that page, that page is our current HEAD. We can switch between bookmarks/branches and HEAD will switch automatically.

Branch commands:

View all branches:
#git branch
* main 

Create a new branch without switching to the branch based on the current HEAD:
# git branch <branch name>
example: 
testgit % git branch test
testgit % git branch
* main
  test
head will point to main branch:
testgit % git log
commit 161f5b3 (HEAD -> main, test)

To switch branch:
testgit % git switch test
Switched to branch 'test'
testgit % git log
commit 161f5b3 (HEAD -> test, main)

Create branch and switch:
testgit % git switch -c test2
Switched to a new branch 'test2'

Delete Branch:
git branch -d <branch name>

Rename Branch:
git branch -m <branch name>

GIT MERGE

Concepts: We merge branches, not specific commits.We always merge to current HEAD branch.

If we are merging into main branch from another branch and main branch had no changes, we can simply run the command “git merge <name of the branch to be merged into main>” in main branch.

If we are merging into the main branch from another branch and main branch has no conflicting changes, we can simply run the command “git merge <name of the branch to be merged into main>” in main branch. Git will figure out.

If we are merging into the main branch from another branch and main branch has conflicting changes, we will get conflict. we should modify the conflicting file and commit.

GIT DIFF

git diff: git diff lists all the changes in our working directory that are NOT staged for the next commit.

testgit % git diff
testgit % rm test1.json
testgit % git diff
diff --git a/test1.json b/test1.json
deleted file mode 100644
index c521c1c..0000000
--- a/test1.json
+++ /dev/null
@@ -1 +0,0 @@
-dfjksdjkjd

git diff HEAD: lists all the changes in working tree since last commit. It includes staged and unstaged changes.

testgit % rm test1.json
testgit % git add .
testgit % git diff
testgit % git diff HEAD
diff --git a/test1.json b/test1.json
deleted file mode 100644
index c521c1c..0000000
--- a/test1.json
+++ /dev/null
@@ -1 +0,0 @@
-dfjksdjkjd

git diff --staged: list all the changes between staging area and our last commit

testgit % git add test1.json
testgit % git diff
testgit % git diff --staged
diff --git a/test1.json b/test1.json
index c521c1c..199aa58 100644
--- a/test1.json
+++ b/test1.json
@@ -1 +1,3 @@
 dfjksdjkjd
+
+I AM STAGED AFTER LAST COMMIT

git diff branch1.. branch2: will list the changes between the tips of branch1 and branch2.

git diff commit1..commit2: to compare two commits, provide git diff with the commit hashes of the commits in question.

GIT STASH

If we have main and test branch and we are making changes in test branch as well as main branch, which are conflicting, they we can use git stash in the test branch to stash the changes. This is only when we haven’t committed on the test branch.

In below example, I created a stash branch and main branch head has moved ahead due to commits in the teststash file. If I switch to stash branch, it still has the old changes in the teststash file. If I make change in teststash file and try to switch to main: I’ll get a conflict as teststash file doesn’t have the latest change in the main:

testgit % vim teststash

testgit % git add .

testgit % git commit -m "commit to teststash"
[main (root-commit) 76dfef9] commit to teststash
 1 file changed, 1 insertion(+)
 create mode 100644 teststash

testgit % git switch -c stash
Switched to a new branch 'stash'

testgit % git switch main
Switched to branch 'main'

testgit % vim teststash

testgit % git commit -am "commit to teststash after creating stash branch"
[main 605929c] commit to teststash after creating stash branch
 1 file changed, 1 insertion(+), 1 deletion(-)

testgit % git switch stash
Switched to branch 'stash'

testgit % vim teststash

testgit % git diff
diff --git a/teststash b/teststash
index 7ed9f26..04f8b06 100644
--- a/teststash
+++ b/teststash
@@ -1 +1 @@
-original change in main branch
+original change in main branch. this change is in stash branch

testgit % git switch main
error: Your local changes to the following files would be overwritten by checkout:
	teststash
Please commit your changes or stash them before you switch branches.
Aborting

git stash: Git provides an easy way of stashing above uncommitted changes so that we can return to them later, without having to make unnecessary commits. Running git stash will take all uncommitted changes(staged and unstaged) and stash them, reverting the changes in your working directory.

 testgit % git switch main
error: Your local changes to the following files would be overwritten by checkout:
	teststash
Please commit your changes or stash them before you switch branches.
Aborting

testgit % git stash
Saved working directory and index state WIP on stash: 76dfef9 commit to teststash

git switch main
Switched to branch 'main'

git stash pop: to remove the most recently stashed changes in your stash and re-apply them to your working directory.

testgit % git switch stash
warning: refname 'stash' is ambiguous.
Switched to branch 'stash'

testgit % git diff

testgit % git stash pop
On branch stash
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   teststash

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (e61d93fc5733c684eb97f904e7309fd944509f97)

testgit % git diff
diff --git a/teststash b/teststash
index 7ed9f26..04f8b06 100644
--- a/teststash
+++ b/teststash
@@ -1 +1 @@
-original change in main branch
+original change in main branch. this change is in stash branch

GIT: Working with Remote repositories

GIT REMOTE: Git remote tells you about the remote repository on which we are working on and we can fetch and push changes to this remote repository. The “origin” is the label/name for the remote git repository. REMOTE is just a remote repository url.

% git remote -v
origin	https://github.com/manojkaushal/IntentBasedCloudNetworking.git (fetch)
origin	https://github.com/manojkaushal/IntentBasedCloudNetworking.git (push)

GIT REMOTE ADD: If we have a local repository which doesn't have a remote, we can use:
git remove add <name> <>url

usually <name> will be origin. Which we are telling git: Okay Git, anytime I use the name "origin", I'm referring to this particular GitHub repo url.

GIT PUSH: If we want to push changes to GitHub, we will use:
git push <remote> <branch>
remote: is the remote name, which is usually origin.
branch: the local branch we want to push.
eg:
git push origin main
or
git push origin purple

PLEASE NOTE that since GitHub doesn't have the "purple" branch above, GitHub will create a remote purple branch first and then do the push

GIT PUSH <remote> <local-branch>:<remote-branch>: We can push to a different remote branch using this command.

GIT PUSH -U: The -u option allows us to set the upstream of the branch we're pushing. You can think of this as a link connecting our local branch to a branch in GitHub. 
Running "git push -u origin main" sets the upstream of the local main branch so that it tracks the main branch on the origin repo.
example: 
git push -u origin main

% git push
fatal: The current branch initial_commit has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin initial_commit

To have this happen automatically for branches without a tracking
upstream, see 'push.autoSetupRemote' in 'git help config'.

% git push -u origin  initial_commit
% git push
Everything up-to-date

GIT: FETCHING AND PULLING

Remote tracking branches: Whenever we clone from GitHub on our local machine, we get local branch and remote tracking branch. so we will have “main” and “origin/main”.

Why remote tracking branches?: Think of remote tracking branches for GitHub to know the latest information it has on the remote branch. If we make changes in the local “main” branch and commit them, we are diverging from the remote “origin/main”.

We can use the command “git branch -r” to check the remote tracking branches.

Please note that only default main branch is tracked and shows locally.

If you would like to work on a remote branch, say “origin/purple”, then simply use “git switch purple” and git will automatically start tracking and do mapping between “purple” local branch and “origin/purple” remote branch.

GIT: UNDOING CHANGES

Key concept: The HEAD points to a branch and not to a commit. The head points to a branch and branch points to the tip of the commit.

git checkout <previous commit>: We can revert back to previous commit using the checkout command.

current commits:
 % cat .git/HEAD
ref: refs/heads/CENG-1842

% git log --oneline
830e911 (HEAD -> CENG-1842, origin/CENG-1842) refactoring code
860ddc8 (origin/CENG-947) additional fixes
cac59a9 changing RSS to isolated in dev
1ce48d7 support for adding routes
e242c54 support for class A RFC1918 IP range

% git checkout e242c54
Note: switching to 'e242c54'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at e242c54 support for class A RFC1918 IP range

% git log --oneline
e242c54 (HEAD) support for class A RFC1918 IP range

 % cat .git/HEAD
e242c54ab778cd70c522f7f0aeaa51dcb33f5001

If you see above, we have detached head.The reason for this is that HEAD is pointing to a commit instead of the branch(see last command above).

We have 2 options here:

  1. Do a sneak peek on what was changes in the commit and go back to the tip of the branch by switching to the branch. It will re-attach the HEAD to the tip of the branch. We can switch to the branch using “git switch -” or “git switch <nameofthebranch>”
  2. We can create a separate branch and start working on it.
Option 1 Example:

% git status
HEAD detached at e242c54
nothing to commit, working tree clean

% git branch
* (HEAD detached at e242c54)
  CENG-1842
  main

% git switch CENG-1842
Previous HEAD position was e242c54 support for class A RFC1918 IP range
Switched to branch 'CENG-1842'


% git status
On branch CENG-1842
nothing to commit, working tree clean

% git log --oneline
830e911 (HEAD -> CENG-1842) refactoring code
860ddc8 additional fixes
cac59a9 changing RSS to isolated in dev
1ce48d7 support for adding routes
e242c54 support for class A RFC1918 IP range


Option 2 Example:
% git status
On branch CENG-1842
nothing to commit, working tree clean

 % git checkout df32f26
Note: switching to 'df32f26'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

 % git switch -c branchtest
Switched to a new branch 'branchtest'

 % git status
On branch branchtest
nothing to commit, working tree clean

git checkout HEAD <file> OR git checkout — <file>: Suppose you’ve made some changes to a file but don’t want to keep them. To revert the file back to whatever it looked like when you last committed, we can use “git checkout HEAD <file>” command. It will discard any changes in the file, reverting back to the HEAD.

git restore <file>: same as above. git restore has taken some burden off from the all in one git checkout command.

git restore –staged <file>: If you have accidentally added a file to your staging area with “git add” and you don’t wish to include it in the next commit, you can use “git restore –staged <file>”, to remove it from staging area and back to your workspace.

git reset <commit-hash>: Suppose you’ve made couple of commits on the master branch, but you actually meant to make them on separate branch instead. To undo those commits, you can use “git reset”.

git reset <commit-hash> will reset the repo back to the specific commit. The commits are gone. The changes will still be in the working directory as unstaged.

git reset –hard <commit-hash>: If you want to undo both the commits AND the actual changes in your files, you can use the –hard option.

git revert <commit-hash>: git revert is is similar to git reset in that they both “undo” changes, but they accomplish it in different ways. “git reset” actually moves the branch pointer backwards, eliminating commits. On the other hand, “git revert” creates a branch new commit which reverses/undos the change from a commit. Because it results in a new commit, you will be prompted to enter a commit message.

Use git revert if you are sharing the code and others has your commit history.


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *