- Git
graph LR
A["Working Directory"] -->|git add| B0(["Staged Changes"])
B0 --> B[Index]
B -->|git commit| C[Commit history]
C -->|git push| D[Remote repository]
C -->|git checkout| A
- Index as the "staging area"
- The index file stores a list of every file at
HEAD
(git ls-files
)
Upstream branch is the default branch that your local branch is set to track. When you push changes to the remote repository, it will be pushed to the upstream branch.
Show list of all local branches and their upstream branches: git branch -vv
Linear:
* 3f6d2a5 (HEAD -> master) Add feature A
* 2c3b1d8 Update documentation
* 1a2b3c4 Initial commit
- Easier to read, easy to revert change, easy to identify bug
- Large number of commits, hard to identify source of the bugs
Non-linear:
* 3f6d2a5 (HEAD -> master) Merge branch 'feature-B'
|\
| * 2c3b1d8 (feature-B) Add feature B
* | 1a2b3c4 Add feature A
|/
* 0d4e5f6 Initial commit
- Can results in more organized history with complex featured project
- Merge conflicts
A fast-forward merge is a type of merge in Git that occurs when the target branch has no new commits since the branch was created. In this case, Git can simply move the pointer of the target branch to the latest commit of the source branch, resulting in a linear history.
If there are new commits on the target branch, Git will create a merge commit to combine the changes from both branches, resulting in a non-linear history.
A "pull" is used to fetch changes from a remote repository and merge them into the local branch. This is typically done when you want to update your local branch with changes that have been made by other developers on the same project.
A "merge" is used to combine two branches together. This is typically done when you have made changes to a local branch and want to incorporate those changes into another branch.
In GitHub, a pull request is a way to propose changes to a repository. It allows other developers to review the changes and provide feedback before they are merged into the main branch.
- Fork the repository you want to contribute to.
- Create a new branch for your changes.
- Make your changes and commit them to your branch.
- Open a pull request from your branch to the main branch of the original repository.
In GitLab, a merge request is similar to a pull request in GitHub. It allows developers to propose changes to a repository and have them reviewed before they are merged into the main branch. Here's an example of how to create a merge request in GitLab:
- Create a new branch for your changes.
- Make your changes and commit them to your branch.
- Open a merge request from your branch to the main branch of the repository.
To know what is HEAD~1
: git show HEAD~1
~
symbol is used to specify a number of commits before the current commit^
symbol is used to refer to the parent of a commit
$ git log --oneline
1e939ba (HEAD -> test1, origin/test1) test1 5 ammended more
2297eab test1 4
92fe48e test1 3
bf789d1 test2 branch
7d5ce04 Update Test1
HEAD
is "1e939ba"HEAD~1
is "2297eab"HEAD~2
is "92fe48e"HEAD~3
is "bf789d1"HEAD~2^
is "bf789d1" (which is the same asHEAD~3
)HEAD^
is "2297eab" (which is the same asHEAD~1
)
- Fetch all remote branches:
git fetch --all
(--prune
to remove deleted branches from local) - List all branches (local & remote):
git branch -a
- List all brances (only remote):
git branch -r
- Create new branch based on the current branch you are on:
git branch <new-feature>
- Create new branch based on another branch:
git branch <new-feature> <old-branch>
- Checkout:
git checkout <new-feature>
- Create new branch and switch to it:
git checkout -b <new-feature>
- Rename the current branch:
git branch -m <newname>
- To rename a branch while pointed to any branch:
git branch -m <oldname> <newname>
- Delete remote branch:
git push <remote_name> --delete <branch_name>
- Delete:
git branch -d <branch_name>
- if the branch has not been merged yet:
git branch -D <branch_name>
- Push local branch to remote:
git push -u origin <branch>
-u
,--set-upstream
: set the upstream branch for the current branch (When you push a branch to a remote repository for the first time, you need to specify the remote branch to push to.)
- Push all local changes:
git push --all <remote_name>
git push <remote_name> <local_branch_name>
if you have created new local branches that have not been pushed yet, you will need to push them individually using the git push <remote_name> <local_branch_name> command.
Setting rules for delete or force push to important branches. For example:
- Status checks: to pass before merging
- Pull request before merging
- Deployments to succeed before merging
- Rules (rulesets):
- Require pull request reviews before merging
# A---B---C topic
# /
# D---E---F---G master
git checkout topic
git rebase master
# A'--B'--C' topic
# /
# D---E---F---G master
###
# A---B---C topic
# /
# D---E---A'---F master
# A' and A introduce the same set of changes, but have different committer information
git checkout topic
git rebase master
# B'---C' topic
# /
# D---E---A'---F master
Rebase topic onto - on top of master. The result is a straight line of commits. Same as get rebased master topic
.
- Developing locally: rebase is safe if works have not been shared with others
- Code is ready for review: created pull request, others are reivewing. Do NOT rebase, create commits and update your feature branch.
- Review is done and ready to be integrated into the target branch: sanitize history, rewrite history, fold the commits into focussed commits.
Main goal: when a feature branch development is complete, rebase/squash all the work down to the minimum number of meaningful commits and avoid creating a merge commit
- Do you value more a clean, linear history? -> rebase.
- making sure the changes fast-forward
- or simply cherry-pick those commits into the target branch
- Rebasing doesn't play well with pull requests, because you can't see what minor changes someone made if they rebased
- Or the traceability of your branches? -> merge.
- History can become intensely polluted by lots of merge commits
- explicit with
--no-ff
, which forces git to record a merge commit in all cases
git checkout <feature-branch>
git rebase -i HEAD~4
- If conflict with upstream
git status
git add <file>
git rebase --continue
git push -u origin <feature-branch>
(akagit push --set-upstream origin <feature-branch>
) - if you never push your local branchgit push -f origin <feature-branch>
- if you already pushed your local branch
# o---o---o---o---o master
# \
# o---o---o---o---o next
# \
# o---o---o topic
git rebase --onto master next topic
# o---o---o---o---o master
# | \
# | o'--o'--o' topic
# \
# o---o---o---o---o next
# H---I---J topicB
# /
# E---F---G topicA
# /
# A---B---C---D master
git rebase --onto master topicA topicB
# H'--I'--J' topicB
# /
# | E---F---G topicA
# |/
# A---B---C---D master
# This is useful when topicB does not depend on topicA.
# E---F---G---H---I---J topicA
# 5 3
git rebase --onto topicA~5 topicA~3 topicA
# E---H'---I'---J' topicA
# removed F and G
or rebase B on top of A starting from the commit that is the parent of B referenced with B^ or B~1.
git checkout B
git rebase --onto A B^
Different between git rebase <branch>
and git rebase --onto <branch>
:
git rebase is going to rebase the branch you currently have checked out, referenced by HEAD, on top of the latest commit that is reachable from but not from HEAD.
git checkout <feature-branch>
git rebase main
git push -f origin <feature-branch>
Use the git rebase
command to rebase the branches onto the main branch. This will create a linear history by applying the changes from the branches onto the main branch, without creating merge commits.
git checkout main
git rebase feature-branch
# Repeat this process for each branch that you want to rebase onto the main branch.
After you've rebased all the branches onto the main branch, you can merge them using the --no-ff
flag (This will create a merge commit even if the changes can be fast-forwarded, resulting in a merge commit that preserves the branch history.)
git checkout main
git merge --no-ff feature-branch
# Repeat this process for each branch that you want to merge onto the main branch.
git pull --rebase
(simpler if there are no local commits)git pull --rebase=i
git reset origin/main --hard
Resets the index and working tree. Any changes to tracked files in the working tree since are discarded.
Alternatively, rename old branch and checkout the new one:
# To fetch changes from the remote repository into your local repo:
git fetch --all
# Rename the tracking branch
git branch -m <old-branch-name> <new-name>
# Create a new local branch tracking the remote branch:
git checkout <old-branch-name>
# rebase last 3 commits
git rebase -i HEAD~3
"Fast-forward merge is not possible. Rebase the source branch onto the target branch,"
- Source branch: feature-branch
- Target branch: main
- applying the changes from the source branch on top of the target branch.
# rebase feature-branch on(to) main
git checkout feature-branch
git rebase main -i
# make modification, fix conflicts, then
# to mark resolution
git add <file>
git rebase --continue
# then, force push
git push --force origin feature-branch
# finally, merge the feature branch into the main branch
git checkout main
git merge --no-ff feature-branch
- For unstaged files
git checkout -- <path/to/file>
: discard changes in working directory- for all files in current working directory use:
git checkout -- .
- or with newer git version:
git restore .
- for all files in current working directory use:
git reset HEAD <path/to/file>
: unstage files⚠️ git clean -df
: remove untracked files and directories
- Undo last commit (keep changes you made in the last commit, but under the commit itself):
git reset --soft HEAD~1
- Discard changes:
git reset --hard
- Move branch pointer:
git reset <commit_hash>
git reset
can also be used to restore the index, overlapping withgit restore
.
git reset --hard
is a classic command in this situation - but it will only discard changes in tracked files (i.e. files that already are under version control).
To get rid of new / untracked files, you'll have to use git clean
git clean -n
: dry run (i.e. see what would be deleted using the command without actually doing it)git clean -f
: force (i.e. delete untracked files even if they are ignored in.gitignore
)git clean -df
: delete untracked files and directoriesgit clean -xdf
: delete untracked files and directories, including those ignored in.gitignore
git reset --hard
# git checkout all tracked files to HEAD while keeping untracked files
git checkout -- .
git fetch origin
git reset --hard origin/master
git reset
git push <remote_name> <branch_name>
git push --all <remote_name>
git push --tags <remote_name>
git push --delete <remote_name> <branch_name>
git push --force <remote_name> <branch_name>
: force push changes to remote branch and overwrite any changes that may have been made on the remote branch since your last pull.- this is needed for
rebase
: because rebase create new commit history.
- this is needed for
Save dirty changes and switch to work on something else
git stash
: save changes to a temporary areagit stash --keep-index
: include all staged content and leave it in the indexgit stash save "message"
: add a message to the stashgit stash branch <branch_name>
: create a new branch from the stash
git stash list
: list all stashesgit stash show
: show the last stash
git stash apply
: apply the last stashgit stash apply stash@{1}
: apply a specific stashgit stash apply --index
: apply the last stash and keep staged changes
git stash drop
: remove the last stashgit stash drop stash@{1}
: remove a specific stashgit stash pop stash@{1}
: apply a specific stash and remove it from the stash list
- Remove file from git:
git rm <path/to/file>
git update-index --assume-unchanged [<file>...]
git reset --soft HEAD <path/to/file>
: unstage filesgit rm --cached -r directory-name
git clean -xdf
: remove untracked and ignored files and directoriesgit clean -xf
: remove only files
git log
: only show history of current branch- Show history from all branches (local & remote):
git log --oneline --graph --decorate --all
--stat
: statistics of changes (number of lines added/removed)--author=<name>
: filter by author--grep=<pattern>
: filter by commit message (e.g.--grep="fix"
)
- Generate the commit graph:
git log --graph --abbrev-commit --decorate --date=relative --all > graph.txt
git show HEAD~1
git show <commit_hash> --name-only
git show <commit_hash>:/path/to/file
git show <commit_hash>..HEAD
: compare two commitsgit show --format='%H' HEAD~1
: show only the commit hash
git log --follow -p <path>
: commit history of a single file--flow
: continue listing the history of a file beyond renames (works only for a single file)-p
: show the diff introduced in each commitgit log --follow -p -- <path1> <path2> <dir>
: for multiple files and/or directory
- add all changes:
git add -A
- commit:
git commit --amend -m "message"
git commit --amend --reset-author
: reset author of the last commit
This can cause issues if the previous commit has already been pushed to a remote repository and other developers have pulled from it.
-
git config --global user.name "John Doe"
-
git config --global user.email "trung@email.com"
-
git config --global core.editor "code --wait"
git config --global core.editor "nano"
-
git config --global alias.delete-branch 'push --delete'
git delete-branch <remote_name> <branch_name>
-
git config push.default <value>
- 'matching': push all branches that have the same name on both ends
- 'simple': push the current branch to a branch of the same name on the remote
- 'upstream': push the current branch to its upstream branch
- 'current': push the current branch to a branch of the same name on the remote
-
git config --get push.default
-
git commit --amend --reset-author
: reset author of the last commit
git config user.name "Trung Ngo"
git config user.email "trung.ngo@nokia.com"
To check line ending of a file from terminal: file -b --mime-encoding <path/to/file>
us-ascii
: Unix/linux line endings (LF)utf-16le
: Windows (CRLF)utf-8
: MacOS (CR)
Use git config core.autocrlf input
: ensures that Git will convert all text files to LF line endings when committing
git config --global core.autocrlf true
: For compatibility, line endings are converted to Unix style when you commit files (globally)
git config --global --unset http.proxy
git config --global --unset http.https://domain.com.proxy
git config --global --unset http.sslVerify
git config --global --unset http.https://domain.com.sslVerify
git config --global http.proxy http://proxyuser:proxypwd@proxyhost:proxyport
git config --global https.proxy http://proxyuser:proxypwd@proxyhost:proxyport
Work with http/https: update ~/.gitconfig
[http]
[http "https://github.com"]
proxy = http://proxyUsername:proxyPassword@proxy.server.com:port
sslVerify = false
Work with ssh config: So I have to set ssh config (~/.ssh/config
) with ProxyCommand properly, git clone start working with proxy.
Host github.com
Hostname github.com
ServerAliveInterval 55
ForwardAgent yes
ProxyCommand /usr/bin/corkscrew <replace_with_your_company_proxy_server> <3128> %h %p ~/.ssh/myauth
The config file is stored at cat ~/.gitconfig
git config --list
When you use mv to move a file, Git will not recognize that the file has been moved. Instead, it will treat it as if you deleted the old file and created a new one with the same content in a different location
git mv <source> <destination>
If you've already used mv to move a file and want to tell Git about the move, you can use the git add command to stage the changes and then use git rm to remove the old file:
mv oldfile newfile
git add newfile
git rm oldfile
git-copy-file-preserving-history
#!/bin/bash
if [[ $# -ne 2 ]] ; then
echo "Usage: git-split.sh original copy"
exit 0
fi
git mv $1 $2
git commit -n -m "Split history $1 to $2"
REV=`git rev-parse HEAD`
git reset --hard HEAD^
git mv $1 temp
git commit -n -m "Split history $1 to $2"
git merge $REV # --no-ff
git commit -a -n -m "Split history $1 to $2"
git mv temp $1
git commit -n -m "Split history $1 to $2"
#!/bin/bash
git checkout -b fruits
git mv foods fruits
git commit --author="Greg <greg>" -am "split foods to fruits"
git checkout -
git checkout -b veggies
git mv foods veggies
git commit --author="Greg <greg>" -am "split foods to veggies"
git checkout -
git mv foods dairy
git commit --author="Greg <greg>" -m "split foods to dairy"
git merge fruits veggies
git tag <tag_name>
: create a taggit tag -a <tag_name> -m "message"
: create an annotated taggit tag -a <tag_name> <commit_hash>
: create a tag for a specific commit
git tag -l
: list all tagsgit tag -d <tag_name>
: delete a tag
git push origin <tag_name>
: push a tag to remotegit push origin --tags
: push all tags to remote
git push -d origin <tag_name>
: delete a tag from remote
- Delete remote tag:
git push --delete origin <tag_name>
- Delete local tag:
git tag -d <tag_name>
- Create a new tag:
git tag <tag_name>
- Push the new tag:
git push origin <tag_name>
git checkout <tag_name>
: checkout a tag in a detached HEAD stategit checkout -b <branch_name> <tag_name>
: checkout a tag in a new branch
If encounter "git lfs is not a command" error, try to install git lfs again
brew install git-lfs
sudo apt install git-lfs
To use Git LFS:
git lfs install
: install git lfsgit lfs track "*.psd"
: track a file typegit lfs track dump.sql
: track a filegit lfs track
: list all tracked filesgit lfs migrate import --include="*.sql"
: migrate all sql files to git lfs
Accidently track too big file, "File dump_070923.sql is 137.97 MB; this exceeds GitHub's file size limit of 100.00 MB"
git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch dump_070923.sql' --prune-empty --tag-name-filter cat -- --all
: remove a file from git history
Sample git hooks files are located at .git/hooks/
. https://pre-commit.com/
This is bash script file to be executed before commiting.
- Install:
pip install pre-commit
- Create :
.pre-commit-config.yaml
file in the root of the project - Update :
pre-commit autoupdate
- use
rev: stable
in the config file - use
rev: master
for lastest version
- use
- Testing how it works:
pre-commit run -a --skip flake8,black
pre-commit run black --all-files
pre-commit install
: runs every time you commitpre-commit install --hook-type pre-push
: runs every time you push- How to prevent running it: using git options
--no-verify
or-n
git commit -n -m "YOUR COMMIT MESSAGE"
- Uninstall:
pre-commit uninstall
It is important to run the hooks against all of the files when adding new hooks (
pre-commit
only run on the changed files during git hooks)
The list of all availalbe hooks: `https://pre-commit.com/hooks.html
ci:
autoupdate_commit_msg: "chore: update pre-commit hooks"
autofix_commit_msg: "style: pre-commit fixes"
fail_fast: True
default_stages: [pre-commit, commit, pre-push, push]
repos:
# Standard hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: stable
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-merge-conflict
- id: check-symlinks
- id: check-yaml
exclude: (^conda\.recipe/meta\.yaml$|ISSUE_TEMPLATE)
- id: debug-statements
- id: mixed-line-ending
- id: requirements-txt-fixer
- id: end-of-file-fixer
stages: [push]
- id: trailing-whitespace
stages: [push]
# Shell
- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 3.0.0
hooks:
- id: shellcheck
- id: shfmt
# Runs Black, pyupgrade, isort, autoflake, blacken-docs
- repo: https://github.com/Zac-HD/shed
rev: 2023.3.1
hooks:
- id: shed
exclude: ^(docs)
# Changes tabs to spaces
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.4.2
hooks:
- id: remove-tabs
exclude: ^(docs)|.*\.cxx
# CMake formatting
- repo: https://github.com/cheshirekow/cmake-format-precommit
rev: v0.6.13
hooks:
- id: cmake-format
additional_dependencies: [pyyaml]
types: [file]
files: (\.cmake|CMakeLists.txt)(.in)?$
# Runs Black
- repo: https://github.com/psf/black
rev: 23.9.1
hooks:
- id: black
language_version: python3.8
args: [--target-version=py38, --line-length=100, --preview]
exclude: ^(.*/)?\..*\.pyi?$
# Runs isort
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
args: [--profile=black]
# Run the Ruff linter.
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.2
hooks:
# Run the Ruff linter.
- id: ruff
# Run the Ruff formatter.
- id: ruff-format
job:
script: echo "Hello, Rules!"
rules:
- if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/ && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME != $CI_DEFAULT_BRANCH
when: never
- if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/
when: manual
allow_failure: true
- if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
Here's the text converted to a Markdown table:
Commit variables:
CI_COMMIT_DESCRIPTION
: The description of the commit. If the title is shorter than 100 characters, the message without the first line.CI_COMMIT_MESSAGE
: The full commit message.CI_COMMIT_TITLE
all The title of the commit. The full first line of the message
CI_MERGE_REQUEST_TITLE
: The title of the merge request.CI_MERGE_REQUEST_LABELS
: Comma-separated label names of the merge request.CI_MERGE_REQUEST_MILESTONE
: The milestone title of the merge request.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
:=~ /^.*onnx.*$/
Variable | Introduced in | Description |
---|---|---|
CI_MERGE_REQUEST_APPROVED | 14.1 | Approval status of the merge request. true when merge request approvals is available and the merge request has been approved. |
CI_MERGE_REQUEST_ASSIGNEES | 11.9 | Comma-separated list of usernames of assignees for the merge request. |
CI_MERGE_REQUEST_ID | 11.6 | The instance-level ID of the merge request. This is a unique ID across all projects on GitLab. |
CI_MERGE_REQUEST_IID | 11.6 | The project-level IID (internal ID) of the merge request. This ID is unique for the current project. |
CI_MERGE_REQUEST_LABELS | 11.9 | Comma-separated label names of the merge request. |
CI_MERGE_REQUEST_MILESTONE | 11.9 | The milestone title of the merge request. |
CI_MERGE_REQUEST_PROJECT_ID | 11.6 | The ID of the project of the merge request. |
CI_MERGE_REQUEST_PROJECT_PATH | 11.6 | The path of the project of the merge request. For example namespace/awesome-project. |
CI_MERGE_REQUEST_PROJECT_URL | 11.6 | The URL of the project of the merge request. For example, http://192.168.10.15:3000/namespace/awesome-project. |
CI_MERGE_REQUEST_REF_PATH | 11.6 | The ref path of the merge request. For example, refs/merge-requests/1/head. |
CI_MERGE_REQUEST_SOURCE_BRANCH_NAME | 11.6 | The source branch name of the merge request. |
CI_MERGE_REQUEST_SOURCE_BRANCH_SHA | 11.9 | The HEAD SHA of the source branch of the merge request. The variable is empty in merge request pipelines. The SHA is present only in merged results pipelines. |
CI_MERGE_REQUEST_SOURCE_PROJECT_ID | 11.6 | The ID of the source project of the merge request. |
CI_MERGE_REQUEST_SOURCE_PROJECT_PATH | 11.6 | The path of the source project of the merge request. |
CI_MERGE_REQUEST_SOURCE_PROJECT_URL | 11.6 | The URL of the source project of the merge request. |
CI_MERGE_REQUEST_TARGET_BRANCH_NAME | 11.6 | The target branch name of the merge request. |
CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED | 15.2 | The protection status for the target branch of the merge request. |
CI_MERGE_REQUEST_TARGET_BRANCH_SHA | 11.9 | The HEAD SHA of the target branch of the merge request. The variable is empty in merge request pipelines. The SHA is present only in merged results pipelines. |
CI_MERGE_REQUEST_TITLE | 11.9 | The title of the merge request. |
CI_MERGE_REQUEST |