Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 28819023 authored by MALANDAIN Mathias's avatar MALANDAIN Mathias
Browse files

Fix sections 6 to 9: proofreading + missing paragraph on local excludes

parent c7203ea5
No related branches found
No related tags found
1 merge request!1Changes after Sébastien Gilles' review
......@@ -14,7 +14,7 @@ There is even a way of collaborating on a project in which none of the developer
# Forks and merge requests {#fork}
I was recently told by a friend of mine that, in the company he is working for, MRs for branches basically do not exist; instead, they rely on MRs for *forks*. For experienced users, there are indeed a lot of advantages to this workflow. To explain this in further detail, we first have to know what a fork is.
I was recently told by a friend of mine that, in the company he used to work for, MRs for branches basically do not exist; instead, they rely on MRs for *forks*. For experienced users, there are indeed a lot of advantages to this workflow. To explain this in further detail, we first have to know what a fork is.
## Forking {#forking}
......@@ -44,7 +44,7 @@ Instead, the button is white:
![](../assets/img/06-forks/fork-01.jpg){: .mx-auto.d-block :}
Yup. Just click on the "Forks" button, and you will have the option to fork to a project, with the name you want to give it, and the place you want to put it in (either your namespace, which is still a bad option in most cases, or a group/subgroup):
Yup. Just click on the "Forks" button, and you will have the option to fork the project. You will be asked the name you want to give your fork and where you want to put it (either your namespace, which is [still a bad option in most cases]({{'/02-creating-projects#groups' | relative_url }}), or a group/subgroup):
![](../assets/img/06-forks/fork-02.jpg){: .mx-auto.d-block :}
......@@ -66,7 +66,7 @@ Well, guess who will help you?
![](../assets/img/06-forks/fork-04.jpg){: .mx-auto.d-block :}
This appeared on the page of the fork (i.e., my "variant" of the project) as soon as I committed some stuff on it. I can totally open an MR in the source project:
This appeared on the page of my fork as soon as I committed some stuff on it. I can totally open an MR into the source project:
![](../assets/img/06-forks/fork-05.jpg){: .mx-auto.d-block :}
......@@ -74,17 +74,17 @@ I can also, at any point, open an MR from the sidebar, on the webpage of my fork
![](../assets/img/06-forks/fork-06.jpg){: .mx-auto.d-block :}
The form you have to fill to actually create the MR is basically the same, except for this new gizmo at the bottom of the page:
The form you have to fill to actually create the MR is basically [the same as with "local" MRs]({{'/04-branches-issues#mr' | relative_url }}), except for this new gizmo at the bottom of the page:
![](../assets/img/06-forks/fork-07.jpg){: .mx-auto.d-block :}
...that's two gizmos. Well. So:
1. If the original project is not a private project, that basically means that you are offering some code to a "public" codebase. It can make sense for the community that developed this codebase in the first place to have their word, and what better way of letting them contribute than directly allowing them to commit?
2. This "Contribution guidelines" stuff does not come from nowhere. Well... Turns out there is a reason why GitLab suggests you (and so did I) to create a file named `CONTRIBUTING.md` at the root of any project to which other people might contribute. This link is a pointer to the `CONTRIBUTING.md` file of the project you forked. The closer you follow these guidelines, the higher the chances of your MR being accepted.
2. This "Contribution guidelines" stuff does not come from nowhere. Well... Turns out there is a reason why GitLab suggests (and [so did I]({{'/05-good-practices#cicd' | relative_url }})) to create a file named `CONTRIBUTING.md` at the root of any project to which other people might contribute. This link is a pointer to the `CONTRIBUTING.md` file of the project you forked. The closer you follow these guidelines, the higher the chances of your MR being accepted.
{: .box-note}
On the other hand, if you want to work from a given snapshot of a project without contributing to it, you may even remove the "fork relationship", by delving into Settings Advanced -- at your own risk, though, because you will also be missing out on new features, bug fixes, etc. (Once again, you should still make sure that you are complying with the licence(s) of the original project.)
On the other hand, if you want to work from a given snapshot of a project without contributing to it, you may even remove the "fork relationship", by delving into *Settings > Advanced* -- at your own risk, though, because you will also be missing out on new features, bug fixes, etc. (Once again, you should still make sure that you are complying with the licence(s) of the original project.)
## Closing remarks {#fork-closing}
......@@ -94,19 +94,22 @@ GitLab will tell you when you are behind the forked project:
![](../assets/img/06-forks/fork-update.jpg){: .mx-auto.d-block :}
...but you have to deal with the consequences (even when you have less commits to pull than here). As a result, you will have to think about your workflow when you work on a fork. Or you may just [read GitLab's page about forkflows](https://docs.gitlab.com/ee/user/project/repository/forking_workflow.html).
...but you have to deal with the consequences (even when you have less commits to pull than here). As a result, you will have to think about your workflow when you work on a fork (more on that below).
**As a forked** (...forkee?), remember that the forks of your project depend on the project itself. For instance, this is the reminder I will get if I want to delete a project that was forked:
**As a forked** (...forkee?), remember that the forks of a project depend on the project itself. For instance, this is the reminder I will get if I want to delete a project that was forked:
![](../assets/img/06-forks/fork-08.jpg){: .mx-auto.d-block :}
This is a nice summary of why your project maybe should not be deleted: the higher the numbers, the more you should think about it. Plus, it could be considered rude.
Regarding forks, there is a simple rule to be remembered: ***If you delete a private project, its forks will be deleted too.*** If you delete a *public* project, one of its forks will be automatically picked as the new "original project" from which all other forks are forked off.
Regarding forks, there is a simple rule to be remembered:
{: .box-warning}
***If you delete a private project, its forks will be deleted too.*** If you delete a *public* project, one of its forks will be automatically picked as the new "original project" from which all other forks are forked off.
# Git workflows {#workflows}
It is now time to get back to our first concern: which workflow should we use for a project? Indeed, there exist several different workflows for Git projects, and which one you actually implement depends on the needs you have. Let us have a look at the three "main" workflows that emerged, each with its benefits and drawbacks:
It is now time to get back to our first concern: which workflow should we use for a project? Indeed, there exist several different workflows for Git projects, and which one you actually implement depends on the needs you have. Let us have a look at the three "main" workflows that emerged, each with its benefits and drawbacks.
## Trunk-based workflow {#trunk-wf}
......@@ -117,12 +120,12 @@ Up to this point, we have been using this workflow without giving it a name. The
A trunk-based workflow can be very efficient for [DevOps](https://www.atlassian.com/devops/what-is-devops), a software engineering practice in which continuous feedback is used for improving the codebase very often (pretty much on a daily basis). It is also a good fit for prototype projects with a small dedicated team of developers. However, it can hinder long-term research and development efforts; its agile characteristics do not always fit the needs of researchware. It also does not scale well: as your development team grows larger, the constant merging and conflict resolution will become more of a hindrance.
A small change to this workflow consists in updating a `develop` branch on a regular basis, while the `main` branch will only get updated once in a while, typically when everything is working ("no test should ever fail on the `main` branch"). This approach, sometimes referred to as **feature branching**, is arguably "cleaner" but suffers from the same drawbacks. However, if you push it a bit further, you can get to...
A small change to this workflow consists in updating a `develop` branch on a regular basis, while the `main` branch will only get updated once in a while, typically when everything is working ("no test should fail on the `main` branch"). This approach, sometimes referred to as **feature branching**, is arguably "cleaner" but suffers from the same drawbacks. However, if you push it a bit further, you can get to...
## Gitflow {#gitflow}
{: .box-success}
The **Gitflow** is a strict branching model centered on releases. It typically relies on two "main" branches, namely a `develop` branch and a `main` branch that only receives commits from `develop`, and adds release branches on which only release-ready versions of the codebase will be pushed, and several feature and hotfix branches with clearly-defined roles.
**Gitflow** is a strict branching model centered on releases. It typically relies on two "main" branches, namely a `develop` branch and a `main` branch that only receives commits from `develop`, and adds release branches on which only release-ready versions of the codebase will be pushed, and several feature and hotfix branches with clearly-defined roles.
Let us delve a bit further into this branching model:
......@@ -130,9 +133,9 @@ Let us delve a bit further into this branching model:
* Feature branches can only be branched off from `develop` and merge back to it. This is where features are developed, issues are handled, and so on. Nothing too fancy for the moment.
* Release branches can only be branched off from `develop`, and they have to be merged back into both `develop` and `master`.
* The basic idea is this: when the code on `develop` is "nearly ready" for a production release, let's say version `0.1` for the sake of illustration, create a branch called `release-0.1`. The code on this branch will only get updates like minor bug fixes and metadata updates. (Meanwhile, the `develop` branch is living its best life, probably incorporating some new features that will be part of version `0.2`!)
* When the release is ready, merge it into the `main` branch, where it will be given a tag (`v0.1` for example). Also merge it back to the `develop` branch, so that pre-release bugfixes get into the current working version!
* When the release is ready, merge it into the `main` branch, where it will be given a tag (`v0.1` for example). Also merge it back to the `develop` branch, so that pre-release bugfixes get into the current working version.
* Hotfix branches can only be merged off from `main`, and must be merged back into both `main` and `develop`.
* Imagine that some bug shows its ugly head just after version 0.1 of your software is released. You can create a branch `hotfix-0.1` (or any other name starting with `hotfix-` indeed) from `main` and get one or several dedicated members of your team working on this branch to work on a fix.
* Imagine that some bug shows its ugly head just after version 0.1 is released. You can create a branch `hotfix-0.1` (or any other name starting with `hotfix-` indeed) from `main` and get one or several dedicated members of your team working on this branch to work on a fix.
* Once the problem is fixed, a new minor version of the software (that would be `0.1.1` in our case) is ready to be released: merge it back into `main` with a nice `v0.1.1` tag, and merge it into `develop` so that the fix is now part of the "main" codebase.
* Of course, hotfix branches can be created for any previous release if needed.
......@@ -147,10 +150,14 @@ The **forkflow**, more commonly named **forking workflow**, consists in letting
Typically, completed feature branches from a fork will be the object of MRs into the original project. It is then up to the maintainers of this project to integrate these new features and deal with releases. As such, the forkflow tends to rely on a branching model similar to Gitflow, except that features are developed in forks instead of new branches in the same project.
Note that, as a forker, you will have to pull the changes from the original project on a regular basis while working on a feature: you do not want to rewrite some piece of code that was recently added by other contributors, and you want to be able to open a MR that is *merge-ready*, i.e., not too far behind the destination branch. You may [read GitLab's page about forkflows](https://docs.gitlab.com/ee/user/project/repository/forking_workflow.html) for further info.
The forkflow is an ideal workflow for open-source projects, where possible contributors are basically "trusted third parties" that should be able to propose new features or fixes without being given write access to the project. However, advantages are not limited to the world of FOSS.
In an active project with a lot of contributors, the history of the project will soon get overloaded with development branches. Even when enforcing naming conventions (none of which is a perfect solution) for branches, this can quickly become a mess. This will not happen with forks, so that the important branches of the original project will have a clearer history. As for the CI, it comes packaged with the rest of the project when you fork it! Just set a runner on your fork and you're good to go.
# Personal takes {#personal-wf}
In practice, as a project gets larger, I believe that you may very well "reinvent the wheel" one step at a time, by reorganizing your workflow once in a while, so as to address the development management problems when they are encountered... until you reach a workflow that actually looks incredibly similar to Gitflow.
......@@ -160,9 +167,9 @@ Or you may converge to a workflow that takes advantage of features from differen
{: .box-note}
On the other hand, if, after reading tens of different articles on this topic and/or because of your own experience, you reach the conclusion that *one* specific workflow suits your needs for a given project, then you should not only implement this workflow as early as possible, but also **describe this workflow in simple terms in the `CONTRIBUTING.md` file at the root of your project**.
To me, for most non-industrial projects, everything is fine as long as:
To me, for most non-industrial projects, pretty much anything is fine as long as:
* collaborators agree on, and are fine with, the workflow they use;
* you still keep in mind that a workflow can be adapted, one step at a time, and *should* be adapted whenever efficiency problems arise.
* you still keep in mind that a workflow can and *should* be improved or changed whenever efficiency problems arise.
----
......
......@@ -26,7 +26,7 @@ We will also be talking about GitLab's very own web IDE, because yes, if you nee
Everything that we already learned about [cloning a repo]({{'/03-linear-git-project#clone' | relative_url }}) still works. You may very well open a terminal in your VSCode window and proceed as before.
Or you may get familiar with the Git menu in VSCode, the third one here:
Or you may get familiar with the Git menu in VSCode. That's the third icon on the left:
![](../assets/img/07-vscode/A-open-and-clone/vscode-02bis.jpg){: .mx-auto.d-block :}
......@@ -34,7 +34,7 @@ If you just click on it right now, unless you are already in a folder containing
![](../assets/img/07-vscode/A-open-and-clone/vscode-03.jpg){: .mx-auto.d-block :}
To clone a Git repo, you have to get and copy its URL from its GitLab page, still in the same Clone menu as before:
To clone a Git repo, you have to get and copy its URL from its GitLab page, still in the same *Clone* menu as before:
![](../assets/img/07-vscode/A-open-and-clone/vscode-04.jpg){: .mx-auto.d-block :}
> Once again, you should get the SSH link, for future convenience.
......@@ -75,7 +75,7 @@ I am asked the name of the file, and bam, I can work on it. A few changes later,
![](../assets/img/07-vscode/B-stage-commit/vscode-stage-02.jpg){: .mx-auto.d-block :}
These colors and letters are not there for nothing:
These colors and letters are not here for nothing:
* **M** means *modified*: the contents of this file have changed since the last time the sources were fetched.
* **U** means *untracked*: this is a file that the repo does not know of. (We just created it in our working copy, so... of course.)
......@@ -104,7 +104,7 @@ Everything went fine, and now, only `recipe-v1.txt` is left in my uncommitted ch
![](../assets/img/07-vscode/B-stage-commit/vscode-stage-08.jpg){: .mx-auto.d-block :}
Yay, we have options! The first one is the default, the second one makes it possible to amend the commit I just created (basically, change its contents and message while I have not pushed it), and I will not be delving into the last one. For now, I would just be content with committing, then pushing all my new commits. Guess what comes next?
Yay, we have options! The first one is the default, the second one makes it possible to [amend the commit I just created]({{'/05-good-practices#amend' | relative_url }}) (which is okay, as I have not pushed it yet), "Commit & Push" does exactly what it says on the label, and "Commit & Sync" will actually commit, then pull, then push. For now, I would just be content with committing, then pushing all my new commits. Guess what comes next?
![](../assets/img/07-vscode/B-stage-commit/vscode-stage-09.jpg){: .mx-auto.d-block :}
......@@ -114,17 +114,15 @@ Yep, I am reaching the repo, so that I have to provide my SSH passphrase. Once t
Remember that `git fetch` will not modify your working copy: it will just update your computer's knowledge of the state of the repo. So, why not? Just remember that `git fetch` and its variants exist if, for a reason or another, you switch back to the editor-and-terminal workflow.
Of course, if you clicked "No", everything is fine:
# Collaborative development {#collab}
We are still working on a single branch right now, but with changes being made from several places. Will VSCode still help us?
We are still working on a single branch right now, but with changes being made from several places, will VSCode still help us?
...you bet it will.
## Using the Web IDE
It's saturday morning, and someone just tested my recipe and told me that there were a few very important and urgent tweaks to apply to the recipe. Not huge changes, but the end result is a total mess without these. My work computer is still in my office, and I really want to commit and push a few changes right now. It might be time for the Web IDE to shine.
It's saturday morning, and someone just tested my recipe and told me that there were a few very important and urgent tweaks to apply to the recipe. No huge changes, but the end result is a total mess without these. My work computer is still in my office, and I really want to commit and push a few changes right now. It might be time for the Web IDE to shine.
I can just log in to GitLab and open the Web IDE to edit our recipe:
......@@ -150,7 +148,7 @@ Also, there is no committing without pushing. I mean, where would the commit be
And it's over. My changes are on the repo.
{: .box-warning}
There are cases in which you do **not** want to use the web IDE. Typically, if you have a bunch of tests that you should run before pushing code to the repo (or, even better, have plugged a pre-commit tool that will prevent you from pushing code that fails tests), then you should always work from your local working copy.
There are cases in which you do **not** want to use the web IDE. Typically, if you have a bunch of tests that you should run before pushing code to the repo (or, even better, have plugged a [pre-commit hook]({{'/09-advanced#git-hooks' | relative_url }}) that will prevent you from pushing code that fails tests), then you should always work from your local working copy.
## Fetching and pulling
......@@ -168,7 +166,7 @@ In both cases, my Git panel now has something to tell me:
![](../assets/img/07-vscode/D-fetch-pull/vscode-sync-07.jpg){: .mx-auto.d-block :}
Basically, there are changes on the repo, and I should sync with it. The downwards arrow indicates that I should "download" changes, from the repo to my working copy. Alright then. "*click*"
Basically, there are changes on the repo, hence I should sync with it. The downwards arrow indicates that I should "download" changes, from the repo to my working copy. Alright then. "*click*"
![](../assets/img/07-vscode/D-fetch-pull/vscode-sync-08.jpg){: .mx-auto.d-block :}
......@@ -216,7 +214,7 @@ Well, yes, but VSCode is really friendly with conflicts. See, this is what is di
Here, the green block is the "current change", i.e., the one that comes from my working copy, while the blue one is the "incoming change", i.e., the one made on the repo while I was looking away. I can choose whether to keep only one of the changes, or both at the same time.
If none of these options are fine with me, I may also just edit the file by hand, as I did before: this whole part (starting with `<<<<<<<<` and ending with `>>>>>>>>` has been formatted by VSCode, but it is just good old editable code). Or I can do the same thing, but better, with the "Resolve in Merge Editor" option:
If none of these options are fine with me, I can also just edit the file by hand, as I did before: this whole part (starting with `<<<<<<<<` and ending with `>>>>>>>>`) has been formatted by VSCode, but it is just good old editable code. Or I can do the same thing, but better, with the "Resolve in Merge Editor" option:
![](../assets/img/07-vscode/E-conflicts/vscode-conflict-09.jpg){: .mx-auto.d-block :}
......@@ -295,7 +293,7 @@ Let us click on this one, then pick a source branch...
This is not right, is it?
There is something at play here: merging branches is something that Git can do by itself, but every nice option that is provided by GitLab (picking reviewers, discussing changes, running tests on a branch before it can be merged...) is forgotten here. Plain old Git merging in a pure unsafe and selfish way. You do **not** want to do this.
There is something at play here: merging branches is something that Git can do by itself, but every nice option that is provided by GitLab (picking reviewers, discussing changes, running tests on a branch before it can be merged...) is forgotten here. This is just plain old Git merging, in a pure unsafe and selfish way. You do **not** want to do this.
Let us make this absolutely clear:
......@@ -317,7 +315,7 @@ Once again, pretty easy, except that it is not called "switching" but "checking
![](../assets/img/07-vscode/F-branch/vscode-branch-11.jpg){: .mx-auto.d-block :}
It is basically the same thing. Well, in this specific case, it is *exactly* the same thing.
It is basically the same thing. Well, in this specific case, it is *exactly* the same thing. [We went through part of this before]({{'/04-branches-issues#feature' | relative_url }}), but here are more details:
{: .box-note}
The `git switch` command was added for disambiguation purposes. The historical `checkout` command can be used in [several different ways](https://stackoverflow.com/a/57266005/21698549) (change branches, create a new branch, inspect a specific commit, remove fresh changes to specific files, ~~bring coffee and toasts, trigger the end of the universe as we know it~~ and, above all, make inexperienced Git users utterly confused). Now, there is `switch` for changing branches, `restore` to remove untracked changes from one or several files, and `checkout` for old-school masochists and IDEs. However, `git switch <branch>` and `git checkout <branch>` are equivalent, and `checkout` should not be deprecated in the foreseeable future.
......
......@@ -30,11 +30,11 @@ The basic object stored by Git is a *blob*. A blob stores the contents of a file
What would be the most convenient way to give names to these blobs? A "naive" solution, like taking the whole path of the file in the project, removing special characters and appending an integer that is incremented by 1 in each commit, does not work that well. For instance, what if I just rename a file, or move it to a subfolder, between two commits? Should I duplicate this file?
Instead, Git relies on [hashing](https://en.wikipedia.org/wiki/Hash_function) to compute a nice fixed-size sequence of hexadecimal digits from the contents of the file, which will yield the name of the file. These blobs are in the `.git/objects` folder, separated in subfolders named after the first 2 digits of the hash (for reasons I will not detail here -- efficiency, basically). Here, for instance:
Instead, Git relies on [hashing](https://en.wikipedia.org/wiki/Hash_function) to compute a nice fixed-size sequence of hexadecimal digits from the contents of the file, which will yield the name of the file. These blobs are in the `.git/objects` folder, separated in subfolders named after the first 2 digits of the hash (for reasons I will not detail here -- efficiency, basically). Here is one of those files in my working copy:
![](../assets/img/08-internals/git-internals-2.jpg){: .mx-auto.d-block :}
the contents of one of my files were hashed and its hash begins with `26d927bb21` (I decided against pasting the remaining 30 characters).
The contents of one of my files were hashed and its hash begins with `26d927bb21` (I decided against pasting the remaining 30 characters -- you can thank me later).
## Trees
......@@ -76,7 +76,7 @@ Hence, a single file that bears the name of the branch, and only contains a sing
# Deltas {#deltas}
There is still a huge aspect of Git that was not addressed yet. Imagine that you make one tiny change in a 1 MB file. From what we have seen so far, we would imagine that Git would take the new contents of the file, compare them to the blobs it already has in store (using their hashes), notice that these are indeed new contents, and... create a new blob weighing 1 MB to store the new contents? Come on, Git is more clever than this, isn't it?
There is still a huge aspect of Git that was not addressed yet. Imagine that you make one tiny change in a 1 MB file. From what we have seen so far, we would imagine that Git would take the new contents of the file, compare them to the blobs it already has in store (using their hashes, for efficiency), notice that these are indeed new contents, and... create a new blob weighing 1 MB to store the new contents? Come on, Git is more clever than this, isn't it?
Indeed it is. In such cases, Git will compute a delta, that is, the difference between the previous and current contents (pretty much like `diff` does with any pair of files in your standard Linux terminal), and store this delta instead of the whole file contents.
......@@ -92,13 +92,13 @@ There are two main consequences of everything we just said. One of them is why G
## Git is unreasonably efficient
To put it simply, I never stumbled upon a single occurrence of Git duplicating a file, and this is not even the most impressing thing about Git.
To put it simply, I never stumbled upon a single occurrence of Git duplicating a file, and this is not even the most impressive thing about Git.
Story time. The most atrocious commit I ever created on a project was when I reorganized all source files in a 4-year-old software project: I renamed a few files, sorted everything in fresh subfolders, used this new file structure to plug in a new build system, added/deleted several files, and changed a few things here and there in some of the source files themselves so that the build could be successful. It was (and still is) a mess of a commit. Close to 400 files were moved, some of them had parts of their contents changed, new files were created... I was even more worried as a few changes had been committed in the meantime, which meant that (because of the project settings) I would also have to rebase. I had pretty vivid nightmares the night before. How did it go?
Story time. The most atrocious commit I ever created on a project was when I reorganized all source files in a 4-year-old software project: I renamed a few files, sorted everything in fresh subfolders, used this new file structure to plug in a new build system, added/deleted several files, and changed a few things here and there in some of the source files themselves so that the build could be successful. It was (and still is) a mess of a commit. Close to 400 files were moved, some of them had parts of their contents changed, new files were created... I was even more worried as a few changes had been committed in the meantime, which meant that (because of the project settings) I would also have to [rebase]({{'/09-advanced#rebase' | relative_url }}). I had pretty vivid nightmares the night before. How did it go?
...well, the whole thing took me 10 minutes. I had not used `git mv` once in the whole process, but Git was able to know which files were moved where, including the ones whose contents had been changed. A single conflict occurred in the whole rebasing stage, and it was a very simple one that I could solve in a handful of seconds. **This is how unreasonably good Git is.**
And this all makes sense from how it handles all this stuff. Git has no difficulty detecting files that were merely moved or renamed, and it can then use sophisticated heuristics to minimize the total size of the deltas for the remaining files (typically, those that were moved *and* changed). It turns out that, when changes in files are not too extensive, Git identifies in the blink of an eye which file was moved where, *even when you also created and deleted files*. Nothing short of amazing, if you ask me.
And this all makes sense from how it handles all this stuff. Git has no difficulty detecting files that were merely moved or renamed (because of hashes), and it can then use sophisticated heuristics to minimize the total size of the deltas for the remaining files (typically, those that were moved *and* changed). It turns out that, when changes in files are not too extensive, Git identifies in the blink of an eye which file was moved where, *even when you also created and deleted files*. Nothing short of amazing, if you ask me.
But...
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment