By now, you can probably look into Git commands on your own. Some useful commands to get familiar with include
$ git cherry-pick # quite easy to use in VSC
$ git reset
$ git rebase
$ git tagMerging and conflicts are a common part of the Git experience. Conflicts generally arise when two people have changed the same lines in a file, or if the same file was modified on both branches involved in the merge. Luckily, resolving a merge conflict is often straightforward. Let's get familiar with solving merge conflicts by purposefully creating a conflict in this next section, and solving it three different ways.
- Checkout a new branch from the
mainbranch and name itconflict - In VSC, open the
learning/mistakes.pyfile and edit line 6return ('Getting a PhD is easy') # update to say return ('Getting a PhD is a piece of cake')
- Add, commit, and push this change to the
conflictbranch. Perhaps the message isbad joke
- Add, commit, and push this change to the
- Checkout
mainand edit the same linereturn ('Getting a PhD is simple')
- Add, commit, and push to
main. Perhaps this message isterrible joke
- Add, commit, and push to
- In GitHub, open a PR
main <-- conflict-
You'll get a warning of the merge conflict. Create the PR anyway
-
Click on Resolve conflicts and you'll enter the GitHub editor. On the top, we have the incoming change from
conflict, and below the======we have the current change frommain -
To resolve, simply pick one version, or a combination of the two, and delete the extra lines. To finish, click Mark as resolved and then Merge
-
Back in the PR, you can see there is a new merge commit that resolves the conflict
-
- Close the PR without merging the PR. Now we want to undo all our merge work and try again another way
- Checkout the
conflictbranch and take a look at the git log or git graph in VSC - Run
git fetchto update the origin remote. You'll see the merge commit onorigin/conflict, while your localconflictbranch is behindorigin - Run
git push -fto force push your local code and overwrite the merge commit onorigin - Alternatively, if you pulled down the merge commit from
originto your local machine (meaning your last commit is a merge commit, combining the two branch histories), we can undo it and then force push$ git reset --merge HEAD~1 $ git push -f
git resetcan be dangerous, and I do not recommend it's use in general unless you fully understand what it does. It can be especially confusing for code that is worked on by multiple developers, as its use is untraceable (unlikegit revert). Learn more by reading the docsHEADrefers to the current state of your repository, andHEAD~1is the last commit in your repository. SimilarlyHEAD~2is the second to last commit.
- Checkout the
- Now, let's manage a merge conflict with VSC
-
Merge
conflictintomain$ git checkout main $ git merge conflict > Auto-merging learning/mistakes.py > CONFLICT (content): Merge conflict in learning/mistakes.py > Automatic merge failed; fix conflicts and then commit the result.
-
In VSC, the Source Control tab shows our file under "Merge Changes" with a "C" for "Conflict." Clicking on it to view the conflict, we see
-
We can manually edit like before, or click one of the options at the top of the merge conflict.
Accept Current Changewill saveGetting a PhD is simpleand discard the rest. You can explore the other options. -
You can save your changes, edit the commit message in VSC if desired, and commit the merge by clicking the check mark. But don't click just yet!
-
- Let's see if we can fix this entirely in Git
- We will use VIM for this, so it will be useful to know some basic commands
- Type
vim learning/mistakes.pyto open the file with VIM and see the merge conflict - Use the arrow keys to move the curser around
- Type
ito switch to "insert" mode - Hit
Esc keyto switch back to command mode - Type
:wqto write your changes (save) and quit VIM
- Type
- Back in Git
$ git status # optional $ git add . $ git status # optional $ git commit # note I left off the -m 'message' part # There is a default merge commit message in place # Edit it with VIM commands and :wq # Or just accept the default and :q $ git push # to finally push the merge changes to master
- We will use VIM for this, so it will be useful to know some basic commands
Code formatting is important for code readability. As you code over time, and especially when you develop with multiple people, a code formatter will make it simple to keep consistent formatting across all your documents. Personally, I like YAPF (yet another python formatter). The "knob settings" I use can be found in the .style.yapf file.
- Install yapf
- Type
Ctrl + `(thats the backtick character) to open the terminal in VSC - In the terminal, type
pip install yapf - Close the terminal and open
learning/mistakes.py
- Type
- Run the formatter
- Type
Shift + Alt + Fto run the formatter- Alternatively type
Ctrl + Shift + Pto bring up the command palette, and search for "Format Document"
- Alternatively type
- Notice which lines change and how the formatter makes the document easier to read.
- Type
- You can obviously find many more examples online, and I encourage you to play around with the "knob settings" to get a sense of what each one does.
When code is released publicly, developers use version numbers to keep things straight. It marks a snapshot in time, and allows users to call a frozen version of the code. Once a version is released, it NEVER change, and the results are always the same. However, new versions can always be created, and they supersede old releases.
For questions regarding versioning, refer to this guide on Semantic Versioning. Here's a quick cheat sheet for how our lab operates (note: devN releases are rare)
- MAJOR.MINOR.PATCH[.DEV] or
X.Y.Z[.devN]will be the version style used- MAJOR version zero
0.y.zis for initial development. Anything MAY change at any time - MAJOR version X
X.y.zMUST be incremented if any backwards incompatible changes are introduced - MINOR version Y
x.Y.zMUST be incremented if new, backwards compatible functionality is introduced - PATCH version Z
x.y.ZMUST be incremented if only backwards compatible bug fixes are introduced - In early development, it MAY be desireable to provide API access to a developmental release
x.y.z.devN- Dev versions are superseded by full releases of the same number. For example
0.1.0 --> 0.2.0.dev0 --> 0.2.0.dev1 --> 0.2.0 --> 0.2.1
- MAJOR version zero
Version releases are achieved by the use of "tags." Tags can be created in GitHub, or with the git tag command. I'll leave it to you to explore more on this front.
- Note that once tags are created, we can reference them quite easily. Similar to how we can reference PRs, Issues, and individual commit hashes.
A great feature about Git and GitHub is that we can reference specific commits, tags, PRs, and issues.
To reference a commit, simply type its unique SHA-hash and it will automatically get turned into a link. In fact, you don't need to type the entire 40-character hash, just enough characters to uniquely identify it. I recommend 8 characters (GitHub requires at least 7, VSC requires at least 6). There is a good conversation on this topic on Stack Overflow.
Examples
- Commit message:
important message that references commit 57f3e830 - PR/Issue comment:
Look into 57f3e830
PRs and Issues are a GitHub feature. They are tracked together by one set of incrementing numbers. To reference them, type # and the appropriate number. Simply typing #1 is enough to reference PR/issue #1 in GitHub.
- In GitHub, a handy dropdown will help you link a specific PR/issue
- In VSC, you will see a dropdown after typing
#in a commit message. VSC will insert the PR/Issue title and number. Be aware that the title is unnecessary for the link. Further, VSC will not show links to PRs/Issues in commit messages in general (this is a GitHub feature) - It all works the same in Git, just no helpful dropdown menu
- The GitHub extension in VSC enables one awesome feature, which is issue creation/linking via code comments. Within a comment, typing
#1will link to issue #1 in VSC (not in GitHub). We also covered how to create issues from# TODOtags in the beginner module - Note, referrals will show up as comments inside the referenced PR/Issue
Examples
- Commit message:
important message that references PR/issue #1 - PR/Issue comment:
Look into #1 - Code comment:
# python comment addressed in #1VSC will link these references to issues via the GitHub extension - You can also create a new issue straight from a PR/issue comment you want to reference: read more
Git allows local repositories to track multiple remotes (i.e. multiple separate repositories). Learn more from the docs.
- Typically, we would want to do this to track a forked repo and its parent. This allows us to directly apply changes or updates to the parent repo to our forked repo. In this case, the two repositories have a common history, so merging changes is straightforward. Just like merging branches in the same repo, you will occasionally have merge conflicts, but they are also generally easy to resolve.
- In rare cases, it may be useful to track repos with unrelated histories. In this case, merging is more complex and requires more manual work.
- Fork this repo and setup a local copy on your machine. If you need help setting up this initial structure, see the beginner module.
- Verify the existing remotes
$ git remote -v > origin https://github.com/<username>/training.git (fetch) > origin https://github.com/<username>/training.git (push)
- Here, there is one remote, named
origin, located at the indicated URL (your forked repo). We can also see where the commandsgit fetchandgit pushpoint (recallgit pullis runninggit fetchfirst).
- Here, there is one remote, named
- Add a new remote to track your fork's parent repo and verify
$ git remote add upstream https://github.com/djmcgregor/training.git # ^ name of new remote $ git remove -v > origin https://github.com/<username>/training.git (fetch) > origin https://github.com/<username>/training.git (push) > upstream https://github.com/djmcgregor/training.git (fetch) > upstream https://github.com/djmcgregor/training.git (push)
- We now have two remotes,
originandupstream, that track two different GitHub repos - It is common to use
upstreamas the remote name for fork parent repos, but you can name it whatever you want. Just don't overwriteoriginunless you know what you are doing
- We now have two remotes,
- Access the
upstreamremotegit fetch/pushdefaults toorigin, but we can access theupstreamremote by explicitly pointing to it
git fetch upstream
- Apply changes from
upstreamtoorigin- Let's assume there are changes made to the parent repo that is being tracked by
upstream - Make a new branch, and pull changes from the
upstream/main(or other) branch
git checkout -b parent_changes git pull upstream/main # OR git fetch upstream # fetches all updates git merge upstream/main
- Solve any merge conflicts
- PR or merge branch into
main
git checkout main git merge parent_changes
- Let's assume there are changes made to the parent repo that is being tracked by
One case where this may be desired is to combine two separate repos. In this case, each repo has it's own unique history with commits, PRs, Issues, etc. To combine them, we will need to sacrifice the PR/Issue history from at least one of the repos. If this is undesirable, there may be alternatives such as git subtree or packaging. Both are explored further in the advanced module. Also see a good discussion on Stack Overflow
- Within
project-a, create a new branch offmain, and add a remote toproject-bgit checkout -b b_work git remote add -f project-b <path/to/project-b> # ^ immediately fetches remote git merge --allow-unrelated-histories project-b/main # or whatever branch you want git remote remove project-b
- If you want to keep tags from
project-b, consider fetching the remote with the--tagsoption
git fetch project-b --tags
- If you want to keep tags from
- Cleanup time
- This effectively superimposes the
project-brepo history on top of theproject-ahistory. Watch out for identically named files or tags - Note: Commits that reference PRs/Issues will retain the number, but the link will disappear. I am unsure if they will create a new link once a matching PR/Issue is created.
- Note: Importing
project-btags whose names are identical to native tags inproject-awill fail silently. The tags will simply be dropped from the import list, and theproject-atags will remain.
- This effectively superimposes the