Git and Linux Basics - Getting Started with Version Control

Git and Linux Basics - Getting Started with Version Control

What You'll Learn

  • Set up Git on Linux with the minimum viable configuration
  • Master the core flow: git init / add / commit / push
  • Avoid Linux-specific pitfalls (permissions, line endings, exec bit)
  • Push to GitHub or GitLab via SSH key authentication

Quick Path (5 steps)

  1. Install: sudo apt install git
  2. Configure: git config --global user.name / user.email
  3. Initialize: git init or git clone <url>
  4. Iterate: git statusgit addgit commit -m "..."
  5. Share: git remote addgit push -u origin main

Assumed Environment

  • Ubuntu / Debian-based Linux (22.04 or later)
  • bash or zsh
  • GitHub / GitLab / self-hosted Git over SSH

1. Install and Configure

1-1. Install Git

$ sudo apt update
$ sudo apt install -y git
$ git --version
git version 2.43.0

RHEL / Fedora: sudo dnf install git. macOS: brew install git.

1-2. Set Global Configuration (one-time)

$ git config --global user.name "Your Name"
$ git config --global user.email "you@example.com"
$ git config --global init.defaultBranch main

init.defaultBranch main makes new repos use main automatically (the standard since GitHub adopted it in 2020).

1-3. Verify Configuration

$ git config --list --global

--global writes to ~/.gitconfig. To override per repo, run the same command without --global inside the repo (saved in .git/config).

2. Create or Clone a Repository

2-1. Initialize an Existing Directory

$ cd ~/projects/myapp
$ git init
$ ls -la .git

The directory is under Git control as soon as .git/ is created. You normally never need to edit it directly.

2-2. Clone a Remote

# HTTPS (simple, but requires PAT)
$ git clone https://github.com/user/repo.git

# SSH (recommended once keys are set up)
$ git clone git@github.com:user/repo.git
$ cd repo

GitHub deprecated HTTPS password authentication in 2021. You now need a Personal Access Token (PAT) for HTTPS. SSH is friction-free for daily use (see §6 for setup).

3. Core Flow: status → add → commit

Daily Git work is the loop "see changes → include them → snapshot."

3-1. Check the State

$ git status
On branch main
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        README.md

3-2. Stage Changes

$ git add README.md           # single file
$ git add src/                # whole directory
$ git add -p                  # interactive, hunk by hunk

git add . is convenient but easily picks up secrets like .env. Always run git status first.

3-3. Commit (Save the Snapshot)

$ git commit -m "Add README"
[main (root-commit) abc1234] Add README
 1 file changed, 5 insertions(+)
 create mode 100644 README.md

Mental model

  • status = "what's the current state?"
  • add = "include this in the next commit"
  • commit = "freeze this snapshot"

4. View History: log / diff

4-1. Commit History

$ git log --oneline --graph --decorate -10
* abc1234 (HEAD -> main) Add README
* def5678 Initial commit

A handy alias for daily use:

$ git config --global alias.lg "log --oneline --graph --decorate --all -20"
$ git lg

4-2. Diffs

$ git diff                    # working tree vs stage
$ git diff --staged           # stage vs HEAD
$ git diff HEAD~1 HEAD        # previous commit vs current
$ git diff main..feature      # between branches

4-3. File-Level History

$ git log --follow -p src/index.js

--follow traces the file across renames.

5. Work with Remotes: push / pull

5-1. Add a Remote

$ git remote add origin git@github.com:user/repo.git
$ git remote -v
origin  git@github.com:user/repo.git (fetch)
origin  git@github.com:user/repo.git (push)

5-2. First Push

$ git push -u origin main

-u (--set-upstream) lets you omit origin main for subsequent git push / git pull commands.

5-3. pull / fetch

$ git pull              # fetch + merge automatically
$ git fetch             # download without merging
$ git log HEAD..origin/main   # show what's ahead of you

Common pull accident in team work

  • git pull runs an automatic merge, so conflicts with local changes are easy to trigger.
  • When unsure, split into three steps: git fetchgit log HEAD..origin/maingit merge.
  • For rebase workflow, consider git config --global pull.rebase true.

6. Set Up SSH Key Authentication

6-1. Generate a Key Pair

$ ssh-keygen -t ed25519 -C "you@example.com"
$ cat ~/.ssh/id_ed25519.pub

ed25519 is the modern default — fast and compact. Use -t rsa -b 4096 only for legacy systems that don't support ed25519.

Always set a passphrase to limit damage if the key leaks. Load it into ssh-agent so you don't type it every time.

6-2. Register the Public Key on GitHub / GitLab

Copy the contents of ~/.ssh/id_ed25519.pub (the .pub file) and add it under Settings → SSH and GPG keys. Never share the private key id_ed25519.

6-3. Test the Connection

$ ssh -T git@github.com
Hi user! You've successfully authenticated, but GitHub does not provide shell access.

This message means success (the exit code is 1, but that's expected).

7. .gitignore: Linux-Specific Patterns

# OS / editors
.DS_Store
*~
.*.swp
.idea/
.vscode/

# Language runtimes
node_modules/
__pycache__/
*.pyc
target/
build/
dist/

# Secrets (most important)
.env
.env.*
*.key
*.pem

# Logs
*.log
logs/

The official github/gitignore repo has language-specific templates. When in doubt, copy from there.

8. Common Linux-Specific Pitfalls

8-1. Permission Denied on Push

ERROR: Permission to user/repo.git denied to other-user.
fatal: Could not read from remote repository.

Likely causes:

  • The SSH key isn't registered on GitHub
  • Multiple keys are loaded and the wrong account's key is used first
  • You don't have write access (not a collaborator)

Diagnosis:

$ ssh -T git@github.com         # which user are you authenticated as?
$ ssh-add -l                    # list keys loaded in ssh-agent

For multiple accounts, use ~/.ssh/config to assign keys per host.

8-2. Mixed Line Endings (CRLF / LF)

Files coming from Windows can poison the diff (entire files appear changed).

# On Linux / macOS: keep line endings as-is (recommended)
$ git config --global core.autocrlf input

To pin the rule per repo, add .gitattributes:

* text=auto eol=lf
*.sh text eol=lf
*.bat text eol=crlf

8-3. The Executable Bit (chmod +x) Isn't Recorded

$ chmod +x scripts/deploy.sh
$ git status      # may show no change

Force Git to record the mode change:

$ git update-index --chmod=+x scripts/deploy.sh
$ git diff
old mode 100644
new mode 100755

If core.fileMode = false is set in the repo, Git ignores mode changes. Verify with git config core.fileMode.

8-4. Filename Case Differences

The Linux filesystem (ext4 etc.) is case-sensitive, but macOS and Windows default to case-insensitive. Mistaking Readme.md for README.md will break Linux-based CI.

# Detect case mismatches across the repo
$ git config --global core.ignorecase false

A naming convention in CONTRIBUTING.md plus PR review is the realistic fix.

9. Safe Templates

First-time setup

git init
echo "node_modules/" > .gitignore
git add README.md .gitignore
git commit -m "Initial commit"
git remote add origin git@github.com:user/repo.git
git push -u origin main

Daily loop

git status
git diff
git add -p              # review and stage hunks
git commit -m "feat: explain the change"
git push

Undo operations (least to most destructive)

git restore <file>           # discard working-tree changes
git restore --staged <file>  # unstage (file content kept)
git commit --amend           # fix the last commit (only before push)
git revert <commit>          # create an inverse commit (safe after sharing)

Never do this

  • git push --force on a shared commit (rewrites everyone's history)
  • git reset --hard without confirming (uncommitted work disappears)
  • Commit .env, private keys, or auth tokens (very hard to remove from history)
  • --no-verify to skip hooks (silently swallows lint or test failures)

Next Reading