{"id":5449,"date":"2024-12-04T03:00:29","date_gmt":"2024-12-04T03:00:29","guid":{"rendered":"https:\/\/algocademy.com\/blog\/mastering-version-control-systems-a-comprehensive-guide-to-git-2\/"},"modified":"2026-10-22T06:57:23","modified_gmt":"2026-10-22T06:57:23","slug":"mastering-version-control-systems-a-comprehensive-guide-to-git-2","status":"publish","type":"post","link":"https:\/\/algocademy.com\/blog\/mastering-version-control-systems-a-comprehensive-guide-to-git-2\/","title":{"rendered":"What Is Git? Version Control Basics and Commands"},"content":{"rendered":"<p><strong>Git is a version control system: a tool that records every change to your project as a snapshot, so you can see what changed, go back to an earlier state, and work on the same code as other people without overwriting their work.<\/strong> It runs on your own computer, and a service such as GitHub or GitLab is optional and only used to share the project. This guide explains the ideas first, then walks through the commands you use every day, in the order you will meet them.<\/p>\n<h2>What is version control?<\/h2>\n<p>Version control (also called source control or source code management) keeps a history of your files. Without it, you end up with folders named <code>final<\/code>, <code>final2<\/code> and <code>final-really<\/code>. With it, every saved state is labelled with who changed what, when, and why, and you can compare or restore any of them.<\/p>\n<p>It solves three everyday problems:<\/p>\n<ul>\n<li><strong>Undo:<\/strong> a change broke the program, and you want the version from yesterday.<\/li>\n<li><strong>Experiments:<\/strong> you want to try a risky idea without touching the working code.<\/li>\n<li><strong>Teamwork:<\/strong> two people edit the same project, and their changes need to be combined.<\/li>\n<\/ul>\n<p>Older systems such as Subversion keep the history on one central server. Git is <em>distributed<\/em>: every copy of the project (a <em>clone<\/em>) contains the full history, so you can commit, browse history and create branches offline. Git was created by Linus Torvalds in 2005 for developing the Linux kernel (<a href=\"https:\/\/git-scm.com\/book\/en\/v2\/Getting-Started-A-Short-History-of-Git\">Pro Git: A Short History of Git<\/a>) and is free and open source under the GPL version 2. For the wider case for version control, see <a href=\"https:\/\/algocademy.com\/blog\/the-importance-of-version-control-in-software-development\/\">the importance of version control in software development<\/a>.<\/p>\n<h2>Git vs. GitHub<\/h2>\n<p>Git is the program on your machine. GitHub, GitLab and Bitbucket are websites that host Git repositories and add features around them, such as pull requests and issue tracking. You can use Git for years without any of them.<\/p>\n<h2>The key terms<\/h2>\n<ul>\n<li><strong>Repository (repo):<\/strong> a project folder plus its history, stored in a hidden <code>.git<\/code> folder.<\/li>\n<li><strong>Commit:<\/strong> a snapshot of your files with a message. Each commit has an ID (a long hash, usually shown shortened) and points to the commit before it.<\/li>\n<li><strong>Working directory:<\/strong> the files you see and edit.<\/li>\n<li><strong>Staging area (index):<\/strong> where you collect the changes that will go into the next commit.<\/li>\n<li><strong>Branch:<\/strong> a movable name for a line of commits. The default branch is usually called <code>main<\/code>.<\/li>\n<li><strong>Remote:<\/strong> another copy of the repository, usually on a server. The default name is <code>origin<\/code>.<\/li>\n<\/ul>\n<p>A change moves through three places: you edit it in the working directory, <code>git add<\/code> puts it in the staging area, and <code>git commit<\/code> stores it in the repository. The staging area lets you commit only part of your work, so each commit is one logical change.<\/p>\n<h2>Install and configure Git<\/h2>\n<p>Download Git from <a href=\"https:\/\/git-scm.com\/\">git-scm.com<\/a>, or use your package manager. Then check it works and tell Git who you are, because the name and email are stored in every commit:<\/p>\n<pre><code class=\"language-bash\">git --version\ngit config --global user.name \"Your Name\"\ngit config --global user.email \"you@example.com\"\ngit config --global init.defaultBranch main<\/code><\/pre>\n<p>The last line makes new repositories start on <code>main<\/code> rather than the older default, <code>master<\/code>.<\/p>\n<h2>Your first repository and commit<\/h2>\n<p>Create a folder, turn it into a repository, and look at its state:<\/p>\n<pre><code class=\"language-bash\">mkdir my-project\ncd my-project\ngit init\necho \"# My Project\" &gt; README.md\ngit status<\/code><\/pre>\n<p>Git reports that <code>README.md<\/code> is untracked:<\/p>\n<pre><code class=\"language-text\">On branch main\n\nNo commits yet\n\nUntracked files:\n  (use \"git add &lt;file&gt;...\" to include in what will be committed)\n\tREADME.md\n\nnothing added to commit but untracked files present (use \"git add\" to track)<\/code><\/pre>\n<p>Stage the file and commit it:<\/p>\n<pre><code class=\"language-bash\">git add README.md\ngit commit -m \"Add README\"\ngit log --oneline<\/code><\/pre>\n<pre><code class=\"language-text\">[main (root-commit) 8334533] Add README\n 1 file changed, 1 insertion(+)\n create mode 100644 README.md\n8334533 Add README<\/code><\/pre>\n<p>Your hash will be different from <code>8334533<\/code>, since it depends on content, author and time. That is normal.<\/p>\n<h2>The daily loop: status, diff, add, commit<\/h2>\n<p>Most of your Git use is this cycle. Say you add a file, commit it, then edit it:<\/p>\n<pre><code class=\"language-bash\">echo \"print('hello')\" &gt; app.py\ngit add app.py\ngit commit -m \"Add app.py\"\necho \"print('hello, world')\" &gt; app.py\ngit diff<\/code><\/pre>\n<pre><code class=\"language-text\">diff --git a\/app.py b\/app.py\nindex b376c99..b2204a3 100644\n--- a\/app.py\n+++ b\/app.py\n@@ -1 +1 @@\n-print('hello')\n+print('hello, world')<\/code><\/pre>\n<p><code>git diff<\/code> shows changes that are not yet staged. After <code>git add app.py<\/code>, the same change appears under <code>git diff --staged<\/code>, and <code>git status<\/code> tells you which files are modified, staged or untracked. Commit with <code>git commit -m \"Greet the world\"<\/code>, and <code>git log --oneline<\/code> lists the history, newest first:<\/p>\n<pre><code class=\"language-text\">cbac9db Greet the world\n6a53071 Add app.py\n8334533 Add README<\/code><\/pre>\n<h3>Commands you will use most<\/h3>\n<table>\n<thead>\n<tr>\n<th>Command<\/th>\n<th>What it does<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>git init<\/code><\/td>\n<td>Start a repository in the current folder<\/td>\n<\/tr>\n<tr>\n<td><code>git clone &lt;url&gt;<\/code><\/td>\n<td>Copy a remote repository, history included<\/td>\n<\/tr>\n<tr>\n<td><code>git status<\/code><\/td>\n<td>Show modified, staged and untracked files<\/td>\n<\/tr>\n<tr>\n<td><code>git add &lt;file&gt;<\/code><\/td>\n<td>Stage a change for the next commit<\/td>\n<\/tr>\n<tr>\n<td><code>git commit -m \"msg\"<\/code><\/td>\n<td>Save the staged changes as a commit<\/td>\n<\/tr>\n<tr>\n<td><code>git diff<\/code> \/ <code>git diff --staged<\/code><\/td>\n<td>Show unstaged \/ staged changes<\/td>\n<\/tr>\n<tr>\n<td><code>git log --oneline<\/code><\/td>\n<td>Show the history, one line per commit<\/td>\n<\/tr>\n<tr>\n<td><code>git switch &lt;branch&gt;<\/code><\/td>\n<td>Move to another branch<\/td>\n<\/tr>\n<tr>\n<td><code>git push<\/code> \/ <code>git pull<\/code><\/td>\n<td>Send your commits to a remote \/ bring in new ones<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Ignore files you should not commit<\/h2>\n<p>Build output, logs, virtual environments and secrets do not belong in a repository. List patterns in a file named <code>.gitignore<\/code>:<\/p>\n<pre><code class=\"language-text\">__pycache__\/\n*.log\n.env<\/code><\/pre>\n<p>Git then stops showing matching untracked files in <code>git status<\/code>. It does not remove files that were already committed; for those, use <code>git rm --cached &lt;file&gt;<\/code>. If a password or API key was committed, treat it as leaked and change it, because deleting it in a later commit leaves it in the history.<\/p>\n<h2>Branching and merging<\/h2>\n<p>A branch lets you work on a feature or a fix without disturbing <code>main<\/code>. Creating one is cheap, because a branch is just a name pointing to a commit.<\/p>\n<pre><code class=\"language-bash\">git switch -c greeting\n# edit files, then:\ngit commit -am \"Change greeting\"\ngit switch main\ngit merge greeting<\/code><\/pre>\n<p><code>git switch -c<\/code> creates a branch and moves to it. (<code>git checkout -b<\/code> does the same and is older; <code>git switch<\/code> and <code>git restore<\/code> were added in Git 2.23 to split up what <code>checkout<\/code> did.) Note that <code>git commit -am<\/code> only stages files Git already tracks, so use <code>git add<\/code> first for new files.<\/p>\n<p>If <code>main<\/code> has not moved since you branched, the merge just slides <code>main<\/code> forward. If both branches changed the same lines, Git stops and asks you to decide.<\/p>\n<h3>Resolve a merge conflict<\/h3>\n<p>Suppose both <code>main<\/code> and <code>greeting<\/code> changed the one line in <code>app.py<\/code>. Merging gives:<\/p>\n<pre><code class=\"language-text\">Auto-merging app.py\nCONFLICT (content): Merge conflict in app.py\nAutomatic merge failed; fix conflicts and then commit the result.<\/code><\/pre>\n<p><code>git status -sb<\/code> marks the file as <code>UU<\/code> (unmerged on both sides), and the file now contains markers:<\/p>\n<pre><code class=\"language-text\">&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD\nprint('hi from main')\n=======\nprint('hi from greeting')\n&gt;&gt;&gt;&gt;&gt;&gt;&gt; greeting<\/code><\/pre>\n<p>Between <code>&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD<\/code> and <code>=======<\/code> is your current branch&#8217;s version; below it is the incoming one. Edit the file to the result you want and delete all three marker lines, then finish the merge:<\/p>\n<pre><code class=\"language-bash\">git add app.py\ngit commit -m \"Merge greeting\"\ngit log --oneline --graph<\/code><\/pre>\n<pre><code class=\"language-text\">*   bd0af22 Merge greeting\n|\\  \n| * 43801a3 Change greeting\n* | 1d66bd6 Change greeting on main\n|\/  \n* cbac9db Greet the world\n* 6a53071 Add app.py\n* 8334533 Add README<\/code><\/pre>\n<p>Changed your mind halfway? <code>git merge --abort<\/code> returns you to the state before the merge. Once a branch is merged, delete it with <code>git branch -d greeting<\/code>; Git refuses if the branch has unmerged work, which protects you.<\/p>\n<h2>Work with a remote<\/h2>\n<p>A remote is a copy of the repository somewhere else. To publish a local project, create an empty repository on your host, add it as <code>origin<\/code>, and push:<\/p>\n<pre><code class=\"language-bash\">git remote add origin https:\/\/github.com\/you\/my-project.git\ngit push -u origin main<\/code><\/pre>\n<p>The <code>-u<\/code> flag remembers that your local <code>main<\/code> tracks <code>origin\/main<\/code>, so later a plain <code>git push<\/code> or <code>git pull<\/code> is enough. To get an existing project, run <code>git clone &lt;url&gt;<\/code>, which downloads the full history and sets up <code>origin<\/code> for you. <code>git remote -v<\/code> lists your remotes.<\/p>\n<p>Two commands bring in other people&#8217;s work. <code>git fetch<\/code> downloads new commits without touching your files; <code>git pull<\/code> is a fetch followed by a merge. If you have local commits and the remote has new ones too, recent Git versions ask you to choose how to combine them:<\/p>\n<pre><code class=\"language-text\">hint: You have divergent branches and need to specify how to reconcile them.\nhint: You can do so by running one of the following commands sometime before\nhint: your next pull:\nhint: \nhint:   git config pull.rebase false  # merge\nhint:   git config pull.rebase true   # rebase<\/code><\/pre>\n<p>Either is fine for a beginner. <code>git pull --no-rebase<\/code> merges, as shown earlier, and <code>git pull --rebase<\/code> replays your commits on top of the new ones for a straighter history. Never rebase commits that other people already have.<\/p>\n<p>On a team, you rarely push straight to <code>main<\/code>. The usual pattern is: create a branch, push it, and open a pull request on the host so others can review it. See <a href=\"https:\/\/algocademy.com\/blog\/understanding-pull-requests-in-github-a-comprehensive-guide\/\">understanding pull requests in GitHub<\/a> for that part.<\/p>\n<h2>Undo things safely<\/h2>\n<p>Which undo command you need depends on how far the change has travelled.<\/p>\n<table>\n<thead>\n<tr>\n<th>Situation<\/th>\n<th>Command<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Discard unstaged edits to a file<\/td>\n<td><code>git restore app.py<\/code> (the edits are gone for good)<\/td>\n<\/tr>\n<tr>\n<td>Unstage a file, keep the edits<\/td>\n<td><code>git restore --staged f.txt<\/code><\/td>\n<\/tr>\n<tr>\n<td>Undo the last commit, keep the changes staged<\/td>\n<td><code>git reset --soft HEAD~1<\/code><\/td>\n<\/tr>\n<tr>\n<td>Undo a commit that is already pushed<\/td>\n<td><code>git revert &lt;commit&gt;<\/code><\/td>\n<\/tr>\n<tr>\n<td>Set work aside for a moment<\/td>\n<td><code>git stash<\/code>, later <code>git stash pop<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><code>git revert<\/code> adds a new commit that cancels an earlier one, so shared history is never rewritten:<\/p>\n<pre><code class=\"language-bash\">git revert --no-edit HEAD\ngit log --oneline | head -3<\/code><\/pre>\n<pre><code class=\"language-text\">[main 265fd43] Revert \"Bad change\"\n Date: Wed Oct 21 23:13:35 2026 +0200\n 1 file changed, 1 deletion(-)\n265fd43 Revert \"Bad change\"\n1af0978 Bad change\nbd0af22 Merge greeting<\/code><\/pre>\n<p><code>git reset<\/code> instead moves the branch back and rewrites history, so use it only on commits you have not pushed. <code>git reset --hard<\/code> also throws away your working changes, so check <code>git status<\/code> first.<\/p>\n<p><code>git stash<\/code> saves your uncommitted changes and gives you a clean working directory, which is handy when you must switch branches mid-task:<\/p>\n<pre><code class=\"language-text\">Saved working directory and index state WIP on main: 265fd43 Revert \"Bad change\"<\/code><\/pre>\n<p>Run <code>git stash pop<\/code> to bring the changes back.<\/p>\n<h3>Recover a deleted branch or lost commit<\/h3>\n<p>Committed work is hard to lose for good. Git keeps a log of where <code>HEAD<\/code> has been, called the reflog. If you force-delete a branch by mistake, find its last commit and name it again:<\/p>\n<pre><code class=\"language-bash\">git branch -D scratch\ngit reflog | head -4<\/code><\/pre>\n<pre><code class=\"language-text\">265fd43 HEAD@{0}: checkout: moving from scratch to main\nfd4f3e8 HEAD@{1}: commit: Scratch work\n265fd43 HEAD@{2}: checkout: moving from main to scratch<\/code><\/pre>\n<pre><code class=\"language-bash\">git branch scratch fd4f3e8<\/code><\/pre>\n<p>The branch is back with its commit. Reflog entries expire after a while (by default at least 30 days for unreachable commits), so recover soon.<\/p>\n<h2>A short list of habits that pay off<\/h2>\n<ul>\n<li><strong>Commit one logical change at a time<\/strong> with a message that says what and why: a short summary line of about 50 characters, then a blank line and details if needed.<\/li>\n<li><strong>Run <code>git status<\/code> and <code>git diff<\/code> before every commit<\/strong> to see what you are about to save.<\/li>\n<li><strong>Pull before you push<\/strong>, and keep branches short-lived, so conflicts stay small.<\/li>\n<li><strong>Branch for every feature or fix<\/strong> and keep <code>main<\/code> working.<\/li>\n<li><strong>Practise in a throwaway repository.<\/strong> Everything in this guide can be tried in a new folder, and deleting the folder resets it.<\/li>\n<\/ul>\n<h2>Where to go next<\/h2>\n<p>Once the basics feel natural, three topics are worth learning: <code>git rebase<\/code> for tidying a branch before sharing it, <a href=\"https:\/\/algocademy.com\/blog\/mastering-git-cherry-pick-a-comprehensive-guide-to-selective-commit-management\/\">git cherry-pick<\/a> for copying a single commit to another branch, and <a href=\"https:\/\/algocademy.com\/blog\/how-to-use-git-tags-for-versioning-releases-a-comprehensive-guide\/\">git tags<\/a> for marking releases. For more learning material, see <a href=\"https:\/\/algocademy.com\/blog\/the-best-git-tutorials-for-beginner-developers\/\">the best Git tutorials for beginner developers<\/a>. Git is also easier to use once you are comfortable writing code, since you will have real projects to track; you can build that skill with the step-by-step lessons at <a href=\"https:\/\/app.algocademy.com\/\">AlgoCademy<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>What Git and version control are, how commits, branches and remotes work, and the commands you use every day, with real output you can reproduce.<\/p>\n","protected":false},"author":1,"featured_media":5448,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[23],"tags":[],"class_list":["post-5449","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-problem-solving"],"_links":{"self":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts\/5449"}],"collection":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/comments?post=5449"}],"version-history":[{"count":2,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts\/5449\/revisions"}],"predecessor-version":[{"id":10077,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts\/5449\/revisions\/10077"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/media\/5448"}],"wp:attachment":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/media?parent=5449"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/categories?post=5449"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/tags?post=5449"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}