2. Git hooks. Git standards. Rebase vs Merge. Squash merge and WIP commit

Video version
(Leave your feedback on YouTube)

TL; DR

Everything that can parse the code - save to DEV dependencies! Test runners, linters, debuggers, static analyzers, builders, minifiers... Everything !

Git hooks are cool. Read here. Husky js - Зручний спосіб роботи з git hooks

Branch name check.

.husky/pre-push
npx validate-branch-name

.validate-branch-namerc.json
{
    "pattern": "^(feature|fix|hotfix)/.+",
    "errorMsg": "Branch name validate failed please rename your current branch"
}

Check commit message. Standards here

.husky/commit-msg
npx --no -- commitlint --edit $1

commitlint.config.js
export default { 
  extends: [
    '@commitlint/config-conventional'
  ] 
};

lock files (composer.lock in backend, yarn.lock|package-lock.json|pnpm-lock.yaml|bun.lockb in frontend) must be in git!

Regardless of which package manager is chosen, npm, yarn, pnpm, bun - it should be one per project!

WIP commit is the norm. Don't be afraid to save!

Review yourself! Even if there is someone who review you

Do not use git add .; Turn off auto adding files in IDE

Standards with git hooks

Standards make code better, make onboarding of new employees easier, analytics easier, reviews easier, support and development easier. If you are a PM, replace "easier" with "faster". If you are a product owner, replace with "cheaper and higher quality."

And here's the problem. Everything written in your README file, confluence, or recorded no matter where - no one will read it. And if someone read it, they won't follow it, or forget it and demand resources every time for review or refactoring, which usually just negates all the above-mentioned benefits. Anything someone wrote or said anywhere we called - recommendation.

Everything that can be checked — can be called a standard. And then the question arises, how and where to check. The correct answer, of course, is on the server. Before deployment, before review, with every commit etc.

Now imagine a normal development process. The developer makes a branch for their task, code -> commit -> code -> commit -> code -> commit -> push. And then there's a queue of pipelines, delays on the server. And believe me, no one developer won't start a new task until previous not checked successfully. Regular developer will go for a coffee.

Coming back, developer see that the first, 3rd, and 10th commit don't meet the standards. So, you can't merge code into production, can't cherry-pick some commits. Otherwise, you have no standards.

In this step developer should start editing their commits, which is difficult, long, and expensive. Very long and very expensive.

To prevent this situation, git hooks will help us. We can run checks or even auto-fixes rules before the commit. On the developer's local machine.

Or before any action with a git. Documentation here.

Husky js

When you read documentation, you can see that git hooks are just a set of commands located in specific git directory. But these commands need to be placed in that directory somehow. Git can't commit itself. So, we need to copy them there and do this for each developer on their working machine.

Now, we can create some shell script, store files in a separate directory, and make running it mandatory during setup. After some time you will notice that some developers skipping this step in setup makes "life easier".

After that you will think about running "copy-step script" onto a some required hook, for example, in the package manager. Some hook like post-install-cmd in composer.json on the backend, or prepare in package.json on the frontend. Nobody will edit it because the chance to make a mistake and push to repository is very high.

And now you've come to a ready-made solution called husky js, and it does all this steps for you.

Please do not test everything said on yourself. Just 2 commands and only 4KB of dependencies. And the most important thing is that it's a well-known tool among developers, not your self-made "creation".

You can say: "Hey, I have only one API on the backend, and I don't want to see anything related to js". And I agree, I don't want to. But unfortunately, I haven't found correct alternative for Husky for composer. Okay, we can develop it easily, but then there will be commit lint, and there's no alternative to it. So here, I give in and accept the superiority of JavaScript in this matter. And I install husky into Laravel project.

About package managers

You'll see four installation options in husky documentation - four package managers npm, yarn, pnpm, bun. I won't specify which one to use. Personally, I have not experienced with bun. Each has its advantages, primarily speed. It's important to understand that speed matters not for you locally work but for deployment. You won't feel the difference in speed locally.

I use npm because it alerts about vulnerabilities found in dependencies. Perhaps other managers do this too, and I just stick to habit. Read, try, choose one.

And here, three important rules follow one after another.

lock files (composer.lock in backend, yarn.lock|package-lock.json|pnpm-lock.yaml|bun.lockb in frontend) must be in git!

It is obvious, it's written in a bunch of instructions, in every documentation. But personally I've seen too often how developers ignore it. TOO OFTEN and in too many different teams and projects. You should know that package managers set dependencies to the latest with a constraint in the json file. Specifically, version constraints define symbols tilde ~ (patch), or caret ^ (minor) before the package version. Read here about semantic versions.

Lock file, precise versions are stored, and right deployment process ensures that your local packages and packages on the server are the same.

Regardless of which package manager is chosen, npm, yarn, pnpm, bun - it should be one per project!

This is the second problem I see too often. Half of the teams on npm, someone read somewhere that yarn is faster, and use yarn. Or some "genius" developer comes along saying that there were stupid developers before him, so now everyone uses pnpm... meanwhile, no one has removed yarn lock...

Everything that can parse the code - save to DEV dependencies! Test runners, linters, debuggers, static analyzers, builders, minifiers... Everything !

The most important about a package manager - think where you install dependencies. Package developers often skip this and suggest installing everything in production. Most do it accidentally, but some may do it with evil intent.

