4 Easy Tips To Work Better With Git

Maybe they are quite trivial and only come from common sense, but sometimes it's useful to repeat. By the way, these are general purpose suggestions that can be used also with other versioning systems.

1. Use Tags To Label Versions

Every single release of your software that leaves your PC must have a commit tagged with the version number, this way:
git tag ver_1.02
Remember to push the tags to the remote repository with:
git push --tags
This will help you to understand which release you have to use to reproduce issues that testers or users find. It's also useful when you need to create a changelog from a previous release. But this is related also with the next point.

2. Write Meaningful Commit Messages

Explaining why you made a commit and which parts are involved can help other developers (and especially you, in the future) to quickly understand the change made without having to deal with differences in the code.

If you are using a bug tracking system, always mention the id of the issue that usually include detailed information on how to reproduce a particular bug. And this may help when that code will be changed in the future.

Besides, good written commit messages can help you to create a complete changelog, even with automatic tools.

For a quick reference about the style to adopt for commit messages, you can look here, while a more complete explanation can be found here.

3. Signal Particular Commits

When you work in a team, it's possible that someone does not respect the conventions about tabs/spaces, position of curly braces, etc. If you are a coding style nazi, you'll fix all of these as soon as you notice them.

The important thing is to not mix these kind of changes with modifications on the behavior of the code. Another good practice is to make clear in the commit message that you are just doing cosmetic changes.

A good way is to start the message with the word "[WHITESPACES]" or "[COSMETIC]". On some projects  I also use prefixes for changes on test cases and documentation.

4. Each Commit Should Leave The Repository In A Good Status

This means that your project must always build without errors, no matter which issue you fix. The repository is not your daily backup.

If for some reason this is not possible, for example because your modification needs someone else's work to be completed, it must be signaled in the commit message (see also previous point).

Conclusions

Following these suggestions is not a waste of time. Believe me, when you are in trouble you'll be grate to easily access all the information you need. The following infographic can help you to remember these tips (click for a larger version).



How To Recover Deleted Git Commits

