Where to start First steps track · 15 stages
Linux Labs

git: change history, undo and a remote repository

15 min git 2.x

A version control system: who changed what in the files, how to get back a working version and exchange changes.

Cheat sheet

12 commands Printable version
  • git status what changed, which branch
  • git diff uncommitted changes
  • git add -A ; git commit -m "msg" commit
  • git log --oneline -10 short history
  • git log -p -- site.conf a file's history with changes
  • git blame site.conf who changed each line, and when
  • git restore site.conf throw away an uncommitted change
  • git revert --no-edit <commit> undo a commit with a new commit
  • git fetch ; git status what is new in the remote
  • git pull get and merge
  • git push send your commits
  • git stash ; git stash pop shelve a change and bring it back

Overview

git is a version control system. A repository is the .git directory next to the working files: it holds the whole history. A commit is a snapshot of the files with an author, a date, a message and a hash (c9ab4a3). Admins use git for configs: a directory like /etc/nginx under git shows who changed what and gets the working version back with one command.

The work cycle: edit → git add → git commit → git push.

bash
git init -b main                  # a new repository, branch main
git clone /srv/git/site.git       # a copy of a remote repository
git add site.conf                 # the change goes into the next commit
git commit -m "site: listen 8080" # commit

Without -b main git 2.43 names the first branch master and prints a long hint hint: Using 'master' as the name for the initial branch.

Options

20

Below are the main git subcommands; the full list is in man git, git help <command>.

Option What it does Example
status What's changed, what's staged, which branch you're on. git status
add Stage changes for the next commit. git add -A
commit Record staged changes as a new commit. git commit -m "message"
push Send local commits to the remote repository. git push
pull Fetch and merge changes from the remote repository. git pull
log Show the commit history. git log --oneline -10
diff What changed in the working files since the last commit; with two commits — the difference between them. git diff HEAD origin/main
fetch Get new commits from the remote repository without touching the working files. git fetch
clone A copy of a repository with working files; the remote gets the name origin. git clone /srv/git/site.git
switch -c <name> Switch to a branch; with -c, create it and switch immediately. git switch -c feature
merge Merge the given branch into the current one. git merge feature
restore Discard changes to a file, restoring it from HEAD (or the index with --staged). git restore file
revert Create a new commit that undoes a given one — safe for shared history. git revert <commit>
stash Shelve uncommitted changes to bring them back later. git stash
reflog A log of HEAD movements — helps find "lost" commits. git reflog
show One commit with its change; <commit>:<file> — the file as it was in that commit. git show HEAD~2:site.conf
blame Who last changed each line of a file, and in which commit. git blame site.conf
init A new repository; -b main sets the first branch name, --bare means no working files. git init --bare -b main /srv/git/site.git
reset --hard|--soft Move HEAD; --hard discards uncommitted changes with no way back. dangerous git reset --hard origin/main
clean -fd Delete untracked files and directories with no way to recover them. dangerous git clean -fd

Nothing found. Try a shorter query.

Recipes

7

History: log -p, show, blame

The /etc/demo repository with three commits by two authors:

text
student@node1:/etc/demo$ sudo git log --format='%h %an %ad %s' \
> --date=short
c9ab4a3 Boris Ops 2026-10-09 site: tune listen
4c69b7d Boris Ops 2026-10-05 site: move root to /srv/www
f7de9e0 Anna Admin 2026-10-01 site: initial config

git log -p adds each commit's change; with -1 only the last one, with -- <file> only the commits touching that file:

text
student@node1:/etc/demo$ sudo git log -p -1 -- site.conf
commit c9ab4a3032a1cf6f1f4b1fee565958cfaf2f67c4
Author: Boris Ops <boris@lab.local>
Date:   Fri Oct 9 09:15:00 2026 +0000

    site: tune listen

diff --git a/site.conf b/site.conf
index d988f4e..f671dff 100644
--- a/site.conf
+++ b/site.conf
@@ -1,4 +1,4 @@
 server {
-    listen 80;
+    listen 8O;
     root /srv/www;
 }

- is the line before the commit, + after it: 80 became 8O with the letter O. git show <commit> prints one commit the same way, with --stat only the list of files. git blame labels each line of a file with the commit that last changed it:

