Created by Esther Schayek
In this presentation, we will explore essential Git commands, providing a comprehensive overview of their functionality.
We will focus on specific use cases, highlight their advantages and disadvantages through practical coding examples, and introduce alternative and complementary commands to each one of them.
To write a great git commit message, take a look at these guidelines and suggestions.
By writing good commits, you are simply future-proofing yourself. You could save yourself and/or coworkers hours of digging around while troubleshooting by providing that helpful description.
The extra time it takes to write a thoughtful commit message as a letter to your potential future self is extremely worthwhile. On large scale projects, documentation is imperative for maintenance.
Begin with a short summary line - message subject
Add foo
Drop foo
Fix foo
Refactor foo
Optimize foo
Continue with a longer description - message body:
We recommend these summary keywords because they use imperative mood, present tense, active voice, and are verbs:
| Case | Right | Wrong |
|---|---|---|
| Capitalize the summary. | Add feature | add feature |
| Finish the summary | Add feature | Add feature. |
| The summary ends with an non-sentence-ending period | Add feature for U.S.A. | Add feature for U.S.A |
| Use imperative mood: | Add feature | Adds feature/Adding feature/Added feature |
[bug] ...
(release) ...
#12345 ...
docs: ...
JIRA-666 #time 1w 2d 4h 30m #comment Task completed ahead of schedule #resolve
Writing good commit messages is an extremely beneficial skill to develop, and it helps you communicate and collaborate with your team. Commits serve as an archive of changes. They can become an ancient manuscript to help us decipher the past, and make reasoned decisions in the future.
There is an existing set of agreed-upon standards we can follow, but as long as your team agrees upon a convention that is descriptive with future readers in mind, there will undoubtedly be long-term benefits.
The pre-commit hook is executed just before a commit is made. It allows you to inspect the changes that are about to be committed and possibly reject the commit if certain conditions are not met.
GitHub Actions provides a continuous integration and continuous delivery (CI/CD) platform that can also execute pre-commit and pre-push checks.
Configuring pre-commit and pre-push hooks using GitHub Actions involves creating a YAML file that defines the workflow steps, including the checks to be executed. Here’s a step-by-step guide:
git add .github/workflows/pre-commit.yml .github/workflows/pre-push.yml
git commit -m "Add GitHub Actions workflows for pre-commit and pre-push checks" git push
Pre-commit allows developers to define and run a series of automated checks on their code before committing changes, ensuring code quality and consistency. However, one of its limitations is the lack of support for all languages and tools.
The git remote command lets you create, view, and delete connections to other repositories. Remote connections are more like bookmarks rather than direct links into other repositories. Instead of providing real-time access to another repository, they serve as convenient names that can be used to reference a not-so-convenient URL.
The git remote command is essentially an interface for managing a list of remote entries that are stored in the repository's ./.git/config file. The following commands are used to view the current state of the remote list.
git remote
git remote -v
The git remote command is also a convenience or 'helper' method for modifying a repo's ./.git/config file. The commands presented below let you manage connections with other repositories. The following commands will modify the repo's /.git/config file. The result of the following commands can also be achieved by directly editing the ./.git/config file with a text editor.
When you clone a repository with git clone, it automatically creates a remote connection called origin pointing back to the cloned repository. This is useful for developers creating a local copy of a central repository, since it provides an easy way to pull upstream changes or publish local commits. This behavior is also why most Git-based projects call their central repository origin.
Git supports many ways to reference a remote repository. Two of the easiest ways to access a remote repo are via the HTTP and the SSH protocols. HTTP is an easy way to allow anonymous, read-only access to a repository. For example:
http://host/path/to/repo.git
But, it’s generally not possible to push commits to an HTTP address (you wouldn’t want to allow anonymous pushes anyways). For read-write access, you should use SSH instead:
ssh://user@host/path/to/repo.git
The git remote command is one of many Git commands that takes additional appended 'subcommands'. Below is an examination of the commonly used git remote subcommands.
| Subcommands | Purpose |
|---|---|
| -f | Will git fetch immediately after the remote record is created. |
| --tags | Will git fetch immediately and import every tag from the remote repository. |
|
Outputs the URLs for a remote record. |
| --push | push URLs are queried rather than fetch URLs. |
| --all | all URLs for the remote will be listed. |
|
Deletes any local branches for <NAME> that are not present on the remote repository. |
|
Will list what branches are set to be pruned, but will not actually prune them. |
The show subcommand can be appended to git remote to give detailed output on the configuration of a remote. This output will contain a list of branches associated with the remote and also the endpoints attached for fetching and pushing.
git remote show upstream
* remote upstream
Fetch URL: https://bitbucket.com/upstream_user/reponame.git
Push URL: https://bitbucket.com/upstream_user/reponame.git
HEAD branch: main
Remote branches:
main tracked
simd-deprecated tracked
tutorial tracked
Local ref configured for 'git push':
main pushes to main (fast-forwardable)
Once a remote record has been configured through the use of the git remote command, the remote name can be passed as an argument to other Git commands to communicate with the remote repo. Both git fetch, and git pull can be used to read from a remote repository. Both commands have different operations that are explained in further depth on their respective slides.
The git push command is used to write to a remote repository.
This example will upload the local state of <branch-name> to the remote repository specified by <remote-name>
The git fetch command downloads commits, files, and refs from a remote repository into your local repo. Fetching is what you do when you want to see what everybody else has been working on. It’s similar to svn update in that it lets you see how the central history has progressed, but it doesn’t force you to actually merge the changes into your repository.
Git isolates fetched content from existing local content; it has absolutely no effect on your local development work. Fetched content has to be explicitly checked out using the git checkout command. This makes fetching a safe way to review commits before integrating them with your local repository.
When downloading content from a remote repo, git pull and git fetch commands are available to accomplish the task. You can consider git fetch the 'safe' version of the two commands. In fact git fetch will not update your local repo's working state, leaving your current work intact. git pull is the more aggressive alternative; it will download the remote content for the active local branch and immediately execute git merge to create a merge commit for the new remote content.
To better understand how git fetch works let us discuss how Git organizes and stores commits. Behind the scenes, in the repository's ./.git/objects directory, Git stores all commits, local and remote. Git keeps remote and local branch commits distinctly separate through the use of branch refs. Executing the git branch command will output a list of the local branch refs.
git branch
main
feature1
debug2
Examining the contents of the /.git/refs/heads/ directory would reveal similar output.
ls ./.git/refs/heads/
main
feature1
debug2
Remote branches are just like local branches, except they map to commits from somebody else’s repository. Remote branches are prefixed by the remote they belong to so that you don’t mix them up with local branches.
git branch -r
origin/main
origin/feature1
origin/debug2
remote-repo/main
remote-repo/other-feature
You can inspect remote branches with the usual git checkout and git log commands. If you approve the changes a remote branch contains, you can merge it into a local branch with a normal git merge.
| Command | Purpose |
|---|---|
|
Fetch all of the branches from the repository. This also downloads all of the required commits and files from the other repository. |
|
Same as the above command, but only fetch the specified branch. |
|
A power move which fetches all registered remotes and their branches: |
|
Will perform a demo run of the command. It will output examples of actions it will take during the fetch but not apply them. |
The following example will demonstrate how to fetch a remote branch and update your local working state to the remote contents. In this example, let us assume there is a central repo origin from which the local repository has been cloned from using the git clone command. Let us also assume an additional remote repository named coworkers_repo that contains a feature_branch which we will configure and fetch. With these assumptions set let us continue the example.
git remote add coworkers_repo git@bitbucket.org:coworker/coworkers_repo.git
git fetch coworkers_repo coworkers/feature_branch
fetching coworkers/feature_branch
git checkout coworkers/feature_branch
The output from this checkout operation indicates that we are in a detached HEAD state. This is expected and means that our HEAD ref is pointing to a ref that is not in sequence with our local history.
git checkout -b local_feature_branch
The following example walks through the typical workflow for synchronizing your local repository with the central repository's main branch.
git fetch origin
a1e8fb5..45e66a4 main -> origin/main
a1e8fb5..9e8ab1c develop -> origin/develop
* [new branch] some-feature -> origin/some-feature
git fetch gives you access to the entire branch structure of another repository.
In review, git fetch is a primary command used to download contents from a remote repository. git fetch is used in conjunction with git remote, git branch, git checkout, and git reset to update a local repository to the state of a remote. The git fetch command is a critical piece of collaborative git work flows. git fetch has similar behavior to git pull, however, git fetch can be considered a safer, nondestructive version.
The git pull command is used to fetch and download content from a remote repository and immediately update the local repository to match that content. Merging remote upstream changes into your local repository is a common task in Git-based collaboration work flows.
The git pull command is actually a combination of two other commands, git fetch followed by git merge. In the first stage of operation git pull will execute a git fetch scoped to the local branch that HEAD is pointed at. Once the content is downloaded, git pull will enter a merge workflow. A new merge commit will be-created and HEAD updated to point at the new commit.
| Commands | Purpose |
|---|---|
|
Fetch the specified remote’s copy of the current branch and immediately merge it into the local copy. This is the same as git fetch <remote> followed by git merge origin/<current-branch>. |
|
Similar to the default invocation, fetches the remote content but does not create a new merge commit. |
|
Same as the previous pull Instead of using git merge to integrate the remote branch with the local one, use git rebase. |
|
Gives verbose output during a pull which displays the content being downloaded and the merge details. |
git pull is one of many commands that claim the responsibility of 'syncing' remote content. In fact, git pull will download remote content and immediately attempt to change the local state to match that content. This may unintentionally cause the local repository to get in a conflicted state.
The --rebase option can be used to ensure a linear history by preventing unnecessary merge commits. Many developers prefer rebasing over merging, since it’s like saying, "I want to put my changes on top of what everybody else has done."
In fact, pulling with --rebase is such a common workflow that there is a dedicated configuration option for it:
git config --global branch.autosetuprebase always
After running that command, all git pull commands will integrate via git rebase instead of git merge.
git pull
git checkout main
git pull --rebase origin
The git push command is used to upload local repository content to a remote repository. Pushing is how you transfer commits from your local repository to a remote repo. It's the counterpart to git fetch, but whereas fetching imports commits to local branches, pushing exports commits to remote branches. Remote branches are configured using the git remote command. Pushing has the potential to overwrite changes, caution should be taken when pushing. These issues are discussed below.
| Command | Purpose |
|---|---|
|
Push to the specified branch, along with all of the necessary commits and internal objects. This creates a local branch in the destination repository. To prevent you from overwriting commits, Git won’t let you push when it results in a non-fast-forward merge in the destination repository. |
|
Same as the above command, but force the push even if it results in a non-fast-forward merge. Do not use the --force flag unless you’re absolutely sure you know what you’re doing. Push all of your local branches to the specified remote. |
|
Tags are not automatically pushed when you push a branch or use the --all option. The --tags flag sends all of your local tags to the remote repository. |
Git prevents you from overwriting the central repository’s history by refusing push requests when they result in a non-fast-forward merge. So, if the remote history has diverged from your history, you need to pull the remote branch and merge it into your local one, then try pushing again. This is similar to how SVN makes you synchronize with the central repository via svn update before committing a changeset.
The --force flag overrides this behavior and makes the remote repository’s branch match your local one, deleting any upstream changes that may have occurred since you last pulled. The only time you should ever need to force push is when you realize that the commits you just shared were not quite right and you fixed them with a git commit --amend or an interactive rebase. However, you must be absolutely certain that none of your teammates have pulled those commits before using the --force option.
git checkout main
git fetch origin main
git rebase -i origin/main
# Squash commits, fix up commit messages etc.
git push origin main
# make changes to a repo and git add
git commit --amend
# update the existing commit message
git push --force origin main
git branch -D branch_name
git push origin :branch_name
git stash temporarily shelves (or stashes) changes you've made to your working copy so you can work on something else, and then come back and re-apply them later on. Stashing is handy if you need to quickly switch context and work on something else, but you're mid-way through a code change and aren't quite ready to commit.
The command:
git stash
The git stash command takes your uncommitted changes (both staged and unstaged), saves them away for later use, and then reverts them from your working copy. For example:
$ git status
On branch main
Changes to be committed:
new file: style.css
Changes not staged for commit:
modified: index.html
$ git stash
Saved working directory and index state WIP on main: 5002d47 our new homepage
HEAD is now at 5002d47 our new homepage
$ git status
On branch main
nothing to commit, working tree clean
At this point you're free to make changes, create new commits, switch branches, and perform any other Git operations; then come back and re-apply your stash when you're ready. Note that the stash is local to your Git repository; stashes are not transferred to the server when you push.
The command:
git stash pop
Throught this command you can reapply previously stashed changes and remove them from your stash. For example:
$ git status
On branch main
nothing to commit, working tree clean
$ git stash pop
On branch main
Changes to be committed:
new file: style.css
Changes not staged for commit:
modified: index.html
Dropped refs/stash@{0} (32b3aa1d185dfe6d57b3c3cc3b32cbf3e380cc6a)
By default, git stash pop will re-apply the most recently created stash: stash@{0} You can choose which stash to re-apply by passing its identifier as the last argument
git stash pop stash@{2}
The command:
git stash apply
This way you can reapply the changes to your working copy and keep them in your stash. This is useful if you want to apply the same stashed changes to multiple branches.
$ git stash apply
On branch main
Changes to be committed:
new file: style.css
Changes not staged for commit:
modified: index.html
The command:
git stash -u (or --include-untracked)
By default git won't stash:
So if we add a third file to our example above, but don't stage it (i.e. we don't run git add), git stash won't stash it.
Adding the -u option (or --include-untracked) tells git stash to also stash your untracked files:
You can include changes to ignored files as well by passing the -a option (or --all) when running git stash.
The command:
git stash save "message"
You aren't limited to a single stash. You can run git stash several times to create multiple stashes, and then use git stash list to view them. By default, stashes are identified simply as a "WIP" – work in progress – on top of the branch and commit that you created the stash from. After a while it can be difficult to remember what each stash contains
Therefore it is recommended to add a message to your stash
$ git stash save "add style to our site"
Saved working directory and index state On main: add style to our site
HEAD is now at 5002d47 our new homepage
$ git stash list
stash@{0}: On main: add style to our site
stash@{1}: WIP on main: 5002d47 our new homepage
stash@{2}: WIP on main: 5002d47 our new homepage
The command:
git stash show -p
You can view a summary of a stash with git stash show and the -p flag offers you the full diff of a stash
The command:
git stash -p
You can also choose to stash just a single file, a collection of files, or individual changes from within files. If you pass the -p option (or --patch) to git stash, it will iterate through each changed "hunk" in your working copy and ask whether you wish to stash it:
The command:
git stash branch add-stylesheet stash@{1}
If the changes on your branch diverge from the changes in your stash, you may run into conflicts when popping or applying your stash. Instead, you can use git stash branch to create a new branch to apply your stashed changes to
The command:
git stash clean or git stash drop stash@{1}
If you want to clean all your stash use git stash clean .If you want to clean a particular stach use git stash drop stash@{1}
In Git, the term "force" usually refers to overriding Git's default behavior to enforce actions that would otherwise be prevented due to potential data loss or conflicts. The most common contexts where "force" is used include git push and git reset.
Here's one of the great things about Git: a safe state on the remote repository always goes first! The wonderful consequence of this is that conflicts cannot happen on the remote repository (unlike in other version control systems).
One of the reasons for this "safety on the remote" is how the "push" operation is designed in Git: you can only upload your own changes with a push if you have previously pulled in any outstanding changes from others. This way, a healthy state on the remote repository for everyone is always guaranteed.
However, there might come situations where you deliberately want to overwrite the commit history on the remote with your local one. This is when git push --force comes into play.
As described above, Git will normally only allow you to push your changes if you have previously updated your local branch with the latest commits from its remote counterpart. Only when you are up-to-date will you be able to push your own new commits to the remote.
The --force option for git push allows you to override this rule: the commit history on the remote will be forcefully overwritten with your own local history.
This is a rather dangerous process, because it's very easy to overwrite (and thereby lose) commits from your colleagues. Also, even if no one else has pushed anything to the remote repository in the meantime, your colleagues might still have based their new work on the old commit history. Your "force push" changes this history and means theirs is not in line with the new one anymore.
The .gitignore is the most commonly used method for excluding files from Git.
Lets learn about it!
| .gitignore entry | Ignores every… |
|---|---|
| target/ | …folder (due to the trailing /) recursively |
| target | …file or folder named target recursively |
| /target | …file or folder named target in the top-most directory (due to the leading /) |
| /target/ | …folder named target in the top-most directory (leading and trailing /) |
| *.class | …every file or folder ending with .class recursively |
| .gitignore entry | Ignores every… |
|---|---|
| #comment | …nothing, this is a comment (first character is a #) |
| \#comment | …every file or folder with name #comment (\ for escaping) |
| target/logs/ | …every folder named logs which is a subdirectory of a folder named target |
| target/*/logs/ | …every folder named logs two levels under a folder named target (* doesn’t include /) |
| target/**/logs/ | …every folder named logs somewhere under a folder named target (** includes /) |
| *.py[co] | …file or folder ending in .pyc or .pyo. However, it doesn’t match .py |
| !README.md | Doesn’t ignore any README.md file even if it matches an exclude pattern, e.g. *.md. NOTE This does not work if the file is located within a ignored folder. |
There are several locations where Git looks for ignore files. Besides looking in the root folder of a Git project, Git also checks if there is a .gitignore in every subdirectory. This way you can ignore files on a finer grained level if different folders need different rules.
Moreover, you can define repository specific rules which are not committed to the Git repository, i.e. these are specific to your local copy. These rules go into the file .git/info/exclude which is created by default in every Git repository with no entries.
One useful file you can define yourself is a global ignore file. It doesn’t have a default location or file name. You can define it yourself with the following command:
git config --global core.excludesfile ~/.gitignore_global
Every rule which goes into this file applies to every Git repository in your user account. This is especially useful for OS-specific files like .DS_Store on MacOS or thumbs.db on Windows.
While .gitignore is the most commonly used method for excluding files from Git, there are alternatives and complementary methods that provide additional flexibility for managing file tracking in your repositories. Each has its own use cases and can be combined based on your needs.
Copy code
# .git/info/exclude
*.log
temp/
git config --global core.excludesfile ~/.gitignore_global
# subdir/.gitignore
*.tmp
git clean -f -d
o -f forces the clean operation.
o -d removes untracked directories.