j3s's git necronomicon

another pufferfish drawn by rekka

j3s's git necronomicon DRAFT DRAFT DRAFT DRAFT WARNING: THERE BE UNFINISHED HTML AHEAD <p><em>for somewhat experienced computer-ers who want to git good</em></p> <p>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.</p> <p>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.</p> <p>i will teach you only what you must know to use git alone, and then with other people.</p> <h1 id="git-is-a-distributed-version-control-system">git is a distributed version control system</h1> <p>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.</p> <p>when people say the word git, they generally mean one of three things:</p> <ul> <li>a git repository</li> <li>the git command line tool</li> <li>github (because it’s so popular people see this as “git” sometimes)</li> </ul> <h1 id="j3ss-recommended-barebones-git-config">j3s’s recommended barebones git config</h1> <p>you can configure git via a file: $HOME/.git/config or $HOME/.config/git/config</p> <p>these are normally preferences, but I highly recommend the following bare-bones config before you even start using git, to fix some questionable defaults:</p> <pre><code>[user] name = Your Name email = you@example.org # to find your SMTP settings, duckduckgo for &quot;email-provider-name SMTP&quot; (for example &quot;gmail SMTP&quot;) [sendemail] smtpserver = mail.example.org smtpuser = you@example.org smtpencryption = tls smtpserverport = 587 [advice] detachedhead = false [merge] ff = only</code></pre> <h1 id="foundational-knowledge-repositories-refs-and-commits-oh-my">Foundational Knowledge: repositories, refs, and commits, oh my!</h1> <p>there are many fundamental concepts in git, and they’re often very confusing because they’re not intuitively named. please read these definitions carefully.</p> <h2 id="repositories">repositories</h2> <p>a git repository is just a pile of commits, and some refs.</p> <h2 id="most-git-work-is-local-work">most git work is local work</h2> <p>no command used in this document besides <code>git push</code> or <code>git fetch</code> affects anything other than our local git repositories - the ones on our own filesystem.</p> <h2 id="commits">commits</h2> <p>a commit is a simple record of who made the changes, and what &amp; when the changes were made</p> <p>each git commit gets a unique ID - it looks like this: <code>3c622a6860bfb21cffcd3bb51066c7b780206ca3</code></p> <p>or, some people use the first 7 or 8 characters of the commit ID as a shorthand: <code>3c622a6</code></p> <p>example of making a git commit while inside a git repository:</p> <div class="sourceCode" id="cb2"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co"># first, we will make a commit:</span></span> <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a></span> <span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> echo <span class="st">&quot;hello world!&quot;</span> <span class="op">&gt;</span> file</span> <span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> git add file</span> <span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> git commit <span class="at">-m</span> <span class="st">&#39;add file&#39;</span></span> <span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="ex">[master</span> <span class="er">(</span><span class="ex">root-commit</span><span class="kw">)</span> <span class="ex">94686b3]</span> add file</span> <span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">1</span> file changed, 1 insertion<span class="er">(</span><span class="ex">+</span><span class="kw">)</span></span> <span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a> <span class="ex">create</span> mode 100644 file</span></code></pre></div> <p>example of viewing a full git commit:</p> <div class="sourceCode" id="cb3"><pre class="sourceCode diff"><code class="sourceCode diff"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>$ git log -p -2</span> <span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>commit 94686b38ed654c2a809a68e4e207cc1819c5764d (HEAD -&gt; master)</span> <span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>Author: j3s &lt;j3s@c3f.net&gt;</span> <span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>Date: Mon Jan 4 13:02:09 2021 -0600</span> <span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a></span> <span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a> add file</span> <span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a></span> <span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="kw">diff --git a/file b/file</span></span> <span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a>new file mode 100644</span> <span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a>index 0000000..a042389</span> <span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="dt">--- /dev/null</span></span> <span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a><span class="dt">+++ b/file</span></span> <span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a><span class="dt">@@ -0,0 +1 @@</span></span> <span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a><span class="va">+hello world!</span></span></code></pre></div> <p>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).</p> <p>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.</p> <h3 id="commit-ids">commit ids</h3> <p>you might refer to this commit as <code>94686b38ed654c2a809a68e4e207cc1819c5764d</code> or <code>94686b38</code> for short.</p> <h3 id="git-checkout">git checkout</h3> <p>the <code>git checkout</code> 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).</p> <h3 id="qa">Q&amp;A</h3> <blockquote> <p>wtf was <code>git add</code> about?</p> </blockquote> <p>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 <code>git add</code> to “stage” only those changes, followed by <code>git commit -m "commit message"</code> to commit them.</p> <blockquote> <p>i’m lost</p> </blockquote> <p>use <code>git status</code> in a repo at any time to get an overview of where you’re at.</p> <p>ask me questions - j3s@c3f.net (email) or <span class="citation" data-cites="j3s:cyberia.club">@j3s:cyberia.club</span> (matrix)</p> <blockquote> <p>how are these commit records ordered?</p> </blockquote> <p>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.</p> <h2 id="refs">refs</h2> <p>referring to a commit by ID really sucks. so refs are a thing.</p> <p>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.</p> <p>refs simply point to commit IDs. easy peasy.</p> <p><code>i-love-this-commit -&gt; 94686b38</code></p> <p><code>development-test-1 -&gt; 94686b38ed654c2a809a68e4e207cc1819c5764d</code></p> <p>there are two super important types of refs - <em>branches and tags</em>.</p> <h3 id="branches">branches</h3> <p>branches always point to exactly 1 commit.</p> <p><em>however</em> when commits are made <em>while you are on a branch</em>, the branch updates the commit it is referencing to the commit you have just made.</p> <p>for example:</p> <pre><code># see the commit ID that our current branch is referencing $ git show-branch --sha1-name [94686b3] add file # make a new commit $ echo &#39;hello world, again??&#39; &gt; file2 $ git add file2 $ git commit -m &#39;add file2&#39; # see that the commit our branch is referencing has changed auto-magically $ git show-branch --sha1-name [9e379d4] add file2</code></pre> <h4 id="qa-1">Q&amp;A</h4> <blockquote> <p>what is this git checkout thing?</p> </blockquote> <p>you may use <code>git checkout</code> to move between commits. you can target either a commit ID or a ref.</p> <p>for example, these are all valid:</p> <p> <code>git checkout my-branch</code> <code>git checkout some-tag</code> <code>git checkout 94686b38</code> </p> <p><em>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.</em></p> <blockquote> <p>if i run <code>git init</code>, what is the default branch name?</p> </blockquote> <p><code>master</code>. 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.</p> <blockquote> <p>what if i don’t commit to a branch?</p> </blockquote> <p>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.</p> <p><em>however</em>, 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.</p> <p>git intends for you to use branches while developing.</p> <blockquote> <p>what is this HEAD thing?</p> </blockquote> <p>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.</p> <blockquote> <p>wtf is a “detached HEAD”</p> </blockquote> <p>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.</p> <p>the only thing you’re “detached” from is the latest commit of branches. so your current HEAD is detached from the branch. get it?</p> <h2 id="tags">tags</h2> <p>tags are refs that are almost exclusively used to point to commits that are known to be stable, and are used for versioning purposes.</p> <p><em>critically, tags never update or change in any way after they’re created.</em></p> <p>there are several types of tags - everyone uses <em>annotated tags</em>. almost everyone refers to “annotated tags” as “tags.” you’re unlikely to need to use or learn about other types of tags.</p> <p>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.</p> <p>to make an annotated tag:</p> <pre><code># hey! i have a commit i really like. # it&#39;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 &quot;yeah it&#39;s lit&quot; # you can now checkout your new funky tag $ git checkout i-really-like-this-particular-commit # note that you will be in the &quot;detached head&quot; state after # checking out your tag. this isn&#39;t bad, but git would make # you believe it is.</code></pre> <p>⚠️WARNING⚠️</p> <p>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!</p> <h2 id="foundational-knowledge-complete">Foundational knowledge complete</h2> <p>congrats! you know a lot about git now. take a break pls, relax your shoulders &amp; 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…</p> <p>…</p> <h2 id="tldr">tl;dr</h2> <p>concepts:</p> <ul> <li>a git repo is a pile of commits and some refs</li> <li>commits contain the who, what, when, and why of changes made to the files. they also have annoying IDs.</li> <li>refs are just human friendly pointers to commit IDs</li> <li>branches are a type of ref that auto-updates when you commit to it</li> <li>tags are a type of ref that have messages, and never change. they are mostly used for versioning.</li> </ul> <p>tools:</p> <ul> <li><code>git checkout &lt;tag/branch/commitID&gt;</code> moves around between commits</li> <li><code>git status</code> shows the current branch and any changed files and other misc helpful info</li> <li><code>git add &lt;filename&gt;</code> will stage changes</li> <li><code>git commit -m "message"</code> will commit changes</li> </ul> <h1 id="working-with-others">working with others</h1> <p>And now, for more annoying things: working with people. or: pushing, fetching, cloning, remotes, and merging</p> <p>If you use git to maintain some personal projects on one machine, stop reading - you’re done!</p> <p>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.</p> <h2 id="remote-confusion">remote confusion</h2> <p>there are three absolute sources of confusion with working with remote repositories, and i want to address them up front.</p> <p><em>ONE: git is almost completely local</em></p> <p>people often think that git commands will interact with servers, in reality this is very rare.</p> <p>no git commands in this guide interact with servers except <code>git push</code> and <code>git fetch</code>.</p> <p>almost all git commands are local.</p> <p><em>TWO: remote repositories are tangled with your local repositories.</em></p> <p>remote repositories are not tangled with your local repository. even if you git clone a remote repository, the local clone is entirely independent.</p> <p><em>the short name for “remote repositories” is “remotes”</em></p> <p>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.</p> <h2 id="remotes-and-fetching-and-pushing">remotes and fetching and pushing</h2> <p>remotes are, quite simply, git repositories that are somewhere else.</p> <p>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:</p> <pre><code>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</code></pre> <p><em>note that HTTPS and FTP URLs are almost always read-only.</em> <em>if you want to push, use the SSH URL</em></p> <h3 id="adding-remotes">adding remotes</h3> <p>we can add a remote to our test repository like so:</p> <pre><code>$ 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 -&gt; terminalshit/master</code></pre> <p>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.</p> <p>once remotes are fetched, you can use them locally, not so remote after all!</p> <pre><code>$ 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 &quot;git checkout terminalshit/master&quot; here, # remotes/ is implied $ ls LICENSE.md README.md main.go</code></pre> <p>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.</p> <p>let’s undo that and add a remote that actually makes sense.</p> <h3 id="changing-remotes">changing remotes</h3> <p>say you’ve added a wrong remote and want to change the URL.</p> <p>One way is to simply remove the bad remote and add a good one:</p> <pre><code>$ git checkout master Switched to branch &#39;master&#39; $ 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)</code></pre> <p>i used the Cyberia Forge to make a blank remote git repository, and got the URL from its’ interface.</p> <p>since we have write access, we can easily push our changes to the remote:</p> <pre><code># for reasons that we may never know, # the syntax of this command is `git push &lt;remote-name&gt; &lt;branch-name&gt;` $ 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 -&gt; master</code></pre> <p>woo! we’ve stored our changes remotely. we can now do infinite stage, commit, push loops forever and ever… unless???</p> <h2 id="merging">merging</h2> <p>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.</p> <p>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.</p> <p>we’ll use the <code>git merge</code> tool - the syntax is <code>git merge &lt;branch&gt;</code>.</p> <p>it will merge whatever branch you target into your current branch.</p> <p>of course, you may target remote branches.</p> <pre><code># 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 &gt; file1 $ git add file1 $ git commit -m &#39;add file1&#39; [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 &#39;different-branch&#39; $ echo blahblah &gt; file2 $ git add file2 $ git commit -m &#39;add file2&#39; [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 &quot;git merge different-branch&quot;. $ git checkout master Switched to branch &#39;master&#39; $ git log commit ab1b6025ea49c40e5ef4acf68272d0dd9e4de963 (HEAD -&gt; master) Author: j3s &lt;j3s@c3f.net&gt; Date: Tue Jan 5 16:54:55 2021 -0600 add file1 $ git merge different-branch Merge made by the &#39;recursive&#39; strategy. file2 | 1 + 1 file changed, 1 insertion(+) create mode 100644 file2 $ git log commit 90c66a1a6fe29073042c1fd2b391640e2b81f4ca (HEAD -&gt; master) Merge: ab1b602 d8b4a32 Author: j3s &lt;j3s@c3f.net&gt; Date: Tue Jan 5 16:55:37 2021 -0600 Merge branch &#39;different-branch&#39; commit d8b4a323726f2dde3e8edfe6cf9d09bc90c6c19f (different-branch) Author: j3s &lt;j3s@c3f.net&gt; Date: Tue Jan 5 16:55:23 2021 -0600 add file2 commit ab1b6025ea49c40e5ef4acf68272d0dd9e4de963 Author: j3s &lt;j3s@c3f.net&gt; Date: Tue Jan 5 16:54:55 2021 -0600 add file1</code></pre> <p>You will note that merging branches causes an extra commit to be generating detailing the merge - that is good! it’s mostly clerical.</p> <p>eventually, possibly even already, you’ll run into the dreaded…</p> <h3 id="a-word-of-mergey-warning-or-why-ff-is-good">A WORD OF MERGEY WARNING, OR: WHY FF is GOOD</h3> <p>ok, let me stop for a sec and address something quick.</p> <p>git merge doesn’t always require a nice commit message. sometimes, it can detect a very particular situation.</p> <p>let’s say that you have cloned a remote repository, like shitchat.</p> <p>let’s say you did this in the year 1974.</p> <pre><code>$ date Tue 05 Jan 1974 05:07:25 PM CST $ git clone https://giit.cyberia.club/~j3s/shitchat</code></pre> <p>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!</p> <pre><code>$ 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 -&gt; origin/master $ git merge origin/master Updating c1466a6..3994c88 Fast-forward README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 README.md</code></pre> <p>Wait, <code>Fast-forward</code>? why didn’t i get to type a commit message?</p> <p>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!</p> <p>if you own two copies of <code>harry potter: the wizard</code> 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.</p> <h3 id="buzzfeed-article-is-git-pull-problematic">BUZZFEED ARTICLE: IS GIT PULL PROBLEMATIC???</h3> <p>git pull has a troubled past.</p> <p>people will tell you to use <code>git pull</code> - don’t listen to those people unless you know this wisdom:</p> <ul> <li>git pull is just “git fetch <remote>” followed by “git merge <remote>”</li> </ul> <p>I highly recommend using this methodology first. fetch and merge.</p> <ul> <li>git pull can create all sorts of hell with your commit history, and it will confuse the shit out of you</li> </ul> <p>see https://stackoverflow.com/questions/15316601/in-what-cases-could-git-pull-be-harmful</p> <p>by default, git pull can have many dreaded consequences - the worst of all is leaving inconsistent nonlinear commits fucking everywhere.</p> <p>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 <code>git pull</code> after a few weeks/months once you have dealt with various edge cases.</p> <p>tl;dr: don’t use <code>git pull</code> if you’re new to git, or it’ll cost you a lot of time/work.</p> <p>tldr;tl;dr: use git fetch &amp;&amp; git merge, forget git pull.</p> <h3 id="merge-conflicts">MERGE CONFLICTS</h3> <p>welp, things were going fine. git seemed easy and intuitive. but then… well…</p> <p>when git tries to merge and cannot automatically do it, it requires human intervention. this is called a “merge conflict”. let us demystify.</p> <p>like merging, merge conflicts are easier to demonstrate than describe.</p> <p>First, we’ll initialize 2 repositories with identical histories.</p> <pre><code>$ git init repo1 Initialized empty Git repository in /tmp/repo1/.git/ $ cd repo1 $ echo &#39;blahblahblah&#39; &gt; file1 $ git add file1 $ git commit -m &#39;add file1&#39; [master (root-commit) 74a9348] add file1 1 file changed, 1 insertion(+) create mode 100644 file1 $ cd .. $ cp -a repo1 repo2</code></pre> <p>Now, we’ll make their <code>master</code> branches have different commits.</p> <pre><code>$ cd repo2 $ echo &#39;this is shitty code&#39; &gt;&gt; file1 $ git add file1 $ git commit -m &#39;i have consumed too much alcohol and produced suboptimal *hic* code&#39; [master 3211e8d] i have consumed too much alcohol and produced suboptimal *hic* code 1 file changed, 1 insertion(+) $ cd ../repo1 $ echo &#39;shit is amazing code&#39; &gt;&gt; file1 $ git add file1 $ git commit -m &#39;i have consumed stimulants and am soulless but produce good code&#39; [master 9099012] i have consumed stimulants and am soulless but produce good code 1 file changed, 1 insertion(+)</code></pre> <p>now, we will simply add repo2 as a remote for repo1! and we’ll try and merge their histories, which of course cannot work.</p> <pre><code>$ 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 -&gt; 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.</code></pre> <p>NOW we’re in it. a weird state. HELP! what does <code>git status</code> say???</p> <pre><code>$ git status On branch master You have unmerged paths. (fix conflicts and run &quot;git commit&quot;) (use &quot;git merge --abort&quot; to abort the merge) Unmerged paths: (use &quot;git add &lt;file&gt;...&quot; to mark resolution) both modified: file1 no changes added to commit (use &quot;git add&quot; and/or &quot;git commit -a&quot;)</code></pre> <p>okay, fix conflicts. let’s pop open the file:</p> <pre><code>$ cat file1 blahblahblah &lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD shit is amazing code ======= this is shitty code &gt;&gt;&gt;&gt;&gt;&gt;&gt; repo2/master</code></pre> <p>git inserted a bunch of shit.</p> <p>basically, everything between <code>&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD</code> and <code>=======</code> is your current branch. everything between <code>=======</code> and <code>&gt;&gt;&gt;&gt;&gt;&gt;&gt; repo2/master</code> is <code>repo2/master</code> stuff.</p> <p>your job is to pick the right stuff, remove the &lt;&lt;&lt;&lt; and &gt;&gt;&gt;&gt; and ==== parts, and then type <code>git add file1</code> and <code>git commit</code>. so, let’s do it.</p> <pre><code>$ cat file1 blahblahblah shit is amazing code $ git status On branch master You have unmerged paths. (fix conflicts and run &quot;git commit&quot;) (use &quot;git merge --abort&quot; to abort the merge) Unmerged paths: (use &quot;git add &lt;file&gt;...&quot; to mark resolution) both modified: file1 no changes added to commit (use &quot;git add&quot; and/or &quot;git commit -a&quot;) $ git add file1 $ git commit [master 24d690d] Merge remote-tracking branch &#39;repo2/master&#39;</code></pre> <p>voila, merge conflict resolved. if this sort of thing ever gets horribly complicated (and god, it does) - use a good GUI, it helps IMMENSELY.</p> <p>check out how cute our commit history looks after that though:</p> <pre><code>* 24d690d - Merge remote-tracking branch &#39;repo2/master&#39; (HEAD -&gt; master) |\ | * 3211e8d - i have consumed too much alcohol and produced suboptimal *hic* code (repo2/master) * | 9099012 - i have consumed stimulants |/ * 74a9348 - add file1</code></pre> <p>hopefully, you’re able to reason about the above commit graph!</p> <h2 id="cloning">cloning</h2> <p>okay, fine, good. we’ve already cloned a whole bunch but let’s sorta cover it.</p> <p>first order of business, clone a repo.</p> <pre><code>git clone https://giit.cyberia.club/~j3s/git-example Cloning into &#39;git-example&#39;... 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.</code></pre> <p>What happened here, exactly? well, it turns out that git clone does a shit ton of things.</p> <p>git clone:</p> <ul> <li>makes a directory</li> <li>makes a blank git repository in that directory</li> <li>adds a remote repository called “origin” based on the URL you typed in</li> <li>makes a local branch called “master”</li> <li>fetches the remote repository</li> <li>merges the default remote branch (master, most likely) into your local master branch, making them identical</li> </ul> <p>We can see the results:</p> <pre><code>$ git branch -a * master remotes/origin/HEAD -&gt; origin/master remotes/origin/master</code></pre> <p>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.</p> <h2 id="summary-2">summary 2</h2> <p>ALRIGHT. we have covered a lot of confusing ground. let’s summarize.</p> <ul> <li>remotes can be added and fetched</li> <li>remotes can use HTTPS, FTP, SSH, local FS, or any number of protocols</li> <li>any branch can be merged into any other branch as long as they share some history</li> <li>git pull is just fetch + merge</li> <li>git push will push your commits to a remote repository</li> <li>clone is a bunch of shit packed together for convinience</li> <li>fast forwarding during merging is good and makes no commit messages</li> <li>merge conflicts happen when git needs a human touch</li> </ul> <h1 id="workflow-examples">workflow examples</h1> <h2 id="working-on-your-own-project-but-keeping-a-copy-of-the-repository-on-a-server-somewhere">working on your own project, but keeping a copy of the repository on a server somewhere</h2> <p>you might choose this workflow so that you can clone your repo from any computer you work on!</p> <ol type="1"> <li>clone remote repository</li> <li>change some files</li> <li>stage and commit your changes (remember, commits can <em>only</em> happen locally)</li> <li>push your changes</li> </ol> <h2 id="working-on-someone-elses-project">working on someone elses project</h2> <p>you might choose this workflow because you have to!</p> <ol type="1"> <li>clone remote repository</li> <li>change some files</li> <li>stage and commit your changes</li> <li>get your commits to them somehow (either via email patches, or pull requests)</li> </ol> <h2 id="a-typical-git-workflow-working-on-your-own-project">a typical git workflow, working on your own project</h2> <ol type="1"> <li>make a local repository</li> <li>make an initial commit to this repository, adding all the files you want to track</li> <li>add a remote to your repository</li> <li>push to the remote</li> </ol> <h1 id="etc">etc</h1> <p>this is other useful stuff to know, in no particular order</p> <h2 id="git-diff">git diff</h2> <p>I use <code>git diff</code> very often. i use <code>git diff --staged</code> just as often!</p> <p><code>git diff</code> will simply show you any unstaged changes that you’ve made. combined with <code>git status</code> it’ll give you confidence before you type <code>git commit</code> and inevitably commit some fucked up stuff.</p> <h2 id="help-i-misspeldt-some-shit-in-my-git-commit">help, i misspeldt some shit in my git commit</h2> <p>if you haven’t pushed yet, <code>git commit --amend</code> can fix up a local commit.</p> <p>if you have already pushed, live with it. embrace it. maybe Randomly capitalize things to keep people on their toes.</p> <h2 id="pull-requests">pull requests</h2> <p>basically, a pull request is the diff of your branch and someone elses branch.</p> <p>but how do you get this diff to them??? why, register an account on github of course!</p> <p><em>pull requests have nothing to do with git. they were invented by github</em> <em>in an effort to proprietarily extend git and make you reliant on their service.</em></p> <p>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.</p> <p>you normally don’t have write access to repos you want to contribute to, so github/gitlab/gitea/etc will have you:</p> <ol type="1"> <li>make an account</li> <li>“fork” (more jargon) someones repository</li> <li>clone your fork</li> <li>make changes locally</li> <li>push those changes to your fork</li> <li>make a pull request via the web GUI</li> <li>the pull request will display the difference between your branch and theirs</li> <li>maintainers can “merge” your branch in, if there are no merge conflicts</li> </ol> <p>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.</p> <p>github was good for free software awhile ago, and they’re still pretty cool i think.</p> <p>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.</p> <h2 id="bare-repositories">bare repositories</h2> <p>“normal” git repos are called “working repos”. weird git repos are called “bare repos”.</p> <p>bare repos and working repos are both fine, but normally:</p> <ul> <li>a repo on a workstation is normally a working repo</li> <li>a remote server-side repo is normally bare</li> </ul> <p>the reasons are pretty boring. read <a href="https://web.archive.org/web/20200410064607/https://www.saintsjd.com/2011/01/what-is-a-bare-git-repository/">this</a></p> <h2 id="recursion-but-upwards-though">recursion, but upwards, though</h2> <p>the git cli will search all parent directories for a .git directory. if it finds one, it will try and use it.</p> <p>example:</p> <pre><code>$ 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 &quot;git add&quot; to track)</code></pre> <h2 id="gitignore">.gitignore</h2> <p>you can make a <code>.gitignore</code> file in the root of your git repo. this file tells git what to not track. you can use globs. so a <code>.gitignore</code> file might look something like this:</p> <pre><code>.DS_STORE huge-compiled-binary .editor-preferences python-libs/*</code></pre> <p>assuming you didn’t want to store the python libs in git. probably not.</p> <h2 id="can-i-store-bigass-non-text-files-in-git">can i store bigass non text files in git?</h2> <p>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.</p> <h1 id="misc-stuff">Miscellaneous Stuff</h1> <h2 id="i-have-horribly-fucked-up-this-git-repository-but-a-remote-repository-exists-in-a-good-state">I have horribly fucked up this git repository, but a remote repository exists in a good state</h2> <pre><code>cd .. rm -rf your-git-repo git clone remote-git-repo</code></pre> <h2 id="i-have-a-merge-conflict-and-have-no-idea-how-to-procede">I have a merge conflict and have no idea how to procede</h2> <p>use git status to find troubleshooting details:</p> <pre><code>git status</code></pre> <h2 id="i-want-to-contribute-to-a-project-that-uses-a-mailing-list">I want to contribute to a project that uses a mailing list</h2> <p>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.</p> <p>Then;</p> <pre><code>git clone https://giit.cyberia.club/~j3s/git-example cd git-example echo &quot;CHANGES!&quot; &gt;&gt; file1 git add file1 git commit -m &#39;change file1&#39; git send-email --to=&quot;mailing-list@example.org&quot; HEAD^ # HEAD^ is a shortcut for &quot;the latest 1 commit&quot;</code></pre> <h2 id="i-accidentally-pushed-a-secret-to-a-public-repo">i accidentally pushed a secret to a public repo</h2> <p>public repos are scraped constantly, the best possible option is to rotate the credential <em>immediately</em> and leave the old credential in git.</p> the second best option is to force overwrite history lololol but nobody does that, right?

follow me on mastodon!

last updated 1993-02-18