Neoverse-Docs

1.15 Version Control and Git

Version control concepts, Git core workflow, and advanced operations covering branches, merges, undo, and remote collaboration

Primary author:
AI Summary of This Section

This section starts with the working tree, the staging area, and the commit history, and introduces Git's everyday committing, branch collaboration, conflict handling, and undo methods. The key point is to first determine where changes live, then choose a command that won't accidentally damage other content.

1. Version Control

Version control records the file states we deliberately commit, making it easy to compare, go back, and collaborate. It does not automatically save every keystroke, nor can it replace backups of uncommitted files.

1.1 Evolution of Version Control

StageRepresentative toolsFeatures
ManualCopying folders and renaming them v1, v2Crude and easy to mess up
Local version controlRCSSingle machine, no collaboration
CentralizedSVN, CVSMain history stored on a central server, limited offline capability
DistributedGit, MercurialEveryone has the full history, usable offline

1.2 What Is Git

Version control is a concept; Git is the most popular concrete implementation of it.

Git was created in 2005 by Linus Torvalds, the founder of the Linux kernel, and was originally used to manage a large codebase like the Linux kernel. It belongs to the "distributed" category in the table above and has two main characteristics:

  • Everyone has the full history: when you clone a repository, you get not only the latest files but the entire commit history. You can commit, view history, and create branches without a network, then sync when you are online.
  • Snapshot-based recording: each commit saves a snapshot of the files at that moment, rather than only recording diffs between files. That is why switching branches and returning to any historical version is fast.

Compared with centralized systems (such as SVN), clients no longer depend on a central server to view history or commit; a server outage does not make you lose your local history either. These features make Git the most widely used version control system today. The rest of this section focuses on Git's core workflow.

Why Use Git?

Actually, you don't have to use Git, or any source control system. You just need to meet these requirements:

  • Enough brainpower to know what you're doing, what you did a week ago, and why you did it that way
  • Enough patience to rewrite a whole day's code after accidentally deleting it
  • Enough ability to develop independently without deep collaboration, or you can accept syncing changes by sending code to each other over WeChat, email, and so on

These requirements are obviously very easy to meet just kidding.

Git offers us a better solution, such as:

  • git blame: see who wrote each line of code and when it changed
  • git log / git cherry-pick: roll back changes, or apply certain changes to other branches
  • git branch: develop different features on different branches so multiple people can work without interfering
—— This part is quoted from SAST Summer Training 2024 · Git.

2. Git Core Concepts

2.1 Three Areas

Git divides files into three areas; understanding these three areas is the key to mastering Git.

AreaDescriptionWhere it lives
Working treeThe directory we are editingActual files
Staging areaThe snapshot ready to be committed.git/index
Local repositoryCommitted version history.git/ directory
RemoteA repository hosted on a serverGitHub / CNB, etc.

INFO

The staging area lets us precisely control which changes are committed together. One round of work may touch several features; we can stage and commit several times so the history stays clear and readable.

2.2 Commits

A commit is the basic unit of Git; each commit records a full snapshot and contains:

  • The changed content
  • The author and the time
  • The commit message
  • References to parent commits; merge commits can have multiple parents

Commits form a directed acyclic graph through parent-child relationships. Without branching and merging, the history looks like a single timeline; once branches diverge and merge again, it becomes multiple interrelated lines of history.

2.3 Branches

A branch is a movable reference pointing to a commit. Developers can create a branch from the current commit, work on an independent line of history, and then merge or rebase it according to the project's workflow.

main is the main branch; feature forks from B, develops commits E and F independently, and is merged back into main when done.

Branches Are Cheap

A Git branch is just a reference, so creating one costs very little. Whether to create a branch for each piece of work should follow the repository's collaboration workflow; team projects usually use short-lived branches to isolate features, fixes, or experimental changes.

3. Initial Configuration

After installing Git (see 1.14 Windows Environment Setup or 1.14 WSL Environment Setup for installation), the first thing is to configure your identity:

Bash
# Configure the user name and email (included in every commit)
git config --global user.name "<Your Name>"
git config --global user.email "<Your Email>"

