In this article we will cover the basics for working with Git and GitHub: local and remote repositories, Personal Access Token authentication as well as collaborative work through branches and pull requests.
Git and GitHub
Git is a version control system and GitHub is a cloud Git service. GitHub has become a very popular source code repository because its plenty of features and has free access to public access projects. Git and GitHub are both huge tools and using them for the first time can be intimidating. This article aims to address the basic usage on a team scenario so future articles will address configuration and advanced scenarios.
Understanding the distributed repository usage in Git and GitHub
Using Git for source code version control involves working in both local and remote (centralized) repositories. Developers clone existing repositories or create their own local ones and then push their changes in remote repositories. One common (and natural for Git) practice is that developers control source code in different simultaneous versions (branches) for new features or bugs. Once they complete their job, they are mark the branch for review and integration (pull request).
.
Signing up to GitHub
You should open a free account in GitHub to follow this guide. Once on board, next step is to be sure you have one of the many Git clients out there. We won’t cover Git history and background here to keep this guide as light and useful as possible.
Git clients
The following is a non extensive list of available Git clients for several platforms. Depending on yours, you can opt for Git plugins for your favorite IDE, GUI clients and the standard command-line tool. We are going to use the latter to explain the mechanics and you can explore the best option for your development environment and team preferences.
Authentication in Git and GitHub
GitHub supports several authentication mechanisms (certificate, 2FA, among others). We will use the Personal Access Token (PAT) authentication in this guide. PAT is a safer replacement for standard username/password authentication and works almost in the same way with few improvements like custom expiry date and the ability to create several PAT each with its own fine grained permissions. To avoid entering your credentials after each command you should set up your username and e-mail and enable a credential helper store (this will allow you to save your different credentials):
git config --global user.name demo40devweb
git config --global user.email [email protected]
git config --global credential.helper 'store --file ~/.git-credentials'
The git credential
command allows to save your credentials in the credential helper store. If you have configure other credential store types the command will replicate the credentials in each one. You can use the command below or enter the data interactively:
git credential approve << EOF
protocol=https
host=github.com
path=40devweb/demo.git
username=40devweb
password=ghp_123456789012345678901234567890123456
EOF
Managing sources in Git and GitHub
Creating a GitHub repository
Github’s simple UI shows the repository options. For our guide, we will create a new empty repository (demo in the example), push an initial version of the Demo application and then we will create a new branch in order to implement a new feature.
Creating a local repository from scratch
A common case for version control in GitHub occurs when you already have the source code and want to be controller on GitHub. For example, consider we have a demo folder with our sources (in this case a Spring Boot Initializr dummy project):
demo40dev@40dev:~/demo$ ls -l
total 32
-rw-r--r-- 1 demo40dev demo40dev 848 dic 20 2022 HELP.md
-rwxr-xr-x 1 demo40dev demo40dev 10284 dic 20 2022 mvnw
-rw-r--r-- 1 demo40dev demo40dev 6734 dic 20 2022 mvnw.cmd
-rw-r--r-- 1 demo40dev demo40dev 1225 dic 20 2022 pom.xml
drwxr-xr-x 4 demo40dev demo40dev 4096 dic 20 2022 src
In this case, some developer or the proprietary of the repository can upload or push the source code into the remote repository. In order to do so, we first create the local repository, commit the new sources in the local repository and then push changes to the remote repository.
The first Git command we are going to use is git init
:
demo40dev@40dev:~/demo$ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint: git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: git branch -m <name>
Initialized empty Git repository in /home/demo40dev/demo/.git/
The git init
command tells Git that the current directory should track file changes (add, remove, new files not tracked before) which lead us to the next command git status
:
shigeo@40dev:~/demo$ git status
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
.mvn/
mvnw
mvnw.cmd
pom.xml
src/
nothing added to commit but untracked files present (use "git add" to track)
GitHub changed the dafault name for their first branch as main
, so you could configure your git client to use main
as the default branch name before creating a new repository:
git config --global init.defaultBranch main
Now, we add the demo project files to the local repository with git add .
and commit changes. The dot indicates to add all non-managed source code and git commit -m "message"
commits recently added sources to the local repository and add the message to the change set. In this case is “First commit”.
git add .
git commit -m "First commit"
You can see with git status the status of the source code changes in the repository.
demo40dev@40dev:~/demo$ git add .
demo40dev@40dev:~/demo$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .gitignore
new file: .mvn/wrapper/maven-wrapper.jar
new file: .mvn/wrapper/maven-wrapper.properties
new file: mvnw
new file: mvnw.cmd
new file: pom.xml
new file: src/main/java/com/_40dev/demo/DemoApplication.java
new file: src/main/resources/application.properties
new file: src/test/java/com/_40dev/demo/DemoApplicationTests.java
demo40dev@40dev:~/demo$ git commit -m "First commit"
[master (root-commit) b0cb492] First commit
9 files changed, 607 insertions(+)
create mode 100644 .gitignore
create mode 100644 .mvn/wrapper/maven-wrapper.jar
create mode 100644 .mvn/wrapper/maven-wrapper.properties
create mode 100755 mvnw
create mode 100644 mvnw.cmd
create mode 100644 pom.xml
create mode 100644 src/main/java/com/_40dev/demo/DemoApplication.java
create mode 100644 src/main/resources/application.properties
create mode 100644 src/test/java/com/_40dev/demo/DemoApplicationTests.java
demo40dev@40dev:~/demo$ git status
On branch master
nothing to commit, working tree clean
demo40dev@40dev:~/demo$
Pushing code to a remote GitHub repository
Now that we have our local repository, we will connect both local and remote repository. For our example the command to connect them is git remote add <name> <url>
:
git remote add origin https://github.com/40devweb/demo.git
When all changes in local repository are committed, next step is to synchronize changes with the remote repository in GitHub. This is done with the git push
command:
demo40dev@40dev:~/demo$ git push -v origin master
Pushing to https://github.com/40devweb/demo.git
Username for 'https://github.com': 40devweb
Password for 'https://[email protected]':
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 6 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 274 bytes | 274.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
POST git-receive-pack (449 bytes)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/40devweb/demo.git
b0cb492..f885213 master -> master
updating local tracking ref 'refs/remotes/origin/master'
Collaborate in GitHub
Adding users to your remote repository
You can add new users and collaborators other than the repository owner from the Github project page.
The command git clone
will clone a remote repository in a local repository (current folder by default). In this case, user shnishi
should have permissions on the remote demo40devweb/demo
repository and has configured credentials as showed in previous sections:
shnishi@40dev:~$ git clone https://github.com/40devweb/demo.git
Cloning into 'demo'...
remote: Enumerating objects: 34, done.
remote: Counting objects: 100% (34/34), done.
remote: Compressing objects: 100% (21/21), done.
remote: Total 34 (delta 4), reused 32 (delta 2), pack-reused 0
Receiving objects: 100% (34/34), 59.48 KiB | 441.00 KiB/s, done.
Resolving deltas: 100% (4/4), done.
shnishi@40dev:~$
Creating a branch
When working with several developers on the same code base, it is expected that you or your development team derive a new version (branch) of the official repository while doing changes and merge this version with the main version once it is ready. Branching strategies may vary according to projects and organizations and we will discuss them in a future post for the sake of simplicity. For the example we will create a new branch for feature XYZ and modify some files:
shnishi@40dev:~/demo$ git branch
* master
shnishi@40dev:~/demo$ git branch featureXYZ
shnishi@40dev:~/demo$ git checkout featureXYZ
Switched to branch 'featureXYZ'
shnishi@40dev:~/demo$ git branch
* featureXYZ
master
shnishi@40dev:~/demo$
Let’s review the commands issued:
git branch
: Shows the branches created in the local repositorygit branch <name>
: Creates a new branch with the specified namegit checkout <name>
: Changes the current working files
Now we will proceed modifying some files and pushing changes to GitHub. Please note that git push <remote> <localBranch>
will create the local branch in the remote repository and will make it visible to other team members.
shnishi@40dev:~/demo$ echo "New modification for featureXYZ" > featureXYZ.txt
shnishi@40dev:~/demo$ git add .
shnishi@40dev:~/demo$ git commit -m "Added feature XYZ"
[featureXYZ 10fca79] Added feature XYZ
1 file changed, 1 insertion(+)
create mode 100644 featureXYZ.txt
shnishi@40dev:~/demo$ git push origin featureXYZ
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 6 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 312 bytes | 312.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote:
remote: Create a pull request for 'featureXYZ' on GitHub by visiting:
remote: https://github.com/40devweb/demo/pull/new/featureXYZ
remote:
To https://github.com/40devweb/demo.git
* [new branch] featureXYZ -> featureXYZ
shnishi@40dev:~/demo$
Creating a pull request
Once the feature XYZ is complete we are ready to merge the featureXYZ branch with the main branch in order to release a new version of our demo application. You could do that with git merge <featureBranch>
but al alternative is to create a request to a project leader or senior developer to review the modifications and approve the marge with official branches (like QA or UAT environments branches). This is what is called a pull request and depending on the permissions model on the repository may be the standard (and only) mechanism to introduce new features to repositories. A pull request can be created from the GitHub repository in the Pull requests tab and selecting the branch we want to be pulled into another branch and selecting Create pull request.
Once created, the specified reviewers receive a notification for the review of the pull request and proceed to accept (merge) the proposed feature or they can reject it. The entire workflow is fully configurable. In this example the branch featureXYZ is no longer needed so we proceed to deleted in both local and remote repositories:
shnishi@40dev:~/demo$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
shnishi@40dev:~/demo$ git branch -D featureXYZ
Deleted branch featureXYZ (was 10fca79).
shnishi@40dev:~/demo$ git push origin --delete featureXYZ
To https://github.com/40devweb/demo.git
- [deleted] featureXYZ
shnishi@40dev:~/demo$
Merging remote changes into local repositories
It is possible that other developers modify source code in the same branch but in the remote repository. In that case you would need to synchronize your local repository with the most recent changes. The git pull
command updates your local repository with the remote changes. For our example, git pull
updates the local repository with the remote changes (the feature XYZ branch merge).
shnishi@40dev:~/demo$ ls
mvnw mvnw.cmd pom.xml src
shnishi@40dev:~/demo$ git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (4/4), 905 bytes | 905.00 KiB/s, done.
From https://github.com/40devweb/demo
cb466f0..91e7f48 master -> origin/master
Updating cb466f0..91e7f48
Fast-forward
featureXYZ.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 featureXYZ.txt
shnishi@40dev:~/demo$ ls
featureXYZ.txt mvnw mvnw.cmd pom.xml src
shnishi@40dev:~/demo$
Advanced topics
This article covers the basics for source code control with Git and GitHub. Future articles will cover:
- GitHub Actions: CI/CD functionalities
- Projects and Issues for project work item management
- Security and workflow management
- Codespaces: Container development environments