When you're the only person touching a project and pushing code to GitHub, Git can be pretty basic. Everything is just add
, commit
, push
, repeat. Branching is unnecessary, so the project's tree can be underwhelmingly bare. While complexities can always arise, the nature of the beast works in your favor and discourages them.
However, all of this changes when you add teammmates to a project. Collaboration can increase productivity, but it also throws a wrench into version control. Enter the dreaded conflicts. Two practices that were previously ancillary to your process suddenly become paramount: branching and pulling.
In fact, I would say that your mantra when doing collaborative work should be: "Always be branching, and always be pulling." Specifically, before you make a pull request, you should pull from the main branch to preemtively check for any conflicts. Additionally, after you make a pull request, you should treat the branch you were just working on as dead. Create a new branch, and never return to the old one.
After barely a week of working on a group project, I've thrown together this list of even more recommendations based on what little I've learned on the fly. If you know of any other useful hacks for handling Git workflow on a group, or if you see anything below that you know is just straight-up wrong, please let me know!
Coding
Begin coding for a project
- Open the repo:
cd REPONAME
(e.g.,cd budget-frontend
) - Ensure you are on the main branch:
git checkout main
- Ensure you are up-to-date:
git pull
- Create a new branch for your task:
git checkout -b BRANCHNAME
(e.g.,git checkout -b error-messages
) - Open VS Code:
code .
- Make all your edits
- Add all your updates:
git add .
- Commit your updates:
git commit -m "[FILENAME] UPDATE"
(e.g.,git commit -m "[Signup] Fix error messages"
) - Set the upstream during the initial push of your updates:
git push -u origin BRANCHNAME
(e.g., `git push -u origin error-messages) - Push directly on later updates (only works if you have already set an upstream):
git push
Continue coding after taking a break
- Go to the branch you were working on:
git checkout BRANCHYOUWEREWORKINGON
(e.g.,git checkout test-signup
) - Pull in any new code from the main branch:
git pull origin main
- Resolve any merge conflicts that may now be revealed
- Proceed as above (i.e., pick up at step 6 in the “Begin Coding for a Project” section)
Continue coding after submitting a pull request
- Create a new branch:
git checkout -b NEWBRANCHNAME
(e.g.,git checkout -b test-login
) - Pull in your code from the branch you were just working on:
git merge OLDBRANCHNAME
(e.g.,git merge test-signup
) - Pull in any new code from the main branch:
git pull origin main
- Resolve any merge conflicts that may now be revealed
- Proceed as above (i.e., pick up from step 6 in the “Begin Coding for a Project” section)
Testing
Test your code before submitting
Frontend
- Run both the frontend and the backend:
npm start
(each one) - Go to the site in Chrome
- Navigate to the section of the site related to your updates
- Investigate page elements if necessary
- Take screenshots of what you do to help others who will test your code to know they can replicate what you have done
Backend
- Run the backend:
npm start
- Open Postman
- Test routes
- Take screenshots of what you do to help others who will test your code to know they can replicate what you have done
Test someone else's code before approving
- Go to the main branch:
git checkout main
- Ensure your main branch is up-to-date:
git pull
- Checkout the branch with the code you need to test:
git checkout SOMEONEELSESBRANCH
(e.g.,git checkout readme-edits
) - Test everything indicated in the pull request
Pull Requests
Submit a pull request
- Pull in any new code from the remote repo:
git pull origin main
- Resolve any merge conflicts that may now be revealed
- Add, commit, and push your latest code
- Go to the main page of the remote repo on GitHub
- Go to a list of the repo’s branches by clicking on the “branch” link to the right of the “main” dropdown above the list of all the repo’s top-level folders and files
- Begin a new pull request by clicking on the “New pull request” button on the far right of the line with your branch’s name
- Include a list of to-do items for someone else to use for testing in the body of the pull request (these should include all the tests that you have already done from the “Test Your Code Before Submitting” section)
- Assign someone else to handle your pull request by clicking the sprocket image next to “Assignees” and selecting someone from the list
- Finalize the pull request by clicking the green “Create pull request” button
- If you pull request has conflicts, add notes about how to resolve them in the comments section of the pull request
Approve pull requests
- Go to the “Pull requests” tab for the repo on GitHub
- Click on the specific pull request in the list
- Check off tasks as you perform each test
- Scroll down and click the green “Merge pull request” button once you have tested everything indicated in the pull request (if this button is grayed out, then you have a merge conflict; see below)
- After merged, delete the branch
Conflicts
Resolve your merge conflicts
- Ensure you are on the branch with the merge conflict:
git checkout CONFLICTEDBRANCH
(e.g.,git checkout budget-function
) - Get all the conflicts from the main branch to display in your local repo’s conflicted branch:
git pull origin main
- Open VS Code (if not already open):
code .
- Resolve conflicts (search for the >>>>>, <<<<<, or ===== pipes to find conflicts; your final version must not include any of those)
- Add all your updates:
git add .
- Commit your updates:
git commit -m "[FILENAME] Resolve conflicts with WHATEVER ISSUE"
(e.g.,git commit -m "[App] Resolve conflicts with updateBudget function"
) - Push your updates:
git push origin CONFLICTEDBRANCH
(or justgit push
if branch’s upstream is already established, which it should be)
Resolve others' merge conflicts
- Go to the “Pull requests” tab for the repo on GitHub
- Click on the specific pull request with the conflict in the list
- Read the provided suggestions about the merge conflict in the comments
- Consider setting up a call with whoever submitted the pull request and possibly other members of the team
- Consider doing a driver-navigator setup to fix the problems with others’ inputs
- Click the gray “Resolve conflicts” button
- Resolve conflicts on each page (search for the >>>>>, <<<<<, or ===== pipes to find conflicts; your final version must not include any of those)
- Click the gray “Mark as resolved” button after resolving conflicts on each page (you will need to do this separately for each page)
- Finalize merge after resolving all the conflicts by clicking the green “Commit merge” button
View Old Work
Check out old commits
- Go to the main branch:
git checkout main
- Ensure your main branch is up-to-date:
git pull
- See a list of all the commits:
git log
- Go to a particular commit:
git checkout STRINGOFRANDOMLETTERSANDNUMBERSASSOCIATEDWITHASPECIFICCOMMIT
(e.g.,git checkout 6ade91e6f0d15a83eg775f0b9907e6fc021fa0fa
) - Create a new branch starting at this commit:
git switch -c NEWBRANCHNAME
(e.g.,git switch -c updates
) - Make any edits in this new branch, which will not include any code from later commits
Undelete an old branch
- Go to the “Pull requests” tab for the repo on GitHub
- Click on the “Closed” link (next to the “Open” link, which is the default tab)
- Click on a pull request from the branch you want to resurrect
- Scroll down to the bottom of the pull request, which includes a line about deleting the branch, and click the “Restore branch” button on that line
- Work with any code specific to this deleted branch (this is only useful if the deleted branch contains code that is not accessible in any other branch; this approach is only possible if the branch was deleted after completing a pull request)
Suggestions
Create useful commit messages
- Include the name of the file you edited in brackets:
git commit -m "[FILE] MESSAGE"
(e.g.,git commit -m "[App] Change function to class"
) - Include a short, imperative message after the brackets to describe what you did in the edits associated with this commit (so instead of
Fixed the stuff with the thing
orI resolved the conflict we were having with the error messages
, doFix error messages
) - If your updates affect multiple files or an entire folder, either put all files in the brackets (e.g.,
[Signup, Login]
) or the folder name in the brackets (e.g.,[src]
or[components]
) - Aim for precision and accuracy, both of which should make the commit log a useful resource in case we run into hiccups and need to backtrack
- In contrast, include detailed information when handling bugs: If you’re troubleshooting a bug, then the longer and more detailed the commit message, the better; do a commit before you attempt to resolve the bug (e.g.,
[Signup] Pull json error messages from backend
), along with various commits while attempting to resolve the bug (e.g.,[Signup] Try to drill into the error object handled by the catch block in the handleSubmit function
), and then a final commit once the bug is resolved (e.g.,[Signup] Display the backend error messages by using error.response.data.msg in the catch block of the handleSubmit function
); this saves the entire debugging process in the commit log for posterity (presumably, if you run into a bug with a functionality on one page, the rest of the team will probably run into a similar bug with a similar functionality on another page, so having the entire history of fixing your bug in the commit log will make the rest of the team’s experience fixing their similar bugs much easier)
Create useful pull requests
- Include a list of to-do items with file paths or URL paths for the assignee to follow in order to perform specific tests:
- [ ] ONEFOLDER/ANOTHERFOLDER/FILE
(e.g.,- [ ] src/components/pages/Signup.js
) - Include screenshots of what the assignee should see at those paths
- Include any additional notes to help the assignee replicate the tests that you have already done
Perform these tasks frequently
- Pull updates from the main branch into your current branch:
git pull origin main
- Resolve any merge conflicts that may result from the above
Adjust frequency of pull requests
- During a workday, submitting multiple pull requests per day makes sense
- Over the weekend or on holiday, submitting multiple pull requests makes less sense
- Coordinate with whoever will be assigned your pull requests to ensure you are doing what will best help them to quickly approve your pull requests
Get GitHub notifications
- Ensure your notifications are turned on for web and mobile devices
- Be on the lookout for notifications on GitHub (they will appear as blue dots on the notification bell image in the upper right corner of the page)
- Whenever anyone assigns you anything, you should receive a notification
Create a backup branch
- When you plan to rewind, delete changes, or otherwise do something drastic, then create a new branch:
git checkout -b BACKUPBRANCH
(e.g.,git checkout -b backup
) - Save the current version of the project in this branch, and push it to GitHub
- Access this later if your radical changes need to be discarded
Never use hard or force options
- Discard all history and go back to the specified commit (thus moving the repo’s HEAD backwards):
git reset --hard STRINGOFRANDOMLETTERSANDNUMBERSASSOCIATEDWITHASPECIFICCOMMIT
(e.g.,git reset --hard 6ade91e6f0d15a83eg775f0b9907e6fc021fa0fa
) - Force your local repo’s main branch to become the remote repo’s main branch (thus ignoring any conflicts or anyone else’s work and commits):
git push --force
Common Git Commands
Clone
- Copy a remote repo to your local computer by using its URL:
git clone https://github.com/USER/REMOTEREPONAME
(e.g.,git clone https://github.com/SEI-ATL/mern-auth-frontend
) - Name your local repo something different than the remote repo:
git clone https://github.com/USER/REMOTEREPONAME LOCALREPONAME
(e.g.,git clone https://github.com/SEI-ATL/mern-auth-frontend frontend
)
Checkout
- Switch from one branch to another:
git checkout OTHERBRANCH
(e.g.,git checkout readme-edits
) - Create and switch to a new branch:
git checkout -b NEWBRANCH
(e.g.,git checkout -b signup-edits
)
Add
- Add a file to staging area:
git add FILENAME
(e.g.,git add index.html
) - Add all recently edited files to staging area:
git add .
Commit
- Record all currently staged changes as a new instance of the version history:
git commit -m "[FILE] IMPERATIVE MESSAGE"
(e.g.,git commit -m "[Signup] Edit appearance"
) - Record all currently staged changed, and make any recently unstaged changes suddenly become staged to lock them into version control, all without using a message:
git commit -a
Push
- Send locally committed changes to the remote repo (to main branch if on main branch or to specified branch if on that branch and an upstream for it is already established):
git push
- Specify that you are sending from your local main branch to the remote’s main branch (achieves the same as above, but with more words to clarify its functionality):
git push origin main
- Specifically send changes from your local branch to its corresponding remote branch:
git push origin BRANCHNAME
(e.g.,git push origin error-messages
) - Set an upstream for a branch (after which, you can just push with
git push
):git push -u origin BRANCHNAME
(e.g.,git push -u origin error-messages
) - Send all recently changed local branches to the remote repo:
git push --all origin
Pull
- Fetch and merge changes from the remote directory to your local directory (if on main branch):
git pull
- Specify that you are pulling from your remote main branch to whatever local branch you are on (achieves the same thing as above if in the main branch, but helps reveal merge conflicts if not in main branch):
git pull origin main
Merge
- Merge a specific branch’s history into the current branch:
git merge OTHERBRANCH
(e.g.,git merge signup-edits
) - Back out of a merge:
git merge --abort
Status
- List all files yet to be staged:
git status
- Show branch and tracking info along with the files to be staged:
git status -b
Branch
- List all local branches:
git branch
- Create a new branch:
git branch BRANCHNAME
(e.g.,git branch signup-edits
) - Delete a branch:
git branch -d BRANCHNAME
(e.g.,git branch -d signup-edits
)
Log
- List the version history of the current branch (i.e., its commit history):
git log
- List the version history of a particular file:
git log --follow FILE
(e.g.,git log --follow index.html
) - List only a one-line version of the current branch’s version history:
git log --oneline
Stash
- Temporarily store all the modified tracked files:
git stash save
- Restore the most recently stashed files:
git stash pop
- List all stashed changed files:
git stash list
- Discard most recently stashed changed files:
git stash drop
- Stash all files, even the ones that aren’t staged, possibly to never touch again:
git stash --include-untracked
RM
- Delete the file from the working directory, and stage the deletion:
git rm FILE
(e.g.,git rm happy.js
)
Clean
- Remove untracked files from the working tree (this will delete files):
git clean -f
Reset
- Unstage your files, but preserve their contents (this will undo an unpushed commit):
git reset
Switch
- Change from one branch to another, and move to the head of that branch (very similar to checking out a branch):
git switch BRANCHNAME
(e.g.,git switch signup-edits
) - Create a new branch and switch to it (very similar to checking out a branch when you create it; necessary for working with old commits):
git switch -c NEWBRANCHNAME
(e.g.,git switch -c updates
)
Top comments (1)
I recommend a few adjustments.
git switch
This command is reimaging of
git checkout
git branch -D main
I think that the local copy for the remote leads to unnecessary management. Rather than
git fetch
git switch -c BRANCHNAME origin/main
git rebase
This is not specific to your article and more of a suggested area of explanation.
Rebase is a very powerful tool for organizing your changes. When consider a core part of your workflow it can have a huge impact on merge conflicts and communication (code reviews)