📜 ⬆️ ⬇️

Daily work with Git

I do not study for a long time and use git almost everywhere where possible. However, during this time I managed to learn a lot and want to share my experience with the community.

I will try to bring the main ideas, to show how this VCS helps to develop a project. I hope that after reading you will be able to answer the questions:


Of course, I will try to tell everything in order, starting with the basics. Therefore, this article will be extremely useful to those who are just starting or want to deal with git. More experienced readers may find something new for themselves, point out mistakes or share advice.
')


Instead of a plan


Very often, in order to start something, I study a whole bunch of materials, and these are different people, different companies, different approaches. All this requires a lot of time for analysis and for understanding whether something will suit me? Later, when it comes to the understanding that there is no universal solution, there are completely different requirements for the version control system and for development.

So, I will highlight the main steps:


Environment



For work we need:
  1. Git
  2. Console
  3. The man on the other side of the monitor, who will be able to put it all under his favorite axis

At the moment, my environment is Debian + KDE + Git + Bash + GitK + KDiff3.
If you find Windows on your computer, then you will most likely have Windows + msysgit (git-bash) + TortoiseGit, etc.

If you open the console, write git and get this:
git help
 usage: git [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path] [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>] [-c name=value] [--help] <command> [<args>] The most commonly used git commands are: add Add file contents to the index bisect Find by binary search the change that introduced a bug branch List, create, or delete branches checkout Checkout a branch or paths to the working tree clone Clone a repository into a new directory commit Record changes to the repository diff Show changes between commits, commit and working tree, etc fetch Download objects and refs from another repository grep Print lines matching a pattern init Create an empty git repository or reinitialize an existing one log Show commit logs merge Join two or more development histories together mv Move or rename a file, a directory, or a symlink pull Fetch from and merge with another repository or a local branch push Update remote refs along with associated objects rebase Forward-port local commits to the updated upstream head reset Reset current HEAD to the specified state rm Remove files from the working tree and from the index show Show various types of objects status Show the working tree status tag Create, list, delete or verify a tag object signed with GPG See 'git help <command>' for more information on a specific command. 


So you are ready.

Stop being afraid to experiment



Surely, most of the teams have already been peeped somewhere, some articles have been read, you want to start, but you are afraid to enter the wrong team or break something. Or maybe nothing else has been learned. Then just remember this:
You can do anything, execute any commands, set up experiments, delete, change. The main thing is not to do git push .
Only this command transfers changes to another repository. This is the only way to break something.

Strictly speaking, even a failed git push can be fixed.

Therefore, you can safely clone any repository and start exploring.

Building repositories



First of all, you need to understand what a git-repository is? The answer is very simple: it is a set of files. Folder `.git`. It is important to understand that this is only a set of files and nothing else. 20 times I saw a problem with my colleagues with authorization in github / gitlab. Thinking that this is part of the git system, they tried to look for a problem in the git configuration, to call some git commands.

And if these are just files, then you need to somehow access them, be able to read from there and write there? Yes! I call it "transport." This may be incorrect, but it was convenient for me to remember. More correct option: "Data transfer protocol". The most common options are:
  1. FILE - we have direct access to the repository files.
  2. SSH - we have access to files on the server via ssh.
  3. HTTP (S) - we use http as reception / transmission.

There are more options. No matter which transport will be used, it is important that you have access to read or read / write files.
Therefore, if you just can not clone the repository with github, and there are no prompts in the logs, you may have a problem with the transport.

In particular, when cloning like this:
 git clone git@github.com:user/repo.git 

url "turns" into
 git clone ssh://git@github.com:user/repo.git 

Those. SSH is used and problems need to be looked for in it. As a rule, this is an incorrectly configured or not found ssh-key. It is necessary to google in the direction of “SSH Auth Key git” or, if completely adult, check what happens:
 ssh -vvv git@github.com 


What protocols are supported help (section GIT URLS):
 git clone --help 


The repository can be cloned, but first we will play with our own:
  1. Let's invent your remote repository
  2. Let's make two clones from it, on behalf of the developers (dev1 and dev2)



In addition to the repository itself, there is also a workspace where files with which you work are stored. It is in this folder is the repository itself (folder. Git). On the servers, working files are not needed, therefore only bare repositories (bare-repo) are stored there.

Let's make ourselves one (will be our main test repository):
 $ mkdir git-habr # ,    $ cd git-habr $ git init --bare origin Initialized empty Git repository in /home/sirex/proj/git-habr/origin/ 


Now we clone it on behalf of the developers. There is only one nuance that will not be with the server: git, knowing that the repositories are local and are on the same section, will create links, and not make a full copy. And we need a complete copy to learn. To do this, you can use the key - --no-hardlinks or explicitly specify the protocol:
 $ git clone --no-hardlinks origin dev1 Cloning into 'dev1'... warning: You appear to have cloned an empty repository. done. $ git clone --no-hardlinks origin dev2 Cloning into 'dev2'... warning: You appear to have cloned an empty repository. done. 


Bottom line: we have 3 repositories. There is nothing there, but they are ready to work.

GIT start




Scandals! Intrigue! Investigations!


You can continue to continue the list, but this is already enough to ask legitimate questions:
How does all this work?
How can all this be understood and remembered?


To do this, look under the hood. Consider everything in general terms.

Git Almost under the hood

Git saves the contents of all files (makes copies of the contents of each file and saves in objects). If the file has not changed, then the old object will be used. Thus, commit as new objects will get only changed files , which will allow to save disk space well and will enable you to quickly switch to any commit.

This allows you to understand why such funny things work here:
 $ git init /tmp/test Initialized empty Git repository in /tmp/test/.git/ $ cd /tmp/test $ cp ~/debian.iso . # iso  168  $ du -sh .git #   .git 92K .git $ git add debian.iso $ git commit -m "Added iso" [master (root-commit) 0fcc821] added iso 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 debian.iso $ du -sh .git #  163M .git # .      (   ) $ cp debian.iso debian2.iso $ cp debian.iso debian3.iso $ git add debian2.iso debian3.iso $ git commit -m "Copied iso" [master f700ab5] copied iso 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 debian2.iso create mode 100644 debian3.iso $ du -sh .git #  163M .git #   .     ,     . 


Yes, you should not store "heavy" files, binaries, etc. without obvious need. They will stay there forever and will be in every clone of the repository.

Each commit can have several commit ancestors and several daughter commits:


We can move (restore any state) to any point of this tree, or rather, the graph. To do this, use git checkout:
 git checkout <commit> 

Each merging of two or more commits into one is a merge (the union of two or more changesets).
Each branching is the appearance of several options for change.

By the way, here I would like to note that it is impossible to make a tag on a file / folder, on a part of a project, etc. The state is restored only entirely. Therefore, it is recommended to keep projects in a separate repository, rather than folding Project1, Project2, etc. just to the root.


Now to the branches. I wrote above:
Git has no branches * (with a small reservation)

It turns out that this is the case: we have many commits that form a graph. Choose any path from parent-commit to any child-commit and get the status of the project on this commit. To commit a “remember” you can create a named pointer to it.
Such a named pointer is a branch (branch). The same with tag. `HEAD` works on the same principle - shows where we are now. New commits are a continuation of the current branch (wherever HEAD looks).

Pointers can be freely moved to any commit if it is not a tag. Tag was made to remember the commit once and for all and never move anywhere. But you can delete it.
That's probably all you need to know from theory for the first time when working with git. The rest of the things should now seem more understandable.

Terminology

index - the area of ​​recorded changes, i.e. all that you have prepared for saving to the repository.
commit - changes sent to the repository.
HEAD - pointer to commit, in which we are.
master is the default branch name; this is also a pointer to a specific commit.
origin - the default name of the remote repository (you can give another)
checkout - take any status from the repository.

Simple edits

There are two things that you should always have on hand:
  1. git status
  2. gitk


If you did something wrong, confused, do not know what is happening - these two teams will help you.

git status - shows the status of your repository (working copy) and where you are.
gitk is a graphical utility that shows our graph. As keys, pass the names of the branches or - all to show all.

Let's return to our repositories that we created earlier. Further I will designate that one developer works in dev1 $, and the second in dev2 $.

