<-

# j3s’s git notes / guide / necronomicon

for somewhat experienced computer-ers who want to git good

git is a complicated system, but i hope to give you an understanding that is sensible. this guide is mainly for people who have a grasp on the command line and some fuzzy git concepts in their head, and want to turn that knowledge into a functional understanding.

i don’t care about fully understanding git from head to toe, i am more concerned with the practical usage and application of the system than anything. most guides teach you about how git uses SHA1SUMs under the hood and blah blah blah. i don’t really care, i just want to show you how i use the system and go over some very common road bumps. you can read a 200 page thesis on the inner workings if you want - but i mostly don’t care.

i will teach you only what you must know to use git alone, and then with other people.

git is a distributed version control system

git is a distributed version control system. the word distributed here really just means that when you clone a git repository, your local copy is exactly as complete as the repo you cloned it from - the full # of commits, all the refs, every little freckle and scar is there.

when people say the word git, they generally mean one of three things:

j3s’s recommended barebones git config

you can configure git via a file: $HOME/.git/config or $HOME/.config/git/config

these are normally preferences, but I highly recommend the following bare-bones config before you even start using git, to fix some questionable defaults:

[user]
name = Your Name
email = you@example.org

# to find your SMTP settings, duckduckgo for "email-provider-name SMTP" (for example "gmail SMTP")
[sendemail]
smtpserver = mail.example.org
smtpuser = you@example.org
smtpencryption = tls
smtpserverport = 587

[advice]
detachedhead = false

[merge]
ff = only

Foundational Knowledge: repositories, refs, and commits, oh my!

there are many fundamental concepts in git, and they’re often very confusing because they’re not intuitively named. please read these definitions carefully.

repositories

a git repository is just a pile of commits, and some refs.

most git work is local work

no command used in this document besides git push or git fetch affects anything other than our local git repositories - the ones on our own filesystem.

commits

a commit is a simple record of who made the changes, and what & when the changes were made

each git commit gets a unique ID - it looks like this: 3c622a6860bfb21cffcd3bb51066c7b780206ca3

or, some people use the first 7 or 8 characters of the commit ID as a shorthand: 3c622a6

example of making a git commit while inside a git repository:

# first, we will make a commit:

$ echo "hello world!" > file
$ git add file
$ git commit -m 'add file'
[master (root-commit) 94686b3] add file
 1 file changed, 1 insertion(+)
 create mode 100644 file

example of viewing a full git commit:

$ git log -p -2
commit 94686b38ed654c2a809a68e4e207cc1819c5764d (HEAD -> master)
Author: j3s <j3s@c3f.net>
Date:   Mon Jan 4 13:02:09 2021 -0600

    add file

diff --git a/file b/file
new file mode 100644
index 0000000..a042389
--- /dev/null
+++ b/file
@@ -0,0 +1 @@
+hello world!

that’s it, the entire concept of a commit. you can see the who (made the commit), what (was committed), when (was it committed), and why (it was committed).

if the “what” portion (changes made, aka diff) seem a little cryptic at first, don’t worry, it is. know that there are much nicer ways to view git diffs.

commit ids

you might refer to this commit as 94686b38ed654c2a809a68e4e207cc1819c5764d or 94686b38 for short.

git checkout

the git checkout command simply moves you between commits. you can either reference short commit ids, long commit ids, or refs (which you’ll learn about now-ish).

Q&A

wtf was git add about?

in order to commit changes, git wants to know which ones to commit. this is so you may break your changes up into little pieces, if you’d like. for example, if you have changed ten files, you may only want to commit files 1 and 3. you may use git add to “stage” only those changes, followed by git commit -m "commit message" to commit them.

i’m lost

use git status in a repo at any time to get an overview of where you’re at.

ask me questions - j3s@c3f.net (email) or @j3s:cyberia.club (matrix)

how are these commit records ordered?

this is quite simple. every commit references only the commit it was placed on top of. you can follow this chain all the way back to commit #1 - it’s a sequential line.

refs

referring to a commit by ID really sucks. so refs are a thing.

you can think of a ref like a CNAME in DNS, or a symbolic link (ln -s) basically, just a human friendly name for a thing.