Security of your program is your problem. Look into it during code reviews. And you may have more than just dev and prod environments. Also, if there are other groups of packages, keep an eye on them too. And, of course, deployment speed comes second - fewer packages download faster.

Validate branch name

Let's set up 2 linters, both related to Git.

The first one is validate branch name. It's a very simple package. It checks if the branch name follows a specified regex. This is important because it makes branch searching easier, minimizes the number of mistakes, and simplify Git integration with your task manager. It also allows for automatically generating correct merge request descriptions (for example, by adding a checklist for reviews).

For my needs, I only require a prefix like feature|fix|hotfix followed by a slash and anything else. However, you could add, for example, task number.

.validate-branch-namerc.json
{
    "pattern": "^(feature|fix|hotfix)/.+",
    "errorMsg": "Branch name validate failed please rename your current branch"
}

It makes sense to check only when pushing to the server. Create what you want locally.

.husky/pre-push
npx validate-branch-name

Commit lint

The second recommendation I suggest installing is a check for commit message. The advantages are the same as with branch names, plus it makes navigating through commit history a bit easier.

If your company doesn't have specific rules, I recommend using commitlint config conventional. This standard was developed by smart people with community feedback.

commitlint.config.js
export default { 
  extends: [
    '@commitlint/config-conventional'
  ] 
};

Use commit-msg hooks for message check

.husky/commit-msg
npx --no -- commitlint --edit $1

WIP(work in progress) commit

Situations where something is left unfinished, but you need to step away happen very often. For example, an iranian kamikaze drone is flying at you (hard Ukrainian humor). You don't want to lose progress. But you can't commit and push because there's a bunch of stuff happening in Git hooks. That's where WIP (Work In Progress) commits and methods of ignoring Git hooks come to rescue.

The workflow with WIP commit is very simple.

After finishing the work, push everything to the server.

git commit -m 'wip: some text' --no-verify # or just 'wip'  
git push --no-verify

Back to work - canceling the WIP commit.

git reset HEAD~

The next push must be done with `-f' option - force push.

Feel free to make WIP commits all the time, don't be afraid no one will review them, people usually have other things to do.

Well, if you are a person with a very large amount of free time - do not look to others WIP commits or WIP merge requests.

And if you are DevOps - cut off checks on the server if the commit is WIP (and prohibitions to blink in prod, of course)

Merge vs Rebase vs Squash -> semi-linear

I'll start with a simple - Squash merge request. It consolidates all your commit work into one commit. It's usefully when you've made a lot of commits, like fix: code style, fix: after review and so on.

I'll try to explain merges in very simple terms.

A simple merge combines your branch with another, leaving a huge history of the merge. It shows who did what and in what order, commit by commit in the timeline. It looks like a huge subway map, specially when many developers work parallel.

When you work solo, merging isn't a problem.

Rebasing, however, makes it as if all commits happened sequentially, one after the other, immediately in the branch, with no branches. The big downside of rebasing is conflicts. Developers can easily make a mistake and lose their or their partner's code forever during a rebase.

For beginners, it's just hell. And for you as a manager, it's lost money.

But there's a third option, and I recommend trying it. GitLab offers semi-linear merge out of the box. It structures the branch history so that all merges go into it sequentially. It's like one developer was working. This way, the Git history is much easier to perceive. We have access to revert merge requests, and we see the history feature by feature, commit by commit.

One drawback is that the branch must match target branch before merging. This is fixed by the same merge (or rebase - recommended).

I'll try the linear history myself, but unfortunately, We will not see the effect because I work solo (for now)

Settings => Merge requests => Merge method => Merge commit with semi-linear history

Read about merger options

All to README.md

We should put all standards and recommendations into the README file.

And remember - no one will never read documentation. Documentation, in general, is never read. Except for setup - everything else is in the blind spot. There will be a separate video about documentation on the frontend and backend.

But writing a README is mandatory for sending to it developers who have complaints like "I didn't know", "there's no documentation" or think that some lead is a despot and made up rules because everything was perfect at his previous job and there were no linters (why he's not usually at that perfect job nobody knows).

README.md
### Branch naming
Requirement described in the file
```
.validate-branch-namerc.json
```
branch name auto validate on pre-push hook using
[validate-branch-name](https://www.npmjs.com/package/validate-branch-name
) package

### Commit convention
Each commit message must follow the [commit convention](https://www.conventionalcommits.org/)

Automatic message validation on commit-msg git hook.
All setting described in the file
```
commitlint.config.js
```

## Merge commit with semi-linear history

Use line git history. Read [documentation](https://docs.gitlab.com/ee/user/project/merge_requests/methods/#merge-commit-with-semi-linear-history)

Self-review

Finally, a couple of recommendations that will boost developer skills .

Review yourself! Even if there is someone who review you

It seems easy and obvious. But no. Serf review is very, very difficult.

You've spent a lot of time working on task, writing tests, solving a lot of problems. And now you can finally create that merge request. Your brain doesn't care anymore about request. The brain has won already, it remembers everything as if it's all "okay".

But what's not "okay" there will notice, the main person on whom your career depends - the reviewer.

Personally, no advice would help me more than "self-review".

The second advice is closely related to the first one.

Do not use git add .; Turn off auto adding files in IDE

Also, it seems obvious, but from my experience of the reviewer - developers commit everything. Unsuccessful file, examples, some meme pictures that were accidentally thrown into the project directory. And yes it's easy to fix everything, but the reviewer will spend his time, and then you will spend your time.