10
votes
[help] Tips on resolving git conflicts, for the faint hearted
I’ve been using git to keep revisions of my website. Since I work alone I only need three commands:
git add -A
git commit -m “<description>”
git push
Soon I’ll be adding a second person, and I remember from experience that conflicts can happen even with two people. So I have two questions:
- Is there a way we can avoid that happening outright?
- Are the commands to resolve fairly standard or does it differ much on a case-by-case basis? I’m hoping to keep the number of commands as small as possible.
Git is very open ended in how you can use it, so you are going to have to come up with a strategy on how to work together first.
Most projects use multiple branches to separate deployable (usually main/master) code from work-in-progress code (usually feature/ticket-id or bugfix/ticket-id) from different people. You then merge the work-in-progress branches to the deployable one after some manner of code review (ie via pull requests on github). Check out Trunk based development for a simple system that resolves around this.
Regarding number of commands, it depends on what system you end up using. If the actual commands are a problem, most IDEs/editors have git plugins that let you automate most of the work. Github/gitlab also allow you to automatically create and merge branches from the UI.
Very helpful, thank you. The reason I mentioned commands is because I find it more consistent to work with regardless of what machine I’m on. Documentation is easier to read without lots of screenshots too.
But because I do this work intermittently I have to remind myself of the commands all over again.
Some things to consider:
As far as the commands go that are involved with working in branches. You need to know how to check out branches
git checkout <branch_name>
for an existing branch.git checkout -b <new_branch_name>
to create a new branch.git push --set-upstream origin <new_branch_name>
when pushing a newly created branch for the first time. After that is justgit push
.Merge conflicts are a bit more tricky and not something you can do by just having commands memorized. I do recommend reading up on it once you do come across the need for it. One thing though, github and similar platforms sometimes have a web interface for resolving merge conflicts. I generally avoid using it, although probably not an issue for you yet I have found it can cause issues with stuff like pipelines.
What I do recommend is using an IDE of sorts to do the diffing, it does make things a bit easier.
This is fantastic: the bullet points make the detail easy to read (I am not a l33t coder by any stretch).
I’ll get branching to start since it seems to be an extension of what I’m already doing.
About the IDE: if I generally work from the terminal, can I still switch to an IDE when needed?
Absolutely! The nice thing with git is that you can quite easily switch between various tools if you want to.
You can also simply do merge conflict resolving in the terminal with your favorite terminal editor. I just personally do not prefer to do so.
As much as I like the terminal for basic coding and scripting, things like diffs are hard to conceptualise.
I agree that diffing from the CLI is a real pain, I avoid it whenever I can. Any tool is going to have a learning curve, and the diff tools built into IDEs will vary, but I think any GUI is probably going to be a better experience on the whole. If you're not coding in an IDE (which you arguably should be, but that's another discussion) it's probably overkill to fire one up just for diffs. I'd recommend a specialized program. My personal favorite is KDiff3, which is kinda fugly but FOSS and full-featured:
As utils go it's basically prehistoric but it continues to be maintained and actively developed. You can configure git to use it as
difftool
andmergetool
so it will open instead of the CLI tool by default. It's loosely "themeable" meaning you can change some colors, icons, and fonts, which isn't amazing but does help to file down some of the rough visual edges on Mac in particular.When contributing to someone else’s repo on GitHub, the workflow I’ve seen is that you create a fork on GitHub, push some changes to it, and then make a pull request. It always struck me as a bit unwieldy. Is there a different workflow for team members?
The workflow for members is indeed different. Although how much less unwieldy you find it sort of depends on how you used to do it previously.
Instead of forking the entire repository (essentially making a copy) you only create a branch within the repository and work based on that. The difference you notice mostly depends on how you have contributed to github projects in the past. Because if you just did one off code contributions, this might not seem all that simpler to you. But if you make multiple contributions this removes the step of having keeping your fork up-to-date and all that.
Do you have to push your temporary branch to GitHub, or can you make a pull request directly?
You still need to push the branch to the remote, otherwise GitHub/GitLab/Azure/Etc don't know there is a branch to begin with.
Though to be honest, from all the things having to push to a remote isn't what I'd call unwieldy. See it as hitting save on a document you are working on.
What can get a bit tedious is following all steps to create a pull request. Mostly the step to actually move away from your terminal, navigate to the right project on github (or whatever), etc.
For tedious tasks like that there also is a variety of tooling available. For example, there is a really nice GitHub extensions for visual studio code (and probably other IDEs as well) that let's you do it right there for the branch you are working on.
Even if you don't use an IDE there are plenty of options. For example, on my previous work laptop I had a little script set up that is bound to a
createPR
alias that would push everything to the remote and based on the last commit it also created the pull request.With my current job, I haven't set up anything like that. We use GitLab and in response to a push it gives a pre-filled link that when clicked opens up the merge request interface for you with a bunch of details filled in.
If you have write access to the repo you would normally create a branch rather than cloning the whole repo. Other than that it's about the same.
Oh that’s a good point. I actually did that once to add an entry to 1mb.club but I blundered through it to such an extent that I wasn’t sure how I did it in the end :)
In my case now, I’m hosting the repo.
I'm sure there are a lot of good suggestions out there but i just want to say there is no shame in redownloading your repo, pasting the changes in, and then deleting the old copy (once you are certain it's working).
Obviously this is not what you are "supposed" to do but really what you are supposed to do is write code, and git is a tool to help you write code. Don't let it rule your life.
Thanks. I did feel a bit of shame doing it that way, but as you say, in life terms it freed up a lot of time, relieved stress and got the job done.
It is indeed perfectly fine, but once you learn about a couple commands you really don't need to do that.
Some basics here https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things do note that restore is a modern command and is not available in older versions of git.
Not git specific, but more general common sense. Git is not magic, so the best way to avoid conflicts is to have tasks where you're not working on the same things at the same time. The more your tasks are in different areas the less conflicts you'll have to resolve. Plan for that.
I think I just assumed that the technology would “handle everything”. I’ll include communication in our process in that case. I’m seriously tempted to work directly on the main branch since they will be working nights and I’ll be working days. We could coordinate that.
What if two people rewrote the same web page from scratch? How would git know what to do? A human being is required for many merge conflicts because you need to have a deep understanding of:
Which then lets you perform the merge resolution, which can be formulated as: How would I achieve both the goals of A and B given the original state?
Sometimes A or B is obsoleted by the other. In that case, you can just pick one and move on. Sometimes they're both important fixes and you need to step through the logic of both implementations.
I would recommend using a very simple workflow where you have a canonical, linear history on
main
and either commit directly there or rebase feature branches ontomain
. That way it's always clear what happens first and who needs to fix which feature branch. This will require you to understand the basics of how git commits are defined and structured so that you can visualize the distributed graph as you're performing and planning rebases.You know, the idea of a linear history is very appealing and something I hadn't considered. We've spoken in the last hour and we could very well end up committing directly to main and making sure not to work on code at the same time.
You'll find that when someone else pushes to main and then you try to push it will reject your change. In this case
git pull --rebase
will take each commit you've made and, in order, apply them to what's hosted remotely. If there is a conflict with any of your commits it will halt and you'll have to fix the issue.You can get a linear history while using feature branches. The linear aspect is almost always a result of retroactive continuity. So when it comes time to add any feature branch to main you'll rebase it onto main just like how you would between your local main and the remote main.
Merging is, in my opinion, to be avoided. It's a shame it's the default way to combine changes. If you only rebase then the question of what happened, when, and in what order is obvious.
So rebase is like a pull, except it steps you through the process to help you retain your own changes? It sounds like it would be used when the remote has been updated since you did your last pull.
Git is overly complex. Rebasing isn't comparable to pulling. Sorry to shake you loose from the small foundation you're starting to build.
Use this image as a reference. Let's say the blue sequence of dots is
main
. Each dot is a commit and history goes from left to right. Orange and green are two independent feature branches, each from different people. Orange's base is the left-most blue dot. Green's base is the second blue dot. Re-basing means you redefine the base for a series of commits.So if you rebase orange in the reference image onto the right-most blue dot now you have 4 blue dots followed by 2 orange dots. Green in this scenarios is unaffected.
Confusingly, this does not affect the definition of
main
, just the definition of the orange branch. In the world of git you would do the following to incorporate orange intomain
:Github does offer a "Rebase and merge" option in pull requests which does all of this for you.
That's a fantastic explanation, thank you.
Honestly, even when I'm working solo, I still use branches. You can keep features and development tasks separate and flip between them cleanly. You can't predict interruptions. (I guess you could use
git stash
in a pinch.)Very interesting, thank you! I’d forgotten about the stash option and hadn’t considered using a branch for solo dev.
Usually I just edit text on a page or update a price, so those are small jobs. But I’ll need to do something more complex later.
I usually
git pull --rebase
and solve any conflicts that come along.Keeping both your developments in sync regularly also prevents a lot of hassle.
I actually saw that frequently posted online, but the first conflict I had wasn’t resolved with that for some reason. It’s ridiculous because it was small amounts of changes on just a few files.
In the end I copied the files from my branch into a directory on my local filesystem, nuked the conflicting branch and then checked the code out again to re-add my changes. It took ages.
Rebasing will not resolve conflicts. What it does is basically merge in everything from the remote and then puts your commits on top of the tree.
I’m still figuring out what exactly a remote means. It seems it can be absolutely any repo or branch, on a local machine or a server. Is it just a pointer to any repo/branch I choose?
My source: https://stackoverflow.com/questions/20889346/what-does-git-remote-mean
It is in the name. You have your local repository and the remote central one. Often hosted on GitHub, Gitlab or some similar platform.
So when you use the pull command, for the branch you currently are in, you are pulling everything from the remote version of that branch to your local branch. In the same way, if you use the push command, you are pushing your local changes to the remote.
Crystal clear, thank you. Where were you when that SO thread was started 😄
I should say I simplified it a bit ;) But it does cover the basics for what most people will be dealing with.
The actual git commands you use to resolve the conflicts will be pretty mundane. The hard part lies in sorting out the conflicts themselves in your IDE/editor in a way that won't break things or inadvertently undo parts of a teammates' work.
Now, with such a small team size, you probably don't want to bog yourselves down with fancy, enterprise-level project management overhead, but you'll probably at least want to do the following:
If it is clear that one teammate's next task depends on the work being done by the other, an option to consider (case by case) is to make a grandchild branch off the work in progress (rather than master/main).
Another point on communication too. Noted, thank you.
There's also https://ohshitgit.com/ which helps you fix some common scenarios. I've got it bookmarked on my computer.
Oh, that's really useful - thank you!
Be really consistent about rebasing. I do it as my first task of the week every Monday on all my work in progress branches.
Here are some unconventional alternatives:
If you’re working in the same office at the same time, maybe try pair programming? Then you don’t need code reviews since you’re reviewing each others’ work as you do it, and only doing one thing at a time.
Another approach we used to use was to have a physical token, where when you’re ready to commit, you take the token, and then nobody else can commit while you’re doing it. We would also have a sound effect set up to play so that when someone commits a change, you would notice and pull right away, so you’re always up to date.
Then you don’t need branches at all. Just work on main. You do need good tests. You don’t need a continuous build if you’re rigorous about running tests before you commit, though it might be nice to avoid “it worked on my machine” problems.
There are probably ways to do this remotely. Maybe you have an always-on video chat, or something like that, and you get the other person’s attention before committing? As others have said, knowing what the other person is working on and doing something else really helps, too.
Some great ideas there. I've decided not to use branches at first, at least. We're going to phone each other before working on any code. It will be very intermittent anyway, with weeks between any changes.
I forgot that the pull is necessary when more than one person is involved. I'll need to explain that to them.
The thing about tests is that it's HTML we're working on. There are no compilers to give warnings or feedback. So I think the only tests we can have need to be manual and visual.
When you pull, the merge might fail and then you have to deal with the consequences. Sometimes it’s easier to redo the change than unscramble it. This is a good reason to commit more often when you can, so there’s less work to redo.
Yes, it’s mostly visual, but there are HTML checkers that can be useful for detecting bad structure. Some might check that links work, too?
I realized recently that conflicts are really straightforward.
You go "git merge master <your branch>" and a file has conflicts. Open the file in your editor, and it shows you "their" code, and "your" code.
All you need to do it remove all the
=====
and>>>>
from the file, put it into the state you want, and thengit add <file that had conflicts>
,git commit
.What happens when the conflicts are super confusing? My approach is to go to my PR in Github, and look at the last clean version I pushed there. What does it look like? What were the changes I was trying to introduce? What changes is the other user trying to introduce?
My reviewing that "last clean state", I often find the merge conflicts much easier to understand.
Again, a merge confict is just git saying "I'm not sure what this file is supposed to look like" and then you go "here, lemme clean that up for you."