The integration strategy of git pull — merge, rebase, or fast-forward only — should be chosen by the team's convention; don't write it into the global configuration before you understand the difference. These options are explained later in this section.

Verify the configuration:

Bash
git config --list --show-origin

3.1 Three Levels of Configuration and Precedence

Git configuration can come from system-level, global-level, and repository-level files. A more specific configuration usually overrides a broader one:

LevelCommon filesScope
System/etc/gitconfig or a config file in the Git installation directoryAll users and repositories on the machine
Global~/.gitconfig or ~/.config/git/configAll repositories of the current user
LocalThe current repository's .git/configThe current repository

View the source of a configuration level:

Bash
git config --system --list --show-origin
git config --global --list --show-origin
git config --local --list --show-origin

--system may require administrator privileges; the command can fail if the file is missing or you lack permission. Project-specific settings should go into the local-level configuration, and personal defaults into the global level. When identity, proxy, or credentials are involved, confirm the current source before changing anything.

Associating Your Commit Email with an Account

If you want commits to be associated with your GitHub account, use an email verified in your GitHub account or the noreply email provided by GitHub (not recommended if you haven't added your personal email to your account). The commit identity and login credentials are two different things; before changing anything, you can check the locally configured email with git config --global --get user.email.

4. Basic Workflow

4.1 Method One: Start a Project from Scratch

Initialize and Push a Project from Scratch

First create an empty repository without an initial commit on a code hosting platform (such as GitHub or CNB), then run locally:

Bash
# Create a new directory and initialize the repository
mkdir my-project && cd my-project
git init

# Create a file
printf '# My Project\n' > README.md

# Check the status
git status

# Stage
git add README.md         # Stage a specific file

# Commit
git commit -m "Initial commit"

# Add the remote and push
git remote add origin https://github.com/YourUsername/ProjectName.git
git branch -M main
git push -u origin main

Verify: run git log --oneline; you should see the commit you just made.

4.2 Method Two: Clone an Existing Project

Bash
# Clone the remote repository locally
git clone https://github.com/User/ProjectName.git
cd ProjectName

Verify: run git remote -v; you should see the origin remote URL.

4.3 The Daily Loop

Most of the time we repeat this loop:

Bash
# Edit code...
git status              # See what changed
git diff                # See the specific changes
git add <file>             # Stage
git commit -m "<message>"     # Commit
git push                # Push to the remote

5. Viewing History and Diffs

5.1 git log: Viewing the Commit History

Bash
git log                 # Full history
git log --oneline       # One line per commit, concise
git log --oneline --graph  # Show branches as a graph (recommended)
git log -5              # The latest 5 commits
git log --author="SSJ"  # Filter by author

Every commit gets a unique hash (SHA-1); the a1b2c3d at the start of each line of git log --oneline is its abbreviation. The a1b2c3d in later command examples is just a placeholder — replace it with a real hash you find with git log.

5.2 git diff: Viewing Diffs

Bash
git diff               # Working tree vs staging area
git diff --staged      # Staging area vs latest commit
git diff HEAD          # Working tree vs latest commit
git diff v1 v2         # Diff between two commits

About the --graph Option

git log --oneline --graph --all shows the commit relationships between references as a text graph. If you use it often, set an alias: git config --global alias.lg "log --oneline --graph --all", then run git lg.

6. Selective Staging

You don't have to stage all changes at once; you can stage only part of them:

Bash
git add file1.txt file2.txt      # Stage specific files
git add -- '*.py'                # Stage .py files in the repository by Git pathspec
git add -p                       # Interactively choose hunks to stage (recommended)

About git add -p

git add -p (patch mode) splits the diff into hunks and asks per hunk whether to stage it. When one round of work mixes several purposes, you can use it to split commits; if a single hunk still contains two kinds of changes, you can split it further or go back to the editor and tidy up first.

7. Commit Message Conventions

Good commit messages keep the history readable and traceable. The Conventional Commits specification is recommended:

Text
<type>: <short description>

<optional detailed description>

Common types:

TypeMeaningExample
featNew featurefeat: add user login
fixBug fixfix: fix login page flickering
docsDocumentation changedocs: update README installation steps
styleFormatting (does not change logic)style: use 2-space indentation consistently
refactorRefactoring (not a feature or bug fix)refactor: extract user validation logic
testTeststest: add login unit tests
choreMaintenance such as build and dependencieschore: update build config

Example:

Text
feat: add user login

Implement JWT authentication and add the /login endpoint

This Project's Commit Conventions

This project also follows a similar convention; see Commit Messages.

Changes may need fixing before or after a commit. First determine whether the content is in the working tree, the staging area, or the commit history.

8. Undoing and Reverting

Run git status and git diff first to determine whether the changes are in the working tree, the staging area, or the commit history, then choose a command. Commands with similar names act on different scopes; don't rely on trial and error.

ScenarioSuggested commandRewrites history?
Unstage while keeping the working treegit restore --staged -- <file>No
Discard unstaged changes to a filegit restore -- <file>No, but the file content is lost
Fix the most recent commit that hasn't been sharedgit commit --amendYes
Undo a commit already pushed to the remotegit revert a1b2c3dNo, it creates a reverse commit

git restore Can Also Lose Content

git restore -- <file> overwrites uncommitted working tree changes. Check git diff -- <file> first; if you're not sure, copy a patch, create a temporary branch, or commit a recognizable temporary version first.

When Should You Use reset --hard?

git reset --hard moves the branch and overwrites both the working tree and the staging area at the same time; beginners usually don't need it. If you really need it, confirm the target commit, back up uncommitted content, and understand which references it rewrites; for history already pushed to the remote, prefer git revert.

9. Branching

9.1 Basic Branch Commands

Bash
git branch                    # List local branches
git branch -a                 # List all branches (including remote)
git branch feature            # Create the feature branch
git checkout feature          # Compatibility with old tutorials: switch to feature
git checkout -b feature       # Compatibility with old tutorials: create and switch
git switch feature            # Switch with the dedicated command
git switch -c feature         # Create and switch

git branch -d feature         # Delete a merged branch
git branch -D feature         # Force delete

9.2 Merging

Merge a branch's changes into the current branch:

Bash
git switch main               # Switch to the target branch first
git merge feature             # Merge feature

If the branch being merged already contains all of the current branch's commits, Git may perform a fast-forward: it only moves the current branch reference and creates no merge commit. If both branches have new commits after the fork point, Git usually performs a three-way merge based on the fork point and may create a merge commit; files that cannot be resolved automatically cause conflicts (see the next section). Whether to force a merge commit can follow the project convention with --no-ff.

Bash
git merge --ff-only feature   # Fast-forward only; fails if it can't, no merge commit
git merge --no-ff feature     # Create a merge commit even when fast-forwarding is possible

Fast-forward: when main has no new commits after the fork point

main simply moves its reference directly to the tip of feature (F), creating no merge commit.

Three-way merge: both branches have new commits after the fork point

The two branches advance separately after the fork point B; Git does a three-way merge based on the fork point and produces merge commit M.

9.3 Rebasing

rebase is another way to integrate changes: it "moves" our commits onto the tip of the target branch, keeping the history linear.

Bash
git switch feature
git rebase main               # Reapply feature's commits on top of main

Before rebase: feature forks from B, and main already has a new commit C

After rebase: E and F are moved and reapplied after C; the history changes from a fork to a straight line

After the rebase, feature looks as if it was developed from the tip of main (C); E' and F' are newly generated commits (their IDs differ from the original E and F). That's why you shouldn't rebase branches already pushed to the remote on your own.

MethodHistoryConflict handling
mergeKeeps the branch structure with merge nodesResolved once
rebaseLinear and cleaner historyMay need resolving per commit

Don't Rewrite Shared History on Your Own

rebase generates new commit IDs for the replayed commits. Branches that others have already built work on should not be rebased and force-pushed without coordination. Whether you may tidy up a personal branch and push with --force-with-lease depends on the project convention. As a beginner, practice on local branches that have not been pushed yet.

9.4 detached HEAD

Normally HEAD points to the current branch; after git checkout a1b2c3d or git switch --detach a1b2c3d, HEAD points directly at a commit and you are in detached HEAD state. You can inspect history or run temporary tests here, but new commits created directly don't belong to any branch; create a branch to save them before you leave:

Bash
git switch --detach a1b2c3d
# Inspect or test temporarily

git switch -c experiment       # Create a branch from the current commit and keep subsequent commits
# Or go back to an existing branch
git switch main

Check the current status:

Bash
git status

10. Resolving Conflicts

When two people change the same line of code, Git can't decide automatically which one to keep during a merge, so a conflict occurs.

Text
$ git merge feature
Auto-merging main.py
CONFLICT (content): Merge conflict in main.py

Open the conflicted file, and you will see:

Text
def hello():
<<<<<<< HEAD
    print("Hello from main")
=======
    print("Hello from feature")
>>>>>>> feature
  • From <<<<<<< HEAD to ======= is the current branch's content
  • From ======= to >>>>>>> feature is the incoming branch's content

To resolve: edit the file manually to keep the version you want (or combine both), delete the conflict markers, then:

Bash
git add main.py        # Mark the conflict as resolved
git commit             # Finish the merge

Resolving Conflicts Visually in VS Code

VS Code recognizes conflict markers and offers a merge editor or inline actions. Button names and the interface vary with the version and language settings; whichever interface you use, check the final file when you're done to confirm the conflict markers are gone, then run tests and git add.

11. Working with Remotes

Quick Reference for Common Remote Commands
Bash
# View remotes
git remote -v

# Add a remote (note: GithubName here is the real username, not a nickname!)
git remote add origin https://github.com/GithubName/ProjectName.git

# Change the remote URL
git remote set-url origin <new-url>

# Push
git push origin main           # Push the main branch
git push -u origin feature     # First push and set up tracking
git push --force-with-lease origin feature  # Carefully overwrite a remote branch; check remote-tracking state first

# Pull
git pull                       # Fetch remote updates, then merge, rebase, or fast-forward only per config
git fetch                      # Only update remote-tracking references; doesn't integrate the current branch
CommandBehaviorWhen to use
git fetchUpdates remote-tracking references without integrating the current branchWhen you want to inspect remote changes first
git pullFetches first, then integrates the current branch per flags or configWhen the merge, rebase, or fast-forward-only strategy is clear
git pushPushes local commits to the remoteTo sync after committing
git remote -vShows the remote URLsTo confirm the remote configuration

Fetch First, Then Merge

When you're not sure about remote changes, fetch first, then inspect branch differences with git log --oneline --graph --all or git diff. Before using git pull, make sure you know the repository's agreed integration strategy, to avoid accidentally creating merge commits or rewriting local commits.

12. Ignoring Files: .gitignore

Some files shouldn't be under version control (temporary files, secrets, build artifacts); use .gitignore to specify them:

Common .gitignore Template
.gitignore
# Build artifacts
*.o
*.exe
build/

# Dependencies
node_modules/
__pycache__/

# IDE local state; whether to ignore shared config is up to the team
.idea/

# System files
.DS_Store
Thumbs.db

# Sensitive information
.env
.env.*
!.env.example
*.pem

Don't Commit Sensitive Information

Private keys, passwords, access tokens, and config files containing them (such as .env) should never enter version control. Once sensitive information is committed, it may still exist in history, branches, caches, or clones even after being deleted; if you discover a leak, revoke or rotate the credentials immediately, then clean up the history following the platform's guidance. Template files without sensitive information (such as .env.example) can be committed as the project requires.

.gitignore only affects paths that aren't tracked yet; it can't automatically remove already-tracked files from version control. You can reference the github/gitignore templates and adjust them for your project's actual artifacts and editor configuration.

13. Useful Tricks

13.1 stash: Temporarily Save Changes

You're in the middle of work, need to switch branches to handle an urgent issue, and don't want to commit a half-finished state:

Bash
git stash push         # Save working tree and staging area changes of tracked files; untracked files are not included by default
git switch main        # Switch away to handle something else
# ...done...
git switch feature     # Switch back
git stash list         # Confirm the entry to restore
git stash pop          # Apply the latest entry and remove it from the stash list on success

13.2 tag: Tagging Releases

Tag versions when releasing:

Bash
git tag v1.0.0                 # Lightweight tag
git tag -a v1.0.0 -m "Release note"  # Annotated tag (recommended)
git push origin v1.0.0         # Push the tag to the remote

13.3 cherry-pick: Picking a Single Commit

When you want just one commit from a branch instead of merging it entirely:

Bash
git cherry-pick a1b2c3d

13.4 reflog: Finding Where References Pointed

reflog records where local references have pointed, which helps locate commits from before a mistake:

Bash
git reflog                         # View the local change history of HEAD
git show HEAD@{2}                  # Check the target content first
git branch recovery HEAD@{2}      # Create a recovery branch without touching the working tree

reflog Is Local Evidence, Not a Backup

Reachable reflog entries are usually kept for 90 days by default and unreachable entries for 30 days, though the actual retention is affected by configuration and garbage collection. It doesn't record content that was never committed, nor does it sync to the remote; important work still needs to be committed and backed up in time.

14. TODO Checklist

  • Can use git status and the two kinds of git diff to tell which area changes are in
  • Try using git add -p to organize a focused commit
  • Understand creating and merging branches and explain what conflict markers mean
  • Understand the purposes of restore, revert, reset, and reflog
  • Check the commit history, remote URL, and the branch to be pushed before pushing
  • Try creating or reading a project's .gitignore and identify sensitive files that should not be committed

15. Questions Worth Thinking About

What is the difference between rebase and merge? When should I use each?

merge keeps the branch structure and creates a merge commit, so the history faithfully records when branches joined; rebase reapplies commits on top of the target branch, producing a linear history but generating new commit hashes.

The main deciding factor is whether the history has already been shared: for branches that have been pushed or are being worked on by multiple people, prefer merge instead of rewriting history; for local branches that have not been pushed, choose according to the project convention — use rebase when you want a clean linear history. Conflicts are also handled differently: merge resolves them once, while rebase may require resolving them commit by commit as each one is replayed. The final choice still depends on the project's convention.

16. Further Reading

17. Videos Worth Watching

The Bilibili collection "从零开始深入 git" (UP: 寰宇牛奶unimilk, collection link, in Chinese) mapped to the structure of this section:

EpisodeTopicRelated sections on this page
01Git design philosophy and the history of version control1. Version Control; 2. Git Core Concepts
02Git installation and configuration; the three levels of config files3. Initial Configuration
03Basic repository usage, commits, file changes, and staging4. Basic Workflow; 6. Selective Staging
04Using Git in the IDE; commit message conventions5. Viewing History and Diffs; 7. Commit Message Conventions
05Multi-branch development: branching and the DAG2. Git Core Concepts; 9. Branching
06Multi-branch development: merging, conflicts, three-way, and fast-forward9. Branching; 10. Resolving Conflicts
07Git workflows, semantic versioning, branch models9. Branching; 13. Useful Tricks
08Rebase: tracking a branch with merge or rebase9. Branching
09amend, stash, detached HEAD, gitignore, revert, reset8. Undoing and Reverting; 12. Ignoring Files: .gitignore; 13. Useful Tricks
10Remote, clone, simple push/pull, and preparation for collaboration4. Basic Workflow; 11. Working with Remotes
11fetch, push, pull, force push, force-with-lease11. Working with Remotes
12Collaboration workflows for small teams, enterprises, and large open source projects11. Working with Remotes; 15. Questions Worth Thinking About
13-AGitHub account authorization, branch protection, PR, Issue, Releases1.16 Code Hosting Platform
13-BContributing to large open source projects1.16 Code Hosting Platform

On this page

Discussion

Welcome to share your thoughts and suggestions