In many Git tutorials it's written "never use git reset --hard". And there is a good reason: this command deletes commits, and, if you don't have pushed to a remote repository, your changes are lost (if you don't know where to find them).

A Little Story

This happened to me some year ago, when I was a Git newbie. There was a bug on a software so I created a new local branch starting from the master and started to fix it. In the meantime, a colleague of mine asked me for a quick workaround to continue his work. So I switched back to the master and added a couple of temporary commits.

After a week, the state of the repository was this:


The bug was fixed, the temporary commits could be removed and the branch merged to the master. Easy to say, easy to do, easy to mess.

My idea was to move to the master, delete the temporary commits and then merge the fix branch. Unfortunately when I run...
git reset --hard HEAD^^
...I was on the wrong branch. The good commits were gone. Panic!

Where Have They Gone?

What I've learned from this experience is that deleted commits are still there, at least until you run git gc or git prune. The problem is to find a way to bring them back. What I did at the time was to use grep to search for the commit message under the directory .git of the repository.

In this way I've discovered that in the directory .git/logs/refs/<branch-name>, in the logs are also recorded the hashes for every commit. With hashes it has been easy to checkout the second commit (going in a 'detached HEAD' state) and verify that nothing was missing.

At that point, I've created a new branch (with git checkout -b new_fix) and carefully executed the original plan, this time without surprises.
I love it when a plan comes together!
- John "Hannibal" Smith

Bitwise Right Shift With Signed Int

It's never too late to learn something new. And sometimes the compiler is smarter than what you think. In this case, I've discovered this behavior a couple of weeks ago while I was working on a C function that manipulates the bits of an unsigned integer variable using the right-shift operator (>>).

The function worked pretty well, until someone wanted to use it with signed integers, too. The issue here is that the sign bit (the leftmost one) is replaced with zero when right-shifting. The solution has been absurdly easy: cast to signed int.

This is an example with 8 bits:
Bitwise right shift with unsigned and signed int
The right shift produces different results depending on the signedness of the variable

Too Good To Be True?

This is wonderful but there is a little thing to consider when you plan to use it. According to the C standard:
1196 If E1 has a signed type and a negative value, the resulting value is implementation-defined.
This means that every compiler can behave in its own way and, worst of all, for different architectures the same compiler can act differently. So, what's the solution?

There isn't just one, of course. The following function should work pretty fine in all cases, but I'm sure you can find at least another algorithm to do the same thing.
int right_shift_with_sign(int var, unsigned int amount)
{
        int mask = (var < 0) ? -1 >> amount : -1;
        int result = var >> amount;
        return (result | ~mask);
}
[Code not fully tested - use it at your own risk]

If you need way to detect how your compiler behave, you can use this short snippet:
if ((-1 >> 2) == -1) {
        /* right shift preserves the sign */
} else {
        /* right shift DOES NOT preserve the sign */
}

[Linux] How to Define a Path for Shared Objects

In Linux, the predefined paths for shared objects (.so) are /lib/ and /usr/lib/. During normal usage this is OK but sometimes it's necessary to specify other additional paths. There are several way to do this task.

The most common way is to define an environment variable in the shell:

$ export LD_LIBRARY_PATH=/path/to/shared/objects

This operation must be done in every new shell/terminal emulator before starting the process that needs the shared objects located there. To avoid this, you can insert the export in your .bashrc/.profile file (located in your home folder): in this way the variable is set every time you log in.

But sometimes you need a shared object to be called from daemons or processes run by various users. The solution is the file /etc/ld.so.conf. In this file you can add all the paths where shared object should be searched.

In Debian-based distros (like Ubuntu), this file only include the following line:

include /etc/ld.so.conf.d/*.conf

This means that you don't have to change this file but it's sufficient to add your paths to a file with extension .conf located in the directory /etc/ld.so.conf.d/

Pay attention! Adding directories not owned by root can be a security breach because it can lead to the execution of arbitrary code.

6 + 1 Tips to Write Easily Extensible Code

Disclaimer: the following tips apply to programs
more complex than this
As promised some weeks ago in the post The 0 Ohm Resistor, here there are some tips to follow while developing a new project that will make your life easier when someone will ask you to add some features in a short time.

If you are working in a team, these suggestions will also help your colleagues to understand your code and make changes autonomously.

1. Use Defines

This is a quite common suggestion but, believe me or not, I've seen several programs using numeric constants. Basically, every time you declare a fixed size array or set a limit, you should create a define. In this way it'll be faster to change it when requirements evolve. Unfortunately, not every programming language supports defines or constants; in such cases, the fastest way is to use variables with all uppercase names.

2. Use Getters and Setters

For object oriented languages, it's not a good idea to directly expose members. Even for simple things it's preferable to use functions to change a member or to retrieve a value in order to hide the implementation of your classes. So, in the future, you may change an integer to an iterator without impacting the existing code. The exception is Python, where properties should be used, but only because they do the same thing: implementation hiding.

3. Write Small Functions that Do a Single Thing

There's nothing worst than having a function one-thousand lines long. It's hard to understand and almost impossible to change it's behavior without rewriting it. With small functions it's easier to change some details or add something in the right place without being frightened of hidden side effects.

Corollary: unless a heavy optimization is needed, don't write code that is hard to understand for an average programmer. If you cannot avoid tricky code, remember to comment very well what it is supposed to do.

4. Prefer Text Files Where Possible

Unless you are working on systems with limited speed or storage, consider using text files to save (and load) configurations, logs and everything else. Binary structures are faster to read and write but every time you'll change the format, you need to update several part of your code. Moreover, the debug is easier and, in case of troubles, an handmade change can save you time and money.

5. Use Names Not Related to the Solution

It is easy to call a class ArrayValues, and it seems reasonable, too. But it's wrong. Again there is a problem with implementation hiding but not only. If one day the array has to be changed in a hash table, what would you do? Keep the name even if it's wrong or change it everywhere in your project? The key point here is that the solution you have found to solve a problem may vary in the future, so it's better that names of variables, functions, classes, etc. reflect a something that will never change: the problem to solve.

6. Avoid Code Duplication

Trivial tip, don't you think? But you won't believe how many times I've seen two (or sometimes three) functions doing almost the same thing in the same project. The reason is simple, who added the second function didn't know (or didn't remember) that a task is already performed by a function already present. In some cases it's the fear that makes someone say: "Maybe if I add this little feature I can add bugs to something that it's working. No, it's better to create my own function". The result is that for every subsequent change there is the risk to not consider every implication.

Bonus Tip - Do the Refactoring

This is something that is (hopefully) not necessary at first, but as the modifications start to accumulate, take some time to review the changes and harmonize them into the structure of your program. In this way, in the future you can keep adding features on a structure that is stable and well implemented.