Git on a daily basis

Initialize repository

cd git-daily-basis
touch file.txt
git init

Create a remote repository on github call git-daily-basis and get the url of the newly created repo. Now we can connect our local repository with the remote repository:

git remote add origin git@github.com:ledahu05/git-daily-basis.git

To check the link

git remote -v
origin	git@github.com:ledahu05/git-daily-basis.git (fetch)
origin	git@github.com:ledahu05/git-daily-basis.git (push)

Clone a repository

cd ~/working
git clone git@github.com:ledahu05/git-daily-basis.git
ls
git-daily-basis

Make a change, commit and push

echo 'console.log("test")' >> file.txt
git add -A file.txt
git commit -m 'add debug statement'
git push

Get data from remote

git pull

This command is actually a shortcut of two commands:

git fetch
git merge

Branch and checkout

Let's create a new branch called new-feature:

git branch new-feature

To see all our branches:

git branch

We are still currently on the master branch, therefore all commit will be store on the master branch. To switch to the new-feature branch:

git checkout new-feature
touch new-feature.txt
git add -A
git commit -m "Add new feature"

If we checkout the master branch by running git checkout master, there will be no new-feature.txt:

git checkout master
ls //no new-feature.txt

To create a branch a check it out directly :

git checkout -b new-feature-2

To switch to the branch we checked out last:

git checkout -
//we are on master
git checkout -
//we are on new-feature-2

Syncing branches

//we are on new-feature-2
echo 'console.log("test from new-feature-2")' >> file.txt
touch file2.txt
echo 'console.log("this is feature 2")' >> file2.txt

At this point on the branch new-feature-2 there is a modified file and an untracked file.

git add -A
git commit -m "add feature 2"

Now we want to merge this branch to the master branch:

git checkout master
git merge new-feature-2

Git will figure out what algorithm it will use to merge the branch. The decision is taken in regards on the type of difference between the branches. If the differences are linear enough, git will use the fast forward algorithm.

Now we can push the master branch and remove the new-feature-2 branch:

git push
git branch -d url-slugs

Resolving merge conflicts

This can happen when running a git pull if 1/ there are changes on a file both locally and and remotely and 2/ these modifications can't be merged automatically.

Let's make a conflict. In ~/working/git-daily-basis:

git pull

Edit file.txt and change echo console.log("test") by echo console.log("test conflict"). Commit and push the change:

git commit -am 'introducing conflict' && git push

Now in ~/git-daily-basis, without pulling the remote repo edit file.txt and change echo console.log("test") by echo console.log("test more conflicts"). Commit the changes and try to push the changes to the remote:

git commit -am "introducing more conflicts" && git push

We get an error from the push command because remote contains changes we don't have locally. To be able to push our changes we first need to pull the changes from the remote. For that we ran

git pull

We get an error from the pull command because git is unable to merge automatically the changes from the remote and the local file. We must fix the conflict and commit the result.

git status //will tell us what files are in conflicts, aka file.txt

We need to open the file file.txt:

<<<<<<< HEAD
echo console.log("test more conflicts")
=======
console.log('test conflicts')
>>>>>>> 4c897c840eac7251e78f4dfa03f3a8ac7d036db2

The code between <<<<<<<HEAD and ======== are the local change and the code between ======= and >>>>>>>> are the remote changes. 4c897c840eac7251e78f4dfa03f3a8ac7d036db2 is a commit id. Now we need to decide what code should stay and what code should be removed. We must remove all markers:

echo console.log("test more conflicts, resolved")

Now we can stagged and commit as normal:

git add -A
git commit -m "test more conflicts"
git push

Branch and stashing

The need may occur for stashing changes in the following use case for example. I'm working a new branch because I've been asked to develop a new feature:

git branch -b new-feature-4
touch file4.txt
echo "console.log('feature4'" >> file.4.txt

At this point, I got a call to tell me there is a critical bug on production. To fix the bug we need to create a new branch. The idea is that I want to create a new branch without having to commit the new changes i've just made (because the code still contain errors for example but there is emergency). The solution here is to stash the changes

git add -A
git stash

The changes are saved but the head is back to the last commit:

ls //no file4.txt

Now I can create safely a new branch to fix the critical bug;

git checkout -b hotfix
//fix bug
git add -A
git commit -m "Fix bug"
git checkout master
git merge hotfix

To resume the work on the new feature:

git checkout new-feature-4
git stash apply
ls //file4.txt is there

The git pager

Git command output are sometimes so big that result are displayed using the pager. By default the less unix command is used for the pager. Here is reminder of some useful command:

git log //output displayed using the pager
q to quit the page
/a_string_to_search
n to go the next search result
N to go the previous search result

Using the git history

The git log command will output all the commit starting from the most recent.

git log

commit 45cff52b9377fb510f1d53e6d12a8739f1ac21ff (HEAD -> master, origin/master)
Merge: bef03c6 4c897c8
Author: ledahu <christophe.seguinot@gmail.com>
Date:   Tue Mar 17 17:47:12 2020 +0100
....
....
....

Format the history

