Common Git Workflows
A Basic Workflow - everybody do their work on main
A dizzying variety of workflows exist, but at some point they pretty much all boil down to (a) merging your team's changes with with your own, and (b) delivering them back to the team.
Here's one way to accomplish it: Work on a local main
branch that tracks the main
of your remote, i.e., origin/main
1, where your team will rendezvous with changes.
git checkout main
- Get onto the branch from which you will push changes.git pull origin main
- Catch your localmain
up with latest changes from your team.- Implement your feature and test it.
git add
andgit commit
your changes on your localmain
.git pull origin main
(in case more changes have been pushed by teammates while you were working).- If there are changes from "upstream", a merge will happen (you may need to resolve conflicts).
git push origin main
- Share your scintillating creativity with your team by "catching-up the remoteorigin/main
to yourmain
. (If someone else pushed first--since your lastgit pull
--you'll have togit pull
again. Then beg your team to hold off, it's your turn now!)
A Pull Request Workflow
Instead of working-on/pushing main
, create/work-on/push a feature branch, and ask someone to look at it before it joins main
.
git checkout main
- The branch from which you will branch.git checkout -b myfeature
- Create and checkout a newmyfeature
branch.- Implement your feature and test it; add and commit your changes on
myfeature
. git push origin myfeature
- Make yourmyfeature
branch visible to the team.- Go to Github and create a Pull Request (PR) for
myfeature
. Choose one or more reviewers. - The reviewers look at your diffs, optionally make comments, and approve & merge
myfeature
tomain
. git checkout main
- Go back to main so you can catch up.git pull
- Grab the latest changes, including your merged PR.
Resolve Merge Conflict
Merge conflicts (aka "collisions") happen whenever git's merge algorithm does not have enough info to make the call--for instance, when a given line in a file has been changed in different ways by two parent commits.
Let's make the above-described kind of merge conflict happen:
- First, make a change to conflict with, by adding a file with one line:
$ git checkout main
$ git show
$ echo "HI THERE" > aNewFile
$ git add aNewFile
$ git commit -m "A new file with one line"
- Checkout a new branch
test
from the parent commit of main's HEAD.
$ git checkout -b test HEAD^
- Make a one-line file of the same name as the one created on main.
$ echo "HI THERE, I'm on a different branch" > aNewFile # Because on this branch, starting from where we did, aNewFile isn't there yet.
$ git add aNewFile
$ git commit -m "A new file with one line, but from a different branch"
- Now, attempt to merge from main.
$ git config --global core.editor "code --wait" # Set git editor to vscode (only need to do this once)
$ git merge main
Auto-merging aNewFile
CONFLICT (add/add): Merge conflict in aNewFile
Automatic merge failed; fix conflicts and then commit the result.
$ git status
$ code aNewFile
Git has modified our source file!
Note that VSCode has added some helpful "phantom text"--'(Current Change)' and '(Incoming Change)'--to help clarify what's what. Note also, that text isn't part of the file.
When you've made the file how you want it, and saved it, git add
to tell git it's OK now (as git itself tells you when you do git status
).
Finally, finish the merge with a git commit
(again, as git status
instructs)..
Save Current Work and Return to it Later
$ git stash # Push current work--your index and modified stuff--onto a "stash stack".
$ git status # Now your working tree is clean (except for files that aren't in git).
$ git checkout -b someNewBranch # Or some existing branch
# Do the work that has pre-empted you, commit/push/etc, and now return to your story...
$ git checkout main
$ git stash pop # Restore your current work to this working tree
$ git stash list # See if anything still on the stash stack.
Clean Up Commit History Before Pushing - git rebase
This is another instance of using git to practice good communication etiquette. Nobody wants to see my stumbling around; they just want to see the final draft.
Assuming I've made a series of commits I don't want people to see, I can turn them all into one commit, while still keeping the same content!
(You may first want to set your git editor to vscode, as demonstrated above in "Resolve Merge Collision").
$ git config --global core.editor "code --wait" # Set your editor to vscode
$ git rebase --interactive <some-commit-before-the-commits-to-fix>
See the git rebase tutorial for details on how to work thru the rebase.
Forgot to pull before committing
This workflow is very common for me!
Another variation of this workflow is "started-my-work-on-wrong-branch".
It typically goes like this:
- I cd into a repo that I already have cloned, but haven't touched in awhile.
- I make some changes and commit.
- I
git push
. - Git rejects my updates with something like:
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'git@github.com:walquis/learning-git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
You can re-enact this scenario in your own repo, by moving your local main
branch to one commit behind origin/main
. (How would you do that? Hover here to see.)
Now change a file and git add/commit/push.
What happened? Your main has diverged from origin/main.
What now? Some options:
- Pull-and-merge - valid, but creates a merge for no good reason - messy graph, confuses your team
- Reset, pull, cherry-pick - makes the graph look like you want it to
$ git reset --hard @^ # Tip: '@' is a synonym for HEAD
$ git pull
$ git cherry-pick <sha-of-commit-you-couldn't-push>
- Git rebase - "Replay" changes onto another commit
$ git fetch # Remember, 'pull' is a fetch and merge...but I don't want to merge this commit
$ git rebase origin/main # Replay current branch's changes onto origin/main
This is easier and more straightforward than cherry-pick, especially if I've made multiple changes.
Find a Bad Commit - git bisect
Sometimes when a bug creeps in, it's hard to find where it happened. git bisect
helps you walk through a range of commits in a binary search.
$ git help bisect # You can also 'git help' any other git command.
$ git bisect start <known-bad-commit> <older-known-good-commit>
# Test the code in your working tree to see if the current version is good.
$ git bisect good # Or bad, if the test fails
# Git will keep iterating until the bad commit is identified.
...
$ git bisect reset # Go back to the head of the branch
This is effective, but a little tedious. What if we could automate it? If you can create a test.sh script that git can run for you, which will exit with status 0 (true) if the test succeeds, and non-zero (e.g. 1, which will mean false), then git bisect can use that script:
$ bash test.sh
$ echo $? # 0 if good, 1 if bad
$ git bisect start <known-bad-commit> <older-known-good-commit>
$ git bisect run bash test.sh
assuming your remote is named origin
. It probably is.