Git – examples of use

Effective and uncomplicated work on Git requires more than just knowing commands, which can be found in the documents anyway. You have to understand the philosophy supporting this powerful tool. So, to start, here is some useful theoretical information.

What is Git and how does it store data

Git is simply a database which stores project statuses at successive stages. Git does not save differences in the files, it saves the snapshot of the entire project. This snapshot, also known as the commit or the revision, is identified through its control sum SHA-1. Git’s commands are usually more or less visibly working on revisions: comparing them, merging them, synchronising the working folder with them, etc.

Change history, branches

Each commit contains a reference to its predecessor; therefore, the set of consecutive revisions forms a chain. This approach allows for structuring the entire change history stored in Git.
cs-git-sceariusze-zycia-creativestyle

The diagram above presents a change history composed of two commit chains and three indicators. The revision chain constitutes a branch, if its element is referred to by a named indicator (in this case feature or master). The chain is the active branch, if this element is additionally indicated by HEAD – in this case, the working catalogue is synchronised with the revision corresponding to the indicator (i.e. all changes to the files are followed in relation to it).

Determining revisions and revision scopes

When working on Git, we can refer to the revision in various ways:

  • directly through SHA-1 (np. f8519fd29909ed37b1e83fad6f259d3e6ed9a9ae)
  • directly through shortcut SHA-1 (np. 6bc603b), if it is explicit
  • through HEAD indicator and the indicators representing the branches (np. master, origin/master)
  • relatively to the indicators (np. HEAD^, HEAD~3, master~1)
  • referring to future indicator statuses (HEAD@{3}, master@{yesterday}, master@{<date>})
  • and through ranges (master..HEAD, origin/master..master, feature…master)

Change history modification

Work on advanced functions produces the problem of control of the already created code: it is easy to get lost in the changes or accidentally overwrite or delete something. This problem can be solved by frequent committing of even the smallest changes – such “partial” commits are perfect security points if something should go wrong.

It should be noted that Git’s main advantage in comparison to centralised version control systems is its ability of local work on private branches and random reediting of the change history – as long as they have not been submitted to the public repository. Here is a sample history of changes to a local branch:

As you can see, the work was described in a rather loose style during its individual stages, which is not necessarily permitted in the main repository. Furthermore, let’s assume that the team’s established rules require covering the entire functionality in the smallest number of commits described in English. The rebase command with the interactive option comes in handy:

In this case, the command will allow us to rewrite the entire history from the place in the master branch where the feature branch begins (we could also have referred to any other revision from this branch). The interactive rebase allows for operations such as commit merging, reordering, editing their descriptions, and so on. After reediting, the history may appear as follows:

In its current form, the feature branch can be merged with the master branch and submitted to the public repository.

Using a code from another branch

Now, let’s assume that the feature branch was for some reason unable to be merged at this time. Meanwhile, we want to execute the refactoring of foo and bar classes (i.e. commit fed82d9) in the master. The following command will come in handy:

It will apply the given commit in the master branch. But what if we only want to use the changes from a single file? We can use the checkout command:

The bar.class file has been placed in the staging area and will be taken into consideration in the next commit.

But why did we use a command which changes the branch? While the branch change is the most frequent reason for the use of checkout, this is a special case. In general, this command is used to synchronise the files in the working catalogue with their status in a given revision.
Use of a file fragment

We may also have made plenty of changes on unrelated subjects (whatever this may mean in the context of one file ;)) or we just want to make the commit with certain changes as soon as possible and tend to the rest later. In such cases, the easiest solution is the –p switch of the add command:

Git will allow us to accept the code blocks to be used in the next revision step by step. You can check the effect with the following command:

The result will be two file versions with different changes – the version with the blocks accepted by us will be located in the staging area, while the other blocks will remain in the modified file in the working catalogue.

Branch status history

I previously noted that we can refer to the revision through the past status of the indicators (HEAD or branch). Caution: the history of indicator status changes is not the same as the history of project changes, which is represented by the revision chain. To illustrate this problem, let’s imagine the merger of the feature branch with the master branch, which would result in the master “receiving” three commits – but its status would change only once. To open the history of the changes of a given branch (in other words, revisions which were referred to by the indicator representing them during the work), we use the reflog command:

We obtain the history of other branches in a similar manner. The reflog command with no parameters will show the history of the changes of the HEAD indicator. We refer to the branch statuses through references such as: master@{1}, master@{2}, feature@{4}, HEAD@{10}, as well as time-related descriptions: master@{yesterday} or master@{2 months ago}. For example, the command:

will provide the image of the single file version from that time.

In practice, this way of referring to revisions is usually used to recover the status of the branch after an unsuccessful command changing its history (e.g. merge or rebase). The command:

is sufficient to return the branch to its state from before the unfortunate change.

Detached HEAD

In conclusion, let’s assume that we want to test the project’s operation quickly or view the entire code of a specific revision. To do this, we can use the checkout command, e.g.:

These commands take us to the “detached HEAD” mode, which means that we are currently not in any branch, but the working catalogue has been synchronised with the revision determined by the parameter.

Bibliography:

For more information on Git’s data storage mechanisms, work with branches, possible ways of referring to revisions, and use of the rebase command, consult the textbook and the documentation:

pluswerk