Files
leecat.art/_site/an-intro-to-git/index.html

1957 lines
68 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>an intro to git | hello hello</title>
<meta name="description" content="Lee Cattarin... on the internet!">
<link rel="alternate" href="/feed.xml" type="application/atom+xml" title="hello hello">
<meta property="og:title" content="an intro to git">
<meta property="og:type" content="website">
<meta property="og:description" content="Lee Cattarin... on the internet!">
<meta property="og:site_name" content="hello hello">
<meta property="og:image" content="/img/goldeneye-tail.jpg">
<meta property="og:image:alt" content="Image unrelated to post. The tail of a diving duck pokes out from the water with a small splash.">
<meta name="generator" content="Eleventy v3.1.2">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible+Mono:ital,wght@0,200..800;1,200..800&family=Atkinson+Hyperlegible+Next:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet">
<script src="https://kit.fontawesome.com/884dded219.js" crossorigin="anonymous"></script>
<style>.post-metadata {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-items: baseline;
margin: 1.5rem 0 .5rem;
}
.post-metadata p {
font-size: .9rem;
margin: 0;
}
.post-tags {
display: flex;
flex-flow: row wrap;
gap: .5rem;
list-style: none;
margin: 0;
}
.post-tags li {
margin: 0;
}
.post-tags li a {
text-decoration: none;
color: var(--color-teal);
padding: 0 .5rem;
border-radius: 1rem;
box-shadow: .15rem .15rem var(--color-shadow);
border: .08rem solid var(--color-teal);
line-height: 2;
/* Click animation handling */
position: relative;
top: 0;
left: 0;
transition: top .1s ease-in, left .1s ease-in;
}
.post-tags li a:focus-visible {
outline: none;
background-color: var(--color-teal);
color: var(--color-bg);
}
@media (any-hover: hover) {
.post-tags li a:hover {
outline: none;
background-color: var(--color-teal);
color: var(--color-bg);
}
}
@media (forced-colors: active) {
.post-tags li a:focus-visible {
outline-offset: .08rem;
outline: .08rem solid;
}
@media (any-hover: hover) {
.post-tags li a:hover {
outline-offset: .08rem;
outline: .08rem solid;
}
}
}
/* Click animation */
.post-tags li a:active {
top: .1rem;
left: .1rem;
box-shadow: .05rem .05rem var(--color-shadow);
}
/* Adapted from PrismJS 1.30.0 Tomorrow Night theme
https://prismjs.com/download
*/
code,
pre,
code[class*=language-],
pre[class*=language-] {
font-family: var(--font-family-code);
background-color: var(--color-bg-alt);
font-size: .9rem;
text-shadow: 0 1px var(--color-shadow);
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre,
pre[class*=language-] {
margin: 1rem 0;
padding: 1rem;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
border-radius: .5rem;
overflow: auto;
}
:not(pre)>code,
:not(pre)>code[class*=language-] {
padding: .2rem;
border-radius: .25rem;
white-space: normal;
}
/* Selected text */
code ::-moz-selection,
code::-moz-selection,
pre ::-moz-selection,
pre::-moz-selection,
code[class*=language-] ::-moz-selection,
code[class*=language-]::-moz-selection,
pre[class*=language-] ::-moz-selection,
pre[class*=language-]::-moz-selection,
code ::selection,
code::selection,
pre ::selection,
pre::selection,
code[class*=language-] ::selection,
code[class*=language-]::selection,
pre[class*=language-] ::selection,
pre[class*=language-]::selection {
text-shadow: none;
background-color: var(--color-bg);
}
/* Syntax highlighting */
.token.namespace {
opacity: .7;
}
.token.bold,
.token.important {
font-weight:700
}
.token.italic {
font-style:italic
}
.token.block-comment,
.token.cdata,
.token.comment,
.token.doctype,
.token.prolog,
.token.punctuation {
color: var(--color-grey);
}
.token.attr-name,
.token.deleted,
.token.namespace,
.token.tag {
color: var(--color-red);
}
.token.boolean,
.token.function,
.token.number {
color: var(--color-orange);
}
.token.class-name,
.token.constant,
.token.property,
.token.symbol {
color: var(--color-yellow);
}
.token.attr-value,
.token.char,
.token.regex,
.token.string,
.token.variable,
.token.inserted {
color: var(--color-green);
}
.token.entity,
.token.operator,
.token.url,
.token.function-name {
color:var(--color-blue);
}
.token.atrule,
.token.builtin,
.token.important,
.token.keyword,
.token.selector {
color: var(--color-purple);
}
#postlist,
#taglist {
list-style: none;
}
#postlist, .post,
#taglist, .tag {
margin: 0;
}
/* Odd-numbered posts & tag layout/coloration */
.post:nth-child(odd) .postlink,
.tag:nth-child(odd) .taglink {
grid-template-areas:
'img h2'
'img info'
'img .';
grid-template-columns: 45% auto;;
--color-primary: var(--color-teal);
--color-accent: var(--color-pink);
}
/* Even-numbered posts & tags layout/coloration */
.post:nth-child(even) .postlink,
.tag:nth-child(even) .taglink {
grid-template-areas:
'h2 img'
'info img'
'. img';
grid-template-columns: auto 45%;
--color-primary: var(--color-pink);
--color-accent: var(--color-teal);
}
/* Layout for all posts on mobile */
@media (max-width: 650px) {
.post:nth-child(n) .postlink,
.tag:nth-child(n) .taglink {
grid-template-areas:
'img'
'h2'
'info';
grid-template-columns: auto;
}
}
/* Link */
.postlink,
.taglink {
display: grid;
border: .25rem solid var(--color-primary);
border-radius: 1.25rem;
box-shadow: .35rem .35rem var(--color-shadow);
margin: 2rem 0;
text-decoration: none;
/* Click animation handling */
position: relative;
top: 0;
left: 0;
transition: top .05s ease-in, left .05s ease-in;
}
.postlink:focus-visible,
.taglink:focus-visible {
background-color: var(--color-primary);
outline: none;
}
@media (any-hover: hover) {
.postlink:hover,
.taglink:hover {
background-color: var(--color-primary);
}
}
/* Forced colors */
@media (forced-colors: active) {
.postlink:focus-visible,
.taglink:focus-visible {
outline-offset: .25rem;
outline: .25rem solid;
}
@media (any-hover: hover) {
.postlink:hover,
.taglink:hover {
outline-offset: .25rem;
outline: .25rem solid;
}
}
}
/* Click animation */
.postlink:active,
.taglink:active {
box-shadow: none;
top: .2rem;
left: .2rem;
box-shadow: .15rem .15rem var(--color-shadow);
}
/* Post & tag elements */
.post h2, .post img,
.post ul, .post li,
.tag h2, .tag p,
.tag img {
margin: 0;
}
.post h2,
.tag h2 {
grid-area: h2;
padding: .25rem .5rem;
text-transform: uppercase;
font-size: 1.5rem;
color: var(--color-primary);
border-radius: 1rem 1rem 0 0;
border-bottom: .25rem solid var(--color-accent);
}
.post:nth-child(even) h2,
.tag:nth-child(even) h2 {
text-align: right;
}
.postlink:focus-visible h2,
.taglink:focus-visible h2 {
color: var(--color-bg);
border-color: var(--color-bg);
}
@media (any-hover: hover) {
.postlink:hover h2,
.taglink:hover h2 {
color: var(--color-bg);
border-color: var(--color-bg);
}
}
/* Images */
.post img,
.tag-imgs {
grid-area: img;
}
.tag-imgs {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: .15rem;
}
.tag-imgs img {
aspect-ratio: 3 / 2;
object-fit: cover;
}
.missing-image {
width: 100%;
aspect-ratio: 3 / 2;
background-color: var(--color-bg-alt);
border-radius: calc(1rem);
}
.taglink:focus-visible .missing-image {
opacity: .7;
}
@media (any-hover: hover) {
.taglink:hover .missing-image {
opacity: .7;
}
}
/* Post tags */
.postlist-tags {
grid-area: info;
list-style: none;
display: flex;
flex-flow: row wrap;
gap: .5rem;
padding: .5rem;
}
.post:nth-child(odd) .postlist-tags {
justify-content: flex-end;
}
.postlist-tags li,
.tagcount {
background-color: var(--color-primary);
color: var(--color-bg);
padding: 0 .5rem;
border-radius: 1rem;
}
.postlink:focus-visible .postlist-tags li,
.taglink:focus-visible .tagcount {
background-color: var(--color-bg);
color: var(--color-primary);
}
@media (any-hover: hover) {
.postlink:hover .postlist-tags li,
.taglink:hover .tagcount {
background-color: var(--color-bg);
color: var(--color-primary);
}
}
/* Tag count */
.tag p {
grid-area: info;
padding: .5rem;
}
.tag:nth-child(odd) p {
text-align: right;
}
:root {
color-scheme: light dark;
--font-family: 'Atkinson Hyperlegible Next', sans-serif;
--font-family-code: 'Atkinson Hyperlegible Mono', monospace;
--color-dark: #2e303e;
--color-dark-alt: #3c3f52;
--color-light: #ebeeef;
--color-light-alt: #dbe1e3;
--color-teal-dark: #18737b;
--color-teal-light: #25b0bc;
--color-pink-dark: #94195d;
--color-pink-light: #ee9fcb;
--color-shadow: rgba(2, 10, 40, .25);
/* Used for syntax highlighting */
--color-red-light: #f195aa;
--color-orange-light: #fab795;
--color-yellow-light: #fbe6bc;
--color-green-light: #29d398;
--color-blue-light: #26bbd9;
--color-purple-light: #ddaeea;
--color-grey-light: #b9c3c6;
--color-red-dark: #991433;
--color-orange-dark: #883206;
--color-yellow-dark: #6a4906;
--color-green-dark: #125940;
--color-blue-dark: #125663;
--color-purple-dark: #722999;
--color-grey-dark: #4a4b64;
--color-text: light-dark(var(--color-dark), var(--color-light));
--color-bg: light-dark(var(--color-light), var(--color-dark));
--color-text-alt: light-dark(var(--color-dark-alt), var(--color-light-alt));
--color-bg-alt: light-dark(var(--color-light-alt), var(--color-dark-alt));
--color-teal: light-dark(var(--color-teal-dark), var(--color-teal-light));
--color-pink: light-dark(var(--color-pink-dark), var(--color-pink-light));
--color-red: light-dark(var(--color-red-dark), var(--color-red-light));
--color-orange: light-dark(var(--color-orange-dark), var(--color-orange-light));
--color-yellow: light-dark(var(--color-yellow-dark), var(--color-yellow-light));
--color-green: light-dark(var(--color-green-dark), var(--color-green-light));
--color-blue: light-dark(var(--color-blue-dark), var(--color-blue-light));
--color-purple: light-dark(var(--color-purple-dark), var(--color-purple-light));
--color-grey: light-dark(var(--color-grey-dark), var(--color-grey-light));
}
/* Base */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-family);
color: var(--color-text);
background-color: var(--color-bg);
}
main {
width: 60vw;
max-width: 1000px;
margin: 0 auto;
scroll-margin-top: 7rem;
}
@media (max-width: 1050px) {
main {
width: 75vw;
}
}
@media (max-width: 650px) {
main {
width: 92vw;
}
}
/* Headers */
h1, h2, h3, h4, h5, h6 {
line-height: 1.25;
color: var(--color-teal);
}
h1 {
margin-top: 3rem;
font-size: 3.5rem;
text-align: center;
}
h2, h3, h4, h5, h6 {
scroll-margin-top: 5rem;
}
h2 {
margin-top: 2rem;
font-size: 2.2rem;
}
h3 {
margin-top: 1.5rem;
font-size: 1.6rem;
}
@media (max-width: 650px) {
h1 { font-size: 2.8rem; }
h2 { font-size: 1.8rem; }
h3 { font-size: 1.35rem; }
}
h4, h5, h6 {
margin-top: 1rem;
font-size: 1.2rem;
}
/* Images */
img {
display: block;
max-width: 100%;
height: auto;
border-radius: 1rem;
}
/* Paragraphs */
p {
margin: 1.25rem 0;
line-height: 1.4;
}
strong,
b {
font-weight: 900;
}
/* Links */
a {
color: var(--color-text);
border-radius: .25rem;
text-decoration: underline;
text-decoration-style: solid;
text-decoration-thickness: .2em;
text-decoration-color: var(--color-teal);
transition: text-decoration-thickness .5s;
}
a:focus-visible {
text-decoration: none;
outline: .15rem solid var(--color-teal);
}
@media (any-hover: hover) {
a:hover {
text-decoration-thickness: .4em;
}
}
a:active {
text-decoration-thickness: .4em;
}
/* Heading anchors */
a.ha,
span.ha-placeholder {
color: var(--color-pink);
}
span.ha-placeholder {
opacity: .55;
}
/* Lists */
::marker {
color: var(--color-pink);
}
ul, ol, dl, li {
margin-left: 1rem;
}
li {
line-height: 1.2 5;
margin-top: .65rem;
margin-bottom: .65rem;
}
li ul, li ol {
margin: .5rem 0;
}
dt {
font-weight: 900;
margin-top: .5rem;
}
dd {
margin-left: 2rem;
margin-bottom: .75rem;
}
/* Blockquotes */
blockquote {
margin: .5rem 0;
padding: 0 1rem;
border-radius: .25rem 1rem 1rem .25rem;
line-height: 1.25;
border-left: .5rem solid var(--color-pink);
}
blockquote,
blockquote p,
blockquote ol,
blockquote ul {
background-color: var(--color-bg-alt);
padding: .5rem;
}
blockquote p {
margin: 0;
}
/* Tables */
table {
width: 100%;
border-spacing: 0; /* border collapse doesn't play nice with radii */
border-radius: .3rem;
border: thin solid var(--color-pink);
}
th {
color: var(--color-bg);
background-color: var(--color-pink);
}
th code {
color: var(--color-text); /* Yes, I actually do this somewhere */
}
th, td {
padding: .5rem;
text-align: left;
}
tr:nth-child(even) { background-color: var(--color-bg-alt); }
th:not(:first-child) { border-left: thin solid var(--color-bg); }
th:first-child { border-top-left-radius: .25rem; }
th:last-child { border-top-right-radius: .25rem; }
td:not(:first-child) { border-left: thin solid var(--color-pink); }
/* Times */
time {
color: var(--color-grey);
}
/* Horizontal rules */
hr {
border: .25rem solid var(--color-pink);
margin: 2rem 0;
}
/* Used on home, reference, gallery pages */
.centered {
text-align: center;
}
/* Currently only used for resume, but it's generalizable */
.upper {
text-transform: uppercase;
}
/* Header */
header {
position: sticky;
top: 0;
background-color: var(--color-bg);
padding: .75rem 0;
z-index: 10;
border-bottom: thick solid var(--color-teal);
box-shadow: 0 .25rem .15rem var(--color-shadow);
}
/* Header links, pagination links */
header a,
.pagination a,
.webring ul a {
border-radius: 1rem;
border: .125rem solid var(--color-pink);
color: var(--color-pink);
text-decoration: none;
padding: 0 .25rem;
box-shadow: .15rem .15rem var(--color-shadow);
font-size: 1.2rem;
/* Click animation handling */
position: relative;
top: 0;
left: 0;
transition: top .05s ease-in, left .05s ease-in;
}
header a,
.pagination .older a,
.webring .prev a,
.webring .rand a {
padding-right: .35rem;
}
.pagination .newer a,
.webring .next a,
.webring .rand a {
padding-left: .35rem;
}
header a:focus-visible,
.pagination a:focus-visible,
.webring ul a:focus-visible {
color: var(--color-bg);
border-color: var(--color-pink);
background-color: var(--color-pink);
outline: none;
}
@media (any-hover: hover) {
header a:hover,
.pagination a:hover,
.webring ul a:hover {
color: var(--color-bg);
border-color: var(--color-pink);
background-color: var(--color-pink);
}
}
@media (forced-colors: active) {
header a:focus-visible,
.pagination a:focus-visible,
.webring ul a:focus-visible {
outline-offset: .125rem;
outline: .125rem solid;
}
@media (any-hover: hover) {
header a:hover,
.pagination a:hover,
.webring ul a:hover {
outline-offset: .125rem;
outline: .125rem solid;
}
}
}
/* Click animation */
header a:active,
.pagination a:active,
.webring ul a:active {
top: .1rem;
left: .1rem;
box-shadow: .05rem .05rem var(--color-shadow);
}
/* Current page */
header a[aria-current="page"] {
border-color: var(--color-teal);
color: var(--color-teal);
}
header a[aria-current="page"]:focus-visible {
color: var(--color-bg);
border-color: var(--color-teal);
background-color: var(--color-teal);
}
@media (any-hover: hover) {
header a[aria-current="page"]:hover {
color: var(--color-bg);
background-color: var(--color-teal);
border-color: var(--color-teal);
}
}
/* Header link icons, pagination icons */
header i,
.pagination i,
.webring ul i {
color: var(--color-teal);
}
header i,
.pagination .older i,
.webring .prev i,
.webring .rand i:nth-child(1) {
padding-left: .25rem;
}
.pagination .newer i,
.webring .next i,
.webring .rand i:nth-child(2) {
padding-right: .25rem;
}
header a[aria-current="page"] i {
color: var(--color-pink);
}
header a:focus-visible i,
a[aria-current="page"] a:focus-visible i,
.pagination a:focus-visible i,
.webring ul a:focus-visible i {
color: var(--color-bg);
}
@media (any-hover: hover) {
header a:hover i,
header a[aria-current="page"]:hover i,
.pagination a:hover i,
.webring ul a:hover i {
color: var(--color-bg);
}
}
/* Skip link */
#skip {
left: -999px;
position: absolute;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
z-index: -99;
}
#skip:focus-visible {
display: inline-block;
left: auto;
top: auto;
width: auto;
height: auto;
overflow: auto;
margin: 0 10%;
z-index: 999;
}
/* Nav */
header ul {
display: flex;
list-style: none;
gap: 1rem;
justify-content: center;
}
header ul,
header li {
margin: 0;
}
@media (max-width: 650px) {
.menu-text {
display: none; /* Icons only on small screens */
}
header a {
padding: .15rem .5rem;
}
header i {
padding: 0;
}
}
/* Footer */
footer {
padding: 1rem 0;
font-size: .9rem;
border-top: thick solid var(--color-pink);
}
footer ul {
display: flex;
list-style: none;
gap: .5rem;
justify-content: center;
margin: 0;
}
footer li {
margin: 0;
}
footer li:nth-child(2)::before,
footer li:nth-child(2)::after {
content: " ● " / "";
color: var(--color-teal);
}
@media (max-width: 650px) {
footer ul {
flex-flow: column;
text-align: center;
}
footer li:nth-child(2)::before,
footer li:nth-child(2)::after {
content: none;
}
}
footer a {
text-decoration-color: var(--color-pink);
}
footer a:focus-visible {
outline-color: var(--color-pink);
}
/* Pagination */
.pagination,
.pagination li {
margin: 0;
}
.pagination {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-areas: "older newer";
list-style: none;
margin-top: 3rem;
}
@media (max-width: 650px) {
.post-pagination {
grid-template-columns: 1fr;
grid-template-areas:
"older"
"newer";
gap: .75rem;
}
}
.pagination .older {
grid-area: older;
}
.pagination .newer {
grid-area: newer;
text-align: right;
}
/* webring navigation */
.webring {
margin-bottom: 3rem;
}
.webring ul {
display: flex;
flex-flow: row wrap;
list-style: none;
justify-content: space-around;
gap: .35rem;
}
.webring ul,
.webring li {
margin-left: 0;
}
@media print {
/* Nav elements */
header,
footer,
nav {
display: none !important;
}
/* Base */
body {
background-color: #fff;
color: #000;
}
main {
width: 95vw;
}
h1,h2,h3,h4,h5,h6 {
color: #000;
}
/* Links */
/* Hover is not really necessary, but it's annoying when testing otherwise */
a,
a:hover {
text-decoration-style: dotted;
text-decoration-thickness: .1rem;
text-decoration-color: #000;
}
a::after{
content: " (" attr(href) ")";
}
/* Code */
code,
pre,
code[class*=language-],
pre[class*=language-] {
text-shadow: none;
background-color: var(--color-light);
color: #000 !important;
}
.token.namespace {
opacity: 1;
}
.token.block-comment,
.token.cdata,
.token.comment,
.token.doctype,
.token.prolog,
.token.punctuation,
.token.attr-name,
.token.deleted,
.token.namespace,
.token.tag,
.token.boolean,
.token.function,
.token.number,
.token.class-name,
.token.constant,
.token.property,
.token.symbol,
.token.attr-value,
.token.char,
.token.regex,
.token.string,
.token.variable,
.token.inserted,
.token.entity,
.token.operator,
.token.url,
.token.function-name,
.token.atrule,
.token.builtin,
.token.important,
.token.keyword,
.token.selector {
color: #000;
}
}</style>
<script type="module">// Thank you to https://github.com/daviddarnes/heading-anchors
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
let globalInstanceIndex = 0;
class HeadingAnchors extends HTMLElement {
static register(tagName = "heading-anchors", registry = window.customElements) {
if(registry && !registry.get(tagName)) {
registry.define(tagName, this);
}
}
static attributes = {
exclude: "data-ha-exclude",
prefix: "prefix",
content: "content",
}
static classes = {
anchor: "ha",
placeholder: "ha-placeholder",
srOnly: "ha-visualhide",
}
static defaultSelector = "h2,h3,h4,h5,h6";
static css = `
.${HeadingAnchors.classes.srOnly} {
clip: rect(0 0 0 0);
height: 1px;
overflow: hidden;
position: absolute;
width: 1px;
}
.${HeadingAnchors.classes.anchor} {
position: absolute;
left: var(--ha_offsetx);
top: var(--ha_offsety);
text-decoration: none;
opacity: 0;
}
.${HeadingAnchors.classes.placeholder} {
opacity: .3;
}
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
opacity: 1;
}
.${HeadingAnchors.classes.anchor},
.${HeadingAnchors.classes.placeholder} {
display: inline-block;
padding: 0 .25em;
/* Disable selection of visually hidden label */
-webkit-user-select: none;
user-select: none;
}
@supports (anchor-name: none) {
.${HeadingAnchors.classes.anchor} {
position: absolute;
left: anchor(left);
top: anchor(top);
}
}`;
get supports() {
return "replaceSync" in CSSStyleSheet.prototype;
}
get supportsAnchorPosition() {
return CSS.supports("anchor-name: none");
}
constructor() {
super();
if(!this.supports) {
return;
}
let sheet = new CSSStyleSheet();
sheet.replaceSync(HeadingAnchors.css);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
this.headingStyles = {};
this.instanceIndex = globalInstanceIndex++;
}
connectedCallback() {
if (!this.supports) {
return;
}
this.headings.forEach((heading, index) => {
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
let anchor = this.getAnchorElement(heading);
let placeholder = this.getPlaceholderElement();
// Prefers anchor position approach for better accessibility
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
if(this.supportsAnchorPosition) {
let anchorName = `--ha_${this.instanceIndex}_${index}`;
placeholder.style.setProperty("anchor-name", anchorName);
anchor.style.positionAnchor = anchorName;
}
heading.appendChild(placeholder);
heading.after(anchor);
}
});
}
// Polyfill-only
positionAnchorFromPlaceholder(placeholder) {
if(!placeholder) {
return;
}
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
if(!heading.nextElementSibling) {
return;
}
// TODO next element could be more defensive
this.positionAnchor(heading.nextElementSibling);
}
// Polyfill-only
positionAnchor(anchor) {
if(!anchor || !anchor.previousElementSibling) {
return;
}
// TODO previous element could be more defensive
let heading = anchor.previousElementSibling;
this.setFontProp(heading, anchor);
if(this.supportsAnchorPosition) {
// quit early
return;
}
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
if(placeholder) {
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
}
}
setFontProp(heading, anchor) {
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
if(placeholder) {
let style = getComputedStyle(placeholder);
let props = ["font-weight", "font-size", "line-height", "font-family"];
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
let vars = style.getPropertyValue("font-variation-settings");
if(vars) {
anchor.style.setProperty("font-variation-settings", vars);
}
}
}
getAccessibleTextPrefix() {
// Useful for i18n
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
}
getContent() {
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
return this.getAttribute(HeadingAnchors.attributes.content);
}
return "#";
}
// Placeholder nests inside of heading
getPlaceholderElement() {
let ph = document.createElement("span");
ph.setAttribute("aria-hidden", true);
ph.classList.add(HeadingAnchors.classes.placeholder);
let content = this.getContent();
if(content) {
ph.innerHTML = content; // CHANGED HERE
}
ph.addEventListener("mouseover", (e) => {
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
if(placeholder) {
this.positionAnchorFromPlaceholder(placeholder);
}
});
return ph;
}
getAnchorElement(heading) {
let anchor = document.createElement("a");
anchor.href = `#${heading.id}`;
anchor.classList.add(HeadingAnchors.classes.anchor);
let content = this.getContent();
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
anchor.addEventListener("focus", e => {
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
if(anchor) {
this.positionAnchor(anchor);
}
});
anchor.addEventListener("mouseover", (e) => {
// when CSS anchor positioning is supported, this is only used to set the font
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
this.positionAnchor(anchor);
});
return anchor;
}
get headings() {
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
}
get selector() {
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
}
}
HeadingAnchors.register();
export { HeadingAnchors }</script>
</head>
<body>
<header>
<a href="#main" id="skip" title="skip to main content">
<i class="fa-solid fa-forward" aria-hidden="true"></i> skip
</a>
<nav aria-label="main navigation">
<ul>
<li>
<a href="/reference/" title="read reference posts">
<i class="fa-regular fa-folder-open" aria-hidden="true"></i>
<span class="menu-text">reference</span>
</a>
</li>
<li>
<a href="/gallery/" title="view the gallery">
<i class="fa-regular fa-images" aria-hidden="true"></i>
<span class="menu-text">gallery</span>
</a>
</li>
<li>
<a href="/" title="">
<i class="fa fa-solid fa-crow" aria-hidden="true"></i>
<span class="menu-text">home</span>
</a>
</li>
<li>
<a href="/about/" title="about Lee">
<i class="fa-regular fa-user" aria-hidden="true"></i>
<span class="menu-text">about</span>
</a>
</li>
<li>
<a href="/contact/" title="contact Lee">
<i class="fa-solid fa-envelope-open-text" aria-hidden="true"></i>
<span class="menu-text">contact</span>
</a>
</li>
</ul>
</nav>
</header>
<main id="main">
<heading-anchors content="<i class='fa-solid fa-anchor'></i>">
<article>
<h1 id="an-intro-to-git">an intro to git</h1>
<div class="post-metadata">
<p>
posted on
<time datetime="2026-01-07">January 7, 2026</time>
by Lee Cattarin
</p>
<ul class="post-tags">
<li>
<a href="/tags/software/">software</a>
</li>
</ul>
</div>
<img src="/img/goldeneye-tail.jpg" alt="Image unrelated to post. The tail of a diving duck pokes out from the water with a small splash." loading="lazy" decoding="async" width="1000" height="666">
<p>alrighty, this one's a real doozy. Strap in.</p>
<hr>
<!-- TOC -->
<ul>
<li><a href="#versioning">versioning</a></li>
<li><a href="#problem-statement">problem statement</a></li>
<li><a href="#what-is-git">what is git?</a>
<ul>
<li><a href="#where-can-i-use-git">where can I use git?</a>
<ul>
<li><a href="#what-is-a-cli">what is a CLI?</a></li>
</ul>
</li>
<li><a href="#where-can-i-use-the-git-cli">where can I use the git CLI?</a>
<ul>
<li><a href="#git-for-windows">git for Windows</a></li>
<li><a href="#wsl">WSL</a></li>
<li><a href="#a-few-terminal-operations">a few terminal operations</a></li>
<li><a href="#edit-files">edit files</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#git-version">git version</a>
<ul>
<li><a href="#a-few-handy-settings">a few handy settings</a></li>
</ul>
</li>
<li><a href="#git-going">git going</a>
<ul>
<li><a href="#git-init">git init</a></li>
<li><a href="#git-clone">git clone</a></li>
</ul>
</li>
<li><a href="#git-status">git status</a>
<ul>
<li><a href="#branch-main">branch main</a></li>
<li><a href="#no-commits--nothing-to-commit">no commits / nothing to commit</a></li>
</ul>
</li>
<li><a href="#commits-and-history">commits and history</a>
<ul>
<li><a href="#git-log">git log</a></li>
<li><a href="#creating-a-commit">creating a commit</a></li>
</ul>
</li>
<li><a href="#the-staging-area">the staging area</a>
<ul>
<li><a href="#git-add">git add</a></li>
</ul>
</li>
<li><a href="#git-commit">git commit</a>
<ul>
<li><a href="#checking-our-work">checking our work</a></li>
</ul>
</li>
<li><a href="#changes-to-existing-files">changes to existing files</a>
<ul>
<li><a href="#git-restore">git restore</a></li>
</ul>
</li>
<li><a href="#git-revert">git revert</a></li>
<li><a href="#git-remote">git remote</a>
<ul>
<li><a href="#git-fetch-and-git-pull">git fetch and git pull</a></li>
<li><a href="#git-push-take-one">git push, take one</a></li>
<li><a href="#authentication">authentication</a></li>
<li><a href="#ssh-keys">SSH keys</a>
<ul>
<li><a href="#creation">creation</a></li>
<li><a href="#an-alias">an alias...</a></li>
<li><a href="#and-a-function">and a function</a></li>
<li><a href="#ssh-keys-and-the-remote">SSH keys and the remote</a></li>
</ul>
</li>
<li><a href="#git-push-take-two">git push, take two</a></li>
</ul>
</li>
<li><a href="#summary">summary</a></li>
</ul>
<!-- /TOC -->
<hr>
<h2 id="versioning">versioning</h2>
<p>have you ever tried to revert to a previous version of a document in MS Office or Google Docs and found that your revision history is cluttered with small changes that by all rights should be grouped into one set of edits, but they aren't, and it's tedious to pick through all the versions?</p>
<p>or, more uniquely and/or uncommonly:</p>
<ul>
<li>you've been using Google's named versions, but you're <a href="https://support.google.com/docs/answer/190843#zippy=%2Ccreate-a-named-version" target="_blank" rel="external">out of named versions (you can have 40 per doc, or 15 per spreadsheet)</a>, so you'll have to start going back and deleting named versions, and isn't that just a pain?</li>
<li>you're using MS Office, but <a href="https://support.microsoft.com/en-us/office/view-previous-versions-of-office-files-5c1e076f-a9c9-41b8-8ace-f77b9642e2c2" target="_blank" rel="external">your file isn't in OneDrive, so there's no version history at all</a>? Or maybe it <em>is</em> in OneDrive, but you were offline, so too many changes got jumbled into one big edit when you next went online?</li>
</ul>
<p>or, websites. Maybe you're building something in Squarespace and find out that <a href="https://forum.squarespace.com/topic/239030-roll-site-back-to-previous-version-changes/" target="_blank" rel="external">in current versions of Squarespace, it doesn't support version history.</a>.</p>
<h2 id="problem-statement">problem statement</h2>
<p>I started writing this to help a friend. She's getting started with a website, and we're using the static site generator <a href="https://www.11ty.dev/" target="_blank" rel="external">11ty</a> as she wants to have a lot of easy-to-write posts. She needs a single-user workflow that allows her to publish her website without hosting it herself, and that's the use case where this was born.</p>
<p>this walkthrough is best suited for people who want to use <code>git</code> in single-person projects, or perhaps with one or two other close collaborators. There's quite a few topics it doesn't cover that are vitally important in large collaborative projects, such as <strong>branching</strong> and <strong>merging</strong>.</p>
<p>this walkthrough also focuses on the &quot;happy path,&quot; without much discussion of troubleshooting. I may write more on the topic in the future, but we're already over <em>4,500 words</em>, so we're calling it a day.</p>
<p>finally, I wrote this walkthrough primarily with knowledge from using <strong>WSL</strong> [more on this later] on <strong>Windows</strong> and with <strong>Zed</strong> as my text editor. While I've tried to cover my bases with other OSes and options, there's a solid chance I'm missing things!</p>
<p>that all said, let's get (git?) into it!</p>
<h2 id="what-is-git">what is git?</h2>
<p><code>git</code> is a <em>version control system</em>. We can use it to track changes we make to a set of files.</p>
<blockquote>
<p>tip: it's important to understand that despite the examples of MS Office and Google Docs above, <code>git</code> <em>isn't useful</em> with word documents. <code>git</code> shines with plain text files - .txt, .md, or basically any type of code.</p>
</blockquote>
<h3 id="where-can-i-use-git">where can I use git?</h3>
<p>many, many tools interact with <code>git</code>:</p>
<ul>
<li>a lot of modern text editors have graphical user interfaces (GUIs) that let you perform <code>git</code> operations, like <a href="https://code.visualstudio.com/docs/sourcecontrol/overview" target="_blank" rel="external">VSCode</a>, <a href="https://zed.dev/docs/git" :target="_blank">Zed</a>, or <a href="https://www.sublimetext.com/docs/git_integration.html" :target="_blank">Sublime Text</a></li>
<li>there are <a href="https://git-scm.com/tools/guis" target="_blank" rel="external">standalone <code>git</code> GUIs</a></li>
<li>and many <code>git</code> users use the <code>git</code> command line interface (CLI), which is fully text-based</li>
</ul>
<p>today we're going to talk about the <code>git</code> CLI... technically. But <strong>don't let that scare you</strong> - we'll talk about <em>concepts</em> and <em>actions</em> that can be applied to other <code>git</code> interfaces as well.</p>
<h4 id="what-is-a-cli">what is a CLI?</h4>
<p>a CLI a way to interact with your computer and with software in text-only form. Rather than using the mouse and clicking on things, you type in commands and see output.</p>
<h3 id="where-can-i-use-the-git-cli">where can I use the git CLI?</h3>
<p>if you want to use the <code>git</code> CLI, you'll need a terminal. You've got a couple options here:</p>
<ul>
<li>on Linux or Mac, you should have one built in! This is the easy path, congrats :) Even better, <code>git</code> generally ships with these systems, so there's no installation required. Search for an application called 'Terminal' or similar
<ul>
<li>note that I don't have a Mac and have never used one. I can't guarantee that everything operates the same over there - there may be discrepancies I don't know about</li>
</ul>
</li>
<li>on Windows, there's two options
<ul>
<li><a href="https://gitforwindows.org/" target="_blank" rel="external"><code>git</code> for Windows</a> packages a Linux-like terminal with a <code>git</code> GUI. This may be slightly friendlier for people who aren't at all familiar with Linux. If choosing <code>git</code> for Windows, <a href="#git-for-windows">see my installation instructions below</a></li>
<li>I use <a href="https://learn.microsoft.com/en-us/windows/wsl/install" target="_blank" rel="external">Windows Subsystem for Linux, or WSL</a>, which gives you a Linux distribution within Windows. It's pretty smooth sailing at this point, but there's some idiosyncracies to conquer - like the fact that your <em>Windows</em> files and your <em>Linux</em> files are stored in different places. If choosing WSL, <a href="#wsl">see my usage notes below</a></li>
</ul>
</li>
<li>both of the Windows options listed work with <a href="https://apps.microsoft.com/detail/9n0dx20hk701" target="_blank" rel="external">Windows Terminal</a> which offers a nicer-looking terminal experience than the basic command prompt. If you're going to keep working with what you set up today, I recommend it!</li>
</ul>
<h4 id="git-for-windows">git for Windows</h4>
<p>on the &quot;Releases&quot; page, scroll down to &quot;Assets&quot; and pick the <code>.exe</code> file.</p>
<p>during installation, you'll be asked to choose some things by the installer. Here's my recommendations:</p>
<ol>
<li>if choosing to install Windows Terminal, check 'Add a Git Bash Profile to Windows Terminal' on the first options page</li>
<li><strong>default editor:</strong> <em>don't use vim.</em> Pick something you have installed - it can just be Notepad - or you can use <code>nano,</code> an in-terminal editor</li>
<li><strong>initial branch name:</strong> choose the 'Override' option and leave it set to &quot;main&quot;</li>
<li><strong>everything else:</strong> keep the recommended choices</li>
</ol>
<p>you can open <code>git</code> for Windows via the start menu by searching for 'git bash.' If you chose to install Windows Terminal, one of the dropdown options will be for a new tab will be 'Git Bash.'</p>
<h4 id="wsl">WSL</h4>
<p>while <em>installing</em> WSL is a single command, here's a couple notes about getting started once <em>inside</em> WSL:</p>
<ul>
<li>don't be concerned that you can't see anything when you type or paste your password! Since it's sensitive information, this is intentional. You'll notice this pattern a couple of times while following this walkthrough</li>
<li><code>Ctrl+C</code> and <code>Ctrl+V</code> won't work like they do on Windows. If you want to copy/paste, right-click (there won't be a context menu, it'll just happen)</li>
<li>if you're downloading a text editor like the ones mentioned above, you'll still follow the <em>Windows</em> instructions</li>
</ul>
<p>you'll also need to be careful of a few things regarding text editors to make them work with WSL:</p>
<ol>
<li>if you install VSCode, you'll need to add the <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack" target="_blank" rel="external">remote development extension pack</a></li>
<li>if you install Zed, you'll need to check &quot;Add to PATH (requires shell restart)&quot; in the installer, then restart your terminal as noted
<ul>
<li>there's a single setting in Zed to disable all AI settings: <strong>Settings &gt; AI &gt; General &gt; Disable AI</strong></li>
</ul>
</li>
<li>I don't recommend installing Sublime Text if using WSL, because summoning it from within WSL is a little more complicated than either of the two above, and I found when I <a href="/comparing-text-editors">reviewed editors</a> that it struggled with some file operations in WSL</li>
</ol>
<p>you <em>must</em> start your editor of choice <em>within WSL</em>. Don't use the Windows start menu! Instead, you'll type <code>code .</code> or <code>zed .</code> (note the <code>.</code>) while in WSL.</p>
<p>you can open WSL via the start menu by searching for 'WSL.' If you chose to install Windows Terminal, one of the dropdown options for a new tab will be your WSL distribution, usually 'Ubuntu.'</p>
<h4 id="a-few-terminal-operations">a few terminal operations</h4>
<p>here's three vital terminal commands:</p>
<ul>
<li><code>pwd</code> <strong>prints the working (current) directory</strong>. A lot of terminals will just <em>show</em> you what your current directory on every line, but if they don't, try <code>pwd</code>.</li>
</ul>
<blockquote>
<p>tip: directory is just another word for folder</p>
</blockquote>
<ul>
<li><code>cd</code> lets us <strong>change directories</strong>. If we type <em>only</em> <code>cd</code>, we'll be brought back to the home directory; if we provide a directory path, we'll be taken to the provided directory</li>
<li><code>ls</code> <strong>lists</strong> files in the current directory (including other directories)</li>
</ul>
<h4 id="edit-files">edit files</h4>
<p>we'll want to edit files, right? How do we open our editor from the terminal?</p>
<p>there's usually a terminal command for the editor. For VSCode, it's <code>code</code>; for Zed, it's <code>zed</code>. If we want to open the <em>current directory</em> in our editor of choice (and we do!), we'll write <code>&lt;editor command&gt; .</code> (note the <code>.</code>), where <code>.</code> means &quot;the current directory.&quot;</p>
<h2 id="git-version">git version</h2>
<p>let's check that you have git installed with <code>git version</code>. You might see something like <code>git version 2.34.1</code> printed out in response. If you don't get a version number, but instead get an error saying you don't have <code>git</code>, <a href="https://git-scm.com/install" target="_blank" rel="external">install <code>git</code></a>.</p>
<h3 id="a-few-handy-settings">a few handy settings</h3>
<p>before we really start, we're going to set a few basics to make it easier for ourselves.</p>
<pre class="language-sh"><code class="language-sh"><span class="token comment"># skip this one if you installed git for windows</span>
<span class="token comment"># this means that if git wants us to edit something,</span>
<span class="token comment"># it'll open in the built-in terminal editor 'nano'</span>
<span class="token comment"># the default is vim, which can be pretty unfriendly to newcomers</span>
<span class="token comment"># nano, on the other hand, will tell you how to do basic</span>
<span class="token comment"># operations at the bottom of the editor</span>
<span class="token function">git</span> config <span class="token parameter variable">--global</span> core.editor <span class="token function">nano</span>
<span class="token comment"># this uses the autocorrect</span>
<span class="token comment"># the value specifies how many *tenths* of a second</span>
<span class="token comment"># so 10 => 1 second</span>
<span class="token function">git</span> config <span class="token parameter variable">--global</span> help.autocorrect <span class="token number">10</span>
<span class="token comment"># skip this one if you installed git for windows</span>
<span class="token comment"># the default branch name is "master" due to older computer terminology</span>
<span class="token comment"># older language used to explain some computing relationships as master/slave</span>
<span class="token comment"># some people consider this outdated and harmful, so "main" is a more common these days</span>
<span class="token comment"># also, I'll be using main, so this will help make your output look like mine</span>
<span class="token function">git</span> config <span class="token parameter variable">--global</span> init.defaultbranch main
<span class="token comment"># this sets our information</span>
<span class="token comment"># if we don't set this, git will prompt us to set it later</span>
<span class="token function">git</span> config <span class="token parameter variable">--global</span> user.name <span class="token operator">&lt;</span>your-name<span class="token operator">></span>
<span class="token function">git</span> config <span class="token parameter variable">--global</span> user.email <span class="token operator">&lt;</span>your-email<span class="token operator">></span></code></pre>
<h2 id="git-going">git going</h2>
<p>(no, that's not a real <code>git</code> command)</p>
<p>there's two main ways to start:</p>
<ol>
<li>create a new project on our local machine, or</li>
<li>work with an existing project</li>
</ol>
<blockquote>
<p>tip: <code>git</code> and associated tooling refer to projects as <strong>repositories</strong>. I'll be sticking with the word project here as I find it a bit friendlier, but you'll probably run across the word repository in the wider world of <code>git</code></p>
</blockquote>
<h3 id="git-init">git init</h3>
<p><code>git init &lt;project&gt;</code> will create a new directory named <code>project</code> ready to be used with git. We can then use <code>cd &lt;project&gt;</code> to enter the directory.</p>
<blockquote>
<p>tip: don't use spaces in your project name!</p>
</blockquote>
<h3 id="git-clone">git clone</h3>
<p><code>git clone &lt;project URL&gt;</code> will pull in an existing project. We're not going to talk about this right now; instead, we're going forward assuming with <code>git init</code>.</p>
<h2 id="git-status">git status</h2>
<p>before we do anything, let's see what <code>git</code> will tell us about our project. Type <code>git status</code> and we might see the following:</p>
<pre class="language-txt"><code class="language-txt">On branch main
No commits yet
nothing to commit (create/copy files and use "git add" to track)</code></pre>
<p>let's dissect this.</p>
<h3 id="branch-main">branch main</h3>
<p><code>git</code> has a concept of <strong>branches</strong>, which are different paths our file history has taken. While branches are <em>incredibly</em> powerful, we're going to stay away from branches during this walkthrough and focus on working on a single branch - in this case, <code>main</code>.</p>
<h3 id="no-commits-nothing-to-commit">no commits / nothing to commit</h3>
<p>&quot;no commits&quot; means that the project has no history whatsoever. &quot;Nothing to commit&quot; means we've made no changes. But what is a commit?</p>
<h2 id="commits-and-history">commits and history</h2>
<p>a <strong>commit</strong> is one set of changes made to our work. We get to choose which changes are part of any given commit, and we write a message describing the commit so that future-us knows what we did if for some reason we need to undo something.</p>
<h3 id="git-log">git log</h3>
<p>in an established project, we can use <code>git log</code> to look at our <strong>commit history</strong>. By default, one commit will output like this:</p>
<pre class="language-txt"><code class="language-txt">commit e2fd6c4772e61f9c074638a933eb92fc1ea885ef
Author: Lee Cattarin &lt;lee.cattarin@gmail.com>
Date: Sun Dec 28 18:47:00 2025 -0800
fix syntax err in alt</code></pre>
<p>In order there, we have:</p>
<ol>
<li>a long string that identifies the commit</li>
<li>the author of the commit</li>
<li>the date it was created</li>
<li>the message written to describe the commit</li>
</ol>
<h3 id="creating-a-commit">creating a commit</h3>
<p>commits are made with the command <code>git commit</code>, but if we try to create a commit right now we'll be told &quot;nothing to commit.&quot;</p>
<p>okay, what if we edit a file?</p>
<blockquote>
<p>tip: if you don't want to actually open your editor, just use <code>touch file.txt</code> to create a new empty file named <code>file.txt</code></p>
</blockquote>
<p>hmmm, there's still nothing to commit! What happens if we check <code>git status</code>? There's some new output!</p>
<pre class="language-txt"><code class="language-txt">Untracked files:
(use "git add &lt;file>..." to include in what will be committed)
file.txt</code></pre>
<p><code>git</code> tells us that we have &quot;untracked&quot; files - a.k.a. files that git hasn't got in its history yet. It also tells us to use <code>git add</code> if we want to be able to commit that file.</p>
<h2 id="the-staging-area">the staging area</h2>
<p><code>git</code> has a concept called the <strong>staging environment</strong> or <strong>staging area</strong>. This captures the set of changes we're adding to a single commit. When we add something to the staging area, we say we are <strong>staging</strong> it or that it is <strong>staged</strong>. In order to stage changes, we'll use <code>git add</code>.</p>
<p>why a staging area? Why not just commit our changes?</p>
<p>well, imagine we're writing a blog post (easy for me to imagine right now). We start reviewing it, and notice that there's a bit of page styling we don't like - not something tied to the content of the post, but the styling of the overall site. We fix it, and want to save <em>that</em> change while continuing to work on our post draft. <code>git add</code> and the staging area allow that kind of choice.</p>
<h3 id="git-add">git add</h3>
<p><code>git add &lt;filename&gt;</code> lets us add <em>all</em> changes in the given file to the staging area. Sometimes this is really useful - if we just created a new file (by, say, using <code>touch file.txt</code>), we probably want to add the whole thing.</p>
<p>personally, I really like using <code>git add -p</code>, so much so that I <a href="/favorite-git-flag">wrote an entire blog post about it</a>. It lets us review changes piece-by-piece and pick only the pieces we want.</p>
<p>for now, we'll try <code>git add file.txt</code>. We'll notice there's no output by default, but we can run <code>git status</code> to see where things are at. <code>git</code> will now tell us:</p>
<pre class="language-txt"><code class="language-txt">Changes to be committed:
(use "git rm --cached &lt;file>..." to unstage)
new file: file.txt</code></pre>
<p><em>now</em> we're ready to create a commit!</p>
<h2 id="git-commit">git commit</h2>
<p>if we just write <code>git commit</code>, it'll open an editor for us to edit the <strong>commit message</strong> - our description of the changes. This can be handy if we want to write a lengthy description, but if we want to just write a one-liner, we can use <code>git commit -m &quot;&lt;message&gt;&quot;</code>. It's quicker and doesn't involve opening an editor.</p>
<p>let's create a super basic commit:</p>
<pre class="language-sh"><code class="language-sh"><span class="token function">git</span> commit <span class="token parameter variable">-m</span> <span class="token string">"baby's first commit"</span></code></pre>
<blockquote>
<p>tip: as excited as you may be, don't use '!' in your commit messages</p>
</blockquote>
<p>we'll see output like this:</p>
<pre class="language-txt"><code class="language-txt">[main (root-commit) 3dcf1ca] baby's first commit
1 file changed, 1 insertion(+)</code></pre>
<h3 id="checking-our-work">checking our work</h3>
<p>trying <code>git log</code> now will show us our single commit!</p>
<blockquote>
<p>tip: type <code>q</code> to exit the <code>git log</code> output</p>
</blockquote>
<p>trying <code>git status</code> will tell us:</p>
<pre class="language-txt"><code class="language-txt">On branch main
nothing to commit, working tree clean</code></pre>
<h2 id="changes-to-existing-files">changes to existing files</h2>
<p>so, we've added a new file - that wasn't bad. Things get a little more interesting when we edit files <code>git</code> already knows about. Let's use <code>&lt;editor-command&gt; .</code> to open the current directory and write a sentence or two in <code>file.txt</code>.</p>
<p>after saving <code>file.txt</code>, try <code>git status</code> again.</p>
<blockquote>
<p>tip: Ctrl+S (or Cmd+S on Mac) is the shortcut for saving <em>basically everywhere</em></p>
</blockquote>
<pre class="language-txt"><code class="language-txt">Changes not staged for commit:
(use "git add &lt;file>..." to update what will be committed)
(use "git restore &lt;file>..." to discard changes in working directory)
modified: file.txt</code></pre>
<h3 id="git-restore">git restore</h3>
<p><code>git restore</code> is new! That lets us get rid of our changes and go back to the last version of the file committed. Be careful with this - we should only do it if we <em>really</em> want to get rid of those changes.</p>
<p>let's not restore, and instead stage and commit our new changes:</p>
<pre class="language-sh"><code class="language-sh"><span class="token function">git</span> <span class="token function">add</span> file.txt
<span class="token function">git</span> commit <span class="token parameter variable">-m</span> <span class="token string">"added a new sentence"</span></code></pre>
<p>again, we can use <code>git status</code> or <code>git log</code> as needed.</p>
<h2 id="git-revert">git revert</h2>
<p>ooooh... I don't actually like that change. What if I want to undo something?</p>
<p>run <code>git log</code> again, and copy the first 6-8 characters in the commit string (we can copy more, including the whole string if we want, but it's not necessary):</p>
<pre><code>commit 8b5dd7838f8c8423cfa445b6cddbed88e9c32511 (HEAD -&gt; main)
Author: Lee Cattarin &lt;lee.cattarin@gmail.com&gt;
Date: Wed Jan 7 15:18:45 2026 -0800
added a new sentence
</code></pre>
<p>in this case, <code>8b5dd7</code>.</p>
<p>now we can try <code>git revert &lt;commit-string&gt;</code>. It'll open our editor to write a message about the change. It's important to know that <code>git revert</code> <em>doesn't delete the old commit</em> - it creates a <strong>new</strong> commit that undoes the previous work.</p>
<pre class="language-txt"><code class="language-txt">[main 9268d5c] Revert "added a new sentence"
1 file changed, 1 deletion(-)</code></pre>
<p>I didn't edit the message - we can tell because it just says &quot;Revert&quot; and then the old commit message. But we can edit and add lots of detail about why we're doing it.</p>
<h2 id="git-remote">git remote</h2>
<p>let's try a new command: <code>git remote</code>. Hmm, nothing happened... what's a &quot;remote&quot;?</p>
<p>remember how I said we could use <code>git clone</code> to work on an existing project? If we did that, we'd be getting that project from a <em>remote</em> server - not our <em>local</em> machine.</p>
<p>the world of git servers is vast - hell, you can run your own! - but we're going to just mention a few major hosts: GitHub, GitLab, and Codeberg. For this walkthrough, we're going to work with Codeberg, but you'll find that the UI is pretty similar across all three, so if you've got a GitHub or GitLab account feel free to use that.</p>
<p>let's head on over to <a href="https://codeberg.org/" target="_blank" rel="external">Codeberg</a> First off, we'll make an account.</p>
<p>now we'll make a new project using the <code>+</code> in the upper right. Choose 'New repository,' then pick a repository name. You can leave the other settings be.</p>
<p>with the project created, Codeberg will tell us three things we can do: clone the repository, create a new repository, or push an existing repository. We'll push an existing one.</p>
<pre class="language-sh"><code class="language-sh"><span class="token function">git</span> remote <span class="token function">add</span> origin https://codeberg.org/inherentlee/testing.git
<span class="token function">git</span> push <span class="token parameter variable">-u</span> origin main</code></pre>
<p>first, we'll add a remote. Across from the project title, we should see a button that says <code>Code</code> with a dropdown indicator. It'll offer a few choices, the first two being SSH and HTTPS. I'll talk about SSH in a bit, but let's try HTTPS first. Copy that URL; we're about to use it in a command.</p>
<blockquote>
<p>tip: the remote can be named whatever you want! Traditionally, it's called <code>origin</code>, but if it's easier for you to remember, you might call it <code>codeberg</code> or maybe <code>remote</code></p>
</blockquote>
<pre class="language-sh"><code class="language-sh"><span class="token function">git</span> remote <span class="token function">add</span> <span class="token operator">&lt;</span>remote-name<span class="token operator">></span> <span class="token operator">&lt;</span>url<span class="token operator">></span></code></pre>
<p>for this walkthrough, we'll call our remote <code>codeberg</code>.</p>
<p>there's no feedback, but that's ok. Re-running <code>git remote</code> shows that we have a remote now: <code>codeberg</code>. That really doesn't tell us much, does it! Let's try a more talkative command: <code>git remote --verbose</code> or, more simply, <code>git remote -v</code>. Now it tells us the following:</p>
<pre class="language-txt"><code class="language-txt">codeberg https://codeberg.org/inherentlee/git-intro.git (fetch)
codeberg https://codeberg.org/inherentlee/git-intro.git (push)</code></pre>
<p>cool! we have a remote set up. What does &quot;fetch&quot; and &quot;push&quot; mean?</p>
<h3 id="git-fetch-and-git-pull">git fetch (and git pull)</h3>
<p><code>git fetch</code> brings <strong>remote</strong> changes to our local machine. So does a command called <code>git pull</code>. Why are there two?</p>
<p><code>fetch</code> brings the remote changes down, but <em>doesn't combine them yet with our local work</em>. This gives us a chance to explore what those changes are before we actually integrate them into our work!</p>
<p>this may seem unhelpful if we're thinking about this project as something only we work on, but imagine there's a team of people all contributing to the project. What if we <em>and</em> another person both work on the same file? Our changes might overlap!</p>
<p>if we're working alone and <em>from one machine</em>, we'll pretty much never have to use <code>git fetch</code> or <code>git pull</code>! If we happen to do our work on multiple machines - for example, I do some work on my PC and some on my fruitpad (using an app called <a href="https://workingcopy.app/" target="_blank" rel="external">Working Copy</a>) - we'll probably update the remote from one machine, then need to pull that work down onto the other machine.</p>
<p>for our use case, we can pretty safely stick to <code>git pull</code> (if we ever even need to use it!), but if you're working in a larger collaborative project, <code>git fetch</code> is your friend!</p>
<h3 id="git-push-take-one">git push, take one</h3>
<p><code>git push</code> is the opposite of <code>git pull</code> - it takes your local changes and adds them to the remote.</p>
<p>the first time we use it on any given branch, we'll want to set what's called the <strong>upstream</strong> - the remote branch that our local branch is connected to by default. We can do this with the following command:</p>
<pre class="language-sh"><code class="language-sh"><span class="token function">git</span> push --set-upstream codeberg main
<span class="token comment"># or, for brevity</span>
<span class="token function">git</span> push <span class="token parameter variable">-u</span> codeberg main</code></pre>
<h3 id="authentication">authentication</h3>
<p>when we call <code>git push</code>, we're prompted for our Codeberg username and password.</p>
<p>personally, I find constantly authenticating tremendously annoying! There's a couple of ways to handle this.</p>
<ol>
<li>in the command line, we can use a few different credential management settings:
<ul>
<li><code>git config --global credential.helper cache</code> will store our username and password in memory. You'll be re-prompted every 15 minutes. I work in long enough sessions that this is still a pain for me, but it may work for you</li>
<li><code>git config --global credential.helper store</code> will save our username and password in a file on our machine, and only re-prompt if we change either value. <strong>Importantly,</strong> this method does <em>not</em> encrypt our password in any way! While it's convenient, it's not very secure</li>
<li>on Mac, <code>git config --global credential.helper osxkeychain</code> is a secure method for saving credentials</li>
</ul>
</li>
<li>if we installed <code>git</code> for Windows, we should have Git Credential Manager (GCM)</li>
<li>in <em>any</em> terminal environment, we can use an SSH key. This is my preferred method! I find it's a good balance between <em>never</em> logging in and <em>constantly</em> logging in - I do it once after opening the terminal, and them I'm good for that work session</li>
</ol>
<h3 id="ssh-keys">SSH keys</h3>
<p>while I'm not going to go into a lot of the technical concepts behind SSH keys, I will talk a bit about how my setup works. If you're happy with one of the other credential management setups above, feel free to <a href="#git-push-take-two">skip past this section</a> cause it's a bit chunky.</p>
<p>the SSH (secure shell) protocol allows for secure communication on an insecure network.</p>
<p>when we generate an SSH key, we get a <strong>public</strong> and a <strong>private</strong> key. These are mathematically related, and if we <em>encrypt</em> something with the private key, it can be <em>decrypted</em> with the public key, and vice versa.</p>
<p>the important thing to know is <em>you should never share your private key</em>. Also, while you aren't <em>forced</em> to set a password when creating these keys, I strongly recommend doing so.</p>
<h4 id="creation">creation</h4>
<p>running <code>ssh-keygen</code> will take us through a series of prompts. Assuming we don't already have an SSH key, the default file location is fine.</p>
<p>when you choose a passphrase, <em>write it down</em>. If you lose it, there is no recourse. You will have to generate a new SSH key.</p>
<p>after generation, we will have two files at the location specified by the tool (or the custom location we chose). Generally, that's the folder <code>.ssh</code> in our home directory. If we navigate to that directory (<code>cd $HOME/.ssh</code>) and look at the files (<code>ls</code>), we'll see files named <code>id_rsa</code> and <code>id_rsa.pub</code>. The one that ends with <code>.pub</code> is the <em>public</em> key.</p>
<h4 id="an-alias">an alias...</h4>
<p>time for a little more terminal knowledge!</p>
<p>I have two handy pieces of tooling in my terminal that I use for SSH operations.</p>
<p>the first one is an <em>alias</em> - basically a simple shortcut for a command. I've written an alias for outputting my public key so that when I need it, I can get it without having to write out the path to the key. Laziness is a virtue, okay?</p>
<p>there's a file in the home directory called <code>.bashrc</code>. It sets a lot of terminal-wide functionality. We're going to add an alias to it!</p>
<p>the command to output a file's contents is <code>cat &lt;filename&gt;</code>. My alias name of choice to <code>cat</code> my public SSH key is <code>sshcat</code> - but feel free to name yours something else.</p>
<p>navigate to the home directory (<code>cd</code>) and open your <code>.bashrc</code> file in your editor (<code>&lt;editor-command&gt; .bashrc</code>). Add the following to the bottom before saving and exiting.</p>
<pre class="language-txt"><code class="language-txt">alias sshcat="cat $HOME/.ssh/id_rsa.pub"</code></pre>
<blockquote>
<p>tip: don't be alarmed if you can't use this right away! Your <code>.bashrc</code> file takes effect when the terminal starts up. If you want to test it, either restart your terminal, or type <code>source $HOME/.bashrc</code></p>
</blockquote>
<h4 id="and-a-function">and a function</h4>
<p>the other piece of shortcut SSH key tooling I use is a <em>function</em> that I call <code>ssa</code>, short for <code>ssh-agent</code>. <code>ssh-agent</code> manages SSH keys and keeps us logged in during a session.</p>
<pre class="language-sh"><code class="language-sh"><span class="token comment"># SSH agent</span>
<span class="token function-name function">ssa</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token assign-left variable">ssa_pid</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span>pgrep ssh-agent<span class="token variable">)</span></span>
<span class="token keyword">if</span> <span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token variable">$ssa_pid</span> <span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span> <span class="token function">kill</span> <span class="token variable">$ssa_pid</span><span class="token punctuation">;</span> <span class="token keyword">fi</span>
<span class="token builtin class-name">echo</span> <span class="token parameter variable">-n</span> <span class="token string">"<span class="token variable">$fg</span>[green]"</span>
<span class="token builtin class-name">eval</span> <span class="token variable"><span class="token variable">$(</span>ssh-agent <span class="token parameter variable">-s</span><span class="token variable">)</span></span>
ssh-add ~/.ssh/id_rsa
<span class="token punctuation">}</span></code></pre>
<p>look, I'll be honest... we're not going to explain this one in detail. In short, though, it removes any existing instance of <code>ssh-agent</code>, then prompts us to put in our SSH key password so it can authenticate us.</p>
<p>open the <code>.bashrc</code> file again for editing, and add the above function to the bottom before saving and exiting. Again, either quit the terminal or type <code>source $HOME/.bashrc</code> to reload and use this new function by typing in <code>ssa</code>.</p>
<h4 id="ssh-keys-and-the-remote">SSH keys and the remote</h4>
<p>when we <a href="#git-remote">added a remote</a>, we used the HTTPS URL. Let's update to using the SSH URL - you can find this on the main project page under the dropdown button that reads <code>Code</code>.</p>
<pre class="language-sh"><code class="language-sh"><span class="token function">git</span> remote set-url codeberg <span class="token operator">&lt;</span>new-url<span class="token operator">></span></code></pre>
<p>we'll notice that the SSH URL starts with <code>git@</code>, whereas the HTTPS URL started with <code>https://</code>.</p>
<p>in order for all this to be useful, we need to tell Codeberg about our SSH key. In Codeberg, navigate to <a href="https://codeberg.org/user/settings/keys" target="_blank" rel="external">settings, then find the left-hand tab for SSH keys</a>. Choose 'Add key' and paste in the <strong>public</strong> key (if you set up that <code>sshcat</code> alias, use it now to output your key for ease of copying). Save and we'll now be set up to authenticate with SSH!</p>
<h3 id="git-push-take-two">git push, take two</h3>
<p>we can now call <code>git push</code> again and again now without having to repeat our credentials every time. We can also call <code>git pull</code> for our private repositories.</p>
<h2 id="summary">summary</h2>
<p>let's talk about what we've done.</p>
<ol>
<li>set up a terminal and learned a couple indispensable commands</li>
<li>created a new project with <code>git init</code></li>
<li>checked in <em>constantly</em> using <code>git status</code></li>
<li>learned what a commit is, and how to use <code>git log</code> to view our commit history</li>
<li>used <code>git add</code> to add new files or file changes to a commit</li>
<li>created commits with <code>git commit</code></li>
<li>undid a commit using <code>git revert</code></li>
<li>talked about remotes, making a new Codeberg project, and using <code>git remote</code> to link that project to our local work</li>
<li>talked about <code>git fetch</code> and <code>git pull</code></li>
<li>added our local work to the remote project using <code>git push</code></li>
<li>and finally, set up some kind of credential management so we don't have to log in for <em>every</em> <code>git push</code>!</li>
</ol>
<p>congratulations, and welcome to <code>git</code>!</p>
</article>
<nav aria-label="pagination">
<ol class="pagination post-pagination">
<li class="older">
<a href="/orion-handspun/">
<i class="fa-solid fa-hand-point-left" aria-hidden="true"></i> orion handspun
</a>
</li>
<li class="newer">
<a href="/comparing-text-editors/">
comparing text editors <i class="fa-solid fa-hand-point-right" aria-hidden="true"></i>
</a>
</li>
</ol>
</nav>
<hr>
<section class="related-posts">
<h2 id="related-posts">related posts</h2>
<ol id="postlist">
<li class="post">
<a class="postlink" href="/redirections/">
<img src="/img/angle-brackets-uwu.jpg" alt="Ascii art of an emoticon with pinched eyes and a small mouth made with two angle brackets." loading="lazy" decoding="async" width="1000" height="316">
<h2 id="redirections">redirections</h2>
<ul class="postlist-tags">
<li>software</li>
</ul>
</a>
</li>
<li class="post">
<a class="postlink" href="/eleventy-lessons/">
<img src="/img/hellebore.jpg" alt="Image unrelated to post. Close up on a pale green hellebore flower." loading="lazy" decoding="async" width="1000" height="666">
<h2 id="eleventy-lessons">eleventy lessons</h2>
<ul class="postlist-tags">
<li>software</li>
</ul>
</a>
</li>
<li class="post">
<a class="postlink" href="/domain-and-site-setup/">
<img src="/img/crinkly-mushrooms.jpg" alt="Picture unrelated to post. Some crinkly brown-orange mushrooms in vibrant green grass." loading="lazy" decoding="async" width="1000" height="750">
<h2 id="domain-and-site-setup">domain and site setup</h2>
<ul class="postlist-tags">
<li>software</li>
</ul>
</a>
</li>
</ol>
</section>
</heading-anchors>
</main>
<footer>
<ul>
<li>
<a href="/colophon/">
colophon
</a>
</li>
<li>
<a href="/" title="go home" aria-label="go home | hello hello from Lee Cattarin in 2026">
hello hello from Lee Cattarin in 2026</a>
</li>
<li>
<a href="https://heckin.technology/inherentlee/leecat.art" title="source code" aria-label="source code" target="_blank" rel="external">
src
</a>
</li>
</ul>
</footer>
<!-- This page `/an-intro-to-git/` was built on 2026-02-20T22:12:04.538Z -->
<body>
</body></body></html>