d6a96ae3b442218a91512b9e1c57b9578b487a0b
Urs Roesch
Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.
Source https://git-scm.com/
The course material is targeted at technically inclined person with a high Unix proficiency and an interest of peaking under the hood bug had very little or no exposure to Git yet.
For most part of this tutorial we stay within the realm of porcelain
.
Term | Description |
---|---|
| Low-level toolkit commands. |
| User-friendly front end commands. |
Most git commands are performed locally. Compared to the CVCS (Centralizes Version Control Systems) such as Subversion, git is blazingly fast. Git always retains the whole repository with history locally. Only when sending changes to a remote site is network latency coming into play.
The 40 character long hex encoded SHA1 checksums are popping up all over when working with git as they are used universally.
d6a96ae3b442218a91512b9e1c57b9578b487a0b
Git only works with files. Directories without a single file are not retained in git. Also files with the same content are only stored once and then referenced.
Stage | Description |
---|---|
committed | The content of the file is safely stored in the repository. |
staged | A modified file is marked for inclusion with the next commit. |
modified | A file git is aware of has been modified but is not commited to the repository yet. |
Understanding the relations between commits is essential for understanding more advanced topics like branching, rebasing and resetting among others. But it also helps with troubleshooting issues. |
The repository of every git project is stored in the root of the working
directory under the .git
directory. This provides a small overview of the
content within the directory.
.git
directory after initializationFor a more in depth description of the repository layout consult the
gitrepository-layout manual page or read it
online. |
There are 4 types of git objects stored within the objects
directory. Namely:
Commits, Trees, Blobs and Tags
Git is very well documented and comes with extensive help accessible from the command line. There are a few ways getting help with a git command.
To list all available porcelain commands git --help
or git help
is used.
To show more help for the git init
command one can either use man git-init
,
git help init
or git init --help
. They all open the Unix man
page for the
topic at hand.
git init --TabTab
--bare --no-... --no-template --quiet
--separate-git-dir= --shared --template=
The comprehensive documentation is also available on the net via the official git website’s documentation section.
Show configuration values.
Set contact information.
Differentiate system, global and local configurations
Useful configuration options.
With recent versions of git a base configuration for the
user’s email and full name is required. |
$ git config --list
core.repositoryformatversion=0 (1)
core.filemode=true (2)
core.bare=false (3)
core.logallrefupdates=true (4)
$ git config --global --add user.name "Urs Roesch" (1)
$ git config --global --add user.email "****@bun.ch" (2)
$ git config --list
user.email=****@bun.ch
user.name=Urs Roesch
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
system | System wide configuration located usually under |
global | User wide configuration located under |
local | Repository only configuration under |
$ cat ~/.gitconfig
[user]
email = ****@bun.ch
name = Urs Roesch
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
global
and local
configuration with no overlap.local
overriding user.email
.These are basic configuration settings in git but they can make a large difference in the overall user experience.
git config --global core.editor emacs (1)
1 | Now git will use Emacs every time interactive input is required regardless of the systems editor settings. |
git config --global core.pager '' (1)
1 | Switches off the pager altogheter. |
git config --global color.ui false (1)
1 | Switch off all colored output. The default is
auto probing for terminal color support. A
value of always send color to the terminal
regardless of capabilities. |
$ git config --list color.TabTab
color.advice
color.advice.hint
color.blame.highlightRecent
color.blame.repeatedLines
color.branch
...
git config --local credential.https://git.bun.ch/repo/foobar.git urs.roesch (1)
1 | Sets the username of the URL https://git.bun.ch/repo/foobar.git to
urs.roesch . |
git config --global credential.helper 'cache --timeout=86400' (1)
1 | Caches the HTTPS credentials for 24 hours. |
Create a new git repository.
Create and stage files.
Commit files.
View the status of the repository.
Display the log.
Excluding files from Git.
As already mentioned under location for the most part Git operates on local storage. In order to create our first repository the only thing required is the git binary. Starting with a clean slate the only other requirement is an empty directory.
Git commands always start with git followed by a command, in
this case init , then the arguments. To get more information about
a git command use git help <command> . |
$ mkdir git-repo
$ cd git-repo
$ git init (1)
Initialized empty Git repository in .../git-repo/.git/
Want to know what exactly what git put into the .git directory have a
look at the content of the initial tree |
$ echo "This is my first file in a git repo" > first-file.txt (1)
$ git status (2)
On branch master (3)
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
first-file.txt
nothing added to commit but untracked files present (use "git add" to track) (4)
$ git add . (5)
# git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage) (6)
new file: first-file.txt (7)
With files in staging it is time to actually put them into the repository we take now a snapshot of the repository.
$ git commit -m "My first git commit" (1)
[master (root-commit) 307c1a0] My first git commit (2)
1 file changed, 1 insertion(+) (3)
create mode 100644 first-file.txt (4)
$ git status
On branch master
nothing to commit, working tree clean (5)
After commiting the first file one should know how to display the commit
history or log
. There is a multitude of options that can be used to
customize the output. While showing every option is out of scope a few
very useful ones are shown. Additionally the show
command used for
inspecting objects is briefly discussed.
$ git log
commit 307c1a0a537758f3d4b6ecea98e9af2e5d0b7b88 (HEAD -> master) (1)
Author: Urs Roesch <****@bun.ch> (2)
Date: Sun Aug 26 12:09:57 2018 +0200 (3)
My first git commit (4)
With only one commit in the repository there is only a single entry shown.
As the repository accumulates commits the number of log
entries is also
growing.
To show what files are part of the commit the --stat
argument
is used.
$ git log --stat
commit 307c1a0a537758f3d4b6ecea98e9af2e5d0b7b88 (HEAD -> master)
Author: Urs Roesch <urs++git@bun.ch>
Date: Sun Aug 26 12:09:57 2018 +0200
My first git commit
first-file.txt | 1 + (1)
1 file changed, 1 insertion(+) (2)
While not really useful at this stage in the project with many commits one can squeeze the log output into a single line.
$ git log --oneline
307c1a0 (HEAD -> master) My first git commit (1)
$ git show
commit 307c1a0a537758f3d4b6ecea98e9af2e5d0b7b88 (HEAD -> master)
Author: Urs Roesch <urs++git@bun.ch>
Date: Sun Aug 26 12:09:57 2018 +0200
My first git commit
diff --git a/first-file.txt b/first-file.txt (1)
new file mode 100644
index 0000000..e7e0b37
--- /dev/null
+++ b/first-file.txt
@@ -0,0 +1 @@
+This is my first file in a git repo
The --stat
switch does only display statistics and skips the diff output.
$ git show --stat (1)
commit 307c1a0a537758f3d4b6ecea98e9af2e5d0b7b88 (HEAD -> master)
Author: Urs Roesch <urs++git@bun.ch>
Date: Sun Aug 26 12:09:57 2018 +0200
My first git commit
first-file.txt | 1 +
1 file changed, 1 insertion(+)
Depending on the project there are files that should not be included in the repository as they can be reproduced by a build script. Or temporary editor file that try to sneak into the repository.
$ mkdir tmp
$ echo temporary > tmp/tmp.txt (1)
$ git status --short
?? tmp/ (2)
echo tmp > .gitignore (3)
git status --short
?? .gitignore (4)
While not strictly necessary it usually makes sense to include the
.gitignore file in the repository. |
$ git add .gitignore (1)
$ git commit -m "Excluding files with .gitignore" (2)
[master 33d3825] Excluding files with .gitignore
1 file changed, 1 insertion(+)
create mode 100644 .gitignore
$ git log --oneline (3)
33d3825 (HEAD -> master) Excluding files with .gitignore
307c1a0 My first git commit
Download or clone
a remote repository.
Add content to the repository.
Learn about origin
.
Push the changes.
Pull changes made outside of the working directory.
Creating a bare repo is an advanced topic but for the purpose of
working with a local shared bare repository it is included.
To distinguish bare repositories from local ones the naming
convention is to suffix it with .git
.
$ git init --shared --bare remote-repo.git (1) (2)
Initialized empty shared Git repository in .../remote-repo.git/
To create a local copy of a remote or shared repository the
clone
command is used.
$ git clone remote-repo.git (1) (2)
Cloning into 'remote-repo'...
warning: You appear to have cloned an empty repository.
done.
$ git config --list
user.email=urs++git@bun.ch
user.name=Urs Roesch
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
remote.origin.url=.../remote-repo.git (1)
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/* (2)
branch.master.remote=origin (3)
branch.master.merge=refs/heads/master (4)
A clear understanding of how this mechanism works is key to comprehend how git works with shared repositories over the network. |
$ echo 'First remote repository content!' > first_file.txt (1)
$ git add first_file.txt (2)
$ git commit -m 'Initial commit' (3)
[master (root-commit) 45d5290] Initial commit
1 file changed, 1 insertion(+)
create mode 100644 first_file.txt
$ git log --oneline
45d5290 (HEAD -> master) Initial commit
This is the same as for the local repository. As all the actions are done locally there is no difference if you work with a remote repository. |
With the commit in place the changes have to be pushed to the remote
repository. This is accomplished with git push
.
$ git push
Counting objects: 3, done. (1)
Writing objects: 100% (3/3), 250 bytes | 250.00 KiB/s, done. (2)
Total 3 (delta 0), reused 0 (delta 0)
To .../remote-repo.git (3)
* [new branch] master -> master (4)
$ git fetch
remote: Counting objects: 3, done. (1)
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From .../remote-repo
45d5290..885f6c7 master -> origin/master (2)
$ ls
first_file.txt (1)
$ git log --oneline
45d5290 (HEAD -> master) Initial commit (2)
$ git log origin/master (1)
commit 885f6c751abb430cb48c0903fcd82a2d35a77d25 (origin/master) (2)
Author: Urs Roesch <urs++git@bun.ch>
Date: Thu Sep 6 06:57:50 2018 +0200
Second commit
commit 45d52900b73a5bd461cbaef2652b9d1ed8220b3b (HEAD -> master) (3)
Author: Urs Roesch <urs++git@bun.ch>
Date: Thu Sep 6 06:37:46 2018 +0200
Initial commit
To bring the changed from the remote master branch into the working
directory the merge
command is used.
$ git merge origin/master (1)
Updating 45d5290..885f6c7
Fast-forward
second_file.txt | 1 + (2)
1 file changed, 1 insertion(+)
create mode 100644 second_file.txt
$ git log
commit 885f6c751abb430cb48c0903fcd82a2d35a77d25 (HEAD -> master, origin/master) (3)
Author: Urs Roesch <urs++git@bun.ch>
Date: Thu Sep 6 06:57:50 2018 +0200
Second commit
commit 45d52900b73a5bd461cbaef2652b9d1ed8220b3b
Author: Urs Roesch <urs++git@bun.ch>
Date: Thu Sep 6 06:37:46 2018 +0200
Initial commit
$ git log origin/master
commit 45d52900b73a5bd461cbaef2652b9d1ed8220b3b (HEAD -> master, origin/master)
Author: Urs Roesch <urs++git@bun.ch>
Date: Thu Sep 6 06:37:46 2018 +0200
Initial commit
$ git pull
remote: Counting objects: 3, done. (1)
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From .../remote-repo
45d5290..885f6c7 master -> origin/master
Updating 45d5290..885f6c7
Fast-forward (2)
second_file.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 second_file.txt
Use of the diff
command.
Use blame
to find who’s done what change.
Find strings in the tree with grep
.
When working with a VCS one of the most important daily tasks is to evaluate changes made during the course of time. Git provides a variety of tools to get the job done.
$ echo 'second line in first file' >> first_file.txt (1)
$ cat first_file.txt (2)
First remote repository content!
second line in first file
$ git diff (3)
diff --git a/first_file.txt b/first_file.txt
index 697ffc4..f79ba9f 100644
--- a/first_file.txt
+++ b/first_file.txt
@@ -1 +1,2 @@
First remote repository content!
+second line in first file
As mentioned basic invocation of git diff
only tracks changes in the
working tree. To see changes of staged files more options are required.
$ git add first_file.txt (1)
$ git diff (2)
$ git diff --cached (3)
diff --git a/first_file.txt b/first_file.txt
index 697ffc4..f79ba9f 100644
--- a/first_file.txt
+++ b/first_file.txt
@@ -1 +1,2 @@
First remote repository content!
+second line in first file
Spinning this thread a bit further showing differences in a already commited file requires a reference to a commit.
$ git commit -m "Second line in first file" (1)
[master 3fb25c5] Second line in first file
1 file changed, 1 insertion(+)
$
urs@automatix:~/var/work/git-tutorial/remote-repo$ git diff HEAD~1 (2)
diff --git a/first_file.txt b/first_file.txt
index 697ffc4..f79ba9f 100644
--- a/first_file.txt
+++ b/first_file.txt
@@ -1 +1,2 @@
First remote repository content!
+second line in first file
To show only the summary of changes to the file one can use the --stat
parameter already shown in previous commands such as log
or show
.
$ git diff --stat HEAD~2 (1)
first_file.txt | 1 +
second_file.txt | 1 +
2 files changed, 2 insertions(+)
Where diff really shines tho is showing changes to a single file only.
$ git diff HEAD~2 second_file.txt (1)
diff --git a/second_file.txt b/second_file.txt
new file mode 100644
index 0000000..20d5b67
--- /dev/null
+++ b/second_file.txt
@@ -0,0 +1 @@
+Second file
Sometimes it is important to know who changed what. For example when questions arise why a change was commited or why something was implemented a certain way. And git has a tool ready for that too.
$ git blame first-file.txt
^8070030 (Urs Roesch 2018-10-06 13:06:01 +0000 1) First file (1)
2e97d380 (Linux Torvalds 2019-12-25 08:07:28 +0000 2) Second line (2)
e5149955 (Junio C Hamano 2020-01-04 11:08:45 +0000 3) Third line (3)
e5149955 (Junio C Hamano 2020-01-04 11:08:45 +0000 4) Fourth line
e5149955 (Junio C Hamano 2020-01-04 11:08:45 +0000 5) Fifth line
00000000 (Not Committed Yet 2020-10-03 09:10:49 +0000 6) (4)
00000000 (Not Committed Yet 2020-10-03 09:10:49 +0000 7)
00000000 (Not Committed Yet 2020-10-03 09:10:49 +0000 8)
00000000 (Not Committed Yet 2020-10-03 09:10:49 +0000 9)
00000000 (Not Committed Yet 2020-10-03 09:10:49 +0000 10) Apologies, I lost count
As with any git command there is a slew of additional options a few of the more useful ones examined in the next few examples. |
When only wanting to review changes starting at a particular revision the SHA1 can be provided followed by two dots.
$ git blame 2e97d380.. -- first-file.txt
^2e97d38 (Linux Torvalds 2019-12-25 08:07:28 +0000 1) First file (1)
^2e97d38 (Linux Torvalds 2019-12-25 08:07:28 +0000 2) Second line
e5149955 (Junio C Hamano 2020-01-04 11:08:45 +0000 3) Third line (2)
e5149955 (Junio C Hamano 2020-01-04 11:08:45 +0000 4) Fourth line
e5149955 (Junio C Hamano 2020-01-04 11:08:45 +0000 5) Fifth line (3)
While the previous example shows changes between a commit and the current
HEAD
the same can be done between two arbitrary revisions.
$ git blame 80700303..2e97d380 -- first-file.txt
^8070030 (Urs Roesch 2018-10-06 13:06:01 +0000 1) First file (1)
2e97d380 (Linux Torvalds 2019-12-25 08:07:28 +0000 2) Second line (2)
There is also the option to view all changes up to certain revision. This is achieved by two dots followed by the revision hash.
$ git blame ..2e97d380 -- first-file.txt
^8070030 (Urs Roesch 2018-10-06 13:06:01 +0000 1) First file (1)
2e97d380 (Linux Torvalds 2019-12-25 08:07:28 +0000 2) Second line (2)
Instead of the fickle SHA1 hashes one can use tags e.g.
git blame v2.2.0.. — first-file.txt or the switch --since e.g.
git blame --since=3.weeks — first-file.txt |
Another options is for limiting the comparision to a range of lines
in the file with the switch -L
.
$ git blame -L 2,4 first-file.txt
2e97d380 (Linux Torvalds 2019-12-25 08:07:28 +0000 2) Second line (1)
e5149955 (Junio C Hamano 2020-01-04 11:08:45 +0000 3) Third line
e5149955 (Junio C Hamano 2020-01-04 11:08:45 +0000 4) Fourth line (2)
Locating certain text patterns in a large repository can be daunting. But
git grep
provides most of the options familiar from the Unix command grep
tailored to work under the specifics of a repository.
$ cat first-file.txt
First file
Second line
Third line
Fourth line
Fifth line
$ git grep First
first-file.txt:First file (1)
There is a way to limit the search to files matching a name pattern,
not unlike normal grep
. However the syntax is slightly different as
wildcards are required to be properly escaped.
$ cp first-file.txt first-file.copy (1)
$ git add first-file.copy (2)
$ git grep First
first-file.txt:First file
first-file.copy:First file (3)
$ git grep First -- '*.txt' (4)
first-file.txt:First file (5)
There is a way to include untracked files in the search by adding the
--untracked
switch.
$ echo "Second file" > second-file.txt (1)
$ git grep --untracked Second (2)
second-file.txt:Second file (3)
Descending into a sub directory within the git tree limits the search to the files under said sub directory. |
List tags with tag
.
Create tags with tag <name>
.
Remove tags with tag -d
.
Push tags to remote repositories using push --tags
Delete tags in remote repositories.
Tags have their own git command aptly named tag
. Issuing tag
without any options list all defined tags. The list of options
for tag
at least for lightweight and annotated tags is
comparatively small.
$ git tag (1)
v1.0.0 (2)
v1.1.0
Creating tags for the current revision is very straight forward. The only additional option is to provide a name for the tag.
$ git tag v1.3.0 (1)
v1.3.0
to HEAD
.$ git tag v1.2.0 64b67952 (1)
1.2.0
was added to a revision prior to HEAD
.$ git tag -a milestone_1 -m "This is the first milestone" (1)
$ git show milestone_1 (2)
tag milestone_1 (3)
Tagger: Urs Roesch <Urs Roesch>
Date: Sun Nov 8 17:17:17 2020 +0100
This is our first milestone (4)
commit 45a6088dd900e5363dddbb656360661adb94c1a1 (HEAD -> production, tag: milestone_1) (5)
Author: Urs Roesch <Urs Roesch>
Date: Sun Nov 8 10:47:36 2020 +0100
first-file: New milestone reached
When pushing changes to a remote repository tags are not being transmitted. Some automated workflows rely heavily on tags. In this rather short module the tat
$ git push --tags (1)
Enumerating objects: 9, done. (2)
Counting objects: 100% (9/9), done.
Delta compression using up to 8 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 748 bytes | 748.00 KiB/s, done.
Total 7 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/octocatterpillar/git-tutorial.git
* [new tag] milestone_1 -> milestone_1 (3)
There is an options for push called --follow-tags which can
be provided during a normal push. Said option will only push
annotated tags! |
Deleting tags is as easy as creating them. Simply provide the
-d
switch before the tag name.
There is deletion of a lightweight tag and an annotated tag. Although they look exactly the same on surface the difference is shown in the figure to each command.
Tags deleted locally which have already been pushed to a
remote repository will be downloaded again with the next
fetch . |
$ git tag -d v1.1.0 (1)
v1.1.0
.$ git tag -d v1.1.0 (1)
Show branches in repository with branch
.
Create new branches with checkout -b
.
Delete branches with branch -d
or branch -D
Use rebase
to keep branches up to date.
Rename branches with branch -m
.
Solve merge conflicts.
Per default with git init
a branch named master
is created.
The master
branch is just a convenience name pointing to a
SHA1 revision.
The master branch can be renamed or deleted as it does not have any relevance to git. It is just an alias. |
$ git branch
* master (1)
master
pointing to revision ccc..
.One of the stated goals of the git creator Linus Torvalds was to make branching as cheap and easy as possible. When working with large repositories and many contributors branching is a necessity. But with a good understanding and a bit of practice it quickly become second nature.
$ git branch testing (1)
$ git branch
* master (2)
testing
$ git checkout testing (3)
Switch to branch 'testing'
$ git branch
* master (4)
testing
Branch names can contain the Unix directory separator / for
grouping changes e.g. bugfix/ticket-123 or release/v1.2.3 . |
There is a shortcut for creating and switching to the newly minted
branch all at once. The command git checkout -b <branchname> will
achieve the same as git branch <branchname> followed by
git checkout <branchname> . |
master
and testing
pointing to the same revision.$ echo 'Branched file' > branched-file.txt (1)
$ git add branched-file.txt
$ git commit -a -m "First file under branch testing" (2)
[testing 4b9b86d] First file under branch testing
1 file changed, 1 insertion(+)
create mode 100644 branched-file.txt
testing
pointer moved to the new commit.For this section the assumption is made that that there is a bug in the
current master
branch that has not been addressed under testing
as it is used to develop new features. To fix the bug a new branch
is created called bugfix
starting out with the same revision as
master
.
$ git checkout master
Switched to branch 'master'
$ git branch
* master (1)
testing
Since git version 2.23 the switch command can be used instead of
checkout . To create a new branch git switch --create <branch name> is
used. The short option for --create is -c . With git switch - one can
toggle between current and last used branch. This is not unlike cd -
under the Bash shell. |
master
the HEAD
is now again at commit ccc…
.$ git checkout -b bugfix (1)
Switched to a new branch 'bugfix'
$ git branch
* bugfix (2)
master
testing
bugfix
it points to the same revision as master
.$ git diff
diff --git a/first-file.txt b/first-file.txt
index 4c5fd91..aa24abd 100644
--- a/first-file.txt
+++ b/first-file.txt
@@ -1 +1 @@
-First file
+First file with bugfix (1)
$ git commit -a -m "Bugfix for first file"
[bugfix a27a927] Bugfix for first file
1 file changed, 1 insertion(+), 1 deletion(-)
bugfix
the branches start diverging.With the bug fix in place the task is to merge it back into the master branch So other users could can use it as well. Assuming the master branch is then pushed to a remote repository that is.
$ git checkout master (1)
Switched to branch 'master'
$ git branch
* master (2)
testing
$ git merge bugfix (3)
Updating e303af7..a27a927
Fast-forward
first-file.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
bugfix
and master
point to the same revision.$ git branch
bugfix
* master (1)
testing
$ git branch -d bugfix (2)
Deleted branch bugfix (was a27a927).
$ git log --oneline -n 1
a27a927 (HEAD -> master) Bugfix for first file (3)
bugfix
branch deleted only master
and testing
remains.With the bug fix merged into branch master
the next logical
step is to fold the changes into the testing branch to ensure
the next release does include the fixed version.
When working with multiple branches this operation is required
to not fall back to far with master
and preventing lots of
merge conflicts.
$ git branch (1)
* master
testing
$ git checkout testing (2)
Switched to branch 'testing'
$ git rebase master (3)
Successfully rebased and updated refs/heads/testing. (4)
Conducting a rebase between two branches requires a common ancestor in the tree. |
master
and testing
are again in sync.As learned earlier branches are just arbitrary names pointing to a revision within the git tree. As such renaming is a very painless and fast operation. No files have to be copied just the reference requires an update.
$ git branch (1)
* master
testing
$ git checkout testing (2)
Switched to branch 'testing'
$ git branch -m production (3)
$ git branch
master
* production (4)
production
.A merge conflict is a situation where the same file has been modified
by one or more person at the same location in the file.
Generally git does an excellent job working around merge conflicts.
But occasionally during merge
or rebase
operations git interrupts
and requires human intervention to solve a conflict between two
revisions.
As a general rule many merge conflicts can be prevented or minimized by:
Communicating changes between team members regularly.
Regular rebases with the merge target branch.
Creating small and atomic commits.
first-file.txt
under branch master
.$ git checkout master (1)
Switched to branch 'master'
$ vi first-file.txt (2)
$ cat first-file.txt
First file with bugfix from branch "master" (3)
$ git commit -m "first-file: Add from 'master'" first-file.txt (4)
[master 64b6795] first-file: Add from 'master'
1 file changed, 1 insertion(+), 1 deletion(-)
first-file.txt
under branch production
.$ git checkout production (1)
Switched to branch 'production'
$ vi first-file.txt (2)
$ cat first-file.txt (3)
First file with bugfix from branch "production"
$ git commit -m "first-file: Add from 'production'" first-file.txt (4)
[production bdfdc4a] first-file: Add from 'production'
1 file changed, 1 insertion(+), 1 deletion(-)
master
and production
.$ git rebase master
Auto-merging first-file.txt (1)
CONFLICT (content): Merge conflict in first-file.txt (2)
error: could not apply bdfdc4a... first-file: Add from 'production'
Resolve all conflicts manually, mark them as resolved with (3)
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply bdfdc4a... first-file: Add from 'production'
$ git branch (1) * (no branch, rebasing production) (2) master production
$ cat first-file.txt (1) <<<<<<< HEAD (2) First file with bug fix from branch "master" (3) ======= (4) First file with bug fix from branch "production" (5) >>>>>>> bdfdc4a... first-file: Add from 'production' (6)
$ vi first-file.txt (1)
$ cat first-file.txt
First file with bugfix from branch "master" and from "production" (2)
$ git add first-file.txt (3)
$ git rebase --continue (4)
(5)
[detached HEAD 7c857af] first-file: Add from 'master' and 'production'
1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/production.
$ git branch
master
* production (6)
ddd…
and a new one eee…
with parent ccc…
.$ git checkout --theirs -- first-file.txt (1)
$ cat first-file.txt
First file with bugfix from branch "production" (2)
$ git add first-file.txt (3)
$ git rebase --continue (4)
(5)
[detached HEAD 45a6088] first-file: Add from 'production'
1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/production.
$ git branch
master
* production (6)
ddd…
and a new one eee…
with parent ccc…
.$ git checkout --ours -- first-file.txt (1)
$ cat first-file.txt
First file with bugfix from branch "master" (2)
$ git add first-file.txt (3)
$ git rebase --continue (4)
Successfully rebased and updated refs/heads/production.
$ git branch
master
* production (5)
master
and production
point to ccc…
and ddd…
became an orphan.Create command short cuts.
Deleting aliases.
Configure aliases for often used commands.
Solve complex recurring workflows with custom git scripts.
For some the git commands such as checkout
are too cumbersome to type
each time what if one could shorten that to say co
like on subversion.
$ git config --global alias.co checkout (1)
$ git config --global alias.ci commit
$ git config --global alias.br branch
$ git config --global alias.st status
$ git st --short (2)
A second-file.txt
While simple alias shortcuts are certainly useful one can also create aliases containing some of the rather long options.
$ git config --global alias.lol 'log --oneline --no-decorate' (1)
$ git config --global alias.top 'log -n 3 HEAD' (2)
$ git lol
49b7a9c The rest (3)
e514995 Third line
2e97d38 Second line
8070030 First commit
To modify an alias one can simply use the same command as when creating it.
$ git config --global alias.top 'log -n 1 HEAD' (1)
$ git top
commit 49b7a9cf2a8f1ae6ba94141268716ba0b07949d6 (HEAD -> master)
Author: Urs Roesch <github@bun.ch>
Date: Tue Nov 3 05:23:49 2020 +0000
The rest
To remove an alias the switch --unset
is used.
$ git config --global --unset alias.top (1)
$ git config --global --get-regex 'alias.*' (2)
alias.st=status
alias.lol=log --oneline --no-decorate
alias.co=checkout
alias.ci=commit
alias.br=branch
Aliases in git can be more than just shortcuts for overly lengthy commands.
One can cram multiple commands into a single alias. This can be done by
prefixing by starting the command with an exclamation mark !
.
$ git config --global alias.sync-upstream \
'!sh -x -c "git fetch upstream && git rebase upstream/master master"' (1)
$ git sync-upstream
+ git fetch upstream (2)
From https://github.com/sample/repository (3)
41cc734..1bd94bb master -> upstream/master
* [new tag] v1.60 -> v1.60
+ git rebase upstream/master master (4)
Current branch master is up to date. (5)
While the above example is fairly sophisticate there is no way to pass
parameters to the alias. With the next sample a list of files is passed
to the vi
editor and when finished editing the files are added to
staging area.
$ git config --global alias.vi '!sh -x -c "vi \"$@\" && git add \"$@\""' (1)
$ git vi second-file.txt
+ vi second-file.txt (2)
+ git add second-file.txt
The sky is the limit! If a difficult operation can be put into a simple alias go for it.
For some task even a complex alias is not cutting it! For such
instances there is the option of creating a custom command. Any
programming language available on the system can be used to do so.
To integrate the newly minted command into git
the script must
be named git-<command>
and be placed in a directrory included
in $PATH
.
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# Small script to push upstream without a fuss
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Setup
# -----------------------------------------------------------------------------
set -o errexit
set -o nounset
set -o pipefail
# check bash version compatiblity requires 4.2 or better
shopt -u compat41 2>/dev/null || {
echo -n "\nBash Version 4.2 or higher is required!\n";
exit 127;
}
# -----------------------------------------------------------------------------
# Globals
# -----------------------------------------------------------------------------
declare -r SCRIPT=${0##*/}
declare -r VERSION=0.3.1
declare -r AUTHOR="Urs Roesch <github@bun.ch>"
declare -r LICENSE="GPLv2"
declare -g FORCE=""
declare -g REMOVE=""
# -----------------------------------------------------------------------------
# Functions
# -----------------------------------------------------------------------------
function usage() {
local exit_code=${1:-1}
cat <<USAGE
Usage:
${SCRIPT//-/ } [options]
Opttions:
-h | --help This message
-f | --force Force a push to upstream
-r | --remove Remove the repository from upstream
-V | --version Display version and exit
Description:
Origin push to upstream without a fuss. Exludes pushes to master.
USAGE
exit ${exit_code}
}
# -----------------------------------------------------------------------------
function parse_options() {
while [[ ${#} -gt 0 ]]; do
case ${1} in
-h|--help) usage 0;;
-f|--force) FORCE="true";;
-r|--remove) REMOVE="true";;
-V|--version) version;;
-*) usage 1;;
esac
shift
done
}
# -----------------------------------------------------------------------------
function version() {
printf "%s v%s\nCopyright (c) %s\nLicense - %s\n" \
"${SCRIPT}" "${VERSION}" "${AUTHOR}" "${LICENSE}"
exit 0
}
# -----------------------------------------------------------------------------
function current_branch() {
git rev-parse --abbrev-ref HEAD
}
# -----------------------------------------------------------------------------
function push_origin() {
local branch=$(current_branch)
if [[ ${branch} == master ]]; then
echo "Not pushing master!"
exit 1
fi
git push ${FORCE:+-f} origin ${REMOVE:+:}${branch}
}
# -----------------------------------------------------------------------------
function push_tags() {
git push --tags
}
# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------
parse_options "${@}"
push_origin
push_tags
Placing the script git-opush
under ${HOME}/bin
wich is in the path
one can execute with git opush
. In below case the command is invoked
while under branch master
.
$ git opush
Not pushing master!