Practice - Git Collaboration Workflow#

This practice guide contains hands-on exercises to reinforce your understanding of Git workflows. Complete each exercise in order to build your skills progressively.

  • Git installed on your system

  • Basic familiarity with command line

  • A text editor of your choice


Exercise 1: Git Fundamentals#

Objective: Initialize a Git repository, make commits, and understand the staging area.

Skills Practiced:

  • Repository initialization

  • Staging and committing changes

  • Viewing history and differences

Steps#

# 1. Create a new project directory
mkdir my-git-project
cd my-git-project

# 2. Initialize Git repository
git init

# 3. Configure user information (if not done globally)
git config user.name "Your Name"
git config user.email "your.email@example.com"

# 4. Create a README file
echo "# My Git Project" > README.md

# 5. Check status - observe untracked files
git status

# 6. Add file to staging area
git add README.md

# 7. Check status again - observe staged files
git status

# 8. Commit the changes
git commit -m "Initial commit: Add README"

# 9. Create additional files
echo "print('Hello, World!')" > app.py
echo "Flask==2.0.1" > requirements.txt

# 10. Add all files at once
git add .

# 11. Commit with a descriptive message
git commit -m "feat: Add Python application and dependencies"

# 12. View commit history
git log --oneline

# 13. Make a change to README
echo "\nThis is a sample project for learning Git." >> README.md

# 14. View differences before staging
git diff

# 15. Stage and commit
git add README.md
git commit -m "docs: Update README with project description"

# 16. View complete history with graph
git log --oneline --graph

Expected Output#

* abc1234 docs: Update README with project description
* def5678 feat: Add Python application and dependencies
* ghi9012 Initial commit: Add README

Verification Checklist#

  • Repository initialized successfully

  • Three commits visible in history

  • Commit messages follow descriptive format

  • git status shows clean working tree


Exercise 2: Branching and Merging#

Objective: Create branches, make changes, and merge them back.

Skills Practiced:

  • Branch creation and switching

  • Making isolated changes

  • Merging branches

Steps#

# 1. Create and switch to a new branch
git checkout -b feature/add-config

# 2. Create a configuration file
cat > config.py << 'EOF'
DATABASE_URL = "postgresql://localhost:5432/mydb"
DEBUG = True
SECRET_KEY = "your-secret-key"
EOF

git add config.py
git commit -m "feat: Add application configuration"

# 3. Add more configuration
echo 'LOG_LEVEL = "INFO"' >> config.py
git add config.py
git commit -m "feat: Add logging configuration"

# 4. View branch status
git log --oneline --graph --all

# 5. Switch back to main branch
git checkout main

# 6. Observe that config.py doesn't exist on main
ls -la

# 7. Merge the feature branch
git merge feature/add-config -m "Merge feature/add-config into main"

# 8. Verify config.py now exists
cat config.py

# 9. Delete the feature branch (optional)
git branch -d feature/add-config

# 10. View final history
git log --oneline --graph

Expected Output#

*   abc1234 Merge feature/add-config into main
|\
| * def5678 feat: Add logging configuration
| * ghi9012 feat: Add application configuration
|/
* jkl3456 docs: Update README with project description
* mno7890 feat: Add Python application and dependencies
* pqr1234 Initial commit: Add README

Exercise 3: Gitflow Workflow#

Objective: Practice the complete Gitflow workflow including feature development, release preparation, and hotfixes.

Skills Practiced:

  • Managing multiple long-lived branches

  • Feature branch workflow

  • Release and hotfix processes

  • Tagging releases

Steps#

# === SETUP ===
# 1. Initialize a new repository
git init gitflow-demo
cd gitflow-demo

# 2. Create initial commit on main
echo "# My Application" > README.md
git add README.md
git commit -m "Initial commit"

# 3. Create develop branch
git checkout -b develop
echo "v0.1.0-dev" > VERSION
git add VERSION
git commit -m "chore: Initialize develop branch"

# === FEATURE DEVELOPMENT ===
# 4. Create feature branch from develop
git checkout -b feature/user-authentication develop

