A commit in a git repository records a snapshot of all the files in your directory. It's like a giant copy and paste, but even better!
Git wants to keep commits as lightweight as possible though, so it doesn't just blindly copy the entire directory every time you commit. It can (when possible) compress a commit as a set of changes, or a "delta", from one version of the repository to the next.
You start a git repo with:
git init
Each repo has its own configurations. You can see them with:
git config --list
Here are the essential configurations you need to add:
# Configure owner of changes just for current repo
git config user.name "NAME"
git config user.email "EMAIL"
Github contributions count only if the repo email is the same as the github email.
When doing git push
, this gets the password from the .git-credentials
file, or adds it there on first push .
git config credential.helper store
# Configure DEFAULT owner of changes for all repos
git config --global user.name "NAME"
git config --global user.email "EMAIL"
# Configure password token
# Generated in developer settings / personal access tokens / classic
# Get the password from the .git-credentials file, or add it there on first push
git config --global credential.helper store
# Allows multiple repositories on the same host to use different credentials. By default (false) it uses the first match.
git config --global credential.useHttpPath true
Feature branches i.e. Main is deployed.
Trunk-based i.e. Branches are deployed.
- Create a Git repository for every new project.
- Create a new branch for every new feature.
- Use Pull Requests to merge code to Master.
Download from repository and set user.
git clone URL
git config user.name "NAME"
git config user.email "EMAIL"
Make changes and upload back.
git status
git add .
git commit -m "MESSAGE"
git push origin master
A previously cloned repo on a server can now pull just the changes (clone is used only the first time to create the local repo).
git pull
- (git fetch
+ git merge
)
Github repo > Clone to local > Push to Github > Pull changes to server
Master branch = Timeline
HEAD
= Last commit on current branch.
HEAD is the symbolic name for the currently checked out commit -- it's essentially what commit you're working on top of.
Commit messages should be in the present tense instead of past. They should tell what a commit does instead of what happened. Ex. Add new module vs Added new module.
Git uses vi as the default editor.
# Initialize an empty repo inside a .git hidden directory.
git init
This is a list of files/folders to be ignored in commits. To use it, create a .gitignore
file in the working directory, NOT inside .git
.
Each line in the file is a new ignore rule. Ex. To ignore node_modules/
, just add that as one line.
If the ignoring is added after committing the files to be ignored, they need to be untracked. Use git rm -r --cached .
to untrack everything, and the git add/git commit
. Careful as this can lose progress to files.
Also, pattern matching can be used to ignore specific files in specific places. Ex. *.txt
ignores all text files, while routes/*.js
ignores all javascript files in that folder. To ingore a folder, use FOLDER/
.
Finally, use #
to comment.
it's generally considered good practice to avoid merges where possible.
A good workflow is to leave the master
for production and do all the coding in a separate develop
branch. The develop
branch can expand into feature
branches which would later be merged back. When a certain stable version is reached in develop
, it can be merged with master
along with a version tag.
Additonal brances such as release
and hotfix
can be introduced between the master
and develop
ones.
If a branch is not shown, it's due to fast-forwarding i.e. no changes were made in the branch being merged into, hence the simpler merge log.
* e67ccc4 (HEAD -> master) Merge branch 'header'
|\
| * ce37b16 (header) Fix original header
* | 707692c Merge branch 'footer'
|\ \
| * | 2dc6e90 (footer) Expand footer
| * | fd61a63 Create footer.html
* | | 8083e87 Add fifth bla in CAPS
* | | 8d56117 Add fourth bla in index.html
* | | 86c50f0 Added third bla in index.html
|/ /
| | * 26326bb (header2) Fix header2 version
| | * d83bebc Modify header into version 2
| |/
| * c2d4091 Create header.html
|/
* 9524ae9 Create index.html
# List all the commands.
git help
# Explain a specific command.
git help COMMAND
# Commit history.
git log
# files in a commit
git show --name-only <commit_hash>
# Show unstaged differences since last commit.
git diff
# Show differences after staging.
git diff --staged
# Show differences between current and specific commit.
git diff HASH
# A visual tree with branch names included.
git log --oneline --decorate --all --graph
# Add the "tree" alias as a shortcut.
git config --global alias.tree "log --oneline --decorate --all --graph"
# What changed since last commit?
git status
# Stage an untracked file for committing.
git add FILENAME
# Multiple files.
git add FILENAME FILENAME
# A folder.
git add FOLDER/
# All specific file types.
git add *.js
# ... in a folder.
git add FOLDER/*.js
# ... in whole project.
git add "*.js"
# Track everything.
git add .
# Unstage files.
git reset HEAD FILENAME
Each commit moves the HEAD further up the timeline.
# Commit changes with inline message.
git commit -m "MESSAGE"
If the -m
is ommited, the screen will move to the vi
text editor, which can be exited with :q
.
A reference to a specific commit, used mostly for release versioning.
# Create tag.
git tag -a v0.0.1 -m "Version 0.0.1"
# List tags.
git tag
# Open a specific version.
git checkout v0.0.1
# Push tags to remote repo.
git push --tags
Unstaged
# Tracked files
git restore .
# Untracked files
git clean -f
Staged (DON'T DO THESE AFTER PUSHING!)
# Revert a file to the last commit version.
git checkout -- FILENAME
# UNDO commit and move everything back to staging. The carrot on the HEAD means move to the previous commit.
git reset --soft HEAD^
# DELETE the last 2 commits.
git reset --HARD HEAD^^
# DELETE everything after the specified commit.
git reset --HARD HASH
# Change last commit with overriding message.
git commit --amend -m "MESSAGE"
# Force change on GitHub. Git interprets the "^" after the hash as the parent of this very commmit, and the "+" as a force push.
git push origin +hash^:master
# Create a local repository from a remote one.
git clone URL
# ... with a different name.
git clone URL NEW_NAME
Used to update the local repo with the latest changes. Should be done often.
git pull
Behind the scenes, this creates an origin/master branch which is automatically merged into the master one, unless there is a merge conflict.
Do not mess with the master. The master branch is deployable production code, meant to be stable. Instead, work on new features in separate branches, which would then be merged
or rebased
into master.
Branches are local, meaning they cannot be worked on at the same time.
Branches in Git are incredibly lightweight as well. They are simply pointers to a specific commit -- nothing more. This is why many Git enthusiasts chant the mantra: branch early, and branch often.
Because there is no storage / memory overhead with making many branches, it's easier to logically divide up your work than have big beefy branches.
A branch essentially says "I want to include the work of this commit and all parent commits."
Switching branches will only show the files in that branch.
# Check which branch we are on.
git branch
# Check remote branches.
git branch -r
# Move to a specific branch (Set HEAD from master to <branch_name>). This is like switching timelines.
git checkout <branch_name>
# Create new branch. HEAD still on master (Use checkout to switch).
git branch <name>
# Create AND move to a branch.
git checkout -b <branch_name>
# Create a remote branch. Usually origin.
git push <repo_name> <branch_name>
# Delete a branch.
git branch -d <branch_name>
# Delete a remote branch
git push <repo_name> --delete <branch_name>
The merging is done from the perspective of where we merge INTO
.
Merging in Git creates a special commit that has two unique parents. A commit with two parents essentially means "I want to include all the work from this parent over here and this one over here, and the set of all their parents."
Move (checkout) to the branch (master) you want to merge into, and use:
git merge BRANCH_NAME
master
now points to a commit that has two parents. If you follow the arrows up the commit tree from master, you will hit every commit along the way to the root. This means that master contains all the work in the repository now.
Merging is very easy if the master branch is not modified. This is fast-forwarding.
If both branches were modified, a commit is created to do the merge. (Vi editor opens for the message)
0
|\
2 1 - 2 master, 1 branch
|/
3 - master assimilates branch
The "merging" is done from the perspective of where we merge FROM
.
The second way of combining work between branches is rebasing. Rebasing essentially takes a set of commits, "copies" them, and plops them down somewhere else i.e. it serializes the history if you do not want a lot of branches in the final history.
While this sounds confusing, the advantage of rebasing is that it can be used to make a nice linear sequence of commits. The commit log / history of the repository will be a lot cleaner if only rebasing is allowed.
Move (checkout) to the branch (BRANCH_NAME) you want to merge from, and use:
git rebase master
This makes it look like two features were developed sequentially, when in reality they were developed in parallel.
0
|\
1 1 - 1 branch
|
2 - 2 master
|
3 - branch (1) was added to the original timeline.
A project can have multiple remotes ex. origin, test, production...
# Add a remote i.e. bookmark a repo i.e. This NAME = this URL. The name is usually "origin", but it can be anything.
git remote add NAME URL
# List all remotes.
git remote -v
# Check a remote.
git remote show origin
# Remove a remote.
git remote rm NAME
Define which local branch (usually master) to push to which repository (usually origin). It asks for user and pass.
# git push -u REPO_NAME BRANCH_NAME
git push -u origin master
-u
remember the repo and the branch, so that only git push
can be used.
Remember login credentials
This stores the username and password in a .git-credentials
file in home.
git config --global credential.helper store
A pull request is functionality that GitHub provides. It is not part of Git itself. You can think of a pull requests as a discussion dedicated to a particular branch, about whether it should be merged with master.
Workflow:
- Fork the repo you want to contribute to.
- Clone the repo to your local machine.
- Create a branch and start making changes.
git checkout -b new-freature
- Push the changes.
git push origin new-freature
- On Github, click on the
Compare & pull request
button. - Add a description and click on
Create pull request
. - The owner of the original repo then decides if the changes should be merged.
- For future changes after the code changes, you pull the update repo.
This uses the Git merge functionality under the hood to ensure that code bases we merge code bases without conflicts.
Once someone completes a feature, they don’t immediately merge it into master. Instead, they push the feature branch to the central server and file a pull request asking to merge their additions into master.
This gives other developers an opportunity to review the changes before they become a part of the main codebase.
For example, if a developer needs help with a particular feature, all they have to do is file a pull request. Interested parties will be notified automatically, and they’ll be able to see the question right next to the relevant commits.
Once a pull request is accepted, the actual act of publishing a feature is much the same as in the Centralized Workflow. First, you need to make sure your local master is synchronized with the upstream master. Then, you merge the feature branch into master and push the updated master back to the central repository.
Cloning does:
- Download entire repo into a new local one.
- Add "origin" remote, pointing to the clone URL.
- Check out initial branch. (Set head to master)
Create a new branch and add features. Each commit moves the HEAD.
git checkout -b admin
git add admin/dashboard.html
git commit -m "Add dashboard"
git add admin/users.html
git commit -m "Add user admin"
Fix bugs on master. Move to the master branch, check that we are on it and pull the latest changes from the remote. Push changes to remote.
git checkout master
git branch
git pull
git add store.rb
git commit -m "Fix store bug"
git add product.rb
git commit -m "Fix product"
git push
Move back to the admin feature to finish changes. When done, move to master branch and merge with admin.
git checkout admin
git checkout master
git merge admin
What the timeline (master) looks like. * = commit.
* - Merge
|\
| * - Admin feature
* | - Bug fix
* | - Bug fix
| * - Admin feature
|/
* - Last commit before branch
While we all used git (and Github) in college to collaborate and save code, we mainly stuck to the the basic "add, commit, push" flow and didn't really go outside of that much, mainly because we didn't know it all that well at the time. But looking back after 2+ years of using [git] in a professional environment, here was out shortlist of things we wish we knew git could do (in order of importance)
-
git reset HEAD
If you could learn one thing about git besides how to commit and push, this would be it. When you commit to git, those commits generate a log that is identified by a commit hash. By using the commandgit reset HEAD <commit-hash>
(there are other variants, this is the most straightforward), you can essentially go back in time in your code. What this is great for is if you are in a point in your project where you are pretty certain you're going to fuck something up, committing is like doing a quick save in a video game right before a boss fight. Something goes wrong, reset to the last commit. -
Branching and Pull Request Workflow
In college we all worked off a single branch pushing our code up to save it. While this worked, the occasional push by a teammate who didn't properly test their code caused our project to crash and one of us having to fix it (this usually happened <12 hours before deadlines). Branching and pull requests would have eliminated that. Not only are they working on a different branch so pushing up won't mess with the original copy. When they want to merge their code, going through a PR (pull request) process will involve the code getting reviewed by another person to make sure it all works. -
git init
When I first started with git I always thought that you had to have a remote somewhere to use it (ie on github or gitlab). As I have learned that is not the case, you can initialize a git repository in any directory and commit any files, you are only restricted from pushing until you add a remote (duh). This is great to pair with the 1st tip, even if you are working by yourself. Having the ability to go back if you mess something up is essential.
TBH that's really it. Git is a very complex application that even I haven't fully learned yet. However, it has a fairly easy learning curve letting you take advantages of the most important bits quickly. If you have any questions feel free to post below and I'll be happy to answer best I can.