From Git Ui To Cli Git

Sep 27th, 2019 - written by Kimserey with .

As a developer I use Git every day. I used to use GitExtension which is nice but at time it was really annoying, for example popups showing out of viewport, impossible to click, or random lags when fetching commits. I had been using GitExtension for at least 5 years while working on Windows, but today I am switching to a Mac where GitExtension isn’t supported. I started to look at alternatives and found some, like Fork, a nice software but similarly to GitExtension with random lags, very frustrating. This exercise highlighted to me a bigger issue; Had I been using Git command line all along, I wouldn’t have been wasting my time looking for alternative. So today I am going through some Git commands which are useful in day to day operation, and that helped me transition from a UI driven Git user to a CLI driven.

Checkout a new Branch

When I work on a project, I start by creating a branch out of the hosted source control website, like Github, Gitlab or VSO. All of them have a way of creating new branches. I work in a feature branch kind of workflow where a branch is made for a particular ticket or feature. And I work on that branch until I make a pull request to merge it to master branch.

So within that context, I initially create a branch kim/some-feature on the remote.

create branch

We then fetch all changes:

1
2
3
> git fetch -a
From https://github.com/Kimserey/kimserey.github.io
 * [new branch]      kim/some-feature -> origin/kim/some-feature

Which indicates to me that we fetched a new branch origin/kim/some-feature. We can also use branch -avv to see all branches with their associated commit.

1
2
3
4
5
> git branch -avv
* master                          f54edb4 [origin/master] Updated index blog
  remotes/origin/HEAD             -> origin/master
  remotes/origin/kim/some-feature f54edb4 Updated index blog
  remotes/origin/master           f54edb4 Updated index blog

we can see that we are currently on master branch on the same commit as remote HEAD. We can then checkout our newly created branch and track it at the same time, which will create a branch of the same name locally adn set the upstream.

1
2
3
> git checkout --track origin/kim/some-feature
Branch 'kim/some-feature' set up to track remote branch 'kim/some-feature' from 'origin'.
Switched to a new branch 'kim/some-feature'

Another look at the branches will show us that we are on the new branch.

1
2
3
4
5
6
> git branch -avv
* kim/some-feature                f54edb4 [origin/kim/some-feature] Updated index blog
  master                          f54edb4 [origin/master] Updated index blog
  remotes/origin/HEAD             -> origin/master
  remotes/origin/kim/some-feature f54edb4 Updated index blog
  remotes/origin/master           f54edb4 Updated index blog

If we wanted to check, we could also use status which will tell us which branch we are on, and what is the upstream branch set.

1
2
3
4
5
> git status
On branch kim/some-feature
Your branch is up to date with 'origin/kim/some-feature'.

nothing to commit, working tree clean

Lastly log can also be used to see where we are at:

1
2
3
4
5
6
> git log --graph --oneline --all -5
* f54edb4 (HEAD -> kim/some-feature, origin/master, origin/kim/some-feature, origin/HEAD, master) Updated index blog
* f192a94 Added the organized mind
* 391a762 added slight edge
* b435eeb added essentialism
* 4e1c9fd declutter book section

Alternatively if we didn’t have a branch created on the remote server, we could have used:

1
2
3
4
5
6
7
8
9
10
11
12
13
> git checkout -b my-branch
> git add -a
> git commit -m "Some changes"
> git push --set-upstream origin my-branch
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 360 bytes | 360.00 KiB/s, done.
Total 4 (delta 3), reused 1 (delta 1)
To https://github.com/Kimserey/kimserey.github.io
 * [new branch]      my-branch -> my-branch
Branch 'my-branch' set up to track remote branch 'my-branch' from 'my-branch'.

This will create a remote branch origin/my-branch and while setting up as upstream of my-branch.

Work on a Branch

Now that we have a branch, we can start work on that branch. Git has the concepts of working tree, index and HEAD.

  • the working tree is the current state of your local repository, with all your changes,
  • the index is the staging state, where you can make checkpoints with git add and reset them with git reset HEAD, right before committing,
  • the HEAD points to the latest commit on the branch currently checkout.

