<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ GitHub - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ GitHub - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 18 May 2026 22:34:42 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/github/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build an Agentic Terminal Workflow with GitHub Copilot CLI and MCP Servers ]]>
                </title>
                <description>
                    <![CDATA[ Most developers live in their terminal. You run commands, debug pipelines, manage infrastructure, and navigate codebases, all from a shell prompt. But despite how central the terminal is to developer  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-an-agentic-terminal-workflow-with-github-copilot-cli-and-mcp-servers/</link>
                <guid isPermaLink="false">69f212526e0124c05e1857b5</guid>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ terminal ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mcp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mcp server ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Caleb Mintoumba ]]>
                </dc:creator>
                <pubDate>Wed, 29 Apr 2026 14:14:42 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/3e4e3d7e-6cbf-4742-a63b-f9a2579f2318.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most developers live in their terminal. You run commands, debug pipelines, manage infrastructure, and navigate codebases, all from a shell prompt.</p>
<p>But despite how central the terminal is to developer workflows, AI assistance there has remained shallow: autocomplete a command here, explain an error there.</p>
<p>That changes when you combine GitHub Copilot CLI with MCP (Model Context Protocol) servers. Instead of an AI that reacts to isolated prompts, you get a terminal that understands your project context, queries live data sources, and chains tool calls autonomously – what the industry is starting to call an agentic workflow.</p>
<p>In this tutorial, you'll learn exactly how to wire these two systems together, step by step. By the end, your terminal will be able to do things like understand your Git history before suggesting a fix, query your running Docker containers before writing a compose patch, or pull live API schemas before generating a request.</p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-is-github-copilot-cli">What is GitHub Copilot CLI?</a></p>
</li>
<li><p><a href="#heading-what-is-the-model-context-protocol">What is the Model Context Protocol?</a></p>
</li>
<li><p><a href="#heading-how-mcp-servers-work-in-a-terminal-context">How MCP Servers Work in a Terminal Context</a></p>
</li>
<li><p><a href="#heading-step-1-install-and-configure-github-copilot-cli">Step 1 – Install and Configure GitHub Copilot CLI</a></p>
</li>
<li><p><a href="#heading-step-2-set-up-your-first-mcp-server">Step 2 – Set Up Your First MCP Server</a></p>
</li>
<li><p><a href="#heading-step-3-wire-copilot-cli-to-your-mcp-server">Step 3 – Wire Copilot CLI to Your MCP Server</a></p>
</li>
<li><p><a href="#heading-step-4-build-a-real-agentic-workflow">Step 4 – Build a Real Agentic Workflow</a></p>
</li>
<li><p><a href="#heading-step-5-extend-with-multiple-mcp-servers">Step 5 – Extend with Multiple MCP Servers</a></p>
</li>
<li><p><a href="#heading-debugging-common-issues">Debugging Common Issues</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>Before you start, make sure you have the following:</p>
<ul>
<li><p><strong>Node.js</strong> v18 or later (<code>node --version</code>)</p>
</li>
<li><p><strong>npm</strong> v9 or later</p>
</li>
<li><p>A GitHub account with Copilot enabled. The free tier (available to all GitHub users) is sufficient to follow this tutorial. Pro, Business, and Enterprise plans unlock higher usage limits but aren't required.</p>
</li>
<li><p><strong>GitHub CLI</strong> (<code>gh</code>) installed. We'll use it to authenticate.</p>
</li>
<li><p>Basic familiarity with the terminal and JSON configuration files</p>
</li>
<li><p>(Optional) <strong>Docker</strong> installed if you want to follow the Docker MCP example in Step 5</p>
</li>
</ul>
<p>You don't need prior experience with MCP or agentic AI systems, as this guide builds that understanding from the ground up.</p>
<h2 id="heading-what-is-github-copilot-cli">What is GitHub Copilot CLI?</h2>
<p>GitHub Copilot CLI is the terminal-native interface to GitHub's Copilot AI. Unlike the IDE plugin (which assists with code completion), Copilot CLI is designed specifically for shell workflows. It exposes three main commands:</p>
<ul>
<li><p><code>gh copilot suggest</code> proposes a shell command based on a natural language description</p>
</li>
<li><p><code>gh copilot explain</code> explains what a given command does</p>
</li>
<li><p><code>gh copilot alias</code> generates shell aliases for Copilot subcommands</p>
</li>
</ul>
<p>Here's a quick example of <code>suggest</code> in action:</p>
<pre><code class="language-shell">gh copilot suggest "find all files modified in the last 24 hours and larger than 1MB"
</code></pre>
<p>Copilot will return something like:</p>
<pre><code class="language-shell">find . -mtime -1 -size +1M
</code></pre>
<p>It will also ask if you want to copy it, run it directly, or revise the request. This interactive loop is already useful – but by itself, Copilot CLI has no awareness of your project context. It doesn't know your repo structure, your running services, or your deployment environment. That's where MCP comes in.</p>
<h2 id="heading-what-is-the-model-context-protocol">What is the Model Context Protocol?</h2>
<p>The <strong>Model Context Protocol (MCP)</strong> is an open standard introduced by Anthropic in late 2024. Its goal is straightforward: give AI models a standardized way to connect to external tools, data sources, and services.</p>
<p>Think of MCP as a universal adapter layer between an AI model and the real world. Without MCP, each AI integration is custom-built: one plugin for GitHub, another for Postgres, another for Slack, all with incompatible interfaces. MCP defines a single protocol that any tool can implement, and any compatible AI client can consume.</p>
<p>An MCP server exposes <strong>tools</strong> (functions the AI can call), <strong>resources</strong> (data the AI can read), and <strong>prompts</strong> (reusable instruction templates). The AI client in our case, a Copilot-powered terminal discovers these capabilities at runtime and uses them autonomously to complete a task.</p>
<p>A few notable MCP servers that are already production-ready:</p>
<table>
<thead>
<tr>
<th>MCP Server</th>
<th>What it exposes</th>
</tr>
</thead>
<tbody><tr>
<td>@modelcontextprotocol/server-filesystem</td>
<td>Read/write access to local files</td>
</tr>
<tr>
<td>@modelcontextprotocol/server-git</td>
<td>Git log, diff, blame, branch operations</td>
</tr>
<tr>
<td>@modelcontextprotocol/server-github</td>
<td>GitHub Issues, PRs, repos via API</td>
</tr>
<tr>
<td>@modelcontextprotocol/server-postgres</td>
<td>Live query execution on a Postgres DB</td>
</tr>
<tr>
<td>@modelcontextprotocol/server-docker</td>
<td>Container inspection, logs, stats</td>
</tr>
</tbody></table>
<p>The full registry lives at <code>github.com/modelcontextprotocol/servers</code>.</p>
<h2 id="heading-how-mcp-servers-work-in-a-terminal-context">How MCP Servers Work in a Terminal Context</h2>
<p>Before we get hands-on, it's worth understanding the communication model.</p>
<p>MCP servers run as local processes. They communicate with the AI client over <strong>stdio</strong> (standard input/output) or over an <strong>HTTP/SSE transport</strong>. The client sends JSON-RPC messages to the server, and the server responds with structured data.</p>
<p>Here's the simplified flow:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66f71ee288cc311f84e563bc/e1844dc5-a869-4201-ad8a-fd1cb305f646.png" alt="An architectural flowchart illustrating the Model Context Protocol (MCP) workflow. The process starts with a user typing a natural language prompt, passes through the Copilot CLI (the MCP client), communicates via JSON-RPC over stdio with an MCP Server (e.g., server-git), executes real tools like git log, returns a structured result to Copilot, which finally synthesizes a context-aware response for the user." style="display:block;margin:0 auto" width="1408" height="768" loading="lazy">

<p>The key word here is <strong>grounded</strong>. Without MCP, Copilot responds based purely on its training data and your prompt. With MCP, it can call <code>git log --oneline -20</code> before answering your question about recent regressions and its answer is based on <em>your actual code history</em>, not a generalized assumption.</p>
<h3 id="heading-step-1-install-and-configure-github-copilot-cli">Step 1 – Install and Configure GitHub Copilot CLI</h3>
<p>If you haven't already, install the GitHub CLI:</p>
<pre><code class="language-shell"># macOS
brew install gh

# Ubuntu/Debian
sudo apt install gh

# Windows (via winget)
winget install --id GitHub.cli
</code></pre>
<p>Then authenticate:</p>
<pre><code class="language-shell">gh auth login
</code></pre>
<p>Follow the interactive prompts. Select <strong>GitHub.com</strong>, then <strong>HTTPS</strong>, and authenticate via browser when prompted.</p>
<p>Now install the Copilot CLI extension:</p>
<pre><code class="language-shell">gh extension install github/gh-copilot
</code></pre>
<p>Verify the installation:</p>
<pre><code class="language-shell">gh copilot --version
</code></pre>
<p>You should see output like <code>gh-copilot version 1.x.x</code>.</p>
<p><strong>Optional but recommended: set up shell aliases.</strong> This makes the workflow much faster. For <code>bash</code> or <code>zsh</code>:</p>
<pre><code class="language-shell"># Add to your ~/.bashrc or ~/.zshrc
eval "$(gh copilot alias -- bash)"   # for bash
eval "$(gh copilot alias -- zsh)"    # for zsh
</code></pre>
<p>After reloading your shell (<code>source ~/.bashrc</code>), you can use <code>ghcs</code> as shorthand for <code>gh copilot suggest</code> and <code>ghce</code> for <code>gh copilot explain</code>.</p>
<h3 id="heading-step-2-set-up-your-first-mcp-server">Step 2 – Set Up Your First MCP Server</h3>
<p>We'll start with <code>server-git</code>. It's the most immediately useful for a development workflow and has zero external dependencies.</p>
<p>Install it globally via npm:</p>
<pre><code class="language-shell">npm install -g @modelcontextprotocol/server-git
</code></pre>
<p>Test that it runs:</p>
<pre><code class="language-shell">mcp-server-git --version
</code></pre>
<p>This server exposes the following tools to any compatible MCP client:</p>
<ul>
<li><p><code>git_log</code> retrieve commit history with filters</p>
</li>
<li><p><code>git_diff</code> diff between branches or commits</p>
</li>
<li><p><code>git_status</code> current working tree status</p>
</li>
<li><p><code>git_show</code> inspect a specific commit</p>
</li>
<li><p><code>git_blame</code> annotate file lines with commit info</p>
</li>
<li><p><code>git_branch</code> list or switch branches</p>
</li>
</ul>
<p>Now create a configuration file. MCP clients look for a file called <code>mcp.json</code> to discover available servers. Create it in your project root or in a global config directory:</p>
<pre><code class="language-shell">mkdir -p ~/.config/mcp
touch ~/.config/mcp/mcp.json
</code></pre>
<p>Add the following content:</p>
<pre><code class="language-markdown">{
  "mcpServers": {
    "git": {
      "command": "mcp-server-git",
      "args": ["--repository", "."],
      "transport": "stdio"
    }
  }
}
</code></pre>
<p>A few notes on this config:</p>
<ul>
<li><p><code>command</code> is the binary to run. Make sure it's on your <code>$PATH</code>.</p>
</li>
<li><p><code>args</code> passes <code>--repository .</code> so the server scopes itself to the current working directory.</p>
</li>
<li><p><code>transport: "stdio"</code> means communication happens over standard input/output the simplest and most stable option for local servers.</p>
</li>
</ul>
<h3 id="heading-step-3-wire-copilot-cli-to-your-mcp-server">Step 3 – Wire Copilot CLI to Your MCP Server</h3>
<p>This is where the two systems connect. GitHub Copilot CLI supports MCP via its <code>--mcp-config</code> flag (available from version 1.3+). You point it at your <code>mcp.json</code>, and Copilot will automatically initialize the declared servers before processing your prompt.</p>
<p>Here's the basic invocation:</p>
<pre><code class="language-shell">gh copilot suggest --mcp-config ~/.config/mcp/mcp.json "why did the build break in the last commit?"
</code></pre>
<p>When you run this inside a Git repository, Copilot CLI will:</p>
<ol>
<li><p>Start the <code>mcp-server-git</code> process</p>
</li>
<li><p>Call <code>git_log</code> to retrieve recent commits</p>
</li>
<li><p>Call <code>git_diff</code> on the most recent commit</p>
</li>
<li><p>Synthesize an answer based on the actual diff output</p>
</li>
</ol>
<p>Try it yourself on a repo with a recent failing commit. The difference in response quality compared to a plain <code>gh copilot suggest</code> is immediately obvious.</p>
<p><strong>Tip: avoid retyping the flag every time.</strong> Add a shell function to your <code>.bashrc</code>/<code>.zshrc</code>:</p>
<pre><code class="language-shell">function aterm() {
  gh copilot suggest --mcp-config ~/.config/mcp/mcp.json "$@"
}
</code></pre>
<p>Now you just type:</p>
<pre><code class="language-shell">aterm "what changed between main and feature/auth?"
</code></pre>
<p>And you're running a fully context-aware, MCP-powered query from a single short command. This function name <code>aterm</code> for <em>agentic terminal</em> is what we'll use throughout the rest of this tutorial.</p>
<h3 id="heading-step-4-build-a-real-agentic-workflow">Step 4 – Build a Real Agentic Workflow</h3>
<p>Let's move beyond individual queries and build a workflow that chains multiple tool calls to complete a real developer task: <strong>diagnosing a regression</strong>.</p>
<p>Imagine you pushed a feature branch and your CI pipeline failed. You don't know exactly which change caused it. Here's how your agentic terminal handles it:</p>
<h4 id="heading-query-1-understand-what-changed">Query 1: understand what changed</h4>
<pre><code class="language-shell">aterm "summarize all commits on feature/auth that aren't on main yet"
</code></pre>
<p>Copilot calls <code>git_log</code> with branch filters, then returns a structured summary of commits unique to your branch. No copy-pasting SHAs manually.</p>
<h4 id="heading-query-2-isolate-the-diff">Query 2: isolate the diff</h4>
<pre><code class="language-shell">aterm "show me everything that changed in the auth middleware between main and feature/auth"
</code></pre>
<p>This triggers <code>git_diff</code> scoped to the path containing your middleware. Copilot returns the diff with an explanation of what each change does.</p>
<h4 id="heading-query-3-find-the-likely-culprit">Query 3: find the likely culprit</h4>
<pre><code class="language-shell">aterm "which of those changes could cause a JWT validation failure?"
</code></pre>
<p>At this point, Copilot has the diff in its context window from the previous tool calls. It reasons over the actual code changes not generic knowledge about JWT and pinpoints the likely issue.</p>
<h4 id="heading-query-4-generate-the-fix">Query 4: generate the fix</h4>
<pre><code class="language-shell">aterm "write the corrected version of that validation function"
</code></pre>
<p>Copilot generates a targeted fix based on the specific code it retrieved via MCP. You get a patch you can directly apply, not a generic code template.</p>
<p>This four-step sequence – understand, isolate, reason, fix – is a complete agentic loop. Each step is grounded in live repository data retrieved through MCP tools. The AI is not hallucinating context. Instead, it's reading your actual codebase.</p>
<h3 id="heading-step-5-extend-with-multiple-mcp-servers">Step 5 – Extend with Multiple MCP Servers</h3>
<p>One MCP server is useful. Multiple MCP servers working together is where the workflow becomes genuinely powerful. Let's add two more: <code>server-filesystem</code> and <code>server-docker</code>.</p>
<p>Install the additional servers:</p>
<pre><code class="language-shell">npm install -g @modelcontextprotocol/server-filesystem
npm install -g @modelcontextprotocol/server-docker
</code></pre>
<p>Update your <code>mcp.json</code>:</p>
<pre><code class="language-markdown">{
  "mcpServers": {
    "git": {
      "command": "mcp-server-git",
      "args": ["--repository", "."],
      "transport": "stdio"
    },
    "filesystem": {
      "command": "mcp-server-filesystem",
      "args": ["--root", "."],
      "transport": "stdio"
    },
    "docker": {
      "command": "mcp-server-docker",
      "transport": "stdio"
    }
  }
}
</code></pre>
<p>With all three servers active, your terminal can now answer cross-domain questions:</p>
<pre><code class="language-shell">aterm "my Express app container keeps restarting, check the logs and compare with what the healthcheck in my Dockerfile expects"
</code></pre>
<p>To answer this, Copilot will:</p>
<ol>
<li><p>Call <code>docker_logs</code> (server-docker) to pull the container's recent stderr output</p>
</li>
<li><p>Call <code>read_file</code> (server-filesystem) to read your <code>Dockerfile</code></p>
</li>
<li><p>Parse the <code>HEALTHCHECK</code> instruction</p>
</li>
<li><p>Cross-reference the log errors with the health endpoint path</p>
</li>
<li><p>Return a diagnosis explaining the mismatch and suggest the fix</p>
</li>
</ol>
<p>This is an <strong>agentic workflow</strong>: the model autonomously decides which tools to call, in what order, and synthesizes the results into a coherent answer. You didn't tell it to read the Dockerfile. It inferred that was necessary based on your question.</p>
<p><strong>A note on security:</strong> When running <code>server-filesystem</code>, always scope it to a specific directory using <code>--root</code>. Never point it at <code>/</code> or your home directory. Similarly, <code>server-docker</code> has access to your Docker socket run it only in trusted environments.</p>
<h2 id="heading-debugging-common-issues">Debugging Common Issues</h2>
<p><code>mcp-server-git: command not found</code></p>
<p>The npm global bin directory isn't on your <code>$PATH</code>. Fix:</p>
<pre><code class="language-shell">export PATH="\(PATH:\)(npm bin -g)"
# or for newer npm versions:
export PATH="\(PATH:\)(npm prefix -g)/bin"
</code></pre>
<p>Add this line to your <code>.bashrc</code>/<code>.zshrc</code> to persist it.</p>
<h4 id="heading-copilot-cli-doesnt-seem-to-be-using-mcp-tools">Copilot CLI doesn't seem to be using MCP tools</h4>
<p>Check your Copilot CLI version:</p>
<pre><code class="language-shell">gh copilot --version
</code></pre>
<p>MCP support requires version 1.3 or later. Update with:</p>
<pre><code class="language-shell">gh extension upgrade copilot
</code></pre>
<p>Also verify your <code>mcp.json</code> is valid JSON a trailing comma or missing bracket will silently prevent server initialization.</p>
<h4 id="heading-mcp-server-starts-but-returns-no-data">MCP server starts but returns no data</h4>
<p>Run the server manually to check for errors:</p>
<pre><code class="language-shell">mcp-server-git --repository .
</code></pre>
<p>If it exits immediately, check that you're running the command inside a valid Git repository. For <code>server-docker</code>, make sure the Docker daemon is running and your user has access to the Docker socket:</p>
<pre><code class="language-shell">sudo usermod -aG docker $USER
# Then log out and back in
</code></pre>
<h4 id="heading-responses-are-slow-with-multiple-servers">Responses are slow with multiple servers</h4>
<p>Each MCP server is a separate subprocess. Spawning several at once adds startup latency, especially on slower machines. Two optimizations:</p>
<ol>
<li><p>Only declare the servers you actually need for a given project in your <code>mcp.json</code></p>
</li>
<li><p>Use project-specific config files instead of one global config:</p>
</li>
</ol>
<pre><code class="language-shell"># project A (backend)
aterm --mcp-config ./mcp-backend.json "..."

# project B (infra)
aterm --mcp-config ./mcp-infra.json "..."
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You've just built an agentic terminal workflow from scratch. Here's a quick recap of what you did:</p>
<ul>
<li><p>Installed and configured GitHub Copilot CLI with shell aliases for fast access</p>
</li>
<li><p>Set up MCP servers (<code>server-git</code>, <code>server-filesystem</code>, <code>server-docker</code>) and wired them through a <code>mcp.json</code> config</p>
</li>
<li><p>Created a shell function (<code>aterm</code>) that transparently passes your MCP config to every Copilot query</p>
</li>
<li><p>Built a multi-step agentic loop for diagnosing regressions using live Git data</p>
</li>
<li><p>Extended the setup with cross-domain tool orchestration across Git, filesystem, and Docker</p>
</li>
</ul>
<p>The architecture you've built here is not a demo – it's a production-ready pattern. You can extend it with any MCP-compatible server: <code>server-postgres</code> for database-aware queries, <code>server-github</code> for issue and PR context, or custom MCP servers you write yourself for your internal APIs.</p>
<p>The terminal has always been the most powerful surface in a developer's environment. With Copilot CLI and MCP, it's finally becoming an intelligent one.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Fix a Failing GitHub PR: Debugging CI, Lint Errors, and Build Errors Step by Step ]]>
                </title>
                <description>
                    <![CDATA[ While many guides explain how to set up Continuous Integration pipelines, not very many show you how to debug them when things go wrong across multiple layers. This is a common experience when contrib ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-fix-failing-github-pr-ci-lint-build-errors/</link>
                <guid isPermaLink="false">69e9033dbca83cce6c5f0209</guid>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ci-cd ]]>
                    </category>
                
                    <category>
                        <![CDATA[ debugging ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ markdown ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ qacheampong ]]>
                </dc:creator>
                <pubDate>Wed, 22 Apr 2026 17:19:57 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/29733bad-98af-4d6e-9fb6-93d55e8f87fd.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>While many guides explain how to set up Continuous Integration pipelines, not very many show you how to debug them when things go wrong across multiple layers.</p>
<p>This is a common experience when contributing to open source: you make a small change, open a pull request, and suddenly everything fails.</p>
<p>Not just one check, but multiple:</p>
<ul>
<li><p>Lint errors</p>
</li>
<li><p>YAML validation issues</p>
</li>
<li><p>Build failures</p>
</li>
<li><p>Deployment failures</p>
</li>
</ul>
<p>Even more confusing, you may see errors in parts of the codebase you didn’t modify.</p>
<p>In this article, you'll learn how to debug these issues step by step. The goal is not just to fix one pull request, but to understand how CI systems validate your changes.</p>
<p>This guide is based on a real debugging experience from contributing to an open source documentation project.</p>
<p>While this example comes from a documentation project, the debugging workflow applies to many repositories that use CI pipelines, linting tools, and automated builds.</p>
<h3 id="heading-table-of-contents">Table of Contents:</h3>
<ul>
<li><p><a href="#heading-understanding-the-ci-pipeline-whats-actually-happening">Understanding the CI Pipeline (What’s Actually Happening)</a></p>
</li>
<li><p><a href="#heading-how-a-ci-pipeline-processes-your-pull-request">How a CI Pipeline Processes Your Pull Request</a></p>
</li>
<li><p><a href="#heading-a-practical-debugging-workflow">A Practical Debugging Workflow</a></p>
<ul>
<li><p><a href="#heading-step-1-fix-authentication-and-permission-issues">Step 1: Fix Authentication and Permission Issues</a></p>
</li>
<li><p><a href="#heading-step-2-run-lint-checks-locally">Step 2: Run Lint Checks Locally</a></p>
</li>
<li><p><a href="#heading-step-3-fix-common-markdown-lint-errors">Step 3: Fix Common Markdown Lint Errors</a></p>
</li>
<li><p><a href="#heading-step-4-fix-yaml-inside-markdown-code-blocks">Step 4: Fix YAML Inside Markdown Code Blocks</a></p>
</li>
<li><p><a href="#heading-step-5-fix-build-errors-after-lint-passes">Step 5: Fix Build Errors After Lint Passes</a></p>
</li>
<li><p><a href="#heading-step-6-debug-cascading-ci-failures">Step 6: Debug Cascading CI Failures</a></p>
</li>
<li><p><a href="#heading-step-7-handle-git-issues-during-ci-debugging">Step 7: Handle Git Issues During CI Debugging</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-key-takeaways">Key Takeaways</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h3 id="heading-prerequisites"><strong>Prerequisites</strong></h3>
<p>To follow this guide, you should have:</p>
<ul>
<li><p>Basic familiarity with Git and pull requests</p>
</li>
<li><p>A GitHub account</p>
</li>
<li><p>Some exposure to CI/CD concepts (helpful but not required)</p>
</li>
</ul>
<h2 id="heading-understanding-the-ci-pipeline-whats-actually-happening"><strong>Understanding the CI Pipeline (What’s Actually Happening)</strong></h2>
<p>In many projects, you will see the term CI/CD, which stands for Continuous Integration and Continuous Deployment (or Delivery).</p>
<p>In this guide, we'll focus specifically on the CI part – that is, Continuous Integration. This refers to the automated checks that run when you push code or open a pull request. These checks validate your changes before they're merged into the main codebase.</p>
<p>CD (Continuous Deployment/Delivery), on the other hand, typically handles what happens after those checks pass, such as deploying the application.</p>
<p>Understanding this distinction is important because most of the issues we debug in this guide happen during the CI stage.</p>
<p>Most repositories run multiple automated checks when you open a pull request:</p>
<ul>
<li><p><strong>Linting tools</strong> (for example, markdownlint, yamllint) enforce formatting rules</p>
</li>
<li><p><strong>Build systems</strong> (for example, mdBook) validate structure and generate output</p>
</li>
<li><p><strong>Deployment checks</strong> (for example, Netlify) ensure that the site can be built and served</p>
</li>
<li><p><strong>Merge controllers</strong> (for example, Tide) enforce approval policies</p>
</li>
</ul>
<p>A key point to remember: CI systems validate the <strong>entire set of files in your commit,</strong> not just the lines you changed.</p>
<h2 id="heading-how-a-ci-pipeline-processes-your-pull-request"><strong>How a CI Pipeline Processes Your Pull Request</strong></h2>
<p>When you push code or open a pull request, the CI pipeline runs several checks in sequence.</p>
<p>Let’s visualize how these checks are connected in a typical CI pipeline.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69d09527e466e2b762fdff59/9cecca6e-e000-46e3-a40e-cb353fc89ff8.png" alt="A CI pipeline diagram showing lint, build, and deployment steps with failure loops returning to code fixes." style="display:block;margin:0 auto" width="2348" height="1516" loading="lazy">

