Git is an essential tool for every developer. In this tutorial, I will explain everything you need to know about branches and conflicts while working in a team, with Laravel examples.
In fact, this article is not about Laravel, it's just that example code will be with Laravel framework, but you can apply Git knowledge from here to other coding languages/frameworks, too.
Notice: in this tutorial, we will use Terminal and not use visual tools like Sourcetree/GitHub Desktop or PhpStorm/VSCode editors. Those tools can help but I want you to learn principles and the syntax if you do need to work with the Terminal.
Start with Branches: Main/Master and Develop
Branches are probably the foundation when working with the team. But even when working solo, you may use branches to work on separate features simultaneously, so you may have branches called "feature-payment", "feature-user-update", etc.
Also, you may use them for testing features from branches without deploying them to live. So, your testing server would get the code from "develop" branch, for example, and you can play around there, until you're sure it works, and then merge the code into the "main" branch for deployment.
Every team choose their own branch names and branch logic, but there's a most typical behavior, here's how it goes.
When the repository is created, it is created with the branch called main.
Notice: GitHub changed its default from
mastertomainfor new repositories in 2020.

Next, let's create a new fresh Laravel project and push its code to GitHub.
laravel new laravelcd laravel
Now we can push to that main branch:
git initgit add .git commit -m "Fresh Laravel"git branch -M maingit push -u origin main
What every command here does?
- Creates an empty Git repository.
- Adds all files to the repository.
- Makes a commit with the message "Fresh Laravel".
- Sets branch to
main. - Adds an origin where to push.
- Pushes code to the
mainbranch.
If you refresh the GitHub repository page, you will see the code was pushed to the main branch.

Next step: usually the main branch is only for the "finalized" features to deploy to live. And the work is being done in other branches. So, from that main branch, someone creates a develop branch where the work is being done. Here's how you can do it on GitHub directly in the browser.