So for example if I modify a file in, I will see the following:

1
2
3
4
5
6
7
8
9
10
11
> git status
On branch kim/some-feature
Your branch is up to date with 'origin/kim/some-feature'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   index.html

no changes added to commit (use "git add" and/or "git commit -a")

The working tree now differs from the index, and I can stage the file with:

1
> git add index.html

Then it will be in the index,

1
2
3
4
5
6
7
8
> git status
On branch kim/some-feature
Your branch is up to date with 'origin/kim/some-feature'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

The file is now marked to be commit. We can unstage it with reset:

1
2
3
> git reset HEAD index.html
Unstaged changes after reset:
M	index.html

And that will bring it back to non-staged or we can commit it:

1
2
3
> git commit -m "Update index.html"
[kim/some-feature b0488c4] Updated index.html
 1 file changed, 2 insertions(+), 2 deletions(-)
1
2
3
4
5
6
> git log --oneline --graph -a -5
* b0488c4 (HEAD -> kim/some-feature) Updated index.html
* f54edb4 (origin/master, origin/kim/some-feature, origin/HEAD, master) Updated index blog
* f192a94 Added the organized mind
* 391a762 added slight edge
* b435eeb added essentialism

We can see that our local repository HEAD is ahead from the remote repository by one commit. In fact Git will be able to tell us that with status:

1
2
3
4
5
6
> git status
On branch kim/some-feature
Your branch is ahead of 'origin/kim/some-feature' by 1 commit.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

We can then push the changes:

1
> git push

or we can also reset to the origin/HEAD which will reset the local commit back to unstaged:

1
2
3
> git reset origin/HEAD
Unstaged changes after reset:
M	index.html

And we can see that our commit is back to unstaged:

1
2
3
4
5
6
7
8
9
10
11
> git status
On branch kim/some-feature
Your branch is up to date with 'origin/kim/some-feature'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   index.html

no changes added to commit (use "git add" and/or "git commit -a")

And if we want to just discard unstaged changes, we can do so with checkout:

1
2
3
4
5
6
> git checkout -- index.html 
> git status
On branch kim/some-feature
Your branch is up to date with 'origin/kim/some-feature'.

nothing to commit, working tree clean

The content of index.html was replaced by the HEAD, which was in line with the origin/HEAD therefore reverted index.html to the unchanged state.

If the file was untracked, we could have removed it with clean:

1
> git clean -fd

Check the Differences

Apart from looking at the status, we can also look at the differences between different state,

We can check the difference between the working tree and index:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> git diff start.cmd 
diff --git a/start.cmd b/start.cmd
index 696e714..21255d6 100644
--- a/start.cmd
+++ b/start.cmd
@@ -1,5 +1,6 @@
+# Example change
 echo off
 cls
 echo Make sure http-server is installed "npm install http-server -g"
 
-http-server C:\Projects\kimserey.github.io -p 5000
\ No newline at end of file
+http-server C:\Projects\kimserey.github.io -p 5000

We can see that I added # Example change and that the last line was replaced with the same line plus a newline. If I add this to the index, there will no longer be any difference:

1
2
> git add start.cmd 
> git diff start.cmd 

That’s because there are no difference between the working tree and the index. If I make another change, I will see the next difference:

1
2
3
4
5
6
7
8
9
10
11
> git diff start.cmd 
diff --git a/start.cmd b/start.cmd
index 21255d6..3dbc943 100644
--- a/start.cmd
+++ b/start.cmd
@@ -1,4 +1,4 @@
-# Example change
+# Example change - change after adding
 echo off
 cls
 echo Make sure http-server is installed "npm install http-server -g"

We can see that the previous version of the index already have the # Example change line and it detects the further added - change after adding.