Condense the history on a single line with --online

git log --oneline
Display a graph of the branch structure with:

git log --graph

Display the changes made in each commit with:

git log -p

Display number of insertion and deletion using:

get log --stat

Filter the history

git log -3 

will show only the 3 most recents commit

git log --after="yesterday" 

will show only the commit that happened between now and yesterday. We could have used "30 minutes ago" or "last tuesday" or "last week" or "2 weeks ago" or "3-15-16" or "3/15/16"

git log --after="yesterday" --before="30 minutes ago" 

will show only the commit that happened from yesterday to 30 minutes ago.

git log --author="ledahu05" 

will only the commit message from the specified author. We can use regular expression here. git log --author="ledahu05\|cseguino". This command perform a search in the author field of each commit.

git log --grep="hotfix" 

will show all commit whose message contains "hotfix"

We can look for a string in the code changes with -S option:

git log -p -S"console"

We can also use regular expression for searching the code changes with -G option:

git log -p -Gconsole\|log

To ignore the case in all our seach just use the -i option

git log --no-merges 

will show only commits that are not merges

To show commits that are contained within a range of references: git log master..new-feature will show the commit between the ref master and the ref new-feature. After a merge this command will have no output.

git log file.txt will show only commits related to the specified filename

Composing formating and filtering options

git log -3 -i --author="ledahu05" file.txt 

will show the last three commit from ledahu05 (or LEDAHU05...) where file.txt was changed

git log -S"console" --after="yesterday" --oneline --stat

will show all the commit that included the word console in the changes from yesterday to now, and show the result in the condensed form with the number of additions and deletions.

Comparing file changes using git

git diff

git diff will show the differences between two references. By default it will use the last commit and the current working directory.

git diff --cached

it will show the differences between the working directory and the staging area

git diff HEAD

it will show all the uncommited changes. It is comparing both the working directory and the staging area with the last commit

Grab the lastest remote branches

git fetch

And the run

git diff origin/master 

It will show all the changes that we have locally that have not been merged to our remote master branch.

git diff file.txt

it will narrow the command to the specified file.

Semantic versionning with git tag

git tag v1.0.0

It will create a tag with the label v1.0.0

git tag

It will output all the tags of our repo.

The purpose of a tag is to create a reference to a commit that can be changed.

To push a tag to the remote use

git push --tags

To annotate a tag use

git tag -a v1.0.1 -m 'patch 1'

Fixing errors

Fixing commit message

It happens to make a typo in a commit message. If the commit has not yet been push we can do as follow to fix it:

git commit --amend -m "message without typo"

Add files to an existing commit

First add the files to the staging area:

git add -A

Then commit using the --amend option:

git commit --amend -m "adding new files"

Remove a file from the staging area

After adding a file to the staging area and before commit the changes we realize there is a file we don't want to commit. This is how we can proceed:

git reset HEAD filename

Now the file is removed from the staging area. Now we can remove the file safely.

Remove changes from a commit before pushing

To compare what we are going to push with what is on the remote we can perform a diff:

git diff origin/master HEAD

In the output we can figure out some changes we already commited that we don't want to push. We need to find the commit id where we we commited the file. For that purpose we run:

git log --oneline

Once we found the commit id, we can copy the previous commit id where we want to go back and run:

git reset 85db789

Here we used git reset, and we are doing that only because changes have not yet been pushed. Don't use git reset on something you already push since it will change the history.

Now we need to run a git status and add/commit only what we really want to commit/push

Comparing git reset options

To better understand the options, let's modify a file and commit the changes

git add file
git commit -m "understanding git reset options"

Let's explore the first option --soft:

git reset --soft HEAD ~1
git status //there is changes to be commited

We are moving the changes from the commited status to the changing area.

git log --oneline //the commit is gone

Let's commit the changes again:

git commit -m "understanding git reset options part 2"

Now let's explore the --mixed option, which is the default option is no option are specified:

git reset --mixed HEAD~1
git status //there is unstaged changes

So the mixed options takes a step further, going from the commit state to the unstage state. Nevertheless, the changes are still in the working directory.

Let's add and commit the file again and try the --hard option:

git add file
git commit -m "understanding git option part 3"
git reset --hard HEAD~1
git status //nothing, clean. The changes are gone.

The commit is removed, the changes are unstaged and removed from the working directory.

What if we made a reset --hard and realized we lost the changes? there is still a hope:

git reflog

Pick the commit id where you want to go back, in our example the one with comment "understanding git option part 3" and run:

git reset --hard commit_id

Undo a commit that has already been pushed

git revert commit_id

The history is maintained here, the command will add commit.

How to push a new branch that does not exist remotely

Let's create a new branch:

git checkout -b new-local-branch

To see detailed information about our branches use the -vv flag

git branch -vv
  master           45cff52 [origin/master] resolving conflicts
* new-local-branch 45cff52 resolving conflicts

We can see that new-local-branch is just a local branch for now. Let's add some code and try to push it:

touch 'file.js'
echo 'console.log("local branch")' >> file.js
git add file.js && git commit -m "add some code"
git push