<p>Figure: A simplified CI pipeline showing how linting, build, and deployment checks are executed sequentially.</p>
<p>The above diagram shows a sequential CI pipeline with feedback loops, where failures at any stage return you to fix the issue before continuing.</p>
<p>Let’s break down what this diagram shows:</p>
<ol>
<li><p>You start by pushing code or opening a pull request.</p>
</li>
<li><p>The CI pipeline begins running automated checks.</p>
</li>
<li><p>The first set of checks typically includes linting tools like markdownlint or yamllint.</p>
<ul>
<li><p>If linting fails, the pipeline stops, and you must fix formatting issues before continuing.</p>
</li>
<li><p>If linting passes, the pipeline moves to the build step (for example, mdBook in documentation projects).</p>
</li>
<li><p>If the build fails, it usually means there is a structural issue, such as duplicate entries or invalid references.</p>
</li>
</ul>
</li>
<li><p>After a successful build, deployment checks (such as Netlify previews) run.</p>
<ul>
<li>If deployment fails, the issue is often related to configuration or build output.</li>
</ul>
</li>
<li><p>If all steps pass, the pull request becomes ready for review.</p>
</li>
</ol>
<h2 id="heading-a-practical-debugging-workflow"><strong>A Practical Debugging Workflow</strong></h2>
<h3 id="heading-step-1-fix-authentication-and-permission-issues">Step 1: Fix Authentication and Permission Issues</h3>
<p>Before CI runs, your push can fail due to authentication errors.</p>
<p>Example error:</p>
<pre><code class="language-shell">refusing to allow a Personal Access Token to create or update workflow
</code></pre>
<p>This happens because GitHub requires special permissions when your commit includes files under:</p>
<pre><code class="language-shell">.github/workflows/
</code></pre>
<p>The solution is to regenerate your Personal Access Token (PAT) with:</p>
<ul>
<li><p><code>repo</code> access</p>
</li>
<li><p><code>workflow</code> permission</p>
</li>
</ul>
<h3 id="heading-step-2-run-lint-checks-locally">Step 2: Run Lint Checks Locally</h3>
<p>Relying only on CI feedback slows you down because you have to push changes and wait for the pipeline to run before seeing errors.</p>
<p>Running checks locally allows you to catch issues immediately before pushing your code.</p>
<p>In practice, you should do both:</p>
<ul>
<li><p>Run checks locally to catch errors early and reduce iteration time</p>
</li>
<li><p>Use CI as the final validation to ensure your changes meet the repository’s standards</p>
</li>
</ul>
<p>Think of local checks as your first line of defense, and CI as the final gate before your code is accepted.</p>
<p>Here's an example (Markdown linting):</p>
<pre><code class="language-shell">npm install -g markdownlint-cli2
markdownlint-cli2 docs/**/*.md
</code></pre>
<h3 id="heading-step-3-fix-common-markdown-lint-errors">Step 3: Fix Common Markdown Lint Errors</h3>
<p>Here are some common issues you may encounter:</p>
<h4 id="heading-1-non-descriptive-links">1. Non-descriptive links</h4>
<p>Non-descriptive links like "here" don't give readers any context about where the link leads. This makes documentation harder to understand and less accessible, especially for users relying on screen readers.</p>
<p>Instead of writing:</p>
<pre><code class="language-shell">[here](https://example.com)
</code></pre>
<p>Use descriptive text like:</p>
<pre><code class="language-shell">[command help documentation](https://example.com)
</code></pre>
<h4 id="heading-2-line-length-violations">2. Line length violations</h4>
<p>Many projects enforce a maximum line length (often around 80 characters) to improve readability across different devices and editors.</p>
<p>If a line is too long, you can split it into multiple lines without changing the meaning.</p>
<p>To do this, break the line at natural points such as spaces between words or after punctuation. Avoid breaking words or disrupting the sentence structure.<br>For example:</p>
<pre><code class="language-shell">This is a long sentence that should be split across multiple
lines to satisfy lint rules.
</code></pre>
<h4 id="heading-3-list-indentation-issues">3. List indentation issues</h4>
<p>List indentation errors occur when nested list items aren't aligned consistently. This can break formatting and cause linting errors.</p>
<p>To avoid this, just make sure you use consistent spacing (usually 2 spaces per level).</p>
<p>Example (incorrect):</p>
<pre><code class="language-shell">- Item 1
 - Subitem
</code></pre>
<p>Correct version:</p>
<pre><code class="language-shell">- Item 1
  - Subitem
</code></pre>
<h3 id="heading-step-4-fix-yaml-inside-markdown-code-blocks">Step 4: Fix YAML Inside Markdown Code Blocks</h3>
<p>YAML has strict formatting rules, including proper indentation, key-value structure, and consistent spacing.</p>
<p>Even when YAML appears inside a markdown code block, tools like yamllint still validate its structure.</p>
<p>Example (incorrect):</p>
<pre><code class="language-yaml">metadata:
annotations:
</code></pre>
<p>Correct version:</p>
<pre><code class="language-yaml">metadata:
  annotations:
    capi.metal3.io/unhealthy: "true"
</code></pre>
<p>In the incorrect example, <code>annotations</code> is not properly nested under <code>metadata</code>, and no key-value pair is defined.</p>
<p>In the corrected version:</p>
<ul>
<li><p><code>annotations</code> is properly indented under <code>metadata</code></p>
</li>
<li><p>a valid key-value pair is added (<code>capi.metal3.io/unhealthy: "true"</code>)</p>
</li>
</ul>
<p>This structure satisfies YAML’s requirement for proper hierarchy and formatting.</p>
<h3 id="heading-step-5-fix-build-errors-after-lint-passes">Step 5: Fix Build Errors After Lint Passes</h3>
<p>Passing lint checks doesn't guarantee that your build will succeed.</p>
<p>This is because linting focuses on syntax and formatting, while the build process validates the structure and integrity of the entire project.</p>
<p>Build failures often occur due to issues such as:</p>
<ul>
<li><p>Duplicate entries in navigation files</p>
</li>
<li><p>Missing or incorrectly referenced files</p>
</li>
<li><p>Invalid configuration settings</p>
</li>
</ul>
<p>Even if your syntax is correct, the build system ensures everything connects properly.</p>
<p>For example, in documentation projects using tools like mdBook, a duplicate entry in <code>SUMMARY.md</code> can cause the build to fail even when all files pass lint checks.</p>
<h3 id="heading-step-6-debug-cascading-ci-failures">Step 6: Debug Cascading CI Failures</h3>
<p>CI pipelines are layered. One failure can trigger multiple downstream failures.</p>
<p>For example, imagine a YAML indentation error:</p>
<pre><code class="language-shell">YAML error → build fails → deploy fails → multiple checks fail
</code></pre>
<p>To fix this:</p>
<ol>
<li><p>Identify the first failing step in the CI logs</p>
</li>
<li><p>Fix that issue</p>
</li>
<li><p>Re-run the pipeline</p>
</li>
</ol>
<p>In this example, the YAML indentation error is the root cause. Once you fix the YAML formatting, the lint check passes, which allows the build to proceed and the deployment step to succeed.</p>
<p>This is why it is important to always fix the first failure in the pipeline rather than trying to address all errors at once.</p>
<h3 id="heading-step-7-handle-git-issues-during-ci-debugging">Step 7: Handle Git Issues During CI Debugging</h3>
<p>When working with updated branches, you may encounter:</p>
<ul>
<li><p>Diverged branches</p>
</li>
<li><p>Rebase conflicts</p>
</li>
<li><p>Push rejections</p>
</li>
</ul>
<p>To resolve these issues, you typically need to update your branch using one of two approaches:</p>
<h4 id="heading-option-1-rebase-clean-history">Option 1: Rebase (clean history)</h4>
<pre><code class="language-shell">git pull --rebase
</code></pre>
<p>Rebasing rewrites your commit history so your changes appear on top of the latest version of the branch.</p>
<p>Use carefully:</p>
<ul>
<li><p>Only rebase your own branches</p>
</li>
<li><p>Avoid rebasing shared branches</p>
</li>
</ul>
<h4 id="heading-option-2-merge-safer">Option 2: Merge (safer)</h4>
<pre><code class="language-shell">git pull --no-rebase
</code></pre>
<p>Merging preserves the full commit history and is safer when working with others, but it may introduce additional merge commits.</p>
<h4 id="heading-pushing-your-changes-safely">Pushing your changes safely</h4>
<p>After updating your branch, you may need to push changes:</p>
<pre><code class="language-shell">git push --force-with-lease
</code></pre>
<p>Avoid using:</p>
<pre><code class="language-shell">git push --force
</code></pre>
<p>The <code>--force</code> option can overwrite the other contributors’ work. The <code>--force-with-lease</code> option is safer because it only pushes if the remote branch has not changed unexpectedly.</p>
<h2 id="heading-key-takeaways"><strong>Key Takeaways</strong></h2>
<ul>
<li><p>CI validates your entire commit, not just the specific lines you changed</p>
</li>
<li><p>Linting and build systems enforce different rules</p>
</li>
<li><p>YAML inside markdown must be structurally correct</p>
</li>
<li><p>Documentation builds can fail due to structural issues</p>
</li>
<li><p>Running checks locally significantly reduces debugging time</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Debugging a failing pull request isn't just about fixing syntax errors.</p>
<p>You also need to understand how different systems interact:</p>
<ul>
<li><p>Version control</p>
</li>
<li><p>CI pipelines</p>
</li>
<li><p>Linting tools</p>
</li>
<li><p>Build processes</p>
</li>
</ul>
<p>Once you understand how these systems work together, you can debug issues systematically instead of guessing.</p>
<p>The next time your pull request fails, you will know exactly where to start and how to fix it.</p>
<p>Debugging CI issues may feel overwhelming at first, but with a structured approach, you can turn failures into a clear path for improvement.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Make Your GitHub Profile Stand Out ]]>
                </title>
                <description>
                    <![CDATA[ If you have a Github profile, you might overlook the many ways you can customize it – and that's completely understandable. After all, at its core, GitHub is a home for your code. But beyond repositor ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-make-your-github-profile-stand-out/</link>
                <guid isPermaLink="false">69e2cd3ffd22b8ad62900fb3</guid>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                    <category>
                        <![CDATA[ hiring ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #developerportfolio ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chinaza Chukwunweike ]]>
                </dc:creator>
                <pubDate>Sat, 18 Apr 2026 00:15:59 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/db5b026f-24ce-4b59-b133-9326eb8c4c06.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you have a Github profile, you might overlook the many ways you can customize it – and that's completely understandable. After all, at its core, GitHub is a home for your code.</p>
<p>But beyond repositories and commits, your profile can say <em>a lot</em> about you as a developer.</p>
<p>When used intentionally, GitHub becomes more than a code hosting platform. It becomes your <strong>CV for your codebase</strong>. It tells your story, showcases your skills, and gives people a reason to trust your work.</p>
<p>In this article, we'll break down the different ways to make your GitHub profile stand out. From setting up your GitHub account to engaging storytelling for your repositories, there's lots you can do.</p>
<p>Let's get started!</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-sign-up-for-a-github-account">Sign Up for a Github Account</a></p>
</li>
<li><p><a href="#heading-add-a-profile-image">Add a Profile Image</a></p>
</li>
<li><p><a href="#heading-add-profile-details">Add Profile Details</a></p>
</li>
<li><p><a href="#heading-add-a-profile-readme-file">Add a Profile README File</a></p>
</li>
<li><p><a href="#heading-tell-a-story-about-each-repository">Tell a Story About Each Repository</a></p>
</li>
</ul>
<h2 id="heading-step-1-sign-up-for-a-github-account">Step 1: Sign Up for a Github Account</h2>
<p>To begin, you'll need a GitHub account. If you don’t have one, you can <a href="http://GitHub.com">set one up here</a>.</p>
<p>Once you have your account set up and you're logged in, we can move on to the next step.</p>
<h2 id="heading-step-2-add-a-profile-image">Step 2: Add a Profile Image</h2>
<p>Your profile image is often the first thing people notice. It could be a professional photo of yourself, or an image or avatar that represents you or your interests</p>
<p>As long as it’s appropriate, you’re good to go.</p>
<p>To add a profile image, you'll need to:</p>
<ul>
<li><p>Open your profile menu/dashboard</p>
</li>
<li><p>Click on the image icon at the left</p>
</li>
<li><p>Click on the edit text on the image icon</p>
</li>
<li><p>Select the image to set as your profile picture</p>
</li>
<li><p>Click the "Set new profile picture" button</p>
</li>
</ul>
<p>So, you should have something like this:</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/69721aa76d7c64defcdf1b2c/b05395a9-3f7a-44ce-89a8-2e7759f06ad7.png" alt="Image showing the new Profile image added" style="display:block;margin:0 auto" width="2963" height="1457" loading="lazy">

<p><strong>GitHub link to this page:</strong> <a href="https://github.com/settings/profile">https://github.com/settings/profile</a></p>
<p>And there you have it, your GitHub profile image is set.</p>
<p>On to the next one…</p>
<h2 id="heading-step-3-add-profile-details">Step 3: Add Profile Details</h2>
<p>This step is all about credibility and discoverability.</p>
<p>At the center of your profile settings you'll see fields like email, location, social media links and so on. We'll be adding those details so you can take advantage of the discoverability it lends to your profile.</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/69721aa76d7c64defcdf1b2c/d3ca4a77-e3a6-4cd5-a139-3059b0dcc802.png" alt="Image showing public profile settings tab" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>GitHub link to this page:</strong> <a href="https://github.com/settings/profile">https://github.com/settings/profile</a></p>
<p>For this step, you'll want to add as much detail as possible (apart from your home address – I think we both know why).</p>
<p>For the location, you can just put in your city or country so others have a general idea of where you are in the world.</p>
<h2 id="heading-step-4-add-a-profile-readme-file">Step 4: Add a Profile README File</h2>
<p>This is where you introduce yourself properly and tell your story.</p>
<p>A <strong>Profile README</strong> is a special repository named exactly the same as your GitHub username. Your README file appears directly on your profile page.</p>
<p>The READme should answer the following questions:</p>
<ul>
<li><p>Who are you?</p>
</li>
<li><p>What are your project highlights?</p>
</li>
<li><p>What are you currently working on or learning?</p>
</li>
<li><p>Your hobbies or interests (optional)</p>
</li>
</ul>
<p>While answering these questions, you should aim to keep it minimal and yet interesting. You don't want to overwhelm the visitor.</p>
<p>Here's how to create your README:</p>
<ul>
<li><p>Click New repository</p>
</li>
<li><p>Name the repository exactly the same as your GitHub username</p>
</li>
<li><p>Check “Add a README file”</p>
</li>
<li><p>Make sure the repository is public</p>
</li>
<li><p>Click Create repository</p>
</li>
</ul>
<p>Profile README file setup:</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/69721aa76d7c64defcdf1b2c/c4ecd23d-6f0b-41d9-a16c-915cea2e9096.png" alt="Image showing profile README file being created" style="display:block;margin:0 auto" width="2566" height="1692" loading="lazy">

<p><strong>GitHub link to this page:</strong> <a href="https://github.com/new">https://github.com/new</a></p>
<p>So if you answered the questions listed above, your README file should look something like this:</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/69721aa76d7c64defcdf1b2c/a8685c0e-8f97-49b4-b7fc-4efa541acc3b.png" alt="Image showing Profile README section already created" style="display:block;margin:0 auto" width="1960" height="1228" loading="lazy">

<p><strong>GitHub link to this page:</strong> <a href="https://github.com/chinazachisom/chinazachisom">https://github.com/chinazachisom/chinazachisom</a></p>
<p>It should also be showing directly on your GitHub profile like below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69721aa76d7c64defcdf1b2c/7ed8961e-17aa-4b35-999a-b871f3c27905.png" alt="Github Profile Showing the Newly Added README file" style="display:block;margin:0 auto" width="2978" height="1708" loading="lazy">

<p><strong>GitHub link to this page:</strong> <a href="https://github.com/chinazachisom">https://github.com/chinazachisom</a></p>
<h2 id="heading-step-5-tell-a-story-about-each-repository">Step 5: Tell a Story About Each Repository</h2>
<p>Now, this is where you can tell a story about each of your repositories using a README file.</p>
<p><strong>NB: Each repository should have its own separate README file.</strong></p>
<p>What to include in a repository README:</p>
<ul>
<li><p>Project title</p>
</li>
<li><p>What the project is</p>
</li>
<li><p>The purpose (the “why”)</p>
</li>
<li><p>Key features</p>
</li>
<li><p>Challenges you faced and how you solved them</p>
</li>
<li><p>Setup or usage instructions (or a live link if hosted)</p>
</li>
<li><p>Technical concepts used (e.g., throttling, caching, lazy loading) <em>(optional)</em></p>
</li>
<li><p>Images or video demos</p>
</li>
</ul>
<p>You may also include badges, charts, contribution graphs or other visual enhancements that help highlight project quality, activity and impact.</p>
<p>With the above structure, you can tell the stories behind your projects, show your problem-solving skills, and make your work easier to understand and evaluate.</p>
<p><strong>Repository README File Sample:</strong></p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/69721aa76d7c64defcdf1b2c/63bc276b-798b-4a9e-a094-93330e3eeab0.png" alt="Image showing the README file for the new repository" style="display:block;margin:0 auto" width="2820" height="1711" loading="lazy">

<p><strong>GitHub link to this page:</strong> <a href="https://github.com/chinazachisom/Artsy">https://github.com/chinazachisom/Artsy</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Your Github Profile is more than just a storage space for your codebase. It's your developer Identity as well.</p>
<p>Following these basic steps can help turn your Github into a portfolio infused with your personal brand. It makes your GitHub Profile stand out, which can help open doors for more opportunities.</p>
<p>Treat it like a CV for your code and let your work speak for you.</p>
<h3 id="heading-about-the-author">About the Author</h3>
<p>Hi there! I'm Chinaza Chukwunweike, a Software Engineer passionate about building robust, scalable systems that make a real world impact. I'm also an advocate for continuous learning and improvement.</p>
<p>If you found this useful, please share it! And follow me for more Software Engineering tips, AI learning strategies, and productivity frameworks.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What Happened When I Replaced Copilot with Claude Code for 2 Weeks ]]>
                </title>
                <description>
                    <![CDATA[ GitHub Copilot costs $10/month, and I'd been using it for two years without thinking twice. But when Claude Code launched, I got curious. What if I just... switched? I didn't want to just add Claude C ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-happened-when-i-replaced-copilot-with-claude-code-for-2-weeks/</link>
                <guid isPermaLink="false">69c6d07e7cf2706510370b13</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ claude.ai ]]>
                    </category>
                
                    <category>
                        <![CDATA[ copilot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Programming Tips ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Balajee Asish Brahmandam ]]>
                </dc:creator>
                <pubDate>Fri, 27 Mar 2026 18:46:22 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/b4f5a663-3ef6-4fcb-a08c-1c0ff36c495d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>GitHub Copilot costs $10/month, and I'd been using it for two years without thinking twice. But when Claude Code launched, I got curious. What if I just... switched?</p>
<p>I didn't want to just add Claude Code to my stack. I actually wanted to replace Copilot entirely for two weeks. I kept everything else the same – same editor, same projects, same workflow. I just swapped the autocomplete suggestion tool.</p>
<p>Here's what broke, what improved, and whether I went back.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-the-setup">The Setup</a></p>
</li>
<li><p><a href="#heading-what-worked-better">What Worked Better</a></p>
</li>
<li><p><a href="#heading-what-broke-or-slowed-things-down">What Broke (Or Slowed Things Down)</a></p>
</li>
<li><p><a href="#heading-the-first-week-vs-the-second-week">The First Week vs The Second Week</a></p>
</li>
<li><p><a href="#heading-why-i-went-back">Why I Went Back</a></p>
</li>
<li><p><a href="#heading-the-honest-verdict">The Honest Verdict</a></p>
</li>
<li><p><a href="#heading-what-i-actually-use-now">What I Actually Use Now</a></p>
</li>
<li><p><a href="#heading-copilot-vs-claude-code-the-breakdown">Copilot vs Claude Code — The Breakdown</a></p>
</li>
<li><p><a href="#heading-a-word-on-developer-experience">A Word on Developer Experience</a></p>
</li>
<li><p><a href="#heading-what-would-make-me-switch">What Would Make Me Switch</a></p>
</li>
<li><p><a href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-the-setup">The Setup</h2>
<p><strong>Environment:</strong></p>
<ul>
<li><p>Python 3.12 for backend work (Django REST framework specifically)</p>
</li>
<li><p>React/TypeScript for frontend</p>
</li>
<li><p>VSCode as my editor</p>
</li>
<li><p>A mid-sized project with about 15k lines of code across backend and frontend</p>
</li>
<li><p>Two weeks, normal workload (roughly 30-40 hours of coding)</p>
</li>
<li><p>Working on features I'd normally tackle: adding endpoints, debugging issues, writing tests</p>
</li>
</ul>
<p><strong>What I did:</strong></p>
<ul>
<li><p>Disabled GitHub Copilot completely. Uninstalled the extension.</p>
</li>
<li><p>Set up Claude Code (via their CLI and VSCode integration).</p>
</li>
<li><p>Kept everything else identical: same repos, same Git flow, same daily work.</p>
</li>
<li><p>Tracked time on each task to see if there was a real difference.</p>
</li>
</ul>
<p><strong>Ground rules:</strong></p>
<ul>
<li><p>I couldn't use Copilot as a fallback. This was an honest comparison.</p>
</li>
<li><p>I logged every time I got frustrated or felt like Claude Code was slowing me down.</p>
</li>
<li><p>I kept track of bugs I caught vs. bugs I missed.</p>
</li>
</ul>
<p>The goal: Does Claude Code work as a day-to-day replacement for Copilot, or does it force me back?</p>
<h2 id="heading-what-worked-better">What Worked Better</h2>
<h3 id="heading-accuracy">Accuracy</h3>
<p>Copilot sometimes suggests things that are close but not quite right. It might finish a regex pattern 80% correctly, and I have to tweak it. It happens maybe 20% of the time.</p>
<p>Claude Code was more accurate. In the first week, I noticed fewer "close but wrong" suggestions. When I typed a function signature, Claude got the implementation right more often than Copilot did.</p>
<p>One example: I was writing a utility to parse JSON and handle errors. Copilot suggested:</p>
<pre><code class="language-python">def parse_json(data):
 try:
 return json.loads(data)
 except:
 return None
</code></pre>
<p>That's sloppy. It catches all exceptions and silently fails.</p>
<p>Claude Code suggested:</p>
<pre><code class="language-python">def parse_json(data):
 try:
 return json.loads(data)
 except json.JSONDecodeError as e:
 logging.error(f"Failed to parse JSON: {e}")
 return None
 except Exception as e:
 logging.error(f"Unexpected error: {e}")
 raise
</code></pre>
<p>Better error handling. More production-ready. That's a real difference.</p>
<p>I estimate Claude Code's suggestions were "immediately usable" about 85% of the time. Copilot was more like 70%.</p>
<h3 id="heading-understanding-context">Understanding Context</h3>
<p>Claude Code seems to understand your project better than Copilot. When I opened a file with Claude Code context, it knew:</p>
<ul>
<li><p>My project's naming conventions (I use <code>fetch_</code> for async functions, <code>get_</code> for sync).</p>
</li>
<li><p>My error handling style.</p>
</li>
<li><p>What libraries I was using.</p>
</li>
</ul>
<p>Copilot sometimes forgot these patterns or suggested things using the wrong library. Claude Code was more consistent.</p>
<p>One morning I was adding a new endpoint to an existing API. I typed the route signature:</p>
<pre><code class="language-python">@app.post("/api/users")
async def create_user(data: UserPayload):
</code></pre>
<p>Copilot might suggest:</p>
<pre><code class="language-python"> response = requests.post(...)
</code></pre>
<p>(Wrong! That's sync. This function is async.)</p>
<p>Claude Code suggested:</p>
<pre><code class="language-python"> async with httpx.AsyncClient() as client:
 response = await client.post(...)
</code></pre>
<p>It remembered that the entire codebase uses async/await and httpx for async calls. That's attention to detail.</p>
<h3 id="heading-reasoning-about-requirements">Reasoning About Requirements</h3>
<p>Sometimes Copilot just completes code. It doesn't think about whether it makes sense.</p>
<p>Claude Code seemed to reason about whether the suggestion was actually what you wanted. A few times, when I was writing ambiguous code, Claude Code offered a clarifying suggestion instead of just finishing it.</p>
<p>Example: I started a function for sorting users:</p>
<pre><code class="language-python">def sort_users(users):
</code></pre>
<p>Copilot would auto-complete with some sorting logic, but I'd have to check if it was what I meant.</p>
<p>Claude Code would sometimes suggest:</p>
<pre><code class="language-python">def sort_users(users, key="created_at", reverse=False):
</code></pre>
<p>It was thinking: "Sorting is ambiguous. What key? What order?" It was right more often than not.</p>
<h2 id="heading-what-broke-or-slowed-things-down">What Broke (Or Slowed Things Down)</h2>
<h3 id="heading-response-time">Response Time</h3>
<p>This was the biggest issue. Copilot is instant. I type <code>def get_</code> and it finishes before I can blink. It's autocomplete, and autocomplete needs to be fast. The latency is maybe 100-200ms.</p>
<p>Claude Code has a noticeable delay. Maybe 1-2 seconds before suggestions appear. On day one, that felt fine – I had time to think. By day two, I was annoyed. By day three, I was genuinely frustrated.</p>
<p>Over a day of coding, that adds up. If you're typing 20 functions and each one has a 2-second delay, that's 40 seconds of just waiting. It doesn't sound like much, but it breaks flow. Flow is where the good coding happens.</p>
<p>By day three, I was getting frustrated. I'd type faster than Claude Code could suggest, which meant I'd often just finish the code myself. The second a suggestion appeared, I'd already moved on. Defeating the purpose.</p>
<p>I tested this by tracking time. Same function, same complexity:</p>
<ul>
<li><p><strong>With Copilot:</strong> 3 minutes (including auto-complete time)</p>
</li>
<li><p><strong>With Claude Code:</strong> 5 minutes (waiting for suggestions + finishing manually)</p>
</li>
</ul>
<p>The delay isn't theoretical. It's real and measurable.</p>
<p><strong>The truth:</strong> Copilot is an autocomplete tool. It needs sub-second latency. Claude Code, being more powerful, is inherently slower. That's a fundamental tradeoff. You can't have both "instant" and "smart." Choose one.</p>
<h3 id="heading-no-inline-acceptance">No Inline Acceptance</h3>
<p>With Copilot, I press Tab to accept. It's in my muscle memory. Tab = accept.</p>
<p>Claude Code doesn't work exactly the same way. I had to click or use a different keyboard shortcut. Small thing, but it broke my rhythm constantly. I'd write code, see a suggestion, and instinctively press Tab. Nothing would happen. Then I'd remember: "Oh right, it's a different tool."</p>
<p>After two weeks, I never fully got used to it.</p>
<h3 id="heading-disconnected-from-flow">Disconnected From Flow</h3>
<p>Copilot is so embedded in the editor that I don't think about it. It's just there, like spellcheck. Claude Code feels like a separate tool I'm using, which means I'm more aware of it. That sounds like a good thing, but it's actually more cognitively expensive.</p>
<p>I wanted to type and have suggestions appear. Instead, I felt like I was using a tool. There's a difference. It's the same difference between walking and thinking about walking. When you're thinking about your walking mechanics, you walk worse.</p>
<p>This affected my productivity more than I expected. On day three, I found myself just typing manually instead of waiting for suggestions. It wasn't a conscious decision. I'd just start typing and then remember "oh, the suggestion came in." By then I'd already finished half the function myself.</p>
<h3 id="heading-limited-to-the-file">Limited to the File</h3>
<p>Copilot understands your entire project. It knows what's in other files, what libraries you import, what conventions you follow. If I'm importing a utility function that doesn't exist yet, Copilot knows to suggest the import with the path I'd use.</p>
<p>Claude Code seemed more limited to the current file. Sometimes it would suggest imports that weren't already in the file, or use patterns different from the rest of my codebase. Not often, but enough to notice. On one occasion, it suggested a database query pattern that was different from my whole codebase. It would've worked, but it would've been inconsistent.</p>
<p>This is less of a limitation and more of a design difference. Claude Code is built for depth on individual files, not breadth across a project.</p>
<h2 id="heading-the-first-week-vs-the-second-week">The First Week vs The Second Week</h2>
<p><strong>Week 1:</strong> I was excited. Claude Code felt smarter. I noticed the accuracy advantage. But the latency was starting to annoy me.</p>
<p><strong>Week 2:</strong> The novelty wore off. The latency was more annoying. I was missing Copilot's speed. I found myself disabling Claude Code's suggestions and typing manually more often, which defeated the purpose. "If I'm typing it all manually anyway, why switch?"</p>
<p>By day 10, I was typing code faster with Claude Code disabled than with it enabled. That's when I knew it wasn't working for me.</p>
<h2 id="heading-why-i-went-back">Why I Went Back</h2>
<p>On day 14, I re-enabled Copilot.</p>
<p>The first thing I noticed: speed. Code was completing again instantly. My rhythm came back. I hit Tab, it accepted, I moved on. That's the entire appeal of Copilot-it's frictionless.</p>
<p>I also realized how much I'd been manually typing. On days 10-14, I was writing more code by hand because the suggestions felt too slow to be worth waiting for. Without realizing it, I'd completely stopped using Claude Code's suggestions. I was just typing. That's the worst of both worlds: no AI help and the cognitive burden of being aware you're using a tool that's not helping.</p>
<p>Was I sacrificing accuracy? A little. But I'm accurate enough that I catch mistakes in review. For day-to-day, Copilot is fine.</p>
<p>The second thing: it just works. No weird setup, no integration issues. It's part of VSCode. It's always there.</p>
<p>By day 15, I was back to normal productivity, maybe even higher because the flow was better.</p>
<h2 id="heading-the-honest-verdict">The Honest Verdict</h2>
<p>Claude Code isn't a Copilot replacement. It's not worse. It's different. It's like comparing a calculator to a calculator app on your phone. One is designed for speed and muscle memory. One is designed to be a full computer in your pocket. They're not competitors.</p>
<p>If I'd tried Claude Code expecting it to be better at debugging, I would've been happy. I was trying it expecting it to replace my autocomplete, which is where it falls flat.</p>
<p>The experiment was valuable, though. It taught me that:</p>
<ol>
<li><p>Latency matters more than I expected. A 2-second delay breaks flow.</p>
</li>
<li><p>Familiarity matters. Tab to accept is burned into my muscle memory.</p>
</li>
<li><p>Tool stacking works. Claude Code is great for debugging. Copilot is great for autocomplete. Together they're better than either alone.</p>
</li>
</ol>
<h2 id="heading-what-i-actually-use-now">What I Actually Use Now</h2>
<p>I didn't abandon Claude Code. I just changed how I use it.</p>
<ul>
<li><p><strong>Claude Code:</strong> For debugging, analysis, and big changes. "Why is this function slow?" "Refactor this for readability." I invoke it deliberately when I need thinking, not continuous autocomplete.</p>
</li>
<li><p><strong>Copilot:</strong> For routine coding. Finishing functions, auto-completing imports, normal flow.</p>
</li>
</ul>
<p>That's the working solution. Claude Code is powerful, but it's not a Copilot replacement for daily work. It's a different tool for a different use case.</p>
<h2 id="heading-copilot-vs-claude-code-the-breakdown">Copilot vs Claude Code: The Breakdown</h2>
<p><strong>Copilot is better for:</strong></p>
<ul>
<li><p>Pure autocomplete speed</p>
</li>
<li><p>Routine, well-understood coding</p>
</li>
<li><p>Low friction, high flow state</p>
</li>
<li><p>Simple suggestions</p>
</li>
</ul>
<p><strong>Claude Code is better for:</strong></p>
<ul>
<li><p>Complex suggestions that require reasoning</p>
</li>
<li><p>Debugging and analysis</p>
</li>
<li><p>Understanding intent (not just completing code)</p>
</li>
<li><p>Asking questions about code you've written</p>
</li>
</ul>
<p>If you're a Copilot user thinking about switching, don't do it as a straight replacement. Claude Code isn't faster. It's smarter, but slower, and for day-to-day autocomplete, faster wins.</p>
<p>Try using both. Use Copilot for normal coding, Claude Code for debugging and complex changes. If you only want to pay for one, stick with Copilot. It's cheaper, it's faster, and it does the job.</p>
<p>If you're a heavy debugger and you spend a lot of time analyzing code, Claude Code might be worth it. But as a Copilot replacement? No.</p>
<h2 id="heading-a-word-on-developer-experience">A Word on Developer Experience</h2>
<p>What surprised me wasn't just the latency. It was how much I missed the seamlessness of Copilot. With Copilot, I don't think about it. It's like breathing-automatic. I type, it suggests, I accept or reject, I move on.</p>
<p>With Claude Code, I was constantly aware I was using a tool. I'd finish typing before the suggestion appeared. I'd have to remember the keyboard shortcut. I'd have to context-switch to look at the suggestion.</p>
<p>That awareness is exhausting. It's why flow state is so important to programming. The best tools get out of your way. Copilot gets out of the way. Claude Code, for autocomplete purposes, doesn't.</p>
<p>Developer experience isn't a nice-to-have. It's core to productivity. A tool that's 10% smarter but 50% more annoying is worse, not better.</p>
<h2 id="heading-what-would-make-me-switch">What Would Make Me Switch</h2>
<ul>
<li><p>Claude Code needs to get faster. Sub-second latency for suggestions.</p>
</li>
<li><p>It needs better editor integration. Tab to accept, like Copilot.</p>
</li>
<li><p>It needs to understand the full project, not just the current file.</p>
</li>
</ul>
<p>Once those three things happen, it'd be competitive. Until then, Copilot is still the better choice for daily coding work.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>This experiment taught me something: better isn't always better. Claude Code is arguably smarter than Copilot. But Copilot is more efficient. For autocomplete, efficiency matters more than intelligence.</p>
<p>It's like comparing a sports car to a Jeep. The sports car is faster on a highway. The Jeep is better on a mountain trail. Neither is "better." They're different. Copilot is trying to predict the next line of code fast. Claude Code is trying to understand your code deeply. They're solving different problems.</p>
<p>I went back to Copilot not because Claude Code is bad. It's actually impressive. But it's a different category of tool. Using it for autocomplete is like using a hammer when you need a screwdriver. The hammer might be fancier, but the screwdriver does the job.</p>
<p>What surprised me most was how much latency matters. I didn't expect a 2-second delay to be that noticeable. But when you're in the zone, typing code, and the autocomplete lags, it completely breaks your flow. It's not about the absolute time. It's about the interruption.</p>
<p>Don't take my word for it though. Run your own two-week experiment. Pick a tool, commit to it, and see what happens. Track your productivity. Track your frustration. The best tool is the one you'll actually use. And you can only find that out by using it.</p>
<h2 id="heading-whats-next">What's Next?</h2>
<p>If you found this useful, I write about Docker, AI tools, and developer workflows every week. I'm Balajee Asish - Docker Captain, freeCodeCamp contributor, and currently building my way through the AI tools space one project at a time.</p>
<p>Got questions or built something similar? Drop a comment below or find me on <a href="https://github.com/balajee-asish">GitHub</a> and <a href="https://linkedin.com/in/balajee-asish">LinkedIn</a>.</p>
<p>Happy building.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ OSS Pull Request Therapy: Learning to Enjoy Code Reviews with npmx ]]>
                </title>
                <description>
                    <![CDATA[ For years, I thought Open Source Software (OSS) just wasn’t for me. I had no plans to join any OSS communities on top of my existing developer community obligations. Curious about the hype I saw on Bl ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learning-to-enjoy-code-reviews-with-npmx/</link>
                <guid isPermaLink="false">69a6f99556428acc6fef7fbc</guid>
                
                    <category>
                        <![CDATA[ Programming Blogs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Open Source ]]>
                    </category>
                
                    <category>
                        <![CDATA[ open source ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Abbey Perini ]]>
                </dc:creator>
                <pubDate>Tue, 03 Mar 2026 15:09:09 +0000</pubDate>
                <media:content url="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/5e1e335a7a1d3fcc59028c64/5765f28c-0d0e-46be-bc60-972a4d879b7e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>For years, I thought Open Source Software (OSS) just wasn’t for me. I had no plans to join any OSS communities on top of my existing developer community obligations.</p>
<p>Curious about the hype I saw on <a href="https://bsky.app/">Bluesky</a>, I recently joined the <a href="https://npmx.dev/">npmx</a> Discord server on a whim. My journey from lurker to contributor taught me a lot about OSS and gave me new confidence going into code reviews.</p>
<p>In this article, I’ll walk you through my journey to give you a little insight into the process of getting involved in Open Source.</p>
<h3 id="heading-heres-what-ill-cover">Here’s what I’ll cover:</h3>
<ul>
<li><p><a href="#heading-my-struggles-with-pull-requests">My Struggles with Pull Requests</a></p>
</li>
<li><p><a href="#heading-my-former-view-of-oss">My Former View of OSS</a></p>
<ul>
<li><p><a href="#heading-the-basics-of-oss">The Basics of OSS</a></p>
</li>
<li><p><a href="#heading-the-dark-side-of-oss">The Dark Side of OSS</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-getting-started-with-npmx">Getting Started with npmx</a></p>
</li>
<li><p><a href="#heading-the-not-so-perfect-pr">The Not So Perfect PR</a></p>
</li>
<li><p><a href="#heading-collaboration-over-perfection">Collaboration Over Perfection</a></p>
</li>
<li><p><a href="#heading-my-current-view-of-oss">My Current View of OSS</a></p>
</li>
<li><p><a href="#heading-tips-for-pr-authors-and-reviewers">Tips for PR Authors and Reviewers</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-my-struggles-with-pull-requests">My Struggles with Pull Requests</h2>
<p>I’ll admit, I’ve always had a hard time with code reviews. I can be quite the perfectionist. I’ll entertain every nitpick and only hear the criticism.</p>
<p>If reviews go on for days, I easily get overwhelmed. I enjoy pairing and co-working. I want to enjoy Pull Request (PRs), but addressing PR comments takes a lot out of me.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771264593124/b5674020-6b0d-4ad2-bb92-921d05ebecc7.png" alt="Me looking at the bugs that my colleagues pointed out in my pull request Patrick Star from Spongebob looking absolutely horrified and staring at a computer" style="display:block;margin:0 auto" width="623" height="566" loading="lazy">

<p>Some of my struggle is a need for <a href="https://askjan.org/disabilities/Attention-Deficit-Hyperactivity-Disorder-AD-HD.cfm#spy-scroll-heading-2">accommodations</a> that I rarely get. I also have plenty of lived experience with how hostile code reviews can become (even in a professional setting). Finally, there’s how I was introduced to PR reviews.</p>
<p>Outside of work, I had only ever experienced perfunctory PRs – I’d receive at most one suggestion, but usually just got a “LGTM” (Looks Good to Me) comment. Professionally, I went from no code reviews to incredibly detailed code reviews basically overnight. I still feel like I’m playing catch up.</p>
<p>On the one hand, thinking deeply about every suggestion has made me a better developer. I thrive in collaborative environments with thoughtful code reviews. Developers who have worked with me have told me that they benefit from answering all my “why?” questions.</p>
<p>On the other hand, I demand compliments, gifs, and video calls from my reviewers. I don’t do well with a bombardment of vague comments on my PRs. I’ve spent a lot of time documenting code guidelines and review processes that other people seem to understand and remember much more easily than I do.</p>
<p>Developer communities have helped me navigate all of this. Community is a priceless resource for career changers and new grads. When everyone shares their experience, the uninitiated learn about how things could be and what kinds of things aren’t normal (like very hostile code reviews).</p>
<h2 id="heading-my-former-view-of-oss">My Former View of OSS</h2>
<p>When I’ve talked and written about developer community, I’ve recommended online networking communities, going to meetups, tech conferences, social media, writing, and posting your writing online. The one thing I haven’t written about? OSS.</p>
<p>My first real introduction to OSS was through the online networking group <a href="https://virtualcoffee.io/">Virtual Coffee</a>. By the end of my first <a href="https://hacktoberfest.com/">Hacktoberfest</a>, I knew the basics.</p>
<h3 id="heading-the-basics-of-oss">The Basics of OSS</h3>
<ul>
<li><p>Find a project that interests you.</p>
</li>
<li><p>Check the Contributing Guide.</p>
</li>
<li><p>Claim an issue.</p>
</li>
<li><p>Following the Contributing Guide, make a fork, write the code, and open a PR.</p>
</li>
<li><p>The maintainer merges it.</p>
</li>
<li><p>You did it! That’s OSS.</p>
</li>
</ul>
<h3 id="heading-the-dark-side-of-oss">The Dark Side of OSS</h3>
<p>Over time, I couldn’t help but see the “dark side” of OSS – maintainers <a href="https://github.com/zloirock/core-js/blob/master/docs/2023-02-14-so-whats-next.md">burning out</a>, <a href="https://github.com/tailwindlabs/tailwindcss.com/pull/2388#issuecomment-3717222957">friction between users and maintainers</a>, corporations suddenly trying to assert control over OSS (for example, <a href="https://www.cmswire.com/digital-experience/whats-with-the-open-source-drama-between-wordpress-and-wp-engine/">Wordpress</a>, <a href="https://dev.to/cseeman/what-just-happened-to-rubygems-31n9">Ruby</a>), and the thankless, frustrating job of maintaining a package that everyone uses but no one wants to pay for.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771191134959/871a6a8d-85ea-402a-950e-ec25d4738859.png" alt="A large structure made out of building blocks labelled All Modern Digital Infrastructure. One tiny, integral block is labelled A project some random person in Nebraska has been thanklessly maintaining since 2003" style="display:block;margin:0 auto" width="385" height="489" loading="lazy">

<p>I have to be honest: I had begun to think of open source maintainers as <a href="https://www.youtube.com/watch?v=mm8R3u_b0yU">Roz from Monsters Inc.</a> – justifiably fed up with the extra work dumped on them by unappreciative people.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770053555027/4f38e415-3100-4737-af1c-947725c60b23.png" alt="A slug person wearing a cardigan, holding a pencil and clipboard with the Monsters Inc. logo. She's wearing glasses and lipstick. Her grey hair is styled straight up, she has a mole on her bottom lip. She currently looks disgusted." style="display:block;margin:0 auto" width="604" height="512" loading="lazy">

<p>Meeting maintainers in-person didn’t contradict my view. Every single one had a story about <a href="https://medium.com/@sohail_saifii/the-open-source-maintainer-burnout-crisis-nobodys-fixing-5cf4b459a72b">burnout and lack of funding</a>. I started to assume that anyone excited about OSS just hadn’t been in it long enough</p>
<p>…so my friends were quite surprised when I suddenly announced that I had joined the OSS project <a href="https://npmx.dev/">npmx</a>.</p>
<h2 id="heading-getting-started-with-npmx">Getting Started with npmx</h2>
<p>It wasn’t the first mention of the npmx project that interested me. It wasn’t the second. It was a <a href="https://bsky.app/profile/erus.dev/post/3mdicpnmijk2o">meme</a>. I’ve known <a href="https://roe.dev/">Daniel Roe</a> long enough to know that he is brilliant. I like learning from people who are smarter than I am.</p>
<p>I reached out to <a href="https://bsky.app/profile/patak.dev">Patak</a>, and got an invite to the <a href="https://chat.npmx.dev/">npmx Discord server</a>. I was amazed by what I saw: a rapidly growing, excited, and inclusive community. I realized that I had only ever contributed to communities with at most a handful of people. My view of OSS immediately changed.</p>
<p>This was it. I was finally going to have fun doing PRs.</p>
<p>So I hopped into the <a href="https://github.com/npmx-dev/npmx.dev">npmx GitHub repository</a> and tried to get my bearings. Very quickly, I was overwhelmed. The project moves <em>so fast.</em> I tried to do step 3 – claim a ticket. As far as I could tell, all the tickets were being claimed in Discord before or as they were being written.</p>
<p><a href="https://bsky.app/profile/jonathanyeong.com">Jono</a> kindly welcomed me into his fork for working on the blog page, but I ran into frustrating and weird issues with running the repository (repo) locally and the pre-commit hooks. Multiple people tried to help me debug and were just as stumped as I was.</p>
<p>The next day, <a href="https://bsky.app/profile/whitep4nth3r.com">Salma</a> arrived. The day after, she was in charge of outreach, and asked me to write a blog. Then life got in the way. I couldn’t keep my promise to <a href="https://www.software.com/devops-guides/context-switching">context switch</a> into a feature branch in a new repo. I felt like my only contribution was going to be a single line change on the blog page and a blog.</p>
<p>It didn’t help that I wasn’t happy with the blog I had started writing. I gave up on keeping up and lurked in the Discord channels. I chimed in on a few conversations, and offered to help with things like failing accessibility tests.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771190012891/ac34a318-46f7-45d8-90c3-10837627ad17.png" alt="check @AbbeyPerini's reaction here, if we manage to set an example on how good an app can be with good #a11y, great #perf, and a good #test story, listening to the #e18e folks on keeping deps clean, the npmx repo will be a great learning resource for folks learning how to build websites Salma If anything I made it MORE accessible with a this react Abbey Perini let me run it locally and see if I can spot something with 3 purple heart reacts God it's nice to look at a repo where a11y wasn't an afterthought 6 100 reacts" style="display:block;margin:0 auto" width="1008" height="708" loading="lazy">

<p>Four days later, the project was officially two weeks old. The maintainers announced a mandatory week of vacation – community members experienced with burnout had seen the writing on the wall. Vacation would start in 10 days, so that’s basically how long I had to get a contribution in before the alpha release.</p>
<h2 id="heading-the-not-so-perfect-pr">The Not So Perfect PR</h2>
<p>An hour later, I finally saw it – my chance to contribute code. <a href="https://bsky.app/profile/alexdln.com">Alex</a> needed <a href="https://github.com/npmx-dev/npmx.dev/issues/1028">a toggle re-written as a checkbox</a>. It was my time to shine. I commented on the ticket to claim it as soon as it was written. I slapped up a draft PR to show I was working on it. Predictably, my focus was once again pulled away from the repo.</p>
<p>A couple days later, <a href="https://bsky.app/profile/knowler.dev">Knowler</a> reviewed my draft PR, and all my PR anxieties came tumbling back. This was going to be The Perfect PR. How dare anyone look at it before I was ready to defend my work. What would they think about my abilities looking at my old copy and pasted portfolio site code that I hadn’t even finished translating from <a href="https://react.dev/">React</a> to <a href="https://vuejs.org/">Vue</a>? I was legitimately embarrassed someone was looking at my code in that state.</p>
<p>Fueled by embarrassment and productive procrastination, I sprung into action. In what little free time I had, I must have toggled my toggle a thousand times. Three days later, it was finally in a state I was happy with. It was time to open up my PR for review.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771263500393/64bc78f5-dd9c-48b7-805b-fcba0456753a.png" alt=" Mona-Lisa Saperstein, played by Jenny Slate, hand outstretched, saying &quot;money please,&quot; but the meme is captioned &quot;review please&quot;" style="display:block;margin:0 auto" width="806" height="454" loading="lazy">

<p>A couple dozen comments came in. Overwhelmed, I tried to remember that I had asked for this. I resolved most of the comments and left a comment saying I’d get to the last item, <a href="https://polypane.app/blog/forced-colors-explained-a-practical-guide/">forced colors mode</a>, in the morning. Frustrated with the code for the forced colors and myself for forgetting a few tiny things, I went to play games with friends.</p>
<p>A few hours later, I got a DM from Daniel. He had some code for my PR. I agreed with his reasoning for all the changes and found out an entire tooltip had been added while I was blissfully ignoring the rest of the repo. (I’m confident in my ability to merge or rebase my way out of any situation.)</p>
<p>Splitting my attention between <a href="https://store.steampowered.com/app/1203620/Enshrouded/">Enshrouded</a> and talking to Daniel, I felt defeated. I knew I finish the forced colors fix the next day, but also adding a tooltip felt daunting. Still, it felt like I needed to do it all.</p>
<h2 id="heading-collaboration-over-perfection">Collaboration Over Perfection</h2>
<p>And then I remembered, this wasn’t work and it wasn’t going to come up on a performance review. I wasn’t alone – Knowler and Daniel were taking the time to help me get this PR merged because they wanted to. I had the opportunity to collaborate with some brilliant people and see how they would write the same thing.</p>
<p>So I pushed through my perfectionism, demanded compliments, and asked Daniel to push his changes. I told him I’d review them in the morning.</p>
<p>Reviewing Daniel’s code, I found that he had forgotten a couple tiny things, just like I had. The code I was frustrated with the night before was legitimately frustrating. <a href="https://cssence.com/2024/forced-colors-mode-strategies/">Emulating forced colors on a Mac</a> was giving me weird and contradictory results. I needed to test on a Windows machine to finally get it right.</p>
<p>Then, six days after I opened the PR, I finally merged it. I was on top of the world. I had gotten my contribution in before our vacation (and more importantly, I had received multiple compliments). Finally, I knew what to write this blog about.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771191470445/caeb7ab5-d54b-4dd9-907c-a95a85e650b1.png" alt="Abbey Perini Bluesky Elder I'd like to thank @knowler.dev and @danielroe.dev and my confidence in my git skills because this is the fastest moving repo I've ever been in. Quoted post - npmx @npmx.dev @abbeyperini.dev at chat.npmx.dev#contributing. A screenshot of the npmx Discord server. The npmx APP posted @Abbey Perini (abbeyperini) is now a contributor! Abbey Perini NERD responds with a gif of Jim Carrey as the Mask giving an acceptance speech and saying Thank You! with 6 raised hands reacts, 2 trophy reacts, and 2 clapping hands reacts 4:10 PM Feb 11, 2026" style="display:block;margin:0 auto" width="1008" height="1254" loading="lazy">

<h2 id="heading-my-current-view-of-oss">My Current View of OSS</h2>
<p>When you’re looking for a project that interests you, the code isn’t the only thing to evaluate. Early in my career, I learned three rules for evaluating software tools.</p>
<ol>
<li><p>Check the date of the last update to make sure it’s actively maintained.</p>
</li>
<li><p>Look at the documentation. Is it up to date and easy to follow?</p>
</li>
<li><p>Check out the community. Do people get fairly quick responses to their questions?</p>
</li>
</ol>
<p>After joining npmx, I’ve discovered that, with a few tweaks, these rules also apply to evaluating an OSS project.</p>
<ol>
<li><p>Check out the last few tickets and PRs to see how fast the repo moves. If it’s fairly slow, you can probably claim an issue in GitHub easily. If it’s rapid, start by getting to know the community and how they’re assigning tickets.</p>
</li>
<li><p>You should always check the repo for a code of conduct, contributing guide, and sufficient documentation. Also evaluate the tickets. Are contributors expected to research solutions on their own or given strict requirements? How do maintainers respond to comments on issues?</p>
</li>
<li><p>Check out the community. An active, inclusive community makes contributing a lot more fun.</p>
</li>
</ol>
<p>Now, my view of OSS is much more nuanced. Yes, there are issues with OSS as whole, but there’s a reason people want to fix them. OSS can be collaborative, inspirational, and enjoyable.</p>
<h2 id="heading-tips-for-pr-authors-and-reviewers">Tips for PR Authors and Reviewers</h2>
<p>People underestimate the importance of the relationship between PR author and reviewer. A collaborative OSS code review process doesn’t happen in a vacuum. It takes careful cultivation by the PR author, PR reviewer, and project community.</p>
<p>For a long time, I focused on the responsibility of the reviewer to make the PR author comfortable (for example, compliments, gifs). Don’t get me wrong – I think one of the most important parts of a senior developer’s job is to provide constructive, actionable feedback.</p>
<p>But I now understand that the PR author’s sense of agency and desire to learn are just as important.</p>
<p>A sense of agency is a sense of control over actions and consequences. In other words, the PR author needs to feel that they have control over what goes into their PR. Before npmx, I understood this a little bit. I always ask “why?” because I’m not putting my name on code that I don’t understand and agree with. I have counseled my own junior developer that it’s his job to get PRs he’s authored reviewed and merged.</p>
<p>After experiencing an in-depth code review outside of work, I finally understand that a PR is a process. Reviews exist to get consensus, so “perfect” is far more subjective than I originally thought. There’s a reason you get a conversation, not a grade.</p>
<p>Maybe I’ll even ignore some nitpicks in the future.</p>
<p>A desire to learn makes remaining open to a reviewer’s suggestions and requests a lot easier. During my first npmx PR, it was only when my desire to learn outweighed my desire to prove something that I started having fun.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Today, March 3rd, 2026, is <a href="https://npmx.dev/blog/alpha-release">the alpha release of npmx</a>, and I am very proud to be a contributor and member of the community.</p>
<p>I look forward to learning about OSS from Patak, fancy, smart code from Daniel, outreach from Salma, and accessibility from Knowler. I know I’ll learn many things outside of that list, too. I’m grateful I’m not the smartest person in the room and that I finally get to have fun with Pull Requests.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Save Multiple Drafts in Git: A Guide to Using Stash ]]>
                </title>
                <description>
                    <![CDATA[ Writing code can be similar to writing tutorials. In both cases, you’ll typically need to create and work on multiple drafts before reaching the final version. In an ideal setting, you would write cod ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-save-multiple-drafts-in-git-a-guide-to-using-stash/</link>
                <guid isPermaLink="false">6989f72cfec80c4e91c007a4</guid>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ version control ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chidiadi Anyanwu ]]>
                </dc:creator>
                <pubDate>Mon, 09 Feb 2026 15:03:08 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770649372924/7c949e9d-627a-4b06-99fe-40ce0e7c8507.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Writing code can be similar to writing tutorials. In both cases, you’ll typically need to create and work on multiple drafts before reaching the final version.</p>
<p>In an ideal setting, you would write code for a feature, add that code to the staging area, and then commit it before going to the next part. This helps keep your commit history clean. (Don't worry if you don't have a clean history. Many of us don’t.)</p>
<p>But now, imagine a scenario where you have multiple features to build. You've committed the first. You've started the third, but then you found out you needed to build the second one first, because the third depends on it. It might seem like you have to go back in time and build out that second feature without mixing in the code changes for the third, and without deleting the code changes for the third.</p>
<p>So how do you do that?</p>
<p>In this article, you’re going to learn about Git stash, what it is, and the basic commands you need to be able to use it.</p>
<h2 id="heading-what-well-cover">What We’ll Cover:</h2>
<ul>
<li><p><a href="#heading-what-is-git-stash">What is Git Stash?</a></p>
</li>
<li><p><a href="#heading-how-to-use-git-stash">How to Use Git Stash</a></p>
<ul>
<li><p><a href="#heading-pushing-commands-to-the-stash">Pushing Commands To The Stash</a></p>
</li>
<li><p><a href="#heading-applying-changes-from-the-stash-to-the-working-directory">Applying Changes From The Stash To The Working Directory</a></p>
</li>
<li><p><a href="#heading-listing-the-items-in-the-stash-list">Listing The Items In The Stash List</a></p>
</li>
<li><p><a href="#heading-removing-items-from-the-stash">Removing Items From The Stash</a></p>
</li>
<li><p><a href="#heading-creating-a-new-branch-from-stash">Creating A New Branch From Stash</a></p>
</li>
<li><p><a href="#heading-showing-the-changes-in-the-stash">Showing The Changes In The Stash</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<p>To understand and get the most out of this tutorial, you’ll need to have a basic understanding of Git.</p>
<h2 id="heading-what-is-git-stash">What is Git Stash?</h2>
<p>Git stash provides storage (in the form of a stack) where you can store changes to your code. It lets you keep these changes separate from the current working directory until you're ready to apply them.</p>
<p><code>git stash</code> is a relatively simple command, and has a few variations like:</p>
<ul>
<li><p><code>git stash push</code></p>
</li>
<li><p><code>git stash pop</code></p>
</li>
<li><p><code>git stash apply</code></p>
</li>
<li><p><code>git stash drop</code></p>
</li>
<li><p><code>git stash clear</code></p>
</li>
<li><p><code>git stash show</code></p>
</li>
<li><p><code>git stash list</code></p>
</li>
</ul>
<h2 id="heading-how-to-use-git-stash">How to Use Git Stash</h2>
<h3 id="heading-pushing-code-changes-to-the-stash">Pushing Code Changes to the Stash</h3>
<p>To push uncommitted changes from the working directory to the stash, you can use <code>git stash push.</code> When you do that, the changes disappear from the working directory and are saved in the stash. The working directory is then set back to the last commit (which, in the example below, is the Feature one).</p>
<pre><code class="language-bash">git stash push
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/7652801b-cb1e-45cc-ac94-281512252f37.png" alt="7652801b-cb1e-45cc-ac94-281512252f37" style="display:block;margin:0 auto" width="1019" height="382" loading="lazy">

<p>You can also write the <code>git stash push</code> command as just <code>git stash</code> – it means the same thing and performs the same way. You can also add a message to it with the <code>-m</code> or <code>--message</code> flag:</p>
<pre><code class="language-bash">git stash push -m "Feature two I was working on"
</code></pre>
<p>You can use the <code>-u</code> flag or <code>--include-untracked</code> to include untracked changes in the stash. This way, the changes not yet tracked by Git can be included in the stash.</p>
<pre><code class="language-bash">git stash push -u
git stash push --include-untracked
</code></pre>
<p>On the other hand, you can decide to push only staged changes to the stash with the <code>-s</code> or <code>--staged</code> flag:</p>
<pre><code class="language-bash">git stash push -s
</code></pre>
<p>You can also suppress feedback messages with the <code>-q</code> or <code>--quiet</code> flag:</p>
<pre><code class="language-bash">git stash push -q
</code></pre>
<p>When you're done with your edits on the current working directory, you can commit and push without the older changes bleeding into it. They're safely stowed away in the stash.</p>
<p>Anytime you use the git stash command, you add a stash to the stash list. The stashes are identified by their index numbers.</p>
<h3 id="heading-applying-changes-from-the-stash-to-the-working-directory">Applying Changes from the Stash to the Working Directory</h3>
<p>Let’s say you had a quick fix to do. You’re done committing that, and you want to continue with what you were working on. You can restore the changes from the stash to continue working on them.</p>
<p>You can do this by using the <code>git stash apply</code> command or the <code>git stash pop</code> command:</p>
<pre><code class="language-bash">git stash apply
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/89d30b86-ba75-412f-8aba-b8bf1ac32933.png" alt="89d30b86-ba75-412f-8aba-b8bf1ac32933" style="display:block;margin:0 auto" width="1005" height="475" loading="lazy">

<p><code>git stash apply</code> applies the latest changes from the stash to the working directory, but doesn’t delete the code from the stash. You can select a particular entry to apply by index number:</p>
<pre><code class="language-bash">git stash apply --index stash@{1}
# You'd need to use quotes "stash@{1}" if you're writing this in PowerShell
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/4a26b84c-9bf0-4bd2-9687-7ebb94046f5d.png" alt="4a26b84c-9bf0-4bd2-9687-7ebb94046f5d" style="display:block;margin:0 auto" width="1001" height="212" loading="lazy">

<p><code>git stash pop</code>, on the other hand, applies the latest changes from the stash, then deletes them from the stash. Basically, popping from a stack. You can select a particular stash entry to pop by index number.</p>
<pre><code class="language-bash">git stash pop
</code></pre>
<pre><code class="language-bash">git stash pop --index stash@{1}
# You'd need to use quotes "stash@{1}" if you're writing this in PowerShell
</code></pre>
<h3 id="heading-listing-the-items-in-the-stash-list">Listing the Items in the Stash List</h3>
<p>You can use <code>git stash list</code> to list out the stashes in the stash list. It’s arranged by index number (like {0}, {1}, and so on). Any time you do a git stash push, it adds a stash to the stash list.</p>
<pre><code class="language-bash">git stash list
stash@{0}: WIP on master: b2a2709 Feature one
stash@{1}: WIP on master: b2a2709 Feature one
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/46c7b4a8-6701-4227-8d48-9da354c9d1b7.png" alt="46c7b4a8-6701-4227-8d48-9da354c9d1b7" style="display:block;margin:0 auto" width="1014" height="156" loading="lazy">

<h3 id="heading-removing-items-from-the-stash">Removing Items from the Stash</h3>
<p>You can use <code>git stash clear</code> to clear the stash list. But if you just want to drop a particular entry from the stash list, you can use <code>git stash drop</code> and specify the entry you want to drop by the index number. This doesn’t apply its changes to the working directory.</p>
<pre><code class="language-bash">git stash clear
</code></pre>
<pre><code class="language-bash">git stash drop stash@{0}
# You'd need to use quotes "stash@{0}" if you're writing this in PowerShell
</code></pre>
<h3 id="heading-creating-a-new-branch-from-stash">Creating a New Branch from Stash</h3>
<p>You can also create a new branch from a stash using <code>git stash branch</code>. The syntax is <code>git stash branch &lt;branchname&gt; [&lt;stash&gt;].</code></p>
<pre><code class="language-bash">git stash branch premium-branch stash@{0}
# You'd need to use quotes "stash@{0}" if you're writing this in PowerShell
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/a14db1c1-b0ac-4a81-ae4d-4b4859989489.png" alt="a14db1c1-b0ac-4a81-ae4d-4b4859989489" style="display:block;margin:0 auto" width="1018" height="232" loading="lazy">

<p>If you don’t add the stash index, it will just use the last stash.</p>
<pre><code class="language-bash">git stash branch premium-branch
</code></pre>
<h3 id="heading-showing-the-changes-in-the-stash">Showing the Changes in the Stash</h3>
<p>You can use <code>git stash show</code> to show the changes you’ve made in your stashed changes.</p>
<pre><code class="language-bash">C:\file-path\git_stash_example&gt; git stash show
 text.md | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/9d5a5e83-7c2f-4a2d-a004-f406b5ed1af7.png" alt="9d5a5e83-7c2f-4a2d-a004-f406b5ed1af7" style="display:block;margin:0 auto" width="1008" height="196" loading="lazy">

<h2 id="heading-conclusion">Conclusion</h2>
<p>Git stash is one of those quiet tools that becomes indispensable once your workflow starts to get messy. It allows you to shelve unfinished ideas, switch context without panic, and keep your commits clean. With it, you can safely carry out urgent fixes, and juggle dependent features without muddling up your commit history.</p>
<p>If you enjoyed this article, share it with others. You can also reach me on <a href="https://linkedin.com/in/chidiadi-anyanwu">LinkedIn</a> or <a href="https://linkedin.com/in/chidiadi-anyanwu">X.</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn How to Use Git and GitHub – A Beginner-Friendly Handbook ]]>
                </title>
                <description>
                    <![CDATA[ In this handbook, you’re going to dive into something really exciting: Git and GitHub. To start, let’s clear one thing up: Git and GitHub are not the same thing. In short, Git is the tool that runs on your own computer and keeps track of changes in y... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-how-to-use-git-and-github-a-beginner-friendly-handbook/</link>
                <guid isPermaLink="false">693c859b176ed5964e28b921</guid>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Gitcommands ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sumit Saha ]]>
                </dc:creator>
                <pubDate>Fri, 12 Dec 2025 21:14:03 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1765495440352/0eadf330-7a89-4328-aed1-3c851d279a5d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this handbook, you’re going to dive into something really exciting: Git and GitHub.</p>