refs simply point to commit IDs. easy peasy.

i-love-this-commit -> 94686b38

development-test-1 -> 94686b38ed654c2a809a68e4e207cc1819c5764d

there are two super important types of refs - branches and tags.

branches

branches always point to exactly 1 commit.

however when commits are made while you are on a branch, the branch updates the commit it is referencing to the commit you have just made.

for example:

# see the commit ID that our current branch is referencing
$ git show-branch --sha1-name
[94686b3] add file

# make a new commit

$ echo 'hello world, again??' > file2
$ git add file2
$ git commit -m 'add file2'


# see that the commit our branch is referencing has changed auto-magically
$ git show-branch --sha1-name
[9e379d4] add file2

Q&A

what is this git checkout thing?

you may use git checkout to move between commits. you can target either a commit ID or a ref.

for example, these are all valid:

git checkout my-branch git checkout some-tag git checkout 94686b38

remember, that if you check out a commit ID or a tag, any commits you make from there will fallinto the aether. you almost always want to be checking out branches.

if i run git init, what is the default branch name?

master. there is nothing special about branches named master, it just happens to be the default. most people use this branch as the “source of truth”, but varies a shit ton and i wouldn’t rely on that to be true.

what if i don’t commit to a branch?

if you do not commit while you have a branch checked out, your commit will still be valid - it’ll fall into the pile of commits per usual.

however, if you do not eventually reference this commit (via either a branch or a tag) it will be automatically garbage collected and removed from your repository.

git intends for you to use branches while developing.

what is this HEAD thing?

HEAD is just a ref to the currently checkout commit. normally, that’s the latest commit on a branch. sometimes it’s a particular commit that you’ve checked out for whatever reason. If it helps, you can think of it the same way you'd think of "." in the terminal.

wtf is a “detached HEAD”

an unfortunate name, tbh. a detached head just means you’ve checked out a commit that is not the tip of a branch. that’s it.

the only thing you’re “detached” from is the latest commit of branches. so your current HEAD is detached from the branch. get it?

tags

tags are refs that are almost exclusively used to point to commits that are known to be stable, and are used for versioning purposes.

critically, tags never update or change in any way after they’re created.

there are several types of tags - everyone uses annotated tags. almost everyone refers to “annotated tags” as “tags.” you’re unlikely to need to use or learn about other types of tags.

annotated tags have a special property: they can store a message, just like commits can. this can be used to include changelog info, or signoff info, or to draw ascii art.

to make an annotated tag:

# hey! i have a commit i really like.
# it's called `9e379d4`.

# first, checkout the commit you like so much:
$ git checkout 9e379d4
HEAD is now at 9e379d4 add file2

# now, make an annotated tag (the only kind of tag anyone cares about):
$ git tag -a i-really-like-this-particular-commit -m "yeah it's lit"

# you can now checkout your new funky tag
$ git checkout i-really-like-this-particular-commit

# note that you will be in the "detached head" state after
# checking out your tag. this isn't bad, but git would make
# you believe it is.

⚠️WARNING⚠️

remember, when you check out a tag, any commits that you make will just fall into the commit abyss, and be deleted unless you reference them!

Foundational knowledge complete

congrats! you know a lot about git now. take a break pls, relax your shoulders & jaw and grab a cofvefe. then go to sleep. let this stuff sink in, and dream gently of how branches and tags are just refs. and how commits all pile up into a daemonic abyss…

tl;dr

concepts:

tools:

working with others

And now, for more annoying things: working with people. or: pushing, fetching, cloning, remotes, and merging

If you use git to maintain some personal projects on one machine, stop reading - you’re done!

But if you care to put your work on a server for people to utilize (including yourself), or you want to help work on someone elses project, I recommend reading on.

remote confusion

there are three absolute sources of confusion with working with remote repositories, and i want to address them up front.

ONE: git is almost completely local

people often think that git commands will interact with servers, in reality this is very rare.

no git commands in this guide interact with servers except git push and git fetch.

almost all git commands are local.

TWO: remote repositories are tangled with your local repositories.

remote repositories are not tangled with your local repository. even if you git clone a remote repository, the local clone is entirely independent.

