When writing code, it’s super easy to mess something up or make a mess. To steer clear of chaos, developers use tools called version control systems. One of the most popular ones is Git. This nifty tool helps you keep track of changes you make to your code, save those changes, and even go back to older versions if needed.
Unlike some other systems, Git doesn’t just keep track of what changed. It takes snapshots of your code so you can see the bigger picture. But here’s the thing: many beginners only scratch the surface with Git. They miss out on some handy tricks, and this slows them down and makes things more complicated than they need to be.
So, let’s dive into some tips from NIX experts to help you make the most out of Git.
Setting up Git
One big reason why Git is so popular is that it’s pretty easy to start. Right from the get-go, you’ve got what you need. Setting up the basics is a breeze. You just need to tell Git who you are (your name and email) to mark your commits. Usually, everyone shares these details in their profile, and they’re stored in a file called .gitconfig. Sometimes, clients might want their details for a specific project, and that’s easy to set up. You can find more details about setting up your info here.
Now, when it comes to actually using Git, you’ve got options. You can work with it using console commands, IDE Git interface, or GitHub Desktop. But before you decide, it’s good to understand what’s happening “under the hood.” While UI is user-friendly, it might not let you do everything that Git can, as some useful commands or parameters can be missed. Meanwhile, you can see all the details in the console, such as indents. There has long been a debate among developers about whether indentation should be Space or Tab. Let’s say that in many IDE they look the same. In the console, there are specific characters for indentation.
Here’s a handy tip: if you prefer the console, try Tig. This utility imitates the UI. It shows your commits on the left and their contents on the right, making it easy to see your project’s history without hopping onto a remote server. It’s great for getting to know a project and doing basic code reviews to understand how your team works.
However, most people find UI more convenient for typical tasks. Tools like WebStorm from JetBrains have everything neatly laid out for Git, with buttons for all the important commands, from push and pull to deleting a branch and changing the name of a commit.
Getting Started with Git
Okay, so now you’re ready to dive into Git. First things first, create a folder, dump your code into it, and run the git init command. That sets up a local repository for your project. If you’re joining an existing project with some commits already done, you’ll want to clone that project instead. Just run the git clone https://git.service.com/your-rep.git command. If you want to send your changes from your local repository to a remote one, use git remote add origin https://git.service.com/your-rep.git. Then, you can push your changes using the git push origin your-branch-name command.
You can use platforms like GitHub, GitLab, or Bitbucket as your remote repository. They all pretty much do the same thing, but they have their quirks. For example, GitHub only lets certain users access private repositories. You can create commands for repositories and specify specific users for each access level. Meanwhile, GitLab lets you manage access in more detail through roles, groups, and projects. Access is configured using groups that combine users and give them different levels of access rights to the respective repositories.
If you’re jumping into an existing project, you might want to peek into the .git configuration file in the folder after creating a local repository. It tells you where the repository is located. You can also use the tig command to see all the recent commits and changes.
Why is it worth masking the .git folder on the prod? Leaving it open to the browser, an attacker can run a script to extract the website code from this repository. Then, he can review the code and find vulnerabilities to hack the resource. After setting up the repositories, the only thing left to do is to add a .gitignore file. In typical projects, it can even be copied from an old one. Also, the IDE (PHPStorm, WebStorm, VS Code) has a template with basic files and folders to ignore. Of course, no one is stopping you from making a list of files you shouldn’t keep remotely or locally.
Managing a .gitignore file is simple: just tell it which files to ignore. Start with things like passwords or logs — stuff you don’t want floating around. If you need those files in your repository, create an N-sample and copy only the file structure into it. Then, during the deployment, you can fill in the necessary parameters on the server yourself.
There is no point in copying IDE settings folders between repositories. These systems or their configurations may be different for your teammates. And if you’re working with JavaScript, you should add node_modules to the ignore folder. This folder can take from 200 MB to 1 GB.
Experts point out the risk of adding builds to projects. They can cause conflicts in Git, making it tough to merge branches later on. It’s better to build your project when you’re ready to deploy it, either on your server or using a service that fits your team’s CI/CD processes.
Basic Git Commands
When you’re just starting with Git, it’s tempting to learn every single command out there. But honestly, you’ll be fine with the basics you’ll use most of the time.
Here are the main ones:
- git add: adds changes from the working directory to the indexing;
- git commit: creates a commit based on the indexed data;
- git checkout: switches the repository to another commit or branch and can also undo changes in uncommitted files;
- git push: pushes changes from the local repository to a remote repository;
- git pull: downloads changes from a remote repository to a local repository;
- git branch: creates, renames, and deletes branches;
- git diff: compares the created commits/branches with each other or shows changes in a file;
- git status: shows a list of changed directories and files;
- git log: shows the existing commits in the repository.
Some flags can be added to commands. For example, git checkout -b your-branch-name allows you to create a branch to switch to. With git commit -m “Your commit message” you can write a description for the commit. git checkout -p selectively rolls back changes in a file. The command asks what it should do with each fragment. The git add -p command works similarly but changes the indexing in parts instead of the whole file.
Once you’ve got those down, you can explore other commands you might not need all the time. Sometimes, you might receive questions about them in an interview if technical experts want to test the depth of your knowledge. For example, the git fetch command. It is similar to git pull, but it doesn’t automatically merge the changes for you — it just grabs them so you can decide what to do with them later.
git fetch allows you to receive updates from remote repositories without automatically merging them with local branches. The developer can review changes before merging, ensuring control and avoiding conflicts. This command is important for flexibility and effective project development management, even in rare scenarios.
Some other commands, like git stash, are useful in everyday work. It lets you save changes you’ve made without committing them. Let’s imagine a situation where you haven’t finished the code for a commit but must fix a bug in another branch. If you switch, your changes are lost. If you commit an incomplete part, it can confuse your colleagues. git stash apply allows you to return the data from the stash when you return to your branch.
The git cherry-pick command is interesting. It allows you not to copy files to another branch (for example, from Develop to Main) but to send a commit immediately. Squash merge is useful for pooled releases – it helps to merge all changes and makes it easier to maintain the history of commits in the repository. Instead of a disorganized list of commits, you’ll have one big commit.
You don’t have to remember all the commands. There is one life hack – try the bash completion configuration script. When you press Tab, it shows a list of available commands. You can add to the autocomplete database to optimize everything for your needs or the tools you use most often. Similar scripts exist for any console utility.
Working with commits and branches
Okay, so now you’ve got the hang of the basics. But what about actually using Git in your day-to-day work?
First off, let’s talk about the rules of naming. It might seem like a small thing, but it’s important. Life is much easier if everyone on your team uses the same naming conventions. So, decide on some rules for naming your branches and commits. For example, branches for fixing bugs are labeled fix or bugfix, and branches for implementing new features are labeled feature or feat. There are also less obvious nuances. For example, how do you start the name of a commit with a capital or lowercase letter? And should several words be combined with a hyphen or underscore when naming a branch? All options are correct. The main thing is that these should be the same rules for the whole team.
How to use changes, commits, and branches in Git
In Git, a repository comprises commits representing different stages of changes. These commits are the building blocks for branches. Let’s break it down:
Imagine you have a main branch called “main”. Let’s say you’re assigned a task to add a new feature to the project. To tackle this, you create a new branch based on the “main” branch specifically for developing this feature. This separate branch allows you to work on your feature without affecting the main project.
You submit a pull request once you’ve completed your work and are ready to integrate it into the main project. It is a request to merge your changes into the “main” branch. But before that, you need to commit your changes and push them to a remote repository. Then, after your changes are reviewed and approved, you merge your feature branch into the “main” branch. This process updates the project with your new feature, making it part of the final product.
You could do without any branches if you worked on the app alone. It’s just one-stream work. In a distributed team, where several specialists work on a project in parallel, it would be difficult without such a system.
Now, imagine another developer is working on a different feature simultaneously. They also create their branch based on the “Main” branch. The branching out allows multiple developers to work on different parts of the project simultaneously without stepping on each other’s toes.
There are services for learning how to work with Git, where you can make commits and immediately see the result as a Git tree. This visualization helps better understand the work of the version control system and its features. However, the main thing is how to combine changes from different branches. After all, you and your colleagues can push commits simultaneously, out of turn. It’s important to update your repository regularly to pull in changes from other developers and avoid conflicts during merges.
Conflicts during merge
Merge conflicts are a common challenge in Git, and they can be quite complex to resolve. But let’s break it down to the basics:
Imagine this scenario: one developer creates a branch called A from the main branch and changes a string value to “ABC”. Meanwhile, another developer creates branch B from the main branch and changes the same string value to “123”. When you merge these changes back into the main branch, Git doesn’t know which version to prioritize, so it flags a conflict. As mentioned above, the version control system comes to the rescue.
Again, you can work with the console and UI to resolve conflicts. For instance, in WebStorm, your code is shown in the center of the screen, and its versions are on the left and right, and you can select what you want to keep step by step with arrows. Problem areas are also highlighted in color, and you can rewrite the commit directly in the interface and save it. Git treats it as a new commit. Other backend and frontend tools work similarly, from VS Code to IntelliJ IDEA.
To gain confidence in working with conflicts, experts advise practicing – create scenarios where conflicts occur between your branches and work through them. The process is relatively straightforward: push your updated branch to the remote repository where you want to merge the changes, and there is already a modified file. It causes a branch merge conflict. You fix everything as it should be. Then, you create a special commit to resolve conflicts. This commit shows that the conflict should be resolved in this way. After sending this commit to the remote repository, the system will know the problem has been resolved. Therefore, the next merge will be successful!
Causes of conflicts
Conflicts often arise due to human error or differences in approach among team members. It could be as simple as forgetting to update the local main branch and pulling these changes into the working branch. Or they made changes in one branch, then in another, merged into the main branch. Or they mistakenly set different values for the same function in several places.
As a way out of the situation, check your actions and, if everything is correct, ask your colleague about the reasons for his actions in his part of the code. Maybe he made a mistake too. Or maybe he learned about new customer guidelines before you did; for some reason, managers or leads didn’t tell you about them. In any case, communication with the team is always the best solution. If the conflict is insignificant, a screenshot and a short message to a colleague in the messenger will suffice.
In cases where conflicts stem from different technical solutions, involving a tech lead or project manager can help find the best solution for the project’s goals. Back up your arguments with reliable sources or best practices to support your case. Professional articles from Medium, best practice collections, books, etc., are suitable arguments.
Sometimes, conflicts are a result of process peculiarities within the team. For example, if QA specialists only check features after they’ve been merged into the main branch. If a bug is found, they have to create a branch with a bugfix and merge it. It can take a lot of time and lead to errors in the main branch. Another example is where changes were merged into a separate QA branch and were sent to the main branch only after being checked by testers. But if the QA specialists couldn’t keep up with the pushes, the features didn’t get to the main branch in time. Developers couldn’t upload the app’s current state without pulling in untested changes. In such cases, juniors don’t solve anything. They just follow the work model set in the project. However, for professional development, you should be aware of different scenarios.
To minimize conflicts, it’s advisable to use remote repository services like GitHub, Azure, or GitLab. These are not changes but a request for them. A team receives notifications of new changes in the code so that other developers or testers can check out the proposed changes and discuss them. The pull request merging is another notification to the team: the main branch is changed, so it’s time to update your branch. This way, you can keep up with the development and scaling of the project and minimize risks. After the merge, there will be no conflicts. At most, there will be a few non-critical issues.
How do we avoid difficulties when checking the pull request? NIX experts recommend breaking large features into parts when working on them. Several more branches will appear for individual parts based on the branch created for a new feature. You can submit these parts for review one by one. Otherwise, hundreds of files will be sent to the pull request. It can take several days to check them. Small pull requests will allow you not to wait for changes to other functionality that developers are working on in parallel to merge.
Setting protection in remote repositories for the main branches – main, staging, and develop – is a good practice. Then, any changes will be accepted after the review of the pull request, and this will prevent critical errors in the project.
Deleting branches and data after merge
Once you’ve successfully merged a working branch into the main branch, it’s essential to tidy up by deleting the feature branch. This can be done manually or automatically using settings in the remote repository.
While you may think you won’t need the old branch anymore, there are situations where having access to its history can be helpful. For instance, if a colleague needs clarification on your changes, it’s faster to refer back to the branch rather than trying to recall the details. However, over time, the relevance of the old branch diminishes, as all relevant changes should be reflected in the new commit. Failing to clean up old branches can clutter the repository and confuse the team.
After a merge, you might notice some files or folders in the repository disappear. There can be many reasons for this. For example, someone may have started deleting individual files in the repository. Sometimes, it’s due to new requirements; sometimes, it’s due to an error. In such cases, Git’s history comes to the rescue. You can use the git revert command to “roll back” the commit. The convenience of Git here is that nothing gets lost in it. That is why, with this system, there is no need to comment out code pieces that you might need later. This is what juniors like to do. However, what’s the point of keeping old code if you just roll back to the previous version and see everything there?
Sometimes, after a merge, you may find that the changes you’ve been working on vanish. As one of the experts recalls, he once refactored the code for almost eight hours, but everything disappeared after merging the “develop” branch into his working branch with the new changes. And it happened because the changes were not committed locally, leading to them not being captured by Git during the merge.
To prevent this, ensure you commit your changes or use a command like git stash to store them temporarily. Although, your UI can also help. For example, WebStorm has a local history. If changes suddenly disappear, see what the code file looked like a minute before the failed merge. It is a copy of the file.
Regardless of your challenges, taking the time to understand Git’s nuances is crucial. It saves you time, effort, and frustration in the long run, ensuring smoother collaboration and project management.