<p>To start, let’s clear one thing up: Git and GitHub are not the same thing.</p>
<p>In short, Git is the tool that runs on your own computer and keeps track of changes in your files, while GitHub is an online platform that lets you store those Git projects in the cloud and collaborate with other people. Git works perfectly fine on its own, but GitHub makes teamwork, sharing, and backup much easier. They’re connected, yes, but completely different.</p>
<p>Think of it this way – Git is the “coffee,” and GitHub is the “coffee shop” where that coffee is served. Fun analogy, right? Don’t worry, in just a moment you’ll see exactly what that means.</p>
<h2 id="heading-what-does-git-do">What Does Git Do?</h2>
<p>So, let’s start by understanding what Git actually is and how it works. Simply put, Git is a powerful tool that constantly keeps track of every change you make to your files - and I mean <em>literally</em> all the time. Day or night, 365 days a year, Git records what changed, when it changed, who changed it, and even where it happened.</p>
<p>Now, what kind of files are we talking about? Almost any kind – not just code. It could be an image, a text file, JavaScript, PHP, Python, or even a video. No matter what you’re working on, Git tracks every single change. Pretty amazing, isn’t it?</p>
<p>But here’s the best part: the magic of Git doesn’t stop there. The coolest thing about Git is that it saves different versions of your files. Imagine you wrote some code and then made a few changes to it after a few days. Now you want to make sure the old version doesn’t get lost. That’s exactly where Git comes to the rescue. It lets you keep multiple versions of the same file effortlessly, and whenever you want, you can roll back to any previous version in just a moment.</p>
<p>By the end of this handbook, you’ll not only understand what Git and GitHub are, but you’ll also be able to use them confidently in real projects. You’ll learn how Git tracks changes locally, how repositories work, how to move changes through Git’s workflow, and how to collaborate with others using GitHub.</p>
<p>We’ll start from the very basics and gradually move toward more advanced concepts like branching, merging, resolving conflicts, and safely undoing mistakes – all with hands-on examples you can follow along with.</p>
<h2 id="heading-heres-what-well-cover">Here’s What We’ll Cover:</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-git">What is Git</a>?</p>
<ul>
<li><a class="post-section-overview" href="#heading-git-vs-github">Git vs GitHub</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-git-architecture-local-and-remote">Git Architecture – Local and Remote</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-install-git-on-your-computer">How to Install Git on Your Computer</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-a-project-and-install-a-local-repository">How to Create a Project and Initialize a Local Repository</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-a-remote-repository-on-github-and-clone-it">How to Create a Remote Repository on GitHub and Clone It</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-track-changes-with-git-status">How to Track Changes with <code>git status</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-move-changes-to-the-staging-agrea-with-git-add">How to Move Changes to the Staging Area with <code>git add</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-save-work-permanently-with-git-commit-and-configure-git">How to Save Work Permanently with <code>git commit</code> (and Configure Git)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-delete-and-restore-files-with-git-rm-and-git-reset">How to Delete and Restore Files with <code>git rm</code> and <code>git reset</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-view-commit-history-with-git-log">How to View Commit History with <code>git log</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-branching-and-merging-in-git">Branching and Merging in Git</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-understanding-merge-conflicts">Understanding Merge Conflicts</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-navigate-history-with-git-checkout-and-compare-commits-with-git-diff">How to Navigate History with <code>git checkout</code> and Compare Commits with <code>git diff</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-work-with-remotes-git-push-git-fetch-and-git-pull">How to Work with Remotes: <code>git push</code>, <code>git fetch</code>, and <code>git pull</code></a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-example-git-push">Example – <code>git push</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-example-pushing-other-branches">Example – Pushing Other Branches</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-example-git-fetch">Example – <code>git fetch</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-example-git-pull">Example – <code>git pull</code></a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-undo-local-changes-with-git-restore">How to Undo Local Changes with <code>git restore</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-temporarily-shelve-staged-work-with-git-stash">How to Temporarily Shelve Work with <code>git stash</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-undo-commits-safely-with-git-revert">How to Undo Commits Safely with <code>git revert</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-keep-history-clean-with-git-rebase">How to Keep History Clean with <code>git rebase</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-collaborate-on-github-with-pull-requests">How to Collaborate on GitHub with Pull Requests</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-git-amp-github-concise-summary">Git &amp; GitHub – Concise Summary</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-git-vs-github">Git vs GitHub</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-core-git-architecture-amp-workflow">Core Git Architecture &amp; Workflow</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-getting-started">Getting Started</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-seeing-amp-moving-changes">Seeing &amp; Moving Changes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deleting-restoring-amp-undoing">Deleting, Restoring &amp; Undoing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-branching-merging-amp-conflicts">Branching, Merging &amp; Conflicts</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-navigating-amp-comparing-history">Navigating &amp; Comparing History</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-working-with-remotes-github">Working with Remotes (GitHub)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-temporarily-shelving-work-with-git-stash-1">Temporarily Shelving Work with git stash</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-keeping-history-clean-with-git-rebase-1">Keeping History Clean with git rebase</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-collaboration-with-pull-requests-prs">Collaboration with Pull Requests (PRs)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-big-picture-takeaways">Big Picture Takeaways</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-final-words">Final Words</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p><strong>Basic File &amp; Folder Navigation</strong> – Using Finder / File Explorer.</p>
</li>
<li><p><strong>CLI Usage</strong> – Running simple commands in Terminal or Command Prompt.</p>
</li>
<li><p><strong>Text Editor</strong> – Any editor for opening and editing files.</p>
</li>
</ul>
<p>I’ve also created a video to go along with this article. If you’re the type who likes to learn from video as well as text, you can check it out below:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/wNrbaAGE2PY" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<h2 id="heading-what-is-git">What is Git?</h2>
<p>Git is most commonly used in coding projects, but its power goes far beyond just code. You can use Git to keep track of changes and maintain different versions of almost any file. This means you never have to worry about losing a file or accidentally overwriting something important.</p>
<p>Let’s look at a real-life example. Suppose you’re working on a project. You’ve spent hours writing code, your client loves it, and everything’s going great. Then, a month later, the client asks for some new changes. You make the updates as requested. But after a few days, the client comes back and says, “Actually, the old version was better.” Now you’re in trouble, right? Because you’ve already overwritten the original code. How do you get it back?</p>
<p>That’s exactly the kind of problem Git solves. This is why Git is known as a Version Control System – it keeps every version of your files or code safely stored, so you can go back to any previous state whenever you need to, without losing a thing.</p>
<p>Git was created by Linus Torvalds – the same brilliant mind behind Linux. And honestly, what he built is nothing short of genius. Most tools programmers use have a short lifespan, but Git is one of those rare ones that, once you learn it, stays useful for the rest of your career.</p>
<p>The best part? The basics of Git aren’t that hard to learn at all. It’s built around a few simple commands and concepts, and once you understand those, the whole system starts to make perfect sense.</p>
<p>Still, for beginners, Git can feel a little confusing at first – and that’s exactly why this handbook exists. Think of it not just as a tutorial, but as a complete learning resource for learning Git inside and out. We’ll cover all the essential topics you’ll actually use in real-world projects. And if you want to jump to a specific topic, you can easily find it by using the section headings in the table of contents after the intro.</p>
<p>If you follow this tutorial closely, you’ll be able to understand every part of Git, step by step.</p>
<h3 id="heading-git-vs-github">Git vs GitHub</h3>
<p>Now, let’s move on to another important topic: the difference between Git and GitHub. Git is a tool that runs locally on your own computer. It tracks all the changes you make to your files and keeps everything organized.</p>
<p>But imagine this: you and your teammate are working on the same project. You’re coding on your own computer, and your teammate is working from another one. That means you both have different versions of the same project, right? And if your team has more members, there will be even more versions – each saved separately on their own machines.</p>
<p>This is exactly where GitHub comes in. When the project is complete, you’ll need to combine everyone’s work into one place to merge all those changes together. For that, you need a central hub where everyone can upload their updates. You push your work there, your teammate pushes theirs, and GitHub brings it all together.</p>
<p>GitHub acts as that central online server where your team’s entire project lives, making it easy for everyone to see, edit, and share updates in one place, without any confusion.</p>
<p>But GitHub isn’t the only place where you can host your Git repositories. There are other popular platforms too, like GitLab and Bitbucket. These are also widely used and trusted by developers around the world. Still, among all of them, GitHub remains the most popular and widely adopted platform.</p>
<p>That’s why in this tutorial, our focus will mainly be on GitHub. It’s now owned by Microsoft and is maintained with great care and attention. Especially in the programming community and in the world of open-source projects, GitHub’s contribution is truly remarkable.</p>
<p>So without wasting any more time, let’s dive in and explore how Git and GitHub actually work – and see some real-world use cases that will help you understand their power in action.</p>
<h2 id="heading-git-architecture-local-and-remote">Git Architecture – Local and Remote</h2>
<p>Before you start working with Git, the most important thing is to understand its core concept: how it actually works and what its internal structure, or architecture, looks like.</p>
<p>Git is mainly divided into two major parts, <strong>Local</strong> and <strong>Remote</strong>.</p>
<ul>
<li><p>The <strong>Local</strong> part refers to your own computer, where you do all your work. This is where your files, code, and every change you make are stored.</p>
</li>
<li><p>The <strong>Remote</strong> part, on the other hand, lives in the cloud. It’s where you push or upload your local work. In most cases, when you say “remote,” you’re usually referring to GitHub.</p>
</li>
</ul>
<p>Now, let’s start with how the local part works. On your computer, the folder where you’re working on your project is called the <strong>Working Directory</strong>. This is where all the action happens: you write code, create new files, modify existing ones, and make changes as needed. And when you feel like, “Alright, this version looks good, I want to save this change,” that’s when you move on to the next stage in Git’s workflow.</p>
<p>So, what do you do next? You move your work to the “<strong>stage</strong>.” At first, this word might sound a bit unfamiliar, but once you use it a few times, it’ll make perfect sense. In simple terms, when you finish your work in the working directory, staging means you’re saying, “Alright, my changes are ready – they can move to the next step.” This staging process is the second phase in Git’s workflow.</p>
<p>Then comes the third phase where you take the staged files and send them to the local repository. You can think of the staging area as a middle ground – a temporary space where files sit between your working directory and the final save in the repository.</p>
<p>Once you’ve reviewed everything and you’re confident the work is correct, you “commit” it. Committing means permanently saving those changes to your local <strong>repository</strong> – that is, locking them in as a recorded version of your project’s history.</p>
<p>Now you might be wondering, what exactly is a repository? Simply put, a repository is a place where all the versions of your files and their complete change history are stored. In the case of a local repository, it’s a specific folder on your own computer. For a remote repository, it lives on a cloud server, like GitHub.</p>
<p>You can think of a repository as a digital cabinet for your code. It’s a secure place where Git neatly stores every record of your work and every change you’ve ever made. Inside this repository, Git automatically creates a few system files that track everything – your changes, history, commits, and more. These files are managed entirely by Git itself, and the whole system runs based on the data stored within them.</p>
<p>So if we summarize everything so far, it goes like this:</p>
<ol>
<li><p>You work inside your local working directory.</p>
</li>
<li><p>Once you’re done, you stage your changes.</p>
</li>
<li><p>Then you commit those staged files to the local repository.</p>
</li>
</ol>
<p>Up to this point, everything happens only on your own computer – nothing has been sent to the cloud yet. You need the cloud only when you want to share your code with others, access it from another computer, or keep it safely backed up.</p>
<p>That’s when you “push” your local repository to the remote repository. In other words, you upload it to GitHub. Think about it like this: just as you use Google Drive or OneDrive to store files, photos, or documents, you could technically keep everything only on your own device. But you store them in the cloud so that you can access them from anywhere, and even if something gets deleted locally, it stays safe online.</p>
<p>GitHub works the same way. It’s your cloud backup for code. So you see, the main purpose of this whole process is to make collaboration and remote access possible. Having a remote means you can easily send your code from the local repository to a cloud server, and if needed, pull that same code back to any other machine.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765442130301/fa1908c2-c2ef-4a07-86dc-1d7d12b13a21.gif" alt="Git Architecture" width="1138" height="640" loading="lazy"></p>
<p>That’s the core idea of Git, and the foundation of the entire system rests on this simple concept. Beyond that, there’s nothing overly complex. Once you clearly understand this basic workflow, everything else in Git will feel much easier to grasp.</p>
<h2 id="heading-how-to-install-git-on-your-computer">How to Install Git on Your Computer</h2>
<p>Now, let’s see how to get started using Git from scratch.</p>
<p>The very first step before working with Git is installing it on your computer. It might already be installed on your machine, so just check first.</p>
<p>If you do need to install Git, you can download it directly from the <a target="_blank" href="https://git-scm.com/downloads">official website</a>. There, you’ll find different versions for the three major operating systems – Windows, macOS, and Linux. Simply choose the one that matches your system, and you’ll get clear download instructions right there.</p>
<p>If you’re on Windows, click on the Windows option, and you’ll see two download choices: one for 32-bit and another for 64-bit systems. Just pick the one that matches your setup, download it, and run the installer.</p>
<p>For Mac users, when you select the macOS option, you’ll also find instructions on how to install Git using <a target="_blank" href="https://brew.sh/">Homebrew</a>. Just follow those steps, and your installation will be done in no time.</p>
<p>And for those using Linux or other Unix-based systems, you can follow the installation guide provided on the site according to your distribution. The entire process is simple and straightforward.</p>
<p>Once you have Git installed, the next step is to open your terminal or command prompt. If you’re using a Mac, open the Terminal app. For Windows users, you can use either Command Prompt or PowerShell – both will work just fine.</p>
<p>But after installing Git, you’ll usually get a separate terminal called <strong>Git Bash</strong>. You can use that too if you prefer. Many developers like working in Git Bash because it feels very similar to a Linux terminal and makes running Git commands easy and intuitive.</p>
<p>On Windows, you can open Git Bash by right-clicking anywhere inside a folder and selecting <strong>“Git Bash Here”.</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765568453563/43d574fe-ba17-4a8a-b7ae-ec0fa71d84eb.jpeg" alt="Open Git Bash from inside any folder" class="image--center mx-auto" width="916" height="638" loading="lazy"></p>
<p>You can also open it by searching for <strong>Git Bash</strong> from the Start menu.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765568550303/8e54b211-475b-4774-bbf6-6b76674d8fcf.png" alt="Opening Git Bash from Start Menu" class="image--center mx-auto" width="1024" height="576" loading="lazy"></p>
<p>When it opens, you’ll see a terminal window that looks very similar to a Linux or macOS terminal, with a dark background and a command prompt ready to accept Git commands.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765567732235/e6edfc98-778b-47be-a70e-97821fa243c1.png" alt="Git Bash Terminal" class="image--center mx-auto" width="2486" height="1510" loading="lazy"></p>
<p>For the rest of this tutorial, I’ll be using <strong>macOS with the default Terminal app</strong>, but the Git commands themselves are exactly the same on Windows (Git Bash) and Linux. If you’re using Windows or Linux, you can follow along using your terminal of choice – only the way you open the terminal may differ slightly.</p>
<p>So now open your terminal. The first thing you need to do is make sure Git is properly installed, right? To check that, type a simple command in the terminal:</p>
<pre><code class="lang-bash">git --version
</code></pre>
<p>Then press Enter. As soon as you do that, you’ll see an output on the terminal showing the version of Git installed on your machine. Keep in mind that the version number might not be the same for everyone – it depends on when you installed Git and which update you’re using.</p>
<p>If Git is installed correctly, you’ll see this version output. But if it isn’t installed, you’ll get an error message instead. Hopefully, Git is now properly set up on your machine, and you’re ready to start using it.</p>
<h2 id="heading-how-to-create-a-project-and-initialize-a-local-repository">How to Create a Project and Initialize a Local Repository</h2>
<p>Now it’s time to get hands-on and do some practical work. As I mentioned earlier, Git can be implemented in any file or folder, right? So first, you need to create a few files and folders to work with.</p>
<p>Start by navigating to the desktop using the terminal:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ~/Desktop
</code></pre>
<p>Here, <code>cd</code> is a terminal command that stands for “change directory.” It simply means you’re moving from one folder to another. Hopefully, you’re already familiar with some basic terminal commands like <code>cd</code>, <code>pwd</code>, <code>touch</code>, and <code>mkdir</code>. If not, you can easily learn them using Google or tools like ChatGPT. They’re simple to understand.</p>
<p>Just remember, for Windows users, some of these commands might be slightly different. And keep in mind, commands like <code>cd</code>, <code>pwd</code>, <code>touch</code>, and <code>mkdir</code> are general terminal commands – not Git commands.</p>
<p>Now that you’re inside the Desktop directory, create a new folder where you’ll keep all your project files. Name it <code>git-one</code>:</p>
<pre><code class="lang-bash">mkdir git-one
</code></pre>
<p>Press Enter. You’ve now created a new folder named <code>git-one</code> inside the Desktop directory. Move into that folder like this:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> git-one
</code></pre>
<p>You’re now inside the <code>git-one</code> folder, and this is where your Git project begins. Inside the <code>git-one</code> folder, create two files:</p>
<pre><code class="lang-bash">touch one.txt
touch two.txt
</code></pre>
<p>Next, create another folder named <code>myfolder</code>:</p>
<pre><code class="lang-bash">mkdir myfolder
</code></pre>
<p>Then use <code>cd myfolder</code> to enter the folder. Inside this folder, create another file:</p>
<pre><code class="lang-bash">touch three.txt
</code></pre>
<p>Now check everything you’ve created in your file system. On macOS, for example, you can open the <code>git-one</code> folder in Finder by running:</p>
<pre><code class="lang-bash">open .
</code></pre>
<p>Here, the dot means “the current folder.” As soon as you press Enter, Finder opens up. Inside the <code>git-one</code> folder, you’ll see two files, <code>one.txt</code> and <code>two.txt</code>, and a folder named <code>myfolder</code>, which contains the <code>three.txt</code> file.</p>
<p>Now write something inside each of the files. Open <code>one.txt</code> in a text editor, write <code>one</code>, and save it. Then open <code>two.txt</code>, write <code>two</code>, and save that as well. Finally, go inside the <code>myfolder</code> directory, open <code>three.txt</code>, write <code>three</code>, and save it. Now all three files contain some content.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765442159140/ff01dfea-fe75-4b27-9a91-8133cd538f1f.gif" alt="Finder Open Git One Folder" width="1138" height="640" loading="lazy"></p>
<p>This <code>git-one</code> folder is your <strong>working directory</strong>. As mentioned earlier, this is the folder where you’re doing all your work, and where all your files are stored.</p>
<p>If you want Git to start monitoring everything inside this <code>git-one</code> folder, you have to tell Git, “This is my project folder. Track all the changes here.” To do this, go back to the terminal, make sure you’re inside the <code>git-one</code> folder, and type this:</p>
<pre><code class="lang-bash">git init
</code></pre>
<p>The word <code>init</code> means “initialize.” In other words, you’re telling Git to start working inside this folder from now on. Once you run the command, it shows a message: “Initialized empty Git repository.” That means Git has now started tracking this folder.</p>
<p>So how do you know that? Since you’re inside the <code>git-one</code> folder, type this:</p>
<pre><code class="lang-bash">ls
</code></pre>
<p>You’ll see all the files and folders inside it. Now type:</p>
<pre><code class="lang-bash">ls -la
</code></pre>
<p>Notice that now you can also see a hidden directory named <code>.git</code>. The <code>ls -la</code> command lets you view hidden files and folders.</p>
<p>Operating systems hide certain files and folders by default to protect users from accidentally modifying or deleting critical system data. These hidden files usually store configuration, metadata, or internal information that regular users don’t need to touch. For example, you might see hidden files like <code>.env</code>, <code>.DS_Store</code>, or <code>.config</code> in different projects.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765442184486/d3a47514-cf80-4401-ae98-cb96b8924464.png" alt="Git Init and Hidden .git Folder" width="2560" height="1440" loading="lazy"></p>
<p>You can see a hidden folder named <code>.git</code> inside your project directory. Who created it? Git did. This <code>.git</code> folder is actually the core of the entire project. It’s where Git keeps all its internal data, such as which files have changed, who made the changes, and what the previous versions were. In short, everything Git does is stored right here.</p>
<p>The <code>.git</code> folder is hidden for the same reason: it contains Git’s internal database and configuration. You normally never edit anything inside <code>.git</code> manually. If this folder is deleted or corrupted, Git loses the entire history of the project. Hiding it helps prevent accidental damage.</p>
<p>From now on, Git will be able to track every change you make in your work. Let’s say you make some changes to the three files you created earlier. That means you now have a <strong>local Git repository</strong>. By running <code>git init</code>, Git created the hidden <code>.git</code> folder and started tracking this directory as a repository. From this point on, any changes you make to files inside this folder can be staged, committed, and recorded in Git’s history. Even though you haven’t made a commit yet, the repository already exists locally and is ready to track your work.</p>
<h2 id="heading-how-to-create-a-remote-repository-on-github-and-clone-it">How to Create a Remote Repository on GitHub and Clone It</h2>
<p>You can also create a <strong>remote repository</strong> directly. I mentioned this earlier, so let’s see how to do it. Open your browser again and go to:</p>
<pre><code class="lang-text">https://github.com
</code></pre>
<p>If you’re new, you’ll need to create an account on GitHub first. If you’re already logged in, you’ll see your profile. On the left side, you can see your repositories, feeds, and other details. Your goal is to store your local files on GitHub.</p>
<p>To do that, you need to create a new repository on GitHub. Click the blue <strong>New</strong> button. Just like you used <code>git init</code> to create a local repository, here you can initialize one in the cloud. Give it a name – for example, <code>git-journey</code> – and in the description, write something like “You are learning Git and GitHub.” Keep it public so everyone can view it. Then click the <strong>Create Repository</strong> button. You’ve now created a new repository on GitHub.</p>
<p>Right now, the repository is completely empty – it doesn’t have any files in it. To add some files, click the <strong>Create a new file</strong> button, name it <code>one.txt</code>, and inside the file, write <code>one</code>. Then click the <strong>Commit changes</strong> button. The term “commit” basically means “save.” Committing is like saving a file – nothing complicated. Don’t worry about this too much now, as I’ll explain commits in more detail later.</p>
<p>For now, there’s already a default commit message saying “Create one.txt,” so you can keep that as it is and click <strong>Commit changes</strong>. Next, create another file the same way – this time naming it <code>two.txt</code>, writing <code>two</code> inside, and saving it. Now, if you check your GitHub repository, you can see two files there: <code>one.txt</code> and <code>two.txt</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765442212397/732bc0ff-60e4-4d43-a863-b1760453ec80.gif" alt="GitHub New Repository with Two Files" width="1138" height="640" loading="lazy"></p>
<p>At this point, you have two repositories – one on the cloud (GitHub), and another one locally on your computer. You initialized the local one yourself earlier, but now you want to bring the GitHub repository down to your machine by <strong>cloning</strong> it.</p>
<p>Go back to your terminal. Suppose you’re currently inside the <code>git-one</code> folder, but you want to clone the remote repository onto your Desktop. In the terminal, type:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ../
</code></pre>
<p>This command takes you back to the Desktop. Now, you’re going to clone the remote repository – the one you just created on GitHub. For that, you need a link, which is the repository’s URL. Go back to GitHub, open that repository, and click on the <strong>Code</strong> button. There, you’ll see an HTTPS link. Copy that link. Then, in the terminal, type the following:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> &lt;repository-https-link&gt;
</code></pre>
<p>Replace <code>&lt;repository-https-link&gt;</code> with the link you just copied, and press Enter. Git pulls your GitHub repository down onto your local machine.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765442236567/d4ad9633-897c-421b-979f-121c77573078.gif" alt="Cloning GitHub Repository" width="1138" height="640" loading="lazy"></p>
<p>After a few moments, the cloning process is complete. To verify whether the repository has been successfully cloned, type in the terminal:</p>
<pre><code class="lang-bash">ls
</code></pre>
<p>Remember that this command shows the list of folders in your current location. You’ll see a new folder named <code>git-journey</code>, alongside your previous <code>git-one</code> folder. Enter the <code>git-journey</code> folder like this:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> git-journey
</code></pre>
<p>You’re now inside the <code>git-journey</code> directory. If you type this:</p>
<pre><code class="lang-bash">ls
</code></pre>
<p>you can see the contents of the folder. Inside it you should see the two files, <code>one.txt</code> and <code>two.txt</code>. To see hidden files, type:</p>
<pre><code class="lang-bash">ls -a
</code></pre>
<p>You’ll find a <code>.git</code> folder here as well. That proves this is also a complete Git repository, cloned from the cloud. Earlier, you saw the same thing in your local repository, right? So, this is how you can create a Git repository in two ways – one by initializing it locally, and the other by cloning it from GitHub or any other remote server. No matter which way you create it, to start working with Git, initialization is always required.</p>
<h2 id="heading-how-to-track-changes-with-git-status">How to Track Changes with <code>git status</code></h2>
<p>Now let’s look at something new. Suppose you’re currently inside the <code>git-journey</code> folder. This is the repository you just cloned from the remote, right? If you make any changes to this repository – for example, you open the file <code>one.txt</code> in a text editor, keep everything as it was, but add the number <code>1</code> at the end and save the file – you might want to know what exactly changed, or whether Git has detected the modification.</p>
<p>To check that, type in the terminal:</p>
<pre><code class="lang-bash">git status
</code></pre>
<p>As soon as you run this command, Git immediately tells you: <code>modified - one.txt</code>. This means Git has already detected the change you made in that file.</p>
<p>Now let’s say you make a small change in <code>two.txt</code> as well – just adding a <code>2</code> at the end – and then run <code>git status</code> again. This time it shows <code>modified - two.txt</code>. That means both files have been changed, and Git has detected both modifications.</p>
<p>This is exactly how Git continuously keeps an eye on every change you make. It’s like it’s always watching over your project. At any moment, you can run the <code>git status</code> command to see which files have been modified or updated.</p>
<p>In a real project, you’ll often work with multiple files at the same time, right? In that case, <code>git status</code> gives you a clear summary of the overall situation: what has changed, which files are new, and which ones are modified.</p>
<p>So, up to this point, you’ve learned that you can initialize Git in two ways – either locally or from a remote repository. And with <code>git status</code>, you can easily check what changes have been made in your working directory.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765442263827/d48beb32-1dcf-4dc6-81df-a64adced52c1.gif" alt="Git Status Showing Modified Files" width="1138" height="640" loading="lazy"></p>
<p>Now let’s move out of the working directory into the staging area.</p>
<h2 id="heading-how-to-move-changes-to-the-staging-area-with-git-add">How to Move Changes to the Staging Area with <code>git add</code></h2>
<p>So far, you’ve only worked inside the <strong>working directory</strong>. That means you’ve created and modified files, but you haven’t yet told Git to keep those changes. Now you’re going to move them into the <strong>staging area</strong>.</p>
<p>In Git’s terminology, the process of moving changes from the working directory to the staging area is called <strong>adding</strong>. Simply put, <code>git add</code> means telling Git, “I want to keep this change.” Right now, inside the <code>git-journey</code> folder, you have two files, and both have been modified.</p>
<p>Now create another folder inside <code>git-journey</code>, named <code>myFolder</code>:</p>
<pre><code class="lang-bash">mkdir myFolder
</code></pre>
<p>Use <code>cd myFolder</code> to enter that folder. Inside <code>myFolder</code>, create a new file named <code>three.txt</code>:</p>
<pre><code class="lang-bash">touch three.txt
</code></pre>
<p>Open <code>three.txt</code> in your text editor, write <code>three</code>, and save the file. Now, you’ve made quite a few changes:</p>
<ul>
<li><p>You created a new folder.</p>
</li>
<li><p>You added a new file.</p>
</li>
<li><p>You modified some existing ones.</p>
</li>
</ul>
<p>Your entire project has gone through multiple changes. Go back to the repository’s root folder (the <code>git-journey</code> folder) by running this:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ..
</code></pre>
<p>From here, check which files have actually been changed:</p>
<pre><code class="lang-bash">git status
</code></pre>
<p>Git shows that two text files have been modified, and a new folder has been created. Git also says that the old files are “tracked,” while the new folder is “untracked.” Why is that? Because the old files came from the remote repository you cloned earlier, so Git already knows about them. But since <code>myFolder</code> is newly created, Git doesn’t recognize it yet.</p>
<p>If you want to move everything to the staging area at once, you have two options: use <code>git add --all</code> or the shorter version <code>git add -A</code>. Both commands do exactly the same thing. Try it out:</p>
<pre><code class="lang-bash">git add --all
git status
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765442889338/448d6a6e-df96-4e74-be07-13d285f3ca5f.png" alt="Git Add All and Status" width="2560" height="1440" loading="lazy"></p>
<p>Git has staged everything. All the changes you made are now “ready to commit.” You’ll learn more about commits in detail later. For now, just remember that when you use <code>git add --all</code>, Git takes every change and prepares it for the next commit. If you want to go back to the previous state – that is, remove everything from the staging area and return them to the working directory – type this:</p>
<pre><code class="lang-bash">git reset
git status
</code></pre>
<p>You’ll notice everything is back to the earlier state.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765442910152/1b8a64da-813c-4925-846f-0a0b5527aa76.gif" alt="Git Reset and Status" width="1138" height="640" loading="lazy"></p>
<p>Now try <code>git add -A</code>:</p>
<pre><code class="lang-bash">git add -A
git status
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765442932087/5eb27c66-5fee-4d87-bc11-2fee381e9288.png" alt="Git Add A and Status" width="2560" height="1440" loading="lazy"></p>
<p>You’ll see that Git has staged everything again. So whether you use <code>git add --all</code> or <code>git add -A</code>, both do the same thing – Git stages every change. Next, run:</p>
<pre><code class="lang-bash">git reset
git status
</code></pre>
<p>Everything is unstaged again. Now, in this state, if you type this:</p>
<pre><code class="lang-bash">git add .
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765442982841/bb3acfb7-b236-4230-8fd8-f48ef9c69093.png" alt="Git Add Dot and Status" width="2560" height="1440" loading="lazy"></p>
<p>at first glance it seems like it’s doing the same thing as <code>git add --all</code>, as everything appears to be staged. But there’s an important difference. Reset again:</p>
<pre><code class="lang-bash">git reset
</code></pre>
<p>Now go inside the <code>myFolder</code> directory:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> myFolder
git add .
git status
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443005193/c5d8a16e-d85d-431c-a56a-2a79326980fb.gif" alt="Git Add Dot Inside Folder and Status" width="1138" height="640" loading="lazy"></p>
<p>Git has only staged the <code>three.txt</code> file that’s inside <code>myFolder</code>. The other files in the root folder are still unstaged. This difference is really important:</p>
<ul>
<li><p><code>git add --all</code> or <code>git add -A</code> stage every change across the entire project.</p>
</li>
<li><p><code>git add .</code> stages only the changes within the <strong>current directory</strong> (and its subdirectories).</p>
</li>
</ul>
<p>Also, if there were a subfolder inside <code>myFolder</code>, using the dot would still include the files inside that subfolder too. In simple terms, the dot means “the current directory and everything inside it.”</p>
<p>So now you’ve seen three variations of the <code>git add</code> command: <code>--all</code>, <code>-A</code>, and <code>.</code> (dot). The first two work exactly the same way, while the dot version is limited to the current directory. Now run:</p>
<pre><code class="lang-bash">git reset
git status
</code></pre>
<p>to bring everything back again. Return to the root folder:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ..
</code></pre>
<p>And stage everything again:</p>
<pre><code class="lang-bash">git add --all
</code></pre>
<p>Now all files are ready to commit. At this point, imagine you make some changes directly in the working directory. For example, you delete the file <code>two.txt</code> and create a new one named <code>four.txt</code>, writing <code>four</code> inside it. That means you’ve deleted one file and added another new one.</p>
<p>Check the status:</p>
<pre><code class="lang-bash">git status
</code></pre>
<p>Git shows that under “Changes to be committed,” the previously staged files are still there. But under “Changes not staged for commit,” it now lists <code>two.txt</code> as deleted and <code>four.txt</code> as untracked. Now type:</p>
<pre><code class="lang-bash">git add *
git status
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443030396/e69e74ab-9980-48f4-bf94-dd1fe525faea.png" alt="Git Add Asterisk and Status" width="2560" height="1440" loading="lazy"></p>
<p>Git has staged the newly created <code>four.txt</code> file, but it hasn’t staged the deleted <code>two.txt</code> file. That’s why it’s important to understand that <code>git add *</code> only stages <strong>new or modified files</strong>, but <strong>not deleted ones</strong>. So to summarize:</p>
<ul>
<li><p><code>git add *</code> stages visible new/modified files, but not deletions.</p>
</li>
<li><p><code>git add .</code> stages changes in the current directory, including modifications and deletions.</p>
</li>
<li><p><code>git add -A</code> / <code>git add --all</code> stage all changes (additions, modifications, deletions) across the entire repo.</p>
</li>
</ul>
<p>Reset again:</p>
<pre><code class="lang-bash">git reset
git status
</code></pre>
<p>If you want to stage only a specific file – say <code>one.txt</code>, which has been modified – you can do that by typing:</p>
<pre><code class="lang-bash">git add one.txt
</code></pre>
<p>Similarly, if you want to stage a file inside a folder (for example the file inside <code>myFolder</code>) you can write:</p>
<pre><code class="lang-bash">git add myFolder/three.txt
</code></pre>
<p>You can also stage a single file like this:</p>
<pre><code class="lang-bash">git add two.txt
git status
</code></pre>
<p>You’ll see that only <code>two.txt</code> has been staged. You can also stage files by their extension. For example, to stage all <code>.txt</code> files:</p>
<pre><code class="lang-bash">git reset
git add *.txt
git status
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443054715/07804ce7-1389-41bf-a48a-f2e83c3c3456.gif" alt="Git Add Txt Files and Status" width="1138" height="640" loading="lazy"></p>
<p>This command stages all <code>.txt</code> files in the current directory, excluding deleted ones and excluding files inside subfolders.</p>
<p>Finally, if you want to stage <strong>everything</strong> at once, a simple and common practice is to go to the root directory and type:</p>
<pre><code class="lang-bash">git add .
git status
</code></pre>
<p>All the changes are now staged together. That means all your changes have been moved from the working directory to the staging area.</p>
<h2 id="heading-how-to-save-work-permanently-with-git-commit-and-configure-git">How to Save Work Permanently with <code>git commit</code> (and Configure Git)</h2>
<p>So far, you’ve learned how to move changes from the working directory to the staging area using the <code>git add</code> command. Now let’s see how to save those changes from the staging area to the local repository. In Git’s language, this is called a <strong>commit</strong>. It means you’re confirming and saving your changes permanently.</p>
<p>You can think of it like getting ready for a party. You don’t just walk straight out the door. First, you stand in front of a mirror and check your clothes, shoes, accessories, and hair. That’s the stage where you make adjustments – maybe you realize, “This shirt doesn’t look good,” or “Let me change these shoes.” That’s an intermediate step. You haven’t left for the party yet – you’re just making sure everything is in order. If something’s off, you can fix it right there. Once you’re satisfied and everything looks perfect, that’s when you finally leave for the party.</p>
<p>Git works in the same way. Instead of going straight from the working directory to the local repository, you first move your changes to the staging area. This is an intermediate step where you can review, adjust, or even remove changes before saving them permanently. You don’t commit directly from the working directory because the staging area gives you a chance to verify everything before finalizing. When you finally “commit,” it means you’re sure everything is correct – no more mistakes – and now the work can be saved permanently. That’s why this process is called a commit.</p>
<p>First, check your current state:</p>
<pre><code class="lang-bash">git status
</code></pre>
<p>It shows that the staging area contains some changes that are ready but not yet committed. To commit them, write this:</p>
<pre><code class="lang-bash">git commit -m <span class="hljs-string">"I have made some changes to files"</span>
</code></pre>
<p>The <code>-m</code> flag lets you add a short message describing what you changed.</p>
<p>Sometimes an error may appear the first time you try to commit. When you install Git for the first time and attempt a commit, you might see a message like this:</p>
<pre><code class="lang-text">*** Please tell me who you are
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443087625/11b0ecda-49e6-48e6-9171-4b3ec11f4478.png" alt="Git Commit Error - Please Tell Me Who You Are" width="2560" height="1440" loading="lazy"></p>
<p>This is completely normal. It’s just Git’s way of asking for your identity before recording a commit. Git needs to know who is making the changes and from which email address. This information is attached to every commit in the project history, which later helps track who made which changes.</p>
<p>Fixing this issue is simple. Git tells you exactly what to do. Run the following two commands:</p>
<pre><code class="lang-bash">git config --global user.email <span class="hljs-string">"your@email.com"</span>
git config --global user.name <span class="hljs-string">"your name"</span>
</code></pre>
<p>By running these two commands, the problem is solved. The first command sets your email address, and the second sets your name. The <code>--global</code> flag means that this configuration will apply to your entire computer - so every Git commit you make from this machine will use the same name and email.</p>
<p>If you want to make the configuration specific to a single project, you can use the <code>--local</code> flag instead. That way, the settings will apply only to that particular repository.</p>
<p>In short, when you use Git for the first time, setting up your user configuration is mandatory. It’s part of the basic Git setup you need to do before committing. Once it’s configured, Git will automatically recognize your identity for all future commits. If Git is already configured, you don’t need to set it up again.</p>
<p>Now, let’s go back to the main task of making a commit:</p>
<pre><code class="lang-bash">git commit -m <span class="hljs-string">"I have made some changes to files"</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443109452/9960deef-7e54-46f0-b0a2-328ab52a2b4a.png" alt="Git Commit Successful" width="2560" height="1440" loading="lazy"></p>
<p>Now the commit is done. The terminal shows how many files were changed, how many lines were added, and how many were deleted. You can verify everything by checking the status:</p>
<pre><code class="lang-bash">git status
</code></pre>
<p>Everything looks clean now, meaning all the changes have been successfully saved in the local repository. From now on, whenever you make new changes to any file, you’ll have to stage and commit them again, just like before.</p>
<p>One more thing about commits: you can always roll back to the previous state if needed. To do that, type the following:</p>
<pre><code class="lang-bash">git reset HEAD~
git status
</code></pre>
<p>This command will undo the last commit and bring everything back to the working directory. You’ll see that all the files have returned to the working directory, ready to be staged again. You can now modify or commit them as you wish.</p>
<p>This shows how much control Git gives you over your work. It’s just about remembering the right commands.</p>
<p>Before we move on to deleting and restoring files, go ahead and make a new commit again so you’re working from a clean state:</p>
<pre><code class="lang-bash">git add .
git commit -m <span class="hljs-string">"I have made some changes to files"</span>
</code></pre>
<h2 id="heading-how-to-delete-and-restore-files-with-git-rm-and-git-reset">How to Delete and Restore Files with <code>git rm</code> and <code>git reset</code></h2>
<p>Now the commit is done. Manually delete the <code>one.txt</code> file from your file system and then go back to the terminal. Type:</p>
<pre><code class="lang-bash">git status
</code></pre>
<p>Git shows that <code>one.txt</code> has been deleted. If you then type:</p>
<pre><code class="lang-bash">git add .
</code></pre>
<p>the deletion will also be staged. Instead of deleting a file manually and then adding it again, you can perform both steps in a single command. If you want to delete a file and stage that deletion at the same time, type:</p>
<pre><code class="lang-bash">git rm four.txt
</code></pre>
<p>Here, <code>four.txt</code> is just an example. This command deletes <code>four.txt</code> and automatically moves that change to the staging area. Once you run this and check the status:</p>
<pre><code class="lang-bash">git status
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443133023/f9d77e54-9829-4ff2-bd45-9e91b520bef0.gif" alt="Git Rm and Status" width="1138" height="640" loading="lazy"></p>
<p>you’ll notice that <code>four.txt</code> has been deleted and staged. You no longer have to perform two separate steps – deleting and adding manually. You can do both at once with this shortcut.</p>
<p>If you check in your file explorer, you’ll see that <code>four.txt</code> has disappeared. Now, roll back the deleted file using <code>git reset</code>. When you run a regular <code>git reset</code>, it only brings back the <strong>staged changes</strong>, not the deleted files. You’ll see the message “Unstaged changes after reset.” But if you check your file system, the deleted file hasn’t returned. That’s because a regular reset only brings back staging information, not the physical file. You can confirm this with:</p>
<pre><code class="lang-bash">git status
</code></pre>
<p>You’ll notice only the changes are back, and that manually deleted files are still missing. If you want to restore everything (both the changes and the deleted files) you have to run:</p>
<pre><code class="lang-bash">git reset --hard
</code></pre>
<p>Once you execute this, both your changes and the deleted files return to their previous state.</p>
<p>Now let’s explore the use of <code>git rm</code> a bit deeper. Suppose you want to remove a file. First, reset everything:</p>
<pre><code class="lang-bash">git reset --hard
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443156544/ba29878e-c5b8-48a8-8110-d7f80bc9d5be.gif" alt="Git Reset Hard" width="1138" height="640" loading="lazy"></p>
<p>Now you’re back to your original state. Edit the <code>four.txt</code> file and write <code>4</code> inside it. That means there’s now a modification in the working directory. If you try to delete it directly using:</p>
<pre><code class="lang-bash">git rm four.txt
</code></pre>
<p>the file isn’t deleted, and Git shows an error: “the following file has local modifications.” This means Git isn’t allowing the deletion because it has detected uncommitted changes in that file.</p>
<p>Before you can remove it, you either have to commit those changes or confirm that you truly want to discard them. If you’re sure you want to delete the file anyway, you can force it by using:</p>
<pre><code class="lang-bash">git rm -f four.txt
</code></pre>
<p>As soon as you run this command, the file is forcefully deleted. If you check your file explorer, you’ll see that <code>four.txt</code> is gone. Now consider another situation. Run another hard reset:</p>
<pre><code class="lang-bash">git reset --hard
</code></pre>
<p>This brings everything back again. Modify <code>four.txt</code> by writing <code>hello</code> inside it. Now, if you type:</p>
<pre><code class="lang-bash">git rm --cached four.txt
</code></pre>
<p>this removes the file from the staging area but keeps it physically in your working directory. Check it:</p>
<pre><code class="lang-bash">git status
</code></pre>
<p>You’ll see that <code>four.txt</code> has moved to the “Untracked files” section: it’s no longer staged, but the file itself still exists in the system. That’s the difference between the <code>--force</code> and <code>--cached</code> flags:</p>
<ul>
<li><p><code>git rm -f</code> completely deletes the file.</p>
</li>
<li><p><code>git rm --cached</code> removes the file from staging (and from tracking), but keeps it in your working directory.</p>
</li>
</ul>
<p>Another useful command is:</p>
<pre><code class="lang-bash">git rm -r folder
</code></pre>
<p>Here, the <code>-r</code> flag stands for “recursive.” This means if the folder contains other subfolders or files, all of them will be removed recursively. If you mention the folder name without <code>-r</code>, then only that folder will be removed, not its contents. Try this out. Reset everything again:</p>
<pre><code class="lang-bash">git reset --hard
</code></pre>
<p>Now experiment with <code>myFolder</code>:</p>
<pre><code class="lang-bash">git rm -r myFolder
git status
</code></pre>
<p>The folder is deleted from your file system as well, and <code>myFolder</code> is listed as deleted and staged automatically. Reset everything again:</p>
<pre><code class="lang-bash">git reset --hard
</code></pre>
<h2 id="heading-how-to-view-commit-history-with-git-log">How to View Commit History with <code>git log</code></h2>
<p>So far, you’ve made quite a few commits. Now you’ll learn how to view those commits. Viewing commits means checking the <strong>commit log</strong>. In the terminal, type:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">log</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443180087/c4acc03d-e2a2-424e-b6e7-30b7cec94ddd.gif" alt="Git Log Showing Commit History" width="1138" height="640" loading="lazy"></p>
<p>You’ll see the full commit history. You might see three commits, for example. Along with each one, there are details and messages that clearly describe what was done in that commit.</p>
<p>You’ll also notice long random strings which are <strong>commit IDs</strong>. Using these IDs, you can go back to previous versions later on. The first two commits might be the ones created when you made files while setting up your GitHub repository, and the last one might be when you modified some files recently.</p>
<p>If you want to view this log in a cleaner, more compact format, you can use:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">log</span> --oneline
</code></pre>
<p>Once you run it, you’ll see a short summary of each commit with just the essential information. The commit IDs are also shown in a shortened format. These shortened IDs can also be used to return to any previous version later on.</p>
<h2 id="heading-branching-and-merging-in-git">Branching and Merging in Git</h2>
<p>Now let’s move on to one of Git’s most powerful and important features: <strong>branching</strong>. At the moment, your repository has only one branch. A branch in Git is like a separate line of development where you can work independently.</p>
<p>If you check the online repository you cloned earlier, you’ll see that the default branch is called <strong>main</strong>. This is the default branch where all work begins. In recent times, Git has shifted from calling it “master” to “main,” but the idea remains the same: the main branch is your project’s central line of development.</p>
<p>To understand branching more simply, imagine you’re working in the kitchen of a large restaurant. The <strong>main branch</strong> is the main kitchen where all the dishes are prepared and served to customers. Now, if your client wants to try a new dish or recipe, you wouldn’t experiment directly in that main kitchen, because if something goes wrong, it could ruin everything.</p>
<p>Instead, you create a separate <em>test kitchen</em> where you can safely prepare and test the new dish. Once the recipe is perfected, you bring it back into the main kitchen to serve it officially.</p>
<p>Git works exactly the same way. Instead of making changes directly in the main branch, you create a separate <strong>development branch</strong> where you test and commit all your changes. Once everything is stable and verified, you merge that branch back into the main branch. This ensures that your main project stays safe, while new features can be developed and tested without breaking anything.</p>
<p>In short, branching in Git provides a secure and organized intermediate step that allows you to review, test, and manage changes before merging them into the main codebase.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443203531/fa09c9c1-c1e6-4617-bc35-8d16c7902b4d.gif" alt="Git Branching and Merging" width="1138" height="640" loading="lazy"></p>
<p>Since I’ve already mentioned the word “merge”, let’s understand what it means. <strong>Merging</strong> simply means combining the changes from two branches into one. It’s an important concept for understanding how branches work.</p>
<p>In Git, you can create multiple branches – like <code>staging</code>, <code>development</code>, <code>frontend</code>, or <code>backend</code> – and work on each of them separately. Once all the changes are finalized and tested, they’re merged back into the main branch.</p>
<p>Right now, your application has only one branch. To see how many branches you have, type:</p>
<pre><code class="lang-bash">git branch
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443230339/361868df-dc2f-4b01-8773-5a6b16fb721f.png" alt="Git Branch Command" width="2560" height="1440" loading="lazy"></p>
<p>This command shows the list of all branches. At the moment, you may have only one, and the branch you’re currently in has a star (<code>*</code>) next to it.</p>
<p>To create a new branch, type <code>git branch</code> followed by the branch name. For example:</p>
<pre><code class="lang-bash">git branch development
git branch
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443248217/e70e516a-1601-401e-a02a-6a25d020b97a.png" alt="Git Create New Branch" width="2560" height="1440" loading="lazy"></p>
<p>You’ll see two branches listed: <code>main</code> and <code>development</code>. The star next to <code>main</code> means you’re still on the main branch. You can easily switch from one branch to another.</p>
<p>When a new branch is created, it takes the exact state of the branch you were on at that moment. So, in this case, the <code>development</code> branch is an exact copy of the <code>main</code> branch. Whenever you create a new branch, it inherits the current state of the branch you’re in.</p>
<p>Now that you’ve created a new branch, switch to it:</p>
<pre><code class="lang-bash">git checkout development
git status
</code></pre>
<p>You’re now inside the <code>development</code> branch. If you check the status, everything looks clean, because the <code>development</code> branch was created with the same content as <code>main</code>. Go to your file system and create a new file named <code>three.txt</code>. Inside it, write <code>three</code>. Then, back in the terminal:</p>
<pre><code class="lang-bash">git status
git add .
git commit -m <span class="hljs-string">"I created three.txt and entered three there"</span>
</code></pre>
<p>Now your commit is done inside the <code>development</code> branch. Next, switch back to the main branch:</p>
<pre><code class="lang-bash">git checkout main
</code></pre>
<p>When you check your file system now, you’ll notice that <code>three.txt</code> is no longer there. Why? Because the changes made in the <code>development</code> branch exist only in that branch. Those changes haven’t been merged into <code>main</code> yet. The moment you switch back to <code>main</code>, Git automatically hides the changes made in <code>development</code>, showing you only what exists in the main branch.</p>
<p>This demonstrates how much control Git has over your file system. When you switch branches, Git instantly adjusts which files are visible so that you only see the changes relevant to that specific branch. There’s no duplication or conflict in your file system, as Git manages everything seamlessly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443271077/5e9a82df-941f-4692-8eb9-56da9e784085.gif" alt="Git Switch Branches and Isolated Changes" width="1138" height="640" loading="lazy"></p>
<p>In this way, you can create hundreds of branches, each containing its own set of changes, and Git will always show you a separate, isolated view for each one.</p>
<p>For any change to take effect, you must commit it. Whatever branch you’re working in, the commit must be made there. Once committed, Git understands that those changes belong exclusively to that specific branch. That’s why, when you switch back to the main branch, the changes made in the <code>development</code> branch don’t appear there.</p>
<p>Now suppose you make a new change to the <code>four.txt</code> file while you’re on the <code>main</code> branch – you add a <code>4</code> to it. Then, if you check the status:</p>
<pre><code class="lang-bash">git status
git add .
git commit -m <span class="hljs-string">"I changed four.txt and added additional 4"</span>
</code></pre>
<p>Now, both branches have changes – the <code>main</code> branch has this new commit, and the <code>development</code> branch still has its previous one. Since both branches now contain updates, you’ll need to merge the changes.</p>
<p>First, switch to the <code>development</code> branch:</p>
<pre><code class="lang-bash">git checkout development
</code></pre>
<p>Now run:</p>
<pre><code class="lang-bash">git merge main -m <span class="hljs-string">"merging main into development"</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443302835/c0510bc0-f03f-4203-9d51-b2bac312af5a.png" alt="Git Merge Main into Development" width="2560" height="1440" loading="lazy"></p>
<p>After the merge, the changes made in the <code>main</code> branch appear inside the <code>development</code> branch as well. For example, the updated <code>four.txt</code> file is now present in the <code>development</code> branch alongside the previous changes. Both branches’ updates have been combined, and the <code>development</code> branch now reflects all the latest modifications.</p>
<p>Next, switch back to the <code>main</code> branch:</p>
<pre><code class="lang-bash">git checkout main
</code></pre>
<p>The changes from the <code>development</code> branch haven’t yet appeared here. From <code>main</code>, merge everything from <code>development</code>:</p>
<pre><code class="lang-bash">git merge development -m <span class="hljs-string">"merging on main with development"</span>
</code></pre>
<p>After running this command, you’ll see that the <code>three.txt</code> file from the <code>development</code> branch now appears in <code>main</code> as well. Both branches’ changes have been successfully merged, and the merge process is complete.</p>
<h3 id="heading-understanding-merge-conflicts">Understanding Merge Conflicts</h3>
<p>Sometimes during a merge, conflicts may occur. A <strong>merge conflict</strong> happens when the same part of a file has been changed differently in two branches.</p>
<p>For example, if you modified <code>one.txt</code> in the <code>main</code> branch and someone else modified the exact same section of <code>one.txt</code> in the <code>development</code> branch, Git won’t know which version to keep.</p>
<p>In such cases, Git flags the file as having a conflict, and you have to resolve it manually by deciding which changes to keep – or by merging both versions yourself.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443321837/154fb298-dd45-496f-b1c9-cc68c6b228c2.gif" alt="Git Merge Conflict Example" width="1138" height="640" loading="lazy"></p>
<p>Right now, you’re on the <code>main</code> branch. From here, you’ll create a new branch so that <code>main</code> remains untouched while you simulate a conflict. Run:</p>
<pre><code class="lang-bash">git branch staging
git checkout staging
</code></pre>
<p>This creates a new branch named <code>staging</code>, which contains all the latest updates from <code>main</code>, and switches you to it. While working on the <code>staging</code> branch, go to <code>four.txt</code>. At the end of this file, there was the number <code>4</code>. Add <code>44</code> at the end and save the file. Now the <code>staging</code> branch has a change.</p>
<p>Run the following:</p>
<pre><code class="lang-bash">git status
git add .
git commit -m <span class="hljs-string">"changed 44"</span>
</code></pre>
<p>The <code>staging</code> branch work is done. Next, switch back to the <code>development</code> branch:</p>
<pre><code class="lang-bash">git checkout development
</code></pre>
<p>When you open <code>four.txt</code> here, you don’t see the <code>44</code> you added earlier in <code>staging</code>. So, write <code>444</code> and save the file. Then run:</p>
<pre><code class="lang-bash">git add .
git commit -m <span class="hljs-string">"added 444 on four.txt"</span>
</code></pre>
<p>The <code>development</code> branch now has its own separate change. While staying in the <code>development</code> branch, try to merge the changes from the <code>staging</code> branch:</p>
<pre><code class="lang-bash">git merge staging
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443344910/2991cf21-6397-45cb-aa96-70467a1244f8.png" alt="Git Merge Conflict Error" width="2560" height="1440" loading="lazy"></p>
<p>Git fails to merge automatically and shows an error:</p>
<pre><code class="lang-text">Automatic merge failed; fix conflicts and then commit the result.
</code></pre>
<p>This happens because the same line in <code>four.txt</code> was modified in both branches, <code>staging</code> and <code>development</code>. Git can’t decide which version should take priority, so it leaves the decision to you.</p>
<p>In this case, the developer working on the <code>development</code> branch (or whoever is managing it) has to manually resolve the conflict. When you open <code>four.txt</code> in a text editor, Git marks the conflicting section clearly, showing which part came from <code>staging</code> and which part came from <code>development</code>. You’ll see both the <code>44</code> from <code>staging</code> and the <code>444</code> from <code>development</code>. It’s now up to you to decide whether to keep one, remove one, or combine both changes.</p>
<p>While staying on the <code>development</code> branch, you decide which version to keep – for example, <code>444</code>. You’ll then need to remove all the conflict markers, save the file, and then run:</p>
<pre><code class="lang-bash">git add .
git commit -m <span class="hljs-string">"merge conflict solved"</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443365329/94fbc5e8-2cca-4423-b4ab-4f90898ea9c0.gif" alt="Git Merge Conflict Markers" width="1138" height="640" loading="lazy"></p>
<p>Now switch back to the <code>staging</code> branch:</p>
<pre><code class="lang-bash">git checkout staging
</code></pre>
<p>When you open the file system and check <code>four.txt</code>, it still shows <code>44</code>, meaning no change has been applied yet. At this point, you can choose to merge changes either into <code>staging</code> or <code>development</code> since both branches are now in sync in terms of expected final content.</p>
<p>From <code>staging</code>, run:</p>
<pre><code class="lang-bash">git merge development
</code></pre>
<p>This time, Git merges everything smoothly without any conflict because both sides now contain compatible content. When you check the status:</p>
<pre><code class="lang-bash">git status
</code></pre>
<p>it shows everything is clean. Now, if you want to bring these merged changes into the <code>main</code> branch, switch to it:</p>
<pre><code class="lang-bash">git checkout main
</code></pre>
<p>Once you’re on the <code>main</code> branch and open the file system, you may notice that <code>four.txt</code> doesn’t yet have the latest update. Merge the <code>staging</code> branch:</p>
<pre><code class="lang-bash">git merge staging
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443386891/5b346d56-073a-4e58-a34c-736e2a25ba23.gif" alt="Git Merge Staging into Main" width="1138" height="640" loading="lazy"></p>
<p>All the latest changes flow into the <code>main</code> branch. The updates from both branches now combine into the final version. That’s how the entire process of merging and resolving merge conflicts works. You’ve now seen how branching works and how to switch from one branch to another using <code>git checkout</code>. Let’s dig a bit deeper into that command.</p>
<h2 id="heading-how-to-navigate-history-with-git-checkout-and-compare-commits-with-git-diff">How to Navigate History with <code>git checkout</code> and Compare Commits with <code>git diff</code></h2>
<p>Earlier, when we talked about <code>git log</code>, I mentioned that you can go back to a previous version using a commit ID. That’s what you’ll see now – and to do that, you’ll again use <code>git checkout</code>.</p>
<p>First, check your commit history:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">log</span> --oneline
</code></pre>
<p>Suppose you have nine commits in your log. Make a small change. Open <code>one.txt</code> and write <code>hello</code> inside it. Then stage and commit:</p>
<pre><code class="lang-bash">git add .
git commit -m <span class="hljs-string">"update one.txt file"</span>
</code></pre>
<p>Next, run:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">log</span> --oneline
</code></pre>
<p>All the commits are displayed. Now let’s say you want to switch back to one of your previous commits, specifically the one named “merging main into development.” To do that, copy the commit ID of that commit and type:</p>
<pre><code class="lang-bash">git checkout &lt;that-commit-id&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443408851/61803cda-c1d2-4b19-a298-bf569e3e36f8.gif" alt="Git Checkout Previous Commit" width="1138" height="640" loading="lazy"></p>
<p>Earlier, when you switched branches, you used the branch name. This time, you’re using a commit ID instead. Remember, your commit ID will be different.</p>
<p>After running this command, the project moves from the <code>main</code> branch to the exact state it was in at that previous commit. You must ensure that all your changes on <code>main</code> are tracked and committed before doing this. If there are any untracked or uncommitted files, Git won’t allow you to checkout to a previous version.</p>
<p>Once you run the command, you’ll see that in the terminal, instead of showing <code>main</code>, it now says something like:</p>
<pre><code class="lang-text">HEAD detached at &lt;commit-id&gt;
</code></pre>
<p>That means you’ve successfully switched to that particular commit. If you open <code>one.txt</code> now, you’ll notice that the <code>hello</code> line you added earlier is no longer there. This confirms that you’ve returned to the previous version of your project.</p>
<p>If you want to go back to your latest state – meaning returning to the main branch – use:</p>
<pre><code class="lang-bash">git checkout main
</code></pre>
<p>You’ve switched back to the <code>main</code> branch, and you’re back to the most recent version of your project.</p>
<p>So now you’ve seen how to switch between commits and move from one version to another. Now we’ll explore how to compare one commit with another.</p>
<p>If you want to see the differences between your current commit and a previous commit (that is, what lines of code were added, what lines were deleted), you can do that easily using Git commands.</p>
<p>First, check your commit history again:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">log</span> --oneline
</code></pre>
<p>Suppose there are now 10 commits in total. You’ll compare the commit “update one.txt file” with the previous one “merging main into development.” For that, you’ll need the commit IDs of both.</p>
<p>Since you’ve already run <code>git log</code>, copy those two IDs. Once you have them, write the command:</p>
<pre><code class="lang-bash">git diff &lt;first-commit-id&gt; &lt;second-commit-id&gt;
</code></pre>
<p>After running this command, Git shows the exact changes made between those two commits. The terminal displays which files were changed, what was removed (usually in red), and what was added (usually in green).</p>
<p>One important detail: in <code>git diff</code>, when you place the most recent commit ID first and the older one second, Git shows differences from the perspective of the newer commit – that is, what’s newly added or removed compared to the older one. If you reverse the order (older commit ID first and newer second), Git shows the opposite perspective. Try running the command both ways a few times and you’ll easily understand how the comparison works.</p>
<p>If you’re ever inside an interactive log or diff view, you can exit by pressing <code>q</code> on your keyboard. That closes the view immediately.</p>
<h2 id="heading-how-to-work-with-remotes-git-push-git-fetch-and-git-pull">How to Work with Remotes: <code>git push</code>, <code>git fetch</code>, and <code>git pull</code></h2>
<p>So far, everything you’ve done has been inside your local repository – staging files, making commits, and storing everything locally. But the real goal is to send those updates to the remote repository, like GitHub.</p>
<p>When you send local changes to a remote repository, that process is called a <strong>push</strong>. If any changes have been made in the remote repository that you want to bring into your local repository, you can use <strong>fetch</strong>.</p>
<p>When you run <code>git fetch</code>, the remote changes are downloaded into your local repository’s memory, but they won’t appear in your working directory yet. To update your working directory and see those changes in your files, you need to run <strong>git pull</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443429569/e3d2882f-ad8e-4766-aa02-45633a983932.gif" alt="Git Push Fetch Pull" width="1138" height="640" loading="lazy"></p>
<p>In short:</p>
<ul>
<li><p><strong>push</strong> sends local changes to the remote.</p>
</li>
<li><p><strong>fetch</strong> brings remote changes into your local repository (but does not merge them into your working directory).</p>
</li>
<li><p><strong>pull</strong> fetches and merges; it updates your working directory immediately.</p>
</li>
</ul>
<p>In other words:</p>
<pre><code class="lang-text">git pull = git fetch + git merge
</code></pre>
<h3 id="heading-example-git-push">Example: <code>git push</code></h3>
<p>You’ve already made several changes in your local repository, and now you want to push them to the remote <code>main</code> branch. Since you’re currently on the <code>main</code> branch, use:</p>
<pre><code class="lang-bash">git push origin main
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443452657/0e0d3603-bc38-4ec0-9517-687dbf83c393.gif" alt="Git Push Main Branch" width="1138" height="640" loading="lazy"></p>
<p>Here, <code>origin</code> refers to the remote repository, and <code>main</code> is the branch you want to push to. After running this command, all your latest commits are sent to the remote <code>main</code> branch. If you check your repository on GitHub, you’ll see that everything is updated. But only the <code>main</code> branch is pushed – other branches aren’t uploaded yet. This means you’ve only pushed your <code>main</code> branch to the remote’s <code>main</code>.</p>
<h3 id="heading-example-pushing-other-branches">Example: Pushing Other Branches</h3>
<p>Switch to the <code>staging</code> branch:</p>
<pre><code class="lang-bash">git checkout staging
git push origin staging
</code></pre>
<p>Git creates a new branch named <code>staging</code> in the remote repository and pushes all your staging changes there. When you check GitHub again, you’ll see that the <code>staging</code> branch has been created and updated.</p>
<p>Similarly, switch to the <code>development</code> branch:</p>
<pre><code class="lang-bash">git checkout development
git push origin development
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443476135/9243730d-5d93-48aa-9d32-27ced9d8a762.gif" alt="Git Push Other Branches" width="1138" height="640" loading="lazy"></p>
<p>The <code>development</code> branch is also uploaded to the remote. Now all three branches (<code>main</code>, <code>staging</code>, and <code>development</code>) are fully synced with the remote repository.</p>
<p>Finally, switch back to the <code>main</code> branch again:</p>
<pre><code class="lang-bash">git checkout main
</code></pre>
<h3 id="heading-example-git-fetch">Example: <code>git fetch</code></h3>
<p>Imagine you’ve already pushed your local changes to the remote. Now let’s see how <code>git fetch</code> works.</p>
<p>Suppose you make a change directly on GitHub. For example, open the file <code>three.txt</code> on GitHub, type <code>3</code>, and commit the change. Now, the remote <code>main</code> branch has a new update that your local <code>main</code> branch doesn’t have – it’s behind the remote version. While staying on the local <code>main</code> branch, bring in the latest changes from the remote:</p>
<pre><code class="lang-bash">git fetch
</code></pre>
<p>Git fetches the new changes from the remote repository, but those updates don’t yet appear in your local file system. For example, you won’t see the update in <code>three.txt</code> until you merge the fetched changes. Once you run:</p>
<pre><code class="lang-bash">git merge
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443499165/ff4f6389-ad36-434e-a2b2-8c321160607a.gif" alt="Git Fetch and Merge" width="1138" height="640" loading="lazy"></p>
<p>the remote changes are integrated into your local working directory as well.</p>
<h3 id="heading-example-git-pull">Example: <code>git pull</code></h3>
<p>Now, suppose you edit the remote <code>three.txt</code> file again, this time adding <code>33</code> after <code>3</code> and committing it on GitHub. The remote <code>main</code> branch is now ahead again.</p>
<p>To bring in all those updates and merge them into your local <code>main</code> branch at once, use:</p>
<pre><code class="lang-bash">git pull
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443520831/0c438637-75f5-4743-a2d7-38cea3cd0d2e.gif" alt="Git Pull Command" width="1138" height="640" loading="lazy"></p>
<p><code>git pull</code> automatically performs both fetch and merge in a single command, meaning all the new remote changes, including the latest updates in <code>three.txt</code>, immediately appear in your local files.</p>
<p>You’ve now learned how to:</p>
<ul>
<li><p>push changes from local to remote (<code>git push</code>),</p>
</li>
<li><p>fetch changes from remote without merging (<code>git fetch</code>), and</p>
</li>
<li><p>pull changes from remote while merging them automatically (<code>git pull</code>).</p>
</li>
</ul>
<p>These three commands are among the most frequently used Git operations and are more than enough for most daily development work.</p>
<h2 id="heading-how-to-undo-local-changes-with-git-restore">How to Undo Local Changes with <code>git restore</code></h2>
<p>Now imagine you’re working on a project. You start developing a new feature, write a lot of new code, and modify several existing files. After some point, you realize that the approach you took isn’t working at all. But by then, you’ve already made changes across 10 to 15 different files – some with new code, some heavily modified.</p>
<p>In this situation, you want to discard everything and go back to the previous state. Would you really open each of those 10 to 15 files manually, remove all the new lines, and try to restore the old code one by one? How accurate or even possible would that be? Probably not at all.</p>
<p>In this case, the simplest answer is that it’s actually not practical manually. And the more complicated answer is that there’s no guarantee that everything will return to its exact previous state. This is exactly where the magic of the <code>git restore</code> command comes in.</p>
<p>The <code>git restore</code> command helps you revert any file or directory back to its previous state, meaning the state of the <strong>last commit</strong>. It’s mainly used to undo local uncommitted changes, or to remove changes that were added to the staging area using <code>git add</code>. Let’s test it. Suppose in <code>one.txt</code>, you write:</p>
<pre><code class="lang-text">new feature
</code></pre>
<p>This repository already had some committed history. Now you’re just trying to develop a new feature. After writing the new code, you realize that the change you made in <code>one.txt</code> (adding <code>new feature</code>) was a mistake. The previous version was fine.</p>
<p>Since this change isn’t ready to be committed, you want to undo it and go back to the last committed state. To do that, run:</p>
<pre><code class="lang-bash">git restore one.txt
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443545767/6e1bb71b-4228-40f5-bcec-23d094254355.gif" alt="Git Restore Single File" width="1138" height="640" loading="lazy"></p>
<p>As soon as you run this command, the file instantly reverts to its previous state – the exact version from the most recent commit.</p>
<p>If you want to restore an entire directory instead of a single file, type the directory name after <code>git restore</code> and it’ll bring everything in that folder back to the last committed version.</p>
<p>If you want to undo all changes across the entire repository, run:</p>
<pre><code class="lang-bash">git restore .
</code></pre>
<p>This restores every file to its last committed state. If you’ve already staged some changes using <code>git add</code>, you can still undo them using the <code>--staged</code> flag. For example:</p>
<pre><code class="lang-bash">git restore --staged fileName
<span class="hljs-comment"># or</span>
git restore --staged .
</code></pre>
<p>This removes the files from the staging area but keeps the working directory unchanged.</p>
<p>In summary, the <code>git restore</code> command helps you bring any file or directory back to its previous (last committed) state. It’s mainly used to undo local, uncommitted changes before they’re ever committed.</p>
<h2 id="heading-how-to-temporarily-shelve-work-with-git-stash">How to Temporarily Shelve Work with <code>git stash</code></h2>
<p>Now imagine this: you’re working on a new branch, developing a big feature. It’s a long process, and you’ve already finished about half of it. But the work isn’t yet in a state to be committed.</p>
<p>Suddenly, another developer messages you saying there’s a new update on a different branch and you need to check it and give feedback. How will you switch branches without losing your unfinished work? Will you throw away all your progress using the <code>git restore</code> command?</p>
<p>Of course not. There’s no reason to go through that kind of hassle when Git gives you a much smarter way to handle this: using the <code>git stash</code> command.</p>
<p>With <code>git stash</code>, you can temporarily set aside your unfinished work, switch to another branch to do something else, and later bring all your changes back with a single command.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443582135/733a4f46-6c92-4902-8c45-97cdcd55990f.gif" alt="Git Stash Command" width="1138" height="640" loading="lazy"></p>
<p>Let’s see how it works. Suppose in <code>one.txt</code>, you write:</p>
<pre><code class="lang-text">another feature
</code></pre>
<p>You’re currently on the <code>main</code> branch. Now you need to switch to the <code>development</code> branch to review a new feature. So you type:</p>
<pre><code class="lang-bash">git checkout development
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443604514/8d70a915-8cac-49c8-8776-f4db7ed37bb5.png" alt="Git Checkout Development Branch with Uncommitted Changes" width="2560" height="1440" loading="lazy"></p>
<p>But Git throws an error:</p>
<pre><code class="lang-text">error: Your local changes to the following files would be overwritten by checkout:
    one.txt