Add a README.md:
 dev1$ vim README.md dev1$ git add README.md dev1$ git commit -m "Init Project" [master (root-commit) e30cde5] Init Project 1 file changed, 4 insertions(+) create mode 100644 README.md dev1$ git status # On branch master nothing to commit (working directory clean) 


Share with everyone. But since we cloned an empty repository, by default git does not know where to add the commit.
He will tell us this:
 dev1$ git push origin No refs in common and none specified; doing nothing. Perhaps you should specify a branch such as 'master'. fatal: The remote end hung up unexpectedly error: failed to push some refs to '/home/sirex/proj/git-habr/origin' dev1$ git push origin master Counting objects: 3, done. Writing objects: 100% (3/3), 239 bytes, done. Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] master -> master 


The second developer can get these changes by making a pull:
 dev2$ git pull remote: Counting objects: 3, done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From /home/sirex/proj/git-habr/origin * [new branch] master -> origin/master 


Add a couple more changes:
 dev1(master)$ vim README.md dev1(master)$ git commit -m "Change 1" -a dev1(master)$ vim README.md dev1(master)$ git commit -m "Change 2" -a dev1(master)$ vim README.md dev1(master)$ git commit -m "Change 3" -a 


Let's see what we did (run gitk):
Hidden text

Selected the first commit. Turning in order, from bottom to top, we can see how the repository has changed:
 @@ -2,3 +2,4 @@ My New Project -------------- Let's start +Some changes 

 @@ -3,3 +3,5 @@ My New Project Let's start Some changes +Some change 2 + 

 @@ -2,6 +2,5 @@ My New Project -------------- Let's start -Some changes -Some change 2 +Some change 3 



So far, we have added commits to the end (where master is ). But we can add another version of README.md. And we can do it from anywhere. Here, for example, we do not like the last commit and we try another option. Create a pointer at the previous point. To do this, through git log or gitk know the commit id. Then, create a branch and switch to it:
 dev1(master)$ git branch <branch_name> <commit_id> #    git branch <branch_name> HEAD~1 #     dev1(master)$ git checkout <branch_name> 


For those who love GUI, there is an even simpler option: select the desired commit with the right mouse button -> “create new branch”.
If you click on the appeared branch, there will be the item “check out this branch”. I called the branch "v2".
Let's make our test changes:
 dev1(v2)$ vim README.md dev1(v2)$ git commit -m "Ugly changes" -a [v2 75607a1] Ugly changes 1 file changed, 1 insertion(+), 1 deletion(-) 

It looks like this:


Now we understand how branches are created from any point and how their history changes.

Fast forward

Surely, you have already met the words fast-forward , rebase , merge together. It is time to deal with these concepts. I use rebase, someone only merge. That "rebase vs merge" a lot. Authors often try to convince them that their method is better and more convenient. We will go another way: understand what it is and how it works. Then it will immediately become clear which option to use in which case.

For now, within the same repository, let's branch: create a file, put it in the repository, create a new file from a new point, and try to combine everything into master:
 dev1(v2)$ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 3 commits. # ,      master   origin 

Create a file collider.init.sh with the following content:
 #!/bin/sh USER=collider case $1 in *) echo Uknown Action: $1 ;; esac 


Let's add, commit and start development in the new branch:
 dev1(master)$ git add collider.init.sh dev1(master)$ git commit -m "Added collider init script" [master 0c3aa28] Added collider init script 1 file changed, 11 insertions(+) create mode 100755 collider.init.sh dev1(master)$ git checkout -b collider/start #   Switched to a new branch 'collider/start' dev1(collider/start)$ git checkout -b collider/terminate #   Switched to a new branch 'collider/terminate' 

git checkout -b <branch_name> creates a pointer (branch) <branch_name> to the current position (the current position is tracked using the HEAD special pointer) and switches to it.
Or simpler: make a new branch from the current location and immediately continue with it.

Please note that in the name of the branch it is not forbidden to use the symbol '/', however, you need to be careful, because A folder with the name before '/' is created in the file system. If a branch with the same name as the folder exists, there will be a file system level conflict. If you already have a dev branch, you cannot create a dev / test .
And if there is a dev / test , then you can create dev / whatever , but you can't just dev .

So, we created two branches collider / start and collider / terminate . Confused? gitk --all to the rescue:

As you can see, we have 3 pointers (our branches) at one point, and the changes in the commit are:
 @@ -0,0 +1,11 @@ +#!/bin/sh + + +USER=collider + + +case $1 in + *) + echo Uknown Action: $1 + ;; +esac 

Now, in each branch we will write a code, which, accordingly, will launch and destroy our collider. The sequence of actions is approximately as follows:
 dev1(collider/start)$ vim collider.init.sh dev1(collider/start)$ git commit -m "Added Collider Start Function" -a [collider/start d229fa9] Added Collider Start Function 1 file changed, 9 insertions(+) dev1(collider/start)$ git checkout collider/terminate Switched to branch 'collider/terminate' dev1(collider/terminate)$ vim collider.init.sh dev1(collider/terminate)$ git commit -m "Added Collider Terminate Function" -a [collider/terminate 4ea02f5] Added Collider Terminate Function 1 file changed, 9 insertions(+) 

Changes made
collider / start
 @@ -3,8 +3,17 @@ USER=collider +do_start(){ + echo -n "Starting collider..." + sleep 1s + echo "ok" + echo "The answer is 42. Please, come back again after 1 billion years." +} case $1 in + start) + do_start + ;; *) echo Uknown Action: $1 ;; 


collider / terminate

 @@ -3,8 +3,17 @@ USER=collider +do_terminate() { + echo -n "Safely terminating collider..." + sleep 1s + echo "oops :(" + +} case $1 in + terminate) + do_terminate + ;; *) echo Uknown Action: $1 ;; 



As always, we’ll see in gitk --all what we’ve done:


The development is finished and now you need to give all the changes to master (there is an old collider, which can do nothing). Merging two commits, as mentioned above, is merge . But let's think about how the master branch differs from collider / start and how to get their union (sum)? For example, you can take common commits of these branches, then add commits related only to master , and then add commits related only to collider / start . And what about us? There are common commits, only master commits — no, only collider / start is. Those. merging these branches is master + commits from collider / start . But collider / start is master + commits of the collider / start branch! Same! Those. do not do anything! Merging branches - this is collider / start !
Once again, only on letters, I hope that it will be easier for perception:
master = C1 + C2 + C3
collider / start = master + C4 = C1 + C2 + C3 + C4
master + collider / start = General_commites (master, collider / start) + Only_y (master) + Only_y (collider / start) = (C1 + C2 + C3) + (NULL) + (C4) = C1 + C2 + C3 + C4

When one branch "lies" on another, it already enters, as it were, in this, another branch, and the result will be the second branch. We simply rewind history from old commits to new ones. This is the rewind (the union in which you do not need to do anything) and was named fast-forward.
Why is fast-forward good?
  1. You do not need to do anything when merging
  2. Automatic merge, with guaranteed
  3. Conflicts are not possible and will never arise.
  4. The story remains linear (as if there was no association), which is often easier to read on large projects.
  5. New commits do not appear


How to quickly find out that fast-forward is possible? To do this, it’s enough to look at gitk for two branches that need to be merged and answer one question: is there a direct path from branch A to B, if we move only upwards (from bottom to top). If so, it will be fast-forward.
In theory it is clear, we try in practice, we take away the changes to the master :
 dev1(collider/terminate)$ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 4 commits. dev1(master)$ git merge collider/start Updating 0c3aa28..d229fa9 Fast-forward #    collider.init.sh | 9 +++++++++ 1 file changed, 9 insertions(+) 

Result (the pointer just moved forward):


Union

Now we take away the changes from collider / terminate . But, the one who read to here (read after all, yes ?!) will notice that there is no direct way and we will not get off so beautifully. Let's try git to ask fast-forward:
 dev1(master)$ git merge --ff-only collider/terminate fatal: Not possible to fast-forward, aborting. 

Which was to be expected. We just do merge:
 dev1(master)$ git merge collider/terminate Auto-merging collider.init.sh CONFLICT (content): Merge conflict in collider.init.sh Automatic merge failed; fix conflicts and then commit the result. 

I am even glad that we have a conflict. Usually, at this point, some get lost, google go and ask what to do.
To start:
A conflict occurs when trying to merge two or more commits, in which changes were made to the same line. And now git does not know what to do: either take the first option, or the second one, or the old one, or remove everything.

As always, the two most necessary teams are rushing to help us:
 dev1(master)$ git status # On branch master # Your branch is ahead of 'origin/master' by 5 commits. # # Unmerged paths: # (use "git add/rm <file>..." as appropriate to mark resolution) # # both modified: collider.init.sh # no changes added to commit (use "git add" and/or "git commit -a") dev1(master)$ gitk --all 


We are in master , HEAD points there, and our commits are added there too.
The file looks like this:
 #!/bin/sh USER=collider <<<<<<< HEAD do_start(){ echo -n "Starting collider..." sleep 1s echo "ok" echo "The answer is 42. Please, come back again after 1 billion years." } case $1 in start) do_start ======= do_terminate() { echo -n "Safely terminating collider..." sleep 1s echo "oops :(" } case $1 in terminate) do_terminate >>>>>>> collider/terminate ;; *) echo Uknown Action: $1 ;; esac 

We need both options, but we would not like to combine manually, right? Here we will be helped by all sorts of merge-tula.
The easiest way to resolve a conflict is to invoke the git mergetool command. For some reason, not everyone knows about this team.
She does something like the following:
  1. Finds and offers to use a choice of diff or merge programs.
  2. Creates filename.orig (file as it is before attempting to merge)
  3. Creates a file filename.base (which file was)
  4. Creates a file filename.remote (as it was changed in another branch)
  5. Creates a file filename.local (how we changed it)
  6. After resolving conflicts everything is saved in filename

We try:
 dev1(master)$ git mergetool merge tool candidates: opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse ecmerge p4merge araxis bc3 emerge vimdiff Merging: collider.init.sh Normal merge conflict for 'collider.init.sh': {local}: modified file {remote}: modified file Hit return to start merge resolution tool (kdiff3): 

Due to the fact that the changes were in the same places of conflict, it turned out a lot. Therefore, I took the first option everywhere, and copied the second right after the first, but with gui it is pretty easy to do. Here is the result - at the top of the file options, below - the union (sorry for the quality):

Save the result, close the window, commit, see the result:


We have created a new commit, which is the union of the other two. Fast-forward did not happen, because there was no direct way for this, the story began to look a little more confusing. Sometimes, a merge is really needed, but an unnecessarily complicated story is also useless.
Here is an example of a real project:

Of course, this is no good! “But development goes in parallel and there will be no fast-forward,” you say? There is an exit!

Restructuring

What to do to keep the story beautiful and direct? You can take our branch and rebuild it to another branch! Those. specify a new beginning to the branch and reproduce all commits one by one. This process is called rebase.Our commits will be a continuation of the branch to which we will rebuild them. Then the story will be simple and linear. And you can make a fast-forward.
In other words: we repeat the history of changes from one branch to another, as if we were really taking another branch and re-doing the same changes.

To begin, we will cancel the last changes. The easiest way is to return the master pointer back to the previous state. Creating a merge-commit, we moved exactly the master , so it should be brought back, preferably (and in some cases important) to the same commit where it was.
As a result, our merge-commit will be left without any pointer and will not belong to any branch.

Usinggitkor console move our pointer. Since the collider / start branch already indicates our commit, we do not need to look for its id, and we can use the name of the branch (it will be the same):
 dev1(master)$ git reset --hard collider/start HEAD is now at d229fa9 Added Collider Start Function 


What happened to merge-commit?
(), . Git , , .. , . , git garbage collector .
, . git reflog . , ( HEAD ). , id , checkout ( ).
( , ):
 d229fa9 HEAD@{0}: reset: moving to collider/start 80b77c3 HEAD@{1}: commit (merge): Merged collider/terminate d229fa9 HEAD@{2}: merge collider/start: Fast-forward 0c3aa28 HEAD@{3}: checkout: moving from collider/terminate to master 4ea02f5 HEAD@{4}: commit: Added Collider Terminate Function 0c3aa28 HEAD@{5}: checkout: moving from collider/start to collider/terminate d229fa9 HEAD@{6}: commit: Added Collider Start Function 0c3aa28 HEAD@{7}: checkout: moving from collider/launch to collider/start 0c3aa28 HEAD@{8}: checkout: moving from collider/terminate to collider/launch 0c3aa28 HEAD@{9}: checkout: moving from collider/stop to collider/terminate 0c3aa28 HEAD@{10}: checkout: moving from collider/start to collider/stop 0c3aa28 HEAD@{11}: checkout: moving from master to collider/start 0c3aa28 HEAD@{12}: commit: Added collider init script 41f0540 HEAD@{13}: checkout: moving from v2 to master 75607a1 HEAD@{14}: commit: Ugly changes 55280dc HEAD@{15}: checkout: moving from master to v2 41f0540 HEAD@{16}: commit: Change 3 55280dc HEAD@{17}: commit: Change 2 598a03a HEAD@{18}: commit: Change 1 d80e5f1 HEAD@{19}: commit (initial): Init Project 



Let's see what happened:


In order to rebuild one branch to another, you need to find their common beginning, then take commits of the tunable branch and, in the same order, apply them to the main (base) branch. A very clear picture ( feature is rebuilt on master ):

Important note: after the “perestroika” it will be new commits. And the old ones have not disappeared anywhere and have not moved.

: merge-commit. collider/terminate collider/start .
collider/terminate collider/start , master merge-commit . , , master ( git checkout master && git reset --hard collider/terminate ). , . Git — , .


In theory, figured out, try to practice. Switch to collider / terminate and rebuild to that commit where master points (or collider / start , to whom it is more convenient). The command literally "take the current branch and rebuild it to the specified commit or branch":
 dev1(master)$ git checkout collider/terminate Switched to branch 'collider/terminate' dev1(collider/terminate)$ git rebase -i master # -i    , ..  git rebase todo list       

An editor will open in which there will be approximately the following:
 pick 4ea02f5 Added Collider Terminate Function # Rebase d229fa9..4ea02f5 onto d229fa9 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. # 

In short: in the process of restructuring, we can change the comments to the commits, edit the commits themselves, merge them, or skip them altogether. Those.You can rewrite the history of the branch beyond recognition. At this stage, we do not need it, just close the editor and continue. Like last time, we cannot avoid conflicts:
 error: could not apply 4ea02f5... Added Collider Terminate Function When you have resolved this problem run "git rebase --continue". If you would prefer to skip this patch, instead run "git rebase --skip". To check out the original branch and stop rebasing run "git rebase --abort". Could not apply 4ea02f5... Added Collider Terminate Function dev1((no branch))$ git status # Not currently on any branch. # Unmerged paths: # (use "git reset HEAD <file>..." to unstage) # (use "git add/rm <file>..." as appropriate to mark resolution) # # both modified: collider.init.sh # no changes added to commit (use "git add" and/or "git commit -a") 

We solve conflicts with git mergetool and continue “rebuilding” - git rebase - continue . Git online gives us the opportunity to change and comment.
Result:

Now it is not difficult to update the master and remove all unnecessary:
 dev1(collider/terminate)$ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 5 commits. dev1(master)$ git merge collider/terminate Updating d229fa9..6661c2e Fast-forward collider.init.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) dev1(master)$ git branch -d collider/start Deleted branch collider/start (was d229fa9). dev1(master)$ git branch -d collider/terminate Deleted branch collider/terminate (was 6661c2e). 


At this stage, we deleted, edited, merged edits, and at the output we got a beautiful linear history of changes:


Break

Quite a lot of information has already been received, so you need to stop and think about everything. Using examples, we met the following git features:


Further examples will go on more difficult. I will use a simple script that will add random lines to the file.
At this stage, it does not matter to us what the contents are, but it would be very nice to have many different commits, and not just one. For clarity.
The script adds a random line to the file and makes git commit. This is repeated several times:
 dev1(master)$ for i in `seq 1 2`; do STR=`pwgen -C 20 -B 1`; echo $STR >> trash.txt; git commit -m "Added $STR" trash.txt; done [master e64499d] Added rooreoyoivoobiangeix 1 file changed, 1 insertion(+) [master a3ae806] Added eisahtaexookaifadoow 1 file changed, 1 insertion(+) 


Send and receive changes



It's time to learn how to work with remote repositories. In general, for work we need to be able to:


Here is a list of the main commands to be used:
  1. git remote - management of remote repositories
  2. git fetch - receive
  3. git pull- the same as git fetch+git merge
  4. git push - send


git remote

As noted above, origin is the default repository name. Names are needed because There may be several repositories and they need to be somehow distinguished. For example, I had a copy of the repository on a flash drive and I added a flash repository . Thus, I could work with two repositories at the same time: origin and flash .

The repository name is used as a prefix to the name of the branch, so that you can distinguish your branch from someone else, for example, master and origin / master

A little trick
master origin origin/master . , '/'.
Those. " origin\/master ", master . Git , , . , .


The help git remoteis described quite well. As expected, there are commands: add, rm, rename, show.
showwill show the main repository settings:
 dev1(master)$ git remote show origin * remote origin Fetch URL: /home/sirex/proj/git-habr/origin Push URL: /home/sirex/proj/git-habr/origin HEAD branch: master Remote branch: master tracked Local branch configured for 'git pull': master merges with remote master Local ref configured for 'git push': master pushes to master (fast-forwardable) 


To add an existing repository we use add:
 git remote add backup_repo ssh://user@myserver:backups/myrepo.git #   git push backup_repo master 


git fetch

The team speaks for itself: get change.
It should be noted that there will not be any changes locally. Git will not touch a working copy, will not touch a branch, etc.
New commits will be downloaded, only remote branches and tags will be updated. This is useful because before updating your repository, you can see all the changes that "came" to you.

Below is a description of the push command, but now we need to pass the changes to origin in order to demonstrate how the fetch works:
 dev1(master)$ git push origin master Counting objects: 29, done. Delta compression using up to 4 threads. Compressing objects: 100% (21/21), done. Writing objects: 100% (27/27), 2.44 KiB, done. Total 27 (delta 6), reused 0 (delta 0) Unpacking objects: 100% (27/27), done. To /home/sirex/proj/git-habr/origin d80e5f1..a3ae806 master -> master 


Now, on behalf of dev2, let's see what we have and get all the changes:
 dev2(master)$ git log commit d80e5f1746856a7228cc27072fa71f1c087d649a Author: jsirex Date: Thu Apr 4 04:21:07 2013 +0300 Init Project #   ,  : dev2(master)$ git fetch origin remote: Counting objects: 29, done. remote: Compressing objects: 100% (21/21), done. remote: Total 27 (delta 6), reused 0 (delta 0) Unpacking objects: 100% (27/27), done. From /home/sirex/proj/git-habr/origin d80e5f1..a3ae806 master -> origin/master 


It looks like this:

Please note that we are in master .
What can be done:
git checkout origin/master- switch to the remote master in order to “touch” it. However, this branch cannot be edited, but you can create your own local branch and work with it.
git merge origin/master- merge new changes with yours. Since we had no local changes, then merge will turn into fast-forward:
 dev2(master)$ git merge origin/master Updating d80e5f1..a3ae806 Fast-forward README.md | 2 ++ collider.init.sh | 31 +++++++++++++++++++++++++++++++ trash.txt | 2 ++ 3 files changed, 35 insertions(+) create mode 100755 collider.init.sh create mode 100644 trash.txt 


If new branches appear in origin, by itself fetch also downloads them. You can also ask fetch to update only a specific branch, not all.
The important point : when someone deletes a branch from origin, you still have a local record about it. For example, you continue to see origin / deleted / branch , although it is no longer there. To remove these entries, use git fetch origin --prune.

git pull

git pullsame as git fetch + git merge. Of course, the changes will be merged with the appropriate branches: master with origin / master , feature with origin / feature . Branches are not combined by name, as someone might think, but due to the upstream tracking branch . When we checkout any branch for work, git does something like this:
  1. whether there is already such a branch locally and if there is, it takes it
  2. if there is no branch, looks to see if it is remotely origin / <branch_name>
  3. if it is, it creates locally <branch_name> in the same place as origin / <branch_name> and “binds” these branches (branch <branch_name> now is tracking remote origin / <branch_name> )

In the .git / config file you can see this:
 [branch "master"] remote = origin merge = refs/heads/master 


In 95% of cases, you do not need to change this behavior.

If there were local changes, it git pullwill automatically merge them with the remote branch and it will be merge-commit, not fast-forward. While we were developing something the code was outdated and it would be nice to upgrade first, apply all our changes already on top of the new code and only then give the result. Or for now continue locally. And so the story does not mix. This is what helps to do it rebase.
To use git by default rebaseinstead of merge, you can ask for it:
 git config branch.<branch_name>.rebase true 


git push

git push origin- submit changes. In this case, all tracking (tracking) branches will be transferred. To convey a certain branch, you must explicitly specify the name of: git push origin branch_name.

At this stage, questions may arise:


The extended use of the command can answer all questions:
git push origin <___>:<___origin>
Examples:
 git push origin d80e5f1:old_master #       old_master.     d80e5f1 git push origin my_local_feature:new_feature/with_nice_name #     new_feature/with_nice_name  my_local_feature git push origin :dead_feature #   ""  dead_feature. ..  dead_feature   .    


Important : when you update a branch, it is assumed that you first took away all the latest changes and only then try to transfer everything back. Git assumes that the story will remain linear, your changes continue the current (fast-forward). Otherwise, you will receive:rejected! non fast-forward push! .
Sometimes when you know exactly what you are doing, it is necessary. You can bypass it like this:
 git push origin master --force #    ,   


Included in the project



At this point, it’s more or less clear how push and pull work. More vaguely, what is the difference between merge and rebase. It is completely unclear why this is necessary and how to apply.
When someone asks:
- Why do we need a version control system?
Most often, the response can be heard:
- This system helps to keep all changes in the project, so that nothing is lost and you can always "roll back".

Now ask yourself the question: “How often do you have to roll back?” Do you often need to store more than the last state of a project? An honest answer will be: "very rarely." I raise this question to highlight the much more important role of the version control system in the project:
The version control system allows you to work together on a project to more than one developer.


The fact how convenient you are to work with the project together with other developers and how much the version control system helps you with this is the most important thing.

Using% VCS_NAME%? Conveniently? Do not limit the development process and easily adapts to the requirements? Quickly? So this% VCS_NAME% fits your project best. Perhaps you do not need to change anything.

Typical scenarios when working on a project


Now let's talk about typical scenarios of working on a project from a code point of view. And this:


To achieve such a process, you need to clearly define where and what will be stored. SinceLightweight branches (i.e. do not waste resources, space, etc.) for all tasks you can create separate branches. This is good practice. This approach makes it easy to operate with change sets, include them in various branches, or completely eliminate unsuccessful options.

The master usually stores the latest released version and it is updated only from release to release. It will not be superfluous to make a tag with the version indicated, since master may be updated.
We will need another branch for the main development, the one into which everything will merge and which will be a continuation of the master . It will be tested and on readiness, during the release, all changes will be merged into the master . Let's call this threaddev .
If we need to release a hotfix, we can return to the master state or to a specific tag, create a hotfix / version branch there and begin work on correcting critical changes. This does not affect the design and current development.
To develop features, it will be convenient to use feature / <feature_name> branches . It is better to start this thread with the most recent changes and periodically “pull up” the changes from dev to yourself. To keep the history simple and linear, it is better to rebuild the branch to dev ( git rebase dev).
Fix minor bugs can go directly to dev. But even for minor bugs or changes, it is recommended to create locally temporary branches. They do not need to be sent to the global repository. These are just your temporary branches. This approach will provide many opportunities:


Fix bugs

Create a dev branch and consider a typical bug fix script.
 dev1(master)$ git status # On branch master nothing to commit (working directory clean) dev1(master)$ git checkout -b dev Switched to a new branch 'dev' dev1(dev)$ git push origin dev Total 0 (delta 0), reused 0 (delta 0) To /home/sirex/proj/git-habr/origin * [new branch] dev -> dev 

