Appendix B: Using Git
Git is vital technology in modern software development, so the more comfortable you are with it, the better. Researching Git online can be overwhelming, though, because writing software is a complex industry, and Git can adapt to nearly every development style. Learning Git in this book is useful because it limits the scope of what you need to learn. This appendix covers the Git basics that you need to follow the coding exercises in this book and in your daily computing life.
You can create a Git repository within any directory. While it is technically possible to make your entire home directory one big Git repository, it is not recommended. Logically, a Git repository is best when it has clearly defined boundaries. A Git repository makes the most sense when it encompasses a specific project or set of data. For instance, if you have set up Geany in a specific way, you could manage its configuration folder with Git; that’s not what you would traditionally think of as a project, but it has a single, clear purpose and set of data.
To create a Git repository, change the directory to the folder you want to use as the basis of the repo. In this example, the directory is called
project
. Then issue this command:
$ mkdir ~/project
$ cd ~/project
$ git init .
The command is
git
, and the option is
init
. The argument, a single dot, means
here
; in other words, it ensures that Git initiates a repository precisely where you are currently “parked” in your terminal.
A directory is a Git repository if it contains a hidden folder called
.git
.
If you prefer to use git-cola, launch it, and then click the New button in the lower-left corner of the git-cola window. You can either select an existing directory or create a new one, and git-cola instantiates the
.git
directory.
git add
To add a file to a Git repository, use the
git add
command. This command marks a file as a trackable object for Git.
To try this out yourself, you must create a sample file. You can use Geany or another text editor to do this, if you want, but it’s also valid to use the
echo
command in the Bash shell. The
echo
command does exactly what you probably imagine: it echoes whatever you type back at you.
$ echo "print('hello world')"
print('hello world')
However, in a terminal, you can redirect anything the computer prints back at you into a file with two
>>
symbols. If a file doesn’t exist, one is created automatically.
$ echo "print('hello world')" >> hello.lua
$ ls
hello.txt
Create a second file called
dummy.lua
. It can contain whatever text you like.
$ echo "print('Dummy file')" >> dummy.lua
You have just created two files, but Git is not aware of either file because they have not been added to Git.
If you’re working with git-cola, select File ➤ Refresh or press Ctrl+R to refresh. Your files appear in the Status panel.
Tell Git to track the
hello.lua
file.
In git-cola, right-click the file in the Status panel and select Staged. The result is shown in Figure
B-1
.
Figure B-1Git add in git-cola
git commit
Letting Git track a file and actually committing a change made to a file are two different things. Adding a file gives Git permission to manage the file. Committing a file tells Git to record changes made to a file, which in turn lets you play those changes back, or rewind the changes to revert to a previous version, or even fast-forward to changes that don’t yet exist (because those changes were made by someone else on your team).
Commit your sample
hello.lua
file now.
$ git commit --message "created hello.lua"
The
git commit
command doesn’t take a specific file as an argument. It commits the current state of your repository, which includes anything you have told Git to track.
In git-cola, add the commit message to the text field in the Commit panel, and then click the Commit button, as shown in Figure
B-2
.
Figure B-2Git commit in git-cola
You can view a log of your commits with the
git log
command.
Press Q to exit your Git log screen.
In git-cola, select View ➤ DAG to view your log.
Reverting Changes
Git’s stated goal is to provide version tracking. That means it lets you keep a record of all the changes made to files, allowing you to revert, or undo, a change when, for instance, some code you thought worked well later reveals a serious bug.
There are many ways to access the history of a Git repository, and in practice, you might have to search the Internet for the best way to grab data from an older version. The most important thing is understanding the basic principles of the process.
To demonstrate, you need a file that’s changed. Open your
hello.lua
file and change its text from
hello world
to
hello git
. If you prefer to do that in the terminal, you can.
$ sed -i 's/world/git/' hello.lua
However you choose to make the change, verify that the file has been altered.
bash-4.3$ git diff hello.lua
diff --git a/hello.lua b/hello.lua
index 3b18e51..8d0e412 100644
--- a/hello.lua
+++ b/hello.lua
@@ -1 +1 @@
-print('hello world')
+print('hello git')
In git-cola, the Status and Diff panels (see Figure
B-3
) show what has changed.
Figure B-3Git diff in git-cola
Look at the state of your repository.
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: hello.lua
Untracked files:
(use "git add <file>..." to include in what will be committed)
dummy.lua
no changes added to commit (use "git add" and/or "git commit -a")
You can see from Git’s report that the
hello.lua
file has been modified but not yet committed, and that the
dummy.lua
file exists, but it is not being tracked by Git at all.
Restoring with git reset
At this point, with a file that has changed but has not yet been committed, you can reset your repository state back to the last known committed version with the
git reset
command.
The
--hard
HEAD options tell Git to wipe the slate clean and to reset all tracked files to their state as of the latest commit. This is important to understand, because a
git reset
doesn’t just reset one file, but
all
the files in your repository. This is very efficient if you have spoiled multiple files in your repository, and you want to start from a safe place again, yet it’s overkill and possibly detrimental if you have many good changes but you only want to reset one or two files.
Confirm that
hello.lua
is back to its original state.
$ lua ./hello.lua
hello world
The
dummy.lua
file remains entirely unaffected by your reset because it is not being tracked by Git.
$ git status
...
Untracked files:
dummy.lua
Restoring with git checkout
If you only want to resurrect one or two files from your history, then use the
git checkout
command. The
checkout
option manipulates a file tree, which includes pulling a file from a previous commit into your current workspace.
To demonstrate this, change the file again and then commit the change.
$ sed -i 's/world/git/' hello.lua
$ git diff
...
-hello world
+hello git
$ git add hello.lua
$ git commit --message "changed world to git"
Now assume that you need to restore
hello.lua
to its original state. You can’t reset, because reset always restores the latest commit.
First, use
git log
to obtain the unique ID (or
hash
, in technical terminology) of each commit. The hashes in this example and your workspace may differ.
$ git log --oneline
42e6b85 (HEAD -> master) changed world to git
ef5288a created hello.lua
The commit you want to restore from is not the latest (the one marked HEAD, at the top of the log), but the original commit, with the ef5288a hash.
The
git checkout
command can take a hash and a specific file name as an argument. To signal Git that you are giving it a file name, however, you must use two dashes as a break point in the command.
$ git checkout ef5288a -- hello.lua
Confirm that your file has changed back to its original form.
$ cat hello.lua
hello world
If you’re happy with your new old file, then you can add it and then commit it back into your master timeline.
The
dummy.lua
file is still untouched, because it isn’t being tracked by Git.
To perform the same operation in git-cola, select View ➤ DAG to launch the Git log interface so that you can find the file that you want to restore. Once you’ve located it, right-click it and select Grab File (see Figure
B-4
), and then save the file to your drive.
Figure B-4Restoring with git-cola
git branch
When you reach a point that involves many changes to many different files, erasing mistakes with resets and checkouts becomes complex. If there are several people working on a single code base, then resets and checkouts become nearly impossible due to all the changes constantly happening. Luckily, Git provides an infinite number of workspaces, called a
branch
.
When Git initializes a repository, it creates a branch named
master
. A branch is meant to serve as a kind of overlay, or if you’re inclined to science fiction, an alternate timeline or parallel universe. Branches let you look at the natural timeline of your Git repository and then “branch” off into any direction you think you might want to go. If the work you’re doing in a branch turns out to be less successful than expected, you can abandon the branch and return to the original master timeline, but if it goes well, then you can merge it into the master—
even if the master timeline has changed since you last left it
.
To test this out, first confirm that a master branch exists.
Create a new branch.
$ git branch dev
$ git branch
dev
* master
And then switch to the new branch.
$ git checkout dev
$ ls
dummy.lua hello.lua
To do this in git-cola, select Branch ➤ Create and create a new branch called
dev
with all the default options. You are automatically switched to your new branch, as indicated by the star icon in the Branches panel.
Make a change to
hello.lua
.
$ echo "print('I am practising Git')" >> hello.lua
Confirm that your change has been made.
$ git diff hello.lua
diff --git a/hello.lua b/hello.lua
index 3b18e51..0eb0a16 100644
--- a/hello.lua
+++ b/hello.lua
@@ -1 +1,2 @@
print('hello world')
+print('I am practising Git')
Add and commit your change, and finally, add
dummy.lua
to your repository.
$ git add hello.lua dummy.lua
$ git commit -m "added a second line to hello.lua"
Get a summary of your dev branch.
$ git log --oneline
db5968c (HEAD -> dev) added a second line to hello.lua
42e6b85 (master) changed world to git
ef5288a created hello.lua
$ lua ./hello.lua
hello world
I am practising Git
$ lua ./dummy.lua
Dummy file
Now check out the master branch to see what your original timeline is like.
In git-cola, right-click the
master
branch in the Branches panel and select Checkout.
View your Git log in the terminal, or use DAG in git-cola.
$ git log --oneline
42e6b85 (HEAD -> master) changed world to git
ef5288a created hello.lua
$ lua ./hello.lua
hello world
$ lua ./dummy.lua
lua: cannot open ./dummy.lua: No such file or directory
The change that you made to
hello.lua
in the dev branch doesn’t exist in your master branch, and the
dummy.lua
has seemingly vanished entirely.
You haven’t lost your work—it just doesn’t exist in this workspace. The version of
hello.lua
in the master branch is the original version, before you added a second line. And
dummy.lua
is no longer floating around the repository; it has been added to the dev branch exclusively so that it no longer shows up as an untracked file in the master branch.
Jump back over to your dev branch again to verify this.
$ git checkout dev
$ git log --oneline
db5968c (HEAD -> dev) added a second line to hello.lua
42e6b85 (master) changed world to git
ef5288a created hello.lua
$ lua ./hello.lua
hello world
I am practising Git
$ lua ./dummy.lua
Dummy file
Generally, you can and should work with Git branches so that you always have a safe, functional code base at the core of your project.
git merge
When two Git branches have diverged, at some point you might want to merge them together. This is a common task in software development just before a release date; the development branch is merged into an official release branch, and then the update is announced to users.
When you’re ready to merge two branches, check out the
target
branch—the one you want to bring your work into.
$ git checkout master
$ git merge dev
In git-cola, the process is similar: check out the master branch, and then right-click the dev branch and select “Merge into current branch” (see Figure
B-5
).
Figure B-5Git merge with git-cola
Verify that the merge has brought in all of your changes.
$ ls
dummy.lua hello.lua
$ lua ./hello.lua
hello world
I am practising Git
$ lua ./dummy.lua
Dummy file
This is a common workflow in software development: clone a Git repository, create a personal branch for yourself, do your work, and then merge your work into the master branch. When you’re finished with a branch, you can delete it.
$ git branch --delete dev
Deleted branch dev (was b5dbe64).
git push
Using Git locally on your Pi is not very different from using it with a coding website like GitLab or GitHub. The most significant difference with Git hosting sites is that you must also perform a
git push
in order to upload the state of your repository to your Git hosting site. For example, if you had a Git repository on GitLab for this practice exercise, the final step would be
In this example,
origin
is the default alias for your Git hosting site, whether it’s GitLab, NotABug.org, GitHub, or any other service. In this example, the term
HEAD
refers to
the current commit
.
As with Lua, practice with Git makes perfect. It’s a big system with lots of options and many different ways to achieve many different results. As long as you commit your work often, you run little chance of losing work by mistake, and you stand a good chance of recovering work that’s been spoiled by buggy code. Understanding how Git works is what enables you to ask sensible and clear questions when looking online for specific commands.