Please commit your changes or stash them before you switch branches.
Aborting
</code></pre>
<p>This happens because Git doesn’t allow switching branches when there are uncommitted changes – it’s protecting your work. But since your feature isn’t ready to be committed, this is exactly when you use <code>git stash</code>. Run:</p>
<pre><code class="lang-bash">git stash
</code></pre>
<p>The moment you execute this command, your uncommitted changes disappear from your working directory – but they’re not lost. Git has safely stored them in a temporary area called the <strong>stash</strong>. This lets you switch branches freely without affecting your unfinished work. Now, switch to the <code>development</code> branch:</p>
<pre><code class="lang-bash">git checkout development
</code></pre>
<p>You’ve now successfully switched branches. You can review the new feature, make notes, and do whatever you need to. Once you’re done, switch back to your <code>main</code> branch again:</p>
<pre><code class="lang-bash">git checkout main
</code></pre>
<p>Now, when you look around, you’ll see that your unfinished feature – the one you were working on earlier – isn’t there. It’s still saved in the stash. To bring it back, “pop” it out of the stash:</p>
<pre><code class="lang-bash">git stash pop
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443623961/17568e8b-ba41-4020-b7f2-00a9ae690a9c.gif" alt="Git Stash Use Example" width="1138" height="640" loading="lazy"></p>
<p>As soon as you run this, all your stashed changes reappear in your working directory. The <code>pop</code> command restores the most recently stashed work and simultaneously removes it from the stash list. If you’ve stashed multiple times, Git keeps them all in order – newest on top, oldest at the bottom. When you pop, it brings back the most recent one first.</p>
<p>If you want to reapply the stashed changes <strong>without removing them</strong> from the stash list (so that you can reuse them later), use the <code>apply</code> command instead:</p>
<pre><code class="lang-bash">git stash apply
</code></pre>
<p>Your changes are restored, but they also remain safely stored in the stash for future use. You can stash again:</p>
<pre><code class="lang-bash">git stash
git stash apply
</code></pre>
<p>You’ll notice it behaves almost the same as before, and the changes come back just like when you used <code>pop</code>. You already know that Git can store multiple stashes, and you can view a list of all those stashed changes. To see that list, type:</p>
<pre><code class="lang-bash">git stash list
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443645465/12d2c1f3-a529-4d36-aa55-2ff459a20b2c.gif" alt="Git Stash List Command" width="1138" height="640" loading="lazy"></p>
<p>You’ll see a list of all your saved stashes. Each stash item is marked with an identifier like <code>stash@{0}</code>, <code>stash@{1}</code>, and so on. You can use these identifiers to apply or pop specific stashes:</p>
<pre><code class="lang-bash">git stash pop stash@{0}
<span class="hljs-comment"># or</span>
git stash apply stash@{0}
</code></pre>
<p>Both commands work. The only difference is how they handle the stash afterward. To understand the difference more clearly, suppose you check your stash list:</p>
<pre><code class="lang-bash">git stash list
</code></pre>
<p>and see just one stash. If you previously used <code>git stash apply</code>, that stash item is still there. To remove a stash manually, use <code>git stash drop</code>. The <code>drop</code> command removes a specific stash from the stash list. For example:</p>
<pre><code class="lang-bash">git stash drop
git stash list
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443664000/db9707b8-d32b-451c-8bd8-a0c992ef4f78.gif" alt="Git Stash Drop Command" width="1138" height="640" loading="lazy"></p>
<p>Now the stash list is empty. Since you used <code>git stash apply</code> earlier, all the changes are still in your working directory. Confirm that with:</p>
<pre><code class="lang-bash">git status
</code></pre>
<p>The changes you applied are still there. Now stash those changes again:</p>
<pre><code class="lang-bash">git stash
git stash list
</code></pre>
<p>You’ll see one stash entry again.</p>
<p>Then run:</p>
<pre><code class="lang-bash">git stash pop
git stash list
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443683035/cdd67489-7797-4cce-a0de-a10506244460.gif" alt="Git Stash Pop Command" width="1138" height="640" loading="lazy"></p>
<p>The stashed changes are restored into your working directory, and the stash list is now empty. <code>pop</code> brings your changes back <strong>and removes</strong> them from the stash list – it’s like taking them out permanently. Stash something again:</p>
<pre><code class="lang-bash">git stash
git stash list
</code></pre>
<p>Now run:</p>
<pre><code class="lang-bash">git stash apply
git stash list
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443701166/3df82aab-14d7-461a-8ee6-320609d3fdcb.gif" alt="Git Stash Apply Command" width="1138" height="640" loading="lazy"></p>
<p>The command works. Your files are restored, just like before. But the stash is still in the list. So here’s the key difference:</p>
<ul>
<li><p><code>git stash pop</code> restores your changes <strong>and removes</strong> them from the stash list.</p>
</li>
<li><p><code>git stash apply</code> restores your changes but <strong>keeps</strong> them in the stash list for future use.</p>
</li>
</ul>
<h2 id="heading-how-to-undo-commits-safely-with-git-revert">How to Undo Commits Safely with <code>git revert</code></h2>
<p>Now let’s talk about <code>git revert</code>. The <code>git revert</code> command is used to undo the changes made in a previous commit – but instead of deleting that old commit, it creates a <strong>new</strong> one that reverses those changes. In other words, it cancels out the effects of a previous commit while keeping the project history completely clean and traceable.</p>
<p>That’s why it’s called “revert”: because it doesn’t delete any commit. Instead, it creates a new commit that brings the project back to its previous correct state. Simply put, <code>git revert</code> means fixing an old mistake without erasing it.</p>
<p>For example, imagine you accidentally added too much salt while cooking. Instead of throwing the whole dish away, you adjust the flavor by adding some extra ingredients to balance the saltiness. Similarly, <code>git revert</code> doesn’t delete the faulty commit – it corrects it through a new one.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443720093/f7506aa8-e16b-4566-a0c8-f497e195b783.gif" alt="Git Revert Command" width="1138" height="640" loading="lazy"></p>
<p>To revert something, you need its commit ID. Suppose in the file <code>three.txt</code>, you added the line:</p>
<pre><code class="lang-text">hello three
</code></pre>
<p>Then you run:</p>
<pre><code class="lang-bash">git add .
git commit -m <span class="hljs-string">"hello three"</span>
</code></pre>
<p>Now, if you check the log:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">log</span> --oneline
</code></pre>
<p>you’ll see there are 10 commits in total. You realize that the <code>"hello three"</code> commit introduced a bug and you want to remove it. Before that, copy the commit ID of <code>"hello three"</code> and then run:</p>
<pre><code class="lang-bash">git revert &lt;that-commit-id&gt;
</code></pre>
<p>After running the command, you’ll see a prompt asking for a commit message. You can type a custom message or simply keep the default one and exit (for example, by using <code>- wq</code> in Vim). Once you do that, the changes from <code>"hello three"</code> will be undone. Check the logs again:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">log</span> --oneline
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443740518/c8ef43d8-78e8-4afe-8424-5a24d0987640.gif" alt="Git Revert Specific Commit" width="1138" height="640" loading="lazy"></p>
<p>You’ll notice there’s a new commit added specifically for the revert. At this point, you might be reminded of <code>git reset</code>. The main difference between <code>git reset</code> and <code>git revert</code> is:</p>
<ul>
<li><p><code>git reset</code> can take you back to a specific commit and discard all commits after that point, <strong>without creating any new commit</strong>.</p>
</li>
<li><p><code>git revert</code> removes the changes from a specific commit by <strong>creating a new commit</strong> that reverses it.</p>
</li>
</ul>
<p>If your project is on GitHub or any remote repository, other contributors can also see the revert commit, ensuring clarity and preventing confusion.</p>
<p>Another key point is that if you check the logs after a reset, there’s no record of the removed commits. But when you use revert, you’ll clearly see an additional commit indicating that a revert took place.</p>
<h2 id="heading-how-to-keep-history-clean-with-git-rebase">How to Keep History Clean with <code>git rebase</code></h2>
<p>Now let’s talk about Git Rebase. Imagine you’re starting to work on a new feature. You create a new branch called <code>feature</code> by checking out from the <code>main</code> branch. You begin developing the new feature, making changes bit by bit, and committing your progress as you go. Meanwhile, another developer adds more updates to the <code>main</code> branch for production. Now you want to include those latest updates from <code>main</code> into your <code>feature</code> branch. What can you do?</p>
<p>There are several ways to handle this. As we’ve seen before, one option is to merge the <code>main</code> branch into your <code>feature</code> branch. If you do that, Git creates a new <strong>merge commit</strong>, and all the latest updates from <code>main</code> are merged into your <code>feature</code> branch.</p>
<p>But merging always creates an additional commit – you can see it clearly if you run <code>git log</code>. Some developers find these extra merge commits a bit messy. If you continue merging multiple times, your commit history can look cluttered.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443759550/d074c631-1e65-4ef3-a007-e2d7665eccd7.gif" alt="Git Rebase Command" width="1138" height="640" loading="lazy"></p>
<p>In such cases, a better solution is to use <strong>Git Rebase</strong>. When you perform a rebase, the base of your <code>feature</code> branch changes. That means if you rebase onto <code>main</code>, all the new commits from <code>main</code> are applied directly into your <code>feature</code> branch, and then your <code>feature</code> branch commits are reapplied on top of them. While your <code>main</code> branch’s code content remains the same, the commit history becomes much cleaner and more linear – and you can easily verify this using <code>git log</code>.</p>
<p>Let’s understand this with an example. Create a new branch called <code>feature</code> from <code>main</code>:</p>
<pre><code class="lang-bash">git branch feature
git checkout feature
</code></pre>
<p>You’re now inside the <code>feature</code> branch. Open <code>one.txt</code> and add the line:</p>
<pre><code class="lang-text">adding dark mode functionality
</code></pre>
<p>Then stage and commit:</p>
<pre><code class="lang-bash">git add .
git commit -m <span class="hljs-string">"adding dark mode functionality"</span>
</code></pre>
<p>Now switch back to the <code>main</code> branch:</p>
<pre><code class="lang-bash">git checkout main
</code></pre>
<p>You’re now in the <code>main</code> branch. Create a new file called <code>two.txt</code> and write:</p>
<pre><code class="lang-text">adding dark mode ui
</code></pre>
<p>Then stage and commit:</p>
<pre><code class="lang-bash">git add .
git commit -m <span class="hljs-string">"adding dark mode ui"</span>
</code></pre>
<p>Now the <code>main</code> branch has a new feature – “dark mode ui”. You want that same “dark mode ui” update to appear in your <code>feature</code> branch as well. Switch back to your <code>feature</code> branch:</p>
<pre><code class="lang-bash">git checkout feature
</code></pre>
<p>Your goal is to bring the latest updates from the <code>main</code> branch into the <code>feature</code> branch. But this time, we’ll do it using <strong>rebase</strong>.</p>
<p>To perform a Git rebase, you need to be on the branch you want to rebase, and then specify the branch from which you want to bring the changes. For example, if you’re currently on the <code>feature</code> branch and want to bring in the changes from <code>main</code>, run:</p>
<pre><code class="lang-bash">git rebase main
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443782936/9dbfa229-e02a-40f3-8f91-14fcbf4c858e.gif" alt="Git Rebase Main into Feature" width="1138" height="640" loading="lazy"></p>
<p>Once you run this command, the rebase completes, and the “dark mode ui” update from the <code>main</code> branch appears in your <code>feature</code> branch.</p>
<p>Here’s what happens behind the scenes when you use rebase:</p>
<ol>
<li><p>Git identifies the most recent common commit shared between your current branch (<code>feature</code>) and the branch you’re rebasing onto (<code>main</code>).</p>
</li>
<li><p>All the commits in your <code>feature</code> branch that come after that common commit are temporarily set aside.</p>
</li>
<li><p>Git applies all the new commits from the <code>main</code> branch into your <code>feature</code> branch.</p>
</li>
<li><p>Finally, it reapplies the saved commits from the <code>feature</code> branch on top of those <code>main</code> commits, one by one.</p>
</li>
</ol>
<p>The result is a clean, linear commit history that’s much tidier than what you get with merging. You can easily see this difference by running <code>git log</code>.</p>
<p>Just keep in mind that <code>git rebase</code> is powerful. So it’s not recommended to use it on <strong>public repositories</strong> or branches where multiple developers are working together. If you must use it, you should always inform your team beforehand. Otherwise, it can cause serious issues.</p>
<p>The reason is that rebase <strong>rewrites existing commit history</strong> – even the commit hashes (IDs) change. So, if someone else is working on the same branch, your rebased commits won’t match their local copies anymore, and they won’t be able to pull or sync normally.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443802090/97b145e9-7b86-4106-99fe-683ef8d5025b.gif" alt="Git Rebase Warning" width="1138" height="640" loading="lazy"></p>
<p>So before using rebase, make sure you fully understand where you’re applying it and whether it could cause problems for your collaborators. It’s perfectly safe to use rebase on your local or personal branches where only you’re working – just avoid rewriting commits that already exist on a shared remote branch.</p>
<h2 id="heading-how-to-collaborate-on-github-with-pull-requests">How to Collaborate on GitHub with Pull Requests</h2>
<p>Before wrapping up, let’s discuss one last important topic: the <strong>Pull Request</strong>. When working with Git and GitHub, the term “Pull Request,” often shortened as “PR,” comes up frequently.</p>
<p>A Pull Request is essentially a request you make to merge your changes into another branch – usually the <code>main</code> branch. It’s a way of saying, “I’ve made some changes in my branch. Please review them, and if everything looks good, merge them into the main branch.”</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443820127/5ea7b5ce-a06d-42d3-88d7-da5e818210dc.gif" alt="GitHub Pull Request" width="1138" height="640" loading="lazy"></p>
<p>In other words, you typically don’t make changes directly to someone else’s repository. When you finish your work in your own branch, you create a Pull Request to ask for permission to merge your code into the main repository. That’s what a Pull Request is.</p>
<p>Go back to your <code>git-journey</code> repository on GitHub. By now, you’ve already pushed your <code>main</code>, <code>staging</code>, and <code>development</code> branches. At the top of the GitHub page, you’ll see several tabs: <strong>Code</strong>, <strong>Issues</strong>, <strong>Pull requests</strong>, and <strong>Actions</strong>.</p>
<p>Click on the <strong>Pull requests</strong> tab. Here, you’ll see a big green button labeled <strong>New pull request</strong>. This is where you can say, “I want to merge the changes from this branch into another branch.”</p>
<p>Once you click New pull request, GitHub shows you two dropdown menus:</p>
<ul>
<li><p><strong>base</strong>: the branch where you want to merge the changes (select <code>main</code> here).</p>
</li>
<li><p><strong>compare</strong>: the branch from which you want to bring the changes (select <code>development</code>).</p>
</li>
</ul>
<p>In other words, you’re saying, “I’ve made some updates in the <code>development</code> branch, and now I want those updates to be merged into the <code>main</code> branch.” GitHub automatically shows a detailed comparison: which files have been modified, which lines have been added, and which ones have been removed, with everything clearly visible. If everything looks good, scroll down and click <strong>Create pull request</strong>.</p>
<p>Give your PR a title, such as:</p>
<pre><code class="lang-text">Merge development updates into main
</code></pre>
<p>In the description box, you can write something like:</p>
<pre><code class="lang-text">This PR adds the latest updates and fixes from development to main.
</code></pre>
<p>Then click <strong>Create pull request</strong> to submit it. Now, if you go back to the <strong>Pull requests</strong> tab, you’ll see your newly created PR:</p>
<pre><code class="lang-text">development → main
</code></pre>
<p>Click on it, and you’ll find three sections:</p>
<ul>
<li><p><strong>Conversation:</strong> where you and your teammates can discuss or comment on the changes.</p>
</li>
<li><p><strong>Commits</strong>: which lists all the commits included in this PR.</p>
</li>
<li><p><strong>Files changed</strong>: which shows exactly what changes were made in which files.</p>
</li>
</ul>
<p>If everything looks good after review, click the green <strong>Merge pull request</strong> button at the top, and then confirm by clicking <strong>Confirm merge</strong>. Once it’s done, GitHub displays a message:</p>
<pre><code class="lang-text">Pull request successfully merged and closed.
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765443840494/12e57fd1-64fb-412d-913d-599edebf1132.gif" alt="Pull Request Example" width="1138" height="640" loading="lazy"></p>
<p>That means all the updates from the <code>development</code> branch have now been successfully merged into <code>main</code>.</p>
<p>This is how Pull Requests make code management much easier. Every change gets reviewed first and then merged into <code>main</code>. As a result, the main branch stays stable, and the entire team can collaborate safely and efficiently.</p>
<h2 id="heading-git-amp-github-concise-summary">Git &amp; GitHub – Concise Summary</h2>
<h3 id="heading-git-vs-github-1">Git vs GitHub</h3>
<ul>
<li><p><strong>Git</strong>: Local version control tool that tracks every change to your files, lets you keep multiple versions, and roll back any time.</p>
</li>
<li><p><strong>GitHub</strong>: Online hosting platform for Git repositories that acts like a central “code server” so teams can share, review, and collaborate.</p>
</li>
<li><p>Other Git hosts exist (GitLab, Bitbucket), but GitHub is the most widely used, especially for open source.</p>
</li>
</ul>
<h3 id="heading-core-git-architecture-amp-workflow">Core Git Architecture &amp; Workflow</h3>
<ul>
<li><p>Work happens in the <strong>working directory</strong> (your project folder).</p>
</li>
<li><p>When changes are ready, you move them to the <strong>staging area</strong> with <code>git add</code>.</p>
</li>
<li><p>You permanently record a version in the <strong>local repository</strong> with <code>git commit</code>.</p>
</li>
<li><p>The <strong>remote repository</strong> (for example, on GitHub) is a copy of your repo stored in the cloud for backup and collaboration.</p>
</li>
</ul>
<p>Basic flow:</p>
<ol>
<li><p>Edit files in the working directory.</p>
</li>
<li><p>Stage changes: <code>git add &lt;files&gt;</code> or <code>git add .</code></p>
</li>
<li><p>Commit: <code>git commit -m "message"</code></p>
</li>
<li><p>Sync with remote: <code>git push</code> / <code>git pull</code></p>
</li>
</ol>
<h3 id="heading-getting-started">Getting Started</h3>
<ul>
<li><p>Install Git from the official site: <a target="_blank" href="https://git-scm.com/downloads"><code>https://git-scm.com/downloads</code></a>.</p>
</li>
<li><p>Check installation: <code>git --version</code>.</p>
</li>
<li><p>Configure identity (needed before first commit):</p>
<ul>
<li><p><code>git config --global</code> <a target="_blank" href="http://user.name"><code>user.name</code></a> <code>"Your Name"</code></p>
</li>
<li><p><code>git config --global</code> <a target="_blank" href="http://user.email"><code>user.email</code></a> <code>"</code><a target="_blank" href="mailto:you@example.com"><code>you@example.com</code></a><code>"</code></p>
</li>
</ul>
</li>
<li><p>Initialize a repo in a folder: <code>git init</code>.</p>
</li>
<li><p>Or clone an existing repo from GitHub: <code>git clone &lt;repo-url&gt;</code>.</p>
</li>
</ul>
<h3 id="heading-seeing-amp-moving-changes">Seeing &amp; Moving Changes</h3>
<ul>
<li><p>Check what changed and what’s staged: <code>git status</code>.</p>
</li>
<li><p>Stage changes:</p>
<ul>
<li><p><code>git add --all</code> / <code>git add -A</code>: stage everything (adds, changes, deletions).</p>
</li>
<li><p><code>git add .</code>: stage changes only in the current directory.</p>
</li>
<li><p><code>git add &lt;file&gt;</code> or <code>git add *.txt</code>: stage specific files or patterns.</p>
</li>
</ul>
</li>
<li><p>Unstage everything (but keep edits in files): <code>git reset</code>.</p>
</li>
<li><p>Save staged changes as a commit: <code>git commit -m "message"</code>.</p>
</li>
<li><p>View commit history:</p>
<ul>
<li><p>Full: <code>git log</code></p>
</li>
<li><p>Compact: <code>git log --oneline</code></p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-deleting-restoring-amp-undoing">Deleting, Restoring, &amp; Undoing</h3>
<ul>
<li><p>Delete a tracked file and stage the deletion: <code>git rm &lt;file&gt;</code>.</p>
<ul>
<li><p>Force delete changed file: <code>git rm -f &lt;file&gt;</code>.</p>
</li>
<li><p>Stop tracking but keep file: <code>git rm --cached &lt;file&gt;</code>.</p>
</li>
</ul>
</li>
<li><p>Undo staged changes only: <code>git reset</code>.</p>
</li>
<li><p>Reset everything (including working directory) to last commit: <code>git reset --hard</code>.</p>
</li>
<li><p>Restore file content back to last commit:</p>
<ul>
<li><p>Single file: <code>git restore &lt;file&gt;</code></p>
</li>
<li><p>All files: <code>git restore .</code></p>
</li>
</ul>
</li>
<li><p>Undo a <strong>commit</strong> safely (create a new “fix” commit):</p>
<ul>
<li><code>git revert &lt;commit-id&gt;</code></li>
</ul>
</li>
<li><p>Move HEAD and history back to an earlier commit (dangerous on shared branches):</p>
<ul>
<li>Example: <code>git reset HEAD~</code> (undo last commit locally).</li>
</ul>
</li>
</ul>
<h3 id="heading-branching-merging-amp-conflicts">Branching, Merging, &amp; Conflicts</h3>
<ul>
<li><p>A <strong>branch</strong> is an independent line of development (for example, <code>main</code>, <code>development</code>, <code>staging</code>, <code>feature</code>).</p>
</li>
<li><p>List branches: <code>git branch</code>.</p>
</li>
<li><p>Create branch: <code>git branch development</code>.</p>
</li>
<li><p>Switch branch: <code>git checkout development</code> (or <code>git switch development</code> in newer Git).</p>
</li>
<li><p>Typical workflow: keep <code>main</code> stable, build new features on separate branches, then merge back.</p>
</li>
<li><p>Merge another branch into the current one: <code>git merge &lt;branch-name&gt;</code>.</p>
</li>
<li><p>If the same lines changed differently in two branches, you get a <strong>merge conflict</strong>:</p>
<ul>
<li><p>Git marks conflicts in the file with <code>&lt;&lt;&lt;&lt;&lt;&lt;&lt;</code>, <code>=======</code>, <code>&gt;&gt;&gt;&gt;&gt;&gt;&gt;</code>.</p>
</li>
<li><p>You manually edit to the correct final content, then:</p>
<ul>
<li><p><code>git add .</code></p>
</li>
<li><p><code>git commit -m "merge conflict resolved"</code></p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="heading-navigating-amp-comparing-history">Navigating &amp; Comparing History</h3>
<ul>
<li><p>Jump to an old commit (read-only “detached HEAD” state): <code>git checkout &lt;commit-id&gt;</code></p>
</li>
<li><p>Return to the latest <code>main</code>: <code>git checkout main</code>.</p>
</li>
<li><p>Compare two commits: <code>git diff &lt;old-id&gt; &lt;new-id&gt;</code> – shows what changed between them.</p>
</li>
</ul>
<h3 id="heading-working-with-remotes-github">Working with Remotes (GitHub)</h3>
<ul>
<li><p>Push local branch to remote: <code>git push origin &lt;branch&gt;</code>.</p>
</li>
<li><p>Fetch latest state from remote <strong>without</strong> updating your working files: <code>git fetch</code></p>
<ul>
<li>Then merge if needed: <code>git merge origin/main</code></li>
</ul>
</li>
<li><p>Pull and merge in one step: <code>git pull</code> (equivalent to <code>fetch + merge</code>).</p>
</li>
</ul>
<h3 id="heading-temporarily-shelving-work-with-git-stash">Temporarily Shelving Work with <code>git stash</code></h3>
<ul>
<li><p>Use <code>git stash</code> when you have uncommitted work but need to switch branches.</p>
</li>
<li><p><code>git stash</code>: saves current changes and cleans your working directory.</p>
</li>
<li><p>Switch branches, do other work, then come back and restore:</p>
<ul>
<li><p><code>git stash pop</code>: restore and remove from stash list.</p>
</li>
<li><p><code>git stash apply</code>: restore but keep entry in the stash.</p>
</li>
</ul>
</li>
<li><p>See all stashes: <code>git stash list</code>.</p>
</li>
<li><p>Remove a stash explicitly: <code>git stash drop</code>.</p>
</li>
</ul>
<h3 id="heading-keeping-history-clean-with-git-rebase">Keeping History Clean with <code>git rebase</code></h3>
<ul>
<li><p><code>git rebase &lt;branch&gt;</code> replays your current branch’s commits on top of another branch, creating a cleaner, linear history.</p>
</li>
<li><p>Safe on your <strong>own</strong> local branches, but dangerous on shared/public branches because it rewrites commit history (IDs change).</p>
</li>
<li><p>Common pattern: on <code>feature</code> branch, run <code>git rebase main</code> to bring latest <code>main</code> changes under your feature commits.</p>
</li>
</ul>
<h3 id="heading-collaboration-with-pull-requests-prs">Collaboration with Pull Requests (PRs)</h3>
<ul>
<li><p>On GitHub, you usually:</p>
<ol>
<li><p>Push your feature branch.</p>
</li>
<li><p>Open a <strong>Pull Request</strong> (PR) to request merging into <code>main</code> (or another base branch).</p>
</li>
<li><p>Teammates review code, discuss in comments, and approve.</p>
</li>
<li><p>The PR is merged (and usually closed) once accepted.</p>
</li>
</ol>
</li>
<li><p>PRs keep <code>main</code> stable and make reviews and history clearer.</p>
</li>
</ul>
<h3 id="heading-big-picture-takeaways">Big Picture Takeaways</h3>
<ul>
<li><p>Git tracks every change, lets you move safely between versions, and protects you from losing work.</p>
</li>
<li><p>GitHub (or other remotes) adds backup, sharing, and collaboration.</p>
</li>
<li><p>Most day‑to‑day work is based on a small set of commands: <code>git status</code>, <code>git add</code>, <code>git commit</code>, <code>git push</code>, <code>git pull</code>, <code>git branch</code>, <code>git checkout</code>, <code>git merge</code>, <code>git log</code>.</p>
</li>
<li><p>Advanced commands like <code>git reset</code>, <code>git restore</code>, <code>git revert</code>, <code>git stash</code>, and <code>git rebase</code> give you powerful ways to undo mistakes and keep history clean.</p>
</li>
</ul>
<h2 id="heading-final-words">Final Words</h2>
<p>This tutorial on Git and GitHub has walked through the essential, day-to-day concepts in a step-by-step, practical way. It’s especially helpful for those who are just getting started. If you’ve always felt a bit intimidated or confused about using Git for the first time, this guide was created with you in mind.</p>
<p>If you found the information here valuable, feel free to share it with others who might benefit from it. I’d really appreciate your thoughts – mention me on X <a target="_blank" href="https://x.com/sumit_analyzen">@sumit_analyzen</a> or on Facebook <a target="_blank" href="https://www.facebook.com/sumit.analyzen">@sumit.analyzen</a>, <a target="_blank" href="https://www.youtube.com/@logicBaseLabs">watch</a> my coding tutorials, or simply <a target="_blank" href="https://www.linkedin.com/in/sumitanalyzen/">connect with me</a> on LinkedIn. You can also checkout my official website <a target="_blank" href="https://www.sumitsaha.me/">sumitsaha.me</a> for more details about me.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Git & GitHub Crash Course for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ Git is important for any developer. We just posted a course that will help you learn Git and GitHub from scratch with clear examples, real workflows, branching, merging, stashing, rebase, pull requests, and more. This course is great for beginners wh... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/git-and-github-crash-course-for-beginners/</link>
                <guid isPermaLink="false">6931ef1a5feb2860f3beafd6</guid>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Thu, 04 Dec 2025 20:29:14 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764880033392/03198f4c-c7c0-4ba1-a383-24c546a418cf.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Git is important for any developer.</p>