# 5. Work on feature - create auth module
mkdir -p src
cat > src/auth.py << 'EOF'
def login(username, password):
    """Authenticate user with credentials."""
    pass

def logout():
    """End user session."""
    pass
EOF

git add src/auth.py
git commit -m "feat(auth): Add login and logout functions"

# 6. Add password validation
cat >> src/auth.py << 'EOF'

def validate_password(password):
    """Validate password meets requirements."""
    return len(password) >= 8
EOF

git add src/auth.py
git commit -m "feat(auth): Add password validation"

# 7. Merge feature back to develop
git checkout develop
git merge --no-ff feature/user-authentication -m "Merge feature/user-authentication"
git branch -d feature/user-authentication

# 8. Create another feature
git checkout -b feature/dashboard develop

cat > src/dashboard.py << 'EOF'
def render_dashboard(user):
    """Render user dashboard."""
    return f"Welcome, {user}!"
EOF

git add src/dashboard.py
git commit -m "feat(dashboard): Add dashboard rendering"

git checkout develop
git merge --no-ff feature/dashboard -m "Merge feature/dashboard"
git branch -d feature/dashboard

# === RELEASE PREPARATION ===
# 9. Create release branch
git checkout -b release/1.0.0 develop

# 10. Bump version and prepare metadata
echo "1.0.0" > VERSION

cat > CHANGELOG.md << 'EOF'
# Changelog

## [1.0.0] - $(date +%Y-%m-%d)

### Added
- User authentication (login/logout)
- Password validation
- User dashboard
EOF

git add VERSION CHANGELOG.md
git commit -m "chore(release): Bump version to 1.0.0"

# 11. Merge release to main and tag
git checkout main
git merge --no-ff release/1.0.0 -m "Release version 1.0.0"
git tag -a v1.0.0 -m "Version 1.0.0"

# 12. Merge release back to develop
git checkout develop
git merge --no-ff release/1.0.0 -m "Merge release/1.0.0 back to develop"
git branch -d release/1.0.0

# === HOTFIX ===
# 13. Create hotfix from main (critical bug discovered!)
git checkout -b hotfix/1.0.1 main

# 14. Fix critical bug - add input validation
cat > src/auth.py << 'EOF'
def login(username, password):
    """Authenticate user with credentials."""
    if not username:
        raise ValueError("Username is required")
    if not password:
        raise ValueError("Password is required")
    pass

def logout():
    """End user session."""
    pass

def validate_password(password):
    """Validate password meets requirements."""
    return len(password) >= 8
EOF

git add src/auth.py
git commit -m "fix(auth): Add input validation to prevent empty credentials"

# 15. Update version
echo "1.0.1" > VERSION
git add VERSION
git commit -m "chore(release): Bump version to 1.0.1"

# 16. Merge to main and tag
git checkout main
git merge --no-ff hotfix/1.0.1 -m "Hotfix: Critical authentication bug"
git tag -a v1.0.1 -m "Version 1.0.1 - Security Hotfix"

# 17. Merge to develop
git checkout develop
git merge --no-ff hotfix/1.0.1 -m "Merge hotfix/1.0.1 to develop"
git branch -d hotfix/1.0.1

# === VERIFY ===
# 18. View complete history
git log --oneline --graph --all --decorate

# 19. List all tags
git tag -l

# 20. Show current branches
git branch -a

Expected Branch Structure#

* main (v1.0.1)
  โ””โ”€โ”€ Initial commit
  โ””โ”€โ”€ Release 1.0.0 (v1.0.0)
  โ””โ”€โ”€ Hotfix 1.0.1 (v1.0.1)

* develop
  โ””โ”€โ”€ Initialize develop branch
  โ””โ”€โ”€ Merge feature/user-authentication
  โ””โ”€โ”€ Merge feature/dashboard
  โ””โ”€โ”€ Merge release/1.0.0
  โ””โ”€โ”€ Merge hotfix/1.0.1

Verification Checklist#

  • Two tags created (v1.0.0, v1.0.1)

  • Both main and develop branches exist

  • Hotfix merged to both main and develop

  • All feature branches deleted after merge


Exercise 4: Conventional Commits#

Objective: Practice writing commit messages that follow the Conventional Commits specification.