text
student@node1:/etc/demo$ sudo git blame site.conf
^f7de9e0 (Anna Admin 2026-10-01 10:00:00 +0000 1) server {
c9ab4a30 (Boris Ops  2026-10-09 09:15:00 +0000 2)     listen 8O;
4c69b7d7 (Boris Ops  2026-10-05 14:30:00 +0000 3)     root /srv/www;
^f7de9e0 (Anna Admin 2026-10-01 10:00:00 +0000 4) }

A ^ before the hash marks the repository's first commit. An uncommitted change shows in git status (modified: site.conf) and git diff.

Undo a change and a commit: restore, revert, reset

What to undo Command History
an uncommitted change to a file git restore site.conf unchanged
one commit git revert --no-edit <commit> a new commit on top
a file, back to an old commit's version git restore --source=<commit> site.conf unchanged, the change is in the working file
the branch, back to an old commit git reset --hard <commit> the commits after it leave the branch

revert makes a commit that is the reverse of the chosen one — the history stays, and it shows who undid what and when:

text
student@node1:/etc/demo$ sudo git revert --no-edit HEAD
[main 107f37c] Revert "site: tune listen"
 Date: Wed Oct 21 11:54:18 2026 +0000
 1 file changed, 1 insertion(+), 1 deletion(-)

The message of such a commit says This reverts commit c9ab4a30…. You can undo an old commit too (git revert --no-edit HEAD~2): git removes only its change and keeps the later ones. To look at a file from an old commit without changing anything — git show HEAD~3:site.conf.

reset --hard rewrites the branch: the commits after the chosen one leave it, and uncommitted changes are gone for good. git reflog remembers the commits that left:

text
student@node1:/etc/demo$ sudo git reflog | head -3
107f37c HEAD@{0}: reset: moving to HEAD~1
d01f2cc HEAD@{1}: revert: Revert "site: move root to /srv/www"
107f37c HEAD@{2}: revert: Revert "site: tune listen"

git reset --hard 'HEAD@{1}' takes the branch back to where it was before the reset. In a shared repository that others pull from, history is not rewritten — there you undo with revert.

A root-owned repository: sudo git

Files in /etc belong to root, and so does a repository in /etc/nginx. git commands there go through sudo, like editing the config. sudo git takes the author name and email from root's settings — your git config --global is not visible to it. So set them in the repository itself:

bash
sudo git -C /etc/nginx config user.name 'Lab Admin'
sudo git -C /etc/nginx config user.email 'admin@lab.local'

Without this sudo git commit and sudo git revert answer Author identity unknown — git. git without sudo in someone else's repository refuses to work — git.

A remote repository: fetch, pull, push

A bare repository is history only, with no working files: commits are pushed to it from different machines. A working copy on a server is a clone, and its remote is called origin:

bash
git init --bare -b main /srv/git/site.git   # the shared repository
git clone /srv/git/site.git ~/site           # a working copy
git remote -v                                # where origin points

git status compares the branch with what git knew about origin at the last exchange. It sees a colleague's new commit after git fetch:

text
student@node1:~/w1$ git fetch
From /srv/git/site
   b1ad7d1..8396b40  main       -> origin/main
student@node1:~/w1$ git status | head -2
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be
  fast-forwarded.

git log --oneline HEAD..origin/main shows which commits will come, git diff HEAD origin/main what they will change. git pull is fetch plus a merge; if you have no commits of your own, the branch simply moves forward (Fast-forward).

git push sends your commits. If origin has commits you do not have, push refuses:

text
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to '/srv/git/site.git'

git pull first, then git push again. If both you and origin have new commits, the branches have diverged, and git pull asks how to reconcile them — git.

Shelve a change: stash

git stash moves uncommitted changes to a separate store and brings the files back to the last commit — so you can get other people's commits when pull refuses because of a local change:

text
student@node1:~/w1$ git stash
Saved working directory and index state WIP on main: b1ad7d1 site: v1
student@node1:~/w1$ git pull
Updating b1ad7d1..8396b40
Fast-forward
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
student@node1:~/w1$ git stash pop
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
...
The stash entry is kept in case you need it again.

git stash list shows what is shelved (stash@{0}: WIP on main: …), git stash pop brings the change back and removes it from the store. Here the returned change touched the same line that came with pull — a conflict (git), and the entry stayed in the store. Remove it after resolving — git stash drop.

Conflict

