Mastering Local Git Repositories
Now that we understand the fundamental concepts of version control and basic Git commands, it's time to get our hands dirty with practical exercises. Working with local repositories is like learning to drive in an empty parking lot before venturing onto busy streets—it allows you to develop essential skills in a controlled environment before collaborating with others.
In this session, we'll focus on hands-on practice with local Git repositories. Through a series of guided exercises, you'll gain experience with repository creation, file tracking, staging and committing changes, viewing history, and navigating your project's timeline. By the end of this session, you'll be comfortable with the core Git workflow and ready to apply these skills to your own projects.
Remember, mastery comes through repetition and practice. Don't worry if you make mistakes—that's part of the learning process. Git is designed to track your changes, not judge them, and almost any mistake can be undone.
Prerequisites for This Session
Required Software
- Git: Make sure Git is installed on your system. You can verify by opening a terminal or command prompt and typing:
$ git --version
Should display something like: git version 2.39.1
If Git is not installed or you need to update it:
- Windows: Download and install from git-scm.com
- macOS: Install via Homebrew with
brew install gitor download from git-scm.com - Linux: Use your distribution's package manager (e.g.,
sudo apt install gitfor Debian/Ubuntu)
Configuration Setup
Before we begin the exercises, let's ensure your Git environment is properly configured:
$ git config --global user.name "Your Name"
$ git config --global user.email "your.email@example.com"
These settings identify you as the author of your commits. If you're concerned about privacy, you can use a GitHub-provided no-reply email address for public repositories.
$ git config --global init.defaultBranch main
Sets the default branch name to "main" for new repositories
Optional but Recommended
- Configure your text editor: Set your preferred editor for Git commit messages
$ git config --global core.editor "code --wait"
For Visual Studio Code (replace with your preferred editor command)
Exercise 1: Creating and Initializing a Local Repository
In this exercise, we'll create a simple Python project and initialize it as a Git repository.
Goals
- Create a new directory for your project
- Initialize a Git repository
- Add initial project files
- Create your first commit
Step-by-Step Instructions
- Create a new project directory:
$ mkdir python_todo_app$ cd python_todo_app - Initialize a Git repository:
$ git initYou should see: Initialized empty Git repository in [path]/python_todo_app/.git/
This creates a hidden .git directory that will store all version history and metadata for your project. Think of it as planting the seed for your project's timeline.
- Verify repository creation:
$ ls -laThis should list all files, including the hidden .git directory
- Create a README file:
$ echo "# Python Todo Application\n\nA simple command-line todo application written in Python." > README.mdEvery good project starts with documentation. This creates a basic README file with a brief description.
- Create a .gitignore file:
$ echo "__pycache__/\n*.py[cod]\n*$py.class\n.env\nvenv/\n.idea/\n.vscode/" > .gitignoreThis tells Git which files to ignore. For Python projects, we typically ignore bytecode files, virtual environments, and IDE-specific files.
- Create the main Python file:
$ touch todo.pyNow open this file in your text editor and add the following code:
#!/usr/bin/env python3 """ Simple Todo Application This application allows users to add, view, and complete todo items from the command line. """ def main(): todos = [] print("Welcome to the Todo App!") print("Enter 'help' to see available commands.") while True: command = input("\nEnter command: ").strip().lower() if command == 'quit' or command == 'exit': print("Goodbye!") break elif command == 'help': print_help() elif command == 'list': list_todos(todos) elif command.startswith('add '): add_todo(command[4:], todos) elif command.startswith('complete '): try: index = int(command[9:]) - 1 complete_todo(index, todos) except ValueError: print("Please provide a valid number.") else: print("Unknown command. Enter 'help' to see available commands.") def print_help(): """Display available commands.""" print("\nAvailable commands:") print(" list - Show all todo items") print(" add [text] - Add a new todo item") print(" complete [num] - Mark an item as completed") print(" help - Show this help message") print(" quit - Exit the application") def list_todos(todos): """Display all todo items with their status.""" if not todos: print("No todos yet! Add some using 'add [text]'") return print("\nTodo List:") for i, todo in enumerate(todos, 1): status = "✓" if todo["completed"] else " " print(f"{i}. [{status}] {todo['text']}") def add_todo(text, todos): """Add a new todo item.""" if not text: print("Cannot add an empty todo. Try 'add [text]'") return todos.append({"text": text, "completed": False}) print(f"Added: {text}") def complete_todo(index, todos): """Mark a todo item as completed.""" if index < 0 or index >= len(todos): print(f"No todo with number {index + 1}") return todos[index]["completed"] = True print(f"Completed: {todos[index]['text']}") if __name__ == "__main__": main() - Check status to see untracked files:
$ git statusThis shows the current state of your working directory. You should see that README.md, .gitignore, and todo.py are listed as untracked files.
- Stage files for commit:
$ git add README.md .gitignore todo.pyThis adds the files to the staging area, preparing them for commit.
- Verify staged files:
$ git statusNow you should see the files listed under "Changes to be committed".
- Create your first commit:
$ git commit -m "Initial commit: Basic Todo application structure"This creates a snapshot of your project at this point in time with a descriptive message.
- Verify your commit:
$ git logThis shows your commit history. You should see your initial commit with the message you provided.
Understanding What Happened
In this exercise, you've gone through the complete basic Git workflow:
- You created a new directory and initialized it as a Git repository
- You added project files (README.md, .gitignore, and todo.py)
- You staged these files using git add
- You committed the staged files with a descriptive message
- You verified your commit using git log
Congratulations! You've created your first Git repository and made your first commit. This is the foundation of Git's version control system.
Exercise 2: Making Changes and Viewing Differences
Now that we have a repository with an initial commit, let's practice making changes, viewing differences, and creating additional commits.
Goals
- Make changes to existing files
- View differences between working directory and repository
- Stage and commit changes
- Explore commit history
Step-by-Step Instructions
- Enhance the Todo application:
Open todo.py in your text editor and add a new function to save todos to a file:
import json import os def save_todos(todos): """Save todos to a file.""" with open("todos.json", "w") as f: json.dump(todos, f) print("Todos saved to file.") def load_todos(): """Load todos from a file if it exists.""" if os.path.exists("todos.json"): with open("todos.json", "r") as f: return json.load(f) return []Now modify the main() function to use these new functions:
def main(): todos = load_todos() print("Welcome to the Todo App!") print("Enter 'help' to see available commands.") while True: command = input("\nEnter command: ").strip().lower() if command == 'quit' or command == 'exit': save_todos(todos) print("Goodbye!") break elif command == 'help': print_help() elif command == 'list': list_todos(todos) elif command.startswith('add '): add_todo(command[4:], todos) elif command.startswith('complete '): try: index = int(command[9:]) - 1 complete_todo(index, todos) except ValueError: print("Please provide a valid number.") elif command == 'save': save_todos(todos) else: print("Unknown command. Enter 'help' to see available commands.")Also update the print_help() function to include the new 'save' command:
def print_help(): """Display available commands.""" print("\nAvailable commands:") print(" list - Show all todo items") print(" add [text] - Add a new todo item") print(" complete [num] - Mark an item as completed") print(" save - Save todos to file") print(" help - Show this help message") print(" quit - Exit the application (auto-saves)") - View changes with git diff:
$ git diffThis shows the differences between your working directory and the last commit. Lines prefixed with + are additions, and lines with - are removals.
- Stage the changes:
$ git add todo.py - View staged changes:
$ git diff --stagedThis shows changes that are staged for commit. It's a good practice to review these before committing.
- Commit the changes:
$ git commit -m "Add file persistence to save and load todos" - Update the README file:
Open README.md and enhance the documentation:
# Python Todo Application A simple command-line todo application written in Python. ## Features - Add and list todo items - Mark items as completed - Persistent storage with JSON - Simple command-line interface ## Usage Run the application with: ``` python todo.py ``` ## Commands - `list` - Show all todo items - `add [text]` - Add a new todo item - `complete [num]` - Mark an item as completed - `save` - Save todos to file - `help` - Show help message - `quit` - Exit application (auto-saves) - Check status again:
$ git statusThis shows that README.md has been modified but not yet staged.
- Stage and commit the README changes:
$ git add README.md$ git commit -m "Update README with features and usage instructions" - View commit history:
$ git logYou should now see three commits in your history.
- Try a more compact log format:
$ git log --onelineThis shows a condensed version of the commit history, with one commit per line.
- View detailed changes in a specific commit:
$ git show [commit_hash]Replace [commit_hash] with the hash of your second commit (the one adding file persistence). This shows the exact changes made in that commit.
Understanding What Happened
In this exercise, you've practiced:
- Making meaningful changes to your project
- Using git diff to view changes before staging
- Creating separate, focused commits for different types of changes
- Exploring commit history with different log formats
- Examining specific commits in detail
These skills are essential for maintaining a clean and informative project history.
Exercise 3: Undoing Changes and Exploring History
In this exercise, we'll practice various ways to undo changes and explore your repository's history. Understanding how to revert mistakes is crucial for confident use of Git.
Goals
- Make and revert unstaged changes
- Undo staged changes
- Examine and revert to previous versions of files
- Explore your repository's history
Step-by-Step Instructions
- Make a change you'll discard:
Open todo.py and add a deliberately "bad" function that we'll discard later:
def experimental_feature(): """A function we'll discard later.""" print("This is an experimental feature that isn't ready yet!") print("It might crash your system or delete all your todos!") return "Oops, something went wrong!" - Check status and view changes:
$ git status$ git diff - Discard the unstaged changes:
$ git restore todo.pyThis reverts todo.py to the version in the last commit, discarding your experimental function.
- Verify the changes were discarded:
$ git status$ git diffYou should see "nothing to commit, working tree clean" and no output from git diff.
- Make a change and stage it, then unstage it:
Add a comment to todo.py:
# TODO: Add support for due dates and prioritiesStage the change:
$ git add todo.pyNow unstage it without discarding the change:
$ git restore --staged todo.py - Verify that the change is unstaged but still present:
$ git status$ git diffYou should see the file listed as modified but not staged, and git diff should show your comment.
- Stage and commit this change:
$ git add todo.py$ git commit -m "Add comment for future enhancements" - Examine your project at a previous commit:
$ git log --onelineFind the hash of your initial commit.
$ git checkout [initial_commit_hash]This puts you in a "detached HEAD" state, where you can look at your project as it was at the time of that commit.
- Notice the state of your files:
Open todo.py in your editor and notice that it's reverted to its initial state without the file persistence functions.
- Return to the latest commit:
$ git checkout main - View changes between specific commits:
$ git log --oneline$ git diff [initial_commit_hash] [second_commit_hash]This shows changes between your initial commit and the second commit.
- View changes to a specific file over time:
$ git log --follow --oneline todo.pyThis shows only commits that affected todo.py.
- Recover a specific version of a file:
Let's say we want to revert README.md to its original version without affecting other files.
$ git checkout [initial_commit_hash] -- README.mdThis extracts README.md from the initial commit and places it in your working directory.
- Stage and commit this restored version:
$ git add README.md$ git commit -m "Revert README to original version"
Understanding What Happened
In this exercise, you've practiced several important undoing operations:
- git restore: Discards unstaged changes
- git restore --staged: Unstages changes without discarding them
- git checkout [commit]: Temporarily revisits a previous state of the repository
- git checkout [commit] -- [file]: Extracts a specific file from a previous commit
These operations give you fine-grained control over your repository and allow you to recover from various types of mistakes.
Exercise 4: Working with Branches
In this exercise, we'll practice creating and managing branches in your local repository. Branches allow you to work on different features or experiments in isolation.
Goals
- Create and switch between branches
- Make changes in different branches
- Merge branches together
- Resolve a simple merge conflict
Step-by-Step Instructions
- Check your current branch:
$ git branchThis shows all local branches with an asterisk next to the current one (should be main).
- Create and switch to a new branch:
$ git checkout -b feature/due-datesThis creates a new branch called "feature/due-dates" and switches to it.
- Make changes in this branch:
Let's modify todo.py to add support for due dates:
# Add these imports at the top from datetime import datetime, timedelta # Modify the add_todo function def add_todo(text, todos): """Add a new todo item.""" if not text: print("Cannot add an empty todo. Try 'add [text]'") return # Check for due date in format: "text @tomorrow" or "text @YYYY-MM-DD" parts = text.split(" @") todo_text = parts[0] due_date = None if len(parts) > 1: date_text = parts[1].lower() try: if date_text == "today": due_date = datetime.now().strftime("%Y-%m-%d") elif date_text == "tomorrow": due_date = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d") elif date_text == "nextweek": due_date = (datetime.now() + timedelta(days=7)).strftime("%Y-%m-%d") else: # Assume it's in YYYY-MM-DD format datetime.strptime(date_text, "%Y-%m-%d") # Validate date format due_date = date_text except ValueError: print(f"Warning: Invalid date format '{date_text}'. Date will not be set.") todos.append({"text": todo_text, "completed": False, "due_date": due_date}) print(f"Added: {todo_text}" + (f" (due: {due_date})" if due_date else "")) # Modify the list_todos function def list_todos(todos): """Display all todo items with their status.""" if not todos: print("No todos yet! Add some using 'add [text]'") return print("\nTodo List:") for i, todo in enumerate(todos, 1): status = "✓" if todo["completed"] else " " due_info = f" (due: {todo['due_date']})" if todo.get("due_date") else "" print(f"{i}. [{status}] {todo['text']}{due_info}") # Update the print_help function def print_help(): """Display available commands.""" print("\nAvailable commands:") print(" list - Show all todo items") print(" add [text] - Add a new todo item") print(" add [text] @YYYY-MM-DD - Add todo with due date") print(" add [text] @today/@tomorrow/@nextweek - Add with relative due date") print(" complete [num] - Mark an item as completed") print(" save - Save todos to file") print(" help - Show this help message") print(" quit - Exit the application (auto-saves)") - Stage and commit these changes:
$ git add todo.py$ git commit -m "Add support for due dates in todos" - Switch back to the main branch:
$ git checkout mainNotice that todo.py reverts to its previous state without due date support.
- Create another branch for a different feature:
$ git checkout -b feature/priority-levels - Make changes for priority levels:
Modify todo.py to add support for priorities:
# Modify the add_todo function def add_todo(text, todos): """Add a new todo item.""" if not text: print("Cannot add an empty todo. Try 'add [text]'") return # Check for priority in format: "text !high" or "text !low" parts = text.split(" !") todo_text = parts[0] priority = "normal" # Default priority if len(parts) > 1: priority_text = parts[1].lower() if priority_text in ["high", "medium", "low"]: priority = priority_text else: print(f"Warning: Invalid priority '{priority_text}'. Using 'normal'.") todos.append({"text": todo_text, "completed": False, "priority": priority}) print(f"Added: {todo_text} (priority: {priority})") # Modify the list_todos function def list_todos(todos): """Display all todo items with their status.""" if not todos: print("No todos yet! Add some using 'add [text]'") return print("\nTodo List:") for i, todo in enumerate(todos, 1): status = "✓" if todo["completed"] else " " priority = todo.get("priority", "normal") priority_marker = "" if priority == "high": priority_marker = " [!]" elif priority == "low": priority_marker = " [.]" print(f"{i}. [{status}] {todo['text']}{priority_marker}") # Update the print_help function def print_help(): """Display available commands.""" print("\nAvailable commands:") print(" list - Show all todo items") print(" add [text] - Add a new todo item") print(" add [text] !high/!medium/!low - Add with priority") print(" complete [num] - Mark an item as completed") print(" save - Save todos to file") print(" help - Show this help message") print(" quit - Exit the application (auto-saves)") - Stage and commit these changes:
$ git add todo.py$ git commit -m "Add support for priority levels in todos" - Switch back to main:
$ git checkout main - Merge the due dates feature:
$ git merge feature/due-datesThis incorporates the changes from the due-dates branch into main.
- Now merge the priority levels feature:
$ git merge feature/priority-levelsThis will likely create a merge conflict since both branches modified the same parts of todo.py.
- Resolve the merge conflict:
Open todo.py in your editor. You'll see sections marked with conflict markers:
<<<<<<< HEAD # Due dates code from feature/due-dates branch ======= # Priority levels code from feature/priority-levels branch >>>>>>> feature/priority-levelsEdit the file to integrate both features. For example:
# Add these imports at the top from datetime import datetime, timedelta # Combined add_todo function def add_todo(text, todos): """Add a new todo item.""" if not text: print("Cannot add an empty todo. Try 'add [text]'") return # Extract priority if present parts = text.split(" !") todo_text = parts[0] priority = "normal" # Default priority if len(parts) > 1: priority_text = parts[1].lower() if priority_text in ["high", "medium", "low"]: priority = priority_text else: print(f"Warning: Invalid priority '{priority_text}'. Using 'normal'.") # Extract due date if present date_parts = todo_text.split(" @") todo_text = date_parts[0] due_date = None if len(date_parts) > 1: date_text = date_parts[1].lower() try: if date_text == "today": due_date = datetime.now().strftime("%Y-%m-%d") elif date_text == "tomorrow": due_date = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d") elif date_text == "nextweek": due_date = (datetime.now() + timedelta(days=7)).strftime("%Y-%m-%d") else: # Assume it's in YYYY-MM-DD format datetime.strptime(date_text, "%Y-%m-%d") # Validate date format due_date = date_text except ValueError: print(f"Warning: Invalid date format '{date_text}'. Date will not be set.") todos.append({ "text": todo_text, "completed": False, "priority": priority, "due_date": due_date }) status_msg = f"Added: {todo_text}" if priority != "normal": status_msg += f" (priority: {priority})" if due_date: status_msg += f" (due: {due_date})" print(status_msg) # Combined list_todos function def list_todos(todos): """Display all todo items with their status.""" if not todos: print("No todos yet! Add some using 'add [text]'") return print("\nTodo List:") for i, todo in enumerate(todos, 1): status = "✓" if todo["completed"] else " " priority = todo.get("priority", "normal") priority_marker = "" if priority == "high": priority_marker = " [!]" elif priority == "low": priority_marker = " [.]" due_info = f" (due: {todo['due_date']})" if todo.get("due_date") else "" print(f"{i}. [{status}] {todo['text']}{priority_marker}{due_info}") # Updated print_help function def print_help(): """Display available commands.""" print("\nAvailable commands:") print(" list - Show all todo items") print(" add [text] - Add a new todo item") print(" add [text] @YYYY-MM-DD - Add todo with due date") print(" add [text] @today/@tomorrow/@nextweek - Add with relative due date") print(" add [text] !high/!medium/!low - Add with priority") print(" complete [num] - Mark an item as completed") print(" save - Save todos to file") print(" help - Show this help message") print(" quit - Exit the application (auto-saves)") - Mark the conflict as resolved:
$ git add todo.py$ git commit -m "Merge priority-levels feature and integrate with due-dates" - View the branch history:
$ git log --graph --oneline --allThis shows a graphical representation of your branch history, including the merge.
- Clean up by deleting the merged branches:
$ git branch -d feature/due-dates$ git branch -d feature/priority-levels
Understanding What Happened
In this exercise, you've practiced:
- Creating and switching between branches
- Developing different features in isolation
- Merging branches back into the main line of development
- Resolving merge conflicts by integrating changes from different branches
- Viewing branch history and cleaning up merged branches
Branching is one of Git's most powerful features, allowing parallel development and experimentation without affecting your main codebase.
Exercise 5: Working with Git Aliases and Configuration
In this exercise, we'll customize Git to make it more efficient for your workflow by setting up aliases and configuration options.
Goals
- Create useful Git aliases
- Configure Git settings for better usability
- Learn about Git's customization options
Step-by-Step Instructions
- View your current Git configuration:
$ git config --listThis shows all your Git configuration settings.
- Create aliases for common commands:
$ git config --global alias.st status$ git config --global alias.co checkout$ git config --global alias.br branch$ git config --global alias.ci commit$ git config --global alias.unstage "restore --staged"$ git config --global alias.last "log -1 HEAD"$ git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" - Try out your new aliases:
$ git stEquivalent to git status
$ git lgShows a colorized, compact log with branch graph
- Configure useful Git settings:
$ git config --global core.editor "code --wait"Sets Visual Studio Code as your default editor for commit messages
$ git config --global pull.rebase trueConfigures git pull to use rebase instead of merge
$ git config --global push.default simplePush only the current branch to the upstream branch
$ git config --global color.ui autoEnables automatic color in Git output
- Create a .gitignore file for Python projects:
Create a global .gitignore file for all your Python projects:
$ git config --global core.excludesfile ~/.gitignore_globalNow edit this file (or create it if it doesn't exist):
$ touch ~/.gitignore_global$ code ~/.gitignore_globalAdd common Python-specific entries:
# Python __pycache__/ *.py[cod] *$py.class *.so .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # Virtual Environments env/ venv/ ENV/ env.bak/ venv.bak/ # IDEs and editors .idea/ .vscode/ *.swp *.swo .DS_Store - Create a Git hook for commit message validation:
In your python_todo_app repository, create a commit-msg hook:
$ mkdir -p .git/hooks$ touch .git/hooks/commit-msg$ chmod +x .git/hooks/commit-msgEdit the commit-msg file:
$ code .git/hooks/commit-msgAdd this script to enforce conventional commit message format:
#!/bin/sh commit_msg_file=$1 commit_msg=$(cat "$commit_msg_file") # Check if the message follows the pattern: type: description if ! echo "$commit_msg" | grep -qE '^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .+$'; then echo "ERROR: Commit message does not follow the conventional format." echo "Please use one of the following types: feat, fix, docs, style, refactor, test, chore" echo "Example: feat: add user authentication" exit 1 fi # Check if the message is too short if [ $ { #commit_msg} -lt 10 ]; then echo "ERROR: Commit message is too short (less than 10 characters)" exit 1 fi # Check if the first letter of the description is capitalized if ! echo "$commit_msg" | grep -qE '^[^:]+: [A-Z]'; then echo "ERROR: Description should start with a capital letter" exit 1 fi exit 0 - Try out the commit hook:
Make a small change to README.md and try committing with different message formats:
$ echo "# Additional note" >> README.md$ git add README.md$ git commit -m "updated readme"This should fail because it doesn't follow the conventional format
$ git commit -m "docs: Update README with additional note"This should succeed
Understanding What Happened
In this exercise, you've learned how to:
- Create aliases to make Git commands shorter and more convenient
- Configure Git settings to match your preferences
- Set up a global .gitignore file for all your projects
- Create Git hooks to enforce commit message conventions
These customizations make Git more efficient and help maintain consistency across your projects.
Best Practices for Working with Local Repositories
Repository Organization
- Keep repositories focused: Each repository should represent a single project or logical unit
- Include documentation: Always add a README.md file with setup instructions and project information
- Use .gitignore: Exclude build artifacts, dependencies, and sensitive files from version control
- Structure your project: Organize code into logical directories based on functionality
Commit Practices
- Commit often: Make small, focused commits that represent a single logical change
- Write meaningful messages: Use clear, descriptive commit messages explaining what and why
- Separate concerns: Don't mix unrelated changes in a single commit
- Review before committing: Always check git status and git diff before committing
Branching Strategy
- Branch for features: Create separate branches for new features and bug fixes
- Keep main/master stable: The main branch should always be in a working state
- Delete merged branches: Clean up branches after they've been merged
- Use descriptive branch names: Name branches according to what they accomplish (feature/login, bugfix/email-validation)
History Management
- Don't rewrite published history: Avoid force-pushing or rewriting history that others may have based work on
- Use interactive rebase locally: Clean up your branch before sharing it with others
- Prefer merge over rebase for shared branches: Preserve the history of collaborative work
- Keep a linear history when possible: This makes it easier to understand the development timeline
General Tips
- Back up your repositories: Regularly push to a remote repository for safekeeping
- Keep your Git updated: Use the latest version to benefit from improvements and security fixes
- Learn Git commands incrementally: Master the basics before moving to advanced features
- Prefer Git CLI over GUIs: Understanding the command line gives you more control and transferable skills
Common Git Pitfalls and How to Avoid Them
Committing Sensitive Information
Problem: Accidentally committing passwords, API keys, or other sensitive data.
Solution:
- Use environment variables or dedicated configuration files that are excluded via .gitignore
- If you accidentally commit sensitive data, do not push it; instead, use
git filter-branchor BFG Repo-Cleaner to remove it - Consider using pre-commit hooks to prevent committing sensitive patterns
Committing Large Files
Problem: Adding large binary files bloats your repository and makes operations slow.
Solution:
- Use .gitignore to exclude build artifacts, compiled binaries, and large datasets
- For essential large files, consider Git LFS (Large File Storage)
- Store data files externally and document how to obtain them
Confusing the Working Directory, Staging Area, and Repository
Problem: Not understanding where your changes reside leads to confusion about what will be committed.
Solution:
- Always check
git statusbefore and after operations - Remember that changes must be explicitly staged before they can be committed
- Visualize the three areas: working directory (your files), staging area (what will be committed), repository (what has been committed)
Losing Work by Checkout or Reset
Problem: Using git checkout or git reset --hard without understanding their implications.
Solution:
- Before using any command that could overwrite changes, ensure your work is committed or stashed
- Use
git checkout -- filenameto discard changes to a specific file, notgit checkoutwithout arguments - Understand the difference between
git reset --soft,git reset --mixed, andgit reset --hard
Creating Messy Commit History
Problem: Making too many small, meaningless commits or combining unrelated changes into a single commit.
Solution:
- Aim for commits that represent a single logical change
- Use
git add -pto stage specific parts of files - Write descriptive commit messages explaining the purpose of changes
- Use branches to separate different features or bug fixes
Merge Conflicts
Problem: Frightening merge conflicts that seem impossible to resolve.
Solution:
- Pull/merge from the target branch frequently to reduce the size of conflicts
- Understand the conflict markers (
<<<<<<<,=======,>>>>>>>) and what they represent - Use a visual diff tool with
git mergetoolfor complex conflicts - Don't panic – conflicts are normal and resolvable with care
Beyond the Basics: Next Steps with Git
As you become comfortable with local Git repositories, consider exploring these more advanced topics:
Remote Repositories and Collaboration
- Working with remotes:
git remote,git push,git pull,git fetch - Pull requests/merge requests: Contributing to projects on GitHub, GitLab, or Bitbucket
- Collaboration workflows: Centralized, feature branch, fork and pull, GitFlow
Advanced Git Operations
- Interactive rebase: Cleaning up commit history before sharing
- Cherry-picking: Applying specific commits from one branch to another
- Rebasing: Alternative to merging for integrating changes
- Reflog: Recovering from Git mistakes by accessing reference logs
Git Internals
- Git objects: Understanding blobs, trees, commits, and refs
- How Git stores data: Content-addressable storage and SHA-1 hashing
- Under the hood: Exploring the .git directory structure
Git Tools and Integrations
- CI/CD integration: Using Git with continuous integration and deployment systems
- Git hooks: Automating tasks before/after Git operations
- Git GUI clients: Visual tools for working with Git repositories
- IDE integrations: Using Git within your development environment
Key Takeaways
- Git is a journey: Start with the basics and gradually build your skills
- Practice is essential: Regular use of Git commands will make them second nature
- Mistakes happen: Git is designed to track changes, not judge them, and provides tools to recover from most errors
- Focus on workflow: Establish good habits for commits, branching, and repository organization
- Every project is different: Adapt Git practices to fit the needs of your specific project
By mastering local repositories, you've built a solid foundation for version control. As you continue your development journey, you'll find that Git becomes an invaluable tool for tracking your progress, exploring alternatives, and collaborating with others.
Assignment: Todo Application with Version Control
Extend the Todo application we've been working on by adding new features while practicing good Git workflow habits.
Requirements
- Continue working with the python_todo_app repository we created in the exercises
- Create a new branch for each of the following features:
- feature/categories: Add the ability to categorize todos (work, personal, shopping, etc.)
- feature/search: Add functionality to search todos by text
- feature/recurring: Add support for recurring todos (daily, weekly, monthly)
- Implement each feature on its respective branch, making multiple meaningful commits for each
- Merge each feature branch back into main when complete
- Use appropriate commit messages following conventional commit format
- Resolve any merge conflicts that arise
- Document your Git workflow as you go, taking screenshots of important steps
Submission
Submit the following:
- A link to your GitHub repository (if you've pushed it) or a zip file of your local repository
- A document with screenshots showing your Git workflow, including:
- Branch creation and switching
- Commit history on each branch
- Merge operations
- Conflict resolution (if applicable)
- A brief reflection on what you learned about Git workflow and any challenges you encountered
Bonus Challenges
- Create a visualization of your commit history using
git log --graph --oneline --all - Create a Git alias for a command you found yourself using frequently
- Implement a pre-commit hook that checks Python syntax before allowing commits
Additional Resources
Documentation and Books
- Official Git Documentation
- Pro Git Book - Free online book by Scott Chacon and Ben Straub
- Learn Git Branching - Interactive visualization tool
Cheat Sheets and Quick References
- GitHub Git Cheat Sheet
- Visual Git Cheat Sheet
- Oh Sh*t, Git!?! - Recovering from common Git mistakes
Practice and Tutorials
- Resources to learn Git - Curated by GitHub
- Git Immersion - Guided tour through Git fundamentals
- Atlassian Git Tutorials