Git wizardry
1. Introduction
In my last post, I tried to outline in general terms the style of working with the
distributed git version control system and point out the differences compared to the
classic centralized hard currency. The goal was, first of all, to generalize the experience of
working with the system without mentioning the subtleties of the syntax of individual commands.
This topic was conceived as a direct introduction to working with git, a cross
between tutorial and generalized help, to which it is still recommended
to read the introduction mentioned above. The technical
details of git are deliberately avoided , only terms common to SLE are used, and the
list of commands mentioned is limited.
2 Working with the local repository
The strength of any distributed systems is that every developer has a local
repository in which it is possible to organize an arbitrary personal
development scheme . In git, there are several basic commands for doing work in place and a
lot of helper ones.
2.1 Basic commands
The basic commands are those that are impossible to do without in development.
2.1.1 git init - creating a repository
The git init command creates an empty repository in the directory in the form of the
.git directory , where all information on the history of commits,
tags will be stored in the future - project development:
mkdir project-dir
cd project-dir
git init
Another way to create a repository is the git clone command but about her a little later.
2.1.2 git add and git rm - change indexing
The next thing to know is the git add command. It allows you to make changes to the index — temporary storage — that will then be included in the commit. Examples of
use:
git add EDITEDFILE - indexing a changed file, or a notification about the
creation of a new one.
git add. - make all changes to the index, including new files.
You can delete a file from the index and the project tree at the same time with the git rm command:
git rm FILE1 FILE2 - individual
git rm files Documentation / \ *. Txt is a good example of deletion from the git documentation,
all txt files from the folder are deleted immediately.
You can reset the entire index or remove changes to a specific file from it
with the git reset command:
git reset - reset the whole index.
git reset - EDITEDFILE - delete a specific file from the index.
The git reset command is used not only to reset the index, so
more attention will be paid to it further.
2.1.3 git status - project status, modified and not added files, indexed files
The git status command can perhaps be considered the most frequently used along with
commit and indexing commands. It displays information about all changes
made to the project directory tree compared to the last commit of the work
branch; Separate indexed and non-indexed
files are displayed separately . Using it is extremely simple:
git status
In addition, git status indicates files with unresolved merge conflicts and
files ignored by git.
2.1.4 git commit - commit
Committing is a basic concept in all version control systems, so
it should be done easily and as quickly as possible. In its simplest form, it’s enough to type
after indexing:
git commit
If the index is not empty, then a commit will be made on its basis, after which the
user will be asked to comment on the changes made by calling the
edit command (for example, in Ubuntu a simple text editor nano is usually called, I
have - emacs). Save, and veil! The commit is ready.
There are several keys that simplify working with git commit:
git commit -a - commits, automatically indexing changes to
project files . New files will not be indexed ! Delete files
will be taken into account.
git commit -m “commit comment” - comment on the commit directly from the command line
instead of a text editor.
git commit FILENAME - will index and create a commit based on changes to a
single file.
2.1.5 git reset - return to a specific commit, rollback changes, “hard” or “soft”
In addition to working with the index (see above), git reset allows you to reset the state of a
project to a commit in history. In git, this action can be of two
types: “soft” (soft reset) and “hard” (hard reset).
“Soft” (with the “--soft” key) cuts will leave your index and the whole
project tree and directory tree intact , will return to work with the specified commit. In other
words, if you find an error in a commit just made or
comments on it, then you can easily fix the situation:
- git commit ... - incorrect commit;
- git reset --soft HEAD ^ - go to work on an already committed commit,
saving all the project status and indexed files - edit WRONGFILE
- edit ANOTHERWRONGFILE
- add.
6.1. git commit -c ORIG_HEAD - return to the last commit, you will be prompted to
edit his message. If the message is left unchanged, it is
enough to change the key register -c:
6.2. git commit -C ORIG_HEAD
Note the notation HEAD ^, it means "refer to the ancestor of the
last commit." The syntax of such relative addressing
will be described in more detail below in the section "Hashes, Tags, Relative Addressing". Accordingly,
HEAD is a link to the last commit. The link ORIG_HEAD after the soft cut
indicates the original commit.
Naturally, you can return to a greater depth of commits,
“Hard” rezet (--hard key) is a command that should be used with
caution. Git reset --hard will return the project tree and index to the state
corresponding to the specified commit, deleting changes to subsequent commits:
git add.
git commit -m "destined to death"
git reset --hard HEAD ~ 1 - no one else will ever see this shameful commit.
git reset --hard HEAD ~ 3 - or rather, the last three commits. No one. Never.
If the command reaches the branch point, the commit will not be deleted.
For merging or pumping out the latest changes from the remote repository,
examples of rezet will be given in the corresponding sections.
2.1.6 git revert - discard changes made in the past by a separate commit
There may be a situation in which you want to undo the changes made by a separate
commit. Git revert creates a new commit, applying the opposite changes:
git revert config-modify-tag - cancel the commit marked with the tag.
git revert 12abacd - cancel the commit using its hash.
To use the team, it is necessary that the state of the project does not differ from the
state recorded by the last commit.
2.1.7 git log - a variety of information about commits in general, for individual files and various depths of immersion in history
Sometimes you need to get information about the history of commits, commits that have changed a
single file; commits for a certain period of time and so on. The
git log command is used for these purposes.
The simplest example of use, which provides a brief reference on all the
commits that touched the currently active branch (for more information on branches and branching
, see the section "Branching and merging" below):
git log
Get detailed information about each in the form of file patches from commits,
you can add the -p (or -u) switch:
git log -p
Statistics of file changes, such as the number of changed files,
lines inserted in them , deleted files are called with the --stat switch:
git log --stat
The
--summary key is responsible for the information on creating, renaming and file permissions:
git log --summary
To examine the history of a single file, just specify
its name as a parameter (by the way, in my old version of git this method does not work,
be sure to add "- "before" README "):
git log README
or, if the version of git is not entirely up to date:
git log - README The
following is only a more modern version of the syntax. It is possible
to indicate the time starting at a certain point ("weeks", "days", "hours", "s"
and so on):
git log --since = "1 day 2 hours" README
git log --since = "2 hours »README
git log --since = "2 hours" dir / - changes concerning a separate folder.
You can build on tags:
git log v1 ... - all commits, starting with the v1 tag.
git log v1 ... README - all commits that include changes to the README file, starting with the
v1 tag.
git log v1..v2 README - all commits that include changes to the README file, starting with the
v1 tag and ending with the v2 tag.
Creating, listing, assigning tags will be given in the appropriate
section below.
The --pretty switch provides interesting opportunities for the format of the command output:
git log --pretty = oneline - displays on each of the
commits a line consisting of a hash (here is a unique identifier for each commit, more about this later).
git log --pretty = short - concise information about commits, only the
author and comment are given
git log --pretty = full / fuller - more complete information about commits, with the
author name , comment, date of creation and making of the commit
In principle, the output format you can define it yourself:
git log --pretty = format: 'FORMAT' You
can find the format definition in the git log section of the Git Community Book
or in the help. A beautiful ASCII commit graph is displayed using the
--graph option.
2.1.8 git diff - differences between project trees; commits; the state of the index and any commit.
A kind of subset of the git log command can be considered the git diff command,
which defines changes between objects in the project: trees (files and
directories):
git diff - shows changes not made to the index.
git diff --cached - changes made to the index.
git diff HEAD - changes in the project compared to the last commit
git diff HEAD ^ - the penultimate commit
You can compare the "heads" of the branches:
git diff master..experimental
Well, or the active branch with any:
git diff experimental
2.1.9 git show - show changes made by a separate commit
You can see the changes made by any commit in history with the git
show command :
git show COMMIT_TAG
2.1.10 git blame and git annotate - helper commands that help you track file changes
When working in a team, you often need to find out exactly who wrote the specific
code. It is convenient to use the git blame
command, which displays line-by-line information about the last commit that touched the line, the author’s name and the hash of the commit:
git blame README
You can also specify specific lines for display:
git blame -L 2, + 3 README - displays information on three lines, starting with the second.
The git annotate command works in a similar way, displaying both lines and information about the
commits that touched them:
git annotate README
2.1.11 git grep - search for words by project, project status in the past
git grep, in general, simply duplicates the functionality of the famous Unix
team. However, it allows you to search for words and their combinations in the past of the project, which
is very useful:
git grep tst - search for the word tst in the project.
git grep -c tst - count the number of references to tst in a project.
git grep tst v1 - search in the old version of the project.
The command allows you to use the logical AND and OR:
git grep -e 'first' --and -e 'another' - find lines where both the first
word and the second are mentioned .
git grep --all-match -e 'first' -e 'second' - find lines where at
least one of the words occurs .
2.2 Branching
Branching and merging operations are the heart and soul of git, it is these features that make
working with the system so convenient.
2.2.1 git branch - create, enumerate and delete branches
Working with branches is a very easy procedure in git, all the necessary mechanisms are
concentrated in one command:
git branch - just list existing branches, marking the active one.
git branch new-branch - will create a new branch new-branch.
git branch -d new-branch - delete a branch if it was merged with the
resolution of possible conflicts in the current one.
git branch -D new-branch - remove the branch anyway .
git branch -m new-name-branch - rename the branch.
git branch --contains v1.2 - will show those branches among whose ancestors there is a
certain commit.
2.2.2 git checkout - switching between branches, extracting individual files from the commit history
The git checkout command allows you to switch between the last commits (if
simplified) of branches:
checkout some-other-branch
checkout -b some-other-new-branch - will create a branch into which
switching will occur .
If there were any changes in the current branch compared to the last commit in the
(HEAD) branch, the team will refuse to switch so as not to lose the
work done. The -f switch allows to ignore this fact:
checkout -f some-other-branch
If you still need to save the changes, use the -m switch. Then,
before switching, the team will try to upload the changes to the current branch and, after
resolving possible conflicts, switch to a new one:
checkout -m some-other-branch A
command like:
git checkout somefile - return somefile to the state of the last commit
git checkout HEAD ~ 2 somefile - return somefile to the state two commits back to the branch to return the file (or just pull it from the last commit) .
2.2.3 git merge - merging branches (resolving possible conflicts).
Merging branches, unlike the usual practice of centralized systems, in git
happens almost every day. Naturally, there is a convenient interface to the
popular operation:
git merge new-feature - will try to combine the current branch and the new-feature branch.
In the event of conflicts, a commit does not occur, and
special tags are placed on problem files a la svn; the files themselves are marked in the index as
“unmerged”. Until the problems are resolved, the commit
cannot be committed .
For example, a conflict arose in the TROUBLE file, which can be seen in git status:
git merge experiment - a failed merge attempt occurred.
git status - look at problem areas.
edit TROUBLE - solve problems.
git add. - index our changes, thereby removing tags.
git commit - commit a merge commit.
That's all, nothing complicated. If during the resolution process you change your mind about resolving the
conflict, just type:
git reset --hard HEAD - this will return both branches to their original state.
If the merge commit was committed, use the command:
git reset --hard ORIG_HEAD
2.2.4 git rebase - building a flat commit line
Suppose a developer has started an additional branch to develop a separate
feature and made several commits in it. At the same time, for some
reason, commits were also committed in the main branch: for example,
changes from a remote server were uploaded to it ; or the developer himself made
commits in it .
In principle, you can do with the usual git merge . But then the
development line becomes more complicated , which is undesirable in projects that are too large, where
many developers are involved .
Suppose there are two branches, master and topic, in each of which there were quite a few commits from the moment of branching.
The git rebase command takes the commits from the topic branch and imposes them on the last commit of the
master branch :
- git-rebase master topic - an option that explicitly indicates what and where to
apply. - git-rebase master - the currently active
branch is superimposed on master .
After using the command, the story becomes linear. If
conflicts arise during alternate imposing of commits
, the team will stop working, and
corresponding marks will appear in problem areas of the files . After editing - conflict resolution - the files
should be added to the index with the git add command and continue to overlay the following
commits with the git rebase --continue command. Alternative outputs are
git rebase --skip (skip overlay commit and go to the next) or git
rebase --abort (cancel the command and all changes made).
With the -i switch (--interactive), the command will work interactively
. The user will be given the opportunity to determine the order of application
changes will automatically call the editor to resolve conflicts and so
on.
2.2.5 git cherry-pick - apply changes made by a separate commit to the project tree
If you have a complicated development history, with several long
development branches , you may need to apply the changes made by a
separate commit of one branch to the tree of the other (currently active).
git cherry-pick BUG_FIX_TAG - changes made by the specified commit will be
applied to the tree, automatically indexed and become a commit in the active
branch.
git cherry-pick BUG_FIX_TAG -n - the "-n" switch indicates that the changes
just need to be applied to the project tree without indexing and creating a commit.
2.3 Other teams and necessary features
For the convenience of working with git, an additional concept was introduced: tag. In addition, the
need for hashes and its application will be further explained; shows a way
to access commits using relative addressing.
2.3.1 Hash - Unique Object Identification
In git, a unique (that is,
most likely unique) 40-character hash is used to identify any objects , which is determined by a
hash function based on the contents of the object. Objects are everything: commits,
files, tags, trees. Since the hash is unique to the contents of, for example, a file,
comparing such files is very easy - just compare two lines
of forty characters.
What interests us most is the fact that hashes identify commits. In this
sense, a hash is an advanced analogue of Subversion revisions. Some examples of
using hashes as an addressing method:
git diff f292ef5d2b2f6312bc45ae49c2dc14588eef8da2 - find the difference of the current
the status of the project and the commit for the number ... Well, you yourself see how.
git diff f292ef5 is the same, but we leave only the first six characters. Git
will understand what commit is in question if there is no other commit with such a
hash start.
git diff f292 - sometimes four characters are enough.
git log febc32 ... f292 - read the log from commit to commit.
Of course, it’s not as convenient for a person to use hashes as a machine, which is why
other objects were introduced - tags.
2.3.2 git tag - tags as a way to mark a unique commit
A tag is an object associated with a commit; storing a link to the commit itself,
author name , own name and some comment. In addition, the developer can
leave his own digital signature on such tags.
In addition, the so-called “lightweight tags”
, consisting only of a name and a link to a commit, are presented in git . Such tags are typically
used to simplify navigation through the history tree; creating them is very easy:
git tag stable-1 - create a “lightweight” tag associated with the last
commit. If there is already a tag, then another will not be created.
git tag stable-2 f292ef5 - mark a specific commit.
git tag -d stable-2 - remove the tag.
git tag -l - list tags.
git tag -f stable-1.1 - create a tag for the last commit, replace the
existing one, if any.
After creating the tag, its name can be used instead of the hash in any commands
like git diff, git log, and so on:
git diff stable-1.1 ... stable-1 It
makes sense to use ordinary tags to attach some
information to the commit , such as version number and commentary on it. In other words, if in the
comment to the commit you write “fixed such and such a bug”, then in the comment to the tag
named “v1.0” there will be something like “stable version, ready to use”:
git tag -a stable - create regular tag for the last commit; A
text editor will be called up to compose a comment.
git tag -a stable -m “production version” - create a regular tag by immediately specifying
a comment as an argument.
The enumeration, deletion, and rewrite
commands for regular tags are no different from the commands for "lightweight" tags.
2.3.3 Relative Addressing
Instead of revisions and tags, you can rely on another
mechanism as the name of the commit - relative addressing. For example, you can go directly to the ancestor of the
last commit of the master branch:
git diff master ^
If you put a number after the “bird”, you can address several ancestors of
merge commits :
git diff HEAD ^ 2 - find changes compared to the second ancestor of the last
commit in master . HEAD here is a pointer to the last commit of the active branch.
Similarly, you can simply indicate with a tilde how deeply
you need to dive into the history of the branch :
git diff master ^^ - what the "grandfather" of the current commit brought.
git diff master ~ 2 is the same.
Symbols can be combined to get to the desired commit:
git diff master ~ 3 ^ ~ 2
git diff master ~ 6
2.3.4 .gitignore file - explain to git which files should be ignored
Sometimes there are files in the project directories that you do not want to
see constantly in the git status summary. For example, helper files for text
editors, temporary files and other garbage.
You can ignore git status by creating
a
.gitignore file in the root or deeper in the tree (if the restrictions should only be in certain directories) . In these files, patterns of ignored files of a
specific format can be described .
An example of the contents of such a file:
>>>>>>> beginning of the file
# comment on the .gitignore file
# ignore .gitignore
.gitignore
# all html-files ...
* .html
# ... except for a certain
! Special.html
# Objects and archives are not needed
*. [ao]
>>>>>>>> End of file
There are other ways to specify ignored files, which can be found
in git help gitignore help.
3 “Together we are power”, or the basics of working with a remote repository
Naturally, most of the projects still involve the work of at
least two developers who need to exchange code. The following will
list the commands required to work together - possibly remotely -.
3.1 remote tracking branches
A new concept here is remote branches. Remote branches correspond to some
branch (usually master) on the remote server. One such one is created automatically when you
create a copy of the remote repository; all commands related to remote
work will by default use this particular remote branch (usually
called “origin”).
Consider these commands.
3.2 git clone - making a copy of a (remote) repository
To start working with the central repository, you should create a copy of the
original project with all its history locally:
git clone / home / username / project myrepo - clone the repository from the same machine
into the myrepo directory.
git clone ssh: // user @ somehost: port / ~ user / repository - clone the repository
using the secure ssh protocol (for which you need to have an
ssh account on your machine ).
git clone git: // user @ somehost: port / ~ user / repository / project.git / - git also has
its own protocol.
3.3 git fetch and git pull - pull changes from the central repository (from a remote branch)
The git fetch and
git pull commands are used to synchronize the current branch with the repository .
git fetch - pick up changes to the remote branch from the default repository, the
main branch; the one that was used to clone the
repository. The changes will update the remote branch (remote tracking branch), after
which it will be necessary to merge with the local branch with the git merge command.
git fetch / home / username / project - pick up changes from a specific
repository.
It is also possible to use synonyms for addresses created by the git remote command:
git remote add username-project / home / username / project
git fetch username-project - pick up the changes at the address defined by the
synonym.
Naturally, after evaluating the changes, for example, with the git diff command, you need
to create a merge commit from the main one:
git merge username-project / master
The git pull command immediately picks up the changes and merges with the active branch:
git pull - pick from the repository for which remote branches were created by
default.
git pull username-project - pick up changes from a specific repository.
As a rule, the git pull command is used immediately.
3.4 git push - make changes to the remote repository (remote branch)
After working in the experimental branch, merging with the main branch,
you need to update the remote repository (remote branch). To do this,
use the git push command:
git push - send your changes to the remote branch created during
cloning by default.
git push ssh: //yourserver.com/~you/proj.git master: experimental - send changes
from the master branch to the experimental branch of the remote repository.
git push origin: experimental - in the remote origin repository, delete the experimental branch.
git push origin master: master - to the remote master branch of the origin repository (
default synonym for the repository) of the branch of the local master branch.
4 git-o-day
In this section, several common and slightly
less unusual situations for working with git will be shown and analyzed in detail .
4.1 Normal workflow when working with a local repository
Git has extraordinary ease of use not only as a distributed
version control system, but also in working with local projects. Let's take a look at the
usual cycle - starting with creating a repository - the work of the git developer on
his own personal project:
- mkdir git-demo
- cd git-demo
- git init
- git add.
- git commit -m "initial commit"
- git branch new-feature
- git checkout new-feature
- git add.
- git commit -m "Done with the new feature"
- git checkout master
- git diff HEAD new-feature
- git merge new-feature
- git branch -d new-feature
- git log --since = "1 day"
We will analyze each of the actions. 1-2 - just create the working directory of the
project. 3 - create a repository in the directory. 4 - index all existing
project files (if, of course, they were at all). 5 - create an initializing
commit. 6 - a new branch, 7 - switching to it (can be done in one step
with the git checkout -b new-feature command). Then, after working directly with the
code, index the changes made (8), commit (9). We switch to the
main branch (10), look at the differences between the last commit of the active branch and the
last commit of the experimental (11). We merge (12) and, if there were
no conflicts, delete the branch that is no longer needed (13). Well, just in case
we evaluate the work carried out over the last day (14).
Why so? Why abandon the linear model? If only because
the programmer has additional flexibility: he can switch
between tasks (branches); always at hand is the “clean” - the
master branch ; commits get smaller and more accurate.
4.2 Workflow when working with a remote repository
Suppose that you and several of your partners created a public
repository in order to take up some kind of common project. What does the most
common work model for git look like ?
- git clone http://yourserver.com/~you/proj.git
... maybe some time has passed. - git pull
- git diff HEAD ^
- git checkout -b bad-feature
... working for a while. - git commit -a -m «Created a bad feature»
- git checkout master
- git pull
- git merge bad-feature
- git commit -a
- git diff HEAD^
… запускаем тесты проекта, обнаруживаем, что где-то произошла ошибка. Упс. - git reset --hard ORIG_HEAD
- git checkout bad-feature
… исправляем ошибку. - git -m bad-feature good-feature
- git commit -a -m «Better feature»
- git checkout master
- git pull
- git merge good-feature
- git push
- git branch -d good-feature
So, first of all, create (1) create a copy of the remote repository (by
default, commands like git pull and git push will work with it). “Pull” the
latest updates (2); look what has changed (3); create a new branch and
switch to it (4); index all changes and at the same time create a
commit from them (5); switch to the main branch (6), update it (7); we
merge with the bad-feature branch (8) and, having discovered and resolved the conflict, we commit the
merge (9).
After committing, we track changes (10), run, for example,
unit tests and find with horror that after the merge, the project falls on most
of the tests.
In principle, the tests could be run before the commit, at the time
mergers (between paragraphs 8 and 9); then a “soft” cut would be enough.
Thus, it is necessary to perform a “hard” (11) reset of the merge that occurred, the
branches returned to their original state. Then we switch to the unsuccessful
branch (12), make the necessary changes and rename the branch (13). Commit
commit (14); go to the main branch (15), update it again (16). This time
we merge seamlessly (17), drop the changes to the remote repository
(18) and delete the branch that is no longer needed (19). We close the laptop, dress and go
home in the morning.
5 Conclusion
The topic has not addressed several important issues, such as administering a
public repository, integrating with text editors or IDEs,
using SSH under Linux and Windows; comments are welcome, and especially the
additions to the git-o-day section.
UPD: a note on working with a remote repository. When working with git in terms of administration, creating public repositories, access control, using encrypted connections, and so on, certain issues may arise; moreover, in this note they are not covered in any way. For example, the above describes working with a ready-made remote repository; its deployment is not covered in any way.
All this I am now collecting in a separate topic, which I will throw here in a week and a half.