Git: Version Control Fundamentals
# Git: Version Control Fundamentals
These notes walk through a live command-line session on Git: from verifying the installation, through initializing a repository, staging and committing files, to cloning, branching, merging (including a full merge-conflict walkthrough), stashing, reverting, tagging, and working with a remote repository on GitHub. Every command is shown with the exact workflow used in the demo, and the questions students asked during the session are preserved with the full answers.
The session is built around two demos: a purely local repository (where files are created, staged, committed, and undone) and a shared repository on GitHub (where two users clone, branch, merge, and collide). The notes follow that order, and each section ends with the command list that was used, so the whole session doubles as a command reference you can replay on your own machine.
9.1 Introducing Git and the Three Ways to Work With It
Hook: Imagine you are editing the same file as five teammates, and everyone's changes have to survive. Git is the tool that makes that possible — and the very first thing it can do is turn one empty folder into a complete history machine with a single command.
9.1.1 Verifying Git Is Installed
The first command in the whole session is git init, but before initializing anything you need to know whether Git exists on your machine. Run the bare command git and press Enter. When Git is installed, it prints its usage text — "this is the way to use the git command," as it was put in the demo. The usage text is simply the list of all the things git knows how to do: git init, git add, git commit, git clone, and so on, along with a one-line description of each. Seeing that list is proof that the tool is on your machine.
If instead you see unrecognized command (Windows) or command not found (Linux and Mac), Git is not installed in your command-line interface, and you must install it first. A slightly deeper check, git --version, prints just the version number (for example git version 2.40.0); it is useful when a colleague asks "which Git are you on?" because different machines in a team can run different versions.
9.1.2 Three Ways to Work With Git
There are three ways of doing Git operations, and it helps to keep them straight because they all execute the same underlying commands:
- Command-line interface (CLI) — the recommended way. When you go for DevOps, most of your work happens in black screens, so you should get comfortable with the command line and use it as much as possible. Real-world: DevOps engineers live in terminals, not in menus.
- Local UI application — for people who find the commands hard, tools like GitHub Desktop give you a local graphical interface for the same operations.
- Web / browser — you can work through a browser on provider sites like github.com or GitLab.
There are multiple providers to choose from: GitHub is free, GitLab comes as a trial version, and there are others like CircleCI. So the three ways are: via your local CLI, via a local application, and via a web application.
| Dimension | Command-line interface | Local UI app (e.g., GitHub Desktop) | Web / browser (github.com) |
|---|---|---|---|
| Where the work happens | Terminal ("black screen") | Desktop window | Browser |
| What you type/click | Typed commands | Buttons and menus | Web forms and buttons |
| Who it suits | DevOps engineers; anyone scripting or automating | People new to commands | Quick browsing, creating repos, pull requests |
| Same underneath? | Runs Git directly | Runs the same Git commands for you | Runs Git on the server side |
When to pick which: use the CLI as your default — it is what automation, build servers, and job interviews assume — and fall back to a UI or the browser when you want a quick visual check or are still learning the command names.
9.1.3 git init and the First Branch
git init means "I am initializing." In the demo, a fresh directory was created (named git_test style), and before initialization a git status check gave a fatal error: fatal: not a git repository. The reason: the folder has no .git file. The leading dot marks a hidden file — it stays hidden from normal directory listings.
After running git init inside the folder, Git prints a hint: Using 'master' as the name for your initial branch. This default branch name is subject to change. So by default the branch created is the master branch. A git status right after shows On branch master.
Worked example — initializing a repository (from the demo):
- Create a fresh folder, say
git_test, and open it in the terminal. - Run
git statusbefore initialization:
fatal: not a git repository (or any of the parent directories): .git
Git refuses: there is no .git folder anywhere above, so nothing is being tracked.
- Run
git init:
hint: Using 'master' as the name for your initial branch. This default
hint: branch name is subject to change.
Initialized empty Git repository in .../git_test/.git/
Two things just happened: the hidden .git folder was created, and the first branch — master — came into existence.
- Run
git statusagain:
On branch master
No commits yet
The fatal error is gone. The folder is now a repository, ready for files.
Sense-check: the fatal error before git init and the clean On branch master after it confirm that initialization — not the folder itself — is what makes a directory a Git repository.
The master branch is the overall branch for the whole project — the one where all people push and pull the data. It is the branch that will actually go for protection and become the master production branch. Everything you change locally, in local commits, lives on your feature branches; after code review, when everything is fine, you merge your local changes into master. So the master branch is mainly for production purposes, and feature branches are mainly for development purposes. The default branch name is not sacred — you can keep the name master, or use main, develop, or whatever you want, by changing the Git configuration.
Intuition: think of a Git repository as a project ledger, and the branch as which "chapter" of the ledger you are writing in. Master is the published, protected chapter; feature branches are your scratch chapters that only reach the published one after someone checks them.
Pitfalls:
- Running Git commands outside any repository — you will get
fatal: not a git repository. Initialize first, or work inside a cloned folder. - Assuming every project must be on
master— the default name is a convention, not a law; GitHub's default today ismain. Both behave identically. - Doing all your work directly on master — this lecture later shows why production branches are protected and feature branches exist.
Recap + bridge: one command, git init, converts a plain folder into a repository with a first branch (default master), and git status is your dashboard for what Git thinks is going on. Next we open the hood: the hidden .git folder that git init just created, and the local and global configuration inside it.
In DevOps practice this matters immediately: the master/main branch is the branch that build systems (like Jenkins, covered later) watch, so every command that moves work toward that branch is a production action. Version control is also the field's foundation: teams keep everything — code, scripts, tests, and configuration — in the repository so that no machine holds the only copy.
9.2 Inside the .git Folder: Local and Global Configuration
Hook: git init printed a short hint and then... what? It quietly created an entire filing cabinet inside your folder. Everything Git remembers about your project lives in one hidden folder — open it once and Git stops being a black box.
9.2.1 The Hidden .git Directory
Right after git init, a normal listing (ls on Linux and Mac, dir on Windows) shows nothing, because the .git folder is hidden. The command ls -a (list all) reveals it. Inside .git there are many files and directories: HEAD (the head revision), config, description, hooks, objects, refs, info, and more. hooks, objects, refs, and info are directories; description, HEAD, and config are plain files — you can tell from the listing because directories carry a d marker. Opening the config file shows the local information that belongs to this specific folder only. This is why git status stops complaining after git init: the .git folder holds all the references Git needs, and from that point on you can start using the repository.
Each entry plays a distinct role in the repository's machinery:
HEAD— a tiny file holding the name of the branch you are currently on (it "points" atrefs/heads/masterright after init).config— the local configuration for this repository only.description— a short human-readable description of the repository.objects/— the content store: every file version, tree, and commit Git has ever recorded, stored as compressed objects keyed by their hash.refs/— the pointer shelf: branch pointers (refs/heads/), remote pointers (refs/remotes/), and tags (refs/tags/).hooks/— optional scripts that Git triggers automatically on events like a commit or a push.info/— extra repository-level metadata.
This is why cloning (section 9.6) brings a working repository with no extra steps: the entire filing cabinet is self-contained inside .git.
9.2.2 Local vs Global Configuration
There are two kinds of Git configuration. The local configuration lives inside the project folder's .git/config and applies only to that repository. In normal work you rarely touch local configuration. The global configuration lives in your home directory — the ~ (tilde) icon is your home — in a file named .gitconfig (~/.gitconfig). When the session started, the global file already contained entries for the email ID and the username in use, so git init and friends were already personalized.
The configuration is key–value paired, much like a JSON file: every key has a value, and values are grouped under section names (for example the section user with keys like user.name and user.email). You can query it with git config: git config --get <key> returns one value, and git config --get-all <key> returns all values under that key.
Worked example — reading your own configuration:
- Open
~/.gitconfig(or view it withgit config --list). A typical file looks like:
[user]
name = Riya
email = riya@example.com
[init]
defaultBranch = main
- Query a single value:
git config --get user.name
Riya
The command returns exactly one value: the key user.name has the value Riya.
- Query all values under a key:
git config --get-all user.email
riya@example.com
--get-all matters when several values are stored under one key — it lists every one instead of stopping at the first.
Sense-check: global config supplies your identity to every repository you create or clone, which is why commits on a fresh machine are automatically attributed to you.
9.2.3 Changing the Default Branch Name
The hint printed by git init explains exactly how to stop creating master by default: open the config file in the global settings, and at initialization time set the default branch to the name you provide. So in ~/.gitconfig you configure init.defaultBranch (for example to main or develop), and from the next initialization onward, the branch created will carry that name instead of master. That is what the message "create the default branch as the branch name" is telling you to do.
Pitfalls:
- Confusing the two config scopes — editing
~/.gitconfigchanges every repository you touch; editing.git/configchanges only the current one. A setting that works on one machine (like a corporate proxy) will not follow you to the next. - Forgetting
--get-all—git config --getreturns only one value per key; when several exist, the command shows one and the rest stay hidden. - Expecting
init.defaultBranchto rename an existing repository's branch — the setting only affects branches created at initialization; an already-createdmasterstaysmasterunless you rename it.
Recap + bridge: Git's state lives in the hidden .git folder — HEAD says which branch you are on, objects/ stores content, refs/ stores pointers — and its behavior is controlled by key–value configuration at two scopes: local (.git/config) and global (~/.gitconfig). With the machinery in place, the next step is the first real workflow: creating a file and watching Git start to track it.
In the field, configuration discipline is part of good version-control hygiene: teams standardize user.name/user.email and init.defaultBranch so that every member's commits are attributable, and automation (build servers) uses local configuration to stamp the machine identity that shows up in git blame later.
9.3 The Staging Area: Adding and Removing Files
Hook: Git does not record your files the moment you save them. There is a deliberate middle step — the staging area — and understanding it is the single biggest unlock for beginners: once you see it, every commit command makes sense.
9.3.1 Untracked Files
After initializing the repository, create a new file — in the demo, sample.txt with a single line of content. Now run git status, the command that provides the current status of your Git directory. It says something like "no commits yet" and reports an untracked file. What does untracked mean? The master branch has some state, and you created a change locally, but Git cannot track that change yet — it is asking: "Can you add that as a Git file?" The file you created is not a Git file as of now.
To answer yes, use git add. There are two ways: add one specific file (git add sample.txt), or add everything with git add . — the dot means "whatever is currently open in this folder, add it all." After adding, git status shows the file as a new file staged for commit: Git did not know this file, you created it, so it records it as new.
Worked example — staging a file (from the demo):
- Create
sample.txtcontaining one line of text, then rungit status:
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
sample.txt
The file is untracked: Git sees it, but has no history for it and will not include it in anything until you say so.
- Stage it — one file or everything at once:
git add sample.txt # or: git add .
- Check status again:
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: sample.txt
The label changed from "Untracked files" to "Changes to be committed" — the file is now a new file in the staging area, waiting for a commit.
Sense-check: the status report moved the file from the untracked list to the staged list, which is Git's way of confirming the file is now part of the pending commit.
9.3.2 Unstaging: git rm --cached and git reset
Sometimes you add a file you do not actually want in Git. Two commands remove a wrongly added file from the staging area:
git rm --cached <file>— "rm" stands for remove, and--cachedrefers to the staged copy (the index).git reset— resets the staging area.
Both work similarly: after either one, git status shows the file as not added anymore. You can add it again and unstage it again as many times as you like — the rule of thumb: until and unless you commit, you will be able to change it multiple times. The analogy used in the session: it is like a database — until you don't commit, you can play as many times as you want; once you commit, things become slightly complex and other commands are needed.
Intuition: the staging area is a loading bay between your working folder and the repository history. git add puts a package on the loading bay; git rm --cached and git reset pull it back off. Nothing that happens in the loading bay is final — the moment of no return is the commit, exactly like a database transaction that has not yet been committed.
9.3.3 .gitignore and Files That Must Never Be Pushed
When would you not want a file added to Git? A real case: when uploading your app to the Play Store or the App Store, you need to create certificate files. These are highly secured items and must never be pushed to a Git directory or anywhere else — if somebody reads those certificates, they can recreate them, break your APK or your IP, and push a new version of the app themselves. In that situation you either write the file name into a .gitignore file (so Git ignores it), or you simply never git add it. If you wrongly added such a file, the git rm --cached and git reset commands above are the way back out.
Security warning — certificates and secrets: signing certificates for the Play Store and App Store are the keys to your app's identity. A leaked certificate lets an attacker rebuild your APK, sign it as you, and push their own version of the app to users. The same logic covers passwords, API keys, and .env files: put them in .gitignore (or never stage them), and if one slips in, unstage it immediately with git rm --cached — and treat it as exposed even after removal, because it already lives in history.
Beyond secrets, .gitignore is the standard home for build output and local junk: compiled binaries, dependency folders (node_modules, vendor), and IDE files all belong there, so a clean repository contains only what a teammate actually needs to build the project.
Pitfalls:
- Using
git add .blindly — the dot adds everything currently open in the folder, including files you never meant to track. After a biggit add ., always read the staged list ingit statusbefore committing. - Deleting a file to unstage it —
git rm --cachedremoves it from the index but keeps it on disk; a plaingit rmwould delete it from your working folder too. - Forgetting that unstaging is not forgetting — after
git rm --cached, the file is untracked again, but any committed copy of it still exists in history.
Recap + bridge: git status is your dashboard: untracked files need an explicit git add to enter the staging area, and git rm --cached / git reset walk them back out — all of it free until the first commit. Next we make the first commit, and see what Git records when the staging area finally becomes history.
In the field, the staging area is what makes the "atomic, reviewable commit" possible: a developer can stage only the files that belong to one logical change, leave work in progress unstaged, and keep secrets out of history entirely — the same discipline that the deployment-pipeline chapters of the reference materials treat as the foundation of safe releases.
9.4 Committing Changes
Hook: Staging is reversible; a commit is the point of no return — the moment your change becomes part of the project's permanent record. Everything about the command is designed around that one idea.
9.4.1 Two Ways to Commit
Once the file is staged, committing moves it from the local directory into the branch. There are two ways to commit:
git commit— opens an editor window where you write the commit message, then save and quit. In the vi editor,:wqmeans "write and quit"; in Notepad you simply open it and save.git commit -m "message"— the message goes directly in the command line, no editor opens.
The demo used both: the first commit typed the message in the editor ("added a new file"), and a later commit used -m "Deleted the file" on the command line itself.
9.4.2 What a Commit Looks Like
The commit output reads: 1 file changed, 1 insertion — the file had one line, so one line was inserted. The output also shows the mode 644 set on the file, the read/execute-style permissions stored with it, and marks it as a file. After committing, git status shows: On branch master, nothing to commit, working tree clean. Everything is committed; nothing is visible as pending. The commit lives in the branch, and your local directory is in sync with it.
The database analogy (from the session): until you commit, you can play as many times as you want — stage, unstage, restage, repeat — because nothing is permanent yet. Once you commit, things become slightly complex: the change is recorded, and undoing it now needs other commands (the reset and revert families coming up). This is why the commit is the boundary between "experiment freely" and "manage history deliberately."
9.4.3 Reading History with git log
git log lists your commits. Each commit shows:
- a commit ID (the hash), plus a GitHub-specific short ID,
- who created it,
- when it was committed — the demo showed IST time 5:30, an Indian timezone timestamp,
- the commit message you wrote.
In the demo, the first commit was "added a new file"; after deleting the file and committing "Deleted the file", the log showed two commits. That pair of commits became the playground for the undo commands in the next section.
Worked example — the first commit (from the demo):
- Stage the file, then commit with an editor:
git commit
Git opens the editor; type the message added a new file and save (in vi: :wq).
- Git reports what it stored:
1 file changed, 1 insertion(+)
create mode 100644 sample.txt
1 file changed — only sample.txt was touched. 1 insertion — the file's single line is a single inserted line. create mode 100644 — Git records the permission mode (644: owner read/write, others read) alongside the content.
- Verify with
git status:
On branch master
nothing to commit, working tree clean
"Clean" means the working folder matches the branch exactly: nothing pending, nothing hidden.
- Read the history:
git log
commit a1b2c3d... (HEAD -> master)
Author: Riya <riya@example.com>
Date: Fri Aug 14 05:30:00 2026 +0530
added a new file
The log shows the commit hash, the author, the timestamp (IST, 5:30), and the message.
Sense-check: 1 file changed, 1 insertion plus a clean working tree confirm that the staged line is now safely inside the branch — the local directory and the branch agree again.
Pitfalls:
- Committing with no staged files —
git commitwith an empty staging area fails or commits nothing;git statusfirst, then stage, then commit. - Forgetting the
-mand getting stuck in the editor — the editor opens precisely because you asked for it;:wq(vi) or save-and-close (Notepad) finishes the commit. - Writing vague messages like "changes" — the commit message is the only record of intent in history; the session's habit of naming the change ("added a new file", "Deleted the file") is what makes
git logreadable months later. - Deleting a file and expecting Git to forget it — deletion itself is a change that must be committed;
git statuskeeps showing it until you commit the removal.
Recap + bridge: a commit moves the staged change into the branch, records who, when, and why, and returns the working tree to clean. The pair of commits created in this demo — "added a new file", then "Deleted the file" — is about to become the test bed for undoing history.
Commits are the raw material of everything else in Git and in the deployment pipeline: build servers watch for new commits to trigger builds, release engineers count commits to decide what goes out, and git log is how every team reconstructs what actually happened and when.
9.5 Undoing Commits: Reset, Checkout, and Force
Hook: You just committed something you did not mean to. Git's answer is not panic — it is three commands that move history backward in different ways, each with a different level of force.
9.5.1 git reset HEAD~1
Scenario: two commits exist — first "added a new file", then "deleted the file". Now you decide you actually want to keep the file. git rm will not help anymore because Git no longer tracks the file; it will still be present in the commit. The way to undo is git reset HEAD~1 — you are telling HEAD to come one step below: wherever you are, step down one level. Git answers with unstage changes after reset, and the deleted state comes back into the working tree. The commit that removed the file is gone from the log; only one commit remains. You can then commit the restored file again, producing two commits once more.
9.5.2 git checkout <commit-id>
The second way to move back to an earlier commit is git checkout <commit-id>. Git reports it is switching, and the HEAD pointer moves to the previous commit — the state of the file returns to that commit's state ("added a new file" is the current HEAD). Because you are no longer on a branch, Git prints warnings about being in a detached HEAD state — you are not experimenting, you are just looking, but Git cannot tell. If you now run git log, it shows the previous history.
9.5.3 The Meaning of Force
When you are sure you want the move and do not want the warnings, add force: git checkout -f. Force tells Git you really want the change to happen and are not doing an experiment, so Git proceeds with no warnings at all. git log then shows only one commit and git status shows the previous state. The lesson: force flags exist because Git trusts that the person typing them understands what they are doing.
Worked example — undoing a commit (from the demo):
- Starting history (two commits):
git log --oneline
b2c3d4e Deleted the file
a1b2c3d added a new file
The file is gone from the working tree; the "deleted the file" commit is on top.
- Step HEAD down one level:
git reset HEAD~1
HEAD~1 reads "HEAD minus one step": the branch pointer moves to a1b2c3d. The commit b2c3d4e is removed from the log, and the file's deleted state comes back into the working tree — the file exists again, marked deleted, awaiting a new decision.
- Check the log now:
git log --oneline
a1b2c3d added a new file
One commit remains. Re-stage and re-commit the restored file, and the log holds two commits again — the "deleted the file" history is replaced by your new decision.
- The checkout alternative, for looking, not changing:
git checkout a1b2c3d
HEAD now points at the old commit directly, and Git warns:
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches...
The file content returns to the "added a new file" state. git log shows only the earlier history.
- If you are certain and do not want the warnings, force it:
git checkout -f
Git proceeds silently — no warnings, because force tells Git this is deliberate.
Sense-check: reset HEAD~1 rewinds the branch itself, while checkout moves the pointer for a look — the warnings on checkout are Git saying "you are not on a branch anymore, are you sure?"; force answers "yes" for you.
Pitfalls:
- Confusing reset with checkout —
git reset HEAD~1rewinds the branch you are on;git checkout <commit-id>points HEAD at a commit with no branch, leaving you detached. - Getting stuck in detached HEAD and committing there — commits made in detached state belong to no branch and can be lost; attach a branch or switch back before continuing.
- Using
-fcasually —checkout -fdiscards local working-tree changes without asking; force is a "I am sure" flag, not a shortcut. - Expecting reset to keep working after the fact — once the commit is gone from the log, the file is only recoverable through Git's reflog (a hidden diary of moved pointers), which is why the professor's rule is: decide before you need force.
Recap + bridge: git reset HEAD~1 steps the branch down one commit and brings the change back to the working tree; git checkout <commit-id> jumps HEAD to any commit, warning about detached state; force suppresses the warnings for people who know what they are doing. The local basics — init, add, commit, reset — are complete, but nothing was pushed anywhere: this repository has no remote. That changes now, with cloning.
Undoing is not just a beginner convenience — in operations it is a production skill: knowing the difference between resetting a local branch and reverting a shared one is exactly the judgment DevOps engineers use at 2 a.m., which section 9.11 explores with git revert.
9.6 Cloning a Remote Repository
Hook: Everything so far happened on one machine. A repository becomes a team tool the moment it lives on a server — and one command, git clone, downloads the whole thing, history included, ready to work.
9.6.1 git clone and the Remote
The second demo moved to a real repository on GitHub. The setup: a company-style organization, a repository named devops (demo230_devops), and two users working on it — call them User A and User B. Both users have write access to the repository; it is not a case of one reader and one writer. The repository contains exactly one file, readme.md.
To bring the repository to the local machine, use git clone. On the GitHub repository page, click Code to see the full clone path. You can clone via HTTPS or via SSH. GitHub does not accept passwords nowadays — you need SSH keys — so the demo used SSH. The command shape is:
git clone git@github.com:<username>/<repository>.git <target-folder>
The username and the target folder differentiate the two users. On one machine, two separate clones were created: one with User A's username and a folder named for him, one with User B's username and his own folder. (On two different machines you could keep the same folder name — the split was only to show the differentiation on one system.) When cloning starts, Git prints Cloning into <folder>... and all the information comes from the remote. Afterward, ls confirms the new folder exists, and inside it the readme.md file is there.
9.6.2 Why Clone Brings Its Own .git
There is an important contrast with the earlier demo: previously, git status in a fresh empty directory errored with "not a git repository." After git clone, the same command works immediately. The reason: cloning downloads a lot of things, including the entire .git folder, automatically. Because .git came along with the download, Git understands this is a repository and shows status normally.
Intuition: cloning is not "downloading the files" — it is copying the whole filing cabinet, objects/, refs/, and all. That is why the clone works with no git init: the repository machinery (section 9.2) arrives pre-built. In distributed version control, every clone is a complete, first-class repository in its own right.
9.6.3 Local Branch vs origin/main
The clone reports On branch main. Earlier the locally initialized repo used master; this repository uses main because the repository was created on GitHub with the branch name main. The status line adds: Your branch is up to date with 'origin/main'. Here origin means the remote directory — the GitHub copy. So there are two branches in play: a local main branch and an origin/main branch, and both are in parallel, updated with each other.
The status line is the only way to find out which side is ahead:
- If you change something locally, the status says you are ahead of
origin/main— your local branch has changes the remote does not have. - If someone pushed new changes to the remote, the status says you are behind.
After the initial clone both are in sync, which means both developers start with the same set of information: one commit — the initial commit — with the same readme.md content on both machines.
Visual intuition: picture two parallel railroad tracks labelled main (your machine) and origin/main (GitHub), with a train sitting at the same station after the clone. The status line is the station sign: "up to date" means both trains are beside each other; "ahead by 1" means your train has pulled out one station while the other hasn't; "behind" means the reverse. The tracks reconnect only when you push (move your train forward) or pull (fetch the other's position).
Worked example — cloning for two users (from the demo):
- On the GitHub repository page, click Code and copy the SSH path, then clone on one machine twice, once per user:
git clone git@github.com:alice/demo230_devops.git alice-devops
git clone git@github.com:bob/demo230_devops.git bob-devops
The @ address names the transport (SSH), the account, and the repository; the final word is the local folder name.
- Git answers:
Cloning into 'alice-devops'...
remote: Enumerating objects: 3, done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Receiving objects: 100% (3/3), done.
All the repository data is received from the remote — one commit's worth here.
- Enter the folder and run
git status:
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
No git init needed, no fatal error — the downloaded .git folder answers every question.
lsshowsreadme.mdinside — the same single file in both users' folders.
Sense-check: both clones report the same origin/main and the same clean state, so both developers are provably starting from identical information — one commit, one file.
Pitfalls:
- Cloning over HTTPS when your account is set up for SSH (or the reverse) — GitHub no longer accepts passwords, so the transport must match your configured keys; the demo's SSH path works only after keys are set up.
- Forgetting the target-folder argument and cloning into the default name — fine in a fresh directory, but inside an existing folder the clone's folder name can collide with your own files.
- Reading "up to date" as "nobody else is working" — the status line compares your local branch with the remote copy at the last fetch; a teammate may have pushed five minutes ago and you would not know until you fetch or pull.
Recap + bridge: git clone downloads the entire repository — .git included — and immediately reports the local main tracking origin/main, with the status line as the ahead/behind gauge. Two users now hold identical copies; the next step is what happens when they both start changing the same file.
Cloning is the default way professional teams start work: the remote server is the system of record, every local clone is a full backup, and the deployment pipeline builds from the remote — which is why the downstream direction (clone and pull) is the accepted flow, a theme the session returns to in section 9.14.
9.7 The Classic Merge Conflict
Hook: Two developers, one file, same lines. Git cannot decide who is right — and that is by design. The conflict you fear most is a daily, expected event in real projects; the skill is not avoiding it but resolving it cleanly.
9.7.1 Setting Up the Conflict
The demo built a classic scenario on purpose. Both users start from the same readme.md — a title line plus the line "This is the demo of merge and merge conflict." The plan: two developers will touch the same file and change the same area. This is exactly what happens in real projects: developers work on the same module, often different functions, but sometimes a common function is called by two teams and both teams change something in it — the same function, the same file, the same path. Most of the time, this kind of conflict should be expected.
Why does Git allow two people to edit the same file at all? Git uses optimistic locking: it assumes that people usually edit different things, so nobody locks files — everyone works freely, and conflicts surface only at merge time when two changes genuinely overlap. That trade-off is what makes large teams possible; the price is that overlapping edits must be resolved by people.
User A opens readme.md, adds a new line (the "second line"), checks git status — it says the branch is up to date but there are unstaged changes; "not staged, can you add this?" — then runs git add readme.md, checks status again ("ready for commit"), and commits with the message Added second line. The status now shows: Your branch is ahead of 'origin/main' by 1 commit — the local main branch has a change the remote branch does not have, and Git suggests pushing. After git push, the change is up on GitHub. The history there shows the initial commit and the new commit (Added second line, one minute ago, with a + sign on the new line).
9.7.2 The Rejected Push
Now the second developer, User B, does not know any of this — no git fetch, no git pull, so Git never told him the remote moved. He opens readme.md, leaves a line empty, adds a third line, checks status (which shows no hint of the remote change), commits Added third line, and happily pushes. The push is rejected:
failed to push some references to this repositoryUpdates were rejected because the remote contains work that you do not have locally.
The important sentence is "the remote contains work that you do not have locally": the remote directory has changes missing from your local chain, so Git cannot merge your data onto the remote. The message adds: "This is usually because another repository is pushing to the same reference. You may want to first integrate the remote changes" — in other words, do a git pull before pushing. Pushing now would overwrite the other developer's change, and that is why Git refuses.
9.7.3 The Pull That Cannot Auto-Merge
Running git pull tries to download and merge. Git starts auto-merging readme and then stops: CONFLICT. There is a merge conflict because both developers touched the same lines. Git says it is not able to apply the commit: "Resolve all the conflicts manually." This is a manual merge scenario, not an auto merge. Git offers two paths: either fix the conflict properly, git add it, and git rebase --continue — the rebase part is what performs the merge for you — or skip this commit. Skipping is normally wrong, because the other developer's code will not come to you. The only real way to fix it is to resolve the conflict manually, add the file, and continue the rebase.
9.7.4 Reading the Conflict
Before editing, check the state: git status shows both modified — both users modified this file. The command git diff <file> (with the file name) shows what changed. There are two sides to the picture:
- the HEAD side — the correct, latest revision in the repository, which holds the other developer's second line;
- your local side — the change you are trying to push, which holds your empty line and third line.
Git marks the disputed region in the file with conflict markers — the <<<<<<<, =======, and >>>>>>> lines. These markers exist precisely to differentiate one change from the other. When you open the file, you see both versions and the markers around them; the resolution step is to decide which lines stay.
Visual intuition: the conflicted file looks like a courtroom record with two witnesses — above the ======= divider sits the HEAD side, below it sits your local side, and <<<<<<< / >>>>>>> are the labels naming each speaker. git diff <file> is the preliminary report listing what each side changed; the markers are where you, the judge, must write the final verdict.
9.7.5 Resolving Manually
The realistic workflow: call the other developer, ask what they changed, get on a connected call, open the file together, and agree line by line. In the demo, the resolution was: remove the conflict marker lines (the comments Git inserted — they only show the difference, they are not content), remove the unwanted empty line, and keep both meaningful lines. Save the file, check git status (it shows modified), then:
git add readme.md— stage the resolved file.git rebase --continue— continue the rebase so Git performs the merge.
At this point Git asks whether you want to change the commit message — you can say yes and write Merge conflict resolved. The rebase picks the local commit and merges it with the existing commit, squashing two different commits into the history. The output says the rebase is successful, no conflict, things are fine. git status is clean; git push now succeeds without problems, and the status line returns to being in par with the origin. On GitHub, a new commit "Merge conflict resolved" appears, and readme.md now has four lines — both the second line and the third line are present.
9.7.6 git blame and the Pull-First Rule
To see who changed what, line by line, use git blame. This exists so that accountability is possible: if somebody deliberately did something wrong — or something right — you can find who did what, and when. In the demo, blame showed that one developer pushed nine minutes ago and the other "mistakenly didn't even pull that data," which is exactly why the merge conflict came in. Peers and managers use this: "From next time, don't do this. This has become a merge conflict." The rule they repeat: normally, before changing something, pull it once.
Worked example — the full conflict resolution (from the demo):
- Both users start from the same file:
# DevOps Demo
This is the demo of merge and merge conflict.
- User A adds a second line, commits
Added second line, pushes. The remote now has the initial commit plus User A's. - User B (who never fetched) adds an empty line and a third line, commits
Added third line, and pushes:
! [rejected] main -> main (fetch first)
error: failed to push some references to this repository
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. You may want to first integrate the remote
hint: changes (e.g., 'git pull ...') before pushing again.
- User B runs
git pull:
Auto-merging readme.md
CONFLICT (content): Merge conflict in readme.md
Automatic merge failed; fix conflicts and then commit the result.
git statusshowsboth modified;git diff readme.mdand the file itself show:
<<<<<<< HEAD
Second line
=======
(empty line)
Third line
>>>>>>> Added third line
HEAD side: User A's second line. Local side: User B's empty line and third line.
- Resolve: delete the three marker lines, delete the empty line, keep both meaningful lines. Then:
git add readme.md
git rebase --continue
Git asks about the commit message; write Merge conflict resolved.
- Final state:
git statusclean,git pushsucceeds, GitHub shows the new commit, andreadme.mdhas four lines — the title, the demo line, User A's second line, and User B's third line.
Sense-check: the file ends with both developers' content — the resolution merged the two changes instead of dropping either — which is exactly what a good manual merge is supposed to produce.
Pitfalls:
- Skipping the conflicted commit — Git offers "skip this commit" as an escape hatch, but skipping abandons the other developer's changes: their code never reaches you, and the conflict comes back at the next pull.
- Leaving the marker lines behind —
<<<<<<<,=======,>>>>>>>are Git's inserted comments, not content; a "resolved" file that still contains them will break the build or the push. - Resolving without talking — resolving blindly keeps your lines and throws away the other developer's work; the demo's process (call, discuss, agree line by line) is the professional norm, because only the authors know the intent.
- Editing the file and forgetting
git add— the resolution counts only after the resolved file is staged; without it,rebase --continuecannot finish.
Recap + bridge: a rejected push is Git's way of saying "the remote has work you do not have"; the pull then exposes the overlap as conflict markers, and manual resolution — remove markers, keep the right lines, git add, git rebase --continue — completes the merge. The pull-first rule — pull once before changing something — is the lesson. Next: the two sync commands behind that rule, git fetch and git pull, and the scenario where merging takes care of itself.
Merge conflicts are the everyday reality of large teams — the reference literature notes that with optimistic locking, conflicts on big teams happen fairly frequently, but nearly all are fixed in seconds when developers commit often. When they take longer, it is because the changes touch shared intent, not just shared lines: a rename in one branch colliding with a new reference in another passes Git's merge silently and surfaces only when the code runs. That is why the professor's pull-first rule and the manual, human review are not process overhead — they are the actual fix.
9.8 git fetch, git pull, and the Auto-Merge Scenario
Hook: The pull-first rule from the conflict sounds simple, but Git actually has two different sync commands — one that whispers and one that shouts. Knowing which to use when keeps you out of the next conflict.
9.8.1 Soft vs Aggressive Syncing
When the second developer needed the merge result, the session also covered the pair of commands for staying in sync:
git fetch— the softer command. It does not force anything onto your working directory; it just shows you the changes that have arrived. It reported that the local branch andorigin/mainhad diverged — one commit on each side — so you can see something changed without touching your work.git pull— the aggressive command. It forcefully brings the information to you; it does not wait. The data comes in whether you asked for it explicitly or not.
The practical pattern: fetch first, look at what is waiting, then decide. In the demo, after the merge-conflict scenario, the second user pulled and everything came up to date, showing the newly merged state.
Intuition: git fetch is like the mail carrier holding your letters at the door and describing them before you take them; git pull is the carrier pushing the letters into your hands and opening them. Fetch updates only Git's picture of the remote (origin/main); pull fetches and merges into your working tree — which is why pull is the aggressive one.
9.8.2 Same Fix, Same Line: Conflict-Free Merge
The demo then ran a second, parallel scenario: both users add a "fourth line" to the same file — the same line, the same fix. One user commits and pushes; for that user, no problem arises because he is the first to push. The other user commits the identical change and pushes — and gets the error again, with the advice "fetch first." Because the change was never fetched, the pull is required: git pull, then merge, then push. This time it works with no manual intervention: auto merge succeeds because both developers worked on the same line and produced the same fix — there are not two different fourth lines, only one. Rebase performs the merge automatically; no conflict appears. The general conclusion: there are two ways of merging — via rebase, which is automatic, and manually, like the previous scenario.
Worked example — the identical-fix auto-merge (from the demo):
- Both users open
readme.mdand both add the same line at the same spot — a "fourth line" with identical text. - User A commits and pushes first: no problem — his commit lands cleanly on the remote.
- User B commits the identical change and pushes:
! [rejected] main -> main (fetch first)
The rejection is the same as before — the remote moved while User B was working.
- User B runs the full cycle:
git pull, then merge, then push:
Auto-merging readme.md
Merge made by the 'ort' strategy.
This time there is no conflict block: Git compares the two changes and finds they are the same line with the same content — one fourth line, not two competing ones — so the merge applies automatically.
git pushsucceeds; the file has one fourth line, not two.
Sense-check: the merge succeeded with no human help because the two sides agreed — a conflict needs two different answers to the same question; one identical answer is not a disagreement.
9.8.3 The Global Team Reality
Why does this matter in industry? The session told the story of legacy mobile application development before the modern tooling. A full mobile application for the old double-zero keypad phones could be 10–20 lakh lines of code — a million to two million lines — and hundreds of members worked on it per location: about 250 members in Chennai, 280 in Bangalore, 500 in China, 500 in Delhi, and 800 in the US — roughly 3,000 to 4,000 people working across multiple modules of the mobile product. Many people touch the same area without ever interacting, because the teams span the globe: when the sun sets in India, the US starts working; when the US is going out, China starts working. People work around the clock and do not know who is fixing what. In that kind of scenario there is no communication, and two people will try to fix the same thing — exactly the situation that generates merge conflicts.
A second real story: a developer works on a fix, commits it locally, and suddenly goes on sick leave. The next person takes over, pushes the fix. When the first developer returns and is back at work, he accidentally pushes again — the same fix from a stale branch — and the conflict appears. These stories set the expectation: merge conflicts are normal, and the pull-first rule exists for exactly these situations.
Pitfalls:
- Pulling with dirty local work —
git pullmerges into your working tree; if your local changes collide with the incoming ones, you get a conflict mid-pull. Fetch first to see what is coming. - Treating fetch as "nothing happened" — fetch did happen: Git's remote-tracking picture (
origin/main) is now correct, and the status line will honestly report ahead/behind. Many conflicts arise only because people never fetch and keep working against a stale picture. - Assuming auto-merge means the result is correct — Git merges lines, not intent; two people "fixing" the same line with the same text is the safe case, but Git cannot judge two different fixes (see the conflict section for that).
Recap + bridge: git fetch shows you the changes softly; git pull forces them into your working tree — fetch first, then decide. When two people make the same change, the merge is automatic; when they differ, it is manual. Up to now both users worked directly on main; the next section introduces the workflow that real teams actually use — feature branches.
The global-team reality is not a history lesson: follow-the-sun development, where one location hands off to the next around the clock, is still how large products are built, and every commit from the 3,000-person project is a potential merge. Tools cannot prevent two people fixing the same thing — only the pull-first habit and short-lived branches can — which is exactly why Git's sync commands are the most-used commands in a DevOps engineer's day.
9.9 Branching: Feature Branches and the Merge Workflow
Hook: Both demo users edited main directly and collided. Real teams avoid that by design: nobody works on the protected branch — everyone works on their own copy of history, called a branch, and brings work over only after review.
9.9.1 Why Not Work on main
git branch lists all branches, with a * (star) marking the branch you are currently on. Up to this point the demo worked on the main (or master) branch — which is what you should not normally do. You should not work directly on a production branch, because pushing something to it can break production: Real-world: Jenkins automatically takes the code from that branch, starts building, and pushes the result to the stores or the websites. One bad push and your production data is broken. So before starting any change, create your own local branch, work there, and only bring the change to the main branch after review.
Why the production branch is protected: in a typical pipeline, a build server like Jenkins watches the main/master branch: every push there triggers an automated build that ships the result to app stores or websites. A half-finished change pushed to main is a half-finished change in production. Feature branches exist so that "work in progress" and "production" are physically different places in history.
9.9.2 Creating and Switching Branches
The command to create a new branch is git checkout -b <branch-name> — the -b means "create a new branch." The difference between git checkout -b and plain git checkout is exactly that flag: git checkout <branch-name> switches to an existing branch; removing the -b and typing the branch name is all it takes to switch back.
Demo: a branch named fifth-line-insertion was created, a fifth line was added to readme.md, and the change was committed with the message Added fifth line. Checking git branch now shows the star on the new branch; git log shows the HEAD is in this branch, with the earlier commits belonging to main. All the changes you make in this state go into the local branch, not into main — your change does not impact the production part; it only impacts your development branch.
9.9.3 Naming Branches for Clarity
Branch names must be specific about what they do. If you are creating a main menu, name the branch main-menu; if you are creating an app bar, name it app-bar. The name should describe the functionality or feature you are building. The reason: tomorrow, when people look at your local branches — if you are not available — they can understand what each branch is doing just by reading the names.
9.9.4 Pushing a New Branch and Upstream
The first push of a new branch fails with: The current branch has no upstream branch. What does that mean? On the remote there is only the default branch (main); there is no branch with your feature name, so Git cannot figure out where to push this information. Upstream means the remote counterpart of your local branch. The fix Git suggests is to set the upstream: copy the suggested command — git push --set-upstream origin <branch-name> — where origin means the remote, and the push both creates a new remote branch and links your local branch to it.
After this succeeds, GitHub shows a "Create a pull request" prompt — not needed right now, because local and remote are both up to date. The local branch is now tracking the remote branch; the two are integrated. On GitHub's branch list, the default branch and the new active branch both appear. The feature branch is "1 commit ahead of main": opening main still shows only four lines, while the feature branch has five — it has one commit more.
Why have the feature branch? It goes for code review. A team opens the file, sees exactly what you changed ("I added a fifth line"), and decides whether it is fine. If the review passes, you get the green signal to merge into the main (production) branch. Note also that the other developer, pulling now, does not get your feature branch: git pull only reports a new branch; git log for him still stops at the fourth line, because he is on main. The feature branch is visible only to the developer who owns it — until it is merged.
9.9.5 Merging a Feature Branch
Once the lead or the DevOps engineer approves, the merge happens: check out main (git checkout main), confirm you are on the right branch, then git merge <feature-branch>. The output — 1 file changed, 1 insertion — shows the feature branch's data coming into the main branch. git status now says the local master is ahead of the origin, and git push sends it up; everything ends in sync, and the other user's next git pull brings the new commit in as well. This is the way you must merge — you should not directly work on the main branch; you work on the feature (development) branch, complete it, and merge it.
9.9.6 Deleting Branches: -d vs -D
Once a feature branch is merged into main, the main branch already has that information — keeping the feature branch serves no purpose, so delete it. There are two ways:
git branch -d <branch>— the soft delete. It works only when the branch is fully merged. In the demo it deleted the merged feature branch immediately.git branch -D <branch>— the capital D or forceful delete. This is for when the branch is not fully merged and you still want it gone.
A classic trap: you cannot delete the branch you are currently on. The demo tried it and Git refused: Cannot delete branch ... checked out. The session recalled the Panchatantra story of the man sitting on a branch trying to cut the same branch he is sitting on — you must check out to another branch (main) first, then delete.
The second demo showed the unmerged case: a test branch was created, a new file was added and committed, and then git branch -d test refused: The branch 'test' is not fully merged — Git warns you made a commit but never merged it and asks if you are sure. When you are sure (the branch's work is no longer needed — perhaps another person already fixed it), use the forceful git branch -D. Small d for the happy, fully merged case; capital D for the non-linear case with problems.
9.9.7 Deleting a Remote Branch
Deleting locally is not enough — the feature branch that was pushed still exists on the remote. To remove it there, the command is git push --delete origin <branch-name> (some spell it git push -d origin <branch>). The push command is not only for pushing changes; it also deletes branches. The demo ran it against the still-pushed fifth-line-insertion branch and Git confirmed: Deleted branch. Refreshing GitHub shows only one branch again.
The practice: whenever a feature is complete and pushed to the remote main (and the local main), delete both the local branch and the remote feature branch. If you keep all the branches around, it becomes very confusing to develop — a repository full of dead branches hurts everyone.
Worked example — the full feature-branch lifecycle (from the demo):
- Create and switch to a new branch in one step:
git checkout -b fifth-line-insertion
(-b = create. Plain git checkout main later switches back.)
- Add a fifth line to
readme.md, stage, and commitAdded fifth line.git branchnow shows the star onfifth-line-insertion;git logshows the new commit on this branch only. - First push fails — no remote counterpart exists yet:
fatal: The current branch fifth-line-insertion has no upstream branch.
Fix it with the suggested command:
git push --set-upstream origin fifth-line-insertion
This creates the remote branch and links (tracks) it to the local one. GitHub now shows the feature branch as "1 commit ahead of main".
- The feature branch goes for code review. On approval, merge it:
git checkout main
git merge fifth-line-insertion
Output: 1 file changed, 1 insertion(+) — the fifth line moves into main. Push, and the remote main is in sync.
- Delete the local branch (fully merged, so the soft delete works):
git branch -d fifth-line-insertion
Deleting while on it would fail with Cannot delete branch ... checked out — checkout main first, as the Panchatantra story teaches: you cannot cut the branch you are sitting on.
- Delete the remote branch too:
git push --delete origin fifth-line-insertion
Deleted branch fifth-line-insertion (was <hash>).
GitHub's branch list shows only main again.
Sense-check: after the cycle, main holds the fifth line, and both the local and remote feature branches are gone — the repository returned to a single-branch state, with the feature safely merged.
Visual intuition: imagine a tree of history. main is the trunk; every feature branch is a twig sprouting from it. While you work, the twig grows its own commits; the merge joins the twig back to the trunk at the point of the trunk's current end. A branch that is never merged stays a dead twig — which is why deleting branches is part of finishing a feature, and why a repository full of unmerged twigs is confusing to everyone.
Pitfalls:
- Pushing a new branch without upstream — the push fails until you set the upstream with
--set-upstream(or-u); the error message itself gives you the command to run. - Using
-Das a shortcut — capital-D deletes unmerged work without asking; use it only when you are sure the branch's commits are truly disposable. - Forgetting the remote copy — deleting locally leaves the remote branch alive;
git push --delete origin <branch>is the second half of the cleanup. - Merging without switching first —
git mergeacts on the branch you are on; merging while checked out on the feature branch merges it into itself, doing nothing.
Recap + bridge: work on feature branches, never directly on main — Jenkins builds whatever lands there; push with --set-upstream, review, merge with git merge, then delete locally (-d/-D) and remotely (git push --delete). Branching is now in place; the next section handles the awkward moment when you want to switch branches but your work is not ready to commit — stashing.
Feature branches with code review is the standard workflow this lecture is preparing you for — the reference material describes release branches (created near release, only critical fixes merged in) and feature branches (short-lived, merged only after acceptance) as the two acceptable uses of branching, with long-lived unmerged branches the classic anti-pattern. The demo's fifth-line cycle is exactly the disciplined version: create, push, review, merge, delete — on both sides of the remote.
9.10 Stashing Work in Progress
Hook: You are mid-change, a teammate says "pull my changes first", and committing now would commit half-finished work. Git's answer is a temporary shelf: put the work aside, do what you must, take the work back.
9.10.1 The Scenario
You have made local changes — in the demo, a "sixth line" added to readme.md — and suddenly a teammate says: "I pushed something; can you pull it before you do changes?" You do not want to commit yet and you do not want to lose your work; but pulling right now would create a merge conflict. This is exactly the situation git stash was built for.
9.10.2 git stash: The Temporary Area
git stash temporarily removes your changes. Git prints Saved working directory and index state, and the changes are stored somewhere — in Git's own words, with "WIP" (work in progress) marked on a different, stashed commit. After stashing, git status shows nothing, and git log shows nothing either. The changes are not deleted: they sit in a temporary area, like a temporary recycle bin — put it in, take it out, put it in, take it out.
9.10.3 git stash pop
To bring the changes back, use git stash pop — "pop" means it is bringing the stash out of the stack, back into your working directory. The process is straightforward: pull your teammate's changes first, then pop your stash, and continue working. There is also a file-removal corner here: to remove a file completely (even untracked), git rm -rf works; if the file is staged, git rm or git reset unstages it.
9.10.4 Stash List, IDs, and Branch Mismatches
When you stash repeatedly, each stash gets an ID and all of them are listed with git stash list — the entries look like stash@{0}, stash@{1}, and so on. You pop a specific one by ID. Stashes belong to branches: if you stash on branch one and try to pop on branch two, Git throws an error telling you these changes belong to another branch — check out the right branch and pop there, or use git stash branch <name> to bring the stashed changes into that branch. Alternatively, create a patch file from the change, keep it somewhere, and apply it whenever you need it.
Worked example — stash and pop (from the demo):
- Add a sixth line to
readme.md— your teammate asks you to pull first. - Stash the change:
git stash
Saved working directory and index state WIP on main: <hash> ...
The change is stored on a stashed (WIP) commit. git status now shows nothing, and git log shows no new commit — the working tree is clean as if the change never existed.
- Pull your teammate's changes (or switch branches) freely — no conflict, because your sixth line is not in the way.
- Bring the work back:
git stash pop
The sixth line reappears in readme.md, exactly as you left it.
Sense-check: after pop, the working tree shows the sixth line again — stash stored the change and restored it intact; nothing was committed and nothing was lost.
Intuition: the stash is a small stack (that is why it is called "pop" — like popping a stack) with numbered drawers stash@{0}, stash@{1}, newest first. Every drawer belongs to the branch where you stashed it, exactly like a note you pinned on that branch's wall; popping from another branch is why Git complains "this stash belongs to another branch".
Q: If we stashed our changes on one branch, switched to another branch, made some changes there and stashed those too, and then used stash pop — will the earlier stashed changes get lost?
A: No. When you stash, Git gives you a stash ID — keep that ID. git stash list shows how many stashes you have (stash@0, stash@1, and so on), and you can pop the one you want by ID. If you try to pop a stash from the wrong branch, Git will throw an error — it explains that this stash belongs to another branch, and asks you to check out that branch and pop it there, or use git stash branch to apply it to the right branch. You can also create a patch and apply it whenever you need.
Q: If another user is modifying the same file, and we pop our stash — will it cause a conflict for them?
A: No. Stash is a temporary area, and you are not committing anything — stash is not a push either. It is your local area; stashing and popping only happens on your machine. Even if another user changes the same file, your stash will not impact them. Problems start only after a commit — stash happens before commit, so there is no issue.
Pitfalls:
- Stashing and forgetting — the changes sit on the stash stack indefinitely;
git stash listis the way to remember them, andgit stash popthe way to bring them back. - Popping the wrong stash — with several stashes, always pop by ID (
git stash pop stash@{1}); popping the top one blindly may restore the wrong change. - Expecting stash to hide a commit — stash happens before commit: once something is committed, it is history (and shared), and stash cannot unpublish it.
- Thinking pop cleans the stack — popping the wrong branch fails and the stash stays where it was; the error message is guidance, not a disaster.
Recap + bridge: git stash shelves uncommitted work (WIP) into a temporary area with IDs; git stash pop takes it back; stashes belong to branches, and stash-before-commit means no one else is ever affected. The next section moves from saving work aside to undoing work that already shipped — git revert, the production-safe undo.
Stashing is a daily habit in shared-repository workflows: it is the way to obey the pull-first rule from section 9.7 without either losing work or committing it prematurely, and the patch alternative is how changes travel between machines and branches when even the stash is the wrong container.
9.11 git revert: Undoing in Production
Hook: It is 2 a.m., the developers are home, and the production build is about to go out — carrying one small commit that should not be there. The engineer's undo cannot rewrite history; it must add a new piece of history that cancels the bad one.
9.11.1 The 2 a.m. Scenario
Imagine this real-world situation: it is two o'clock in the morning. The DevOps engineer is working on your commits, pushing a very important production build. The developers have gone home. The build contains many important fixes that must go out — but one commit has a problem. It is a very small problem: a tiny fix, like changing a color from green to blue, that will not impact the production deployment. Still, the build should not carry that broken change. What does the engineer do?
9.11.2 How Revert Works
The answer is git revert <commit-id>. Reverting does not delete the commit; it creates a new commit on top of the history that nullifies the old one. The log keeps both: the original commit is still present, and a new "revert" commit sits above it, canceling its effect. The engineer then pushes, and production is safe without losing the other fixes. The next day the developer comes to the office, is told about the revert, resets HEAD back to the commit, fixes it, and commits it again. So revert is the softer way of nullifying previous changes — mostly used by DevOps engineers and people working late nights, when the developer is not there and the fix is small enough that nobody wants to introduce errors. It keeps the history intact and everything recoverable.
9.11.3 reset --hard: The Forceful Undo
The forceful counterpart is git reset --hard. In the demo, a commit "Adding a new file" existed; git reset --hard HEAD~1 told Git to come one step below, and it did so without asking any questions — the commit disappeared from the log, leaving only the earlier "Added first line" commit. With a soft git reset, Git shows warnings about doing experimentation; with --hard, Git understands you know what you are doing and simply executes. The same idea as the capital -D for branch deletion: when you use these hard words, Git trusts you and does not ask questions.
Worked example — revert and reset --hard (from the demo):
- History before the incident:
a1b2c3d Added first line
b2c3d4e Adding a new file
- The problematic commit
b2c3d4e(say, the color change) must not ship. Revert it:
git revert b2c3d4e
[main c3d4e5f] Revert "Adding a new file"
1 file changed, 1 deletion(-)
Git builds a new commit c3d4e5f on top that applies the opposite of b2c3d4e — the file returns to its previous content. The log now shows three commits: the original, and the revert that cancels it:
c3d4e5f Revert "Adding a new file"
b2c3d4e Adding a new file
a1b2c3d Added first line
Push — production is safe, and the other fixes in the build travel along untouched.
- Contrast — the forceful undo on a local-only commit:
git reset --hard HEAD~1
HEAD steps down one level and the top commit c3d4e5f (or b2c3d4e in the local variant) disappears from the log with no questions asked:
a1b2c3d Added first line
Sense-check: after git revert, both the bad commit and its canceling commit exist — history is intact and recoverable; after reset --hard, the commit is gone from the log entirely — history was rewritten instead of annotated.
Comparison — revert vs reset:
| Dimension | git revert |
git reset (soft / --hard) |
|---|---|---|
| What it creates | A new commit nullifying the old one | Nothing — the branch pointer moves back |
| History | Kept intact; log shows both commits | Rewritten; top commits disappear from the log |
| Safety on shared branches | Safe — other people's clones can pull the revert | Dangerous — other clones still hold the old history |
| When it is used | Production: undo a shipped commit at 2 a.m. | Local cleanup: before anything was pushed |
| Force level | Softer nullification | Forceful; --hard asks no questions |
When to pick which: revert for anything that reached other people (production, teammates); reset for anything that exists only on your machine.
Pitfalls:
- Resetting a shared branch — if the commit you reset was already pushed, other developers still hold it; the branch histories diverge and recovery needs force (see the shift-delete warning in section 9.18). Revert is the shared-branch undo.
- Confusing revert with reset — revert adds history, reset removes it. The next-day workflow in the demo ("reset HEAD back to the commit, fix it, commit again") deliberately reuses reset on a local branch, not the shared one.
- Using
--hardas a first resort — it drops commits and working-tree changes without a safety question; soft reset shows warnings precisely because it is reversible.
Recap + bridge: git revert nullifies a commit with a new commit on top — history stays intact and production stays safe; git reset --hard removes commits forcefully, trusting the person who typed it. The next section shows the surgical cousin of these undos: taking a single commit and moving it somewhere else — cherry-picking.
Revert is the DevOps engineer's production undo precisely because the reference material warns that changing history in distributed systems is a red line for audited environments: a revert is an honest record ("this commit was bad, we canceled it"), while a reset rewrites the record itself. Teams with release pipelines lean on reverts and tags (section 9.13) to keep every production state reproducible.
9.12 Cherry-Pick and Interactive Rebase
Hook: Merging a whole branch to get one fix is like moving your entire house to change one window. Cherry-picking exists for the surgical case: take exactly one commit from one branch and apply it onto another.
9.12.1 The Concept
Cherry-picking means taking one commit from one branch and applying it onto another — without merging everything else. It came up naturally during the conflict resolution: when rebase completed, it "picked" the local commit and squashed it with the existing one. The full interactive demo was deferred to the next class, but the mechanics were shown.
In distributed version control, cherry-picking is the mechanism that lets you move selected changesets between repositories without moving whole branches — the reference material describes it as the way to approve or reject individual patches, and the way to carry a single fix (like a hotfix) from one release line to another.
9.12.2 Interactive Rebase: pick and squash
The command is an interactive rebase — git rebase -i — which lists the commit IDs on the branch and lets you assign each one a command. In the editor, each commit line starts with a command word:
pick(short formp) — take this commit.squash(short forms) — merge this commit into the previous one.
So to combine a pair of commits, you mark the first with pick and the second with squash, and Git cherry-picks the first commit and squashes the second into it. A real-time scenario (taking a single commit from a test branch into main) was promised to be shown in the next class.
Visual intuition: the rebase editor is a to-do list of your own history, newest at the bottom. Each line names a commit and the action to take on it; editing the action words and saving is like changing the plan for a train: pick keeps each stop, squash folds two stops into one, and after the rerun, the history shows the squashed result instead of the original two commits.
9.12.3 Student Question and Answer
Q: How do we do cherry-picking?
A: Use an interactive rebase. It shows the commit IDs with a pick command in front of each one; you mark the commit you want with pick (p) and the one to merge with squash (s), and it will take one commit and squash the other into it. I did not have a live scenario ready in this session — I will bring one for the next class.
Pitfalls:
- Cherry-picking into the wrong branch — the commit is applied to whatever branch you have checked out; check the branch first, then pick.
- Expecting cherry-pick to carry context — a commit's change applies cleanly only when the target branch has compatible content; otherwise the pick itself can conflict, exactly like a merge.
- Using squash to hide history mistakes forever — squashing rewrites the local history; once the branch is shared, rewriting it forces teammates to reconcile, which is why history surgery belongs on unshared branches.
Recap + bridge: cherry-picking moves one commit onto another branch without merging everything else; git rebase -i assigns each commit a command — pick to take it, squash to fold it into the previous one. The live scenario arrives next class; meanwhile, the next section adds the release-side partner of these history tools: tags.
Cherry-pick is the hotfix carrier in real pipelines: when a critical fix lands on the production branch, teams cherry-pick it into the development branch (section 9.18), and when a fix was made on the development line, it is cherry-picked onto the release branch — the reference material's "merge just the features you want" is precisely this operation, and it is why DVCS platforms like GitHub build review workflows around it.
9.14 Moving a Local Repository to GitHub
Hook: Every demo so far started by cloning from GitHub — the accepted, downstream direction. But what if you built the repository locally first? The upstream path exists, and this session showed exactly why it is the harder, rarer direction.
9.14.1 The Steps
The question of pushing a locally created repository to a remote came up in the session. The steps are:
- Create the local repository (
git init). - Add the changes (
git add) and commit them. - Tell Git where the remote is:
git remote add <name> <url>— you give the remote a name (the demo usedorigin) and the repository URL, HTTPS or SSH. - Push with the upstream flag:
git push --set-upstream origin <branch>(orgit push -u origin <branch>), which answers the "which upstream do I set up" question.
9.14.2 Why It Fails: SSH Keys and Password Removal
The demo failed at the push step: "repository not found," then the SSH question. The reason: you need SSH keys. GitHub removed password-based authentication in August (last year), so username-and-password pushing no longer works — it brought in SSH keys instead. You must create the SSH keys, configure them, and only then push. This is why the flow is called slightly complex: nowadays Git does not let you go fully from a local repository to a remote; the normal, accepted direction is downstream — clone from the remote and download — while the upstream direction (creating the repository from your local side) requires SSH setup that companies typically do not accept.
Worked example — remote add and the failed push (from the demo):
- The local repository exists with commits. Give the remote a name and URL:
git remote add origin git@github.com:alice/devops.git
origin is the conventional name; the URL may be SSH (shown) or HTTPS.
- Push with the upstream flag:
git push --set-upstream origin main
- The demo's push fails:
ERROR: Repository not found.
fatal: Could not read from remote repository.
followed by the SSH key question: Git cannot authenticate because the machine has no SSH key that GitHub recognizes. The fix is not a password (GitHub no longer accepts them) but SSH keys: generate a key pair, add the public key to the GitHub account, configure the agent — and only then can the push succeed.
Sense-check: the failure message points at authentication, not at the repository content — proving that the blocker is the credential setup, which is exactly the part companies refuse to standardize on.
9.14.3 The Private Key Warning
There is a security reason behind the rule: doing the upstream route involves changing internal, identifiable settings — including your private key, which becomes very visible. That is why companies do not let you go that way; they create the repository on the server side first, and you clone it.
Q: If we created the project with git init, how can we create it on a remote repository?
A: It is possible, but it is not a direct, immediate process — it is slightly complex. You first give the remote repository a name and a URL with git remote add (HTTPS or SSH), then push and set the upstream. In the demo it failed because the repository was not found and SSH keys are needed — nowadays Git makes you create the keys on the server side first, then work locally. That is why in practice only downstream works: you start with git clone and download, not upload.
Pitfalls:
- Expecting username-and-password pushing to work — GitHub removed password authentication in August; without configured SSH keys the push is rejected with "Repository not found" or a key prompt.
- Treating
git remote addas the whole job — adding the remote only names it; the actual transfer needspush --set-upstream, and the credential setup must exist first. - Handling private keys carelessly — the upstream route touches internal, identifiable settings; a visible private key is a security incident, which is why organizations standardize on the server-side-first, clone-down approach.
Recap + bridge: the upstream path is: init, add, commit, git remote add <name> <url>, then git push --set-upstream origin <branch> — but it requires SSH keys because GitHub dropped passwords, and the private-key visibility makes companies prefer the downstream route: create on the server, clone locally. The last stretch of the session turns to the practical setups: Git on Windows, Linux, and inside IDEs.
In practice, the repository-first flow is what keeps credentials and audit trails on the server side: the reference material notes that once a central repository is designated, all the properties of a centralized system (backups, build triggers, access control) come back — and the organization controls the keys, not the individual's machine.
9.15 Git in Windows, Linux, and the IDE
Hook: Wherever you start — Windows command prompt, a Linux virtual machine, or a team still living on Microsoft TFS — the same Git tool installs the same way and answers the same commands. The platform is a detail; the workflow is the same.
9.15.1 Installing Git
For Windows: you install git — the tool — not a "git repository." Installing the tool is all that is required; after that, cloning and the rest of the commands work in the Windows command prompt. For Linux (for example on a VMware machine): install git the same way — with Homebrew the command is brew install git. After installation, running git shows the usage text and confirms it is installed; then everything shown in the session from git clone onward applies. If your background is Microsoft TFS (Team Foundation Server), the mental mapping is straightforward: the remote team server becomes GitHub, and the git commands replace the TFS workflows.
9.15.2 Student Question: Setting Up Git on Linux
Q: I am totally new to this — we normally use Microsoft TFS. How do I set up Git on a Linux VMware machine? What do I need to download and how do I set it up?
A: You need to install git. On Linux with Homebrew, run install git. Once it is installed, searching for git shows that it is installed, and after that everything shown here — starting from git clone — applies. git init is needed only when you are creating a local Git instance; in normal work you do not use git init at all, you use only git clone, because GitHub holds the repository and you download it. The upstream direction (uploading) requires changing a lot of SSH parameters, which companies will not accept.
9.15.3 Creating a Repository on GitHub.com
The remote repository lives on the web at GitHub.com. With your own account (free), go to Repositories, click the New button, give the repository a name, optionally add a readme file, choose private or public, add a description, optionally pick a license (for example Apache), and click Create repository. Then go to the repository's Code section, copy the clone path, and run git clone <path> in your Windows command prompt. That is all there is to it — very straightforward.
9.15.4 Student Question: Getting Started on Windows
Q: I have a very basic question — how do I get started on Windows, and how do I build that remote repository? Is it on the web?
A: It is GitHub — github.com. Create your own account; it is free. Go to the Repositories tab and click New. Give it a repository name, choose whether you want a readme file, choose private or public, add a description, and optionally a license like Apache, then click Create repository. Once it is done, copy the clone URL from the repository page and run git clone in your Windows command prompt. You only need to install git — the tool — locally; you do not install a git repository locally.
9.15.5 UI Tools
Most IDEs provide Git access in their UI: IntelliJ IDEA, Visual Studio Code, Android Studio — every major tool ships this kind of direct integration. The UI runs the same commands underneath: clicking the "git commit" button, typing the commit message, and pressing commit executes git commit -m "..." for you. The command line remains essential for DevOps core engineers, who need to know all the commands; but if you are not comfortable with commands, the UI performs the same operations — knowing the commands just makes the UI easy to follow. GitHub Desktop offers the same: create a repository, clone a repository, commit, publish a branch, create a branch — all in a desktop window. There are also comparison tools like Beyond Compare: you compare two code versions side by side before merging, and you can use it instead of git diff when a merge conflict needs untangling. The session noted other, very user-friendly drag-and-drop repository tools exist too (the name was not recalled), where moving the head around changes which commits are active.
Pitfalls:
- Installing "a git repository" instead of the git tool — the install is the software; the repository is created by
git initorgit clone, never downloaded as a package. - Using
git initin normal team work — when the repository already exists on GitHub, cloning is the correct start; init is only for creating a brand-new local instance. - Relying on UI buttons without knowing the commands — the IDE buttons execute the same commands underneath; when a button's dialog confuses you, knowing the command tells you what it is doing (and the CLI is what DevOps interviews and automation assume).
Recap + bridge: install the git tool (Windows installer or brew install git on Linux), create the repository on GitHub.com, and clone it down; the UI tools (IntelliJ IDEA, VS Code, Android Studio, GitHub Desktop) run the same commands underneath, with Beyond Compare as the conflict-comparison helper. The session closes with two real-world management questions: ordering many branches, and what happens to storage and production hotfixes.
The TFS-to-Git migration described in this section mirrors what the reference material says about Microsoft's Team Foundation Server: its strength is Visual Studio integration, and teams moving to modern workflows replace the central server with GitHub and the check-out/check-in model with clone/pull/push — the mental mapping the professor gives is exactly the industry one.
9.16 Managing Many Branches: Merge Order Guidelines
Hook: Two developers, two features, one common module, one release date — who merges first? The honest answer from the session: there is no rule that answers that; there are only mitigations, and they start before the merge.
9.16.1 The Sprint Scenario
A student raised a practical question about merge ordering. In a sprint, developer A works on feature A and developer B works on feature B; when the release decision comes, both merge their code into main — and both may have updated a common module. Is there a guideline for who merges first?
The answer: there is no such guideline — but the first thing you try to control is not letting two people work on the same module at all. If that cannot be controlled, both developers know they are touching the same module, and before pushing they use a comparison tool — Beyond Compare is the one used most in this context — to compare the two code versions and decide which one goes where, in a group session. The same tool is used when merge conflicts come up, instead of git diff. There is no baseline because people work remotely, and not only within one team — teams span locations (the session mentioned developers in India too) — so the only real defense is the mitigation plan: compare before you merge.
9.16.2 The 148-Branches Problem
A student then connected this to a live pain point: in his project, a vendor created 148 code branches over the past two years and left them behind. How do you clean those up — is there a guideline, using rebase and squash?
The guideline exists and it is based on the commit history: every branch knows its commit IDs — where it started and where it ended — and from the dates and times you know which branch came in first and which came in second. The procedure: take the branches, check their commits, find the one with the oldest commit, and merge them one by one starting from that one. Done in that order you will not even get a merge conflict. Do not automate this cleanup: rebase will try to auto-merge and will produce a lot of merge conflicts. You should squash manually, one branch at a time.
9.16.3 The Takeaway
The commit history — with its IDs, authors, and timestamps — is the source of truth for untangling any branch mess, because every branch carries its own commits with the exact order they came in.
Q: Multiple developers work on multiple features in the same sprint. When the release comes, both merge to main — and they may have updated a common module. Is there a guideline for which one merges first, like the one with fewer changes?
A: There is no such guideline. The first thing we do is avoid letting two people work on the same module. If that is not possible, both know they are touching the same module, and before pushing we compare the two code versions with tools like Beyond Compare and decide together in a group session. We use that tool before merging and also when merge conflicts come up. There is no baseline because people work remotely, across teams and locations — only mitigation plans exist.
Q: In my project a vendor created 148 code branches over the last two years and left them. Is there a guideline to kill them — rebase and squash?
A: Yes, there is a guideline, and it uses the commit IDs: from where each branch started and where it ended, and you know which branch came in first and which came in second from the dates and times. Take the branches, check the commits, find the oldest commit, and merge one by one starting from that one — you will not even get a merge conflict. Do not use rebase for this: it will try to auto-merge and you will get a lot of merge conflicts. Squash manually, one by one — do not do this the automated way.
Pitfalls:
- Trying to automate a 148-branch cleanup — an automated rebase auto-merges everything at once and produces a storm of conflicts; the manual, oldest-first, one-branch-at-a-time order keeps each merge simple.
- Merging without checking the history order — branch start/end commit IDs and timestamps are the only reliable guide to which branch came first; without that order, conflicts pile up needlessly.
- Believing a "merge order guideline" protects you — the professor's honest answer is that no baseline exists for who merges first; ownership control and pre-merge comparison are the actual defenses.
- Letting dead branches accumulate — the 148-branch mess came from two years of no cleanup; deleting local and remote branches after every feature (section 9.9) is the prevention.
Recap + bridge: there is no merge-order guideline for a common module — control ownership and compare before merging; for leftover branches, the commit history (IDs, authors, timestamps) is the source of truth: merge oldest first, one by one, manually. The session's last questions look at the infrastructure behind all of this — storage, backups, and hotfixes on production branches.
The reference material reaches the same conclusion from the other direction: long-lived, rarely-merged branches are the source of "merge hell", and disciplined teams keep every active branch merged back to mainline daily. The vendor's 148 branches are the extreme form of that anti-pattern — and the professor's oldest-first cleanup is the manual version of what the book's dedicated merge team did with disciplined ordering.
9.17 Git Storage, Backups, and Availability
Hook: Your repository sits on a server somewhere — what happens if that server burns down? The answer explains why Git hosting is built the way it is: multiple copies, in multiple places, by design.
9.17.1 Storage Limits
There is no storage limit for a repository as such: locally, you are limited by your hard disk; on GitHub, the storage is unlimited. You can create as many repositories as you need.
9.17.2 Backups and High Availability
Repositories are stored on servers, and servers are backed up automatically. The mechanism is high availability (HA): multiple remote instances run in parallel across the globe — American servers, Indian servers, Southeast Asian servers, Chinese servers, and more. Whatever you store in one location (say, India) gets stored in four or five different server locations. That is the concept of a code repository: it needs multiple instances so that a single server failure never costs you your data.
Intuition: a repository is like a library that exists in four or five cities at once. If one building burns, the others still have every book — and because every Git clone is itself a full repository (not a partial copy), even your laptop is a backup. High availability is the server-side version of the same idea: redundancy is not an add-on, it is the design.
Q: Is there any storage limit for one repository on the server? And do they take backups?
A: There is no limit — until your hard disk on the local side, and GitHub is unlimited. And yes, backups are automatic: it is a server with high availability, HA. They run multiple remote instances across the globe — American, Indian, Southeast Asian, Chinese servers — all running in parallel. Whatever you store in India gets stored in four or five different server locations. That is the concept of a code repository: you need multiple instances.
Pitfalls:
- Treating "unlimited" as "unlimited in every respect" — storage is unbounded, but transfer and bandwidth and repository size limits still apply on hosting platforms; very large binary repositories need special handling (like Git LFS).
- Assuming one server equals safety — a single instance is a single point of failure; the HA design keeps four or five copies across regions so no single failure costs the data.
- Skipping local backups because "GitHub backs up" — the server-side copies protect you from hardware failure, not from your own mistakes (force pushes, section 9.18); the reflog and your other clones are the local safety net.
Recap + bridge: storage is unlimited (up to your disk locally), and automatic backups via high availability replicate every repository to four or five global server locations — a code repository needs multiple instances. One last scenario remains: what happens when a production release needs a fix right now — hotfixes on production branches.
The reference material states the same principle as a core property of distributed version control: since there are many full copies of the repository, DVCSs are more fault-tolerant, and local proxy repositories make high availability easy. The professor's "four or five locations" story is the hosting industry's concrete implementation of that design — and it is also why losing a laptop is an inconvenience, not a catastrophe.
9.18 Hotfixes on Production Branches
Hook: Release A shipped with ten commits, release B with five — and now release A needs an emergency fix while release B must not be touched. The session's last scenario shows how far a reset can take you, and the one rule that keeps it safe.
9.18.1 The Hotfix Scenario
A student described a release-management situation: release A has ten commits that went in; release B has five commits. Now a hotfix is needed for release A. Can we point the HEAD back to release A?
Yes — git reset head to that specific commit, fix it, and release. But the classical way is to keep two separate branches for the two releases. If you do not have those branches, resetting the head is the only way — and the student's worry was about losing release B's five commits. The answer: you will not wipe them out, provided you first commit your current changes to a different branch; then nothing gets cleared and the commits stay present. The extra rule after a reset: do not force push. A force push (git push -f) is like shift-delete — the data is gone even from the recycle bin. With a normal git push or a git revert, the data is still in your bucket and can be taken out again.
Worked example — hotfixing release A without losing release B (from the session):
- Release A has ten commits, release B has five — on one line of history, A's ten came first, B's five on top.
- The classical setup avoids the whole problem: keep two separate branches, one per release. A hotfix then lands on A's branch without touching B's.
- Without those branches, reset is the only way:
git reset <commit-id of release A>
HEAD returns to release A's commit.
- Protect release B's work first — commit your current changes to a different branch, so nothing gets cleared and B's five commits stay present in the repository.
- Fix the bug, then release A again.
- After a reset, never force push.
git push -fis like shift-delete: the data is gone even from the recycle bin — recovery becomes impossible. A normalgit pushor agit revertleaves the data in your bucket, where it can be taken out again.
Sense-check: the reset itself is safe as long as B's work was committed to another branch first; the danger is the force push that erases the evidence — which is exactly why the shift-delete rule follows the reset in the professor's answer.
9.18.2 Direct Production Commits
A related question concerned where changes land when you commit straight to production. If you push directly to the production branch, the change is available only in the main branch — you are not committing into a feature branch and merging it over. So a hotfix made directly on the production master branch will not duplicate into the development branch by itself; you have to bring it over manually — with a cherry-pick, as discussed earlier.
9.18.3 The Complete Command Inventory
Closing the session, the full set of commands covered was: git config, git init, git clone, git status, git branch, git checkout, git add, git rm (remove), git reset, git revert, git stash, git stash pop, git commit, git push, git push --delete, git pull, git rebase, git log, git show, git merge, git fetch, git tag, git blame, git diff, and git remote add. git show <commit-id> adds one more capability worth knowing: it shows the details of any commit — which lines were added, what new files came in — so you can inspect each and every commit in history.
Q: If release A has ten commits and release B has five, and we need a hotfix for release A — can we just point our head back to release A?
A: Yes. You do a git reset head to that specific commit, fix it, and release. The classical way is to keep two different branches — then you do not have this problem. If you do not have the branches, resetting the head is the only way. And you will not wipe out release B's five commits: first commit your changes to a different branch, then nothing gets cleared — the commits stay present. After a reset, never do a force push: git push -f is like shift-delete, the data is gone even from the recycle bin. With a normal git push or a git revert, it is still in your bucket and you can take it out.
Q: If I commit directly to the production branch, is that change available in the other feature branch, or is it committed only to the master branch?
A: If you push directly to the production branch, it is available only in the main branch — you are not committing into a feature branch and then pushing it to main. If you do hotfixes on the production master branch and want the change in a development branch too, it will not duplicate by itself — you have to do it manually with a cherry-pick.
Pitfalls:
- Force pushing after a reset —
git push -fis shift-delete: the removed history is unrecoverable even from the recycle bin; normal push and revert keep the data in your bucket. - Resetting without protecting the newer work first — release B's commits vanish from the working view only if you did not commit them to another branch first; the commit-first step is the safety net.
- Forgetting the cherry-pick for direct production fixes — a hotfix committed straight to master lives only in master; duplicating it into development branches is always a manual cherry-pick.
Recap + bridge: a hotfix on release A uses git reset to the release's commit — the classical way is separate release branches; never force push after a reset (shift-delete); and direct production commits need a manual cherry-pick to reach development branches. With that, the full command inventory — from git init to git show — closes the session.
Hotfix discipline is what separates safe release engineering from heroics: the reference material's model is release branches (create the branch at release, fix critical defects there, merge fixes to mainline immediately) — the professor's "classical way" — with the reset path as the last resort when branches were never created, and revert/cherry-pick as the tools that keep history recoverable and cross-branch changes explicit.
Exam Guidance Summary
No exam-specific guidance was given in this session — no mark distributions, question types, or study advice were mentioned. What the session did establish for the road ahead: the cherry-pick scenario with a real-time example will be completed in the next class, and after that the course moves on to Maven and Selenium. The command list from the session is available in the course file section for review.
For revision purposes, the session's core is the command inventory from section 9.18 — git config, git init, git clone, git status, git branch, git checkout, git add, git rm, git reset, git revert, git stash/git stash pop, git commit, git push, git push --delete, git pull, git rebase, git log, git show, git merge, git fetch, git tag, git blame, git diff, and git remote add — and the mental models behind them: the staging area, the branch as production protection, the pull-first rule, and the revert-vs-reset distinction.
Key Industry Applications
- DevOps work happens in black screens — the command line is the primary tool for Git in DevOps roles, with UI tools as a fallback.
- Providers: GitHub (free), GitLab (trial version), CircleCI, and others; work can happen through a browser or a desktop app (GitHub Desktop).
- GitHub authentication: passwords were removed in August; SSH keys are now the standard, which shapes how repositories are cloned and pushed.
- Jenkins automation: pushing to the main/master branch triggers automatic builds that deploy to stores and websites — the reason production branches are protected and feature branches are used.
- Play Store / App Store certificates must never be committed — leaked certificates can break an APK or an app's identity.
- Legacy mobile development: apps with 1–2 million lines of code built by roughly 3,000–4,000 people across Chennai, Bangalore, China, Delhi, and the US, working follow-the-sun around the clock — the extreme case of merge conflicts.
- Code freeze in December: release tags mark exactly what goes to production at release time.
- Cleanup practice: after a feature ships, delete local and remote feature branches; leftover branches (the 148-branch vendor scenario) are untangled oldest-first using commit IDs.
- Hotfix workflow: revert small problematic commits in production rather than deleting them; cherry-pick hotfixes from production into development branches; avoid force pushes after resets.
- High availability storage: repositories are replicated across American, Indian, Southeast Asian, and Chinese servers.
- Tools in the field: GitHub Desktop, IntelliJ IDEA, Visual Studio Code, Android Studio integrations, and Beyond Compare for merge conflict resolution.
Together these add up to the working reality of version control in DevOps: the command line as the default interface, protected production branches feeding automated builds, and a disciplined undo/cleanup culture that keeps a many-thousand-person codebase shippable.
ITD Lecture 9 notes · Git: Version Control Fundamentals
Sections Breakdown
Verifying Git is installed with the bare git command, the three ways to operate Git (CLI, local UI, web), and git init creating the repository with its first (master) branch.
The hidden .git directory holds HEAD, config, objects, refs and hooks; configuration is key-value paired at local (.git/config) and global (~/.gitconfig) scopes, including the init.defaultBranch setting.
New files are untracked until git add stages them; git rm --cached and git reset unstage; .gitignore keeps secrets like app-store certificates out of the repository.
git commit (editor or -m) moves staged changes into the branch, reports the changed-file count, and returns the working tree to clean; git log shows hashes, authors, timestamps, and messages.
git reset HEAD~1 rewinds the branch one commit, git checkout <commit-id> moves HEAD to any commit (with detached HEAD warnings), and -f suppresses the warnings.
git clone downloads the entire repository including .git so no init is needed; the local main branch tracks origin/main and the status line reports ahead/behind.
A rejected push because the remote has new work; the pull exposes a conflict marked by <<<<<<< ======= >>>>>>>, resolved manually with git add and git rebase --continue; git blame and the pull-first rule.
git fetch shows changes softly without touching the working directory; git pull forces them in; identical changes on the same line auto-merge, differing ones conflict; global teams make conflicts normal.
Feature branches isolate development from the protected main branch; git checkout -b creates them, push --set-upstream links them, code review precedes merge, and -d/-D plus push --delete remove them.
git stash shelves uncommitted changes as WIP on a stashed commit; git stash pop restores them; stashes have IDs (stash@{0}), belong to branches, and affect no one else until a commit.
git revert creates a new commit that nullifies an old one while keeping history intact (the 2 a.m. production undo); git reset --hard removes commits forcefully without asking.
Cherry-picking applies one commit onto another branch without merging everything else; git rebase -i lets each commit be picked or squashed into the previous one.
A tag names a particular commit for release purposes; the December code freeze shows the tag as a filter deciding what reaches production and what stays on feature branches.
The upstream path (init, add, commit, remote add, push --set-upstream) requires SSH keys because GitHub removed password authentication; the private-key visibility makes companies prefer the downstream clone flow.
Install the git tool (Windows installer or brew install git), create repositories on GitHub.com and clone them; IDEs and GitHub Desktop run the same commands underneath, with Beyond Compare for conflicts.
No guideline exists for who merges first into a common module — control ownership and compare with Beyond Compare; leftover branches are merged oldest-first one by one using commit IDs.
No storage limit exists (local disk is the local bound; GitHub is unlimited); automatic backups via high availability replicate repositories to four or five global server locations.
Hotfixing release A uses git reset to that commit with release B protected by committing to another branch first; never force push after a reset; direct production commits need a manual cherry-pick.
Exam Revision Notes
Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.
Introducing Git and the Three Ways to Work With It
Must-know: git init creates the hidden .git folder and the default branch (master); git status reports 'On branch master' only after initialization.
⚠️ Top pitfall: Running git commands outside a repository produces 'fatal: not a git repository'.
Self-check: What output proves Git is installed when you run the bare 'git' command?
Connects to: Section 9.2, Section 9.6
Inside the .git Folder: Local and Global Configuration
Must-know: git status works only because .git exists; local config (.git/config) applies to one repository, global config (~/.gitconfig) to all repositories of the user.
⚠️ Top pitfall: Editing the global config changes every repository; init.defaultBranch only affects branches created after the setting is applied.
Self-check: Which command reveals the hidden .git folder?
Connects to: Section 9.1, Section 9.6
The Staging Area: Adding and Removing Files
Must-know: git status shows an untracked file until git add moves it to the staging area; git rm --cached and git reset unstage it; until you commit you can change things any number of times.
⚠️ Top pitfall: git add . stages everything in the folder, including files (like certificates) that must never be pushed.
Self-check: Which two commands take a wrongly added file back out of the staging area?
Connects to: Section 9.4, Section 9.2
Committing Changes
Must-know: git commit records the staged change in the branch; '1 file changed, 1 insertion' summarizes the delta; a clean working tree means nothing is pending.
⚠️ Top pitfall: Committing with nothing staged commits nothing; forgetting the message editor and not knowing :wq strands beginners in vi.
Self-check: What does git log show for each commit?
Connects to: Section 9.3, Section 9.5
Undoing Commits: Reset, Checkout, and Force
Must-know: reset HEAD~1 removes the top commit and brings its change back to the working tree; checkout to a commit detaches HEAD with warnings; force tells Git you are sure.
⚠️ Top pitfall: Committing in detached HEAD state creates commits that belong to no branch and can be lost.
Self-check: Why does git checkout <commit-id> print detached HEAD warnings?
Connects to: Section 9.4, Section 9.11
Cloning a Remote Repository
Must-know: git clone brings its own .git folder, so git status works immediately; origin is the remote copy and the status line is the only ahead/behind indicator.
⚠️ Top pitfall: Reading 'up to date' as proof that nobody else is working — the status line reflects the last fetch, not live server state.
Self-check: Why does git status work right after a clone but fail in a fresh folder?
Connects to: Section 9.2, Section 9.7
The Classic Merge Conflict
Must-know: A rejected push means the remote has commits you lack; pull, resolve conflict markers manually, git add, then git rebase --continue; normally pull before changing something.
⚠️ Top pitfall: Skipping the conflicted commit drops the other developer's changes; leaving marker lines in the file breaks the build.
Self-check: What do the <<<<<<<, =======, and >>>>>>> lines in a conflicted file separate?
Connects to: Section 9.8, Section 9.6
git fetch, git pull, and the Auto-Merge Scenario
Must-know: Fetch first, then decide: fetch updates the remote picture softly, pull forces the merge into your working tree; identical fixes on the same line auto-merge, differing ones need manual resolution.
⚠️ Top pitfall: Working against a stale remote picture because you never fetch — most conflicts start with a stale local chain.
Self-check: Why did two identical fourth-line fixes merge with no conflict?
Connects to: Section 9.7, Section 9.9
Branching: Feature Branches and the Merge Workflow
Must-know: Never work directly on main: Jenkins auto-builds it. Create feature branches with checkout -b, push with --set-upstream, merge after review, delete with -d (merged) or -D (unmerged) and push --delete for the remote.
⚠️ Top pitfall: Deleting the branch you are on fails ('Cannot delete branch... checked out'); forgetting the remote copy leaves a dead branch on GitHub.
Self-check: Why does the first push of a new branch fail, and what fixes it?
Connects to: Section 9.8, Section 9.7
Stashing Work in Progress
Must-know: git stash stores uncommitted changes in a temporary area with IDs; git stash pop restores them; stash happens before commit so it never affects other users.
⚠️ Top pitfall: Popping a stash on the wrong branch fails because stashes belong to the branch where they were created; popping by ID avoids restoring the wrong change.
Self-check: Why does popping a stash on another branch throw an error?
Connects to: Section 9.7, Section 9.9
git revert: Undoing in Production
Must-know: Revert adds a canceling commit on top (history intact, safe for shared branches); reset --hard rewinds the branch without questions (local-only undo).
⚠️ Top pitfall: Resetting a branch whose commits were already pushed strands teammates with diverging histories; revert is the shared-branch undo.
Self-check: Why is revert called the softer way of nullifying previous changes?
Connects to: Section 9.5, Section 9.12, Section 9.18
Cherry-Pick and Interactive Rebase
Must-know: Interactive rebase (git rebase -i) lists commits with commands: pick takes a commit, squash merges it into the previous one.
⚠️ Top pitfall: Cherry-picking on the wrong checked-out branch applies the commit somewhere unintended.
Self-check: Which two commands does the interactive rebase editor assign to commits?
Connects to: Section 9.11, Section 9.18
Tags and Releases
Must-know: Tags are for release purposes: a code freeze (like December) stops production changes, everything before it gets the release tag, later changes stay on feature branches.
⚠️ Top pitfall: Git refuses ambiguous reference names; a tag bookmarks whatever commit you are on, so check the log before tagging.
Self-check: What is the tag's role at a December code freeze?
Connects to: Section 9.14, Section 9.9
Moving a Local Repository to GitHub
Must-know: Pushing a local repository needs git remote add, push --set-upstream, and SSH keys — GitHub dropped password auth; companies standardize on clone-first (downstream) instead.
⚠️ Top pitfall: Expecting password-based pushes to work — without SSH keys the push fails with 'Repository not found'.
Self-check: Why did the demo's push from a local repository fail?
Connects to: Section 9.6, Section 9.15
Git in Windows, Linux, and the IDE
Must-know: You install the git tool, not a repository; in normal work you clone from GitHub, git init is only for creating a new local instance; UI tools execute the same git commands underneath.
⚠️ Top pitfall: Using git init when the repository already exists on GitHub — clone instead.
Self-check: What is the mental mapping when coming from Microsoft TFS?
Connects to: Section 9.6, Section 9.14, Section 9.16
Managing Many Branches: Merge Order Guidelines
Must-know: For branch cleanup: use commit IDs and dates to find the oldest branch, merge one by one oldest first; never automate with rebase; squash manually.
⚠️ Top pitfall: Automating a large branch cleanup with rebase produces many merge conflicts; manual oldest-first merging avoids them.
Self-check: Why do you merge the oldest branch first when cleaning up leftover branches?
Connects to: Section 9.9, Section 9.7
Git Storage, Backups, and Availability
Must-know: Storage is unlimited (disk-bound locally); high availability (HA) keeps multiple remote instances across the globe so one server failure never costs the data.
⚠️ Top pitfall: Trusting server backups to protect against your own mistakes — force pushes and resets are still dangerous even with HA.
Self-check: Why does a code repository need multiple instances across the globe?
Connects to: Section 9.6, Section 9.18
Hotfixes on Production Branches
Must-know: For a hotfix, keep two release branches classically; otherwise reset head, first commit current changes to another branch, and never force push after a reset (shift-delete).
⚠️ Top pitfall: git push -f after a reset destroys the removed history beyond recovery, unlike normal push or revert.
Self-check: Why must you commit your current changes to another branch before resetting to release A?
Connects to: Section 9.11, Section 9.12, Section 9.13
Was this lecture useful?
BitsNotes AI Assistant
Subject Notes AssistantConfigure AI Chat
Choose how to access the chatbotSigned in as
Powered by BitsNotes — 20 messages per day. No API key needed. Want unlimited access? Use "Bring Your Own Key" mode.
Sign in to use AI Chat
Get 20 free AI messages per day to ask questions about your lecture notes. Sign in with Google or GitHub — it takes 5 seconds.
Sign In to BitsNotesSwitch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.