Developers normally work in teams. You write plenty of code; sometimes you test some and decide to delete it, and other times you decide to stick to it. Managing this can be a painful process, which is why you can use Source Control Management (SCM)
software to help you focus on what you do best—writing beautiful code. Git is rapidly becoming the preferred SCM of developers everywhere.
Git
The Git SCM was developed by Linus Torvalds for managing the Linux kernel source code
. It’s also been used for several million open source projects, including Rails.
Git is different from other SCMs because it’s a distributed source control system. This means that instead of having a single repository on your server that all your teammates use to check out working copies (client-server or centralized SCM), each team member has their own repository along with a working copy, and you all push a copy of that repository to a remote repository.
This approach has some great benefits, such as the ability to work and commit your code, even if you’re offline, and being able to operate on your repository more quickly.
Now that you have a good understanding of what an SCM is and how it works, let’s install Git and try it.
Installing Git
Installing Git is relatively easy. Thanks to open source contributions, several Git installation packages are available to facilitate a quick installation for most platforms.
Installing on Windows
If you’re on Windows
, one option for installing Git is from the official source: https://git-scm.com/download/win
. Simply download the installer, accept the default options, and you’ll end up with Git Bash for command-line usage, as well as Git GUI for a graphical interface. Use the Git Bash tool for executing commands mentioned in this appendix.
Installing on macOS
To install Git on macOS, there are a few easy options
.
If you’ve already installed the Xcode Command-Line Tools, you may already have it. Try running git --version from your terminal. It should either report the version of Git that’s already installed or offer to install it for you.
It’s also possible to install Git using the Homebrew package manager, via brew install git. If you don’t yet have Homebrew installed, visit https://brew.sh/
for installation instructions, or consult Chapter .
Installing on Linux
Most Linux distributions
ship with a package manager. The most common one is the Debian package manager apt, and Git is part of its library.
To install Git using
apt, run the following
apt-get command from the terminal:
Accept if the package manager asks your permission to use additional disk space for this installation. When the installation is complete, Git is ready to use.
Setting Global Parameters
Every commit you make in your repository has flags for the user who executed the commit; those flags are the user’s name and email address. Now that you have Git installed on your system, it’s important to set a global username and email address for Git to use for any new repository you work on.
To set
global parameters
, you use the
git config command with the
--global option, followed by the parameters you want to set. Listing
C-1 shows the command to set up both the
user.name and
user.email parameters.
git config --global user.name "dude"
Listing C-1Setting the Global Git Username and Email
These parameters can be set on a repository level as well; you can do that by using the same commands but without the --global option in your repository’s working directory.
Initializing a Repository
Often, we need to initialize a new
repository
for our application. Let’s begin by creating a test application, making sure to be outside of any directories which are already under version control:
$ rails new testapp
create
create README.md
create Rakefile
create .ruby-version
create config.ru
create .gitignore
create Gemfile
run git init from "."
Initialized empty Git repository in /Users/brady/Sites/testapp/.git/
create package.json
...
Notice that
rails new took care of initializing a new Git repository for us! If it hadn’t or if we wanted to initialize a new Git repository for a project which didn’t already have one, we could have done the following:
$ cd testapp
$ git init
Initialized empty Git repository in /Users/brady/Sites/testapp/.git/
The
git init command initializes an empty local repository for the application, but it doesn’t add any files to the repository. To determine which files we can add to the repository, we can use the
git status command
:
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
.browserslistrc
.gitignore
.ruby-version
Gemfile
Gemfile.lock
README.md
Rakefile
app/
babel.config.js
bin/
config.ru
config/
db/
lib/
log/
package.json
postcss.config.js
public/
storage/
test/
tmp/
vendor/
yarn.lock
nothing added to commit but untracked files present (use "git add" to track)
As you can see, all the folders and files of the Rails application are in the untracked files list, which means that they’re not under Git’s control. To start tracking those files, we need to add them to the track list; as this indicates, we can do this by using the git add command. (While sometimes git may seem a bit perplexing to use, you’ll find it often includes helpful hints in its output—so read closely!)
Ignoring Files
Before we add those files
, let’s think a little: Do we want all of our files to be tracked? Are there any files we don’t want to track? Normally, those would be configuration files that contain passwords, such as database.yml, the tmp folder, log files, and SQLite databases. If we add those files, our teammates will have this information, and it may even conflict with their files. A worst-case scenario involves our Git repository becoming public and sensitive secrets falling into the hands of malicious users.
To skip those files in any
git add and
git status commands and to tell Git to never bother you about them again, we must configure Git to ignore them. We can do that by declaring those files in a hidden configuration file called
.gitignore, which is normally stored at the root of your working copy (in this case, at the root of the
testapp directory). The
.gitignore file is a regular text file; it is generated by Rails in all new projects. View it using the text editor of your choice, and you’ll see that it looks like the code in Listing
C-2.
# Ignore bundler config.
/.bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
/db/*.sqlite3-*
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# Ignore uploaded files in development.
/storage/*
!/storage/.keep
/public/assets
.byebug_history
# Ignore master key for decrypting credentials and more.
/config/master.key
/public/packs
/public/packs-test
/node_modules
/yarn-error.log
yarn-debug.log*
.yarn-integrity
As you can see, the files and folders listed in the .gitignore file weren’t listed in the git status command you issued earlier. To help make sure we don’t commit any unwanted or sensitive information to our Git repository, Rails initializes new projects with a preconfigured .gitignore, especially made for Rails projects
.
There may be times when we need to modify which files should or shouldn’t be tracked by Git; simply add a pattern to exclude the file(s) you wish to exclude, and that’s it!
Adding and Committing
We can add all of the untracked files to our repository by using the
git add command
and passing a dot to it, which refers to the current directory and all its content. Be cautious when using
"git add ." to make sure you aren’t adding files that don’t belong in the repository:
Try the
git status command again:
$ git status
On branch master
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .browserslistrc
new file: .gitignore
new file: .ruby-version
new file: Gemfile
new file: Gemfile.lock
new file: README.md
new file: Rakefile
...
The git status command still shows all the files, because the git add command just added those files to be committed, but they aren’t committed yet. Notice how instead of being shown in the “untracked files” list, they’re now included in the “changes to be committed.” Another way of phrasing this using Git terminology is that these changes have been “staged.” (Notice the helpful command to unstage a file. Running this command won’t delete the file from your filesystem; it will simply remove the file from the list of changes to be committed.)
In order to commit the changes you added to
the
commit list, we use the
git commit command. Use the
–m argument to include a message describing the purpose of and the changes in this commit:
$ git commit -m "Empty Rails application"
master (root-commit) 15c012e] Empty Rails application
91 files changed, 9213 insertions(+)
create mode 100644 .browserslistrc
create mode 100644 .gitignore
create mode 100644 .ruby-version
create mode 100644 Gemfile
create mode 100644 Gemfile.lock
create mode 100644 README.md
create mode 100644 Rakefile
....
Congratulations! You’ve completed your first commit to your local repository! If we check the
git status command now, we’ll see that there are no changes to be added or committed:
$ git status
On branch master
nothing to commit, working tree clean
Now, let’s change a file. For example, let’s edit app/views/layouts/application.html.erb to include
<p>Hey!</p> in the body of the page. Save the file, and now let’s check our status again:
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: app/views/layouts/application.html.erb
no changes added to commit (use "git add" and/or "git commit -a")
Git knows that we changed the file since the last time we committed changes, so shows the file as modified, but not yet staged for commit.
Instead of using “
git add .”, like we did before, we can add this specific file:
$ git add app/views/layouts/application.html.erb
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: app/views/layouts/application.html.erb
Now, the file is still modified, but labeled as to be committed
. You could then run the git command -m "Updated layout" command to commit the change. When reasonable, it’s best to carefully add specific files; using “git add .” without looking at your git status closely will inevitably lead to unwanted files being added to your repository.
Branching and Merging
Let’s say you decide to test out a major refactor on your project and you’re not sure if it will work out and don’t want to break the code for everyone else. Meanwhile, you need to be able to keep working on the main project without changes from your experiment breaking your application. To have a safe place to experiment with your project’s code, you need to create a branch. A branch is a duplicate of your project’s code that you can work on in parallel with the master copy of the same project.
When you called the
git init command earlier, Git initialized a new repository for your application with a default branch called
master. To create a new branch in the repository, use the
git checkout –b command followed by the name of the new branch you want to create:
$ git checkout -b articles
This command creates a new branch named
articles as a duplicate of the current branch—
master—and then switches to the newly created branch. To see a list of the branches in your project, we can use the
git branch command
:
$ git branch
* articles
master
The output indicates we have two branches—articles and master. The asterisk in front of
articles indicates that it’s the
current branch you’re working on. To switch branches, use the
git checkout command followed by the name of the branch you want to switch to:
$ git checkout master
Switched to branch 'master'
$ git checkout articles
Switched to branch 'articles'
The
articles branch is the current branch again. We can confirm this by listing the branches again:
$ git branch
* articles
master
Now, let’s implement a new feature—an articles scaffold:
$ rails generate scaffold Article title:string body:text
invoke active_record
create db/migrate/20200427012944_create_articles.rb
create app/models/article.rb
invoke test_unit
create test/models/article_test.rb
create test/fixtures/articles.yml
invoke resource_route
route resources :articles
invoke scaffold_controller
create app/controllers/articles_controller.rb
invoke erb
create app/views/articles
create app/views/articles/index.html.erb
create app/views/articles/edit.html.erb
create app/views/articles/show.html.erb
create app/views/articles/new.html.erb
create app/views/articles/_form.html.erb
invoke test_unit
create test/controllers/articles_controller_test.rb
create test/system/articles_test.rb
invoke helper
create app/helpers/articles_helper.rb
invoke test_unit
invoke jbuilder
create app/views/articles/index.json.jbuilder
create app/views/articles/show.json.jbuilder
create app/views/articles/_article.json.jbuilder
invoke assets
invoke scss
create app/assets/stylesheets/articles.scss
invoke scss
create app/assets/stylesheets/scaffolds.scss
Let’s say we’re done with the new feature changes. It’s time to add the changes and commit them to the
articles branch:
$ git add .
$ git commit -m "Adding Article scaffold"
[articles cb4bed1] Adding Article scaffold
19 files changed, 351 insertions(+)
create mode 100644 app/assets/stylesheets/articles.scss
create mode 100644 app/assets/stylesheets/scaffolds.scss
create mode 100644 app/controllers/articles_controller.rb
create mode 100644 app/helpers/articles_helper.rb
create mode 100644 app/models/article.rb
create mode 100644 app/views/articles/_article.json.jbuilder
create mode 100644 app/views/articles/_form.html.erb
create mode 100644 app/views/articles/edit.html.erb
create mode 100644 app/views/articles/index.html.erb
create mode 100644 app/views/articles/index.json.jbuilder
create mode 100644 app/views/articles/new.html.erb
create mode 100644 app/views/articles/show.html.erb
create mode 100644 app/views/articles/show.json.jbuilder
create mode 100644 db/migrate/20200427012944_create_articles.rb
create mode 100644 test/controllers/articles_controller_test.rb
create mode 100644 test/fixtures/articles.yml
create mode 100644 test/models/article_test.rb
create mode 100644 test/system/articles_test.rb
When you check the
git status command now, you see that you have nothing to commit in the
articles branch:
$ git status
On branch articles
nothing to commit, working tree clean
The
articles branch now has an articles scaffold, and the
master branch doesn’t. If you switch back to the
master branch, notice that none of the articles scaffold files exist there:
$ git checkout master
Switched to branch 'master'
For fun, you could switch back to the articles branch
and watch your articles scaffold files magically reappear. Hopefully the magic is a little clearer now; your branches can have different files in them, and depending on which branch is currently checked out, your filesystem will reflect the committed changes for that branch.
We could go on modifying the code in the
master branch completely in isolation from the
articles branch. But at some point, the feature being developed in the articles branch will be ready to be added to the “main” code branch, master. In Git terminology, that’s a
merge. Let’s
merge the
articles branch into the
master branch. We’ll do that using the
git merge command
followed by the branch name you want to merge into the current branch:
$ git checkout master
$ git merge articles
Updating 15c012e..cb4bed1
Fast-forward
app/assets/stylesheets/articles.scss | 3 +++
app/assets/stylesheets/scaffolds.scss | 65 +++++++++++++++++++++++++++
app/controllers/articles_controller.rb | 74 +++++++++++++++++++++++++++++
app/helpers/articles_helper.rb | 2 ++
app/models/article.rb | 2 ++
app/views/articles/_article.json.jbuilder | 2 ++
app/views/articles/_form.html.erb | 27 +++++++++++++++++++++++++++
app/views/articles/edit.html.erb | 6 ++++++
app/views/articles/index.html.erb | 29 +++++++++++++++++++++++++++++
app/views/articles/index.json.jbuilder | 1 +
app/views/articles/new.html.erb | 5 +++++
app/views/articles/show.html.erb | 14 ++++++++++++++
app/views/articles/show.json.jbuilder | 1 +
config/routes.rb | 1 +
db/migrate/20200427012944_create_articles.rb | 10 ++++++++++
test/controllers/articles_controller_test.rb | 48 ++++++++++++++++++++++++++++
test/fixtures/articles.yml | 9 +++++++++
test/models/article_test.rb | 7 +++++++
test/system/articles_test.rb | 45 +++++++++++++++++++++++++++++++
19 files changed, 351 insertions(+)
create mode 100644 app/assets/stylesheets/articles.scss
create mode 100644 app/assets/stylesheets/scaffolds.scss
create mode 100644 app/controllers/articles_controller.rb
create mode 100644 app/helpers/articles_helper.rb
create mode 100644 app/models/article.rb
create mode 100644 app/views/articles/_article.json.jbuilder
create mode 100644 app/views/articles/_form.html.erb
create mode 100644 app/views/articles/edit.html.erb
create mode 100644 app/views/articles/index.html.erb
create mode 100644 app/views/articles/index.json.jbuilder
create mode 100644 app/views/articles/new.html.erb
create mode 100644 app/views/articles/show.html.erb
create mode 100644 app/views/articles/show.json.jbuilder
create mode 100644 db/migrate/20200427012944_create_articles.rb
create mode 100644 test/controllers/articles_controller_test.rb
create mode 100644 test/fixtures/articles.yml
create mode 100644 test/models/article_test.rb
create mode 100644 test/system/articles_test.rb
The output shows the effects of our merge; it shows which files have been updated and how many lines in each file were added or removed. (In our case, they were all additions.)
The task is complete: we “developed” a new feature in a separate branch without affecting the master branch; and when we finished, we merged those changes back into master.
There’s much more to learn about these git commands; this is merely a brief introduction.
Remote Repositories and Cloning
As stated previously, Git is a distributed SCM; therefore, your repository is hosted locally on your machine, hidden inside your working copy directory. No one else has access to it.
However, if you want to set up a repository that you and your team can work on, you may want to create a remote repository
that all of you can access and clone from. Your remote repository can be hosted on any machine that is available to all developers who need access to the repository and have Git installed. It can be hosted on your local network, online, or with a third-party Git hosting provider like the famous GitHub (https://github.com
), which hosts Rails, as well as many, many other projects.
We used Git for this book’s blog application, and we hosted the repository on GitHub. It’s publicly available for you to browse and use; simply point your browser at
https://github.com/nicedawg/beginning-rails-6-blog
. This means you can
clone a copy of the blog repository to your machine and browse the code locally. To do that, you need the Public Clone URL, which you find from the “Clone or download” button on the GitHub page for the repo. Let’s clone the blog application repository using the
git clone command:
Cloning into 'beginning-rails-6-blog'...
remote: Enumerating objects: 499, done.
remote: Counting objects: 100% (499/499), done.
remote: Compressing objects: 100% (243/243), done.
remote: Total 1023 (delta 300), reused 424 (delta 253), pack-reused 524
Receiving objects: 100% (1023/1023), 265.83 KiB | 1.53 MiB/s, done.
Resolving deltas: 100% (537/537), done.
Now you have a local copy of the blog application repository cloned to your machine. You can change files and even commit them to your own local repository, but what you cannot do is share those commits with others. In order to push your changes, you need write access to the remote repository
, which you don’t have.
If you want to try that, sign up for a free account on GitHub and create a repository of your own there. You then have two URLs: a public URL that everyone can see and your clone URL, which gives you full access to this remote repository.
The concept is simple: after you clone your own repository using your own URL, you can work normally in your working copy, commit changes, and add and remove files. Whenever you want to share those commits with the rest of the world, you push them to the remote repository on GitHub using the git push command. If you have teammates pushing changes to the same repository, you can retrieve those changes by using the git pull command.
To sum up, you create a remote repository to allow more than one developer to work on the same repository. Although all developers on the team have their own copies, they still need to push their copies to the remote repository to allow the rest to pull from it and stay in sync.
When you sign up for a free account on GitHub, the repositories you create can be made publicly available for everyone to clone from. Or, if you want your repositories to be private, so only you and your teammates can access them, you can choose to make them private on GitHub, or you could host them on your own server with your own setup.
Learning More
Git is a great tool and has a lot of commands; however, this appendix has covered only the basic features and commands. We highly encourage you to read more. You can see a list of the most used Git commands using the
git help command
:
$ git help
usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
<command> [<args>]
These are common Git commands used in various situations:
start a working area (see also: git help tutorial)
clone Clone a repository into a new directory
init Create an empty Git repository or reinitialize an existing one
work on the current change (see also: git help everyday)
add Add file contents to the index
mv Move or rename a file, a directory, or a symlink
restore Restore working tree files
rm Remove files from the working tree and from the index
sparse-checkout Initialize and modify the sparse-checkout
examine the history and state (see also: git help revisions)
bisect Use binary search to find the commit that introduced a bug
diff Show changes between commits, commit and working tree, etc
grep Print lines matching a pattern
log Show commit logs
show Show various types of objects
status Show the working tree status
grow, mark and tweak your common history
branch List, create, or delete branches
commit Record changes to the repository
merge Join two or more development histories together
rebase Reapply commits on top of another base tip
reset Reset current HEAD to the specified state
switch Switch branches
tag Create, list, delete or verify a tag object signed with GPG
collaborate (see also: git help workflows)
fetch Download objects and refs from another repository
pull Fetch from and integrate with another repository or a local branch
push Update remote refs along with associated objects
'git help -a' and 'git help -g' list available subcommands and some
concept guides. See 'git help <command>' or 'git help <concept>'
to read about a specific subcommand or concept.
See 'git help git' for an overview of the system.
To learn more about a specific command, you can use git help COMMAND, which shows that command’s documentation and how to use the command.