We can check the difference between the working tree and local HEAD:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> git diff HEAD start.cmd 
diff --git a/start.cmd b/start.cmd
index 696e714..3dbc943 100644
--- a/start.cmd
+++ b/start.cmd
@@ -1,5 +1,6 @@
+# Example change - change after adding
 echo off
 cls
 echo Make sure http-server is installed "npm install http-server -g"
 
-http-server C:\Projects\kimserey.github.io -p 5000
\ No newline at end of file
+http-server C:\Projects\kimserey.github.io -p 5000

We can see that the total line is there, or we can check the difference between the index and local HEAD:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> git diff --cached
diff --git a/start.cmd b/start.cmd
index 696e714..21255d6 100644
--- a/start.cmd
+++ b/start.cmd
@@ -1,5 +1,6 @@
+# Example change
 echo off
 cls
 echo Make sure http-server is installed "npm install http-server -g"
 
-http-server C:\Projects\kimserey.github.io -p 5000
\ No newline at end of file
+http-server C:\Projects\kimserey.github.io -p 5000

Which is only the addition of # Example change since we haven’t staged the extra words.

Now lastly a useful difference to check is between your current branch and the origin/master or origin/HEAD.

1
2
3
4
> git diff --name-only origin/HEAD HEAD
start.cmd

> git diff origin/HEAD HEAD start.cmd

This will display the difference between your current checkout latest commit branch and the latest origin/master commit, which will be the differences listed when creating a pull request.

Clean up merged Branches

Once the feature is completed and the PR merged, a common practice is to delete the branch. The deletion of the branch usually is done from the source control UI like GitHub where the pull request was originated. Deleting it from their doesn’t reflect on our local repository therefore in order to clean it we can use prune:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
> git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

> git fetch -a
remote: Enumerating objects: 9, done.
remote: Counting objects: 100% (8/8), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 2), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), done.
From https://github.com/Kimserey/kimserey.github.io
   f54edb4..eef6802  master     -> origin/master

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

nothing to commit, working tree clean

> git pull
Updating f54edb4..eef6802
Fast-forward
 expenseking.html | 4 ++--
 index.html       | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

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

nothing to commit, working tree clean

We start by checking out master branch, then fetch everything, then status shows us that our local master is behind by 4 commits so pull the latest.

1
2
3
4
> git remote prune origin
Pruning origin
URL: https://github.com/Kimserey/kimserey.github.io.git
 * [pruned] origin/kim/some-feature

Prune will remove references from deleted branches. But we will still have the local branch:

1
2
3
4
5
> git branch -avv
  kim/some-feature      94be2c6 [origin/kim/some-feature: gone] Update index.html
* master                eef6802 [origin/master] Merge pull request #2 from Kimserey/kim/some-feature
  remotes/origin/HEAD   -> origin/master
  remotes/origin/master eef6802 Merge pull request #2 from Kimserey/kim/some-feature

We can see that kim/some-feature is referencing origin/kin/some-feature which is marked as gone. This means that it has been pruned on the remote therefore we can delete it using -D:

1
2
> git branch -D kim/some-feature
Deleted branch kim/some-feature (was 94be2c6).

And we now have completed the full workflow of creating a feature branch, working on it, checking the differences to make a pull request, and lastly cleaned up our local copy after we merged the pull request!

Conclusion

Today we saw how to work with Git CLI to do simple actions, like working on a branch, checking the differences and cleaning up after a PR is squashed. As mentioned in the introduction, I was a Git UI user for a long time and I am only really starting now to move away from it. There are so many other problems and frustrations in Software development that I personally feel like Git shouldn’t be adding on top of that. Therefore trimming UI, button click, lags and more importantly preventing from getting used to a specific UI is a plus during day to day operation. I would still recommend GitExtension and Fork, for those who are against using CLI, and I will still use them as it is very nice to browse repository, just like how we would browse GitHub repositories. Anyway I hope you liked this post and I see you on the next one!

External Sources

Designed, built and maintained by Kimserey Lam.