Git Tricks from an Old Dog: Managing Course Repos Like a Pro

Git Tricks from an Old Dog: Managing Course Repos Like a Pro

How it started…

Here I am, decades of coding under my belt, deciding to dive back into student life with Harvard/edX’s CS50 AI. Nothing humbles a seasoned developer quite like being back in 'submission mode' - except maybe a poorly timed git merge conflict.

If you've ever decided to shake off the cobwebs and take an online course with coding assignments, you've probably faced the juggling act: keeping your work organized while maintaining the connection to the course repository. Here's how I managed this during CS50 AI, and some Git tricks this old dog employed along the way...

The Scenario

Let me paint you a picture of what I was dealing with: Each CS50 AI 'Project' (their fancy name for course chapters) came with its own repository. Sounds simple enough, right? But here's where it gets fun:

  • I wanted to keep all my work, including my shameful first attempts and 'note to self' commits

  • The course wanted just the solution file, pristine and perfect, in a specifically named branch

  • And naturally, I wanted the totality of my work in my personal GitHub repo for posterity (and future reference when my memory inevitably fails me)

This meant I was constantly doing the Git equivalent of a quick change act - switching origins, cleaning repositories, and cherry-picking files faster than you can say 'merge conflict'.

Starting Fresh: The Setup

The first pain point I hit was just getting started with each Project. Instead of a nice git clone command, CS50 AI drops a .zip file in your lap. After manually creating repos for the first couple of Projects, I decided automation was in order. Enter my first Git helper function (which lives, like all my aliases and helper functions, in my oh-my-zsh ‘custom’ folder):

git_init_new() {
     if [ -z "$1" ]; then
         echo "Usage: git_init_new <repository_name>"
         return 1
     fi

     repository_name="$1"
     github_username="sethderrick"

     # Check if the repository already exists on GitHub
     # Note this relies on Github's 'gh' cli...
     if gh repo view "$github_username/$repository_name" &>/dev/null; then
         echo "Repository $repository_name already exists on GitHub."
         return 1
     fi

     # Initialize a new Git repository locally
     git init

     # We're just starting with a semi-completed codebase so just add everything
     git add .

     # Create an initial commit
     git commit -m "Initial commit"

     # Create a new repository on GitHub
     gh repo create "$github_username/$repository_name" --private --confirm

     echo "Initialized local Git repository and created GitHub repository: $repository_name"
}

This function is my 'unzip to Git repo' express lane. It:

  • Makes sure I'm not about to create a repo that already exists (yup…I’ve done it)

  • Sets up the local Git repo

  • Creates a matching GitHub repo

  • Links them together

All I have to do is unzip the Project files and run git_init_new project_name.

The Two-Remote Tango

For each Project submission, CS50 AI has very specific requirements. They want your work pushed to a repo URL that looks like github.com/me50/USERNAME.git (where USERNAME is your GitHub username), and they want it on a very precisely named branch. For example, in the 'attention' Project, they wanted everything on a branch called ai50/projects/2024/x/attention.

The solution was straightforward: maintain two separate remotes. After setting up my personal repo as 'origin', I'd add the course submission repo as a second remote named 'course':

git remote add course https://github.com/me50/USERNAME.git

This gave me two remotes to work with:

  • origin pointing to my personal GitHub repo (where I kept all my work, comments, and learning process)

  • course pointing to the CS50 submission repo (where only my pristine solution file needed to go)

This setup allowed me to keep my complete work history in my personal repo while maintaining a clean submission process for the course requirements.

The Submission Shuffle

When it came time to submit my work, I needed to ensure only my solution file made it to the course repo. This meant cleaning out all the extra files and directories I'd been using for testing and notes. Here's where things got a bit tricky.

Let's say I was deep in the middle of testing something when I realized I was ready to submit. Rather than losing my work in progress, I'd use gstall (my alias for git stash --all) to temporarily store everything, including untracked files. The companion alias gstl (git stash list) helped me keep track of what I'd squirreled away.

Then came the cleaning process:

git checkout -b ai50/projects/2024/x/attention  # The exact branch name they wanted
git clean -fdx  # The nuclear option - removes all untracked files and directories

But here's a pro tip I learned the hard way: ALWAYS run git clean -fdx -n first (the -n flag does a 'dry run' and shows what would be deleted) before running the actual clean command. Nothing worse than realizing you just deleted something you meant to keep!

After the great cleanup, I needed to bring back just the solution file(s). Here's where Git's selective checkout comes in handy:

git checkout master -- filename.py    # Grab just the solution file from master

This is one of those Git commands that feels like magic - it lets you pluck exactly what you need from another branch without bringing along any extra baggage. For CS50 AI submissions, this meant I could maintain a clean submission branch while still keeping all my work safe on master.

The full submission dance looked something like this:

gstall                                              # Stash everything away
git checkout -b ai50/projects/2024/x/attention      # Create submission branch
git clean -fdx                                      # Delete all untracked files
git checkout master -- attention.py                 # Grab just the solution file
ggpush                                             # Push to current branch

And if things went sideways? That's where groh (my alias for git reset origin/$(git_current_branch) --hard) came to the rescue. It's the Git equivalent of 'undo that last thing I did' - though use it with caution!

Lessons Learned

  1. Automate early, automate often. Those repetitive Git commands will eventually bite you with a typo.

  2. Always have a backup plan. Keep your complete work in your personal repo before doing any cleanup for submission.

  3. The git clean -fdx -n dry run is your friend. Trust me on this one.

  4. Git aliases and functions aren't just for show - they reduce errors and save mental energy for actual problem-solving.

For this old dog, learning to manage multiple Git remotes and branches for course submissions was an old trick I was able to employ while learning some new tricks (AI with Python). With a bit of shell scripting and some carefully chosen aliases, even the most rigid submission requirements become manageable. Now, if you'll excuse me, I have another Project to submit...

Happy coding!