Git (/ɡɪt/) is a distributed revision control system with an emphasis on speed, data integrity, and support for distributed, non-linear workflows.
Git was initially designed and developed by Linus Torvalds for Linux kernel development in 2005, and has since become the most widely adopted version control system for software development.
Source: Git - Wikipedia
Source: Learn Version Control with Git
Source: Learn Version Control with Git
Reference: Git Introduction for Beginners
Source: Git - Wikipedia
Torvalds has quipped about the name git, which is British English slang meaning "unpleasant person".
The man page describes Git as "the stupid content tracker".
Source: Git - Wikipedia
Subversion is a centralized version control system:
All team members work towards a single central repository, placed on a remote server.
A "checkout" from this central repository will place a "working copy" on the user's machine. (This is a snapshot from a certain version of the project on his disk.)
Think of a repository as a kind of database where your VCS stores all the versions and metadata that accumulate in the course of your project.
In Git, the repository is just a simple hidden folder named ".git" in the root directory of your project.
Knowing that this folder exists is more than enough. You don't have to (and, moreover, should not) touch anything inside this magical folder.
Change into the project's root folder, and use the "git init" command to start versioning this project:
$ cd path/to/project/folder
$ git init
You'll see that a new, hidden folder was added, named ".git". All that happened is that Git created an empty local repository for us.
Please mind the word "empty": Git did not add the current content of your working copy as something like an "initial version". The repository contains not a single version of your project, yet.
$ git clone http://example.com/git-repo.git
$ git clone https://example.com/git-repo.git
$ git clone git://example.com/git-repo.git
$ git clone ssh://user@server/git-repo.git
$ git clone user@server:git-repo.git
Git will now download a complete copy of this repository to your local disk - on condition that you're allowed to access this repository.
Source: Working on Your Project
In general, files can have one of two statuses in Git:
Source: Working on Your Project
A commit is a wrapper for a specific set of changes. The author of a commit has to comment what he did in a short "commit message". This helps other people (and himself) to understand later what his intention was when making these changes.
Every set of changes implicitly creates a new, different version of your project. Therefore, every commit also marks a specific version. It's a snapshot of your complete project at that certain point in time (but saved in a much more efficient way than simply duplicating the whole project...).
To get an overview of what you've changed since your last commit, you simply use the "git status" command:
Source: Working on Your Project
To stage some changes with the "git add" command:
$ git add new-page.html index.html css/*
To record the removals with the "git rm" command:
$ git rm error.html
The "git commit" command wraps up your changes:
$ git commit -m "Implement the new login box"
Use "git log" command to display commit history:
$ git log
commit 2dfe283e6c81ca48d6edc1574b1f2d4d84ae7fa1
Author: Tobias Günther <support@learn-git.com>
Date: Fri Jul 26 10:52:04 2013 +0200
Implement the new login box
commit 2b504bee4083a20e0ef1e037eea0bd913a4d56b6
Author: Tobias Günther <support@learn-git.com>
Date: Fri Jul 26 10:05:48 2013 +0200
Change headlines for about and imprint
Source: Working on Your Project
Source: Working on Your Project
Every commit has a unique identifier: a 40-character checksum called the "commit hash".
Since in most projects, the first 7 characters of the hash are enough for it to be unique, referring to a commit using a shortened version is very common.
In centralized version control systems like Subversion or CVS, an ascending revision number is used for this, this is simply not possible anymore in a distributed VCS like Git: The reason herefore is that, in Git, multiple people can work in parallel, committing their work offline, without being connected to a shared repository. In this case, you can't say anymore whose commit is #5 and whose is #6.
Source: Working on Your Project
Source: Branching Can Change Your Life
All the changes you make at any time will only apply to the currently active branch; all other branches are left untouched. This gives you the freedom to both work on different things in parallel and, in case things go wrong you can always go back / undo / start fresh / switch contexts...
We create a new branch and name it "contact-form":
$ git branch contact-form
Use the "git branch" command lists all of our branches:
$ git branch -v
contact-form 3de33cc Implement the new login box
* master 3de33cc [ahead 1] Implement the new login box
Our new branch "contact-form" was created and is based on the same version as "master". Additionally, the little asterisk character (*) next to "master" indicates that this is our current HEAD branch. To emphasize this: the "git branch" command only created that new branch - but it didn't make it active.
Source: Working With Branches
You should only commit code when it’s completed.
This doesn’t mean you have to complete a whole, large feature before committing. Quite the contrary: split the feature’s implementation into logical chunks and remember to commit early and often.
But don’t commit just to get half-done work out of your way when you need a "clean working copy". For these cases, consider using Git’s “Stash” feature instead.
Source: Working With Branches
Think of the Stash as a clipboard on steroids: it takes all the changes in your working copy and saves them for you on a new clipboard. You're left with a clean working copy, i.e. you have no more local changes.
Later, at any time, you can restore the changes from that clipboard in your working copy - and continue working where you left off.
You can create as many Stashes as you want - you're not limited to storing only one set of changes. Also, a Stash is not bound to the branch where you created it: when you restore it, the changes will be applied to your current HEAD branch, whichever this may be.
Source: Saving Changes Temporarily
Stash away local changes to have a clean working copy:
$ git stash
Saved working directory and index state WIP on master:
2dfe283 Implement the new login box
HEAD is now at 2dfe283 Implement the new login box
$ git status
# On branch master
nothing to commit (working directory clean)
$ git stash list
stash@{0}: WIP on master: 2d6e283 Implement the new login box
Stashing helps you get a clean working copy. While it can be helpful in many cases, it's strongly recommended...
Source: Saving Changes Temporarily
Switch to (or "check out") our newly created branch:
$ git checkout contact-form
At each point in time, only one branch can be HEAD / checked out / active. The files in your working copy are those that are associated with this exact branch. All other branches (and their associated files) are safely stored in Git's database.
Source: Checking Out a Local Branch
Integrate the changes from "contact-form" into "master":
$ git checkout master
$ git merge contact-form
When starting a merge, you don't have to (and cannot) pick individual commits that shall be integrated. Instead, you tell Git which branch you want to integrate, only these commits will then be integrated as a result.
Source: Merging Changes
When you clone a repository from a remote server, Git automatically remembers this connection for you. It saves it as a remote called "origin" by default.
In other cases where you started with a fresh local repository, no remote connections are saved. In that situation, we need to connect our local repository to a new remote before we can try some remote interactions:
$ git remote add crash-course-remote
https://github.com/gittower/git-crash-course-remote.git
$ git remote -v
crash-course-remote https://github.com/gittower/git-crash-course-remote.git (fetch)
crash-course-remote https://github.com/gittower/git-crash-course-remote.git (push)
origin https://github.com/gittower/git-crash-course (fetch)
origin https://github.com/gittower/git-crash-course (push)
Source: Connecting a Remote Repository
So, what exactly did we achieve by connecting remote?
Apparently not much happened: still our two local branches ("master" and "contact-form") and two items from our "origin" remote ("remotes/origin/HEAD" and "remotes/origin/master").
So why don't we see any data from our new "crash-course-remote"? Because, with the "git remote add" command, we have only established a relationship - but no data was exchanged so far.
Source: Inspecting Remote Data
Git stores information about remote data (like branches, commits, etc.) in your local repository for you. However, there is no "live" connection to your remote. E.g. you will not automatically see new commits or branches that your teammates published on a remote - because you have to explicitly tell Git to update!
The information about remote branches, remote commits, etc. is only as fresh as the last snapshot that you requested. There is no "automatic" update in the background.
Source: Inspecting Remote Data
To update the information about a remote, use "Fetch":
$ git fetch crash-course-remote
From https://github.com/gittower/git-crash-course-remote
* [new branch] faq-content -> crash-course-remote/faq-content
* [new branch] master -> crash-course-remote/master
Fetch will not touch any of your local branches or the files in your working copy. It just downloads data from the specified remote and makes it visible for you.
Source: Inspecting Remote Data
In general, branches have nothing to do with each other. However, a local branch can be set up to "track" a remote branch. Git will then inform you if one branch contains new commits that the other one doesn't have:
Source: Inspecting Remote Data
$ git status
# On branch faq-content
# 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: faq.html
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git add faq.html
$ git commit -m "Add new question"
[faq-content 814927a] Add new question
1 file changed, 1 insertion(+)
With a new local branch "faq-content" checked out, we're ready to contribute to this feature. Let's make some modifications, and share these changes we've just made with our colleagues:
$ git push
Source: Inspecting Remote Data
By default, the "git push" command expects us to provide it with two things:
The full command, therefore, looks something like this:
$ git push crash-course-remote faq-content
With the tracking connection that we've set up already, we've defined a "remote counterpart" branch for our local branch. Git can then use this tracking information and lets us use the "git push" and "git pull" commands without further arguments.
Source: Inspecting Remote Data
To integrate changes into working copy, use "git pull":
$ git pull
This command downloads new commits from the remote and directly integrates them into your working copy.
It's actually a "fetch" command (which only downloads data) and a "merge" command (which integrates this data into your working copy) combined.
As with the "git push" command: in case no tracking connection was established for your local HEAD branch, you will have to tell Git from which remote repository and which remote branch you want to pull ("git pull origin master", e.g.). If a tracking connection already exists, "git pull" is sufficient.
Source: Integrating Remote Changes
Let's share "contact-form" branch on the "origin" remote:
$ git checkout contact-form
Switched to branch 'contact-form'
$ git push -u origin contact-form
Counting objects: 36, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (31/31), done.
Writing objects: 100% (36/36), 90.67 KiB, done.
Total 36 (delta 12), reused 0 (delta 0)
Unpacking objects: 100% (36/36), done.
To file://Users/tobidobi/Desktop/GitCrashkurs/remote-test.git
* [new branch] contact-form -> contact-form
Branch contact-form set up to track remote branch contact-form from origin.
Source: Publishing a Local Branch
This command tells Git to publish our current local HEAD branch on the "origin" remote under the name "contact-form"
The "-u" flag establishes a tracking connection between that newly created branch on the remote and our local "contact-form" branch.
To delete a local branch, we can use below command:
$ git branch -d contact-form
We can also delete the remote branch by adding "-r" flag:
$ git branch -dr origin/contact-form
Source: Deleting Branches
After publishing the local branch, we now get:
Source: A Successful Git Branching Model
Main Branches:
Supporting Branches:
Both branches have infinite lifetime
To restore a file to last committed version, use "git checkout":
$ git checkout -- file/to/restore.ext
If you use it with two dashes and (separated with a space!) the path to a file, it will discard the uncommitted changes in that file.
If you need to discard all current changes in your working copy and restore the last committed version of your complete project, use "git reset" command:
$ git reset --hard HEAD
This tells Git to replace the files in your working copy with the "HEAD" revision (which is the last committed version).
Discarding uncommitted changes cannot be undone.
Source: Undoing Things
Using the "git revert" command is a possible way to undo a previous commit. It reverts the effects of a certain commit, by producing a new commit with changes that revert each of the changes in that unwanted commit.
$ git revert 2b504be
[master 364d412] Revert "Change headlines for about and imprint"
2 files changed, 2 insertions(+), 2 deletions (-)
Source: Undoing Things
Another tool to "undo" commits is the "git reset" command. It neither produces any new commits nor does it delete any old ones. It works by rolling back your current HEAD branch to an older revision:
$ git reset --hard 2be18d9
Source: Undoing Things
Both commands, "revert" and "reset", only affect your current HEAD branch. Therefore, you should make sure you have checked out the correct branch before starting to play with them.
Just like "revert", the "reset" command also doesn't delete any commits. It just makes it look as if they hadn't existed and removes them from the history. However, they are still stored in Git's database for at least 30 days. So if you should ever notice you accidentally removed commits you still need, one of your Git expert colleagues will still be able to restore them for you.
Source: Undoing Things
Source: Inspecting Changes with Diffs
$ git diff
diff --git a/about.html b/about.html
index d09ab79..0c20c33 100644
--- a/about.html
+++ b/about.html
@@ -19,7 +19,7 @@
</div>
<div id="headerContainer">
- <h1>About</h1>
+ <h1>About This Project</h1>
</div>
<div id="contentContainer">
"git diff" will show us all current local changes in our working copy that are unstaged.
If you want to see only changes that have already been added to the Staging Area, use "git diff --staged".
Source: Inspecting Changes with Diffs
To know how one branch differs from another one:
$ git diff master..contact-form
Instead of requesting such information on the branch level, you can even compare two arbitrary revisions with each other:
$ git diff 0023cdd..fcd6199
Source: Inspecting Changes with Diffs
Git will tell you that you have "unmerged paths" (which is just another way of telling you that you have one or more conflicts) via "git status":
Source: Dealing with Merge Conflicts
Source: Submodules
Often in a project, you want to include libraries and other resources. The manual way is to simply download the necessary code files, copy them to your project, and commit the new files into your Git repository, while:
Since these are quite common problems in everyday projects, Git of course offers a solution: Submodules.
A "Submodule" is just a standard Git repository. The only specialty is that it is nested inside a parent repository.
In the common case of including a code library, you can simply add the library as a Submodule in your main project.
A Submodule remains a fully functional Git repository: you can modify files, commit, pull, push, etc. from inside it like with any other repository.
We first create a new "lib" folder to host this (and future) library code, with the "git submodule add" command, we'll add a little JavaScript library from GitHub:
$ git submodule add https://github.com/djyde/ToProgress
Cloning into 'lib/ToProgress'...
remote: Counting objects: 180, done.
remote: Compressing objects: 100% (89/89), done.
remote: Total 180 (delta 51), reused 0 (delta 0), pack-reused 91
Receiving objects: 100% (180/180), 29.99 KiB | 0 bytes/s, done.
Resolving deltas: 100% (90/90), done.
Checking connectivity... done.
It's important to understand that the actual contents of a Submodule are not stored in its parent repository. Only its remote URL, the local path inside the main project and the checked out revision are stored by main repository.
Of course, the Submodule's working files are placed inside the specified directory in your project. But they are not part of the parent project's version control contents.
A new ".gitmodules" file was created. This is where Git keeps track of our Submodules and their configuration:
[submodule "lib/ToProgress"]
path = lib/ToProgress
url = https://github.com/djyde/ToProgress
Let's have a look at our project's status:
$ git commit -m "Add 'ToProgress' Javascript library as Submodule"
Git regards adding a Submodule as a modification like any other and requests you to commit it to the repo:
When you clone a project that contains Submodules: by default, the "git clone" command only downloads the project itself. Our "lib" folder, would stay empty.
You have two options to end up with a populated "lib" folder (or wherever else you choose to save your Submodules; "lib" is just an example):
You can add the "--recurse-submodules" option to "git clone"; this tells Git to also initialize all Submodules when the cloning is finished.
$ cd lib/ToProgress/
$ git log --oneline --decorate
83298f7 (HEAD, master) update .gitignore
a3b6186 remove page
ed693b7 update doc
3557a0e (tag: 0.1.1) change version code
2421796 update readme
$ git checkout 0.1.1
$ git submodule status
+3557a0e0f7280fb3aba18fb9035d204c7de6344f lib/ToProgress (0.1.1)
We see that Git regards moving the Submodule's pointer as a change like any other:
$ git status
On branch master
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: lib/ToProgress (new commits)
What if one of our teammates does this in our project? Let's say we integrate his changes after he has moved the Submodule pointer to a different revision:
$ git pull
Updating 43d0c47..3919c52
Fast-forward
lib/ToProgress | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git submodule status
+83298f72c975c29f727c846579c297938492b245 lib/ToProgress (0.1.1-8-g83298f7)
That little "+" sign tells us that the Submodule revision was moved (not the one we committed before).
The "update" command helps us correct this:
$ git submodule update lib/ToProgress
Submodule path 'lib/ToProgress': checked out '3557a0e0f7280fb3aba18fb9035d204c7de6344f'
Let's see if there's new code available in the Submodule:
$ cd lib/ToProgress
$ git fetch
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/djyde/ToProgress
83298f7..3e20bc2 master -> origin/master
When checking the Submodule's status, we're informed that we're on a detached HEAD:
$ git status
HEAD detached at 3557a0e
nothing to commit, working directory clean
$ git pull origin master
$ git checkout master
Note that you cannot use the shorthand "git pull" syntax but instead need to specify the remote and branch, too.
In some cases, you might want to make some custom changes to a Submodule. You've already seen that working in a Submodule is like working in any other Git repository: any Git commands that you perform inside a Submodule directory are executed in the context of that sub-repository.
You should make sure you currently have a branch checked out in the Submodule before you commit. That's because if you're in a detached HEAD situation, your commit will easily get lost: it's not attached to any branch and will be gone as soon as you check out anything else.
Apart from that, everything else you've already learned still applies: in the main project, "git submodule status" will tell you that the Submodule pointer was moved and that you'll have to commit the move.
If you really want to remove a submodule, please don't do this manually: trying to mess with all the configuration files in a correct way will almost inevitably cause problems.
$ git submodule deinit lib/ToProgress
$ git rm lib/ToPogress
$ git status
...
modified: .gitmodules
deleted: lib/ToProgress
Source: Why Git?