<p>We just posted a course that will help you learn Git and GitHub from scratch with clear examples, real workflows, branching, merging, stashing, rebase, pull requests, and more. This course is great for beginners who want strong foundations. Sumit Saha developed this course.</p>
<p>Here are the things you will learn in this video:</p>
<ul>
<li><p>The difference between Git and GitHub in the simplest way</p>
</li>
<li><p>How Git tracks your work using the working directory, staging area, and repository</p>
</li>
<li><p>Core Git commands: add, commit, status, log, reset, restore, rm</p>
</li>
<li><p>How to work with branches, merge updates, and fix merge conflicts</p>
</li>
<li><p>How to push, pull, and sync your code with GitHub</p>
</li>
<li><p>Using stash, revert, and rebase to handle real-world workflows</p>
</li>
<li><p>How pull requests work and why teams use them</p>
</li>
</ul>
<p>Watch the full course on the freeCodeCamp.org YouTube channel (1-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/mAFoROnOfHs" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Structure Your README File – README Template Example ]]>
                </title>
                <description>
                    <![CDATA[ As a developer who aspires to be a founder, building your first startup can be filled with excitement and ideas. The worst thing that could happen to you is jumping straight into the coding part. I was in this situation and the last thing on my mind ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-structure-your-readme-file/</link>
                <guid isPermaLink="false">690e02f392fcbf371b6e1b9d</guid>
                
                    <category>
                        <![CDATA[ Collaboration ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ startup ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Casmir Onyekani ]]>
                </dc:creator>
                <pubDate>Fri, 07 Nov 2025 14:32:19 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762523233143/4555ff83-b390-4cb2-b6de-acea129de4b1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>As a developer who aspires to be a founder, building your first startup can be filled with excitement and ideas. The worst thing that could happen to you is jumping straight into the coding part. I was in this situation and the last thing on my mind was writing a README file.</p>
<p>I thought, <em>“I’ll add it later.”</em> But “later” never came.</p>
<p>Weeks turned into months, and my once-simple idea turned into chaos. A developer who joined my project had no idea how to set it up. Even I, the founder, started forgetting why I structured certain parts of the app the way I did.</p>
<p>What was supposed to be a few months of development stretched to nearly a year. All because I ignored one small file: <strong>the README.</strong></p>
<p>In this article, you’ll learn how to structure your README file to show all the important information about your project. You can see what it’ll look like here: <a target="_blank" href="https://github.com/nuelcas/mybrandname.git">MybrandName repo</a>.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-the-readme-file-is-not-just-a-formality">The README File is Not Just a Formality</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-readme-structure">README Structure</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-mybrandname-ai-branding-assistant">MyBrandName — AI Branding Assistant</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-features">Features</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-tech-stack">Tech Stack</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-quick-start">Quick Start</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-installations">Installations</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-repository-structure">Repository Structure</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-architecture-overview">Architecture Overview</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-example-api-endpoints">Example API Endpoints</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-authentication-supabase">Authentication (Supabase)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-environment-variables">Environment Variables</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-testing">Testing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-continuous-integration-ci">Continuous Integration (CI)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-versioning-amp-changelog">Versioning &amp; Changelog</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-contributing">Contributing</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-code-of-conduct">Code of Conduct</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-deployment">Deployment</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-license">License</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-github-repository">The GitHub Repository</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-developer-checklist">Developer Checklist</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-common-pitfalls-amp-how-to-avoid-them-beginner-friendly">Common Pitfalls &amp; How to Avoid Them (Beginner-Friendly)</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-problem-hardcoding-api-keys">Problem: Hardcoding API Keys</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-problem-no-quick-start-section">Problem: No Quick Start Section</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-problem-missing-example-requests-or-screenshots">Problem: Missing Example Requests or Screenshots</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-problem-confusing-folder-structure">Problem: Confusing Folder Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-problem-forgetting-to-version-your-project">Problem: Forgetting to Version Your Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-problem-no-testing-before-deployment">Problem: No Testing Before Deployment</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-what-you-can-learn-from-this">💡 What You Can Learn from This</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-words">Final Words</a></p>
</li>
</ul>
<h2 id="heading-the-readme-file-is-not-just-a-formality">The README File is Not Just a Formality</h2>
<p>Many beginners see the README as optional—something you add just before submitting your GitHub repo. But that’s isn’t the right mindset.</p>
<p>Your README is your project’s map. It tells any developer (including your future self) where to start, how to set up the environment, and how everything connects. It saves time, reduces frustration, and turns a pile of code into a usable, understandable project.</p>
<p>If someone can clone your repository and get it running in under 10 minutes, your README did its job!</p>
<h3 id="heading-readme-structure">README Structure</h3>
<p>Your README acts like the user manual for any developer who clones your repository. It should guide a developer to:</p>
<ul>
<li><p>Clone the repo.</p>
</li>
<li><p>Install dependencies.</p>
</li>
<li><p>Configure environment variables.</p>
</li>
<li><p>Run both backend and frontend successfully.</p>
</li>
<li><p>Understand how the system works.</p>
</li>
</ul>
<p>Let me walk you through a sample README from a project called <strong>MyBrandName</strong>.</p>
<p>Here’s what the README looks like: <a target="_blank" href="https://github.com/nuelcas/mybrandname">https://github.com/nuelcas/mybrandname</a></p>
<h2 id="heading-mybrandname-ai-branding-assistant">MyBrandName — AI Branding Assistant</h2>
<p>MyBrandName is an AI-powered platform that helps startups create a complete brand identity—logos, stories, and marketing assets—in minutes.</p>
<h3 id="heading-features">Features</h3>
<ul>
<li><p><strong>AI-Powered Branding</strong> – Instantly generate logos, brand stories, and marketing assets using OpenAI.</p>
</li>
<li><p><strong>Authentication</strong> – Secure user login and registration powered by Supabase.</p>
</li>
<li><p><strong>Database</strong> – Supabase for storing users, brands, assets, and subscription data.</p>
</li>
<li><p><strong>Frontend</strong> – Responsive UI built with TypeScript, Vite, and TailwindCSS.</p>
</li>
<li><p><strong>Backend API</strong> – Node.js + Express handles AI generation, authentication, and data management.</p>
</li>
<li><p><strong>Subscription Management</strong> – Stripe integration for plan upgrades and payments.</p>
</li>
<li><p><strong>Continuous Integration (CI)</strong> – Automated testing and build workflows via GitHub Actions.</p>
</li>
<li><p><strong>Versioning &amp; Changelog</strong> – Semantic versioning with a clear project evolution record.</p>
</li>
<li><p><strong>Deployment Ready</strong> – Easily deploy frontend (Vercel) and backend (Render) with Supabase integration.</p>
</li>
</ul>
<h3 id="heading-tech-stack">Tech Stack</h3>
<ul>
<li><p><strong>Runtime:</strong> Node.js + Express.js.</p>
</li>
<li><p><strong>Language:</strong> TypeScript.</p>
</li>
<li><p><strong>Frontend:</strong> Vite + Tailwind CSS.</p>
</li>
<li><p><strong>Database &amp; Auth:</strong> Supabase (Database, Storage, Authentication).<br>  <strong>AI Service:</strong> OpenAI API (Logo, Story, and Content Generation).</p>
</li>
<li><p><strong>HTTP Client:</strong> Axios/Fetch API.</p>
</li>
<li><p><strong>CI/CD:</strong> GitHub Actions (Automated Testing &amp; Deployment).</p>
</li>
<li><p><strong>Hosting:</strong> Vercel (Frontend) + Render (Backend).</p>
</li>
</ul>
<h2 id="heading-quick-start">Quick Start</h2>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ul>
<li><p><strong>Node.js 16+</strong></p>
</li>
<li><p><strong>Supabase project</strong> (for Authentication, Database, and Storage)</p>
</li>
<li><p><strong>OpenAI API key</strong> (for AI-powered logo and content generation)</p>
</li>
<li><p><strong>Stripe account</strong> (for subscription and payment handling)</p>
</li>
</ul>
<h3 id="heading-installations">Installations</h3>
<ol>
<li>Clone the repository</li>
</ol>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/nuelcas/mybrandname.git
</code></pre>
<ol start="2">
<li>Install Dependencies</li>
</ol>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> backend &amp;&amp; npm install
<span class="hljs-built_in">cd</span> ../frontend &amp;&amp; npm install
</code></pre>
<ol start="3">
<li>Environment setup</li>
</ol>
<pre><code class="lang-bash">cp backend/.env.example backend/.env
</code></pre>
<p>Update <code>.env</code> with your configuration:</p>
<ul>
<li><p>Supabase URL and API key</p>
</li>
<li><p>OpenAI API key</p>
</li>
<li><p>Stripe API key</p>
</li>
</ul>
<ol start="4">
<li>Development</li>
</ol>
<pre><code class="lang-bash"><span class="hljs-comment"># Run backend</span>
<span class="hljs-built_in">cd</span> backend &amp;&amp; npm run dev

<span class="hljs-comment"># Run frontend</span>
<span class="hljs-built_in">cd</span> frontend &amp;&amp; npm run dev
</code></pre>
<ol start="5">
<li>Production Build</li>
</ol>
<pre><code class="lang-bash">npm run build
npm start
</code></pre>
<p>Visit: <a target="_blank" href="http://localhost:5173">http://localhost:5173</a></p>
<h2 id="heading-repository-structure">Repository Structure</h2>
<pre><code class="lang-bash">/mybrandname
├── /frontend
│   ├── /src
│   │   ├── /components        <span class="hljs-comment"># UI Components (AuthForm, Navbar, etc.)</span>
│   │   ├── /pages             <span class="hljs-comment"># App pages (Home, Dashboard, Pricing)</span>
│   │   ├── /hooks             <span class="hljs-comment"># Custom React hooks (useAuth, useLogoGenerator)</span>
│   │   ├── /lib               <span class="hljs-comment"># Config files (Supabase, API client, constants)</span>
│   │   ├── /styles            <span class="hljs-comment"># Global and component styles</span>
│   │   ├── App.tsx            <span class="hljs-comment"># Main routing setup</span>
│   │   └── main.tsx           <span class="hljs-comment"># React entry point</span>
│   ├── public/                <span class="hljs-comment"># Public assets (icons, logos)</span>
│   ├── tailwind.config.ts     <span class="hljs-comment"># Configures Tailwind CSS settings</span>
│   ├── vite.config.ts         <span class="hljs-comment"># Contains build and development settings for the Vite bundler</span>
│   └── package.json           <span class="hljs-comment"># Lists frontend project dependencies, scripts, and metadata</span>
│
├── /backend
│   ├── /src
│   │   ├── /routes            <span class="hljs-comment"># Express routes (auth, brand, assets, subscription)</span>
│   │   ├── server.ts          <span class="hljs-comment"># Main Express server entry</span>
│   │   └── config/            <span class="hljs-comment"># Environment and DB configs</span>
│   └── package.json           <span class="hljs-comment"># Lists backend project dependencies, scripts, and metadata for Node.js</span>
│
└── README.md
</code></pre>
<h3 id="heading-architecture-overview">Architecture Overview</h3>
<p><strong>Frontend</strong></p>
<ul>
<li><p>Built with TypeScript + Vite + Tailwind CSS</p>
</li>
<li><p>Connects to Supabase for authentication, backend API for AI generation, and Stripe for payments</p>
</li>
</ul>
<p><strong>Backend</strong></p>
<ul>
<li><p>Built with Node.js + Express</p>
</li>
<li><p>Handles authentication, AI content generation, and database writes via Supabase</p>
</li>
</ul>
<p><strong>Supabase Tables</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Table</strong></td><td><strong>Purpose</strong></td></tr>
</thead>
<tbody>
<tr>
<td>users</td><td>Stores user accounts</td></tr>
<tr>
<td>brands</td><td>Saves generated brand info</td></tr>
<tr>
<td>assets</td><td>Links to stored images/files</td></tr>
<tr>
<td>subscriptions</td><td>Tracks plan and payment status</td></tr>
</tbody>
</table>
</div><h3 id="heading-example-api-endpoints">Example API Endpoints</h3>
<p><strong>Auth Routes</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Endpoint</strong></td><td><strong>Method</strong></td><td><strong>Description</strong></td></tr>
</thead>
<tbody>
<tr>
<td>/api/auth/signup</td><td>POST</td><td>Register new user</td></tr>
<tr>
<td>/api/auth/login</td><td>POST</td><td>Log in user</td></tr>
</tbody>
</table>
</div><p><strong>Branding Routes</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Endpoint</strong></td><td><strong>Method</strong></td><td><strong>Description</strong></td></tr>
</thead>
<tbody>
<tr>
<td>/api/brand/logo</td><td>POST</td><td>Generate AI-powered logo</td></tr>
</tbody>
</table>
</div><p>Example Request:</p>
<pre><code class="lang-bash">POST /api/brand/logo
{
  <span class="hljs-string">"brandName"</span>: <span class="hljs-string">"NovaTech"</span>,
  <span class="hljs-string">"industry"</span>: <span class="hljs-string">"Tech"</span>,
  <span class="hljs-string">"style"</span>: <span class="hljs-string">"Modern Minimal"</span>
}
</code></pre>
<p>Example Response:</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"logoUrl"</span>: <span class="hljs-string">"https://supabase.storage/novatech-logo.png"</span>,
  <span class="hljs-string">"palette"</span>: [<span class="hljs-string">"#121212"</span>, <span class="hljs-string">"#FF005C"</span>]
}
</code></pre>
<h3 id="heading-authentication-supabase">Authentication (Supabase)</h3>
<pre><code class="lang-bash">import { createClient } from <span class="hljs-string">'@supabase/supabase-js'</span>;

const supabase = createClient(
  import.meta.env.VITE_SUPABASE_URL,
  import.meta.env.VITE_SUPABASE_KEY
);
</code></pre>
<h3 id="heading-environment-variables">Environment Variables</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Variable</strong></td><td><strong>Description</strong></td></tr>
</thead>
<tbody>
<tr>
<td>VITE_SUPABASE_URL</td><td>Supabase project URL</td></tr>
<tr>
<td>OPENAI_API_KEY</td><td>API key for AI generation</td></tr>
<tr>
<td>PORT</td><td>Backend port (default: 5000)</td></tr>
</tbody>
</table>
</div><h3 id="heading-testing">Testing</h3>
<p>Use Vitest/Jest for unit testing and Supertest for API routes.</p>
<pre><code class="lang-bash">npm run <span class="hljs-built_in">test</span>
</code></pre>
<h3 id="heading-continuous-integration-ci">Continuous Integration (CI)</h3>
<p>CI automatically runs tests when you push new code. This ensures your main branch always stays stable.</p>
<p>Example GitHub Action Workflow:</p>
<pre><code class="lang-bash">name: MyBrandName CI
on: [push, pull_request]
<span class="hljs-built_in">jobs</span>:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: |
          <span class="hljs-built_in">cd</span> backend &amp;&amp; npm ci &amp;&amp; npm run <span class="hljs-built_in">test</span>
          <span class="hljs-built_in">cd</span> ../frontend &amp;&amp; npm ci &amp;&amp; npm run build
</code></pre>
<p><strong>Tip:</strong> CI helps avoid “it works on my machine” problems.</p>
<h3 id="heading-versioning-amp-changelog">Versioning &amp; Changelog</h3>
<p>Keep a <a target="_blank" href="http://CHANGELOG.md"><code>CHANGELOG.md</code></a> file documenting updates.<br>Use <strong>Semantic Versioning (MAJOR.MINOR.PATCH)</strong>, for example,<br><code>1.1.0</code> → Added new features.</p>
<h2 id="heading-contributing">Contributing</h2>
<p>We welcome contributions from developers who want to improve <strong>MyBrandName</strong>!<br>Follow these steps to contribute effectively:</p>
<ul>
<li><p><strong>Fork the Repository</strong></p>
<ul>
<li>Click the <em>Fork</em> button on GitHub to create your own copy of the project.</li>
</ul>
</li>
<li><p><strong>Clone Your Fork</strong></p>
<ul>
<li>Run:</li>
</ul>
</li>
</ul>
<pre><code class="lang-bash">    git <span class="hljs-built_in">clone</span> https://github.com/nuelcas/mybrandname.git
</code></pre>
<ul>
<li><p><strong>Create a Feature Branch</strong></p>
<ul>
<li>Keep your changes organized:</li>
</ul>
</li>
</ul>
<pre><code class="lang-bash">    git checkout -b feat/your-feature-name
</code></pre>
<ul>
<li><p><strong>Set Up the Environment</strong></p>
<ul>
<li>Follow the setup instructions in the README to install dependencies and configure your <code>.env</code> files.</li>
</ul>
</li>
<li><p><strong>Follow Code Style and Formatting Rules</strong></p>
<ul>
<li>Ensure consistent formatting before committing:</li>
</ul>
</li>
</ul>
<pre><code class="lang-bash">    npm run lint
</code></pre>
<ul>
<li><p><strong>Use Clear Commit Messages</strong></p>
<ul>
<li><p>Follow the conventional commit style:</p>
<ul>
<li><p><code>feat:</code> – new feature</p>
</li>
<li><p><code>fix:</code> – bug fix</p>
</li>
<li><p><code>docs:</code> – documentation update</p>
</li>
<li><p><code>refactor:</code> – code restructuring</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Write or Update Tests</strong></p>
<ul>
<li><p>Use <code>Vitest</code> or <code>Jest</code> for unit testing and <code>Supertest</code> for API routes.</p>
</li>
<li><p>Run:</p>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-bash">    npm run <span class="hljs-built_in">test</span>
</code></pre>
<ul>
<li><p><strong>Document Your Changes</strong></p>
<ul>
<li>Update <a target="_blank" href="http://README.md"><code>README.md</code></a>, <a target="_blank" href="http://CHANGELOG.md"><code>CHANGELOG.md</code></a>, or <a target="_blank" href="http://CONTRIBUTING.md"><code>CONTRIBUTING.md</code></a> if needed.</li>
</ul>
</li>
<li><p><strong>Submit a Pull Request (PR)</strong></p>
<ul>
<li><p>Push your branch and open a PR with:</p>
<ul>
<li><p>A short, clear description of your changes.</p>
</li>
<li><p>Any related issue numbers (for example, “Closes #12”).</p>
</li>
<li><p>Screenshots or example outputs (if applicable).</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Participate in Code Review</strong></p>
<ul>
<li>Respond to feedback, make improvements, and help maintain project quality.</li>
</ul>
</li>
</ul>
<h3 id="heading-code-of-conduct">Code of Conduct</h3>
<p>To maintain a positive and inclusive community, all contributors are expected to:</p>
<ul>
<li><p>Be respectful, kind, and patient when interacting with others.</p>
</li>
<li><p>Welcome feedback and engage in constructive discussions.</p>
</li>
<li><p>Avoid discriminatory or offensive language.</p>
</li>
<li><p>Focus on collaboration and problem-solving rather than criticism.</p>
</li>
<li><p>Credit other contributors where due.</p>
</li>
<li><p>Report any violations or concerns to the maintainers privately.</p>
</li>
</ul>
<p>Let’s work together to make <strong>MyBrandName</strong> a project where everyone feels valued and supported. 💙</p>
<h2 id="heading-deployment">Deployment</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Component</strong></td><td><strong>Platform</strong></td><td><strong>Notes</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Frontend</td><td>Vercel/Netlify</td><td>Add env variables</td></tr>
<tr>
<td>Backend</td><td>Render/Railway</td><td>Add Supabase &amp; AI keys</td></tr>
<tr>
<td>Database</td><td>Supabase</td><td>Auth + Storage + Database</td></tr>
</tbody>
</table>
</div><h3 id="heading-license">License</h3>
<p>This project is licensed under the MIT License—see the LICENSE file for details.</p>
<h3 id="heading-the-github-repository">The GitHub Repository</h3>
<p>You can clone the GitHub repo, edit and build your app from it: <a target="_blank" href="https://github.com/nuelcas/mybrandname.git">MybrandName repo.</a></p>
<h3 id="heading-developer-checklist"><strong>Developer Checklist</strong></h3>
<p>Think of this checklist as your <em>final review</em> before sharing your app publicly:</p>
<p><strong>1. Supabase Authentication is Working</strong></p>
<ul>
<li><p>Test your login and registration flow.</p>
</li>
<li><p>Try creating a new account and logging in.</p>
</li>
<li><p>Make sure the user’s data appears correctly in the Supabase “users” table.</p>
</li>
</ul>
<p><strong>2. AI Endpoints Return Proper Results</strong></p>
<ul>
<li><p>Test your backend endpoints for AI-powered features (for example, logo generation).</p>
</li>
<li><p>Use tools like <strong>Postman</strong> to send sample requests.</p>
</li>
<li><p>Confirm that Supabase stores the generated data or files correctly.</p>
</li>
</ul>
<p><strong>3. Frontend is Responsive</strong></p>
<ul>
<li><p>Open your app on a mobile device and desktop browser.</p>
</li>
<li><p>Ensure the design adjusts properly to different screen sizes.</p>
</li>
<li><p>Check for broken buttons, misaligned text, or hidden sections.</p>
</li>
</ul>
<p><strong>4. Continuous Integration (CI) Tests Pass</strong></p>
<ul>
<li><p>If you use GitHub Actions, make sure your tests run automatically when you push code.</p>
</li>
<li><p>Fix any failed tests before merging branches.</p>
</li>
<li><p>This helps you catch bugs early.</p>
</li>
</ul>
<p><strong>5. Documentation Files Are Complete</strong></p>
<ul>
<li><p>Ensure your <strong>README</strong>, <strong>CONTRIBUTING</strong>, and <strong>CHANGELOG</strong> files are up to date.</p>
</li>
<li><p>Add setup steps, contribution guidelines, and update notes.</p>
</li>
<li><p>This makes your repo beginner-friendly and professional.</p>
</li>
</ul>
<blockquote>
<p>Run through your README’s <strong>Quick Start</strong> section as if you’re a new user.<br>If you can set up the project in less than 10 minutes, your documentation is clear enough.</p>
</blockquote>
<h2 id="heading-common-pitfalls-amp-how-to-avoid-them-beginner-friendly">Common Pitfalls &amp; How to Avoid Them (Beginner-Friendly)</h2>
<p>Here are some common mistakes new developers make and how you can prevent them:</p>
<h3 id="heading-problem-hardcoding-api-keys">Problem: Hardcoding API Keys</h3>
<p>Never store API keys directly in your code. If you push your project to GitHub, anyone can see them.</p>
<p><strong>Solution:</strong> Store them in a <code>.env</code> file and add <code>.env</code> to <code>.gitignore</code>.</p>
<h3 id="heading-problem-no-quick-start-section">Problem: No Quick Start Section</h3>
<p>If your README doesn’t explain how to install and run the app, other developers will be lost.</p>
<p><strong>Solution:</strong> Always include a <strong>Quick Start</strong> section showing installation and setup steps.</p>
<h3 id="heading-problem-missing-example-requests-or-screenshots">Problem: Missing Example Requests or Screenshots</h3>
<p>Readers want to see what your API or app does before trying it.</p>
<p><strong>Solution:</strong> Add example API requests and responses (like the <code>/api/brand/logo</code> example). You can also include screenshots of the UI.</p>
<h3 id="heading-problem-confusing-folder-structure">Problem: Confusing Folder Structure</h3>
<p>A messy project makes it hard for contributors to navigate your code.</p>
<p><strong>Solution:</strong> Explain your folder structure under “Repository Structure.” Include short descriptions of what each folder does.</p>
<h3 id="heading-problem-forgetting-to-version-your-project">Problem: Forgetting to Version Your Project</h3>
<p>If you don’t track changes, it’s hard to know what was updated or fixed.</p>
<p><strong>Solution:</strong> Use <strong>Semantic Versioning</strong> (<code>1.0.0</code>, <code>1.1.0</code>, and so on) and keep a simple <strong>CHANGELOG.md</strong> file.</p>
<h3 id="heading-problem-no-testing-before-deployment">Problem: No Testing Before Deployment</h3>
<p>Beginners often deploy without testing—and later find bugs in production.</p>
<p><strong>Solution:</strong> Run your tests locally first. Automate them using <strong>GitHub Actions</strong> so that every code change is verified.</p>
<p>By addressing these simple issues early, you’ll build reliable, professional-looking projects that others can understand and contribute to easily.</p>
<h2 id="heading-what-you-can-learn-from-this">💡 What You Can Learn from This</h2>
<p>A good README file saves you from:</p>
<ul>
<li><p>Wasting hours debugging setup issues</p>
</li>
<li><p>Confusing collaborators or testers</p>
</li>
<li><p>Forgetting your own logic months later</p>
</li>
</ul>
<p>It also makes your project look professional to employers and recruiters.</p>
<h2 id="heading-final-words">Final Words</h2>
<p>When I finally embraced writing detailed README files, everything changed. New collaborators understood my projects faster. Deployment became smoother. And most importantly—I never had to “learn the hard way” again.</p>
<p>So if you’re just starting out, take my advice: <strong>Before you write your next line of code, write your README file.</strong></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is the GitHub CLI? How to Use GitHub from the Command Line ]]>
                </title>
                <description>
                    <![CDATA[ The GitHub CLI (Command Line Interface) is a powerful tool developed by GitHub that allows developers to interact with GitHub directly from the terminal. It provides a simple way to perform many GitHub tasks without leaving the command line interface... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-github-from-the-command-line/</link>
                <guid isPermaLink="false">68d6c4873324dabb31fd7214</guid>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                    <category>
                        <![CDATA[ terminal ]]>
                    </category>
                
                    <category>
                        <![CDATA[ command line ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ AYUSH MISHRA ]]>
                </dc:creator>
                <pubDate>Fri, 26 Sep 2025 16:51:19 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758905411969/b1506cff-650a-4098-bd70-e8bb3b0bcb9a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The GitHub CLI (Command Line Interface) is a powerful tool developed by GitHub that allows developers to interact with GitHub directly from the terminal. It provides a simple way to perform many GitHub tasks without leaving the command line interface, such as managing repositories, handling pull requests and issues, working with GitHub Actions, and more.</p>
<p>In this tutorial, you’ll to learn what the GitHub CLI is, how to install and set it up, and how to use it for everyday tasks such as creating repositories, managing issues and pull requests, working with GitHub Actions, and automating tasks using custom aliases. You’ll learn how to replace some functionalities on GitHub’s web interface with quick commands in your terminal.</p>
<h2 id="heading-heres-what-well-cover"><strong>Here’s what we’ll cover:</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-overview-of-github-cli">Overview of GitHub CLI</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-key-features-of-github-cli">Key Features</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-benefits-of-using-github-cli">Benefits of Using GitHub CLI</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-installation-and-setup">Installation and Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#authenticating-with-a-github-account">Authenticating with a GitHub Account</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-navigating-the-github-cli">Navigating the GitHub CLI</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-manage-repositories-with-the-github-cli">Managing Repositories</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-managing-branches-and-pull-requests">Working with Pull Requests and Issues</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-pushing-and-pulling-changes">Pushing and Pulling Changes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-working-with-github-actions">Working with GitHub Actions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-manage-gists-with-the-github-cli">Managing Gists</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-interacting-with-releases-and-tags">Releases and Tags</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-extend-the-github-cli-with-custom-scripts-and-aliases">Custom Scripts and Aliases</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-troubleshooting-common-issues">Troubleshooting</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-overview-of-github-cli">Overview of GitHub CLI</h2>
<p>You can use the GitHub CLI to bridge the gap between GitHub's web interface and your local environment. You can perform various tasks such as creating issues, managing repositories, or even checking the status of your GitHub Actions workflows using the CLI. Using the CLI, you can perform almost all the tasks that you might complete on the GitHub website.</p>
<h3 id="heading-key-features-of-github-cli">Key Features of GitHub CLI</h3>
<ul>
<li><p><strong>Repository management:</strong> Easily create, clone, view, and manage repositories.</p>
</li>
<li><p><strong>Pull requests and issues:</strong> Manage pull requests and issues directly from the terminal, including creating, merging, and listing them.</p>
</li>
<li><p><strong>GitHub Actions:</strong> Interact with workflows and manage workflow runs.</p>
</li>
<li><p><strong>Authentication:</strong> Provides a secure way to authenticate with your GitHub account, supporting SSH keys, tokens, and OAuth.</p>
</li>
<li><p><strong>Custom scripting:</strong> Lets you create custom scripts and aliases to automate repetitive tasks and streamline development processes.</p>
</li>
</ul>
<h3 id="heading-benefits-of-using-github-cli">Benefits of Using GitHub CLI</h3>
<p>Suppose you’re working on a project, and you need to create a new issue on GitHub. Normally, you would switch to your browser, log in to GitHub, navigate to the repository, click on the “Issues” tab, and then click “New Issue.” With GitHub CLI, you can do all of this by typing a single command, without ever leaving your terminal. This makes your workflow faster and saves time.</p>
<h2 id="heading-installation-and-setup">Installation and Setup</h2>
<p>To install GitHub CLI on Windows, you can use the winget package manager. Winget is a command-line tool that allows you to install software easily.</p>
<h3 id="heading-installing-github-cli-on-windows-macos-and-linux"><strong>Installing GitHub CLI on Windows, macOS, and Linux</strong></h3>
<h3 id="heading-windows"><strong>Windows:</strong></h3>
<p>Run the command given below:</p>
<pre><code class="lang-plaintext">winget install --id GitHub.cli
</code></pre>
<ul>
<li><p><code>winget install</code><strong>:</strong> Tells Windows to install a new software package.</p>
</li>
<li><p><code>--id GitHub.cli</code><strong>:</strong> Specifies the exact package ID for GitHub CLI.</p>
</li>
</ul>
<p>After running this command, GitHub CLI will be installed on your Windows system.</p>
<h3 id="heading-macos">macOS:</h3>
<p>You can use Homebrew to install GitHub CLI on macOS. Open your terminal and run:</p>
<pre><code class="lang-plaintext">brew install gh
</code></pre>
<h3 id="heading-linux">Linux:</h3>
<p>On Linux, you can use your package manager. For example, on Ubuntu, you can run:</p>
<pre><code class="lang-plaintext">sudo apt install gh
</code></pre>
<h3 id="heading-authenticating-with-a-github-account">Authenticating with a GitHub Account</h3>
<p>After installing GitHub CLI, the next step is to authenticate it with your GitHub account.</p>
<h4 id="heading-run-authentication-command">Run Authentication Command:</h4>
<p>Type <code>gh auth login</code> in the terminal and press Enter.</p>
<pre><code class="lang-plaintext">gh auth login
</code></pre>
<p>You’ll then be prompted to select an authentication method. The recommended option is to authenticate via a web browser.</p>
<p>If you select the browser method, GitHub CLI will open a link in your default browser, where you can log in to GitHub.</p>
<h4 id="heading-complete-authentication">Complete Authentication:</h4>
<p>After logging in, the browser will confirm that the GitHub CLI is connected to your account.</p>
<p>You can verify the authentication status by running:</p>
<pre><code class="lang-plaintext">gh auth status
</code></pre>
<h2 id="heading-navigating-the-github-cli">Navigating the GitHub CLI</h2>
<p>The GitHub CLI is easy to navigate, and its command structure is intuitive.</p>
<h3 id="heading-command-structure-and-syntax">Command Structure and Syntax</h3>
<p>GitHub CLI commands follow a simple and straightforward pattern:</p>
<pre><code class="lang-plaintext">gh [command] [subcommand] [flags]
</code></pre>
<ul>
<li><p><strong>Command:</strong> The main action you want to perform (for example, repo, issue, pr).</p>
</li>
<li><p><strong>Subcommand:</strong> A specific task within the command (for example, create, list, view).</p>
</li>
<li><p><strong>Flags:</strong> Optional parameters that modify the command's behavior (for example, --title, --body).</p>
</li>
</ul>
<h3 id="heading-commonly-used-commands-and-flags">Commonly Used Commands and Flags</h3>
<p>Here are some common GitHub CLI commands:</p>
<ul>
<li><p><strong>Creating a repository:</strong> <code>gh repo create</code></p>
</li>
<li><p><strong>Listing issues:</strong> <code>gh issue list</code></p>
</li>
<li><p><strong>Creating a pull request:</strong> <code>gh pr create</code></p>
</li>
<li><p><strong>Viewing a repository's details:</strong> <code>gh repo view</code></p>
</li>
</ul>
<p>To see all available commands and options, you can always run:</p>
<pre><code class="lang-plaintext">gh help
</code></pre>
<h2 id="heading-how-to-manage-repositories-with-the-github-cli">How to Manage Repositories with the GitHub CLI</h2>
<p>Let’s go through examples of some of the commands you’ll use the most often.</p>
<h3 id="heading-creating-and-cloning-repositories">Creating and Cloning Repositories</h3>
<p>To create a new GitHub repository directly from the terminal, just use the following command:</p>
<pre><code class="lang-plaintext">gh repo create my-repo-name
</code></pre>
<p>To clone an existing repository, use the following command:</p>
<pre><code class="lang-plaintext">gh repo clone owner/repo-name
</code></pre>
<h3 id="heading-managing-branches-and-pull-requests">Managing Branches and Pull Requests</h3>
<p>GitHub CLI allows you to handle issues and pull requests (PRs) without leaving the terminal.</p>
<p>Switching branches or creating pull requests is simple. To create a new branch:</p>
<pre><code class="lang-plaintext">git checkout -b new-branch-name
</code></pre>
<p>Then, to create a pull request:</p>
<pre><code class="lang-plaintext">gh pr create --title "Your PR Title" --body "Description of your PR"
</code></pre>
<h3 id="heading-pushing-and-pulling-changes">Pushing and Pulling Changes</h3>
<p>Push your changes to GitHub with this command:</p>
<pre><code class="lang-plaintext">git push origin branch-name
</code></pre>
<p>And pull the latest changes with:</p>
<pre><code class="lang-plaintext">git pull
</code></pre>
<h3 id="heading-working-with-github-actions">Working with GitHub Actions</h3>
<p>GitHub CLI also supports GitHub Actions, allowing you to manage workflows directly from your terminal.</p>
<p>You can manually trigger workflows using the following:</p>
<pre><code class="lang-plaintext">gh workflow run workflow-name
</code></pre>
<p>And you can monitor the status of workflows with:</p>
<pre><code class="lang-plaintext">gh run list
</code></pre>
<p>To see detailed logs of a workflow, run this:</p>
<pre><code class="lang-plaintext">gh run view run-id --log
</code></pre>
<h3 id="heading-cloning-and-forking-repositories">Cloning and Forking Repositories</h3>
<p>Cloning and forking are essential tasks when working on projects from other repositories.</p>
<p>To clone a repository, use this command:</p>
<pre><code class="lang-plaintext">gh repo clone &lt;repository-name&gt;
</code></pre>
<p>To fork a repository, do this:</p>
<pre><code class="lang-plaintext">gh repo fork &lt;repository-url&gt;
</code></pre>
<h4 id="heading-example"><strong>Example:</strong></h4>
<p>Here’s what it would look like:</p>
<pre><code class="lang-plaintext">gh repo clone example-repo
</code></pre>
<pre><code class="lang-plaintext">gh repo fork https://github.com/username/repository-name
</code></pre>
<h3 id="heading-how-to-work-with-github-actions">How to Work with GitHub Actions</h3>
<p>Using the GitHub CLI, you can also manage GitHub Actions, which are automated tasks you can run in response to certain events in your repository.</p>
<h4 id="heading-triggering-and-monitoring-workflows">Triggering and Monitoring Workflows</h4>
<p>You can trigger a workflow manually like this:</p>
<pre><code class="lang-plaintext">gh workflow run &lt;workflow-name&gt;
</code></pre>
<p>And you can monitor workflow runs with this:</p>
<pre><code class="lang-plaintext">gh run list
</code></pre>
<h4 id="heading-managing-workflow-runs-and-logs">Managing Workflow Runs and Logs</h4>
<p>If you want to check the details of a specific workflow run, you can view logs directly from the CLI:</p>
<pre><code class="lang-plaintext">gh run view &lt;run-id&gt; --log
</code></pre>
<p>You can also use GitHub CLI commands to enhance your Continuous Integration/Continuous Deployment (CI/CD) pipelines, ensuring smooth automation and better control over our workflows.</p>
<h3 id="heading-how-to-update-the-github-cli">How to Update the GitHub CLI</h3>
<p>To make sure that you’re using the latest version of GitHub CLI with all the latest features and fixes, you can update it using winget.</p>
<pre><code class="lang-plaintext">winget upgrade --id GitHub.cli
</code></pre>
<ul>
<li><p><strong>winget upgrade:</strong> Checks for updates for the specified package.</p>
</li>
<li><p><strong>--id GitHub.cli:</strong> Identifies the GitHub CLI package for the upgrade.</p>
</li>
</ul>
<h2 id="heading-advanced-github-cli-features-and-integrations">Advanced GitHub CLI Features and Integrations</h2>
<p>The GitHub CLI is not only useful for performing basic tasks. You can also perform some advanced operations with its help.</p>
<h3 id="heading-how-to-manage-gists-with-the-github-cli">How to Manage Gists with the GitHub CLI</h3>
<p>Gists are a simple way to share snippets of code. You can create, list, and manage your Gists right from the CLI. Here’s how you can create a gist:</p>
<pre><code class="lang-plaintext">gh gist create my-code-snippet.py
</code></pre>
<p>To list your gists:</p>
<pre><code class="lang-plaintext">gh gist list
</code></pre>
<h3 id="heading-interacting-with-releases-and-tags">Interacting with Releases and Tags</h3>
<p>To manage releases and tags, GitHub CLI provides commands to create, list, and delete releases. Here’s an example of creating a release:</p>
<pre><code class="lang-plaintext">gh release create v1.0.0
</code></pre>
<h3 id="heading-how-to-extend-the-github-cli-with-custom-scripts-and-aliases">How to Extend the GitHub CLI with Custom Scripts and Aliases</h3>
<p>You can write your own scripts and integrate them into GitHub CLI, or create aliases for commands you use frequently to save time. Aliases let you create shortcuts for commands that you use often. For example, the command given below creates an alias <code>prlist</code> that will show all pull requests, regardless of their state:</p>
<pre><code class="lang-plaintext">gh alias set prlist "pr list --state all"
</code></pre>
<p>In the same manner, you can create a shortcut <code>co</code> to quickly check out a pull request branch without typing the full command each time. The command is given below:</p>
<pre><code class="lang-plaintext">gh alias set co "pr checkout"
</code></pre>
<h3 id="heading-troubleshooting-common-issues">Troubleshooting Common Issues</h3>
<p>If you face any issues, you can troubleshoot by checking the command syntax, ensuring your GitHub CLI is up to date, or consulting the documentation using the command:</p>
<pre><code class="lang-plaintext">gh help &lt;command&gt;
</code></pre>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>GitHub CLI is an excellent tool that helps developers work directly from the terminal. It lets you manage repositories, handle pull requests and issues, trigger and monitor GitHub Actions, and even work with Gists.</p>
<p>You can save time and improve productivity as developers using this powerful tool. Keep exploring its new features and stay updated with the latest version.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Automate API Documentation Updates with GitHub Actions and OpenAPI Specifications ]]>
                </title>
                <description>
                    <![CDATA[ Maintaining up-to-date API documentation is often one of the biggest pain points for developers and teams. Too often, the API spec changes but the docs lag behind, leaving developers with outdated or inconsistent information. This frustrates consumer... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-automate-api-documentation-updates-with-github-actions-and-openapi-specifications/</link>
                <guid isPermaLink="false">68c0398aeff4d53c8494c5ab</guid>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ github-actions ]]>
                    </category>
                
                    <category>
                        <![CDATA[ OpenApi ]]>
                    </category>
                
                    <category>
                        <![CDATA[ documentation ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ EZINNE ANNE EMILIA ]]>
                </dc:creator>
                <pubDate>Tue, 09 Sep 2025 14:28:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1757428080226/175085d0-cfea-41a0-aa52-a50ad8212980.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Maintaining up-to-date API documentation is often one of the biggest pain points for developers and teams. Too often, the API spec changes but the docs lag behind, leaving developers with outdated or inconsistent information. This frustrates consumers of your API and increases support overhead.</p>
<p>This is where automation comes in. By combining OpenAPI specifications with GitHub Actions, you can ensure your documentation is always in sync with your API changes.</p>
<ul>
<li><p><strong>OpenAPI</strong> acts as the single reference point for your API design, keeping your docs consistent, accurate, and aligned with your API.</p>
</li>
<li><p><strong>GitHub Actions</strong> automates the workflow, validating your spec, building docs, and publishing to GitHub Pages in seconds.</p>
</li>
</ul>
<p>This tutorial walks you through a working example of how to use GitHub Actions to auto-update your docs.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-your-repository">How to set up your repository</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-the-openapi-specification">How to Create the OpenAPI Specification</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-test-the-api-spec-locally">How to Test the API Spec Locally</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-install-tools">Install tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-a-landing-page">Create a landing page</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-validate-your-spec">Validate Your Spec</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-preview-in-the-browser">Preview in the Browser</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-push-local-changes-to-github">How to Push Local Changes to GitHub</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-your-github-actions-workflow">How to Set Up Your GitHub Actions Workflow</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-github-pages">How to Set Up GitHub Pages</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-github-pages">What is GitHub Pages?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-setting-up-github-pages">Setting Up GitHub Pages</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-handle-multiple-versions">How to Handle Multiple Versions</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-about-the-versions">About the Versions</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-version-1-v1">Version 1 (v1)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-version-2-v2">Version 2 (v2)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-version-3-v3">Version 3 (v3)</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-the-versions-locally">How to Set Up the Versions Locally</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-validate-the-api-specs">How to Validate the API Specs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-update-the-github-actions-workflow">How to Update the GitHub Actions Workflow</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p><a target="_blank" href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm">Node.js and npm installed.</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/gitting-things-done-book/">A GitHub account with basic Git knowledge.</a></p>
</li>
<li><p><a target="_blank" href="https://code.visualstudio.com/download">Visual Studio Code Editor</a>.</p>
</li>
<li><p><a target="_blank" href="https://idratherbewriting.com/learnapidoc/">Basic knowledge of documentation and OpenAPI</a>.</p>
</li>
</ul>
<h2 id="heading-how-to-set-up-your-repository">How to Set Up Your Repository</h2>
<p>If you don’t already have one, create a GitHub repository. For this tutorial, I’ll use <code>api-docs</code> as the repo name.</p>
<p>Then open VSCode and create a folder with the same name.</p>
<h2 id="heading-how-to-create-the-openapi-specification">How to Create the OpenAPI Specification</h2>
<p>Inside the folder you just created, create a folder called <code>spec</code> and add a file named <code>greetings.yaml</code> with the following content:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">openapi:</span> <span class="hljs-number">3.0</span><span class="hljs-number">.3</span>
<span class="hljs-attr">info:</span>
  <span class="hljs-attr">title:</span> <span class="hljs-string">Greetings</span> <span class="hljs-string">API</span>
  <span class="hljs-attr">version:</span> <span class="hljs-number">1.0</span><span class="hljs-number">.0</span>
  <span class="hljs-attr">description:</span> <span class="hljs-string">This</span> <span class="hljs-string">is</span> <span class="hljs-string">a</span> <span class="hljs-string">greetings</span> <span class="hljs-string">API</span> <span class="hljs-string">demonstrating</span> <span class="hljs-string">a</span> <span class="hljs-string">simple</span> <span class="hljs-string">greeting</span> <span class="hljs-string">endpoint</span> <span class="hljs-string">with</span> <span class="hljs-string">query</span> <span class="hljs-string">parameters</span> <span class="hljs-string">and</span> <span class="hljs-string">multilingual</span> <span class="hljs-string">support.</span>
  <span class="hljs-attr">license:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">MIT</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">https://opensource.org/licenses/MIT</span>
<span class="hljs-attr">servers:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">url:</span> <span class="hljs-string">https://api.yourdomain.com/v1</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">Production</span> <span class="hljs-string">server(v1)</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">url:</span> <span class="hljs-string">https://staging.yourdomain.com/v1</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">Staging</span> <span class="hljs-string">server(v1)</span>
<span class="hljs-attr">security:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">api_key:</span> []
<span class="hljs-attr">paths:</span>
  <span class="hljs-string">/hello:</span>
    <span class="hljs-attr">get:</span>
      <span class="hljs-attr">summary:</span> <span class="hljs-string">Returns</span> <span class="hljs-string">a</span> <span class="hljs-string">greeting</span>
      <span class="hljs-attr">operationId:</span> <span class="hljs-string">getGreeting</span>
      <span class="hljs-attr">parameters:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">name</span>
          <span class="hljs-attr">in:</span> <span class="hljs-string">query</span>
          <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Name</span> <span class="hljs-string">of</span> <span class="hljs-string">the</span> <span class="hljs-string">person</span> <span class="hljs-string">to</span> <span class="hljs-string">greet</span>
          <span class="hljs-attr">schema:</span>
            <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
            <span class="hljs-attr">example:</span> <span class="hljs-string">Ezinne</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">lang</span>
          <span class="hljs-attr">in:</span> <span class="hljs-string">query</span>
          <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Language</span> <span class="hljs-string">of</span> <span class="hljs-string">the</span> <span class="hljs-string">greeting</span> <span class="hljs-string">(default</span> <span class="hljs-string">is</span> <span class="hljs-string">English)</span>
          <span class="hljs-attr">schema:</span>
            <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
            <span class="hljs-attr">enum:</span> [<span class="hljs-string">en</span>, <span class="hljs-string">fr</span>, <span class="hljs-string">es</span>, <span class="hljs-string">ig</span>]
            <span class="hljs-attr">example:</span> <span class="hljs-string">en</span>
      <span class="hljs-attr">responses:</span>
        <span class="hljs-attr">'200':</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Successful</span> <span class="hljs-string">response</span>
          <span class="hljs-attr">content:</span>
            <span class="hljs-attr">application/json:</span>
              <span class="hljs-attr">schema:</span>
                <span class="hljs-attr">type:</span> <span class="hljs-string">object</span>
                <span class="hljs-attr">properties:</span>
                  <span class="hljs-attr">message:</span>
                    <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
              <span class="hljs-attr">examples:</span>
                <span class="hljs-attr">english:</span>
                  <span class="hljs-attr">value:</span> { <span class="hljs-attr">message:</span> <span class="hljs-string">"Hello, Ezinne!"</span> }
                <span class="hljs-attr">french:</span>
                  <span class="hljs-attr">value:</span> { <span class="hljs-attr">message:</span> <span class="hljs-string">"Bonjour, Ezinne!"</span> }
                <span class="hljs-attr">spanish:</span>
                  <span class="hljs-attr">value:</span> { <span class="hljs-attr">message:</span> <span class="hljs-string">"¡Hola, Ezinne!"</span> }
                <span class="hljs-attr">igbo:</span>
                  <span class="hljs-attr">value:</span> { <span class="hljs-attr">message:</span> <span class="hljs-string">"Ndeewo, Ezinne!"</span> }
<span class="hljs-attr">components:</span>
  <span class="hljs-attr">securitySchemes:</span>
    <span class="hljs-attr">api_key:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">apiKey</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">Authorization</span>
      <span class="hljs-attr">in:</span> <span class="hljs-string">header</span>
</code></pre>
<p>This is a simple spec with multilingual greetings. As your API grows (say more languages or versions), keeping docs in sync manually might get tedious. That’s why automation helps.</p>
<h2 id="heading-how-to-test-the-api-spec-locally">How to Test the API Spec Locally</h2>
<h3 id="heading-install-tools">Install tools:</h3>
<p>Before setting GitHub Actions, you can test the API Spec locally on your machine by setting up <a target="_blank" href="https://github.com/Redocly">Redocly</a> (used to be called Redoc) and testing it in an HTML environment.</p>
<p>Redocly is a lightweight, customizable tool to render OpenAPI specs as an interactive HTML documentation. It’s ideal for static site deployment which makes it ideal for this scenario.</p>
<ul>
<li><p>Install Redoc globally with <code>npm install -g @redocly/cli</code></p>
</li>
<li><p>Install http-server globally with <code>npm install -g http-server</code></p>
</li>
</ul>
<p>The http-server is a local server you can use to test the doc on your machine before you push to GitHub and deploy to GitHub Pages.</p>
<h3 id="heading-create-a-landing-page">Create a landing page:</h3>
<p>In your project, make a <code>docs</code> folder and add <code>index.html</code>:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>API Documentation<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1"</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">redoc</span> <span class="hljs-attr">spec-url</span>=<span class="hljs-string">"../spec/greetings.yaml"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">redoc</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h3 id="heading-validate-your-spec">Validate Your Spec:</h3>
<p><code>redocly lint spec/greetings.yaml</code></p>
<p>You should see this if there are no errors or warnings:</p>
<pre><code class="lang-powershell">Woohoo! Your API description is valid. 🎉
</code></pre>
<p><strong>Note:</strong> Validating your API Spec before testing is important as it’ll flag any possible errors. This is because Redocly will fail to run the preview if there are any errors in your spec. </p>
<h3 id="heading-preview-in-the-browser">Preview in the browser:</h3>
<p>Run <code>http-server</code>, and you should see this in the terminal:</p>
<pre><code class="lang-powershell">Starting up http<span class="hljs-literal">-server</span>, serving ./
Available on:
  http://<span class="hljs-number">127.0</span>.<span class="hljs-number">0.1</span>:<span class="hljs-number">8080</span>
  http://<span class="hljs-number">192.168</span>.x.x:<span class="hljs-number">8080</span>
Hit CTRL<span class="hljs-literal">-C</span> to stop the server
</code></pre>
<p>Open <a target="_blank" href="http://localhost:8080/docs/index.html"><code>http://127.0.0.1:8080/</code></a> and navigate to <code>/docs</code> to see your docs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756983802999/944b8603-7b2e-477a-8156-fdaa60f7e0af.png" alt="A preview of the API Specification in a Html page" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-push-local-changes-to-github">How to Push Local Changes to GitHub</h2>
<p>After making local changes, you need to set up the API documentation so it can update automatically whenever you make changes.</p>
<p>Run these commands if you are pushing to the repository for the first time:</p>
<pre><code class="lang-powershell">git init
git add .
git commit <span class="hljs-literal">-m</span> <span class="hljs-string">"first commit"</span>
git branch <span class="hljs-literal">-M</span> main
git remote add origin &lt;your<span class="hljs-literal">-repo</span><span class="hljs-literal">-url</span>&gt;
git push <span class="hljs-literal">-u</span> origin main
</code></pre>
<h2 id="heading-how-to-set-up-your-github-actions-workflow">How to Set Up Your GitHub Actions Workflow</h2>
<p>You can set up your GitHub workflow by creating a few folders.</p>
<p>First, create <code>.github/workflows/</code> in the <code>api-docs</code> folder. Then, inside the <code>workflows</code> folder, create a <code>docs.yml</code>. This is the workflow file that will serve as a trigger to run validation, generate the HTML with Redocly, and deploy to GitHub Pages at the same time.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">API</span> <span class="hljs-string">Documentation</span> <span class="hljs-string">and</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Pages</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span>
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'spec/greetings.yaml'</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build-spec:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">permissions:</span>
      <span class="hljs-attr">contents:</span> <span class="hljs-string">write</span> <span class="hljs-comment"># needed for gh-pages deployment</span>

    <span class="hljs-attr">steps:</span>
      <span class="hljs-comment"># 1. Checkout repository</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-comment"># 2. Set up Node.js</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Node.js</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">node-version:</span> <span class="hljs-string">'20'</span>

      <span class="hljs-comment"># 3. Install Redocly CLI</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Redocly</span> <span class="hljs-string">CLI</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">install</span> <span class="hljs-string">-g</span> <span class="hljs-string">@redocly/cli</span>

      <span class="hljs-comment"># 4. Validate OpenAPI spec</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Validate</span> <span class="hljs-string">OpenAPI</span> <span class="hljs-string">Spec</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">redocly</span> <span class="hljs-string">lint</span> <span class="hljs-string">spec/greetings.yaml</span>

      <span class="hljs-comment"># 5. Build output directory</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">build</span> <span class="hljs-string">directory</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">public</span>

      <span class="hljs-comment"># 6. Copy spec</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Copy</span> <span class="hljs-string">spec</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">public/spec</span> <span class="hljs-string">&amp;&amp;</span> <span class="hljs-string">cp</span> <span class="hljs-string">spec/greetings.yaml</span> <span class="hljs-string">public/spec/</span>

      <span class="hljs-comment"># 7. Copy landing page</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Copy</span> <span class="hljs-string">landing</span> <span class="hljs-string">page</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">cp</span> <span class="hljs-string">docs/index.html</span> <span class="hljs-string">public/index.html</span>

      <span class="hljs-comment"># 8. Deploy to GitHub Pages</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Pages</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">peaceiris/actions-gh-pages@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">github_token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">publish_dir:</span> <span class="hljs-string">./public</span>
</code></pre>
<p>Here’s what’s going on in this code:</p>
<ul>
<li><p>Runs when changes are pushed to <code>main</code> that affect <code>spec/greetings.yaml</code>.</p>
</li>
<li><p>Checks out the repo code.</p>
</li>
<li><p>Sets up Node.js and installs Redocly.</p>
</li>
<li><p>Validates your OpenAPI spec (so broken specs won’t deploy).</p>
</li>
<li><p>Copies the spec and index page into a <code>public/</code> folder.</p>
</li>
<li><p>Deploys <code>public/</code> to the <code>gh-pages</code> branch with GitHub Pages.</p>
</li>
</ul>
<p>Since we’re done with local testing, update the file path in the <code>index.html</code>:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>API Documentation<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1"</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">redoc</span> <span class="hljs-attr">spec-url</span>=<span class="hljs-string">"./spec/greetings.yaml"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">redoc</span>&gt;</span> <span class="hljs-comment">&lt;!--update the filepath to match your gh config--&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>This is so the <code>public</code> directory in the workflow will be able to access it correctly.</p>
<p>This workflow will only run when it detects changes in the API Spec (<code>greetings.yml</code>). To see the workflow in action, make a minor edit in the <code>greetings.yaml</code>.</p>
<p>Push the changes to your GitHub repository:</p>
<pre><code class="lang-powershell">git add .
git commit <span class="hljs-literal">-m</span> <span class="hljs-string">'add changes'</span>
git push
</code></pre>
<h2 id="heading-how-to-set-up-github-pages">How to Set Up GitHub Pages</h2>
<h3 id="heading-what-is-github-pages">What is GitHub Pages?</h3>
<p><a target="_blank" href="https://docs.github.com/en/pages/getting-started-with-github-pages/what-is-github-pages">GitHub Pages</a> is a hosting platform owned by GitHub where you can host websites directly from your GitHub account. This means you can publish static sites on the internet using a GitHub domain and anyone with the website link can access it.</p>
<p>There are other hosting platforms you can use to deploy static websites such as <a target="_blank" href="https://www.netlify.com/">Netlify</a> and <a target="_blank" href="https://vercel.com/">Vercel</a>. But using GitHub Pages for this documentation is easier to set up as it’s on the same platform.</p>
<h3 id="heading-setting-up-github-pages">Setting up GitHub Pages</h3>
<p>Set up GitHub Pages by clicking on the Settings tab in your repository.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756985360548/fa3518a7-0b44-4c7b-ae7f-d0e0b17a84c6.png" alt="A preview of the settings tab in the `api-docs` repository" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Under Source, choose:</p>
<ul>
<li><p>Deploy from branch: <code>gh-pages</code></p>
</li>
<li><p>Folder: <code>/ (root)</code></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756985446692/a4774bcc-1a42-49f8-a9fd-8ca9339808ef.png" alt="A step-by-step preview of the gh-pages and root setup" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Then save and wait for the workflow to finish.</p>
<p>Your docs will be live at: <code>https://&lt;username&gt;.github.io/api-docs</code>.</p>
<h2 id="heading-how-to-handle-multiple-versions">How to Handle Multiple Versions</h2>
<p>What if you had multiple API versions to update? Let’s assume the simple greetings API in this tutorial had more features added to it across different versions. In this case, you can manage the APIs for the different versions in a single page and also build and deploy it automatically. </p>
<h3 id="heading-about-the-versions">About the Versions</h3>
<h4 id="heading-version-1-v1">Version 1 (v1)</h4>
<p>This is the starting point which is <code>greetings.yaml</code>. The API only has a single <code>/hello</code> endpoint that returns a greeting in four languages (English, French, Spanish, or Igbo).</p>
<h4 id="heading-version-2-v2">Version 2 (v2)</h4>
<p>In version 2, the API adds create and read features. You can:</p>
<ul>
<li><p>Use <code>POST /hello</code> to create and save a greeting.</p>
</li>
<li><p>Retrieve greetings by their unique ID with <code>GET /hello/{id}</code>.</p>
</li>
</ul>
<h4 id="heading-version-3-v3">Version 3 (v3)</h4>
<p>Version 3 builds on top of v2 by adding an update functionality. Along with creating and retrieving greetings, you can now update an existing greeting using <code>PUT /hello/{id}</code>.</p>
<h3 id="heading-how-to-set-up-the-versions-locally">How to Set Up the Versions Locally</h3>
<p>First, create a <code>v1</code> folder and move the <code>greetings.yaml</code> file to it. Since we are going to be using versions, you can delete the existing <code>spec</code> folder.</p>
<p>Then, create a <code>v2</code> folder and create a <code>greetings-v2.yaml</code> file. <a target="_blank" href="https://ezinneanne.github.io/api-doc/v2/greetings-v2.yaml">Get the greetings API for version 2 here</a>.</p>
<p>Next, create a <code>v3</code> folder and add <code>greetings-v3.yaml</code> file. <a target="_blank" href="https://ezinneanne.github.io/api-doc/v3/greetings-v3.yaml">Get the greetings API for version 3 here</a>.</p>
<p>To follow the same pattern with others, rename the version 1 file to <code>greetings-v1.yaml</code>. Then update your <code>index.html</code> to accommodate the other two versions.</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>API Documentation<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
      <span class="hljs-selector-tag">body</span> {
        <span class="hljs-attribute">font-family</span>: Arial, sans-serif;
        <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
      }
      <span class="hljs-selector-tag">header</span> {
        <span class="hljs-attribute">background</span>: <span class="hljs-number">#2c3e50</span>;
        <span class="hljs-attribute">color</span>: white;
        <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
        <span class="hljs-attribute">display</span>: flex;
        <span class="hljs-attribute">justify-content</span>: space-between;
        <span class="hljs-attribute">align-items</span>: center;
      }
      <span class="hljs-selector-tag">select</span> {
        <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.4rem</span>;
        <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
      }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>API Documentation<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"version"</span>&gt;</span>Version: <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"version"</span> <span class="hljs-attr">onchange</span>=<span class="hljs-string">"loadSpec()"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"./v1/greetings-v1.yaml"</span>&gt;</span>v1<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"./v2/greetings-v2.yaml"</span>&gt;</span>v2<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"./v3/greetings-v3.yaml"</span>&gt;</span>v3<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- ReDoc container --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"redoc-container"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- ReDoc script --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">loadSpec</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">const</span> version = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"version"</span>).value;
        Redoc.init(version, {}, <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"redoc-container"</span>));
      }
      <span class="hljs-comment">// Load default (v1) on first load</span>
      <span class="hljs-built_in">window</span>.onload = loadSpec;
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h3 id="heading-how-to-validate-the-api-specs">How to Validate the API Specs</h3>
<p>Earlier in this article, I mentioned testing your specification locally. Now that you have two more versions of the greetings API, run the test to highlight and fix any existing errors.</p>
<ul>
<li><p>For the version V2: <code>redocly lint v2/greetings-v2.yaml</code></p>
</li>
<li><p>For the version V3: <code>redocly lint v3/greetings-v3.yaml</code></p>
</li>
</ul>
<h3 id="heading-how-to-update-the-github-actions-workflow">How to Update the GitHub Actions Workflow</h3>
<p>Now that you have three API Spec versions, you need to update your workflow so it will monitor the three spec files and the HTML document for changes, and then push and deploy them to GitHub Pages as well.</p>
<p>Add this to your <code>.github/workflows/docs.yml</code>:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Name of the workflow</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">and</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">API</span> <span class="hljs-string">Documentation</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">main</span> ]
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'docs/index.html'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'v1/greetings-v1.yaml'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'v2/greetings-v2.yaml'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'v3/greetings-v3.yaml'</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build-and-deploy:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>

    <span class="hljs-attr">permissions:</span>
      <span class="hljs-attr">contents:</span> <span class="hljs-string">write</span>

    <span class="hljs-attr">steps:</span>
      <span class="hljs-comment"># 1. Checkout the repository</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">repository</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-comment"># 2. Create build directory</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">build</span> <span class="hljs-string">directory</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">public</span>

      <span class="hljs-comment"># 3. Copy YAML specs into public folder</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Copy</span> <span class="hljs-string">v1</span> <span class="hljs-string">spec</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">public/v1</span> <span class="hljs-string">&amp;&amp;</span> <span class="hljs-string">cp</span> <span class="hljs-string">v1/greetings-v1.yaml</span> <span class="hljs-string">public/v1/</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Copy</span> <span class="hljs-string">v2</span> <span class="hljs-string">spec</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">public/v2</span> <span class="hljs-string">&amp;&amp;</span> <span class="hljs-string">cp</span> <span class="hljs-string">v2/greetings-v2.yaml</span> <span class="hljs-string">public/v2/</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Copy</span> <span class="hljs-string">v3</span> <span class="hljs-string">spec</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">public/v3</span> <span class="hljs-string">&amp;&amp;</span> <span class="hljs-string">cp</span> <span class="hljs-string">v3/greetings-v3.yaml</span> <span class="hljs-string">public/v3/</span>

      <span class="hljs-comment"># 4. Copy landing page into public</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Copy</span> <span class="hljs-string">landing</span> <span class="hljs-string">page</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">cp</span> <span class="hljs-string">docs/index.html</span> <span class="hljs-string">public/index.html</span>

      <span class="hljs-comment"># 5. Deploy to GitHub Pages</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Pages</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">peaceiris/actions-gh-pages@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">github_token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">publish_dir:</span> <span class="hljs-string">./public</span>
