{"id":"l-git-4-3","courseId":"git-github","lang":"en","kind":"lesson","summary":"Write commit messages that explain why, follow the Conventional Commits format when a team uses it, mark versions with annotated tags, and publish a GitHub Release with generated notes.","html":"<p>Six months from now you’ll run <code dir=\"ltr\">git log</code> on your portfolio looking for when the dark theme broke, and you’ll be reading messages written by past you. “updates”, “fix”, “asdf” won’t help. History is only as useful as the words attached to it. This lesson is about making those words count, and then about naming the moments that matter: versions.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">What a good commit message looks like</h2>\n<p>A commit message has a <strong>subject line</strong>, a blank line, and an optional <strong>body</strong>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Text</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code>Fix footer overlapping content on mobile\n\nThe footer used position: fixed, so on screens shorter than\nthe content it covered the last project card. Switch to a\nnormal flow footer and add bottom padding to the grid.\n\nCloses #13</code></pre></div>\n<p>The conventions, and why they exist:</p>\n<ul>\n<li><strong>Subject under about 50 characters.</strong> It’s what <code dir=\"ltr\">git log --oneline</code>, GitHub’s commit lists and pull request titles show; long ones get cut off.</li>\n<li><strong>Imperative mood:</strong> “Fix”, “Add”, “Remove”, not “Fixed” or “Adds”. A handy test: the subject completes “If applied, this commit will…”. It also matches the messages Git writes itself, like “Merge branch ‘footer’”.</li>\n<li><strong>No full stop</strong> at the end of the subject.</li>\n<li><strong>The body explains why</strong>, wrapped at about 72 characters. The diff already shows <em>what</em> changed; only you know the reason, the alternative you rejected, the bug it fixes.</li>\n<li><strong>Trailers go at the bottom</strong>: <code dir=\"ltr\">Closes #13</code>, or <code dir=\"ltr\">Co-authored-by: Name &lt;email&gt;</code>, which GitHub uses to credit pair-programming partners.</li>\n</ul>\n<p>Use <code dir=\"ltr\">git commit</code> without <code dir=\"ltr\">-m</code> when a change needs a body; your editor makes multi-line messages easy. For one-line changes, <code dir=\"ltr\">-m</code> is fine.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Conventional Commits</h2>\n<p>Many teams adopt a structured format called <strong>Conventional Commits</strong>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Text</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code>feat(projects): add tag filter to projects page\nfix(nav): close mobile menu when a link is clicked\ndocs: add setup steps to README\nci: run HTML check on pull requests\nfeat!: rename data-theme attribute to data-color-scheme</code></pre></div>\n<p>The shape is <code dir=\"ltr\">type(optional scope): description</code>. The specification defines <code dir=\"ltr\">feat</code> (a new feature) and <code dir=\"ltr\">fix</code> (a bug fix); teams commonly add <code dir=\"ltr\">docs</code>, <code dir=\"ltr\">style</code> (formatting only, not CSS), <code dir=\"ltr\">refactor</code>, <code dir=\"ltr\">perf</code>, <code dir=\"ltr\">test</code>, <code dir=\"ltr\">build</code>, <code dir=\"ltr\">ci</code> and <code dir=\"ltr\">chore</code>. A <code dir=\"ltr\">!</code> before the colon, or a <code dir=\"ltr\">BREAKING CHANGE:</code> footer, marks a change that breaks existing users.</p>\n<p>The payoff is automation. Because the type is machine-readable, tools can generate changelogs and choose the next <strong>semantic version</strong> for you: <code dir=\"ltr\">fix</code> bumps the patch number (1.4.2 to 1.4.3), <code dir=\"ltr\">feat</code> the minor (1.5.0), a breaking change the major (2.0.0).</p>\n<p>Conventional Commits usually keeps the description lowercase, which differs from the capitalised style above. Neither is wrong. Use what the repository already uses; consistency beats preference. For your portfolio, try Conventional Commits; it’s common enough on teams that the habit pays off.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>One commit message, four changes</span></p>\n<p>A message that needs “and” three times (“add contact form and fix nav and change colours”) is telling you the commit should have been split. Before committing, run <code dir=\"ltr\">git diff --staged</code> and ask whether a single sentence covers it. If not, unstage and commit in pieces with <code dir=\"ltr\">git add -p</code>.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Tags: naming a moment</h2>\n<p>Branches move; <strong>tags</strong> don’t. A tag is a permanent name for one commit, used to mark versions: the portfolio as it was at launch, at v1.1, and so on.</p>\n<p>Git has two kinds. A <strong>lightweight</strong> tag is just a name pointing at a commit. An <strong>annotated</strong> tag is a full object with the tagger’s name, a date and a message. Use annotated tags for releases:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> tag</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -a</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> v1.0.0</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -m</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"Portfolio launch\"</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> tag</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">                       # list tags</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> show</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> v1.0.0</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">               # tag message, then the commit it points at</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> tag</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -a</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> v0.9.0</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> b2e04f3</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -m</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"Preview for mentors\"</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">   # tag an older commit</span></span></code></pre></div>\n<p>Tags stay on your machine until you push them, which surprises almost everyone the first time:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> push</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> origin</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> v1.0.0</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">        # one tag</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> push</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --follow-tags</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">        # commits plus any annotated tags that point at them</span></span></code></pre></div>\n<p>Treat a pushed tag as permanent. If you tagged the wrong commit and already pushed it, publish a new version (v1.0.1) rather than moving v1.0.0 under people who may have used it.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">GitHub Releases</h2>\n<p>A <strong>release</strong> on GitHub is a tag plus a title, notes and optional downloadable files. Open your repository’s Releases page, choose Draft a new release, pick the tag (or create one), and click <strong>Generate release notes</strong>: GitHub lists the pull requests merged since the previous release, with their authors. Edit the notes into something a human wants to read, then publish.</p>\n<p>The CLI does the same in one line:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">gh</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> release</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> create</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> v1.0.0</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --title</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"Portfolio v1.0.0\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --generate-notes</span></span></code></pre></div>\n<p>Good pull request titles become good release notes for free, one more reason the earlier habits pay off.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Version numbers for a website</span></p>\n<p>A portfolio site has no API users, so strict semantic versioning is optional. Tagging launches and major redesigns still gives you named points you can compare (<code dir=\"ltr\">git diff v1.0.0 v2.0.0 -- styles.css</code>) and return to, which is worth the ten seconds.</p>\n</aside>\n<p>Clean messages, tags and releases make history readable. The final step is making <code dir=\"ltr\">main</code> trustworthy without having to watch it yourself: automated checks on every pull request, and rules that enforce them.</p>\n","toc":[{"id":"sec-1","text":"What a good commit message looks like","level":2},{"id":"sec-2","text":"Conventional Commits","level":2},{"id":"sec-3","text":"Tags: naming a moment","level":2},{"id":"sec-4","text":"GitHub Releases","level":2}],"takeaways":["A good subject line is short, imperative and specific (“Fix footer overlap on mobile”); the body, after a blank line, explains why.","Conventional Commits prefix the subject with a type such as <code dir=\"ltr\">feat</code>, <code dir=\"ltr\">fix</code> or <code dir=\"ltr\">docs</code>, which lets tools derive version numbers and changelogs.","Annotated tags (<code dir=\"ltr\">git tag -a v1.0.0 -m &quot;…&quot;</code>) record who tagged what and when; they’re the right kind for releases.","<code dir=\"ltr\">git push</code> doesn’t send tags; push them with <code dir=\"ltr\">git push origin v1.0.0</code> or <code dir=\"ltr\">git push --follow-tags</code>.","A GitHub Release is a tag plus notes and optional files, and GitHub can generate the notes from merged pull requests."],"further":[{"title":"Git Basics, Tagging (Pro Git)","url":"https://git-scm.com/book/en/v2/Git-Basics-Tagging"},{"title":"Commit Guidelines (Pro Git)","url":"https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines"},{"title":"About releases","url":"https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases"},{"title":"Automatically generated release notes","url":"https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes"}],"quiz":[{"q":"<p>Which subject line best follows common Git conventions?</p>","options":[{"text":"<code dir=\"ltr\">Fixed some bugs.</code>","why":"Past tense, a full stop, and “some bugs” tells a future reader nothing about what changed."},{"text":"<code dir=\"ltr\">Changes to the footer, nav, colours and the contact form so it works on phones now</code>","why":"Far too long for a subject, and it lists four changes, which suggests four commits."},{"text":"<code dir=\"ltr\">WIP</code>","why":"Fine as a temporary local commit you’ll squash, but meaningless once it’s in shared history."},{"text":"<code dir=\"ltr\">Fix footer overlapping content on mobile</code>","why":"Correct. Imperative, specific, under 50 characters. It completes the sentence “If applied, this commit will…”."}],"answer":3},{"q":"<p>Under Conventional Commits and semantic versioning, which change should bump version 1.4.2 to 2.0.0?</p>","options":[{"text":"<code dir=\"ltr\">fix: correct contrast on dark mode links</code>","why":"A fix maps to a patch release, 1.4.3."},{"text":"<code dir=\"ltr\">feat: add tag filter to projects page</code>","why":"A new feature maps to a minor release, 1.5.0."},{"text":"<code dir=\"ltr\">feat!: rename the data-theme attribute to data-color-scheme</code>","why":"Correct. The <code dir=\"ltr\">!</code> (or a <code dir=\"ltr\">BREAKING CHANGE:</code> footer) marks a breaking change, which maps to a major release."},{"text":"<code dir=\"ltr\">docs: explain how to run the HTML check</code>","why":"Documentation changes don’t affect the published behaviour and usually don’t trigger a release at all."}],"answer":2},{"q":"<p>You ran <code dir=\"ltr\">git tag -a v1.0.0 -m &quot;Portfolio launch&quot;</code> and <code dir=\"ltr\">git push</code>. The tag isn’t on GitHub. Why?</p>","options":[{"text":"<code dir=\"ltr\">git push</code> doesn’t push tags by default; run <code dir=\"ltr\">git push origin v1.0.0</code> (or <code dir=\"ltr\">git push --follow-tags</code>).","why":"Correct. Tags are pushed explicitly, so you don’t accidentally publish experimental ones."},{"text":"Annotated tags can’t be pushed; only lightweight tags can.","why":"Both kinds can be pushed. Annotated tags are the recommended kind for releases."},{"text":"GitHub only accepts tags created through its Releases page.","why":"GitHub accepts any pushed tag, and a release can be created from it afterwards."},{"text":"The tag name needs to start with <code dir=\"ltr\">release-</code>.","why":"Tag names are free-form. <code dir=\"ltr\">v1.0.0</code> is the common convention."}],"answer":0},{"q":"<p>What’s the main advantage of an annotated tag over a lightweight tag for a release?</p>","options":[{"text":"Annotated tags can point at any commit; lightweight tags only at HEAD.","why":"Both kinds can point at any commit. Give the commit ID after the tag name."},{"text":"An annotated tag is a full object with tagger, date and message, so the release records who marked it and why.","why":"Correct. A lightweight tag is only a name for a commit, like a branch that never moves."},{"text":"Annotated tags move forward with new commits, like branches.","why":"Neither kind of tag moves. That’s the point of a tag."}],"answer":1}],"exercise":{"kind":"spot-bug","language":"text","code":"feat(projects): add tag filter to projects page\nfix: stop footer overlapping content on mobile\nUpdated stuff\ndocs: add setup steps to README\nfeat(contact): Added contact form, fixed nav and changed colors\nchore: update html-validate dev dependency\nfix(nav): close mobile menu when a link is clicked\nstyle projects grid spacing","bugLines":[3,5,8],"title":"Review the commit messages","prompt":"<p>These are the commit subjects on a pull request to your portfolio, a repository that follows Conventional Commits (<code dir=\"ltr\">type(optional scope): description</code>, imperative, one change per commit). Select the three lines a reviewer should ask to be rewritten.</p>\n","hints":["Every subject needs a type followed by a colon.","Check the mood of the verbs, and count how many changes a single line describes.","In Conventional Commits, <code dir=\"ltr\">style</code> means code formatting, not CSS work, and it still needs a colon."],"explanation":"<p>Line 3, <code dir=\"ltr\">Updated stuff</code>, has no type and says nothing; something like <code dir=\"ltr\">refactor: rename nav classes to BEM</code> would. Line 5 uses past tense and bundles three changes (contact form, nav, colours) into one commit; split it into <code dir=\"ltr\">feat(contact): add contact form</code>, <code dir=\"ltr\">fix(nav): …</code> and a separate commit for the colours, such as <code dir=\"ltr\">feat(theme): …</code> (not <code dir=\"ltr\">style:</code>, which is for formatting only). Line 8 is missing the colon, so tools can’t parse it, and <code dir=\"ltr\">style</code> in Conventional Commits means formatting-only changes; spacing in the grid is a visible change, so <code dir=\"ltr\">fix(projects): tighten grid spacing on mobile</code> fits better. The other lines are well-formed: type, optional scope, colon, and a specific imperative description.</p>\n","checkLabels":{}},"words":713,"readMinutes":3}