A conflict means both sides changed the same lines, and git does not know which version to keep. It writes both into the file:

text
<<<<<<< Updated upstream
v2 colleague
=======
v1 local edit
>>>>>>> Stashed changes

Between <<<<<<< and ======= is the version already in the branch, between ======= and >>>>>>> the one coming in. After stash pop the markers say Updated upstream and Stashed changes, after pull — HEAD and the commit hash. git status shows such files as both modified.

To resolve:

  1. Open the file, keep what is needed, remove all three markers.
  2. git add <file> — the conflict is resolved.
  3. After pull — git commit (the merge message is ready); after stash pop no commit is needed, the change is simply back in the working files.

Changed your mind about merging — git merge --abort: the files go back to how they were before pull.

Branches

bash
git branch                        # list branches
git switch -c feature             # create and switch
git switch main                   # switch to it
git merge feature                 # merge a branch into the current one
git branch -d feature             # delete a merged branch

Troubleshooting

8

detected dubious ownership

text
student@node1:~$ git -C /etc/demo status
fatal: detected dubious ownership in repository at '/etc/demo'
To add an exception for this directory, call:

	git config --global --add safe.directory /etc/demo

The repository belongs to another user (root here), and git will not work in it under your name: someone else's settings in .git/config could run someone else's commands. A root-owned repository needs sudo git. A safe.directory exception only lets you read: student still cannot write to root's .git — fatal: Unable to create '/etc/demo/.git/index.lock': Permission denied.

Author identity unknown

text
Author identity unknown

*** Please tell me who you are.
...
fatal: unable to auto-detect email address (got 'root@node1.(none)')

The user making the commit has no name and email. The brackets show who it is: root here — because of sudo. Set them in the repository (git config user.name, git config user.email) — git.

Your local changes … would be overwritten by merge

text
error: Your local changes to the following files would be overwritten
  by merge:
	index.html
Please commit your changes or stash them before you merge.
Aborting

The working copy has an uncommitted change to a file the incoming commits change. Either commit it and pull, or shelve it with git stash, pull, and bring it back with git stash pop — git.

Need to specify how to reconcile divergent branches

text
hint: You have divergent branches and need to specify how to
  reconcile them.
...
fatal: Need to specify how to reconcile divergent branches.

Both you and origin have commits the other side lacks (git status: have diverged). Merge them with a merge commit — git pull --no-rebase; in a terminal git opens an editor with a ready message, --no-edit takes it without the editor. To choose for the future — git config pull.rebase false. After the merge — git push.

rejected … (fetch first)

origin has someone else's commits. git pull, resolve a conflict if there is one, and git push again — git.

refusing to update checked out branch

A push into an ordinary repository with working files: git does not change the branch checked out there, or its files would drift from the history. A shared repository is made bare — git init --bare.

Permission denied (publickey) on push

The SSH key is not set up or not on the server — see ssh-keys. Check git remote -v: an SSH or an HTTPS address.

Accidentally ran reset --hard

git reflog remembers where HEAD was — you can go back: git reset --hard <hash> (git).

Danger zone

2

git reset --hard and git clean -fd

reset --hard discards uncommitted changes to tracked files with no confirmation; git reflog only rescues what was committed — uncommitted edits lost to reset --hard cannot be recovered. git clean -fd removes untracked files and directories (including ones you forgot to add to git) — also with no trash can. Before clean, run git clean -nd (a dry-run that only shows what would be removed).

git push --force

Overwrites the remote branch's history: commits your colleagues already pulled "disappear" from the branch — they end up with diverging copies, and merging turns into a mess. For a shared branch, git push --force-with-lease is safer (it refuses if commits you didn't know about have appeared on the server).

Terms

4
git repository
The .git directory next to the working files: it holds the whole history — commits, branches, settings. git status and git log work inside a directory with such a .git.
commit
A snapshot of the repository files with an author, a date and a message. Each commit has its own hash (c9ab4a3), by which git show, git revert and git blame find it. Also appears in: GPG, WireGuard
bare repository
A git repository with no working files — history only. Commits are pushed to it from different machines; created with git init --bare, usually with a name ending in .git.
merge conflict
Both sides changed the same lines, and git cannot merge them by itself: it writes both versions into the file between the <<<<<<<, ======= and >>>>>>> markers and waits for an edit and git add.

See also

Loading…