</code></pre>
<p>And finally, push the changes and reload the site. This should showcase the updated documentation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756986868235/9de187f1-12c4-46ca-a73b-daafa353ed1f.png" alt="A preview of the API documentation in a hosted GitHub Pages environment" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-summary">Summary</h2>
<p>In this tutorial, you have learned how to auto-update your API docs. We started with a single OpenAPI spec and a basic HTML page rendered by Redocly, and tested it locally. We then set up GitHub Actions to automatically validate the spec, copy the files, and deploy the docs to GitHub Pages. Finally, we extended the setup to handle multiple API versions in one place.</p>
<p>With this workflow, your documentation stays accurate, up-to-date, and hassle-free so every change you make to your API spec goes live when you push the changes.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Common Open Source Contribution Myths – Debunked ]]>
                </title>
                <description>
                    <![CDATA[ Many developers shy away from contributing to open source, as it can be intimidating and hard to get started. Even though your contributions might seem inconsequential at first, they can potentially have a huge impact on your career. In this article,... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/common-open-source-contribution-myths-debunked/</link>
                <guid isPermaLink="false">68b703ede63728a780d3fc1f</guid>
                
                    <category>
                        <![CDATA[ Open Source ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ community ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Tue, 02 Sep 2025 14:49:17 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756821843592/e345ed9b-4cae-4273-b677-05e7047be8b7.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Many developers shy away from contributing to open source, as it can be intimidating and hard to get started. Even though your contributions might seem inconsequential at first, they can potentially have a huge impact on your career.</p>
<p>In this article, we’ll discuss some common misconceptions that might be holding you back from contributing to open source. I’ll show you what you’re missing out on, and give you some advice to help you begin.</p>
<h2 id="heading-table-of-contentsheading-table-of-contents"><a class="post-section-overview" href="#heading-table-of-contents">Table of Contents</a></h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-is-open-source">What is Open Source?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-key-factors-influencing-open-source-contributions">Key Factors Influencing Open Source Contributions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-people-hesitate-to-contribute">Why People Hesitate to Contribute</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-you-feel-like-an-impostor">You feel like an impostor</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-you-have-to-do-it-for-free">You have to do it for free</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-you-have-a-busy-schedule">You have a busy schedule</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-start-contributing-to-open-source-today">Start Contributing to Open Source Today</a></p>
</li>
</ol>
<h2 id="heading-what-is-open-source">What is Open Source?</h2>
<p>Open source refers to software that has its code publicly available for viewing, modification, and use. The code is usually hosted on platforms like GitHub where developers can contribute to the codebase and share their expertise with the project.</p>
<p>Although open source software code is publicly accessible, it doesn’t mean that the software has to be free. Creators of the software can make money by charging a fee for things like optional plugins, consultation about the software, and so on.</p>
<p><a target="_blank" href="https://github.com/nginx">Nginx</a> is an example of open source software that charges a fee for additional but optional plugins. <a target="_blank" href="https://github.com/nestjs">NestJS</a> is open source too, but has an official paid course (and its maintainers charge a consultation fee for more complex uses of the software).</p>
<h2 id="heading-key-factors-influencing-open-source-contributions">Key Factors Influencing Open Source Contributions</h2>
<p>In 2023, roughly <a target="_blank" href="https://opensource.googleblog.com/2024/08/2023-open-source-contribution-report.html">10% of Alphabet’s full-time workforce</a> actively contributed to open source projects. And according to <a target="_blank" href="https://opensourcesurvey.org/2024/">GitHub’s 2024 Open Source Survey</a>, respondents reported that the top five factors that influenced their contribution to open source projects, in order of importance, were:</p>
<ul>
<li><p>Whether the project had an open source license or not. Having an open source license was considered favourable.</p>
</li>
<li><p>The responsiveness of the project’s maintainers. Fast and positive responses are encouraging.</p>
</li>
<li><p>A welcoming community, indicating support from the maintainers and others contributing to the project.</p>
</li>
<li><p>The level of activity on the project – lots of activity signifies an actively maintained project (which is more rewarding and useful to work on).</p>
</li>
<li><p>Whether the project had a contribution guide that helps developers ease into contributing to the project.</p>
</li>
</ul>
<p><a target="_blank" href="https://opensourcesurvey.org/2024/"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753830717981/b753b4ae-36ec-4773-990d-dcb83a530c3f.png" alt="GitHub’s 2024 Open Source Survey - Key Factors Influencing Open Source Contributions" class="image--center mx-auto" width="600" height="400" loading="lazy"></a></p>
<p>But even though many projects are attractive, and their maintainers are welcoming, some people still hesitate to contribute for reasons not linked to the projects themselves.</p>
<h2 id="heading-why-people-hesitate-to-contribute">Why People Hesitate to Contribute</h2>
<p>After looking into responses from various discussions, I found that there are three primary barriers developers face when they’re considering contributing to open source:</p>
<ul>
<li><p>You feel like an impostor</p>
</li>
<li><p>You have to do it for free</p>
</li>
<li><p>You have a busy schedule</p>
</li>
</ul>
<p>The rest of this article will address (and hopefully break down) these barriers. I hope that by the end, you’ll be encouraged to contribute to open source.</p>
<h3 id="heading-you-feel-like-an-impostor">You feel like an impostor</h3>
<p>Many people think that you have to know a lot about a project to work on it, but this misconception is one of the biggest barriers that holds developers back from contributing to open source. Some people say to themselves “I don't have the experience”, “I have nothing to contribute”, “What if I break something?”, “I don’t know enough”.</p>
<p>Here’s some advice from <a target="_blank" href="https://www.freecodecamp.org/news/how-to-not-feel-like-an-imposter-3d41fdc91182/">How to Overcome Imposter Syndrome</a> that can help:</p>
<ul>
<li><p>Stop obsessing over not being good enough. It’s unproductive.</p>
</li>
<li><p>Everyone excels at different things. You likely excel at some things that others don’t.</p>
</li>
<li><p>Don’t compare yourself with others who are more experienced than you. In fact, stop comparing yourself with anyone else. You are unique.</p>
</li>
</ul>
<p>Try to contribute to projects that you have used. You already understand them better than projects that you haven’t used. As a beginner, working on issues labeled “good first issue”, “up-for-grabs”, “beginner-friendly” and so on is a great place to start. If you find an issue that interests you, read the discussions under it if there are any so you can understand it better. State that you are interested in working on it and an experienced contributor will likely respond to let you know if you can proceed. They can also provide you with more information if you need it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756458374283/cf14b8d9-b7e0-45b9-a7ca-a88e93df28c2.png" alt="Issues with labels that beginners can contribute to" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Put in the work to resolve it yourself first, and if you have any issues, then you can ask more questions. When you try to resolve it yourself before asking questions, it tells maintainers that you have put in the effort and they will be more willing to help. It also makes your question(s) sound thoughtful and direct.</p>
<blockquote>
<p><strong>“The three most powerful motives are curiosity, delight, and the desire to do something impressive. “</strong></p>
<p><strong>— How to Do Great Work by Paul Graham</strong></p>
</blockquote>
<p>I shared my experience in an article titled <a target="_blank" href="https://orimdominic.vercel.app/posts/you-dont-need-to-know-it-all-to-contribute/">You Don't Need to Know It All to Contribute</a> where I found out that the author of a popular JavaScript project was not the one that wrote the TypeScript part of the project and he was happy to receive my contribution.</p>
<p>If you still feel like you are not good enough, try to look at it as an opportunity to learn from a codebase built by developers with a wide range of experiences and expertise. If you dive into the code and try to improve it, you’ll learn things that you won’t find in online tutorials and blog posts.</p>
<p>Just keep in mind that you don’t always have to contribute code. Even though the majority of contributions are usually code-based, there are other areas that need attention, too, and are often overlooked. What happens if the documentation is nonexistent or outdated? What happens when issues aren’t triaged? You can help with these issues by:</p>
<ul>
<li><p>Discussing them with project maintainers and other contributors to clarify and improve docs and other resources</p>
</li>
<li><p>Giving feedback on pull requests</p>
</li>
<li><p>Adding labels for organising issues into proper categories</p>
</li>
</ul>
<p>By doing this, you provide value to these projects. What matters is your interest in solving problems and your ability to do the required research.</p>
<h3 id="heading-you-have-to-do-it-for-free">You have to do it for free</h3>
<p>Another misconception is that devs always do open source without getting paid. But this isn't true for all cases. Some open source repositories reward contributors via bounties, for example. With bounties, prize money is placed on an an issue and whoever solves it gets the prize money or reward. I earned money through this via <a target="_blank" href="https://orimdominic.vercel.app/posts/my-first-open-source-contribution-to-remotion/">my first contribution to Remotion</a>.</p>
<p>And there are other benefits of contributing to open source projects aside from getting paid. And often, people contribute primarily for these other reasons.</p>
<p>For example, for beginner developers, there aren’t many opportunities to get hands-on experience building “real” projects, or to learn what working on a tech team is really like.</p>
<p>But by contributing to an open source project and becoming part of that community, you get to practice teamwork, contribute to code that’s constantly changing and growing, and solve real-life problems. This gives you valuable experience that’ll help prepare you for the workforce (and that you can put on your résumé). The image below is a screenshot of two people who received a <a target="_blank" href="https://training.linuxfoundation.org/blog/500-promising-individuals-worldwide-receive-linux-foundation-it-training-certification-scholarships/">scholarship from the Linux Foundation</a> for their contributions to open source.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756482636970/e3fe1fad-7edb-4748-b760-00e054dd4e61.png" alt="Screenshot of open source scholarship winners" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>So as you can see, even if you don’t get monetary rewards, contributing to open source presents many other opportunities. You get to:</p>
<ul>
<li><p>Work with people from diverse cultures</p>
</li>
<li><p>Develop clear written technical communication skills</p>
</li>
<li><p>Improve your knowledge of various tools/frameworks</p>
</li>
<li><p>Prove yourself to be a self-motivated developer</p>
</li>
<li><p>Work asynchronously with people across multiple time zones</p>
</li>
</ul>
<p>You also get the opportunity to network with other developers and grow your reputation and credibility in the software development space. <a target="_blank" href="https://github.com/unicodeveloper">Prosper Otemuyiwa</a> and <a target="_blank" href="https://github.com/antfu">Anthony Fu</a> are great examples and beneficiaries of this type of career growth.</p>
<h3 id="heading-you-have-a-busy-schedule">You have a busy schedule</h3>
<p>Having other pressing commitments is a reasonable barrier to contributing to open source. We’re all human, and our resources (time and energy) are limited – so we have to manage them judiciously.</p>
<p>But have you considered what would happen if the open source project that you use had a serious bug that affects your performance or deliverables at work? What happens when the problem you are trying to solve at work requires expert knowledge of that open source project that you heavily depend on?</p>
<p>Contributing to open source projects in the little ways that you can will give you insight into how the projects work. Since you have to maintain a rapport with other developers on the project, you’ll form a professional bond with them and they’ll be glad to help you out if you have questions or require expert opinions on the project. If you are a major contributor, you may have the chance to influence the project’s roadmap.</p>
<p>Continuing to develop your skills is paramount to you as a developer. If you can make time to improve your skills, then you can make time to contribute to open source because it offers more rewards than just skill improvement. You can start small – try dedicating, for example, one hour a week before work, or a couple hours on the weekend. Then you can ramp up once you get into the rhythm and find more time. Contributing to open source doesn’t have to be overly demanding.</p>
<p>You’re already solving problems. Why not solve them in a visible way that helps others and builds your own credibility?</p>
<h2 id="heading-start-contributing-to-open-source-today">Start Contributing to Open Source Today</h2>
<p>We all should contribute to open source one way or another. If you feel that you don’t know enough, just keep in mind that other people feel (or felt, before starting) the same way. And know that nobody can claim to know it all – not even the founder of the project. So don’t let the feeling of impostor syndrome hold you back.</p>
<p>Remember also that Rome was not built in a day. It wasn’t also built by one person’s hand – but by countless laborers over many centuries and millennia. You could be part of building the next great open source project (or helping maintain the many great ones that are already out there and need your help).</p>
<p>So consider contributing today by visiting the issues tab of any of the open source projects that you use extensively and pick up something. You can find links to beginner-level issues from the following websites:</p>
<ul>
<li><p><a target="_blank" href="https://goodfirstissue.dev/">Good First Issue</a></p>
</li>
<li><p><a target="_blank" href="https://up-for-grabs.net/">Up For Grabs</a></p>
</li>
<li><p><a target="_blank" href="https://www.codetriage.com/">CodeTriage</a></p>
</li>
<li><p><a target="_blank" href="https://www.onlydust.com/">OnlyDust</a></p>
</li>
</ul>
<p>If you see an issue that you are interested in, take the following steps as a new contributor:</p>
<ul>
<li><p>Look for a <code>CONTRIBUTING.md</code> file and read it for guidance on how to contribute</p>
</li>
<li><p>Clone the repository and set it up locally</p>
</li>
<li><p>State your intention to work on the issue by making a comment on the issue</p>
</li>
</ul>
<p>Ask a question if an issue is unclear to you and you’ll get guidance.</p>
<p>Good luck on your open source journey!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up GitHub CLI on WSL2 ]]>
                </title>
                <description>
                    <![CDATA[ Recently, I set up WSL2 and Ubuntu on my Windows 11 to work on some open-source projects. Since I also maintain these projects, I installed GitHub CLI to ease my workflow. I successfully installed the GitHub CLI, but failed to authenticate it. The er... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/github-cli-wsl2-guide/</link>
                <guid isPermaLink="false">689e444cbfe79386885372b0</guid>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ WSL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayu Adiati ]]>
                </dc:creator>
                <pubDate>Thu, 14 Aug 2025 20:17:16 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755202477019/fbc68131-107a-40ae-9dae-c14224d0866a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Recently, I set up WSL2 and Ubuntu on my Windows 11 to work on some open-source projects. Since I also maintain these projects, I installed <a target="_blank" href="https://cli.github.com/">GitHub CLI</a> to ease my workflow. I successfully installed the GitHub CLI, but failed to authenticate it.</p>
<p>The error message <code>failed to authenticate via web browser: Too many requests have been made in the same timeframe. (slow_down)</code> appeared on my terminal, while on the web browser, it indicated that the authentication was successful.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754718774837/0d1de969-a1e3-4f0a-a3ce-e3c4661ce0d0.png" alt="A message says &quot;Congratulations, you're all set,&quot; marking GitHub CLI authentication is successful " class="image--center mx-auto" width="457" height="334" loading="lazy"></p>
<p>I googled and found some workarounds that I tried, but only one worked like a charm!</p>
<p>After finally solving the tricky authentication issue for GitHub CLI on WSL2, I've put together this guide. It's a complete walkthrough for a solution that works, covering everything from a smooth installation to ongoing management.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-install-github-cli-on-wsl2">How to Install GitHub CLI on WSL2</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-authenticate-github-cli-on-wsl2-with-your-github-account">How to Authenticate GitHub CLI on WSL2 with Your GitHub Account</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-upgrade-github-cli-on-wsl2">How to Upgrade GitHub CLI on WSL2</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-uninstall-github-cli-on-wsl2">How to Uninstall GitHub CLI on WSL2</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-revoke-github-cli-access-on-github">How to Revoke GitHub CLI Access on GitHub</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-words">Final Words</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before getting started, ensure that you have these installed on your Windows machine:</p>
<ul>
<li><p>WSL2</p>
</li>
<li><p>A Linux distro</p>
</li>
<li><p>Windows PowerShell</p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/windows/terminal/install">Windows Terminal</a> (optional)</p>
</li>
</ul>
<p>To follow the instructions in this article, you can use Windows PowerShell terminal as an administrator.</p>
<p>Alternatively, if you have Windows Terminal installed, you can use the Linux terminal by clicking the ‘down arrow’ icon at the top and selecting the distro.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754677301223/7e846117-3fd1-42a2-ab3e-029e94672aca.png" alt="Dropdown menu at Windows Terminal" class="image--center mx-auto" width="582" height="375" loading="lazy"></p>
<h2 id="heading-how-to-install-github-cli-on-wsl2">How to Install GitHub CLI on WSL2</h2>
<p>You can use the installation process described here if you use Ubuntu, Debian, or Raspberry Pi OS (apt) distros. For other distros other than those mentioned here, you can walk through the installation process that's available on the <a target="_blank" href="https://github.com/cli/cli/blob/trunk/docs/install_linux.md">GitHub CLI official docs</a>.</p>
<p>To install GitHub CLI in WSL2:</p>
<ol>
<li><p>Run this command:</p>
<pre><code class="lang-bash"> (<span class="hljs-built_in">type</span> -p wget &gt;/dev/null || (sudo apt update &amp;&amp; sudo apt install wget -y)) \
     &amp;&amp; sudo mkdir -p -m 755 /etc/apt/keyrings \
     &amp;&amp; out=$(mktemp) &amp;&amp; wget -nv -O<span class="hljs-variable">$out</span> https://cli.github.com/packages/githubcli-archive-keyring.gpg \
     &amp;&amp; cat <span class="hljs-variable">$out</span> | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg &gt; /dev/null \
     &amp;&amp; sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
     &amp;&amp; sudo mkdir -p -m 755 /etc/apt/sources.list.d \
     &amp;&amp; <span class="hljs-built_in">echo</span> <span class="hljs-string">"deb [arch=<span class="hljs-subst">$(dpkg --print-architecture)</span> signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main"</span> | sudo tee /etc/apt/sources.list.d/github-cli.list &gt; /dev/null \
     &amp;&amp; sudo apt update \
     &amp;&amp; sudo apt install gh -y
</code></pre>
</li>
<li><p>Type your Linux password when you get prompted.</p>
</li>
<li><p>Ensure the GitHub CLI is installed by running <code>gh --version</code> command. If the installation is successful, you should see something like this in your terminal:</p>
<pre><code class="lang-bash"> gh version 2.76.2 (2025-07-30)
 https://github.com/cli/cli/releases/tag/v2.76.2
</code></pre>
</li>
</ol>
<h2 id="heading-how-to-authenticate-github-cli-on-wsl2-with-your-github-account">How to Authenticate GitHub CLI on WSL2 with Your GitHub Account</h2>
<p>Before you can use GitHub CLI, you must first authenticate it. You will get an <code>HTTP 401: Bad credentials (https://api.github.com/graphql)</code> error message if you run any GitHub CLI command without authenticating.</p>
<p>To authenticate GitHub CLI with your GitHub account:</p>
<ol>
<li><p>Run the <code>gh auth login</code> command in your terminal.</p>
</li>
<li><p>You will receive several prompts, and you need to choose the methods you prefer. Here’s what I selected in each prompt:</p>
<pre><code class="lang-plaintext"> ? Where do you use GitHub? GitHub.com
 ? What is your preferred protocol for Git operations on this host? HTTPS
 ? How would you like to authenticate GitHub CLI? Login with a web browser
</code></pre>
<p> After answering all prompts, you should get the message to copy a one-time code as shown below. You <strong>don’t need to copy the code</strong> at this point.</p>
<pre><code class="lang-bash"> ! First copy your one-time code: XXXX—XXXX
</code></pre>
</li>
<li><p>Press ‘Enter’. It automatically opens the "Device Activation" page on your browser.</p>
</li>
<li><p>Click the green ‘Continue’ button.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754848322666/2a4af9ab-c197-4ec9-802f-ad9b4f24375c.png" alt="GitHub Device Activation page on a browser" class="image--center mx-auto" width="486" height="384" loading="lazy"></p>
<p> GitHub should ask you to enter the code displayed on your terminal, as shown in the screenshot below. But here’s the trick! <strong>Don’t paste any code, and don’t close the browser</strong>. Let’s first get back to your terminal.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754722491767/d84534da-522f-4e82-84c2-a1bfc75940ef.png" alt="GitHub Device Activation page on a browser" class="image--center mx-auto" width="436" height="460" loading="lazy"></p>
<p> Now you might get this error message on your terminal:</p>
<pre><code class="lang-bash"> grep: /proc/sys/fs/binfmt_misc/WSLInterop: No such file or directory
 WSL Interopability is disabled. Please <span class="hljs-built_in">enable</span> it before using WSL.
 grep: /proc/sys/fs/binfmt_misc/WSLInterop: No such file or directory
 [error] WSL Interoperability is disabled. Please <span class="hljs-built_in">enable</span> it before using WSL.
</code></pre>
</li>
<li><p>Press <code>Ctrl + C</code> to stop the process if it's still running, or let it stop by itself. Once it's stopped, you should see this message:</p>
<pre><code class="lang-bash"> failed to authenticate via web browser: Too many requests have been made <span class="hljs-keyword">in</span> the same timeframe. (slow_down)
</code></pre>
</li>
<li><p>Run the <code>gh auth login</code> command again and repeat the process to select the methods of your choice. This time, when it asks you to press ‘Enter’, <strong>don’t press it</strong>.</p>
</li>
<li><p>Copy the latest code and return to the "Device Activation" page that you left open in your browser.</p>
</li>
<li><p>Paste the code that you copied and click the green ‘Continue’ button.</p>
</li>
<li><p>Click the green ‘Authorize github’ button after GitHub redirects you to the “Authorize GitHub CLI” page. You should now see the message “Congratulations, you're all set!”</p>
</li>
<li><p>Get back to your terminal and press ‘Enter’. Doing so triggers these actions:</p>
<ul>
<li><p>It automatically opens a new “Device Activation” page in your browser. You can safely ignore this.</p>
</li>
<li><p>In the terminal, you first see the error message as in step 4. Don’t do anything and wait for a little bit. Then, you get:</p>
<pre><code class="lang-bash">  ✓ Authentication complete.
  - gh config <span class="hljs-built_in">set</span> -h github.com git_protocol https
  ✓ Configured git protocol
  ! Authentication credentials saved <span class="hljs-keyword">in</span> plain text
  ✓ Logged <span class="hljs-keyword">in</span> as YOUR-GITHUB-USERNAME
  ! You were already logged <span class="hljs-keyword">in</span> to this account
</code></pre>
</li>
</ul>
</li>
</ol>
<p>And GitHub CLI is now successfully authenticated!</p>
<blockquote>
<p>Credit goes to <a target="_blank" href="https://github.com/cli/cli/discussions/6884#discussioncomment-10176332">username “ikeyan” on GitHub for their GitHub CLI authentication solution</a>!</p>
</blockquote>
<h2 id="heading-how-to-upgrade-github-cli-on-wsl2">How to Upgrade GitHub CLI on WSL2</h2>
<p>It’s always a good practice to regularly check for package and dependency updates, and upgrade to the newest version when it’s available — this includes GitHub CLI. To check for updates and upgrade the version of GitHub CLI:</p>
<ol>
<li><p>Run the <code>sudo apt update</code> command in your terminal. This command fetches the list of available updates.</p>
</li>
<li><p>Type your Linux password when you get prompted.</p>
</li>
<li><p>If you need to upgrade your GitHub CLI, run <code>sudo apt install gh</code>. This command installs the newest version of GitHub CLI.</p>
</li>
<li><p>Type your Linux password when you get prompted.</p>
</li>
</ol>
<p>Now your GitHub CLI has the newest version.</p>
<h2 id="heading-how-to-uninstall-github-cli-on-wsl2">How to Uninstall GitHub CLI on WSL2</h2>
<p>If one day you feel like you don’t need to use GitHub CLI anymore, you can uninstall it by following these steps:</p>
<ol>
<li><p>Run the <code>sudo apt remove gh</code> command in your terminal.</p>
</li>
<li><p>Type your Linux password when you get prompted.</p>
</li>
<li><p>Press ‘Y’ to continue the uninstall process.</p>
</li>
</ol>
<p>GitHub CLI is now uninstalled from your WSL environment.</p>
<h2 id="heading-how-to-revoke-github-cli-access-on-github">How to Revoke GitHub CLI Access on GitHub</h2>
<p>After uninstalling the GitHub CLI, you might think your account access is gone, but it's not. The authentication you granted is still active. If you don't plan on using the CLI again, it's a good practice to revoke this access.</p>
<p>Here's how to do it directly from your GitHub account:</p>
<ol>
<li><p>On your GitHub account, click your profile picture on the top right and click ‘Settings’.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754725091482/8fb8a0fd-8dbd-4342-9fe8-309a13d72c39.png" alt="Settings option on dropdown menu at GitHub" class="image--center mx-auto" width="283" height="471" loading="lazy"></p>
<ol start="2">
<li><p>On the left side bar, find ‘Integrations’ and click ‘Applications’.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754815240842/ca49d207-6ee2-476f-a53d-bde53b2d57dd.png" alt="Applications tab in the Integrations settings on GitHub" class="image--center mx-auto" width="425" height="158" loading="lazy"></p>
</li>
<li><p>Click the ‘Authorized OAuth Apps’ tab on top.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754815346304/a360f7dc-7024-44c3-8e19-15d94b35ce8e.png" alt="Authorized OAuth Apps tab on GitHub" class="image--center mx-auto" width="837" height="141" loading="lazy"></p>
</li>
<li><p>Find GitHub CLI and click the ‘three dots’ icon next to it.</p>
</li>
<li><p>Click ‘Revoke’.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754725454783/dd544380-482a-4385-97c1-4ebc35026658.png" alt="Revoke option on GitHub to revoke an authorized OAuth apps" class="image--center mx-auto" width="1369" height="164" loading="lazy"></p>
</li>
<li><p>Confirm it by clicking the ‘I understand, revoke access’ button.</p>
</li>
</ol>
</li>
</ol>
<p>Now, GitHub CLI doesn’t have access to your GitHub account.</p>
<hr>
<h2 id="heading-final-words">Final Words</h2>
<p>🖼️ Credit cover image: <a target="_blank" href="http://undraw.co">undraw.co</a></p>
<p>Thank you for reading! Last, you can find me on <a target="_blank" href="https://twitter.com/@AdiatiAyu">X</a> and <a target="_blank" href="https://www.linkedin.com/in/adiatiayu/">LinkedIn</a>. Let's connect! 😊</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Protect Your GitHub Repos Against Malicious Clones ]]>
                </title>
                <description>
                    <![CDATA[ The world of open-source development comes with various cyber threats. GitHub is still facing a type of attack that is ongoing since last year where attackers mirrored a huge number of repositories. So as it turns out…the clone wars are not over! If ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/protect-github-repos-from-malicious-clones/</link>
                <guid isPermaLink="false">68781639d48174ae283f8a5b</guid>
                
                    <category>
                        <![CDATA[ repo confusion ]]>
                    </category>
                
                    <category>
                        <![CDATA[ repository confusion ]]>
                    </category>
                
                    <category>
                        <![CDATA[ fake repos ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ clone ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #cybersecurity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Malware ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Supply Chain Attack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ malicious ]]>
                    </category>
                
                    <category>
                        <![CDATA[ checklist ]]>
                    </category>
                
                    <category>
                        <![CDATA[ phishing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ infostealer ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Prevention ]]>
                    </category>
                
                    <category>
                        <![CDATA[ best practices ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ brooklyn ]]>
                </dc:creator>
                <pubDate>Wed, 16 Jul 2025 21:14:33 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1752700407765/5fe06816-3d3a-40e4-8a4e-5cfe96a22368.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The world of open-source development comes with various cyber threats. GitHub is still facing a type of attack that is ongoing since last year where attackers mirrored a huge number of repositories. So as it turns out…the clone wars are not over!</p>
<p>If you haven’t heard about what’s going on:</p>
<blockquote>
<p>GitHub is struggling to contain an ongoing attack that’s flooding the site with with millions of code repositories. These repositories contain obfuscated malware that steals passwords and cryptocurrency from developer devices. … The result is millions of forks with names identical to the original one.</p>
<p>– Dan Goodin, <a target="_blank" href="https://arstechnica.com/security/2024/02/github-besieged-by-millions-of-malicious-repositories-in-ongoing-attack/">Ars technica</a></p>
</blockquote>
<p>Because search engines and GitHub’s own search rankings favor recent activity, these cloned repositories often float to the top – then they lure unsuspecting developers into pulling code that may contain malware.</p>
<p>One of my <a target="_blank" href="http://github.com/brooks-code/miniature-fortnight">repositories</a> has been targeted by such an attack, prompting me to monitor it closely. This guide offers tips to spot malicious repository clones before they catch you off guard.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-is-a-repository-confusion-attack">What is a Repository Confusion Attack?</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-supply-chain-attacks">Supply Chain Attacks</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-basic-mitigation-strategies">🛡️ Basic Mitigation Strategies</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-verify-the-contributors-profiles">Verify the contributors profiles</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-search-for-clone-repositories">Search for clone repositories</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-examine-the-commit-pattern">Examine the commit pattern</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-examine-the-commit-history">Examine the commit history</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-examine-the-commit-contents">Examine the commit contents</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-compare-the-concerned-files">Compare the concerned files</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-some-information-about-the-malware">Some information about the malware</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-action-time">Action Time</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-more-resources">More Resources</a></p>
</li>
</ol>
<h2 id="heading-what-is-a-repository-confusion-attack"><strong>What is a Repository Confusion Attack?</strong></h2>
<p>A repository confusion attack involves:</p>
<ul>
<li><p>Cloning legitimate repositories.</p>
</li>
<li><p>Injecting malicious code into the clone.</p>
</li>
<li><p>Uploading the clone.</p>
</li>
<li><p>Spreading through various unaware actors.</p>
</li>
</ul>
<h3 id="heading-supply-chain-attacks"><strong>Supply Chain Attacks</strong></h3>
<p>If you search for repository confusion on the internet, you'll find out it's a type of <em>supply chain attack</em>.</p>
<p>A supply chain attack is an <em>indirect</em> threat where hackers try infiltrating a system by targeting a trusted third-party or software component, rather than attacking the primary target directly.</p>
<p>It's not the first time this has happened. Before GitHub was targeted, PyPI was attacked in 2023 with <a target="_blank" href="https://arstechnica.com/information-technology/2023/01/more-malicious-packages-posted-to-online-repository-this-time-its-pypi/">fake packages</a> posing as legitimate. These packages lured negligent pip users into downloading malicious payloads (containing in most cases <a target="_blank" href="https://en.wikipedia.org/wiki/Infostealer">infostealer malware</a>).</p>
<h2 id="heading-basic-mitigation-strategies"><strong>🛡️ Basic Mitigation Strategies</strong></h2>
<p><strong>Before</strong> using any repository, make sure you follow these steps and take these precautions.</p>
<h3 id="heading-verify-the-contributors-profiles"><strong>Verify the contributors profiles</strong></h3>
<p>That's a first check: if you see a rather empty GitHub profile – one without reputation that contains just one repository but with a lot of daily commits to it – well, that's a bit suspicious.</p>
<p>In the fake repository, the original author will be listed as a contributor, too. Check that profile. You should be able to find the legitimate repository and do some comparisons.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752335573817/c39aca11-2605-47a2-8a6b-aded16547783.png" alt="GitHub screenshot of a repository contributors" width="316" height="156" loading="lazy"></p>
<p>In the above screenshot you can see solotech143, my evil doppelgänger <em>(he’s been taken down since)</em>.</p>
<h3 id="heading-search-for-clone-repositories"><strong>Search for clone repositories</strong></h3>
<p>You can do a GitHub search by repository name and sort the results by most recent first. Malicious repositories tend to appear at the top of the search results because they are updated more frequently. The original repository might be hidden deeper in the search results.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752335785307/943c6dd3-aa28-4d72-b63a-65736de06dcf.png" alt="GitHub clone search results." width="1172" height="314" loading="lazy"></p>
<p>It’s like clone wars.</p>
<p><strong>This is where it’s dangerous:</strong> users generally click on the first few search results, and in that type of attack, you’re almost guaranteed to see the attacker’s fake repository at the top of the results. The attacker achieves that by giving the fake repository regular fresh commits (and sometimes even a few stars!).</p>
<p>In my case, the original repository is a submission for the HackaViz 2025 competition. Hackathons offer a good attack surface because, beyond the fact they draw niche communities, they are also time sensitive.</p>
<p>Now, let’s move forward a year and imagine Hackaviz 2026 is starting soon. The attacker has easily outranked the untouched original submission. Which repository is most likely to be visited when future competitors – unaware of the scam – will look for the previous submissions?</p>
<h3 id="heading-examine-the-commit-pattern"><strong>Examine the commit pattern</strong></h3>
<p>Here’s when things take a weird turn. Malicious clones are run by automated agents, so the commit history fits a pattern that is rather unusual for a human. Of course, you can automate for many legitimate reasons but… this will always follow a clear goal and there will always be a human-touch at some point. In this case, commits are not adding up.</p>
<p>Let's see how that looks in the screenshots below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752335872381/1238dee9-3568-4d2b-88bb-f63258ffb045.png" alt="1238dee9-3568-4d2b-88bb-f63258ffb045" width="390" height="197" loading="lazy"></p>
<p>Regular like a clock...</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752335891381/77f835fe-cccf-409f-85d7-789f918d4aa3.png" alt="A GitHub screenshot of a very active contribution activity.." width="764" height="602" loading="lazy"></p>
<p>... and hyperactive!</p>
<h3 id="heading-examine-the-commit-history"><strong>Examine the commit history</strong></h3>
<p>You can’t! And that's the weird part. You're just able to see the last and the initial commit. So why is it hiding all of them? Do you like it when someone hide things from you?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752336385127/6274dd87-0a97-4c38-8849-9d547b9edb22.png" alt="A github commits history screenshot for one day." width="1304" height="225" loading="lazy"></p>
<p>For July 10th, we should be able to see 11 commits, where are the ten others?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752336355084/4c7c343d-2000-4359-ae98-dcc98fb08732.png" alt="A github commits history screenshot for a whole period." width="1307" height="369" loading="lazy"></p>
<p>Well, you can only check the first and last commit. That is not a lot for a repository that has more than 2000 commits registered.</p>
<h3 id="heading-examine-the-commit-contents"><strong>Examine the commit contents</strong></h3>
<p>Well, since I can always check the last commit, I checked some of them. They share the same pattern: the bot is constantly looping over the README file doing the same modifications. As you can see in the screenshot below, it’s updating the file with links to an infected release.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752336493881/e8b57b4c-4d13-44f8-bca6-bd8fbad2738c.png" alt="A github screenshot of commits to a malicious repository." width="1382" height="800" loading="lazy"></p>
<p>Above you can see an AI agent stuck in the Readme loop of change.</p>
<p>Human edits are more varied. In a human-driven project, you will see a large mix of commits: feature commits, exploratory experiments, bug fixes, styling tweaks, and sometimes reverts. A bot clone will often just overwrite files, bump versions, or re-inject the same malicious payload repeatedly with no real contribution to the codebase.</p>
<h3 id="heading-compare-the-concerned-files"><strong>Compare the concerned files</strong></h3>
<p>This is where common sense comes handy. So, you have two README's:</p>
<ol>
<li><p>The <a target="_blank" href="https://web.archive.org/web/20250711182419/https://github.com/solotech143/miniature-fortnight/blob/main/README.md">first</a> consists of AI-generated content that is cluttered with emojis and low-value information. It is designed solely to entice you into clicking the download link of the release.</p>
</li>
<li><p>The <a target="_blank" href="https://github.com/brooks-code/miniature-fortnight/blob/main/README.md">other</a> follows <a target="_blank" href="https://www.freecodecamp.org/news/how-to-write-a-good-readme-file/">best practices</a> for creating a good README file. It is accurate and well-structured and functions as a valuable helper and explainer to the code. It also goes deep into the most important aspects of the project. This is usually a good sign that a repository is organic and genuine.</p>
</li>
</ol>
<h3 id="heading-some-information-about-the-malware"><strong>Some information about the malware</strong></h3>
<p>What do we have so far? Well, a suspicious link in a phishy, AI-generated README file that is consistent with a very suspicious pattern in the commit history.</p>
<p>Now, let’s have a closer look at that dubious release and let’s see what an online antivirus scanner might reveal about it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752336589656/124aecf2-39e9-4158-9a06-f0fd59cbf8c1.png" alt="A  github screenshot of commits to a malicious repository." width="1216" height="404" loading="lazy"></p>
<p>The malware is packed only in the miniature-fortnight-v1.7.6.zip release.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752336609780/7eaf7fc8-73f2-4d9d-b169-1d5c50ce84f2.png" alt="A malware analysis result." width="1317" height="757" loading="lazy"></p>
<p>Above you can see the result of a scan with an online scanner.</p>
<p>The .zip file contains <strong>only</strong> four files:</p>
<ul>
<li><p>config.txt</p>
</li>
<li><p>launch.bat</p>
</li>
<li><p>lua51.dll</p>
</li>
<li><p>luajit.exe</p>
</li>
</ul>
<p>These files are <strong>totally unrelated</strong> to the source project (a Python data science project with Jupyter notebooks combined to a React app using three.js).</p>
<p>I will not go into the detail in this article. But for the curious ones, it's an infostealer malware (a malware that will exfiltrate your credentials and other precious information about your configuration) similar to the one described in <a target="_blank" href="https://www.trendmicro.com/en_us/research/25/c/ai-assisted-fake-github-repositories.html#">detail here</a>.</p>
<h2 id="heading-action-time"><strong>Action Time</strong></h2>
<p>If you discover a potentially malicious repository, here are some steps you can take:</p>
<ol>
<li><p>Document some evidence.</p>
</li>
<li><p>Notify the original repository maintainers.</p>
</li>
<li><p>Report the malicious clone to GitHub.</p>
</li>
</ol>
<p>Reporting a repository or a profile on GitHub is easy and fast. Go to the user’s profile page, click “Block or report” in the left sidebar and choose “Report abuse” in the pop-up. You will have to complete a short contact form with some details about the behavior before submitting. If needed, you can find more information on <a target="_blank" href="https://docs.github.com/en/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam#reporting-a-user">GitHub</a>.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>This is a description of just one attack, from the perspective of someone who found out that one of his repository had been targeted. There are likely cases of more sophisticated attacks. But the clone repository flood we can see on GitHuB is definitely massive low quality automation. Quantity over quality.<br>To be honest, I'm quite surprised algorithms crafted at GitHub didn't manage to spot this one.</p>
<p>This also raises questions related to AI.</p>
<ul>
<li><p>What happens when LLMs are trained on malicious content? That’s a more general question about <a target="_blank" href="https://arstechnica.com/information-technology/2024/01/ai-poisoning-could-turn-open-models-into-destructive-sleeper-agents-says-anthropic/">AI poisoning</a>.</p>
</li>
<li><p>A human might easily spot the patterns and the low quality content <em>for now</em>. But..</p>
<ul>
<li><p>Imagine you are using coding agents, many of them. Will the agents pick-up the malicious clone instead of the original one? How to distinguish the repositories from an automaton's perspective?</p>
</li>
<li><p>The attackers <strong>will</strong> refine their tactics, making the clones more human-like and therefore luring us more easily into their traps.</p>
</li>
</ul>
</li>
<li><p>This is really a situation that makes me wonder about the early days of Google. Back then, the company had to fight huge amounts of spam due to keyword stuffing and manipulative SEO tactics. Will big tech companies have to go through a <a target="_blank" href="https://en.wikipedia.org/wiki/Timeline_of_Google_Search#Full_timeline">Florida update</a> moment to face the rise of AI generated spam ?</p>
</li>
</ul>
<h2 id="heading-more-resources"><strong>More Resources</strong></h2>
<ul>
<li><p><a target="_blank" href="https://www.trendmicro.com/en_be/research/23/j/infection-techniques-across-supply-chains-and-codebases.html">A detailed description of the attack</a></p>
</li>
<li><p><a target="_blank" href="https://www.cisa.gov/sites/default/files/publications/ESF_SECURING_THE_SOFTWARE_SUPPLY_CHAIN_DEVELOPERS.PDF">Complete safety recommendations</a></p>
</li>
</ul>
<p><strong>Stay Informed, Stay Secure!</strong></p>
<p>A <strong>cheat-sheet</strong> is also available on my <a target="_blank" href="https://github.com/brooks-code/repo-confusion-guard">GitHub</a>. Feel free to contribute to it!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How To Deploy a Next.js App To Vercel With GitHub Actions ]]>
                </title>
                <description>
                    <![CDATA[ Vercel is a cloud platform or Platform-as-a-Service (PaaS) designed to help frontend developers create, preview, and deploy web applications swiftly and efficiently. In this tutorial, we’ll focus on deploying a Next.js application to Vercel using Git... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/deploy-to-vercel-with-github-actions/</link>
                <guid isPermaLink="false">684871b16bc1898625c952fd</guid>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub Actions ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chidiadi Anyanwu ]]>
                </dc:creator>
                <pubDate>Tue, 10 Jun 2025 17:56:01 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749577622920/8e35a6c1-3f4f-49a3-a4fe-dba80e24eec3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Vercel is a cloud platform or Platform-as-a-Service (PaaS) designed to help frontend developers create, preview, and deploy web applications swiftly and efficiently. In this tutorial, we’ll focus on deploying a Next.js application to Vercel using GitHub Actions.</p>
<p>In a <a target="_blank" href="https://www.freecodecamp.org/news/how-to-build-a-simple-portfolio-blog-with-nextjs/">previous article</a>, we built a Next.js portfolio blog. Here, you’ll learn how to deploy it on Vercel with <a target="_blank" href="https://www.freecodecamp.org/news/automate-cicd-with-github-actions-streamline-workflow/">GitHub Actions</a>.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To be able to deploy your project, you should have a GitHub repository of the project (you can still follow along if you already have a Next.js project), and a Vercel account. <a target="_blank" href="https://github.com/chidiadi01/simple-writer-portfolio">Here is the GitHub repository that we’ll be working with</a>. You can clone it to follow along.</p>
<h2 id="heading-how-to-deploy-your-next-app">How to Deploy Your Next App</h2>
<h3 id="heading-create-vercel-token-and-add-it-to-your-secrets-in-github">Create Vercel Token and Add it to Your Secrets in GitHub</h3>
<p>In your Vercel account, go to Settings, then go to Tokens.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749036906930/1c483351-0e78-4392-a948-53921ba2916c.png" alt="Vercel account settings tokens." class="image--center mx-auto" width="1354" height="602" loading="lazy"></p>
<p>In the Create Token section, enter a name for your token, select an expiration date and click “create”.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749037009419/2a48b48d-31c4-4b72-a281-dd8eb689770d.png" alt="Creating a vercel token" class="image--center mx-auto" width="1347" height="593" loading="lazy"></p>
<p>You should see a success message with your token. Next, go to your GitHub repository, and click on the “Settings“ tab.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749037335441/1a46fb8b-8f3c-4d44-8ad5-c7de3340ef2b.png" alt="Vercel, token created success message." class="image--center mx-auto" width="1339" height="599" loading="lazy"></p>
<p>In the Settings tab, go to Secrets and Variables on the sidebar, then click on Actions.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749037726010/5ca33111-0dbe-4e3e-bde5-91a8e518f05b.png" alt="Actions secrets in GitHub repository settings." class="image--center mx-auto" width="1100" height="601" loading="lazy"></p>
<p>You’ll see a section for adding secrets. Add a secret named <code>VERCEL_TOKEN</code>, and paste the token there.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749037787198/53b42a96-ad79-4895-aba0-abe6d79eceb5.png" alt="vercel token, project id, org id." class="image--center mx-auto" width="880" height="334" loading="lazy"></p>
<p>The Vercel token is a token used to authenticate the GitHub runner. The Vercel CLI installed on the GitHub runner is going to execute the commands with your account. So, instead of it having to login, it uses the access token to verify that it was actually authorized by you to take the actions.</p>
<p>The Organization ID is used to tell Vercel which organization or team account the project should be created under.</p>
<p>The Project ID then tells Vercel the specific project you want to deploy. Just like the Organization ID, it is a unique identifier.</p>
<h3 id="heading-install-the-vercel-cli-and-login">Install the Vercel CLI and Login</h3>
<p>Use the command below to install vercel CLI globally on your computer:</p>
<pre><code class="lang-bash">npm install -g vercel
</code></pre>
<p>Then <a target="_blank" href="https://vercel.com/docs/cli/login">log into the CLI</a> with the following command:</p>
<pre><code class="lang-bash">vercel login
</code></pre>
<p>Use one of the options to login.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749312895981/93c13b75-83da-4da7-b5c5-17572d126ce4.png" alt="login methods" class="image--center mx-auto" width="762" height="159" loading="lazy"></p>
<p>I used GitHub. Select one with your arrow keys, and click enter.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749312974078/2f470d5a-9e73-44be-b520-5179da61f86b.png" alt="login success" class="image--center mx-auto" width="1335" height="571" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749038078744/bbb5a43d-66a4-4531-a27c-12442c977568.png" alt="vercel login" class="image--center mx-auto" width="921" height="140" loading="lazy"></p>
<h3 id="heading-create-a-vercel-project-from-your-local-directory">Create a Vercel Project from Your Local Directory</h3>
<p>Navigate to your project directory if you’re not already in it. If you have already created a project on Vercel through the web interfce, use the <a target="_blank" href="https://vercel.com/docs/cli/link">vercel link</a> command to link your current directory to the Vercel project. If you don’t already have a Vercel project, just type <code>vercel</code> in the CLI and follow the prompts to setup the project.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749314606959/f6d7a71c-edc5-48f2-81ca-4fd3a92c44da.png" alt="Create new Vercel project" class="image--center mx-auto" width="604" height="52" loading="lazy"></p>
<p>With that, Vercel will create a <code>.vercel</code> folder in the project. Open it, and go to the <code>project.json</code> file.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749038798352/23d8b1b9-5bbf-43df-9444-7adf1f7a9c2f.png" alt="Project.json" class="image--center mx-auto" width="232" height="105" loading="lazy"></p>
<p>In the file, you should see your project ID and organization ID. Copy them and create secrets in your GitHub repository for each one.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749037787198/53b42a96-ad79-4895-aba0-abe6d79eceb5.png" alt="vercel token, org ID, project ID." class="image--center mx-auto" width="880" height="334" loading="lazy"></p>
<h3 id="heading-create-your-github-workflow-file">Create your GitHub Workflow File</h3>
<p>At the root of your project folder, create the <code>.github/workflow</code> folder. Then create a workflow file called <code>vercel_deploy.yml</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749039652451/f3c3a4ca-5d19-4866-a69d-9f4d3a760d8f.png" alt="f3c3a4ca-5d19-4866-a69d-9f4d3a760d8f" class="image--center mx-auto" width="233" height="45" loading="lazy"></p>
<p>In the file, write this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Vercel</span> <span class="hljs-string">Production</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">env:</span>
  <span class="hljs-attr">VERCEL_ORG_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.VERCEL_ORG_ID</span> <span class="hljs-string">}}</span>
  <span class="hljs-attr">VERCEL_PROJECT_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.VERCEL_PROJECT_ID</span> <span class="hljs-string">}}</span>
<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span>
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'01-simple-blog/**'</span>  

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">Deploy-Production:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">defaults:</span>
      <span class="hljs-attr">run:</span>
        <span class="hljs-attr">working-directory:</span> <span class="hljs-number">01</span><span class="hljs-string">-simple-blog</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Vercel</span> <span class="hljs-string">CLI</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">install</span> <span class="hljs-string">--global</span> <span class="hljs-string">vercel@latest</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Pull</span> <span class="hljs-string">Vercel</span> <span class="hljs-string">Environment</span> <span class="hljs-string">Information</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">vercel</span> <span class="hljs-string">pull</span> <span class="hljs-string">--yes</span> <span class="hljs-string">--environment=production</span> <span class="hljs-string">--token=${{</span> <span class="hljs-string">secrets.VERCEL_TOKEN</span> <span class="hljs-string">}}</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Project</span> <span class="hljs-string">Artifacts</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">vercel</span> <span class="hljs-string">build</span> <span class="hljs-string">--prod</span> <span class="hljs-string">--token=${{</span> <span class="hljs-string">secrets.VERCEL_TOKEN</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">continue-on-error:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">Project</span> <span class="hljs-string">Artifacts</span> <span class="hljs-string">to</span> <span class="hljs-string">Vercel</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">vercel</span> <span class="hljs-string">deploy</span> <span class="hljs-string">--prebuilt</span> <span class="hljs-string">--prod</span> <span class="hljs-string">--token=${{</span> <span class="hljs-string">secrets.VERCEL_TOKEN</span> <span class="hljs-string">}}</span>
</code></pre>
<p>This is the workflow file for my <a target="_blank" href="https://github.com/chidiadi01/simple-writer-portfolio/blob/main/.github/workflows/vercel_deploy.yml">simple-writer-portfolio</a> project.</p>
<p>First, we have the environment variables:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">env:</span>
  <span class="hljs-attr">VERCEL_ORG_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.VERCEL_ORG_ID</span> <span class="hljs-string">}}</span>
  <span class="hljs-attr">VERCEL_PROJECT_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.VERCEL_PROJECT_ID</span> <span class="hljs-string">}}</span>