At this point, the develop code is identical to the main branch.
Then, whenever a developer starts working on the project, they clone the repository, check out the develop branch and starts the work.
The main branch is only for deployment to the live server which happens from time to time, but more rarely than everyday work.
You can even protect the branches on GitHub, to restrict pushing to the main branch, to avoid...
Premium Members Only
This advanced tutorial is available exclusively to Laravel Daily Premium members.
Already a member? Login here
Premium membership includes:
Comments & Discussion
Resolving Conflict Section *I think Re consider third paragraph of this section *
we merge the develop branch into the feature/user-country branch by doing git merge develop. And of course, we get the expected conflict message:
I think, It Should be we merge the feature/user-country branch into the develop branch by doing git merge develop. And of course, we get the expected conflict message:
@Povilas
I have a following problem: I have 4 laravel apps wich are very similar but not identical and mostly the difference is the design (they are made for different clients). Now I have to add some fixes and some new functionality to all of them. All the fixes and the new functionality are the same for all the apps. Is there a way to make some kind of patch to apply so I will not have to copy-paste all of this fixes and new stuff to every one of the apps separately?
Well, this is tricky.
Is there any way this could be done? Yeah! Private packages would help here. They are quite good at keeping things up to date from a single point.
Is there any other way? Sadly - no. Separate projects/repositories means separate bugfixes. And that has to be done manually.
I just found the better (I think) way:
- change one app as you need (if the app has any irrelevant to the topic changes commit them before starting)
- make all necessary changes
When finished the changes there are 2 ways
- from command line do: git diff > mypatch.patch and then commit
- if you use PHP Storm commit the work and right click the commit you've just created and select Create Patch... and name it as you wish eg. mypatch.patch
Now copy the mypatch.patch to other apps you want to patch and from command line do: git apply mypatch.patch
tada! you have it patched.
Oh no, not fixes! I'm talking about the case, where you just mentioned that patch was pretty much identical between all applications. This indicates, that the code behind is also identical (to some extent!). That means - it should be possible to completely move that code to a private package and maintain it via that package.
In other words:
- You create a common codebase as a package
- Use that in any number of projects
- Once they need identical updates - you just update the package and pull newer version in the projects
At least that's how I feel it should be done, as otherwise you will constantly have to update the same thing in 4 or more places :)
Hey there,
I follow your work from a long time, but honestly I don't accept every point of your article.
What you showed here, is the path from developing a new feature, to merge it into
develop. What I don't see is, how you merge features safely to main? Do you mergedeveloptomainfrom time to time? How can you release features tomainseparately? With this technique you can't, or there is only a few feature development parallel.I would change one step (above in the article). Imagine a team with a real client with multiple feature request. The client wants to test feature by feature, so he/she can mark the features, which can go live. And it is really important.
Whenever you create a new feature branch, you create it from the
mainbranch, and not from thedevelop. Thedevelopbranch can have unaccepted features. If you create a new feature branch from here, there is no chance, your newly created feature could be released sooner, if the client approves it. Let's say, there is an ongoing blog feature request, which some part is already in the development, but the client changes his minds from time to time. Also there is a new popup request, if the visitor wants to left site, a popup should appear. If this popup feature comes from the 'unfinished'developbranch, you can't release the popup stuff, until everything is accepted.As always you say: it depends, of course you are right :)
What I wanted to point out: This approach (features came always from
developbranch) can cause headaches and lost of hours, if there are multiple feature developments in parallel & someone create a new feature fromdevelopbranch.It will only show something unexpected, when you want to merge the accepted feature to
mainand you see more changes than it was committed.But this is my opinion & experience which I wanted to share :)
Hey, I got your point but another view is, you can use your main/master branch for versioning and develop branch to put a last step before release.
For example, your app/package is on version 0.1.0 and want to add x, y and z features for the version 0.2.0. I think it's not a proper way to merge these features to main/master branch separately. You can combine them on develop branch and when it's merged to main/master, it will be the next version.
But, as you said, the client may wants test feature by feature or some other case. And again as we all say, it depends :)
Thanks for the very valuable and long comment Pappz. I agree, this article mostly describes the "almost ideal" world where nothing needs to be hot-fixed or deployed urgently.
For urgent deployments like these, every team have different approaches. Personally in our experience, we used to break all the rules and committed straight to "main" for very urgent things, then merging it into develop, after it all calms down. But do you really think that I need to recommend this approach? I kinda deliberately left it our of the article, pointing out only the "best" scenarios because it's best to keep it that way.
I guess there should be a separate article of "Ways to quickly deploy/launch things", will think about it.
Thanks for the reply! I know situations, teams and clients are all different. Also I think, maybe there is a little misunderstanding so I try to show it with a timeline.
I think this can be a good approach, and this is what I meant, that every new feature branch should be made from
main, in my point of view:Day 1,
mainanddevelopbranches are equalmain, create a newfeature/AbranchDay2,
feature/Abranch todevelopmain, create a newfeature/BbranchDay3,
feature/Bbranch todevelopmainis not touched, butdevelophasfeature/Aandfeature/Bfeature/Abranchfeature/Bbranch back to mainmainbranch contains only the accepted featureAll-in-all:
What you mentioned above, always create new branch from
developis what I wanted to point out.On Day2, if developerKing would checkout from
developbranch, then hisfeature/Bbranch would containfeature/A, which was not accepted on Day3. This means, they couldn't releasefeature/Bon Day3. Only afterfeature/Aacceptance.I hope it is clearer now.
Moreover, belive me, I made also some ASAP hot fixes, directly to main, so I understand, situations create the rules!
But I have to mention, I like to keep things clear. I always have fix/pappz or hotfixes branch, which is always "my-urgent-asap-fixes" branch. Whenever some hotfixes have to be done, I merge
mainto myhotfixbranch, then after merge it tomain. This make things more clear for me. (At least I try to belive in :) ).Wow what an amazing long comment, thanks for taking the time! And yes, I do agree with the approach you described, as you explained it in more detail.
mainbranch atoriginshould reflect production-ready state.developbranch reflects a state with the latest delivered development changes for the next release, it may be called as "integration branch". In general not completed or not accepted feature shouldn't be merged into develop in the first place, but that's not always the case. Good strategy could be to use--no-ffflag when merging feature intodevelop.This way it always creates new commit object and preserves historical changes (even if fast-forward is possible) of a feature branch, and allows to easily discard feature from the next release.
Then another dev created
feature/Bfromdevelopwithfeature/Amerged. Got work done, work was approved.feature/Agets discarded fromdevelopby this point.Now
feature/Bhas conflicts and stillfeature/Apresent. It is good to resolve that upfront (for example we do not know yet if client approvedfeature/Bor not). So what dev can do is just to runrebase.rebasewill reapply feature's B commits on top of currentdevelopbranch and will allow to resolve conflicts in the feature branch instead of when merging intodevelop.This way you can merge
feature/Banytime intodevelopwithout any consequences, and ever having to deal withmainto apply hotfixes.When the code in the
developreaches a stable point, changes are merged back intomainand this is a new production release by definition.