forked from b-rodrigues/rap4all
-
Notifications
You must be signed in to change notification settings - Fork 0
/
github.qmd
1237 lines (1018 loc) · 40.9 KB
/
github.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Collaborating using Trunk-based development {#sec-chapter-github}
As already mentioned several times, there are two ways of collaborating with Git
(and Github): either as a team, or as an external dev (external, as in,
not part of the development team of a given project). External contributors can
only contribute code to public repositories, and the project owners can either
accept or refuse the patches.
We are going to learn about these two ways of collaborating. Let’s first focus
on collaboration within a team.
## Collaborating as a team
### TBD basics
Remember the issue we opened and assigned to Bruno? Bruno will now take care of
this issue by adding a Readme file. This will also be the opportunity to
introduce trunk-based development. The idea of trunk-based development is
simple; team members should work on separate branches to add features or fix
bugs, and then merge their branch to the "trunk" (in our case the *master*
branch) to add their changes back to the main codebase. And this process should
happen quickly, ideally every day, or as soon as some code is ready. When a lot
of work accumulates in a branch for several days or weeks, merging it back to
the master branch can be very painful. So by working in short-lived branches, if
conflicts arise, they can be dealt with quickly. This also makes code review
much easier, because the reviewer only needs to review little bits of code at a
time. If instead long-lived branches with a lot of code changes get merged,
reviewing all the changes and solving the conflicts that could arise would be a
lot of work. To avoid this, it is best to merge every day or each time a piece
of code is added, and, **very importantly**, this code does not break the whole
project (we will be using unit tests for this later).
So in summary: to avoid a lot of pain by merging branches that moved away too
much from the trunk, we will create branches, add our code, and merge them to
the trunk as soon as possible. *As soon as possible* can mean several things,
but usually this means *as soon as a feature was added*, *a bug was fixed*, or
*as soon as we added some code that does not break the whole project, even if
the feature we wanted to add is not done yet*. The philosophy is that if merging
fails, it should fail as early as possible. Early failures are easy to deal
with.
Our aim should be to provide a functioning project to anyone cloning the master
branch anytime (but still offer a simple way to install a point release of the
project).
So, back to our issue. First, Bruno needs to clone the repository:
::: {.content-hidden when-format="pdf"}
```bash
bruno@computer ➤ git clone [email protected]:rap4all/housing.git
```
:::
::: {.content-visible when-format="pdf"}
```bash
bruno@computer $ git clone [email protected]:rap4all/housing.git
```
:::
To add the feature, Bruno will now create a new branch by using the `git
checkout` command with the `-b` flag:
::: {.content-hidden when-format="pdf"}
```bash
bruno@computer ➤ git checkout -b "add_readme"
```
:::
::: {.content-visible when-format="pdf"}
```bash
bruno@computer $ git checkout -b "add_readme"
```
:::
The project automatically switches to the new branch:
```bash
Switched to a new branch 'add_readme'
```
We can also run `git status` to double-check:
::: {.content-hidden when-format="pdf"}
```bash
bruno@computer ➤ git status
```
:::
::: {.content-visible when-format="pdf"}
```bash
bruno@computer $ git status
```
:::
```bash
On branch add_readme
nothing to commit, working tree clean
```
Bruno adds a file called `README.md` and adds the following text to it:
````
# Housing data for Luxembourg
These scripts for the R programming language download nominal
housing prices from the *Observatoire de l'Habitat* and
tidy them up into a flat data frame.
- save_data.R: downloads, cleans, and creates data frames from the data
- analysis.R: creates plots of the data
````
Let's save this and run `git status` to see what happened:
::: {.content-hidden when-format="pdf"}
```bash
bruno@computer ➤ git status
```
:::
::: {.content-visible when-format="pdf"}
```bash
bruno@computer $ git status
```
:::
Git tells Bruno that the `README.md` file is not being tracked:
```bash
On branch add_readme
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
nothing added to commit but untracked files present (use "git add" to track)
```
So next Bruno is going to track it and push the changes. Also, Bruno is going to
use a neat trick when pushing: because Bruno is working on fixing an issue, it
would be great if he could close it as he pushes the fix. This is possible by
referencing the issue number in the commit message:
::: {.content-hidden when-format="pdf"}
```bash
bruno@computer ➤ git add .
bruno@computer ➤ git commit -m "fixed #1"
```
:::
::: {.content-visible when-format="pdf"}
```bash
bruno@computer $ git add .
bruno@computer $ git commit -m "fixed #1"
```
:::
`#1` refers to the number of the issue (it's the first issue that was opened
in the repository). So by referencing this issue with its number in the commit
message and pushing, the issue gets automatically closed when Bruno pushes:
::: {.content-hidden when-format="pdf"}
```bash
bruno@computer ➤ git push origin add_readme
```
:::
::: {.content-visible when-format="pdf"}
```bash
bruno@computer $ git push origin add_readme
```
:::
As you can see from the command above, Bruno pushes to "add_readme", the branch
he opened to solve the issue, not "master". If he tried to push to "master" a
message saying that "master" is up-to-date would get printed. Let's see the
output of pushing to "add_readme":
::: {.content-visible when-format="pdf"}
```bash
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 501 bytes | 501.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a pull request for 'add_readme' on GitHub by visiting:
remote: https://github.com/rap4all/housing/ pull/new/add_readme
remote:
To github.com:rap4all/housing.git
* [new branch] add_readme -> add_readme
```
:::
::: {.content-hidden when-format="pdf"}
```bash
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 501 bytes | 501.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a pull request for 'add_readme' on GitHub by visiting:
remote: https://github.com/rap4all/housing/pull/new/add_readme
remote:
To github.com:rap4all/housing.git
* [new branch] add_readme -> add_readme
```
:::
Git tells us that Bruno now needs to create a pull request. What is that? Well,
if we want to merge our branch back into the trunk, we need to do so by using a
pull request. Let's see what Bruno sees on Github:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_add_readme_recent_push.png"
alt="Bruno sees that the 'add_readme' branch has been recently updated."></img>
<figcaption>Bruno sees that the 'add_readme' branch has been recently updated.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "Bruno sees that the 'add_readme' branch has been recently updated."
knitr::include_graphics("images/github_add_readme_recent_push.png")
```
:::
Bruno can now decide to continue working on this branch, or, since the purpose
of this branch was only to add the Readme file, decide instead to do a pull
request.
By clicking on the "Compare & pull request" button Bruno now sees this:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_pull_request.png"
alt="This screen makes it easy to see what changed."></img>
<figcaption>This screen makes it easy to see what changed.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.height="400px"}
#| fig-cap: "This screen makes it easy to see what changed."
knitr::include_graphics("images/github_pull_request.png")
```
:::
Bruno can leave a comment, and see what changed (in this case, a single file was
added) and most importantly, add a reviewer if needed:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_add_reviewer.png"
alt="Let boss decide if this is good enough."></img>
<figcaption>Let boss decide if this is good enough.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.height="150px"}
#| fig-cap: "Let boss decide if this is good enough."
knitr::include_graphics("images/github_add_reviewer.png")
```
:::
This is what Bruno sees now:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_reviewer_requested.png"
alt="Github tells us that this branch can safely be merged."></img>
<figcaption>Github tells us that this branch can safely be merged.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "Github tells us that this branch can safely be merged."
knitr::include_graphics("images/github_reviewer_requested.png")
```
:::
Bruno requested the review, but Github tells us that the branch can safely be
merged. This is because we added a file and did not touch anything else, and no
one else worked on the project while Bruno was working. So there are no risks
of conflicts arising.
Let's see what the owner now sees. The project owner should have gotten a
notification to review the pull request:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_reviewer_notified.png"
alt="The owner was notified to review the pull request."></img>
<figcaption>The owner was notified to review the pull request.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "The owner was notified to review the pull request."
knitr::include_graphics("images/github_reviewer_notified.png")
```
:::
By clicking on the notification, the owner gets taken to this view:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_review.png"
alt="Time to review the pull request."></img>
<figcaption>Time to review the pull request.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F}
#| fig-cap: "Time to review the pull request."
knitr::include_graphics("images/github_review.png")
```
:::
Here, the reviewer can check the commit, the files that were changed, and see if
there are any conflicts between this code and the code base on the master (or
trunk) branch. Github also tells us two interesting things: the owner can add a
rule that states that any pull request must be approved, and also that
continuous integration has not been set up (we are going to see what this means
in the second part of this book).
Let's go ahead and add a rule forcing each pull request to be approved. By
clicking on "Add rule", the following screen appears:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_rules.png"
alt="Choose how to protect the master branch."></img>
<figcaption>Choose how to protect the master branch.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "Choose how to protect the master branch."
knitr::include_graphics("images/github_rules.png")
```
:::
By clicking the first option, more sub-options appear:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_rules_2.png"
alt="Reviews are now required."></img>
<figcaption>Reviews are now required.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "Reviews are now required."
knitr::include_graphics("images/github_rules_2.png")
```
:::
By choosing these options, the owner can basically enforce trunk-based
development (well, collaborators still have to submit pull requests frequently
enough though, because if they don't, we can be in a situation where merging can
be very difficult).
Let's choose one last option: by scrolling down, it's possible to select the
option "Do not allow bypassing the above settings". This makes sure that even
administrations (the owners of the project) must abide by the same rules.
Let's go back to the pull request. We can see now that a review is required:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_review_required.png"
alt="Time to review."></img>
<figcaption>Time to review.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.height="400px"}
#| fig-cap: "Time to review."
knitr::include_graphics("images/github_review_required.png")
```
:::
So now the owner actually has to go and see the files that were changed:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_add_your_review.png"
alt="Check the code and add comments if needed."></img>
<figcaption>Check the code and add comments if needed.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "Check the code and add comments if needed."
knitr::include_graphics("images/github_add_your_review.png")
```
:::
It's possible to add comments to single lines if needed:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_review_comments.png"
alt="It's possible to add comments to lines."></img>
<figcaption>It's possible to add comments to lines.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "It's possible to add comments to lines."
knitr::include_graphics("images/github_review_comments.png")
```
:::
By clicking on the plus sign, a box appears and it's possible to leave a
comment. In this case, everything is fine, so the owner is going to click on the
"Viewed" button:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_file_viewed.png"
alt="Good job!"></img>
<figcaption>Good job!</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "Good job!"
knitr::include_graphics("images/github_file_viewed.png")
```
:::
Then, by clicking on "Review changes", it's possible to either add a general
comment, approve the pull request, or request changes that must be addressed
before merging. Let's go ahead and approve:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_approve.png"
alt="Nothing to complain about."></img>
<figcaption>Nothing to complain about.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "Nothing to complain about."
knitr::include_graphics("images/github_approve.png")
```
:::
By submitting the review, the reviewer is taken back to the issue:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_changes_approved.png"
alt="We're done, we can merge the pull request."></img>
<figcaption>We're done, we can merge the pull request.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "We're done, we can merge the pull request."
knitr::include_graphics("images/github_changes_approved.png")
```
:::
The reviewer can now merge the pull request by clicking on the "Merge pull
request" button. Github even suggests we delete the branch, which has served its
purpose:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_pull_request_done.png"
alt="Let's get rid of this branch."></img>
<figcaption>Let's get rid of this branch.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "Let's get rid of this branch."
knitr::include_graphics("images/github_pull_request_done.png")
```
:::
Let's delete it (it's always possible to restore it).
### Handling conflicts
As mentioned in the previous chapter, Git makes it easy to handle conflicts.
Well, let's be clear; even with Git, it can sometimes be very tricky to resolve
conflicts. But you should know that when solving a conflict with Git is
difficult, this usually means that it would be impossible to do any other way,
and would inevitably result in someone having to reconcile the files by hand.
What makes handling conflicts easier with Git though, is that Git is able to
tell you where you can find clashes on a per-line basis. So for instance, if you
change the first ten lines of a script, and I change the next ten lines, there
would be no conflict, and Git will automatically merge both our contributions
into a single file. Other tools, like Dropbox, would fail in a situation like
this, because these tools can only handle conflicts on a per-file basis. The
same file was changed by two different persons? Regardless of where these
changes happened, you now have a conflict to deal with on your hands... and
worse, you don't even know where the conflicts are in the file! You will need to
scan the two resulting copies of the file by hand. Git, in the case where the
same lines were changed, highlights them very clearly so that you can quickly
find them and deal with the problems.
We will see all of this in the coming sections.
So how do conflicts happen? Let's imagine the following scenario. Both Bruno and
the project owner create branches, and edit the same file. Perhaps they
talked over the phone and decided to add a feature or correct a bug. Perhaps
they decided that it wasn't worth opening an issue on Github and assign someone
to do it. After all, they discussed this on the phone and decided that Bruno
should do it. Or was it the owner who needed to solve the issue? No one
remembers now. Either way, they both did, and changed the same file, so a
conflict will ensue.
First, Bruno needs to switch back to the master branch on his computer:
::: {.content-hidden when-format="pdf"}
```bash
bruno@computer ➤ git checkout master
```
:::
::: {.content-visible when-format="pdf"}
```bash
bruno@computer $ git checkout master
```
:::
```bash
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
```
Git tells us to update the code on our computer by running `git pull`. We use
`git push` to upload code to Github, and use `git pull` to download code from
Github. Let's run it and see what happens:
::: {.content-hidden when-format="pdf"}
```bash
bruno@computer ➤ git pull
```
:::
::: {.content-visible when-format="pdf"}
```bash
bruno@computer $ git pull
```
:::
```bash
Updating b7f82ee..c774ebf
Fast-forward
README.md | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 README.md
```
Files on Bruno's computer have been updated. The owner of the project (called
`owner`, remember?) can do the same and will see the same. Now, Bruno creates a
new branch to work on the new feature:
::: {.content-hidden when-format="pdf"}
```bash
bruno@computer ➤ git checkout -b add_cool_feature
```
:::
::: {.content-visible when-format="pdf"}
```bash
bruno@computer $ git checkout -b add_cool_feature
```
:::
And the project owner also creates a new branch:
::: {.content-hidden when-format="pdf"}
```bash
owner@localhost ➤ git checkout -b add_nice_feature
```
:::
::: {.content-visible when-format="pdf"}
```bash
owner@localhost $ git checkout -b add_nice_feature
```
:::
They now edit the same file, `analysis.R`. Bruno added this function:
```{r, eval = F}
make_plot <- function(country_level_data,
commune_level_data,
commune){
filtered_data <- commune_level_data %>%
filter(locality == commune)
data_to_plot <- bind_rows(
country_level_data,
filtered_data
)
ggplot(data_to_plot) +
geom_line(aes(y = pl_m2,
x = year,
group = locality,
colour = locality))
}
```
This way, Bruno could delete the repeating code and create plots like this:
```{r, eval = F}
lux_plot <- make_plot(country_level_data,
commune_level_data,
communes[1])
# Esch sur Alzette
esch_plot <- make_plot(country_level_data,
commune_level_data,
communes[2])
# and so on...
```
The end effect is the same, but by using this function, the code is now shorter,
and clearer. Also, if someone wants to change, say, the theme of the plot, now
this only needs to be changed in one place and not for each commune. Now, what
did the owner change? The owner started by removing the line that loaded the
`{purrr}` package, as no function from the package was used in the script, and
then also changed every `%>%` to `|>`. It seems that much more than just who
would make the changes got lost in translation... Anyways, both now push their
changes to their respective branches. This is Bruno:
::: {.content-hidden when-format="pdf"}
```bash
bruno@computer ➤ git add .
bruno@computer ➤ git commit -m "make_plot() for plotting"
bruno@computer ➤ git push origin add_cool_feature
```
:::
::: {.content-visible when-format="pdf"}
```bash
bruno@computer $ git add .
bruno@computer $ git commit -m "make_plot() for plotting"
bruno@computer $ git push origin add_cool_feature
```
:::
::: {.content-hidden when-format="pdf"}
```bash
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 647 bytes | 647.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote:
remote: Create a pull request for 'add_cool_feature' on GitHub by visiting:
remote: https://github.com/rap4all/housing/pull/new/add_cool_feature
remote:
To github.com:rap4all/housing.git
* [new branch] add_cool_feature -> add_cool_feature
```
:::
::: {.content-visible when-format="pdf"}
```bash
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 647 bytes | 647.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote:
remote: Create a pull request for 'add_cool_feature' on GitHub by visiting:
remote: https://github.com/rap4all/housing/pull/ new/add_cool_feature
remote:
To github.com:rap4all/housing.git
* [new branch] add_cool_feature -> add_cool_feature
```
:::
and this is the owner:
::: {.content-hidden when-format="pdf"}
```bash
owner@localhost ➤ git add .
owner@localhost ➤ git commit -m "cleanup"
owner@localhost ➤ git push origin add_sweet_feature
```
:::
::: {.content-visible when-format="pdf"}
```bash
owner@localhost $ git add .
owner@localhost $ git commit -m "cleanup"
owner@localhost $ git push origin add_sweet_feature
```
:::
::: {.content-hidden when-format="pdf"}
```bash
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 449 bytes | 449.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote:
remote: Create a pull request for 'add_sweet_feature' on GitHub by visiting:
remote: https://github.com/rap4all/housing/pull/new/add_sweet_feature
remote:
To github.com:rap4all/housing.git
* [new branch] add_sweet_feature -> add_sweet_feature
```
:::
::: {.content-visible when-format="pdf"}
```bash
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 449 bytes | 449.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote:
remote: Create a pull request for 'add_sweet_feature' on GitHub by visiting:
remote: https://github.com/rap4all/housing/pull/ new/add_sweet_feature
remote:
To github.com:rap4all/housing.git
* [new branch] add_sweet_feature -> add_sweet_feature
```
:::
So, let's think about what just happened: two developers changed the same file,
`analysis.R`, in two separate branches. These two branches need to be merged
back to the trunk.
So Bruno does a pull request:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_new_pull_request.png"
alt="Bruno opens a pull request after finishing his changes."></img>
<figcaption>Bruno opens a pull request after finishing his changes.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "Bruno opens a pull request after finishing his changes."
knitr::include_graphics("images/github_new_pull_request.png")
```
:::
First, Bruno selects the feature branch (1), then clicks on "Contribute" (2) and
then "Open pull request" (3). Bruno gets taken to this screen:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_new_pull_request_2.png"
alt="No conflicts, for now..."></img>
<figcaption>No conflicts, for now...</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "No conflicts, for now..."
knitr::include_graphics("images/github_new_pull_request_2.png")
```
:::
Now Bruno can click on "Create pull request", but remember, because reviews are
required, automatic merging is disabled.
If now we go see what happens from the project owner's side of things, first of
all, there's now a notification for a pending review:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_new_pull_request_review_pending.png"
alt="New review pending."></img>
<figcaption>New review pending.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "New review pending."
knitr::include_graphics("images/github_new_pull_request_review_pending.png")
```
:::
By clicking on it, the project owner can review the pull request and decide what
to do with it. So at this point, the owner did not open a pull request for the
feature he or she worked on yet. And maybe that's a good thing, because now the
project owner can see that the changes that Bruno made on the file will conflict
with the project owner's changes.
So how to move forward? Simple: the project owner can decide to approve the pull
request, which will merge Bruno's changes into the master branch (or the trunk).
Then, instead of opening a pull request for merging his or her changes into
trunk, which will cause a conflict, the project owner can instead merge the
changes from the trunk into his or her feature branch. This will also create a
conflict, but now the project owner can easily deal with it on his or her
machine, and then push a new commit with both changes integrated gracefully. The
image below illustrates this workflow:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_conflict_tbd.png"
alt="Conflict solving with trunk-based development."></img>
<figcaption>Conflict solving with trunk-based development.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "Conflict solving with trunk-based development."
knitr::include_graphics("images/github_conflict_tbd.png")
```
:::
First step, the owner reviews and approves Bruno's pull request:
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_pull_request_approved.png"
alt="First, let's approve the changes."></img>
<figcaption>First, let's approve the changes.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "First, let's approve the changes."
knitr::include_graphics("images/github_pull_request_approved.png")
```
:::
The pull request can get merged and Bruno's feature branch deleted. Now, it
wouldn't make sense for the project owner to create a pull request to merge his
or her changes. They would conflict with what Bruno did. So the project owner
goes back to his or her computer and essentially updates the code in his or her
feature branch by merging master into it.
So, the project owner checks that he or she is working on the feature branch:
::: {.content-hidden when-format="pdf"}
```bash
owner@localhost ➤ git status
```
:::
::: {.content-visible when-format="pdf"}
```bash
owner@localhost $ git status
```
:::
```bash
On branch add_sweet_feature
nothing to commit, working tree clean
```
Ok, so now let's get the updated code from master, by pulling from master:
::: {.content-hidden when-format="pdf"}
```bash
owner@localhost ➤ git pull origin master
```
:::
::: {.content-visible when-format="pdf"}
```bash
owner@localhost $ git pull origin master
```
:::
The owner now sees this:
```bash
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (4/4), 1.23 KiB | 418.00 KiB/s, done.
From github.com:rap4all/housing
* branch master -> FETCH_HEAD
c774ebf..a43c68f master -> origin/master
Auto-merging analysis.R
CONFLICT (content): Merge conflict in analysis.R
Automatic merge failed; fix conflicts and then commit the result.
```
Git detects that there are some conflicts and tells the owner to fix them, and then
commit the results. So let's open `analysis.R` and see how it looks (you can
view the file online on this
[link](https://gist.github.com/b-rodrigues/f713702268c99328ad16af56f7d32892)^[https://is.gd/ktWtjr].
First of all, you will see Git deals with conflicts on a per-line basis. So each
line that the owner changed that does not conflict with Bruno's change gets
immediately updated to reflect the owner's changes. For example, remember that
the owner removed the line that loaded the `{purrr}` package? This line was also
removed by pulling the changes from master into the feature branch. Also, you
should notice that every `%>%` was changed into `|>` as well. These two changes
happened without any issues.
Then, you should understand what happens when a conflict gets detected on some
lines. For example, this is the first conflict you should see:
```r
<<<<<<< HEAD
filtered_data <- commune_level_data |>
filter(locality == communes[1])
=======
filtered_data <- commune_level_data %>%
filter(locality == commune)
>>>>>>> a43c68f5596563ffca33b3729451bffc762782c3
```
We see how the lines look on the owner's computer and how they look in the
master branch (or trunk). The lines between `<<<<<<< HEAD` and `=======` are the
lines in the owner's feature branch. The lines between `=======` and `>>>>>>>
a43c68f5596563ffca33b3729451bffc762782c3` are how they look in the master branch
(or trunk). This very long chain of characters that starts with `a43c68f` is the
hash of the commit from which these lines come from.
So this makes things quite easy; one simply needs to remove the outdated code,
and then commit and push the fixed file! The project owner only needs to remove
`<<<<<<< HEAD` and `=======` and what's between these lines, as well as the
lines that show the hash commit. The project owner can now commit and push the
changes, open a pull request, ask Bruno to review the changes one last time and
merge everything back to master.
::: {.content-hidden when-format="pdf"}
<figure>
<img src="images/github_conflict_solved.png"
alt="The conflict has been gracefully solved."></img>
<figcaption>The conflict has been gracefully solved.</figcaption>
</figure>
:::
::: {.content-visible when-format="pdf"}
```{r, echo = F, out.width="300px"}
#| fig-cap: "The conflict has been gracefully solved."
knitr::include_graphics("images/github_conflict_solved.png")
```
:::
In (1) we see the commit that deals with the conflict, in (2) the owner asks Bruno
for a review and then in (3) we see that Bruno reviewed and approved. Finally, the
pull request can be merged (4) and the feature branch deleted.
### Make sure you blame the right person
If many people contribute to a single project, it might sometimes be difficult
to know who changed what and when exactly. This is where the `git blame` command
is useful. If you want to know who changed the file called `analysis.R` for
example, simply run: