1. Working Directory, Index and Commits
On Mac: http://git-scm.com/download/mac
On Linux: http://git-scm.com/download/linux
On Windows: http://git-scm.com/download/win
During installation on Windows:
- Select your favorite text editor as default
- leave Let Git decide option selected for the initial branch name
- leave other default options intact
Open Git Bash and verify:
git version
On Windows in addition to git please also install WinMerge (free software).
Present yourself to git
git config --global user.name my_username
git config --global user.email my@email.com
Choose your default editor (for example nano or notepad++)
git config --global core.editor nano
git config user.name
git config user.email
git config core.editor
Create an account on our Cisco Gitlab (VPN required) if you don't have it yet:
- Create a new local folder called 'git_workshop'
mkdir git_workshop
- Initialize the git repository in the new folder
cd git_workshop
git init
- Display all files, including hidden ones. Notice there's .git folder created which includes all git objects, trees and refs. Display all files in .git folder:
ls -la
ls -l .git
- Create a new file "test_file.txt" with a single line of text: "This is line 1"
- The new file is now in the Working Directory but it is not tracked by git. "git status" helps determine which files are tracked by git:
git status
- Add the file to the Index (Staging Area):
git add test_file.txt
- Issue "git status" to verify. From now on git will track changes to this file.
git status
- Edit the file by adding a new line of text: "This is line 2".
- Issue "git status" and notice the file is now listed twice, in fact git recognized there's two versions of the same file - the new file staged for commit (in the Index) and the modified (dirty) version of the same file which we're still working on (it's in Working Directory)
git status
- Issue "git diff" to compare the current version of the file in Working Directory with the version that exists in the Index
git diff
- Create a new file "test_file2.txt" with some text in it. Don't add this file to Index yet.
- Issue "git status" and notice that "test_file2.txt" is listed as untracked - changes to this file are not tracked by git
git status
- Commit changes to the main (default) branch
git commit -m "test_file added"
- Issue 'git status' and notice that modified version of test_file.txt and untracked file test_file2.txt are still displayed in Working Directory (not staged for commits)
git status
- Issue "git log" to see the existing commits in the main branch. The HEAD points to the latest commit.
git log
- Add both the modified test_file.txt and a new test_file2.txt to the Index
git add .
- Issue "git" status and notice both files are staged for commit and will be added to the local repo upon the next commit.
git status
- Issue "git diff" and notice there's no output produced... why?
git diff
- Issue
git diff --cached
and observe the difference
git diff --cached
- You realized that test_file2.txt is added by mistake and shouldn't be committed. Let's unstage it using
git reset [file]
command.
git reset test_file2.txt
Confirm the file is no longer listed in Index
git status
- Commit changes with below commit message:
git commit -am "added line2 in test_file"
- Issue 'git log' and notice that HEAD now points to the new commit
git log
- Let's see what is the diff between the last two commits. Issue
git diff <sha_nr1> <sha_nr2>
where each parameter includes the first 6 digits of the hash of the last 2 commits.
git diff <sha_nr1> <sha_nr2>
- Change the order of hashes:
"git diff <sha_nr2> <sha_nr1>"
What changed? Why?
- Instead of referring to SHA IDs let's refer to HEAD and its parent commit (HEAD^):
git diff HEAD^ HEAD
Notice that we're still comparing the same two commits as before.
- clean up the working directory
git clean -f
- create .gitignore file and list /static folder there:
echo "/static" >> .gitignore
cat .gitignore
- commit changes
git add .gitignore
git commit -m "added .gitignore with /static"
git status
- create 2 new folders named 'static' and static2, add a new file into each folder
mkdir static static2
echo "some text" >> static/newfile.txt
echo "some text" >> static2/newfile.txt
ls -l static static2
-
display
git status
and notice only static2 is listed as a folder that can be added to Index and then committed. -
Clean up by removing the recently added folders from working directory
rm -rf static static2
- Clone the test repository into your local folder:
git clone https://gitlab-sjc.cisco.com/mstanczy/git_workshop
Verify the below file and folder are present in your local folder:
drwxr-xr-x@ 9 mstanczy1 staff 288 May 25 18:17 files
-rw-r--r-- 1 mstanczy1 staff 4626 May 26 14:49 jumbotron.html
- Configure difftool:
On Mac/Linux:
git config diff.tool opendiff
git config --global difftool.prompt false
On Windows:
git config diff.tool winmerge
git config --global difftool.prompt false
- add a tag to the most recent commit:
git tag last_good_commit
Check if the tag got attached to the commit
git log
- Open jumbotron.html in your favorite text editor. Open the same file also in your favorite browser.
- Update line 80 of the code - replace 'btn-default' with 'btn-primary'.
- Save the file and refresh the page in the browser. Notice the color of the button in Section 1 is different.
- display diffs in a slightly different format
git diff --word-diff
Changes between the commited and staged versions of the file are presented.
- commit the changes
git commit -am "changed button in section 1"
Confirm the new commit was added
git log
- Update line 85 - replace 'btn-default' with 'btn-info'
- Save the file and refresh the page in the browser. Notice the color of the button in Section 2 is different.
- display diffs using preconfigured difftool
git difftool
Close the FileMerge / WinMerge window to get back the prompt in the Terminal.
- commit the changes
git commit -am "changed button in section 2"
Confirm the new commit was added
git log
-
Update line 90 - replace 'btn-default' with 'btn-warning'
-
Save the file and refresh the page in the browser. Notice the color of the button in Section 3 is different.
-
commit the changes and confirm the new commit was added
git commit -am "changed button in section 3"
git log
- Display diffs using "gitk"
gitk
Close the gitk window to get back the prompt in the Terminal.
- You realized that you were committing to the main branch instead of creating a separate branch. Rewind HEAD to the "last_good_commit"
git reset last_good_commit
- display 'git log' and notice the last 3 commits are no longer in the history
git log
- display
git status
and notice that changes to 'jumbotron.html' are now in the Working Directory
git status
- display
git diff
and verify all 3 changes are included
git diff
- Create a new branch and switch to it automatically.
git checkout -b buttons
Display 'git log' and notice that both branches are pointing to the same commit.
git log
Issue git status
to confirm our current active branch is "buttons"
git status
- Display diffs and check which changes are currently in the working directory
git diff
If we stage changes in a standard way they will be all added into a single commit. Let's say we want to split those changes into 3 commits, the same way it was done before. For this purpose we can selectively stage changes per hunk. This operation is called staging patches.
git add -p
We want to split the hunk into smaller pieces hence the first option will be "split"
s
Then we accept the new proposed hunk for staging (for Section 1)
y
We then decline the next 2 proposed hunks :
n
n
- display diffs between the last commit and the Index:
git diff --cached
- We should see only changes to Section 1 button (changed to btn-primary)
- commit changes to the button in Section 1
git commit -m "changed button in section 1"
- display the list of commits - our HEAD is now pointing to the most recent commit in "buttons" branch; the master branch is still pointing to the 'last_good_commit'
git log
- continue with selective staging per hunk, this time we want to stage changes for Section 2 button
git add -p
s
y
n
- commit changes to the button in Section 2:
git commit -m "changed button in section 2"
- Stage and commit the remaining changes
git commit -am "changed button in section 3"
- display the history of commits. The 'buttons' branch is now 3 commits ahead of 'master'.
git log
- Display the history of commits in a more user friendly way
git log --oneline --graph --all
- Configure a new alias for a simple reference to this view
git config --global alias.history "log --oneline --graph --all"
From now on you can use the new alias instead the long command:
git history
- Let's say we want to view the version of the code after one of the previous commits. Let's try to go back to the commit "changed button in section 1" - this is two commits before HEAD
git checkout HEAD~2
This way of referencing commits allows us to navigate back in the history of commits.
Notice that git warned you that you're moving into "detached HEAD" state.
- Issue "git log" and "git status".
git log
git status
Notice that HEAD points to the commit you referred to but the subsequent commits and the pointer to the "buttons" branch are not displayed anymore. That's why it's called detached HEAD state. The changes you might apply at this stage will not go into "buttons" branch.
Issue "git history" to view all branches, notice that "buttons" branch still points to the most recent commit:
git history
-
Refresh the browser tab - notice that buttons for Section 2 and Section 3 went back to the original color.
-
Let's try make some experimental changes. Edit line 97 and replace "2016" with "2021".
-
Commit changes with the below message:
git commit -am "updated footer to 2021"
- display the list of commits and notice that HEAD points to the new commit but still the "buttons" branch isn't displayed.
git log
- Go back to the original HEAD ("buttons" branch in our case):
git checkout -
or
git checkout buttons
Before completing the operations git emits a warning about the extra commit that is not connected to any of the branches. We can still create a new branch for that single commit, for easier reference in the future, without switching to that branch. If we don't do it, this commit will remain in git database but no branch will be pointing to it. Let's create a new branch called "footer" and point it to the extra commit we just created:
Git branch footer <sha>
where sha refers to the SHA ID for the commit called "updated footer to 2021"
- Notice that our active branch is now "buttons". Visualize the status of all branches:
git history
or
gitk --all
- Refresh the browser tab and confirm all buttons have updated colors.
- Let's see how git helps restore deleted files. Remove bootstrap.css file from the /files folder and confirm the file is no longer there:
rm files/bootstrap.css
ls files/bootstrap.css
- Refresh the browser tab and notice the page formatting is now broken.
- Issue
git status
and notice that git detected changes in the tracked files.
git status
- Let's commit those changes:
git commit -am "deleted bootstrap.css"
- Let's add a dummy commit just to move HEAD forward, to simulate a scenario where some other changes have been committed to the branch as well:
git commit --allow-empty –m "dummy commit"
git history
- Our webpage is broken and we want to investigate what changes were made to the bootstrap.css file in the past. Let's display the git log for that specific file only:
git log -- files/bootstrap.css
- Now we know which commit is responsible for this change. Let's call it "bad commit". Git allows to go back and see what our page looked like before that bad commit. We can simply checkout the specific commit - this will put us in "detached HEAD" state. How do we know which commit we should refer to? We can display "git log" and find the SHA ID of the commit added before the "bad commit" OR we can use a trick and refer to the "parent commit of the bad commit":
git checkout <bad commit>^
- Refresh the browser tab. The page should look OK. At this point your local version of the project includes the deleted file. You can start a new branch from here if that fits your needs but we decide to go back to the active branch and later on simply revert the bad commit.
git checkout -
- Let's restore the deleted file. This is possible thanks to the way git tracks all changes in its database. We can use
git revert
command which effectively creates a new commit with reverted changes (you will be prompted to provide the commit message)
git revert <bad commit>
- Check the history of commits and refresh the browser tab to confirm the formatting is now correct.
git history
Let's merge some of our changes to the main branch.
- switch to the target (master) branch:
git checkout master
Refresh the browser page, the buttons should go back to their original state.
- in jumbotron.html update the line 56. Change "Sign in!" into "Login".
- Commit changes
git commit -am "Login text"
- View the status of branches. Notice that there's one new commit on the main branch.
git history
- merge "buttons" branch into master:
git merge buttons
Since the target branch has it's own commit added after the "buttons" branch was created git will apply a merge commit and you will be prompted to provide the commit message (git uses the tool you configured as core.editor) If "master" branch didn't have it's own commit then git would have applied the fast forward merge (with no merge commit required).
- display the updated graph of commits
git history
- Git automatically keeps the state of the repository from before each risky operation (e.g. merge) in the commit tagged as ORIG_HEAD. By default, the .orig file is created with the original conflicts (as a backup). Where is this information stored? As everyting else in git it's in one of the files that constitute git database. In this case the SHA1 ID of the commit that corresponds to ORIG_HEAD is stored in .git/ORIG_HEAD text file:
ls -l .git
cat .git/ORIG_HEAD
Let's see if we're able to revert the merge commit at this point.
"git reset --hard [commit]" allows to rewind HEAD and Working Directory to a specific commit. In our case, we'll refer to the ORIG_HEAD commit, which is our safeguard.
git reset --hard ORIG_HEAD
- display the list of commits and notice that our recent merge was reverted back and we're again in the state where "buttons" and "master" branches are separated.
git history
- Let's now try a different operation - rebase.
git rebase buttons
- display
git history
and notice that commits from buttons branch were rewinded and then re-applied on the master branch instead of creating a merge commit.
git history
- Let's try create some conflict. Update line 97 and change '2016' into '2020'. Save the updated file and commit changes into "master"
git commit -am "updated footer to 2020"
- Now, let's try merge the 'footer' branch into 'master'
git merge footer
Notice git is reporting a conflict that couldn't be resolved automatically and will have to be fixed manually.
- issue "git status" to see which file has a conflict.
An ongoing merge that results in conflicts can be cancelled with "git merge --abort". In our case we will proceed with resolving the conflict instead of aborting the merge.
- issue "git diff" to see which lines are conflicting
git diff
As expected, when trying to merge 'master' and 'footer' branches we hit a scenario where the same line differs between two branches and git cannot decide which version is correct.
HEAD commit (on master branch) includes '2020' whereas the most recent commit on footer branch includes '2021'.
<<<<<<< HEAD
<p>© 2020 Company, Inc.</p>
=======
<p>© 2021 Company, Inc.</p>
>>>>>>> footer
You as the project admin have to make a call and remove the bad line as well as separators added by git. You decided that "© 2020 Company, Inc." is the correct one.
- Edit the jumbotron.html file and remove line 97, 99, 100 ad 101:
97 <<<<<<< HEAD
98 <p>© 2020 Company, Inc.</p>
99 =======
100 <p>© 2021 Company, Inc.</p>
101 >>>>>>> footer
- save the changes and issue commit
commit -am "resolved conflict in the footer"
- display the history of commits and notice that the branches have been merged and HEAD now points to the merge commit.
git history
- branch 'footer' is now obsolete and we can delete it. Effectively, we will just remove a pointer without affecting any commits.
git branch -d footer
Display git history
and notice all the commits are still there, just the "footer" branch isn't listed anymore.
Origin is the default name of the remote repo when you do git clone, the same way as master is the default name of the main branch
- display the currently known remote repos
git remote -v
- Configure a new remote repo on your gitlab account
git remote add newrepo https://gitlab-sjc.cisco.com/<username>/newrepo
- display the list of remote repos
git remote -v
- The newly configured remote repository is not linked to any local repository yet. In order to be able to push local changes to the remote repo such association (tracking branch) needs to be created. The local branch will be tracking the remote branch on the remote server. The tracking branch is also referred to as upstream branch - that's why we use -u option (upstream) in git command:
git push newrepo -u master
Branch 'master' is now set up to track remote branch 'master' from 'newrepo'.
Go to your gitlab account and notice that a new project has been created automatically.
- display the list of branches (local and remote):
git branch -vv
- display "git log" or "git history" and notice that the new remote branch is now listed in the graph.
git history
-
Refresh the gitlab page. Verify if new files were added to the newrepo project and all commits are present.
-
Let's simulate a scenario where another developer commits changes to the same branch on the remote repo. On the gitlab account edit the jumbotron.html file (pay attention to do it on the remote repo).
In line 65 change "Hello, world!" into "Hello, Programmability Makers!".
Add a commit message "Hello message updated on remote branch". Commit changes.
-
Display the list of commits in gitlab project
-
In the terminal issue "git log" and notice the local repo doesn't have the latest changes.
git log
- Issue "git pull" to download the latest version of the code from the remote branch.
git pull
- Notice that the changes were automatically merged into your working directory. Confirm the local branch has the new commit and the local version of jumbotron page was updated
git log
-
Make another edit on the remote branch (on gitlab account). Edit line 65 and change "Hello, Programmability Makers!" into "Welcome". Add a commit message "Updated welcome message". Commit changes.
-
This time instead of pulling changes we will use fetch command.
git fetch
- Notice this time we downloaded the objects and refs from the remote repo to a local branch but didn't merge those changes into the working directory.
git history
- Since we fetched the updated version from the remote repo we can review the diffs before merging them into the local branch. HEAD points to the most recent commit on the currently active local branch and we can use this pointer instead of the commit SHA ID. newrepo/master refers to the most recent commit on the remote master branch on newrepo repository.
git diff HEAD newrepo/master
- Once we're comfortable with all changes we can incorporate them into the local branch and working directory:
git merge
If any conflict occurs during this merge we will be able to resolve it locally and when we're done we can push the updates to the remote repo.