Git is the defacto standard version control for software development on Linux. For new comers to git, there is often lack of non-technical resources to quickly get them up to speed. This article attempts to provide some quick insight on how beginners can utilize git without too much angst.
Optimistic vs. Pessimist Locking
File locking models are used by source code systems to prevent developers from corrupting each others work in the event that they need to make changes to the same file.There are two types of locking mechanisms for source code systems that have been used in the history of modern software development.
In pessimistic locking, the developer must lock the file every time they want to make a change. This is said to be pessimistic because from the version control standpoint, the change the developer is making is assumed to be a conflicting change with the existing version of the code. In order to prevent the conflict, a lock is given to the developer to work on the file through a checkout process. Conflicts are prevented so merges are not required. Often the lock inhibited development on the same file unnecessarily and slowed development. This occurs often because the changes multiple developers were making did not cause conflict and could be easily merged by a developer or machine.
Optimistic locking version control systems allow multiple changes to be made to the same file at the same time while being optimistic that the changes can be resolved through a future merge. Optimistic locking has been the locking mechanism of choice in the popular source control systems in recent years such as git or svn. Developers like the flexibility of being able to change any file at any time they would like but they must remain cognizant of the ramifications of the optimistic locking approach on other developers.
Optimistic Locking with Git
Optimistic locking does require some awareness on the part of developers. Developers proceed with work in their local repositories changing any files at will. Changes are committed to their local repository and then pushed to the remote repository. Other developers on the team then pull changes from the remote repository into their local system. The are several basic development workflows that developers must understand to use git effectively. More complex workflows are be needed for maintenance and releases but these should cover non-maintenance projects.
Common WorkflowsThese workflows assume the developer is familiar with basic git commands in IntelliJ. They are presented not as a complete HOW TO guide but to give understanding of what is conceptually happening with Git. After all most developers know how to find content menus within an IDE.
Each developer starts the project by cloning the remote git repository. This copies the remote repo from the remote repo to the local machine. At this point, the local repo is an exact copy of the remote repo completely synchronized.
Creating a change
When starting development on a change, the developer is encouraged to get others’ changes. If all of the others’ changes are in your local repository and it builds, then conflicts will be minimized during the development time. Once others’ changes are merged into your local repo, you can proceed with development and testing. Once tested is it time to share your change.
Note: It is often wise to use the Intellij “rebuild project” option instead of “make project “ to make sure the entire project should be rebuilt instead of letting Intellij choose which new files to build. Intellij has shown that it does not always detect changed files and not rebuild the right parts of the project resulting in hidden compile errors. This differs from eclipse in that it compile after each change to file automatically.
Getting others’ changes
In order to get changes which others have made, you do a git pull on the remote repository. This will bring down their changes to your local repository. The pull will fail if one of your non-committed changes would be overwritten. So you have to commit your local changes to git or stash the changes before pulling. During the pull process, changes are merged from the source files to the destination files automatically by git. If git cannot merge the change, then it brings up a conflict and indicates several options to merge the file.
If a conflict occurs, the developer is then prompted to resolve the conflict through the manual merge process or by choosing the entire remote or local file. Obviously choosing your local file over the repo file is going to create problems for other developers on the team so this should be avoided. The changes made which cause the conflict will be removed from the remote code base the next time the local repository is pushed. I can testify to the frustration of working with a developer who did this daily and routinely on a project using SVN with no remorse to any since other developers’ work to the bit bucket. Choosing the remote file could be easiest if it is a small change to an interface file that is easy to put in back in your local repository. The developer can also use the merge tool to accept or reject individual changes in a file.
In summary here are the recommended steps:
1. Commit local changes
2. Pull from the remote repo.
3. If merge is required due to conflicting changes between local and remote file:
a. Either accept ‘your’ file or ‘their’ file or use the merge tool in git.
b. Commit changes locally.
Note: Per the working of git, the developer will have to commit all files instead of individual files when in a merge. This can be done using git commit -m “finished merge” from the command line as IntelliJ can be confused by this state.
c. Rebuild the entire project, fix any build errors, and commit any changes locally.
d. Consider sharing the change for the local changes to synchronize your local environment with the remote repo. If you don’t share the change and your development takes a longer amount of time, there may be even more conflicts to merge.
4. If merge is not required:
Rebuild entire project and if there are build errors, then resolve the errors either yourself by sharing the change or having another developer fix the remote repo and get others’ changes.
When you have completed, your local changes, proceed to share the change with others.
Share the change
When your changes are committed locally, they need to be ‘pushed’ to the remote repository for them to be available to other developers. During the push process, git attempts to resolve any conflicts between files in the local repository and remote repository by merging. If a conflict occurs and cannot be merged, then the push will fail. If the push fails, then it is recommended to get others changes from the repo to resolve the conflict locally. Once the changes are reconciled and committed locally, then push again and you should be successful in pushing to the remote repo. Even if there is no conflict reported during the push process, if you are not careful you could have broken the build.
Breaking the Build
The remote repo for the project always should have buildable code. In fact on many projects, there is a continuous build where builds are done AND unit tests are run hourly for code regression. Developers that break the build due to carelessness cause to the lost development time increase developer frustration. The frustration may even cause the developer to be subjected to the finger guillotine after repeated violations (after being on “double secret probation” of course!).
Merely pushing changes does not ensure the remote repo will build. Consider the case where you code for a few days changing things locally, and everything is working on your local machine. You think, great I am done and push your code. However, this doesn’t mean that the remote repo will build with your code changes. Others may have shared their changes in the meantime. If you push your local changes, it may break the new code base in the remote repo which you don’t have on your local repo. The only way to minimize the odds that your change will break the build is to get others’ changes and immediately rebuild your project just before you share your changes. Then fix any build problems before you share your changes. When this is done you can be assured the change will compile against the remote repo unless another faster developer has pushed changes in the time between you pulled, compiled, fixed any changes, and pushed. This is one disadvantage of the optimistic locking philosophy.
Java Interface Files
If the project has many Java interface files for constants, it is especially important to get others’ changes before you push your changes to the remote repo. This is because these interface files are changed frequently during development and easily break the build.
In summary for sharing the change the recommended workflow is as follows:
1. Make sure all your changes are committed locally then get others’ changes2. Push your changes