Practical #3: Advanced Git & Code Quality
This session covers Git workflows for collaborative development and a set of automated tools for maintaining code quality: formatting, static analysis, and runtime bug detection.
Part A — Advanced Git (60 min)
Exercise 1 — Branching and History
Difficulty: EasyUse your tp_makefile/ project from TP1.
-
Initialize a Git repository and make an initial commit:
cd tp_makefile git init git add . git commit -m "Initial commit: data stats project" -
Create and switch to a feature branch:
git checkout -b feature/add-median -
Add a
stats_medianfunction tostats.candstats.h(you can use a simple implementation — sort then pick the middle value). Commit your changes. -
Switch back to
mainand modify something else (e.g. update aprintfformat inmain.c). Commit. -
Visualize the history:
git log --oneline --graph --all
You should see two branches diverging from the initial commit.
Exercise 2 — Merge and Conflict Resolution
Difficulty: Rx-
From the
mainbranch, merge the feature branch:git merge feature/add-median -
If there is no conflict, create one deliberately: before merging, modify the same line in
stats.hon both branches (e.g. add a different comment on the same line), then attempt the merge. -
When the conflict appears, open the conflicted file. You will see markers like:
<<<<<<< HEAD // current branch version ======= // incoming branch version >>>>>>> feature/add-median -
Resolve the conflict by editing the file to keep the desired content. Remove all conflict markers.
-
Stage and commit:
git add stats.h git commit -m "Merge feature/add-median with conflict resolution" -
Verify the merge:
git log --oneline --graph --all
Use git diff to review changes before committing. Use git status to see which files are conflicted.
Exercise 3 — Stash and Rebase
Difficulty: RxGit Stash
Sometimes you are in the middle of work and need to switch context without committing incomplete changes. git stash saves your working directory and index to a stack.
-
Make some changes to
main.c(do not commit them):# add a printf or change something in main.c git status # should show modified: main.c -
Stash the changes:
git stash push -m "WIP: new output format" git status # working tree should be clean -
Switch to a different branch, do some work, then come back:
git checkout -b hotfix/typo # fix something in stats.h git add stats.h && git commit -m "fix: correct typo in header" git checkout main -
Restore your stashed changes:
git stash list # list saved stashes git stash pop # restore the most recent stash git status # your WIP changes are back
Use git stash drop to discard a stash without applying it, or git stash apply stash@{N} to apply a specific entry without removing it from the stack.
Git Rebase
Rebase rewrites your branch history on top of another branch, producing a linear history.
-
Create a new feature branch from
main:git checkout -b feature/variance -
Add a
stats_variancefunction instats.c/stats.h. Commit. -
Go back to
mainand make another commit (e.g. update theREADMEorMakefile):git checkout main # make a small change and commit -
Rebase your feature branch on top of the updated
main:git checkout feature/variance git rebase main git log --oneline --graph --all
You should now see a linear history where feature/variance starts from the tip of main.
Questions
- What is the key difference between
git mergeandgit rebase? - Why is it generally unsafe to rebase a branch that has already been pushed and shared with others?
- When would you prefer
git stashover creating a temporary commit?
Exercise 4 — Remote Collaboration
Difficulty: Rx-
Create a new project on GitLab (or use the one provided by your instructor).
-
Add the remote and push:
git remote add origin <your-gitlab-url> git push -u origin main -
Create a new branch, make a small change, push it:
git checkout -b feature/improve-output # ... make changes and commit ... git push -u origin feature/improve-output -
On GitLab, open a Merge Request from
feature/improve-outputintomain. -
Review the diff in the GitLab UI: look at the changed files, add a comment on a specific line.
-
Approve and merge the MR via the UI, then pull locally:
git checkout main git pull
Part B — Code Quality (60 min)
Exercise 5 — Code Formatting with clang-format
Difficulty: Easy
- Take the following deliberately ugly file and save it as
ugly.c:
#include<stdio.h>
#include"stats.h"
int main( void ){
int arr[]={10,20,30 ,40,50};
int n=5;
printf("Sum=%d\n",stats_sum(arr,n));
printf( "Mean=%d\n",stats_mean(arr ,n ));
if(n>0){printf("Min=%d Max=%d\n", stats_min(arr,n),stats_max(arr,n));}
return 0;}
-
Run
clang-formaton it:clang-format ugly.c -
Create a
.clang-formatconfiguration file in your project root:clang-format -style=llvm -dump-config > .clang-format -
Apply formatting in-place:
clang-format -i ugly.c -
Inspect the result. Try changing the style (e.g.
BasedOnStyle: GoogleorBasedOnStyle: Mozilla) and reformat.
Exercise 6 — Static Analysis with clang-tidy
Difficulty: Rx
-
Run
clang-tidyon one of your source files:clang-tidy stats.c -- -Wall -Wextra -
If any warnings are reported, fix them.
-
Try running it on
ugly.c(before and after formatting) —clang-tidymay report different issues thanclang-format. -
Add Makefile targets for both tools:
format: clang-format -i $(SRCS) lint: clang-tidy $(SRCS) -- $(CFLAGS) .PHONY: format lint
Combining tools
In real projects, clang-format handles style (whitespace, braces) while clang-tidy catches bugs and bad practices (unused variables, unsafe functions, etc.). They complement each other.
Exercise 7 — Runtime Bug Detection with AddressSanitizer
Difficulty: Hard- Save the following program as
buggy.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
// Bug 1: heap buffer overflow
char *buf = malloc(8);
strcpy(buf, "This string is way too long for the buffer");
printf("%s\n", buf);
// Bug 2: use after free
free(buf);
printf("After free: %c\n", buf[0]);
return 0;
}
-
Compile with AddressSanitizer enabled:
gcc -Wall -Wextra -g -fsanitize=address -o buggy buggy.c -
Run the program:
./buggyAddressSanitizer will report the first memory error it detects and abort. Read the output carefully — it tells you the exact file and line number.
-
Fix both bugs:
- Allocate enough memory for the string (don’t forget the null terminator)
- Remove the use-after-free
-
Recompile with
-fsanitize=addressand verify the program runs cleanly.
AddressSanitizer in practice
ASan adds ~2x memory overhead and slows execution. Use it during development and testing, not in production builds. This is why we defined a separate debug target in TP1.
Exercise 8 — Memory Leak Detection with Valgrind
Difficulty: HardAddressSanitizer detects invalid memory accesses. Valgrind (specifically its memcheck tool) goes further: it also reports memory leaks — allocations that were never freed.
- Save the following program as
leaky.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *duplicate(const char *s) {
char *copy = malloc(strlen(s) + 1);
if (copy) strcpy(copy, s);
return copy;
}
int main(void) {
char *a = duplicate("hello");
char *b = duplicate("world");
printf("%s %s\n", a, b);
free(a);
// Bug: b is never freed
return 0;
}
-
Compile with debug symbols (no sanitizers):
gcc -Wall -Wextra -g -o leaky leaky.c -
Run under Valgrind:
valgrind --leak-check=full --track-origins=yes ./leaky -
Read the Valgrind report:
- Look for
LEAK SUMMARY— it distinguishes between definitely lost, indirectly lost, and still reachable memory. - The stack trace points to where the leaked memory was allocated.
- Look for
-
Fix the leak and re-run Valgrind. The report should end with
All heap blocks were freed -- no leaks are possible. -
Now try running
leaky(the unfixed version) under ASan:gcc -Wall -Wextra -g -fsanitize=address -o leaky_asan leaky.c ./leaky_asanDoes ASan report the leak? Compare how the two tools handle this case.
Questions
- Why doesn’t AddressSanitizer always report memory leaks, while Valgrind does?
- What does
--track-origins=yesadd to the Valgrind report? - When would you prefer Valgrind over ASan, and vice versa?
Summary
| Topic | Key commands |
|---|---|
| Git branching | git checkout -b, git merge, git log --graph |
| Stash | git stash push -m "msg", git stash pop, git stash list |
| Rebase | git rebase <branch> |
| Merge conflicts | Edit markers <<</===/>>>, then git add + git commit |
| Remote workflow | git push -u, Merge Request on GitLab |
| Formatting | clang-format -i file.c |
| Static analysis | clang-tidy file.c -- -Wall |
| Memory errors | gcc -fsanitize=address |
| Memory leaks | valgrind --leak-check=full ./prog |