the short name for “remote repositories” is “remotes”

remember that repositories include all refs and commits, so fetching a remote means you get all of its refs and commits as well. many people think that “remotes” refers to branches or servers - not so! remotes are simply repositories that are somewhere that isn’t your current repository.

remotes and fetching and pushing

remotes are, quite simply, git repositories that are somewhere else.

you must access remote branches somehow - the typical methods are via HTTPS, FTP, local filesystem, or SSH. therefore, remote URLs will look something like this:

https://git.sequentialread.com/forest/threshold.git
git@giit.cyberia.club:~vvesley/terminal_shit # this is ssh in disguise
ssh://git@giit.cyberia.club/~starless/fate-discord-bot # this is the full ssh URL
/home/j3s/code/shitchat

note that HTTPS and FTP URLs are almost always read-only. if you want to push, use the SSH URL

adding remotes

we can add a remote to our test repository like so:

$ git status
On branch master
nothing to commit, working tree clean

$ git remote add terminalshit https://giit.cyberia.club/~vvesley/terminal_shit

$ git remote -v
terminalshit    https://giit.cyberia.club/~vvesley/terminal_shit (fetch)
terminalshit    https://giit.cyberia.club/~vvesley/terminal_shit (push)

$ git fetch terminalshit
warning: no common commits
remote: Enumerating objects: 19, done.
remote: Total 19 (delta 0), reused 0 (delta 0), pack-reused 19
Unpacking objects: 100% (19/19), 14.73 KiB | 457.00 KiB/s, done.
From https://giit.cyberia.club/~vvesley/terminal_shit
 * [new branch]      master     -> terminalshit/master

as you can see, there’s nothing very special about remotes at all. they’re very dumb. you just add them and fetch them. fetching just gets the latest version of one of your remotes.

once remotes are fetched, you can use them locally, not so remote after all!

$ ls
file  file2

$ git branch --all
* master
  remotes/terminalshit/master

$ git checkout remotes/terminalshit/master
HEAD is now at 8e21382

# HELPFUL j3s TIP! you can just type "git checkout terminalshit/master" here,
# remotes/ is implied

$ ls
LICENSE.md  README.md  main.go

in practice, you would probably never add the remote URL of an unrelated repository. but i wanted to illustrate a point - that remotes are in no way tied to your own local branches. they are completely, 100% independent.

let’s undo that and add a remote that actually makes sense.

changing remotes

say you’ve added a wrong remote and want to change the URL.

One way is to simply remove the bad remote and add a good one:

$ git checkout master
Switched to branch 'master'

$ git remote rm terminalshit

$ git remote add origin git@giit.cyberia.club:~j3s/git-example # i add an SSH URL because I want to write to this repo.

$ git branch -a
* master
  remotes/origin/master

$ git remote -v
origin  git@giit.cyberia.club:~j3s/git-example (fetch)
origin  git@giit.cyberia.club:~j3s/git-example (push)

i used the Cyberia Forge to make a blank remote git repository, and got the URL from its’ interface.

since we have write access, we can easily push our changes to the remote:

# for reasons that we may never know,
# the syntax of this command is `git push <remote-name> <branch-name>`

$ git push origin master

Enumerating objects: 6, done.
Counting objects: 100% (6/6), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (6/6), 443 bytes | 443.00 KiB/s, done.
Total 6 (delta 0), reused 5 (delta 0), pack-reused 0
To giit.cyberia.club:~j3s/git-example
 * [new branch]      master -> master

woo! we’ve stored our changes remotely. we can now do infinite stage, commit, push loops forever and ever… unless???

merging

merging is simple in theory. basically, git takes two branches and attempts to make them one branch by identifying their common history and zipping them together - kind of like if you were to mash two trolli crawler worm snacks together.

merging is best explained by doing. please follow along with this example and get a feel for switching between branches and merging them into each other.

we’ll use the git merge tool - the syntax is git merge <branch>.

it will merge whatever branch you target into your current branch.

of course, you may target remote branches.