<span class="hljs-comment"># Other code</span>
</code></pre>
<p>Then we have the trigger. This triggers when I push to the main branch, affecting files in the <code>01-simple-blog</code> subdirectory.</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Previous code</span>
<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span>
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'01-simple-blog/**'</span>  
<span class="hljs-comment"># Other code</span>
</code></pre>
<p>Then we have the job definition. Here, I defined a job “Deploy-Production” that runs on Ubuntu. By default, all commands there will run in the <code>01-simple-blog</code> directory, which is equivalent to running <code>cd 01-simple-blog</code> from the root before running commands on the shell. I did this because the Next.js project is in that directory, where the <code>package.json</code> is located.</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Previous code</span>
<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">Deploy-Production:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">defaults:</span>
      <span class="hljs-attr">run:</span>
        <span class="hljs-attr">working-directory:</span> <span class="hljs-number">01</span><span class="hljs-string">-simple-blog</span>
<span class="hljs-comment"># Other code</span>
</code></pre>
<p>Then the steps involved:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Previous code</span>
 <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Vercel</span> <span class="hljs-string">CLI</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">install</span> <span class="hljs-string">--global</span> <span class="hljs-string">vercel@latest</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Pull</span> <span class="hljs-string">Vercel</span> <span class="hljs-string">Environment</span> <span class="hljs-string">Information</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">vercel</span> <span class="hljs-string">pull</span> <span class="hljs-string">--yes</span> <span class="hljs-string">--environment=production</span> <span class="hljs-string">--token=${{</span> <span class="hljs-string">secrets.VERCEL_TOKEN</span> <span class="hljs-string">}}</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Project</span> <span class="hljs-string">Artifacts</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">vercel</span> <span class="hljs-string">build</span> <span class="hljs-string">--prod</span> <span class="hljs-string">--token=${{</span> <span class="hljs-string">secrets.VERCEL_TOKEN</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">continue-on-error:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">Project</span> <span class="hljs-string">Artifacts</span> <span class="hljs-string">to</span> <span class="hljs-string">Vercel</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">vercel</span> <span class="hljs-string">deploy</span> <span class="hljs-string">--prebuilt</span> <span class="hljs-string">--prod</span> <span class="hljs-string">--token=${{</span> <span class="hljs-string">secrets.VERCEL_TOKEN</span> <span class="hljs-string">}}</span>