And the second developer "takes" a new branch to itself:
 dev2(master)$ git pull From /home/sirex/proj/git-habr/origin * [new branch] dev -> origin/dev Already up-to-date. 


Let 2 developers each work on their own bug and make several commits (don't be embarrassed that I’m generating a lot of random commits in such an ugly way):
 dev1(dev)$ for i in `seq 1 10`; do STR=`pwgen -C 20 -B 1`; echo $STR >> trash.txt; git commit -m "Added $STR" trash.txt; done [dev 0f019d0] Added ahvaisaigiegheweezee 1 file changed, 1 insertion(+) [dev c715f87] Added eizohshochohseesauge 1 file changed, 1 insertion(+) [dev 9b9672c] Added aitaquuerahshiqueeph 1 file changed, 1 insertion(+) [dev 43dad98] Added zaesooneighufooshiph 1 file changed, 1 insertion(+) [dev 9da2de3] Added aebaevohneejaefochoo 1 file changed, 1 insertion(+) [dev e93f93e] Added rohmohpheinugogaigoo 1 file changed, 1 insertion(+) [dev 54ba433] Added giehaokeequokeichaip 1 file changed, 1 insertion(+) [dev 05f72db] Added hacohphaiquoomohxahb 1 file changed, 1 insertion(+) [dev 8c03e0d] Added eejucihaewuosoonguek 1 file changed, 1 insertion(+) [dev cf21377] Added aecahjaokeiphieriequ 1 file changed, 1 insertion(+) 

 dev2(master)$ for i in `seq 1 6`; do STR=`pwgen -C 20 -B 1`; echo $STR >> trash.txt; git commit -m "Added $STR" trash.txt; done [master 1781a2f] Added mafitahshohfaijahney 1 file changed, 1 insertion(+) [master 7df3851] Added ucutepoquiquoophowah 1 file changed, 1 insertion(+) [master 75e7b2b] Added aomahcaashooneefoavo 1 file changed, 1 insertion(+) [master d4dea7e] Added iexaephiecaivezohwoo 1 file changed, 1 insertion(+) [master 1459fdb] Added quiegheemoighaethaex 1 file changed, 1 insertion(+) [master 1a949e9] Added evipheichaicheesahme 1 file changed, 1 insertion(+) 


When the work is finished, we transfer the changes to the repository. Dev1:
 dev1(dev)$ git push origin dev Counting objects: 32, done. Delta compression using up to 4 threads. Compressing objects: 100% (30/30), done. Writing objects: 100% (30/30), 2.41 KiB, done. Total 30 (delta 19), reused 0 (delta 0) Unpacking objects: 100% (30/30), done. To /home/sirex/proj/git-habr/origin a3ae806..cf21377 dev -> dev 


Second developer:
 dev2(master)$ git push origin dev error: src refspec dev does not match any. error: failed to push some refs to '/home/sirex/proj/git-habr/origin' 


What happened? First of all, we did pull, but forgot to switch to dev . And they began to work in the master .
When working with git, try to “command” as specifically as possible.
I wanted to send the changes to dev . If, instead, git push origin devI used simply git push,
then git would have done what he should have done - passed the changes from our master to origin / master . This situation can be corrected, but more difficult. It is much easier to fix everything locally.


As mentioned above, nothing terrible, everything is fixable. As usual, let's see what we have done gitk --all:

“go ahead” master, and had to "go" dev . There are several options to correct the situation.
Here you can, for example, like this:
  1. Since there were no other changes, switch to dev
  2. Combine all the changes from master to dev , where they should have been (direct path, fast-forward , there will be no problems)
  3. We will start the correct dev , and we will install the incorrect master in the old, correct position, where it used to be (on origin / master )


At first glance, it seems that there is a lot of action and everything is somehow complicated. But, if you look, the description of what needs to be done is much more than the work itself. And most importantly, we have already done so above. Let's start the practice:
 dev2(master)$ git checkout dev #  Branch dev set up to track remote branch dev from origin. Switched to a new branch 'dev' dev2(dev)$ git merge master #  fast-forward Updating a3ae806..1a949e9 Fast-forward trash.txt | 6 ++++++ 1 file changed, 6 insertions(+) dev2(dev)$ git checkout master #   master ... Switched to branch 'master' Your branch is ahead of 'origin/master' by 6 commits. dev2(master)$ git reset --hard origin/master # ...       HEAD is now at a3ae806 Added eisahtaexookaifadoow dev2(master)$ git checkout dev #   dev Switched to branch 'dev' Your branch is ahead of 'origin/dev' by 6 commits. 


Let's look at the result:

Now, everything is as it should be: our changes to dev are ready to be passed to origin.
Passing:
 dev2(dev)$ git push origin dev To /home/sirex/proj/git-habr/origin ! [rejected] dev -> dev (non-fast-forward) error: failed to push some refs to '/home/sirex/proj/git-habr/origin' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Merge the remote changes (eg 'git pull') hint: before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details. 


It was not possible to transfer, because someone else (dev1) updated the repository, submitting his changes and we just “fell behind”. We need to update our state by making git pullor git fetch. Sincegit pull will immediately merge branches, I prefer to use git fetch, because He gives me the opportunity to look around and make a decision later:
 dev2(dev)$ git fetch origin remote: Counting objects: 32, done. remote: Compressing objects: 100% (30/30), done. remote: Total 30 (delta 19), reused 0 (delta 0) Unpacking objects: 100% (30/30), done. From /home/sirex/proj/git-habr/origin a3ae806..cf21377 dev -> origin/dev 


There are several options to submit our changes:
  1. Forcibly transfer our changes, erasing what was there: git push origin dev --force
  2. Continue the thread using git merge origin/devand then git push origin dev(merge the new changes with your own and transfer)
  3. Rebuild our branch to the top of a new one, which will keep the development sequence and eliminate unnecessary branching: git rebase origin/devand then git push origin dev.

The most attractive is the third option, which we will try, and in the interactive mode:
 dev2(dev)$ git rebase -i origin/dev #      origin/dev 

The editor will open and give us the opportunity to correct our history, for example, change the order of commits or merge several. This was written above. I will leave it as it is:
 pick 1781a2f Added mafitahshohfaijahney pick 7df3851 Added ucutepoquiquoophowah pick 75e7b2b Added aomahcaashooneefoavo pick d4dea7e Added iexaephiecaivezohwoo pick 1459fdb Added quiegheemoighaethaex pick 1a949e9 Added evipheichaicheesahme # Rebase cf21377..1a949e9 onto cf21377 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. # 


In the process of restructuring conflicts will arise. We solve them through git mergetool(or as you prefer) and continue the restructuringgit rebase --continue
SinceI keep writing lines to one file all the time, conflicts cannot be avoided, but in practice in projects, when work on different parts of a project is distributed, there will be no conflicts at all. Or they will be quite rare.
From here you can derive a couple of useful rules :


After all conflicts have been resolved, the story will be linear and logical. For clarity, I changed the comments (interactive rebase gives this opportunity):

Now our branch continues origin / dev and we can give our changes: current, adapted for new commits:
 dev2(dev)$ git push origin dev Counting objects: 20, done. Delta compression using up to 4 threads. Compressing objects: 100% (18/18), done. Writing objects: 100% (18/18), 1.67 KiB, done. Total 18 (delta 11), reused 0 (delta 0) Unpacking objects: 100% (18/18), done. To /home/sirex/proj/git-habr/origin cf21377..8212c4b dev -> dev 

Further the history repeats. For convenience or in the case of working on several bugs, as mentioned above, it is convenient to create separate branches from dev or origin / dev before starting work .

A brief summary : the usual fix for bugs can be made using the following simple algorithm:
  1. Fix in dev or separate branch
  2. Getting changes from origin ( git fetch origin)
  3. Rebuild changes to latest version ( git rebase -i origin/dev)
  4. Passing changes to origin ( git push origin dev)


Feature branch

It so happens that you need to do some big piece of work, which should not fall into the main version until it is finished. Over these branches can work several developers. From the point of view of the process of working in git, a very serious and voluminous bug can be considered as a feature - a separate branch, on which several people work. The process itself is exactly the same as in the usual bug fixes. Only the work goes not with dev , but with the feature / name branch.
In general, such branches can be short-living (1-2 weeks) and long-living (a month or more). Of course, the longer a branch lives, the more often it needs to be updated, “pulling” changes from the main branch into it. The larger the branch, the more likely it will be the overhead of its maintenance.

Let's start with short-living feature branches

Usually a branch is created from the latest code, in our case from the dev branch:
 dev1(dev)$ git fetch origin remote: Counting objects: 20, done. remote: Compressing objects: 100% (18/18), done. remote: Total 18 (delta 11), reused 0 (delta 0) Unpacking objects: 100% (18/18), done. From /home/sirex/proj/git-habr/origin cf21377..8212c4b dev -> origin/dev dev1(dev)$ git branch --no-track feature/feature1 origin/dev #   feature/feature1  origin/dev,      dev1(dev)$ git push origin feature/feature1 #     ,      Total 0 (delta 0), reused 0 (delta 0) To /home/sirex/proj/git-habr/origin * [new branch] feature/feature1 -> feature/feature1 


Now you can work on feature1 in the feature / feature1 branch . After some time in our branch there will be many commits, and the work on feature1 will be finished. In this case there will be many changes in dev too. And our task is to give our changes to dev .
It will look something like this:

The situation resembles the previous one: two branches, one must be merged with the other and transferred to the repository. The only difference is two public (remote) branches, not local ones. This requires little communication. When the work is completed, one developer should warn the other that he is going to “merge” the changes into dev and, for example, delete the branch. Thus, to transfer something to this thread will be meaningless.
And then the algorithm is almost the same as it was:
 git fetch origin #    git rebase -i origin/dev # ,     ,    feature1    dev   .  origin/dev,   dev       ,       git checkout dev #   ,      git merge feature/feature1 #   git reset --hard feature/feature1.       .  ,   fast-forward. 

The picture, feature1 became part of the dev, which was what was needed:

We push and clean the unnecessary things:
 dev1(dev)$ git push origin dev Counting objects: 11, done. Delta compression using up to 4 threads. Compressing objects: 100% (9/9), done. Writing objects: 100% (9/9), 878 bytes, done. Total 9 (delta 6), reused 0 (delta 0) Unpacking objects: 100% (9/9), done. To /home/sirex/proj/git-habr/origin 3272f59..e514869 dev -> dev dev1(dev)$ git push origin :feature/feature1 #   ,   To /home/sirex/proj/git-habr/origin - [deleted] feature/feature1 dev1(dev)$ git branch -d feature/feature1 #   Deleted branch feature/feature1 (was e514869). 


Long-living feature branches

The complexity of long-living branches in their support and updating. If you do everything as described above, then there is probably trouble: time passes, the main branch changes, the project changes, and your feature is based on a very old version. When the time comes to release the feature, it will be so out of the project that the merger can be very difficult or even impossible. That is why the branch needs to be updated. Once the dev goes forward, then we will simply rebuild our branch to dev from time to time.

Everything is good, but you just can’t just take it and rebuild the public branch: a branch after a rebeise is a new set of commits, a completely different story. She does not continue what has already happened. Git will not accept these changes: two different paths, no fast-forward. To rewrite history, developers need to agree.
Someone will rewrite history and forcefully upload a new version, and at this moment the rest should not transfer their changes to the current branch, since it will be overwritten and everything will be gone. When the first developer finishes, all the others will transfer their commits, which they will have time to do during the census to the new branch. Who said that you can not re-raise public branches?

Let's start the practice:
 dev1(dev)$ git checkout -b feature/long Switched to a new branch 'feature/long' dev1(feature/long)$ git push origin dev Everything up-to-date dev1(feature/long)$ __ dev1(feature/long)$ git push origin feature/long Counting objects: 11, done. Delta compression using up to 4 threads. Compressing objects: 100% (9/9), done. Writing objects: 100% (9/9), 807 bytes, done. Total 9 (delta 6), reused 0 (delta 0) Unpacking objects: 100% (9/9), done. To /home/sirex/proj/git-habr/origin * [new branch] feature/long -> feature/long 


The second developer connects to the work all standard:
 dev2(dev)$ git pull remote: Counting objects: 11, done. remote: Compressing objects: 100% (9/9), done. remote: Total 9 (delta 6), reused 0 (delta 0) Unpacking objects: 100% (9/9), done. From /home/sirex/proj/git-habr/origin * [new branch] feature/long -> origin/feature/long Already up-to-date. dev2(dev)$ git checkout feature/long Branch feature/long set up to track remote branch feature/long from origin. Switched to a new branch 'feature/long' dev2(feature/long)$    dev2(feature/long)$ git pull --rebase feature/long # fetch + rebase   dev2(feature/long)$ git push origin feature/long Counting objects: 11, done. Delta compression using up to 4 threads. Compressing objects: 100% (9/9), done. Writing objects: 100% (9/9), 795 bytes, done. Total 9 (delta 6), reused 0 (delta 0) Unpacking objects: 100% (9/9), done. To /home/sirex/proj/git-habr/origin baf4c6b..ce9e58d feature/long -> feature/long 

Add a few more commits to the main dev branch and the story will be like this:


It’s time to update feature / long , but the development should continue separately. Let dev1 be rebuilt. Then he warns dev2 about it and starts:
 dev1(feature/long)$ git fetch origin dev1(feature/long)$ git rebase -i origin/dev ...  ,  ,  .. 

At this time, dev2 continues to work, but knows that it cannot push, since there is no necessary branch yet (and the current one will be deleted).
The first one ends the rebase and the story will be like this: The

branch is rebuilt, and origin / feature / long stayed where it was. The goals we have achieved, now need to share with everyone:
 dev1(feature/long)$ git push origin feature/long To /home/sirex/proj/git-habr/origin ! [rejected] feature/long -> feature/long (non-fast-forward) error: failed to push some refs to '/home/sirex/proj/git-habr/origin' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Merge the remote changes (eg 'git pull') hint: before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details. 

Git once again reminds you that something is wrong. But now, we know exactly what we are doing, and we know that this is necessary:
 dev1(feature/long)$ git push origin feature/long --force Counting objects: 20, done. Delta compression using up to 4 threads. Compressing objects: 100% (18/18), done. Writing objects: 100% (18/18), 1.58 KiB, done. Total 18 (delta 12), reused 0 (delta 0) Unpacking objects: 100% (18/18), done. To /home/sirex/proj/git-habr/origin + ce9e58d...84c3001 feature/long -> feature/long (forced update) 

Now you can continue to warn others about the end of the work.

Let's see how these changes affected others and dev2 in particular:
 dev2(feature/long)$ git fetch origin remote: Counting objects: 20, done. remote: Compressing objects: 100% (18/18), done. remote: Total 18 (delta 12), reused 0 (delta 0) Unpacking objects: 100% (18/18), done. From /home/sirex/proj/git-habr/origin + ce9e58d...84c3001 feature/long -> origin/feature/long (forced update) 



The story is divided, our commits are rebuilt, except for one. If there were no commits at all, i.e. dev2 developer would read habr, while the first one works, then it would be enough to move the pointer feature/longto origin/feature/longand continue working. But we have one commit to add. Here rebase will help us again, together with the key --onto:
git rebase --onto <___> <c____> <_____>
This approach allows us to take only a part (some sequence of commits) for rebuilding. It is useful when you need to transfer several recent commits.
Recall also about the record HEAD~1. The ~ N operator can be applied to the pointer to point to the previous Nth commit.
HEAD~1- this is the previous one, but this is the previous one HEAD~2. HEAD~5- 5 commits back. Convenient not to memorize id.

Let's see how we can now rebuild only one commit:
 git rebase -i --onto origin/feature/long feature/long~1 feature/long 

We will analyze in more detail:
  1. we are on feature / long, which is completely different from the new one, but contains the commit we need (1, last)
  2. we are commanding to rebuild to origin / feature / long (new beginning at the branch)
  3. this feature / long branch itself
  4. but you need to rebuild it not all (the search for a common beginning and the restructuring of all commits are not needed, they are already there), but only starting from the previous commit (not inclusive). Those.with feature / long ~ 1

If we had to drag 4 commits, it would be feature/long~4.
 dev2(feature/long)$ git rebase -i --onto origin/feature/long feature/long~1 feature/long Successfully rebased and updated refs/heads/feature/long. 


It remains to continue work.

There is another useful command that could be useful in this case, and in many others git cherry-pick.
From anywhere, you can ask git to take any commit and apply it to the current location. The commit we needed could be “dragged off”:
 git reset --hard origin/feature/long && git cherry-pick commit_id # commit_id   , ..  reset      


Hotfixes

Hotfixes are needed because there are very critical bugs that need to be fixed as quickly as possible. At the same time, the last code cannot be transmitted along with the hotfix. it is not tested and there is no time for full testing. Only this fix is ​​needed, as quickly as possible and tested. To do this, it is enough to take the latest release, which was sent to the production. This is where you left tag or master . Tags play a very important role, helping to understand what exactly was collected and where it got.
Tags are made by team
 git tag     '/'   . 

hotfix- :
hotfix tag master ( cherry-pick , - ) cherry-pick , . .
git:
dev2(feature/long)$ git checkout master Switched to branch 'master' dev2(master)$ git tag release/1.0 # , dev2(master)$ git checkout -b hotfix/1.0.x Switched to a new branch 'hotfix/1.0.x' dev2(hotfix/1.0.x)$ .. dev2(hotfix/1.0.x)$ git push origin hotfix/1.0.x Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 302 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] hotfix/1.0.x -> hotfix/1.0.x dev2(hotfix/1.0.x)$ git tag release/1.0.1 # dev2(hotfix/1.0.x)$ git checkout dev # Switched to branch 'dev' dev2(dev)$ git cherry-pick release/1.0.1 # id, . # , git commit dev2(dev)$ git commit [dev 9982f7b] Added rahqueiraiheinathiav 1 file changed, 1 insertion(+) dev2(dev)$ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 328 bytes, done. Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin b21f8a5..9982f7b dev -> dev
    git tag '/' .

    hotfix- :
    hotfix tag master ( cherry-pick , - ) cherry-pick , . .
    git:
    dev2(feature/long)$ git checkout master Switched to branch 'master' dev2(master)$ git tag release/1.0 # , dev2(master)$ git checkout -b hotfix/1.0.x Switched to a new branch 'hotfix/1.0.x' dev2(hotfix/1.0.x)$ .. dev2(hotfix/1.0.x)$ git push origin hotfix/1.0.x Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 302 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] hotfix/1.0.x -> hotfix/1.0.x dev2(hotfix/1.0.x)$ git tag release/1.0.1 # dev2(hotfix/1.0.x)$ git checkout dev # Switched to branch 'dev' dev2(dev)$ git cherry-pick release/1.0.1 # id, . # , git commit dev2(dev)$ git commit [dev 9982f7b] Added rahqueiraiheinathiav 1 file changed, 1 insertion(+) dev2(dev)$ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 328 bytes, done. Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin b21f8a5..9982f7b dev -> dev
  1. git tag '/' .

    hotfix- :
    hotfix tag master ( cherry-pick , - ) cherry-pick , . .
    git:
    dev2(feature/long)$ git checkout master Switched to branch 'master' dev2(master)$ git tag release/1.0 # , dev2(master)$ git checkout -b hotfix/1.0.x Switched to a new branch 'hotfix/1.0.x' dev2(hotfix/1.0.x)$ .. dev2(hotfix/1.0.x)$ git push origin hotfix/1.0.x Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 302 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] hotfix/1.0.x -> hotfix/1.0.x dev2(hotfix/1.0.x)$ git tag release/1.0.1 # dev2(hotfix/1.0.x)$ git checkout dev # Switched to branch 'dev' dev2(dev)$ git cherry-pick release/1.0.1 # id, . # , git commit dev2(dev)$ git commit [dev 9982f7b] Added rahqueiraiheinathiav 1 file changed, 1 insertion(+) dev2(dev)$ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 328 bytes, done. Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin b21f8a5..9982f7b dev -> dev
    git tag '/' .

    hotfix- :
    hotfix tag master ( cherry-pick , - ) cherry-pick , . .
    git:
    dev2(feature/long)$ git checkout master Switched to branch 'master' dev2(master)$ git tag release/1.0 # , dev2(master)$ git checkout -b hotfix/1.0.x Switched to a new branch 'hotfix/1.0.x' dev2(hotfix/1.0.x)$ .. dev2(hotfix/1.0.x)$ git push origin hotfix/1.0.x Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 302 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] hotfix/1.0.x -> hotfix/1.0.x dev2(hotfix/1.0.x)$ git tag release/1.0.1 # dev2(hotfix/1.0.x)$ git checkout dev # Switched to branch 'dev' dev2(dev)$ git cherry-pick release/1.0.1 # id, . # , git commit dev2(dev)$ git commit [dev 9982f7b] Added rahqueiraiheinathiav 1 file changed, 1 insertion(+) dev2(dev)$ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 328 bytes, done. Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin b21f8a5..9982f7b dev -> dev
  2. git tag '/' .

    hotfix- :
    hotfix tag master ( cherry-pick , - ) cherry-pick , . .
    git:
    dev2(feature/long)$ git checkout master Switched to branch 'master' dev2(master)$ git tag release/1.0 # , dev2(master)$ git checkout -b hotfix/1.0.x Switched to a new branch 'hotfix/1.0.x' dev2(hotfix/1.0.x)$ .. dev2(hotfix/1.0.x)$ git push origin hotfix/1.0.x Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 302 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] hotfix/1.0.x -> hotfix/1.0.x dev2(hotfix/1.0.x)$ git tag release/1.0.1 # dev2(hotfix/1.0.x)$ git checkout dev # Switched to branch 'dev' dev2(dev)$ git cherry-pick release/1.0.1 # id, . # , git commit dev2(dev)$ git commit [dev 9982f7b] Added rahqueiraiheinathiav 1 file changed, 1 insertion(+) dev2(dev)$ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 328 bytes, done. Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin b21f8a5..9982f7b dev -> dev
    git tag '/' .

    hotfix- :
    hotfix tag master ( cherry-pick , - ) cherry-pick , . .
    git:
    dev2(feature/long)$ git checkout master Switched to branch 'master' dev2(master)$ git tag release/1.0 # , dev2(master)$ git checkout -b hotfix/1.0.x Switched to a new branch 'hotfix/1.0.x' dev2(hotfix/1.0.x)$ .. dev2(hotfix/1.0.x)$ git push origin hotfix/1.0.x Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 302 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] hotfix/1.0.x -> hotfix/1.0.x dev2(hotfix/1.0.x)$ git tag release/1.0.1 # dev2(hotfix/1.0.x)$ git checkout dev # Switched to branch 'dev' dev2(dev)$ git cherry-pick release/1.0.1 # id, . # , git commit dev2(dev)$ git commit [dev 9982f7b] Added rahqueiraiheinathiav 1 file changed, 1 insertion(+) dev2(dev)$ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 328 bytes, done. Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin b21f8a5..9982f7b dev -> dev
  3. git tag '/' .

    hotfix- :
    hotfix tag master ( cherry-pick , - ) cherry-pick , . .
    git:
    dev2(feature/long)$ git checkout master Switched to branch 'master' dev2(master)$ git tag release/1.0 # , dev2(master)$ git checkout -b hotfix/1.0.x Switched to a new branch 'hotfix/1.0.x' dev2(hotfix/1.0.x)$ .. dev2(hotfix/1.0.x)$ git push origin hotfix/1.0.x Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 302 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] hotfix/1.0.x -> hotfix/1.0.x dev2(hotfix/1.0.x)$ git tag release/1.0.1 # dev2(hotfix/1.0.x)$ git checkout dev # Switched to branch 'dev' dev2(dev)$ git cherry-pick release/1.0.1 # id, . # , git commit dev2(dev)$ git commit [dev 9982f7b] Added rahqueiraiheinathiav 1 file changed, 1 insertion(+) dev2(dev)$ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 328 bytes, done. Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin b21f8a5..9982f7b dev -> dev
    git tag '/' .

    hotfix- :
    hotfix tag master ( cherry-pick , - ) cherry-pick , . .
    git:
    dev2(feature/long)$ git checkout master Switched to branch 'master' dev2(master)$ git tag release/1.0 # , dev2(master)$ git checkout -b hotfix/1.0.x Switched to a new branch 'hotfix/1.0.x' dev2(hotfix/1.0.x)$ .. dev2(hotfix/1.0.x)$ git push origin hotfix/1.0.x Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 302 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] hotfix/1.0.x -> hotfix/1.0.x dev2(hotfix/1.0.x)$ git tag release/1.0.1 # dev2(hotfix/1.0.x)$ git checkout dev # Switched to branch 'dev' dev2(dev)$ git cherry-pick release/1.0.1 # id, . # , git commit dev2(dev)$ git commit [dev 9982f7b] Added rahqueiraiheinathiav 1 file changed, 1 insertion(+) dev2(dev)$ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 328 bytes, done. Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin b21f8a5..9982f7b dev -> dev
  4. git tag '/' .

    hotfix- :
    hotfix tag master ( cherry-pick , - ) cherry-pick , . .
    git:
    dev2(feature/long)$ git checkout master Switched to branch 'master' dev2(master)$ git tag release/1.0 # , dev2(master)$ git checkout -b hotfix/1.0.x Switched to a new branch 'hotfix/1.0.x' dev2(hotfix/1.0.x)$ .. dev2(hotfix/1.0.x)$ git push origin hotfix/1.0.x Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 302 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] hotfix/1.0.x -> hotfix/1.0.x dev2(hotfix/1.0.x)$ git tag release/1.0.1 # dev2(hotfix/1.0.x)$ git checkout dev # Switched to branch 'dev' dev2(dev)$ git cherry-pick release/1.0.1 # id, . # , git commit dev2(dev)$ git commit [dev 9982f7b] Added rahqueiraiheinathiav 1 file changed, 1 insertion(+) dev2(dev)$ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 328 bytes, done. Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin b21f8a5..9982f7b dev -> dev
    git tag '/' .

    hotfix- :
    hotfix tag master ( cherry-pick , - ) cherry-pick , . .
    git:
    dev2(feature/long)$ git checkout master Switched to branch 'master' dev2(master)$ git tag release/1.0 # , dev2(master)$ git checkout -b hotfix/1.0.x Switched to a new branch 'hotfix/1.0.x' dev2(hotfix/1.0.x)$ .. dev2(hotfix/1.0.x)$ git push origin hotfix/1.0.x Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 302 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] hotfix/1.0.x -> hotfix/1.0.x dev2(hotfix/1.0.x)$ git tag release/1.0.1 # dev2(hotfix/1.0.x)$ git checkout dev # Switched to branch 'dev' dev2(dev)$ git cherry-pick release/1.0.1 # id, . # , git commit dev2(dev)$ git commit [dev 9982f7b] Added rahqueiraiheinathiav 1 file changed, 1 insertion(+) dev2(dev)$ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 328 bytes, done. Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin b21f8a5..9982f7b dev -> dev
  5. git tag '/' .

    hotfix- :
    hotfix tag master ( cherry-pick , - ) cherry-pick , . .
    git:
    dev2(feature/long)$ git checkout master Switched to branch 'master' dev2(master)$ git tag release/1.0 # , dev2(master)$ git checkout -b hotfix/1.0.x Switched to a new branch 'hotfix/1.0.x' dev2(hotfix/1.0.x)$ .. dev2(hotfix/1.0.x)$ git push origin hotfix/1.0.x Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 302 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] hotfix/1.0.x -> hotfix/1.0.x dev2(hotfix/1.0.x)$ git tag release/1.0.1 # dev2(hotfix/1.0.x)$ git checkout dev # Switched to branch 'dev' dev2(dev)$ git cherry-pick release/1.0.1 # id, . # , git commit dev2(dev)$ git commit [dev 9982f7b] Added rahqueiraiheinathiav 1 file changed, 1 insertion(+) dev2(dev)$ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 328 bytes, done. Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin b21f8a5..9982f7b dev -> dev
    git tag '/' .

    hotfix- :
    hotfix tag master ( cherry-pick , - ) cherry-pick , . .
    git:
    dev2(feature/long)$ git checkout master Switched to branch 'master' dev2(master)$ git tag release/1.0 # , dev2(master)$ git checkout -b hotfix/1.0.x Switched to a new branch 'hotfix/1.0.x' dev2(hotfix/1.0.x)$ .. dev2(hotfix/1.0.x)$ git push origin hotfix/1.0.x Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 302 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] hotfix/1.0.x -> hotfix/1.0.x dev2(hotfix/1.0.x)$ git tag release/1.0.1 # dev2(hotfix/1.0.x)$ git checkout dev # Switched to branch 'dev' dev2(dev)$ git cherry-pick release/1.0.1 # id, . # , git commit dev2(dev)$ git commit [dev 9982f7b] Added rahqueiraiheinathiav 1 file changed, 1 insertion(+) dev2(dev)$ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 328 bytes, done. Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin b21f8a5..9982f7b dev -> dev
git tag '/' .

hotfix- :
hotfix tag master ( cherry-pick , - ) cherry-pick , . .
git:
dev2(feature/long)$ git checkout master Switched to branch 'master' dev2(master)$ git tag release/1.0 # , dev2(master)$ git checkout -b hotfix/1.0.x Switched to a new branch 'hotfix/1.0.x' dev2(hotfix/1.0.x)$ .. dev2(hotfix/1.0.x)$ git push origin hotfix/1.0.x Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 302 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin * [new branch] hotfix/1.0.x -> hotfix/1.0.x dev2(hotfix/1.0.x)$ git tag release/1.0.1 # dev2(hotfix/1.0.x)$ git checkout dev # Switched to branch 'dev' dev2(dev)$ git cherry-pick release/1.0.1 # id, . # , git commit dev2(dev)$ git commit [dev 9982f7b] Added rahqueiraiheinathiav 1 file changed, 1 insertion(+) dev2(dev)$ git push origin dev Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 328 bytes, done. Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /home/sirex/proj/git-habr/origin b21f8a5..9982f7b dev -> dev


Quick and easy, right? If you had 2 commits in hotfixes , it may be easier to do 2 times cherry-picka dev . But if there were a lot of them, you can again use the command git rebase --into ...and rebuild a whole chain of commits.

Any utility


Git allows you to customize alias for various commands. This is very convenient and reduces the time for recruiting commands. I will not give examples here, on the Internet they are full, just look for it.

Before you give your changes, you can rebuild them on themselves to put the story in order.
For example, in the logs may be:
 9982f7b Finally make test works b21f8a5 Fixed typo in Test 3eabaab Fixed typo in Test e514869 Added Test for Foo b4439a2 Implemented Method for Foo 250adb1 Added class Foo 

Rebuilding ourselves, but starting from 6 commits ago:
 dev2(dev)$ git rebase -i dev~6 # ..       origin/dev       dev2(dev)$ git rebase -i origin/dev 

Now in interactive mode, you can squash the first two commits into one and the last four. Then the story will look like this:
 0f019d0 Added Test for class Foo a3ae806 Implemented class Foo 

This is much more pleasant to transfer to the common repository.

Be sure to look what can git configand git config --global. Before you start working with a real project, it would be nice to set up your username and email:
 git config --global user.name sirex git config --global user.email jsirex@gmail.com 


On one project we had a parallel development of very many features. They were all developed in separate branches, but they had to be tested together. At the same time, at the time of the release of the build for testing, it was not clear whether the features were ready or not: the requirements could change or there were serious bugs. And there was a question: how to continue working on all the features without ever uniting them in principle, but uniting everything before the release? Contradiction? Git beautifully helps solve this problem and here's how:
  1. for the release of the build, a separate branch was created, where all features were merged ( git merge --no-ff)
  2. branch was given for testing
  3. development continued in its branches
  4. new changes again merged ( git merge --no-ff)
  5. , .
  6. - , dev .
  7. , ,


findings






gcc recommended looking at git extensions .
borNfree was prompted by another Source Tree GUI client .
zloylos shared a link to the visualizer for git
olancheg offered to look at another tutorial for beginners .

Ps. What is usually written here when the first post on Habré? Please do not judge strictly, he wrote as he could.

Source: https://habr.com/ru/post/174467/


All Articles