# first, we will make a git repository, put a file in it, and make our first
# commit
$ cd /tmp
$ git init merge-test
Initialized empty Git repository in /tmp/merge-test/.git/
$ cd merge-test
$ echo blah > file1
$ git add file1
$ git commit -m 'add file1'
[master (root-commit) ab1b602] add file1
 1 file changed, 1 insertion(+)
 create mode 100644 file1
 
# next, we will flip to a different branch and make a commit over there
$ git checkout -b different-branch
Switched to a new branch 'different-branch'
$ echo blahblah > file2
$ git add file2
$ git commit -m 'add file2'
[different-branch d8b4a32] add file2
 1 file changed, 1 insertion(+)
 create mode 100644 file2
 
# finally, we switch back to our master branch and check out our log -
# you can see that file2 is missing. we merge different-branch into
# our master branch with "git merge different-branch".
$ git checkout master
Switched to branch 'master'
$ git log
commit ab1b6025ea49c40e5ef4acf68272d0dd9e4de963 (HEAD -> master)
Author: j3s <j3s@c3f.net>
Date:   Tue Jan 5 16:54:55 2021 -0600

    add file1
$ git merge different-branch
Merge made by the 'recursive' strategy.
 file2 | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 file2
$ git log
commit 90c66a1a6fe29073042c1fd2b391640e2b81f4ca (HEAD -> master)
Merge: ab1b602 d8b4a32
Author: j3s <j3s@c3f.net>
Date:   Tue Jan 5 16:55:37 2021 -0600

    Merge branch 'different-branch'

commit d8b4a323726f2dde3e8edfe6cf9d09bc90c6c19f (different-branch)
Author: j3s <j3s@c3f.net>
Date:   Tue Jan 5 16:55:23 2021 -0600

    add file2

commit ab1b6025ea49c40e5ef4acf68272d0dd9e4de963
Author: j3s <j3s@c3f.net>
Date:   Tue Jan 5 16:54:55 2021 -0600

    add file1

You will note that merging branches causes an extra commit to be generating detailing the merge - that is good! it’s mostly clerical.

eventually, possibly even already, you’ll run into the dreaded…

A WORD OF MERGEY WARNING, OR: WHY FF is GOOD

ok, let me stop for a sec and address something quick.

git merge doesn’t always require a nice commit message. sometimes, it can detect a very particular situation.

let’s say that you have cloned a remote repository, like shitchat.

let’s say you did this in the year 1974.

$ date
Tue 05 Jan 1974 05:07:25 PM CST
$ git clone https://giit.cyberia.club/~j3s/shitchat

and now it’s the year of our lord, $CURRENT_YEAR, and you want to pull in all of the latest shitchat updates! well, you git fetch from your remote of course!

$ date
Tue 05 Jan 2077

$ git fetch origin
remote: Enumerating objects: 514, done.
remote: Counting objects: 100% (444/444), done.
remote: Compressing objects: 100% (358/358), done.
remote: Total 385 (delta 205), reused 1 (delta 0)
Receiving objects: 100% (385/385), 104.62 KiB | 3.87 MiB/s, done.
Resolving deltas: 100% (205/205), completed with 17 local objects.
From https://giit.cyberia.club/~j3s/shitchat
   61ae285..757305d  master                         -> origin/master

$ git merge origin/master
Updating c1466a6..3994c88
Fast-forward
 README.md | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 README.md

Wait, Fast-forward? why didn’t i get to type a commit message?

in this case, git noticed that the branches you’re merging have common ancestry - and that one of them (your local copy) can simply be sped up, so that it’s up to date with the remote! no merge necessary, since they weren’t merged at all - they’re two identical (but separate) repos!

if you own two copies of harry potter: the wizard and one is half complete, you wouldn’t tear the ending out of the good copy out to fix the incomplete copy, you’d just finish the incomplete one with a pen. that’s basically how fast-forwards work. no commit message necessary.

BUZZFEED ARTICLE: IS GIT PULL PROBLEMATIC???

git pull has a troubled past.

people will tell you to use git pull - don’t listen to those people unless you know this wisdom:

I highly recommend using this methodology first. fetch and merge.

see https://stackoverflow.com/questions/15316601/in-what-cases-could-git-pull-be-harmful

by default, git pull can have many dreaded consequences - the worst of all is leaving inconsistent nonlinear commits fucking everywhere.