</code></pre>
<p>With these steps, Vercel is first installed on the GitHub runner. Then the vercel environment information is pulled. The project is built with <code>vercel build</code>, and the pre-built artifacts are then pushed to Vercel.</p>
<h3 id="heading-push-to-github-and-watch-your-code-deploy">Push to GitHub and watch your code deploy</h3>
<p>Stage your changes, if any:</p>
<pre><code class="lang-bash">git add .
</code></pre>
<p>Commit the changes:</p>
<pre><code class="lang-bash">git commit -m <span class="hljs-string">"Added GitHub Actions workflow"</span>
</code></pre>
<p>And push:</p>
<pre><code class="lang-bash">git push origin
</code></pre>
<p>Now, go to your repository online, and check the deployment.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749042451313/3fa5a9a8-c14b-4f55-9e04-c51310500c3f.png" alt="3fa5a9a8-c14b-4f55-9e04-c51310500c3f" class="image--center mx-auto" width="899" height="46" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749042384631/d941710c-697d-46b8-a106-30f6ac3cedc3.png" alt="Workflow run logs" class="image--center mx-auto" width="981" height="492" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>With your basic GitHub workflow in place, you can now make changes to your code, push to GitHub, and have it deploy automatically. Though Vercel allows you to connect your repository directly, this method provides you with more flexibility and customizability. If you enjoyed this article, share it with others. You can also reach me on <a target="_blank" href="https://linkedin.com/in/chidiadi-anyanwu">LinkedIn</a> or <a target="_blank" href="https://x.com/chidiadi01">X</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