Skills Practiced:

  • Structured commit messages

  • Semantic versioning awareness

  • Clear change documentation

Task#

Create a new repository and make commits using proper conventional commit format:

# 1. Initialize repository
mkdir conventional-commits-demo
cd conventional-commits-demo
git init

# 2. Initial setup
echo "# Todo App" > README.md
git add README.md
git commit -m "chore: Initial project setup"

# 3. Add new feature
cat > todo.py << 'EOF'
todos = []

def add_todo(task):
    todos.append({"task": task, "done": False})
    return len(todos) - 1
EOF

git add todo.py
git commit -m "feat: Add todo creation functionality"

# 4. Add another feature with scope
cat >> todo.py << 'EOF'

def complete_todo(index):
    if 0 <= index < len(todos):
        todos[index]["done"] = True
        return True
    return False
EOF

git add todo.py
git commit -m "feat(todo): Add ability to mark todo as complete"

# 5. Fix a bug
cat >> todo.py << 'EOF'

def get_all_todos():
    return todos.copy()  # Return copy to prevent mutation
EOF

git add todo.py
git commit -m "fix: Return copy of todos list to prevent external mutation"

# 6. Add documentation
cat >> README.md << 'EOF'

## Features
- Create todos
- Mark todos as complete
- List all todos
EOF

git add README.md
git commit -m "docs: Add feature list to README"

# 7. Refactor code
cat > todo.py << 'EOF'
class TodoManager:
    def __init__(self):
        self._todos = []

    def add(self, task):
        self._todos.append({"task": task, "done": False})
        return len(self._todos) - 1

    def complete(self, index):
        if 0 <= index < len(self._todos):
            self._todos[index]["done"] = True
            return True
        return False

    def get_all(self):
        return self._todos.copy()
EOF

git add todo.py
git commit -m "refactor: Convert todo functions to TodoManager class"

# 8. Add tests
cat > test_todo.py << 'EOF'
from todo import TodoManager

def test_add_todo():
    manager = TodoManager()
    index = manager.add("Buy groceries")
    assert index == 0

def test_complete_todo():
    manager = TodoManager()
    manager.add("Buy groceries")
    assert manager.complete(0) == True
EOF

git add test_todo.py
git commit -m "test: Add unit tests for TodoManager"

# 9. Breaking change
cat > todo.py << 'EOF'
class TodoManager:
    def __init__(self, storage_backend="memory"):
        self._todos = []
        self._backend = storage_backend

    def add(self, task, priority="normal"):
        self._todos.append({
            "task": task,
            "done": False,
            "priority": priority
        })
        return len(self._todos) - 1

    def complete(self, index):
        if 0 <= index < len(self._todos):
            self._todos[index]["done"] = True
            return True
        return False

    def get_all(self):
        return self._todos.copy()
EOF

git add todo.py
git commit -m "feat(todo)!: Add priority support and storage backend

BREAKING CHANGE: TodoManager now requires storage_backend parameter
and add() method accepts optional priority parameter."

# 10. View history
git log --oneline

Expected Commit Types Used#

Commit

Type

Description

1

chore

Initial setup

2

feat

New feature

3

feat(scope)

Feature with scope

4

fix

Bug fix

5

docs

Documentation

6

refactor

Code restructuring

7

test

Adding tests

8

feat!

Breaking change


Exercise 5: Resolving Merge Conflicts#

Objective: Learn to handle and resolve merge conflicts.

Skills Practiced:

  • Understanding conflict markers

  • Resolving conflicts manually

  • Completing conflicted merges

Steps#

# 1. Initialize repository
mkdir conflict-demo
cd conflict-demo
git init

# 2. Create initial file
cat > greeting.py << 'EOF'
def greet(name):
    return f"Hello, {name}!"
EOF

git add greeting.py
git commit -m "feat: Add basic greeting function"

# 3. Create two branches that will conflict
git checkout -b feature/formal-greeting
cat > greeting.py << 'EOF'
def greet(name, formal=False):
    if formal:
        return f"Good day, {name}. How do you do?"
    return f"Hello, {name}!"
EOF

