usethis::use_git_ignore(c("*.html", "*_files/"))Exemplary solution: your Report 1, on GitHub
Session 4 — Applied Data Science 2026
There is no single right answer here the way there is for a data exercise — your repository is yours. What follows is the standard the rest of the course expects, plus worked answers to every one of the optional tasks.
The commands are real and the output is real, copied from a working repository. The clicking is described rather than shown, because a screenshot of the Git tab goes out of date faster than the words do. If a button is not where this says it is, look for the word rather than the position.
0.1 Check your setup
usethis::git_sitrep() prints a long report. The three lines that matter:
── Git global (user)
• Name: "Anna Musterfrau"
• Email: "anna.musterfrau@stud.uni-flensburg.de"
── GitHub user
• Personal access token for "https://github.com": <discovered>
• GitHub user: "amusterfrau"
<discovered> is the word you want. Anything else — a warning, an absence — means the token is not stored and nothing that follows will work.
GitHub links a commit to your account by matching the commit’s email against the addresses registered on your account. Use a different one and your commits show up as an unknown author — harmless for the work, mildly confusing for a marker looking at who wrote what.
0.2 What stays out
A typical Report 1 folder, marked up:
| File or folder | In or out | Why |
|---|---|---|
report1.qmd |
in | You wrote it. Nothing can recreate it. |
data/hotels-barcelona.csv |
in | Input, from outside, read-only |
report1_Musterfrau_Anna.Rproj |
in | Defines the project |
README.md |
in | You wrote it |
.gitignore |
in | It is a decision, and it should travel |
report1.html |
out | The render makes it |
report1_files/ |
out | The render makes it |
.Rhistory, .RData |
out | Session leftovers, nobody’s business |
.Rproj.user/ |
out | RStudio’s private bookkeeping |
.DS_Store |
out | macOS folder clutter |
The command:
✔ Adding "*.html" and "*_files/" to '.gitignore'.
The other five lines arrive on their own when you run use_git() in Section 0.3 — it appends to the file, so nothing you wrote is overwritten.
.gitignore itself is committed
It is tempting to think of it as a local preference. It is not: it is part of the project’s definition, and a collaborator who does not have it will commit the rubbish you carefully excluded. It goes in.
*.html and not report1.html
Because next month the file will be called something else and you will not think to update the rule. *.html is a statement about kinds of file — “rendered output does not belong here” — and it keeps being true. The same reasoning gives *_files/ rather than report1_files/.
There is one case where this bites: if your project legitimately contains an HTML file you wrote by hand, *.html will hide it. Nothing in this course does.
0.3 History and remote
usethis::use_git()✔ Initialising Git repo at '.../report1_Musterfrau_Anna'.
✔ Adding ".Rproj.user", ".Rhistory", ".RData", ".httr-oauth",
".DS_Store", and ".quarto" to '.gitignore'.
Then the dialogue asks what to commit. The correct list is exactly this:
.gitignore
data/hotels-barcelona.csv
report1.qmd
report1_Musterfrau_Anna.Rproj
Four items. If report1.html is on your list, use_git_ignore() did not run, or did not run first. Cancel, fix it, and start again — that is much easier now than after the commit exists.
usethis::use_github(private = TRUE)The browser opens on the repository — private, in your own account. It holds your four files and one commit.
use_github() refuses because the name is taken
You already have a repository of that name in your account — usually from an earlier attempt. Two ways out, and neither loses anything:
- Delete the old one on GitHub (Settings → bottom of the page), then rerun
use_github(); or - Rename your project folder, reopen the
.Rproj, and run it again — the repository is named after the folder.
Your commits live on your machine throughout, so the worst case costs a minute.
Everything in this session lives in your account, which is the normal case and stays yours after the semester. Reports 2 and 3 are handed in differently, and those instructions come with the report itself — there is nothing to arrange here.
0.4 Three commits
The point of the task is the messages, so here is a history that would pass and one that would not.
Passes:
5a1c9e2 Add a note on the November-only limitation
9f3b012 Widen the central band to include 1 km exactly
c74d8aa Give the price table a caption
e0b2f41 Initial commit: report, data and project file
Does not pass:
77aa019 changes
2b91ee0 changes 2
41cc8de final
e0b2f41 Initial commit
The test from the slides: “if you apply this commit, it will …”. “…widen the central band to include 1 km exactly” finishes the sentence. “…changes 2” does not finish anything.
Because a commit is the unit you can inspect and undo. Three changes in one commit means that when one of them turns out to be wrong, the diff you are reading contains two innocent changes as well.
The practical version of this rule: commit when you have finished a thing, not when you have finished working.
0.4.1 Reading the diff
The habit the task is really training. A change to a filter looks like this:
hotels <- read_csv(here("data", "hotels-barcelona.csv"))
-central <- hotels |> filter(distance < 1)
+central <- hotels |> filter(distance <= 1)
outer <- hotels |> filter(distance >= 1)Two things to notice. The - line is gone and the + line replaced it — Git shows a changed line as a deletion plus an addition, not as an edit. And the unmarked lines above and below are context: they are unchanged, and they are there so you can see where in the file you are.
0.5 The README
A model answer. This is genuinely all that is needed:
# Report 1 — Pricing the Barcelona hotel market
Applied Data Science, Fall 2026, Europa-Universität Flensburg.
Anna Musterfrau.
## The question
Does the location premium found in Vienna also hold in Barcelona?
## How to reproduce it
1. Open `report1_Musterfrau_Anna.Rproj` in RStudio
2. Restart R
3. Render `report1.qmd`
Needs `tidyverse`, `flextable` and `here`.
## Data
`data/hotels-barcelona.csv` — Békés & Kézdi (2021), *Data Analysis for
Business, Economics and Policy*. Licensed CC BY-NC-SA 4.0. Unmodified.
One row is one hotel, priced for one night in November 2017.The four required things are all there: what it is, what the data is and where it came from, how to reproduce it, who wrote it.
“Restart R” in the reproduce instructions, because a document that only renders in your warmed-up session is not reproducible and you will not notice until someone else tries.
“One row is one hotel” in the data section. Everything else about a dataset can be worked out from the file; what a row represents usually cannot, and it is the first thing a reader needs.
GitHub renders README.md as Markdown — the same Markdown as session 3, # for headings, * for lists, backticks for code. The one difference worth knowing: GitHub does not run R. A README is written, not computed, so this is the one document in the course where typing a number is acceptable. Better still, do not put numbers in it — put them in the report, where they are computed.
0.6 Breaking something and getting it back
Delete a chunk, save, and the Git tab shows M report1.qmd. Open the Diff and the deleted chunk appears as a block of red - lines — which is the point of doing it this way round: you are looking at proof of what you destroyed before you decide.
Revert discards the change and the chunk is back.
0.6.1 The second run — the answer to the question in the sheet
Did Revert undo only step 5, or also the staged sentence from step 4?
Only step 5. The staged sentence survives, and you land on exactly the state you staged. That is the whole finding, and it is not the obvious guess — most people assume Revert rewinds to the last commit.
| What you had done | Revert returns you to |
|---|---|
| staged an intermediate version | that staged version |
| staged nothing since the last commit | the last commit |
So staging does two jobs, not one. It chooses what goes into the next commit, and it moves your safety net forward. The second job is the one that changes how you work day to day.
It discards uncommitted changes to the selected file. It cannot touch anything that is already committed, and it does not ask twice. Read the diff, then click it.
Worth knowing precisely, because the obvious guess is wrong:
- If you staged an intermediate version, Revert gives you that
- If you staged nothing since your last commit, it gives you the commit
So staging is not only about choosing what goes into a commit. It is a cheap checkpoint: every time a piece of your work starts behaving, tick the box, and the worst a later mistake can cost you is the minutes since then.
This is why the recommended rhythm is write → stage when it works → commit when something is achieved → push when you stand up, rather than one commit at the end of the evening.
You deleted the chunk, committed, and pushed. Now Revert is useless — there is nothing uncommitted to discard.
The route that works, without a command line:
- Your repository on GitHub → click
report1.qmd→ History - Click the commit before the deletion
- You are looking at the old file. Copy the chunk out of it
- Paste it back in RStudio, commit with a message that says what you restored
It is inelegant and it always works. And it is rare, if you commit often enough that the mistake is usually still uncommitted — which is the actual argument for committing often.
1 Answers to “if you finish early”
1.1 (a) Reading somebody else’s repository
The exercise asks three questions of a stranger’s repository, and the honest finding is usually that one of them cannot be answered.
- What is this project? — almost always answerable, from the README’s first line or, failing that, the repository name.
- Where did the data come from? — the one most often missing. A
data/folder with a CSV in it says nothing about provenance, and the licence obligation that comes with the Békés–Kézdi data is not discharged by a filename. - How would you reproduce it? — usually guessable, rarely stated. “Open the
.Rproj, restart R, render” removes the guessing.
You cannot judge your own README, because you already know all four answers. The only test that works is somebody else reading it. That is why this task is in the sheet and not just in the checklist.
1.2 (b) Finding a change in the history
Two routes to the same information.
On GitHub: open the file, click History, and you get the list of commits that touched this file — not the whole project, which is the useful part. Click one and you see its diff.
In RStudio: the Git tab’s clock icon opens the History window. The commit list is at the top, the diff of the selected commit below it, and the dropdown at the top left filters to a single file.
Same commits, same diffs, same seven-character identifiers like 9f3b012. That identifier is worth recognising: it is how a commit is named everywhere, in both tools, and in the error message in Section 1.5.
Project history answers “what happened on Tuesday”. File history answers “when did this number change”, which is the question you actually have when a result moves and you do not know why.
1.3 (c) Proving it reproduces somewhere else
Clone the repository into a new place: File → New Project… → Version Control → Git, paste the URL from the green <> Code button (HTTPS), choose the Desktop as the parent folder, Create Project. The console route does the same:
usethis::create_from_github(
"amusterfrau/report1_Musterfrau_Anna", # your username, your repo
destdir = "~/Desktop")Either way you get a fresh copy from GitHub as an RStudio project, opened in a new window. It is a genuinely independent copy: nothing in your working folder is consulted, so anything the render needs and the repository lacks will fail here.
Then: restart R, render. Three things commonly go wrong, and each is worth recognising.
| What fails | What it means |
|---|---|
cannot open file 'data/...' |
The data was never committed — check .gitignore |
there is no package called 'x' |
Fine. A package is not part of your project |
object 'y' not found |
The chunk depended on your console, not on the document |
The first is a real defect and the reason this task exists: a repository that does not carry its own data is not a submission, it is a promise.
The third is session 3’s lesson arriving again, from a new direction. The render does not see your environment — and now neither does the folder.
Not “does it render on your laptop”. Does it render on a clean clone. You can check that yourself in two minutes, every time, by cloning as above.
It is a throwaway. Keeping it invites the confusion of two working copies of the same project, which is a real way to lose an afternoon’s work — you commit in one, carry on in the other, and wonder where your changes went.
If you do want two copies deliberately, that is Section 1.5, and the rule there is Pull first.
1.4 (d) Testing the retroactivity warning
The sequence: create scratch.R, commit it, push. Then add it to .gitignore, commit that. Then edit scratch.R and save.
The Git tab still reports it:
M .gitignore
M scratch.R
Should it? By the letter of what .gitignore says, no. By what Git actually promises, yes — and this is the distinction worth taking away:
.gitignoredecides which untracked files Git will mention. It has no authority over a file that is already in the history.
Once Git is tracking a file it keeps tracking it, because the alternative would be worse: a line in a config file could silently stop recording changes to something you care about.
Untracking a file while keeping it on disk is git rm --cached, and it is a command line operation with no button in RStudio and no wrapper in usethis. Rather than teach a terminal command for one rare case, the course puts .gitignore first, in Section 0.2, where the problem does not arise.
If you do hit it for real, bring it to a consultation hour — it is one line, and it is easier shown than written.
The tidy way out of the situation you just created: delete scratch.R and commit the deletion. The file is gone from your folder and from the current state of the repository. It remains in the history, at the commits where it existed, which is exactly what a history is for.
This mechanism is why the warning about the token in the setup tutorial is worded so strongly. A password committed once is in the history permanently, and deleting the file afterwards does not remove it — anybody who can read the repository can read the commit where it was added.
The only real fix is to treat the token as compromised and generate a new one. Do not put credentials in files.
1.5 (e) Two machines, on one machine
Using the clone from Section 1.3 as the “library computer”.
In the clone: change the title, commit, push. In the original: change the last line, commit, push. Git refuses:
! [rejected] main -> main (fetch first)
error: failed to push some refs
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
Read it as what it is: not an error in your work, but GitHub declining to discard a commit it has and you do not. Follow the hint — Pull, then Push:
Auto-merging report1.qmd
Merge made by the 'ort' strategy.
report1.qmd | 2 +-
Afterwards the title change is there, alongside your change to the last line. Both edits survived.
Why there was no conflict: the two changes were in different places — one at the top of the file, one at the bottom. Git merges line by line, so two changes that do not overlap need no decision from anybody. A conflict is not “two people edited the file”; it is “two people edited the same lines”.
Repeat the exercise, but change the same line in both copies. Then:
Auto-merging report1.qmd
CONFLICT (content): Merge conflict in report1.qmd
Automatic merge failed; fix conflicts and then commit the result.
The Git tab shows UU report1.qmd — both sides modified — and the file contains:
<<<<<<< HEAD
central <- hotels |> filter(distance < 1)
=======
central <- hotels |> filter(distance <= 1)
>>>>>>> b324c85ebbea36068215fc0b1d58876e972fea12
HEAD is this copy. Below the ======= is what came from GitHub, labelled with the commit it came from. To resolve: delete all three marker lines, leave the version you want, save, stage, commit.
Nothing clever is required and nothing is at risk — both versions are in front of you, and the history has them both if you pick wrong.
1.6 (f) Branches and pull requests
Not part of this course. No report needs it, it will not be examined, and you can close this document now with a clear conscience. It is here because it is how every professional team uses Git, and because a walkthrough is more use than a definition.
1.6.1 The problem branches solve
So far your history is a single line: each commit on top of the last, on a branch called main. That is fine alone. It stops being fine when you want to try something substantial without your half-finished version being the current one — because main is what anybody cloning the repository gets.
A branch is a second line of commits, starting from where you left main. You work on it, main stays as it was, and when the work is good you bring the two back together.
1.6.2 Making one
In RStudio’s Git tab, the purple icon at the right is New Branch. Name it after the work: add-limitations, not test or new.
add-limitations
That is the branch dropdown now, in place of main. Commit as normal — twice, three times, however many the work takes. Then Push, and RStudio pushes the branch to GitHub rather than to main.
Look at main in the meantime and your changes are simply absent:
# Report
Central hotels charge more.
That is the whole point. main is undisturbed while the work is in progress.
1.6.3 The pull request
On GitHub, a pushed branch produces a banner offering Compare & pull request. A pull request is a proposal: here are my commits, here is the diff against main, please look at it before it becomes the official version.
It is not a Git feature — it is a GitHub feature built on top of branches. What it adds is the conversation: reviewers comment on specific lines, you push more commits to the same branch and they appear in the same pull request, and the whole discussion stays attached to the change.
Clicking Merge pull request does what a local merge does:
Merge made by the 'ort' strategy.
report1.qmd | 4 ++++
1 file changed, 4 insertions(+)
Now main has the limitations section, and the pull request is a permanent record of why it was added and who agreed.
1.6.4 Why this matters even working alone
Mostly it does not, and that is why the course leaves it out. One commit at a time on main is the right workflow for one person writing one report, and adding branches to it is ceremony.
Two cases where it earns its keep even solo:
- A change you might abandon. Try the restructure on a branch. If it fails, delete the branch and
mainnever knew. - A deadline you have already met. Your report renders and passes. Now you want to improve it. Do that on a branch and the version that works is never the version that is broken at 23:50.
If you take one thing from this section, take that second one.
Gräbner-Radkowitsch (2026) covers the setup. Beyond that, Happy Git and GitHub for the useR (Bryan and Hester 2024) is the standard reference for exactly this audience — R users, RStudio, no prior command line. The chapters that match this document:
- ch. 22, Branches
- ch. 28, Dealing with push rejection — the error in Section 1.5, in more detail than here
- ch. 33, Explore and extend a pull request