i recommend using fetch followed by merge to get a grasp on the individual steps (since they can sometimes be confusing) - and then transtioning to git pull after a few weeks/months once you have dealt with various edge cases.

tl;dr: don’t use git pull if you’re new to git, or it’ll cost you a lot of time/work.

tldr;tl;dr: use git fetch && git merge, forget git pull.

MERGE CONFLICTS

welp, things were going fine. git seemed easy and intuitive. but then… well…

when git tries to merge and cannot automatically do it, it requires human intervention. this is called a “merge conflict”. let us demystify.

like merging, merge conflicts are easier to demonstrate than describe.

First, we’ll initialize 2 repositories with identical histories.

$ git init repo1
Initialized empty Git repository in /tmp/repo1/.git/
$ cd repo1
$ echo 'blahblahblah' > file1
$ git add file1
$ git commit -m 'add file1'
[master (root-commit) 74a9348] add file1
 1 file changed, 1 insertion(+)
 create mode 100644 file1
$ cd ..
$ cp -a repo1 repo2

Now, we’ll make their master branches have different commits.

$ cd repo2
$ echo 'this is shitty code' >> file1
$ git add file1
$ git commit -m 'i have consumed too much alcohol and produced suboptimal *hic* code'
[master 3211e8d] i have consumed too much alcohol and produced suboptimal *hic* code
 1 file changed, 1 insertion(+)
$ cd ../repo1
$ echo 'shit is amazing code' >> file1
$ git add file1
$ git commit -m 'i have consumed stimulants and am soulless but produce good code'
[master 9099012] i have consumed stimulants and am soulless but produce good code
 1 file changed, 1 insertion(+)

now, we will simply add repo2 as a remote for repo1! and we’ll try and merge their histories, which of course cannot work.

$ git remote add repo2 ../repo2
$ git fetch repo2
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 271 bytes | 271.00 KiB/s, done.
From ../repo2
 * [new branch]      master     -> repo2/master
$ git merge repo2/master
Auto-merging file1
CONFLICT (content): Merge conflict in file1
Automatic merge failed; fix conflicts and then commit the result.

NOW we’re in it. a weird state. HELP! what does git status say???

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
    both modified:   file1

no changes added to commit (use "git add" and/or "git commit -a")

okay, fix conflicts. let’s pop open the file:

$ cat file1
blahblahblah
<<<<<<< HEAD
shit is amazing code
=======
this is shitty code
>>>>>>> repo2/master

git inserted a bunch of shit.

basically, everything between <<<<<<< HEAD and ======= is your current branch. everything between ======= and >>>>>>> repo2/master is repo2/master stuff.

your job is to pick the right stuff, remove the <<<< and >>>> and ==== parts, and then type git add file1 and git commit. so, let’s do it.

$ cat file1
blahblahblah
shit is amazing code
$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
    both modified:   file1

no changes added to commit (use "git add" and/or "git commit -a")
$ git add file1
$ git commit
[master 24d690d] Merge remote-tracking branch 'repo2/master'

voila, merge conflict resolved. if this sort of thing ever gets horribly complicated (and god, it does) - use a good GUI, it helps IMMENSELY.

check out how cute our commit history looks after that though:

*   24d690d - Merge remote-tracking branch 'repo2/master'  (HEAD -> master)
|\
| * 3211e8d - i have consumed too much alcohol and produced suboptimal *hic* code  (repo2/master)
* | 9099012 - i have consumed stimulants
|/
* 74a9348 - add file1

hopefully, you’re able to reason about the above commit graph!

cloning

okay, fine, good. we’ve already cloned a whole bunch but let’s sorta cover it.

first order of business, clone a repo.

git clone https://giit.cyberia.club/~j3s/git-example
Cloning into 'git-example'...
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), 423 bytes | 47.00 KiB/s, done.

What happened here, exactly? well, it turns out that git clone does a shit ton of things.

git clone:

We can see the results:

$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

you’ll note that the remote HEAD is pointing at the remote master branch. this is almost always goin to be the case, feel free to read the HEAD section again for why this makes sense.

summary 2

ALRIGHT. we have covered a lot of confusing ground. let’s summarize.

