Difference between revisions of "Git"

From Ever changing code
Jump to navigation Jump to search
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
=Install=
{{Special:PrefixIndex/Git/}}
sudo apt-get install git


=Terminology=
[[Category:git]]
Git’s collaboration model is based on repository-to-repository interaction. In principle you push or pull commits from one repository to another.
:GitLab - web based interface to your Git repositories (GitHub)
 
:Staging Area - a place where we can group files together before we "commit" them to Git.
:Commit - is a snapshot of our repository. This way if we ever need to look back at the changes we've made (or if someone else does), we will see a nice timeline of all changes.
 
File status
*staged: Files are ready to be committed.
*unstaged: Files with changes that have not been prepared to be committed.
*untracked: Files aren't tracked by Git yet. This usually indicates a newly created file.
*deleted: File has been deleted and is waiting to be removed from Git.
 
:HEAD - is a pointer that holds your position within all your different commits. By default HEAD points to your most recent commit, so it can be used as a quick way to reference that commit without having to look up the SHA.
:Branching - are what naturally happens when you want to work on multiple features at the same time. You wouldn't want to end up with a master branch which has Feature A half done and Feature B half done. Rather you'd separate the code base into two "snapshots" (branches) and work on and commit to them separately. As soon as one was ready, you might merge this branch back into the master branch and push it to the remote server.
 
:Commands - are only relevant to directory that is tracked, it means it has .git/ tracking directory. Each time you use git commit changes are recorded in that directory.
 
=Git settings - pre requisites =
Specify your username and user email as these details are used each time you commit changes
git config --global user.email "piotr@example.com"
git config --global user.name "Piotr"  #name used for all your commits
git config --global --list      #list configuration, also can use --local, --system
git config --system core.editor "/bin/vim"
 
Unset configuration
git config --global --unset http.proxy
 
Global system config file. It can contain per user settings.
/etc/gitconfig
 
=Commands=
== Create new repository in a current directory ==
git init  #creates hidden directory .git/ where changes are recorded
git status # show files status
git log --summary #show details of each commit
git diff    #see changes, use HEAD argument for the current position in your commits
 
Create a new repository
 
git clone git@ourgit.example.com:piotr/ansible-training.git
cd ansible-training
touch README.md
git add README.md
git commit -m "add README"
git push -u origin master
 
Existing folder or Git repository
 
cd existing_folder
git init
git remote add origin git@ourgit.example.com:piotr/ansible-training.git
git add .
git commit
git push -u origin master
 
== Update remote repository reference ==
List your existing remotes in order to get the name of the remote you want to change.
git remote -v
origin  <nowiki>https://github.com/USERNAME/REPOSITORY.git (fetch)</nowiki>
origin  <nowiki>https://github.com/USERNAME/REPOSITORY.git (push)</nowiki>
Change your remote's URL from HTTPS to SSH with the git remote set-url command. This command also accepts '''https://''' url.
git remote set-url origin '''git@github.com:USERNAME/REPOSITORY.git'''
 
== Git clone ==
git clone git@github.com/yourLogin/repository.git #clones using ssh loaded in ssh-add -l
git clone http://github.com/{project}/repository.git #clones using http/s, will prompt for username and password that exists in Github.
 
It will clone/add another project within already existing projects. It's a way to develop projects avoiding to create softlinks between projects.
git submodule add git@github.com/yourLogin/repository-submodule.git
 
It will recursively clones a project and update subprojects(submodules)
git clone --recursive git@github.com/yourLogin/repository.git
 
=== Move a repository from GitLab to other GitLab ===
Let's assume you did clone a repo to your local machine. Notice you created --bare repository (can only push/fetch can't commit)
git clone --bare git@old-github.com/yourLogin/repository.git
Then you create an empty repo in the new GitLab called ''yourRepoName'', so you can upload/push to it
cd repository.git
git remote remove origin
#add new remote GitLab repository before push
git remote add newgitlab git@new-github.com/yourLogin/yourRepoName.git
git push --mirror newgitlab        #push all branches and tags to new remote
 
This process also allows to change the project/repository name although you do not need to.
 
=== Create bare repository, central storage, GitLab repo ===
git init --bare project.git  #this will create a folder called ''project.git'' (bare repository)
<code>--bare</code> flag creates a repository that doesn’t have a working directory, making it impossible to edit files and commit changes. Ultimately it creates the central repository for developers to push/commit their changes. GitLab new projects are created this way, become ''remote repository''. Think of --bare as a way to mark a repository as a storage facility, opposed to a development environment.
 
=== Putting the Bare Repository on a Server ===
In preparation, you create a copy of ''project'' folder and place them in ''project.git'' pre-setup <code>--bare</code> folder
piotr@vmgitlab:~/git$ git clone --bare project project.git #project.git is ready to be moved to the server
 
Create a repository/project directory on the server. Need to be own by '''git:git''' user and group.
sudo -u git mkdir /var/opt/gitlab/git-data/repositories/your_gitlab_user
 
Assuming that /var/opt/gitlab/git-data/repositories/ (GitLab Omnibus) exists on that server, you can set up your new repository by copying your bare repository over:
                    local
                    user
scp -r project.git piotr@vmgitlab.home:project.git  #copy repo to the server
sudo mv ~/project.git /var/opt/gitlab/git-data/repositories/piotrek #move to the right GitLab repo store path
 
Default remote repo path used by GitLab Omnibus installation
  git:x:998:998::/var/opt/gitlab:/bin/sh
  git user home path              <span style="color: green">GitLab repos store</span> 
    |                            <span style="color: green">/</span>
  /var/opt/gitlab/<span style="color: green">git-data/repositories</span>/piotrek
                                          |
                          this is a namespace equals a username or group project name
                          all projects .git folders are stored here
 
sudo chown -R git:git /var/opt/gitlab/git-data/repositories/piotrek/ #each file need to be owned by git:git
 
Add repo to GitLab, below command for Omnibus Installation. It will search, add and process all git repositories.
piotr@vmgitlab:~$ sudo gitlab-rake gitlab:import:repos
Processing piotrek/project.git
  <span style="color: green">* Created gittest (piotrek/project.git)</span>  <- repo has been added
Processing piotrek/ansible.git
  * ansible (piotrek/ansible.git) exists
Processing piotrek/ansible.wiki.git
  * Skipping wiki repo
 
You will find the new project on the web interface of GitLab where it can be cloned from.
 
== Add a file to a staging area for changes to be tracked ==
git add octocat.txt
git add .            #prior to 2.0 - . means current directory and all subdirectories
git add -A            #prior to 2.0 - add all files, even file deletions are included
git add '*.txt'      #add files using a wildcard, notice that quotes are required
git reset <filename>  #remove a file from staging area, the file and its changes will not be tracked
 
New behaviour in Git 2.0
To add content for the whole tree, run:
  git add --all :/
  (or git add -A :/)
 
To restrict the command to the current directory, run:
  git add --all .
  (or git add -A .)
 
== Committing (taking a snapshot of the repository) ==
The files are in the Staging Area, are not in a repository yet. To store our staged changes we run:
git commit -m "Description of what we changed"
 
== Add local repository to remote GitHub server ==
                creates repo    on remote GitHub
                name            server and repository
git remote add origin          <nowiki>https://vmgitlab.home/yourgitspace/gitrepo.git</nowiki>  #uses https connection
git remote add origin          git@vmgitlab.home:<span style="color: green">piotrek</span>/<span style="color: blue">ansible.git</span> #SSH connection; this need to be created in first place via Gitlab, create/add new project
                                                                      #Gitlab Omnibus server path for repos is  /var/opt/gitlab/git-data/repositories/<span style="color: green">piotrek</span>
                                                                      #containing relevant directories: <span style="color: blue">ansible.git</span> and <span style="color: blue">ansible.wiki.git</span>
git remote -v    #shows remote 'origin' repo path
    origin git@vmgitlab.home:piotrek/ansible.git (fetch)
    origin git@vmgitlab.home:piotrek/ansible.git (push)
 
==Push commits (code) to a remote server==
$ git remote -v    #shows remote repository that locally is called 'origin'
<span style="color: blue">origin</span> git@gitlab.com:pio2pio/python27-la.git (fetch)
<span style="color: blue">origin</span> git@gitlab.com:pio2pio/python27-la.git (push)
 
When we want to push our code the first time, we need to configure the upstream server (think as: a source of the sources)
git push --set-upstream <span style="color: blue">origin</span> <span style="color: green">master</span>  #push commits to <span style="color: blue">origin</span> remote repository of the <span style="color: green">master</span> branch
git push            -u origin master  #-u is short version of --set-upstream
 
Next time using <code>git push</code> will be sufficient.
 
== Pull and rebase ==
It happens sometimes that you both work on the same branch and your colleague has committed a code a head of you then you committed just after him. So you ended up with the code that cannot be pushed up as your branch is behind the remote repo commits. If you try to pull, the newest changes will override your code. In this situation you can 'rebase'
 
git pull --rebase
 
What it does, is pulling the new code and applies your code on the top of it and creates a new commit. Your previous local commit will be deleted.
 
= Updating a local repo from a Remote Repository =
== Fetch changes from the remote repository ==
''git fetch'' doesn’t touch a working tree at all but updates tracking system allowing you to decide what you want to do next
git fetch    #download changes to local repo from remote 'origin/master' branch
git diff master origin/master  #compare local 'master' branch with remote 'origin/master' branch
git merge origin/master  #while working on 'master' (after a git checkout master) merge in the changes that you’ve just got from 'origin'
 
== Pull changes and automagicly merge ==
 
git pull origin master      #from the 'origin' remote repository its master branch
git stash        # to stash your not committed changes before pull
git stash apply  # to re-apply your changes after your pull
 
Preview changes of your staged files
git diff --staged
 
== Working with branches ==
Git has 2 types of branches:
 
'''Local branches''' -what you see when you type git branch, e.g. to use an abbreviated example I have here:
      $ git branch
        debian
        server
      * master
'''Remote-tracking branches''' -what you see when you type git branch -r, e.g.:
      $ git branch -r
      cognac/master
      origin/albert
      origin/ant
 
The names of tracking branches are made up of the name of a "remote" (e.g. origin, cognac) followed by "/" and then the name of a branch in that remote repository.
 
These are stored locally:
* <tt>.git/refs/heads/</tt> [for local branches]
* <tt>.git/refs/remotes/</tt> [for tracking branches]
 
=== Recovering files (from staging area, HEAD, or other) ===
Retrieve file from the staging area
git checkout directory/file.txt
Retrieve a file from the latest commit
git checkout HEAD -- directory/file.txt
Retrieve a file from a previous commit
  git checkout HEAD^ -- directory/file.txt #or git checkout 13bd00 -- directory/file.txt
It’s important to note that this stages the file, too!
 
=== Undoing changes in a local branch, fixing typoos ===
==== Undo a merge that hasn't been pushed yet ====
Make your local master branch look identical to origin/master. It is possible that origin/master may be ahead of your local master just previous to the merge by some commits, in that case this might not give the desired results
(master)$ git reset --hard origin/master
 
The ref ORIG_HEAD will point to the original commit from before the merge, <tt>--merge</tt> doesn't touch uncommitted changes
(master)$ git reset --merge ORIG_HEAD
 
With newer Git versions, if you have not committed the merge yet and you have a merge conflict:
git merge --abort
 
====Resetting branch pointer without changing files====
This command will move the current branch pointer back by one commit
git reset HEAD~1
 
You can check the result by running <tt>git show</tt> which will give you the details of the most recent commit on the branch. Running it before and after resetting the branch pointer should show how it moved to the previous commit. Note that the files stay in the state of your undone commit and will therefore be marked as modified (or added, deleted) again. I think of this command as moving changes from the last commit back to the staging area.
Resetting branch pointer and files
 
====Set back the current branch by one commit and undo all changes from that commit====
git reset --hard HEAD~1
 
This will completely wipe out the most recent commit from the current branch. I am not sure if the actual commit data is also deleted, but it will definitely no longer be part of the current branch and your repository should look exactly like one commit ago.
 
====Amending the most recent commit====
There is a flag for the commit command itself that allows you to pack changes on top of the most recent one. Instead of creating a new commit, Git will replace the most recent one. The new comment message will override the previous one.
git commit --amend -m "New commit"
 
== Create a new branch space to work for us ==
git branch branchname-newcode
git branch -d branchname-newcode      #deletes branch
git branch -D branchname-newcode      #forces delete since you cannot delete a branch that has not been merged, -D is equivalent -d -f
 
=== Change branch ===
git branch #list branches, one with asterisks it active current branch
git checkout branchname-newcode    #change to new branch
 
== Remove files ==
git rm '*.txt'
git rm -r folder_of_cats  #remove folders recursively
git commit -am "Delete stuff"    #-a auto removes deleted files within commit
 
== Merge other branch with the current active branch ==
You always merging into the current 'checked out branch'. So, if you currently working in 'master-vpc' branch
git merge aws-subnet
will write code from 'aws-subnet' branch to your 'master-vpc' branch.
 
== Preview changes before merging ==
Current branch is 'master-vpc'. So to preview changes before importing/merging 'aws-subnet' branch into the current use this command:
git diff aws-subnet
 
= Preview a file changes =
The diff command does not work on symbolic links, you need you point to the hard file.
 
Changes between commits, ^ - last commit, ^^ - 2 commits and so on
git diff HEAD^ HEAD main.c
 
git diff (working directory and staging)
git diff --cached (staging and repo)
git diff HEAD (working dir and repo)
git diff 225bb (?)
git diff HEAD 225bb (?)
git diff MY-PROGS/TEST.PROG.A (?)
Navigate with Space (next page), Enter (next line), B (previous page), Q (quit)
 
Show all historical commits against the file
git log -- [filename]
git log --follow -p -- [filename]  #show content/diff
 
Show the file content/diff commits history
gitk [filename]
 
Preview a file between branches
git diff ''mybranch branch_to_compare'' -- ''folder/file_to_compare.txt''
 
Show a file form a different branch
git show origin/branchName:path/to/file
 
= Search through commits logs =
git log --grep="string" #will look for a string in every commit comment
git log --graph --decorate  #will show branches as a tree
 
= Resolve git conflicts =
When you get into conflicts your index is locked so you cannot change the current branch until cnflicts are resolved.
 
The files with conflicts will be changed and markers added on:
 
<<<<<<< HEAD:file.txt  #this is a delimiter
Hello world                    #this top part shows '''current branch''' file content
=======                #this is delimiter
Goodbye                        #bottom part whows content of the file from '''master branch'''
>>>>>>> master        #this is delimiter to delete
 
You can edit the file keep or remove the offending lines to resolve the conflict. Then remember to add the file to commit using <code>git add folder/file.txt</code>
 
You can also setup vimdiff (multi-window comparision) as the default git merge tool, plus disable a prompt when running <code>git mergetool</code>
git config merge.tool vimdiff
git config merge.conflictstyle diff3
git config mergetool.prompt false
 
= Git config - configuration =
Git comes with a tool called git config that lets you get and set configuration variables that control all aspects of how Git looks and operates. These variables can be stored in three different places:
 
* <code>/etc/gitconfig</code> contains values for every user on the system and all their repositories. Option '''--system''' reads this file
* <code>~/.gitconfig</code> or <code>~/.config/git/config</code> specific to your user, '''--global''' option writes to this file
* <code>.git/config</code> config file in the Git directory of whatever repository you’re currently using: option '''--local''' is specific to that single repository.
 
Each level overrides values in the previous level, so values in <.git/config trump those in /etc/gitconfig.
 
On Windows systems, Git looks for the '''.gitconfig''' file in the '''$HOME''' directory (C:\Users\$USER for most people). It also still looks for /etc/gitconfig, although it’s relative to the MSys root, which is wherever you decide to install Git on your Windows system when you run the installer. If you are using Git for Windows 2.x or later, there is also a system-level config file at C:\Documents and Settings\All Users\Application Data\Git\config on Windows XP, and in C:\ProgramData\Git\config on Windows Vista and newer. This config file can only be changed by git config -f <file> as an admin.
 
= Set all branches to be tracked =
#!/bin/bash
for branch in `git branch -a | grep remotes | grep -v HEAD | grep -v master `; do
    git branch --track ${branch#remotes/origin/} $branch
done
 
Download/fetch all tracking branches
git fetch
 
= List all your repositories =
Replace <tt>$GHUB_TOKEN</tt> with authorization token.
<nowiki>alias ghrepos="curl -s -i -H 'Authorization: token $GHUB_TOKEN' \>
                https://api.github.com/user/repos | grep -o 'https://github.com[^\"]*.git'"</nowiki>
 
= GitLab - download all repositories =
You can get token when log in to your account > settings and generate access token. Assign all permissions. Also so you know v3 APIs may be deployed on older version Gitlab.
<source lang="bash">
#!/bin/bash
 
# Get pages count of all repos you can access
# $ curl --head "http://<host.com>/api/v4/projects?private_token=<token>=100&page=1" | grep Total
#  X-Total: 509
#  X-Total-Pages: 6
 
#Create JSON file with repo name and path
#for PAGE in {1..6}; do curl "<host.com>/api/v4/projects?private_token=CMQdz-Qik8XanGYJTbnH&per_page=100&page=$PAGE" | jq --raw-output --compact-output ".[] | { "path": .path, "git": .ssh_url_to_repo }" >> repos.json; done
 
[ -d repos ] || mkdir repos
cd repos
while read repo; do
    THEPATH=$(echo "$repo" | jq -r ".path")
    echo "path: $THEPATH"
    GIT=$(echo "$repo" | jq -r ".git")
    echo "git: $GIT"
 
    if [ ! -d "$THEPATH" ]; then
        echo "Cloning $THEPATH ( $GIT )"
        git clone "$GIT"
    else
        echo "Pulling $THEPATH"
        (cd "$THEPATH" && git pull)
    fi
done < "../$FILENAME"
cd ..
</source>
 
 
Example repos.json
<source lang="json">
{"path":"project-x","git":"git@<host.com>:Spikes/project-x.git"}
{"path":"project-y","git":"git@<host.com>:Spikes/AWS/project-y.git"}
</source>
 
= Add an empty directory to a repository =
Although it's not supported by design to track an empty directories you can add .gitignore file
find . -name .git -prune -o -type d -empty -exec touch {}/.gitignore \;
with this content inside, however I've tested and an empty file did the job.
# Ignore everything in this directory
*
# Except this file
!.gitignore
Remember to remove or alter the .gitignore file if you want to start tracking new files within such directory.
 
=Tags=
Add tag in your local branch
$ git tag version1  #add
$ git tag            #list all tags
$ git push version1  #push only a particular tag
$ git push --tags    #push all tags to remote
 
Delete a tag locally then remove from remote repo
git tag --delete version1
git push origin :refs/tags/version1
 
=Resources=
*[https://git-scm.com/book/en/v2 Pro Git book written by Scott Chacon] v2
*[https://git-scm.com/blog Git workflow]
*[https://try.github.io/wrap_up Git training]
*[https://www.codeschool.com/users/2384618 My profile at Code School]
*[http://longair.net/blog/2009/04/16/git-fetch-and-merge/ git: fetch and merge, don’t pull]
*[https://atom.io/ Atom cross platform text editor that syncs with Git]
*[http://nvie.com/posts/a-successful-git-branching-model/ A successful Git branching model] Git flow diagram

Latest revision as of 10:19, 7 November 2019