But the push command fail and git tells us new-local-branch does not exist remotely. To fix this:

git push -u origin new-local-branch
git branch -vv
  master           45cff52 [origin/master] resolving conflicts
* new-local-branch a6b9362 [origin/new-local-branch] some change

Copy a commit from one branch to another

You want a piece of code from a branch without merging the whole branch.

On the branch containing the piece of code you want, do git log --online and copy the commit_id

Now switch to the branch you want to add the code to and run:

git cherry-pick commit_id

Move a commit that was committed on the wrong branch

Before pushing we realize we commited the code on the master branch instead of the hotfix branch. Here this a cherry-pick scenario on hotfix branch and a git reset scenario on the master branch.

On the master branch get the commit_id that commited the code. Now switch to the hotfix branch and run the cherry-pick command to copy the code:

git checkout -b hotfix
git cherry-pick commit_id
git push

Ok now the hotfix branch is clean. Now we need to clean the master branch. On the master, get the commit_id we want to target:

git checkout -b master
git log --oneline //get the commit_id before the one we used before
git reset commit_id

This is a mixed reset to the changes are still in the working area. We can use git checkout filename to revert file.

git status //will give the file we must checkout
git checkout file
git status //clean.

Save local change before pulling

You are working locally on a file and we realize we didn't pull before starting to code. So we run git pull and it fails because some changes have already been on the same file and these changes has already been pushed. It is a good scenario for stashing our changes.

git stash
git pull

Now we need to get our code back.

git stash pop

Eventually we run here in a merge conflict. We edit the file in conflict and make the appropriate changes. Then we add and commit the changes. Finally we can push the changes.

To check the stach has been correctly cleaned:

git stach list //get the stach_id if any
git stach drop stach_id

Starting from an old comit

git checkout commit_id

Here we are in a detached mode (HEAD is not pointing to a branch) which is not good if we want to commit new changes from there. To fix this:

git checkout -b exploring-old-commit

Delete a branch after a pull request

The branch delete_branch_after_pull_request has been deleted on github, how to update our local repo ?

git remote prune origin --dry-run
git remote prune origin
git branch -d delete_branch_after_pull_request

Git rebase

Change the commit message of a previous commit with interactive rebase

First of all, we don't want to change a commit message that has already been pushed because it might have been pulled by someone else. Let's make some changes:

touch file.js
echo 'change1' >> file.js && git add file.js && git commit -m "change1"
echo 'change2' >> file.js && git add file.js && git commit -m "change2"
echo 'change3' >> file.js && git add file.js && git commit -m "change3"
git log --oneline //all 3 commits are above origin/master, we can safely change them
git rebase -i HEAD~3 
pick xxxxxx change1
pick xxxxxx change2
pick xxxxxx change3
//options are: p (pick), r (reword), e (edit), s (squash), f (fixup)

Our goal here is to change the commit message change1, so we will mark it with reword:

reword xxxxxx change1
pick xxxxxx change2
pick xxxxxx change3

After an other text editor open to allow us to edit the commit message "change1", we can set it to "Change 1"

Add a file to a previous commit with rebase

We want to add a file to the "change2" commit.

git rebase -i HEAD~2
pick xxxxxx change2
pick xxxxxx change3
//Edit to:
edit xxxxxx change2
pick xxxxxx change3

We are back to the command line but we are in interactive rebase session.

touch interactive-rebase.js && git add interactive-rebase.js
git commit --amend --no-edit

We can finish the rebase interactive session:

git rebase --continue

Merge conflicts while changing commits during a rebase session

git rebase -i HEAD~2
pick xxxxxx change2
pick xxxxxx change3
//Edit to:
edit xxxxxx change2
pick xxxxxx change3

We are back to the command line but we are in interactive rebase session. Let's make a change to an existing file that will create a merge-conflit.

//edit and save existing-file-with-conflictual-changes.js
git commit --amend --no-edit
git rebase --continue
//MERGE CONFLICT

We are going into a merge conflict during a rebase. As usual edit and clean the file in conflict:

//edit and clean existing-file-with-conflictual-changes.js
git add existing-file-with-conflictual-changes.js
git commit -m "merge rebase conflicts"
git rebase --continue

Squash commits before they are pushed with rebase

Now is time to push, but we don't want to push change1, change2 and change3 as 3 differents commits but instead as a single commit. This is a scenario for rebase & squash.

git rebase -i HEAD~3 
pick xxxxxx change1
pick xxxxxx change2
pick xxxxxx change3
//Edit to
pick xxxxxx change1
squash xxxxxx change2
squash xxxxxx change3
//after save & exit, we can edit commit message
//after save & exit
git push

Ignore a file that has already been commited and pushed

touch .ignore //add the secret.js to .gitignore, add, commit and push

But secret.js file was pushed and adding secret.js to .gitignore is not enough here, it's too late. To fix this scenario we must first remove the file from the cache first:

git rm -r --cached . //it will remove all our changes
git add -A //add all our changes again
git status //will inform our secret.js is deleted
git commit -m "secret.js removed" && git push