workflow examples

working on your own project, but keeping a copy of the repository on a server somewhere

you might choose this workflow so that you can clone your repo from any computer you work on!

  1. clone remote repository
  2. change some files
  3. stage and commit your changes (remember, commits can only happen locally)
  4. push your changes

working on someone elses project

you might choose this workflow because you have to!

  1. clone remote repository
  2. change some files
  3. stage and commit your changes
  4. get your commits to them somehow (either via email patches, or pull requests)

a typical git workflow, working on your own project

  1. make a local repository
  2. make an initial commit to this repository, adding all the files you want to track
  3. add a remote to your repository
  4. push to the remote

etc

this is other useful stuff to know, in no particular order

git diff

I use git diff very often. i use git diff --staged just as often!

git diff will simply show you any unstaged changes that you’ve made. combined with git status it’ll give you confidence before you type git commit and inevitably commit some fucked up stuff.

help, i misspeldt some shit in my git commit

if you haven’t pushed yet, git commit --amend can fix up a local commit.

if you have already pushed, live with it. embrace it. maybe Randomly capitalize things to keep people on their toes.

pull requests

basically, a pull request is the diff of your branch and someone elses branch.

but how do you get this diff to them??? why, register an account on github of course!

pull requests have nothing to do with git. they were invented by github in an effort to proprietarily extend git and make you reliant on their service.

but that could be a lot of changes ?? yeah, well, pull requests are kind of a weird way of git contribution. but they are industry standard, so there’s that.

you normally don’t have write access to repos you want to contribute to, so github/gitlab/gitea/etc will have you:

  1. make an account
  2. “fork” (more jargon) someones repository
  3. clone your fork
  4. make changes locally
  5. push those changes to your fork
  6. make a pull request via the web GUI
  7. the pull request will display the difference between your branch and theirs
  8. maintainers can “merge” your branch in, if there are no merge conflicts

that’s the basic process. it’s not too bad but tbh but doesn’t make a lot of sense to compare entire branches when you just want to get them a little commit... but the sea knows no sense and the ocean hears no reason.

github was good for free software awhile ago, and they’re still pretty cool i think.

even still, i try not to use github nowadays. github has an industry monopoly on git repositories, which were meant to be distributed. they also invented the concept of a “pull request” and really not much else.

bare repositories

“normal” git repos are called “working repos”. weird git repos are called “bare repos”.

bare repos and working repos are both fine, but normally:

the reasons are pretty boring. read this

recursion, but upwards, though

the git cli will search all parent directories for a .git directory. if it finds one, it will try and use it.

example:

$ pwd
/tmp/garbage
$ git init
Initialized empty Git repository in /private/tmp/garbage/.git/
$ mkdir some-directory
$ cd some-directory/
$ git status
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)

.gitignore

you can make a .gitignore file in the root of your git repo. this file tells git what to not track. you can use globs. so a .gitignore file might look something like this:

.DS_STORE
huge-compiled-binary
.editor-preferences
python-libs/*

assuming you didn’t want to store the python libs in git. probably not.

can i store bigass non text files in git?

you can, you might, but you probably shouldn’t. if those binaries change, you definitely shouldn’t. but idk, store your family photo albums in git, it’ll be slow but who cares. i’m not ur boss.

Miscellaneous Stuff

I have horribly fucked up this git repository, but a remote repository exists in a good state

cd ..
rm -rf your-git-repo
git clone remote-git-repo

I have a merge conflict and have no idea how to procede

use git status to find troubleshooting details:

git status

I want to contribute to a project that uses a mailing list

First, set up git-send email. Either use the config at the top of this document or use https://git-send-email.io/ to guide you.

Then;

git clone https://giit.cyberia.club/~j3s/git-example
cd git-example
echo "CHANGES!" >> file1
git add file1
git commit -m 'change file1'
git send-email --to="mailing-list@example.org" HEAD^ # HEAD^ is a shortcut for "the latest 1 commit"

i accidentally pushed a secret to a public repo

public repos are scraped constantly, the best possible option is to rotate the credential immediately and leave the old credential in git.

the second best option is to force overwrite history lololol but nobody does that, right?