On this page

Git Explained: Branches, Pull Requests, Merge Conflicts and Multiple Devices

How Git really works for low-code makers: commits, branches, pull requests, merge conflicts and one repository across multiple devices.

Ynias Bensch9 min read

Key takeaways

  • Git is snapshots (commits) plus movable labels (branches). That is the whole model.
  • Everything is local until you push. Nobody sees your work before that.
  • You run git init once. Every other device clones.
  • Pull requests are a feature of GitHub or Azure DevOps, not of Git itself.
  • reset rewrites history, revert adds history. Know the difference and you can undo almost anything.

In the low-code world you can get surprisingly far without Git. Solutions, environments and pipelines do the heavy lifting, and version control feels like something the pro-code people worry about. For me that stopped being true the moment I started doing more pro-code work and letting AI agents write code for me. Suddenly Git was not a nice-to-have, it was the safety net under everything.

The problem is that most of us learn Git backwards. You copy a few commands from a colleague and it works. Until it doesn’t. Then you are looking at a rejected push, a “detached HEAD” or a file full of strange arrows, and nobody ever explained what is actually going on.

So today we will go through the whole picture:

  • How Git actually works (commits, staging, local versus remote)
  • Branches and main
  • Push, pull and pull requests
  • Merging and merge conflicts
  • Setting up Git on multiple devices
  • Best practices and the most common issues

Why bother? The benefits

  • Full history and undo: every change is recorded, and you can go back to any point in time.
  • Safe experimentation: try something on a branch. If it fails, delete the branch and main never knew.
  • Collaboration without overwriting each other: several people work in parallel and Git combines the results.
  • An audit trail: who changed what, when and why. Very useful when a bug shows up three months later.
  • The foundation for ALM: build pipelines, automated checks and deployments all start from a repository.

And there is one benefit that matters more every month: Git is your safety net for AI-assisted development. When a coding agent like Claude Code or GitHub Copilot edits your project, commits are your checkpoints and branches are your sandbox. Let the agent work on a branch, review the diff like you would review a colleague’s pull request, and if it went the wrong way, throw everything away with one command. Without Git, you are trusting the agent blindly. With Git, you stay in control.

The mental model: snapshots and labels

Forget the commands for a moment. Git is two things:

  • Commits: snapshots of your entire project at a moment in time. Every commit has a unique hash, a message and a pointer to the commit before it. Together they form a chain of history.
  • Branches: movable labels that point to a commit. Nothing more.

Everything happens locally first. Your changes travel through three zones on your machine before anyone else sees them:

text
Working directory  --git add-->  Staging  --git commit-->  Local repo  --git push-->  Remote
        ^                                                                              |
        +------------------------------ git pull / git clone ---------------------------+

You edit files in your working directory. With git add you choose what goes into the next snapshot. With git commit you save that snapshot in your local repository. Only git push shares it with the rest of the world.

bash
git status                  # what changed, what is staged?
git add file.js             # or: git add .
git commit -m "Add validation to login form"
git log --oneline --graph   # view history

Branches and main

main is simply the conventional name for the stable line of your project. Whatever lives there should work. When you want to build a feature or fix a bug, you create a branch so you can experiment without breaking main.

bash
git switch -c feature/login   # create a new branch and switch to it
git switch main               # back to main
git branch                    # list branches

Because a branch is literally a tiny file containing a hash, branches are cheap. Create one per feature or fix and delete it when you are done.

Remote, push and pull

The remote (called origin by default) is the shared copy of your repository on GitHub, Azure DevOps or GitLab.

  • git push sends your commits to the remote.
  • git fetch downloads what others pushed, without touching your work.
  • git pull is fetch plus merge into your current branch.
bash
git push -u origin feature/login   # first time: links local and remote branch
git push                           # afterwards this is enough
git pull                           # download and merge

Pull requests (and why “push requests” do not exist)

A common point of confusion: push is a Git command, but a pull request is not. A pull request (PR, or “merge request” in GitLab) is a feature of the platform hosting your repository.

The flow looks like this:

  1. You push your feature branch to the remote.
  2. You open a PR: “please pull my changes into main“.
  3. Colleagues review the diff and leave comments.
  4. Automated checks run (build, tests, solution checker).
  5. After approval, the branch gets merged and deleted.

In most teams main is protected, so nobody can push to it directly. Everything goes through a PR. Even when you work alone, a PR is a useful moment to review your own work before it lands.

Merging: three flavours and a rebase

  • Fast-forward: main did not change since you branched off, so Git just moves the label forward. No extra commit.
  • Merge commit: both branches have new commits. Git creates a commit with two parents that joins them. Full history stays visible.
  • Squash merge: all commits of your feature branch are compressed into one commit on main. Clean, linear history. Very popular for PRs.

Then there is rebase: Git replays your commits on top of the latest main, as if you had branched off later. The result is a linear history, but your commits are rewritten. Rule of thumb: only rebase branches that are yours alone. Never rebase a shared branch.

A full example from start to finish

Let’s put it all together. This is the complete loop for one feature, and it is the rhythm you will repeat every day:

bash
# 1. Get the project (first time only)
git clone https://github.com/you/project.git
cd project
# 2. Start from an up-to-date main
git switch main
git pull
# 3. Create a branch for your work
git switch -c feature/export-button
# 4. Do the work, commit in small steps
git add .
git commit -m "Add export button to order screen"
git add .
git commit -m "Handle empty result in export"
# 5. Share your branch
git push -u origin feature/export-button
# 6. Open a pull request on GitHub or Azure DevOps,
#    get it reviewed and merge it there
# 7. Clean up
git switch main
git pull
git branch -d feature/export-button

