Git Introduction
On this page, I try to describe some key git workflows, utilizing SmartGit. For each command, I show how to execute it via menus, via keyboard shortcuts (on Manjaro Linux), and the git command line that SmartGit executed as a result. This should allow you to replicate the workflows in the tool of your choice.
I am particularly fond of SmartGit's merging support, you may find that other tools are different in that regard. I will also describe merging conflicts without tool support.
I will not go into patches, as I haven't used those at all. Except for initially migrating to git, using patches should not be necessary at all.
First of all, I have created a repository forge.git, and cloned it as forge-a.
The repository contains an empty .gitignore file, and our_program:
Welcome to Git! Imagine this file contains a sensible program. Feature 1 - this is a feature Feature 2 - this is another feature
The forge-a repository is supposed to represent the clone of another developer with push access to the repository.
Now, "you" clone your own copy of the repository.
- Menu: Repository/Clone...
- Shortcut: Ctrl+Shift+O
In this particular case, I will clone the "Local Git repository" in
/data/tmp/git_tut/forge.git into /data/tmp/git_tut/forge-b,
but that is of course specific to the example.
-
$ git clone -v --progress --branch master /data/tmp/git_tut/forge.git /data/tmp/git_tut/forge-b
The commands SmartGit executes are often more verbose than necessary; I will still put the full commands here so that I don't remove important parts by mistake. Just keep in mind that if you think the command is unnecessarily specific, it probably is! Also, except for clone commands, all commands are run from the repository directory.
Now that you have your own clone, let's look at the history: select repository forge-b, then
- Menu: Query/Log
- Context Menu "forge-b": Log
- Toolbar: Log
- Shortcut: Ctrl+L
- (similar)
$ git log
In particular, the log shows that the master branch is up-to-date with origin,
and that master is the current branch, denoted as (origin[>master).
Let's leave the log for now and improve feature 1.
To do this, we create a feature branch.
Make sure that forge-b is selected, then
- Menu: Branch/Add Branch...
- Contect Menu "Local Branches": Add Branch...
- Shortcut: F7
Branch name is feature-1-patch, then "Add Branch & Checkout"
-
$ git branch --no-track feature-1-patch -
$ git checkout feature-1-patch
Now we edit Feature 1 to "this is the improved feature". Let's also add a change we don't want to commit yet, as that happens sometimes. In the end, the program looks like this:
Welcome to Git! Imagine this file contains a sensible program. Some description, but we're not sure whether to commit this... Feature 1 - this is the improved feature Feature 2 - this is another feature
To commit only some changes, we need to stage exactly these. The Index Editor will help us here. Select the changed file, then
- Menu: Local/Index Editor
- Context Menu file: Index Editor
- Toolbar: Index Editor
- Shortcut Ctrl+Alt+T
Using the arrows, I put the Feature 1 change on the index, then save.
-
$ git update-index --cacheinfo 100644 a71fab16d4bd5d79f9e547b194773d7261d1d43c our_program
As you can see, the git command contains hashes and probably references a temporary file somewhere, so it's hard to reproduce that on the command line; I still show the Index Editor as it neatly visualizes an important concept of Git: HEAD vs. Index vs. Working Tree
- HEAD is what's already committed into the repository
- The Index are changes ready for commit. It will be important especially for merging.
- The Working Tree is just the file system
Now let's commit the staged changes ("staged" means "on the index")
- Menu: Local/Commit...
- Context Menu "forge-b" or file: Commit...
- Toolbar: Commit
- Shortcut: Ctrl+K
Smartgit lets you choose "Staged Changes" (the whole index) or "Local Changes" (specific changed files on the working tree). Let's keep with staged changes, enter "improve feature 1" as the message, and commit.
-
$ git commit --cleanup=whitespace --file=/tmp/smartgit-360896725199907360tmp/commit-1151560046432673715.tmp
(SmartGit gives the commit message as a temporary file)
Now we decide to commit the other change as well. As this is the only change to the file, we stage it as a whole. Select the file, then
- Menu: Local/Stage
- Context Menu: Stage
- Toolbar: Stage
- Shortcut: Ctrl+T
- $ git add --force -- our_program
and commit again. Let's take a look at the log. In SmartGit, be sure that you select the repo, otherwise you'll only see commits that changed the selected file.
(origin[master) is still there, but now we have (>feature-1-patch).
It's clear that this branch is local, as there's no origin equivalent.
Now, let's create a conflict by going back to forge-a.
Change our_program to this:
Welcome to Git! Imagine this file contains a sensible program. Feature 1 - this is part 1 of the feature - this is part 2 of the feature Feature 2 - this is another feature
Let's commit this with message "change feature 1", but without staging for a change. The commands are:
-
$ git add --force -- our_program -
$ git commit --file=/tmp/smartgit-360896725199907360tmp/commit-1142649907362181323.tmp -o -- our_program
As you can see SmartGit stages for us, but also lists explicitly which files to commit.
Looking at forge-a history, you see some new stuff:
There's ]>master 1>) and (origin/master[ separately, denoting, that we could push master to origin.
The puzzle-like connection tells us that master "tracks" the corresponding origin branch.
This makes pushing more convenient.
Let's push the new commit. Either from the log or in the project overview, do
- Menu: Remote/Push...
- Context Menu "master": Push 'master'...
- Context Menu "forge-a": Push...
- (probably some other context menus)
- Toolbar: Push
- Shortcut: Ctrl+U
-
$ git push --porcelain --progress --recurse-submodules=check origin refs/heads/master:refs/heads/master
If you haven't noticed, this is the first time since cloning forge-b that we interacted with the origin repository forge.git!
Everything else so far was local, which means it was fast and we can do it regardless of internet access or permissions.
Distributed version control systems let you track changes independently of the choice if, when, and how to share these changes.
Back in forge-b, let's pull:
- Menu: Remote/Pull...
- Context Menu "forge-b": Pull...
- etc.
- Toolbar: Pull
- Shortcut: Ctrl+P
The dialog shows "Fetch Only", because our current branch (feature-1-patch) does not exist remotely.
A full "pull" is a combination and fetch and merge/rebase.
-
$ git fetch --progress --prune --recurse-submodules=no origin
In the log, to the left make "origin" visible to see everything that's in the repository:
We have a fork in the history; let's resolve that by two strategies: Merge and Rebase.
First, we create a new branch to be able to do both strategies.
Select the HEAD commit "describe improvements", and add branch feature-1-patch-copy (e.g. using F7 or the toolbar).
-
$ git branch --no-track feature-1-patch-copy edc514e84680ad14779e20f276fca671ce409c41
Now, try to merge feature-1-branch (which should still be HEAD) with origin/master.
Select the origin/master branch's commit, then
- Menu: Branch/Merge...
- Context Menu: Merge...
- etc.
- Shortcut: Ctrl+M
Select "Create Merge-Commit", but we expect a conflict anyways, so it makes no difference.
- $ git merge --no-ff origin/master
We got a conflict! What does the file system look like?
Welcome to Git! Imagine this file contains a sensible program. Some description, but we're not sure whether to commit this... Feature 1 <<<<<<< HEAD - this is the improved feature ======= - this is part 1 of the feature - this is part 2 of the feature >>>>>>> origin/master Feature 2 - this is another feature
The <<<< ... ==== .... >>>> separates the conflicting versions
on HEAD (what we had before attempting to merge) and origin/master (what we're trying to merge with).
Also, the "Some description" line is not showing a conflict, though it did change in one of the file's versions.
Nice!
What does merging look like in SmartGit? Step out of the log and double-click on the conflicting file:
A three-way merge screen like the Index Editor. Of course, we don't just want to take one side, but intelligently decide how to integrate both versions:
Welcome to Git! Imagine this file contains a sensible program. Some description, but we're not sure whether to commit this... Feature 1 - this is the improved part 1 of the feature - this is part 2 of the feature Feature 2 - this is another feature
You can enter this in the middle of the Conflict Solver, or put it in the file (note that the middle editor is labelled "Working Tree"). Save and close, and SmartGit will tell you to stage changes to mark conflicts as resolved. Do so.
-
$ git add --force -- our_program
Now commit to finish the merge. In the log, the result looks like this:
For the Rebase strategy, check out the feature-1-patch-copy branch, e.g. by double-clicking it.
-
$ git checkout feature-1-patch-copy
Select the origin/master branch's commit, then
- Menu: Branch/Rebase HEAD (...) to...
- Context Menu: Rebase HEAD (...) to...
- etc.
- Shortcut: Ctrl+D
-
$ git rebase 2d6183ae9284c72ce1a0927485092e45b2a363bd feature-1-patch-copy
Again, this fails. We resolve conflicts in the same way, save, stage, and continue the rebase.
- Menu: Branch/Rebase/Continue...
- Context Menu "forge-b": Commit...
- Toolbar: Continue
- Shortcut: Ctrl-K
If you chose an option other than the menu, say that you want to Continue Rebase instead of Create Commit.
-
$ git add --update -
$ git rebase --continue
One more look at the log for both versions separately:
What version is preferrable? It depends on taste and on situation, and I won't be able to bring all arguments here. Rebase keeps the history linear, but some call this an outright lie. If you look at the timestamps, you see that the dates are not increasing, because earlier commits are applied on top of later ones. Also, how did development of feature-1-patch go when viewed separately from master?
Rebase also has a practical implication: suppose you run tests on every commit. The original "improve feature 1" commit passed tests, but does the rebased commit pass the tests as well? That version of the code was never manually committed by a developer, and rebasing can automatically create lots of commits when there are no conflicts. Passing the tests can be seen as a more thorough conflict check that Git does not perform during the rebase.
A merge commit is a single commit that can be tested by the developer. However, having merge commits everwhere in the history can make the history ugly, and as with every part of software, readability is a desirable property of the history as well.
The question merge vs rebase should be best decided per-project, not per-developer. That doesn't mean that only one is ever valid, just what is preferred in what situation. For example, rebasing can be risk-free for small changes (1-2 commits) that are developed in a short amount of time by one developer. As soon as multiple developers work on a feature, or the master branch has advanced a lot while implementing the feature, a proper merge will likely make more sense.