git add greeting.py
git commit -m "feat: Add formal greeting option"

# 4. Go back to main and make conflicting change
git checkout main
cat > greeting.py << 'EOF'
def greet(name, enthusiasm=1):
    exclamation = "!" * enthusiasm
    return f"Hello, {name}{exclamation}"
EOF

git add greeting.py
git commit -m "feat: Add enthusiasm level to greeting"

# 5. Try to merge - this will conflict!
git merge feature/formal-greeting
# CONFLICT! Auto-merge failed

# 6. View the conflict
cat greeting.py
# You'll see conflict markers:
# <<<<<<< HEAD
# ... (main's version)
# =======
# ... (feature branch's version)
# >>>>>>> feature/formal-greeting

# 7. Resolve the conflict by combining both features
cat > greeting.py << 'EOF'
def greet(name, formal=False, enthusiasm=1):
    """
    Generate a greeting for the given name.

    Args:
        name: Person's name to greet
        formal: Use formal greeting style
        enthusiasm: Number of exclamation marks (ignored if formal)
    """
    if formal:
        return f"Good day, {name}. How do you do?"
    exclamation = "!" * enthusiasm
    return f"Hello, {name}{exclamation}"
EOF

# 8. Mark as resolved and complete merge
git add greeting.py
git commit -m "Merge feature/formal-greeting: Combine formal and enthusiasm features"

# 9. Verify the merge
git log --oneline --graph

# 10. Test the merged code
python3 -c "from greeting import greet; print(greet('World', enthusiasm=3))"
python3 -c "from greeting import greet; print(greet('Sir', formal=True))"

Conflict Resolution Tips#

<<<<<<< HEAD
(your current branch's changes)
=======
(incoming branch's changes)
>>>>>>> feature-branch
  1. Read both versions carefully before deciding

  2. Combine logic when both changes are needed

  3. Test thoroughly after resolving

  4. Use a merge tool for complex conflicts: git mergetool


Exercise 6: Interactive Rebase#

Objective: Clean up commit history using interactive rebase.

Skills Practiced:

  • Squashing commits

  • Reordering commits

  • Editing commit messages

Steps#

# 1. Create a messy history
mkdir rebase-demo
cd rebase-demo
git init

echo "# Project" > README.md
git add README.md
git commit -m "Initial commit"

echo "line 1" > file.txt
git add file.txt
git commit -m "wip"

echo "line 2" >> file.txt
git add file.txt
git commit -m "more wip"

echo "line 3" >> file.txt
git add file.txt
git commit -m "still working"

echo "line 4" >> file.txt
git add file.txt
git commit -m "almost done"

echo "line 5" >> file.txt
git add file.txt
git commit -m "done!"

# 2. View messy history
git log --oneline
# Shows: done! -> almost done -> still working -> more wip -> wip -> Initial

# 3. Interactive rebase to clean up (last 5 commits)
git rebase -i HEAD~5

# In the editor, change the commits:
# pick abc1234 wip
# squash def5678 more wip
# squash ghi9012 still working
# squash jkl3456 almost done
# squash mno7890 done!

# 4. Update the commit message to something meaningful
# "feat: Add complete file content"

# 5. View clean history
git log --oneline
# Now shows: feat: Add complete file content -> Initial commit

Rebase Commands Reference#

Command

Action

pick

Keep commit as-is

reword

Keep commit, edit message

edit

Pause to amend commit

squash

Combine with previous commit

fixup

Like squash, but discard message

drop

Remove commit entirely

Never rebase commits that have been pushed to a shared repository!


Summary Checklist#

After completing all exercises, verify you can:

  • Initialize repositories and make commits

  • Create and merge branches

  • Follow Gitflow workflow for releases

  • Write conventional commit messages

  • Resolve merge conflicts

  • Use interactive rebase to clean history

  • Tag releases appropriately


Additional Challenges#

  1. Set up a remote: Create a repository on GitHub/GitLab and practice push/pull operations

  2. Collaborate with a partner: Practice creating and reviewing pull requests

  3. Automate with hooks: Set up a pre-commit hook to lint commit messages

  4. Cherry-pick exercise: Practice moving specific commits between branches