Seven steps, and only step 4 is where the actual work happens. Everything else is routine that becomes muscle memory within a week.

Merge conflicts

Git merges automatically as long as changes do not touch each other. A conflict happens when two branches changed the same lines differently, or when one branch deletes a file the other one edits. Git stops and marks the file:

text
<<<<<<< HEAD
const timeout = 30;
=======
const timeout = 60;
>>>>>>> feature/login

Above the ======= is your current branch, below is the incoming one. Pick one version, combine them or write something new, remove the markers and finish:

bash
git add file.js
git commit              # during a rebase: git rebase --continue
git merge --abort       # emergency brake: back to before the merge

VS Code gives you buttons for this (Accept Current, Accept Incoming, Accept Both), which makes it a lot less scary.

The best way to avoid conflicts is small, short-lived branches and pulling main into your branch regularly.

Git on multiple devices

The misconception I see most: people think they need to initialise Git on every device. You don’t. You run git init exactly once. Every other device clones.

bash
# Device 1 (existing project)
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/you/project.git
git push -u origin main
# Device 2, 3, ...
git clone https://github.com/you/project.git

Starting from scratch? Create the repository on GitHub or Azure DevOps first and clone it everywhere. That is the easiest route.

Per device, configure your identity once:

bash
git config --global user.name "Your Name"
git config --global user.email "you@example.com"

A few things to keep in mind:

  • Use a separate SSH key per device (or the Git Credential Manager with HTTPS). If you lose a laptop, you revoke one key.
  • Pull when you start working, push when you stop. That is the whole discipline.
  • Never put a repository inside OneDrive or Dropbox to “sync” it. Those tools corrupt the .git folder. The remote is your sync.

Best practices

  • Small, focused commits with a message that explains why, written in the imperative: “Fix rounding in VAT calculation”.
  • One branch per feature or fix, with a clear name (feature/..., fix/...). Keep them alive for days, not weeks.
  • Protect main and work through pull requests.
  • Add a .gitignore from the very first commit (node_modules, bin, obj, .env, build output).
  • Never commit secrets. Once pushed, a password or API key lives in the history. The only correct response is to rotate the key.
  • If you must force push after a rebase, use git push --force-with-lease instead of --force. Never on shared branches.
  • Tag your releases (git tag v1.2.0) so you can always find what was in production.
  • Mixing Windows and Mac or Linux? Define line endings in a .gitattributes file.

Common issues and how to fix them

ProblemCauseFix
Push rejected (non-fast-forward)The remote has commits you do not havegit pull (optionally --rebase), then push again
Committed on the wrong branchForgot to switchgit switch -c right-branch, then on the wrong branch git reset --hard HEAD~1
Detached HEADYou checked out a commit instead of a branchgit switch -c new-branch to keep your work
Local changes block a switch or pullUncommitted workgit stash, do your thing, git stash pop
Last commit was wrongTypo or forgotten filegit commit --amend (only if not pushed yet)
Undo a pushed commitBug made it to the remotegit revert <hash> creates a new commit that reverses it
Commits “lost” after reset or rebaseHistory was rewrittengit reflog shows everywhere HEAD has been. Almost nothing is truly gone.
File in .gitignore is still trackedIt was committed beforegit rm --cached file
Large file refuses to pushGitHub limit of 100 MBUse Git LFS or remove the file from history

The distinction that causes the most confusion: reset rewrites history (fine locally, dangerous after a push), while revert adds history (always safe). If you know those two and reflog, you can recover from almost any mistake.

What about Power Platform?

Power Platform solutions are not flat code, so you work with unpacked solutions (pac solution unpack) or the native Git integration in Dataverse. All the principles above still apply, but merge conflicts in solution XML and canvas app source files are harder to resolve than conflicts in regular code. That makes short-lived branches and clear agreements about who works on which component even more important.

A few practical tips:

  • Commit the unpacked solution, never only the zip file. A zip is a binary blob, so Git cannot show you what changed.
  • Agree on one person per canvas app at a time. Canvas app source files conflict easily and are painful to merge.
  • Let a pipeline do the export and unpack, so every commit has the same structure regardless of who made it.

If you want to see how this fits into a full deployment setup, have a look at my guide on setting up Power Platform ALM with Azure DevOps.

Conclusion

Git looks intimidating because of its commands, but the model underneath is simple: snapshots, labels, and a shared remote. Commit small, branch often, merge through pull requests and pull before you start. Once that rhythm is in place, the scary parts become rare, and when they do show up, git reflog has your back.

Questions, or a Git horror story of your own? Let me know in the comments.

FAQ

What is the difference between git fetch and git pull?

git fetch only downloads new commits from the remote. Your own files stay untouched. git pull does a fetch and then immediately merges those commits into your current branch.

Do I need to run git init on every computer?

No. You run git init once, on the machine where the project starts. On every other device you use git clone.

What is the difference between merge and rebase?

Merge joins two branches with a new commit and keeps the history as it happened. Rebase replays your commits on top of another branch, which gives a linear history but rewrites your commits. Use rebase only on branches nobody else uses.

Is a pull request part of Git?

No. It is a feature of platforms like GitHub, Azure DevOps and GitLab, built on top of Git branches.

How do I undo a commit that is already pushed?

Use git revert <hash>. It creates a new commit that reverses the change, which is safe for shared branches. Avoid git reset on anything you already pushed.

Ynias Bensch
Power Platform consultant. Automating business processes with applications and workflows.