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:

    1. git commit ... - incorrect commit;

    2. git reset --soft HEAD ^ - go to work on an already committed commit,
      saving all the project status and indexed files
    3. edit WRONGFILE

    4. edit ANOTHERWRONGFILE

    5. 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 :

    1. git-rebase master topic - an option that explicitly indicates what and where to
      apply.
    2. 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:

    1. mkdir git-demo

    2. cd git-demo

    3. git init

    4. git add.

    5. git commit -m "initial commit"

    6. git branch new-feature

    7. git checkout new-feature

    8. git add.

    9. git commit -m "Done with the new feature"

    10. git checkout master

    11. git diff HEAD new-feature

    12. git merge new-feature

    13. git branch -d new-feature

    14. 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 ?

    1. git clone http://yourserver.com/~you/proj.git
      ... maybe some time has passed.

    2. git pull

    3. git diff HEAD ^

    4. git checkout -b bad-feature
      ... working for a while.

    5. git commit -a -m «Created a bad feature»

    6. git checkout master

    7. git pull

    8. git merge bad-feature

    9. git commit -a

    10. git diff HEAD^
      … запускаем тесты проекта, обнаруживаем, что где-то произошла ошибка. Упс.


    11. git reset --hard ORIG_HEAD

    12. git checkout bad-feature
      … исправляем ошибку.

    13. git -m bad-feature good-feature

    14. git commit -a -m «Better feature»

    15. git checkout master

    16. git pull

    17. git merge good-feature

    18. git push

    19. 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.

    Also popular now: