Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
920 lines (763 sloc) 36.3 KB
---
title: "Using git"
author: "Lorenz Walthert"
date: "13. Mai 2016"
output:
html_document:
toc: yes
toc_depth: 4
toc_float: yes
pdf_document:
toc: yes
toc_depth: '4'
---
```{r, global_options,include=F}
knitr::opts_chunk$set(eval = F) # disable eval
```
## Basic Commandline navigation
### Key terminal commands
```{bash}
help # some basic information
ls # show all elements in directory
ls -la # structure the content a bit more and also show hidden files (starting with a .)
ls -t # order according to the date.
ls -rt # oder according to date, but in reverse order.
cd /Users # change directory to /Users
cd Program # change the directory relative to the current directory
cd .. # up the tree in the directory
cd # go to the home directory
echo $PATH # show the environment variable Path
echo $PATH > file # replace the content of the file with the path variable
echo $PATH >> file # append the path variable to the content of file
pwd # show working directory
rm testfile.txt # remove a file
rm -rf testfolder # -r for recursive force for removing folders
mv testfile.txt subolder_x/movedtestfile.txt # move a file to a subfolder_x and rename it
cp testfile.txt subolder_x/movedtestfile.txt # copy the file instead of moving it
cp test-a test-b # put the folder tes-a into test-b
cp -r test-a test-b # copy all *contents* of test-a into test-b
mkdir hithere # create new folder called "hithere"
cat testfile.txt # display content
head testfile.txt # display 10 first line
tail testfile.txt # display last
```
#### some advanced stuff
*general*
```{bash}
ls -ld -- */ # list all directories in the working directory
ls -a # list all
ls -l # list in long format
ls -t # sort by time
ls -rt # reverse order.
top -o cpu # list all processes sort by CPU usage
# the find function
find [path] [what to do]
find ~/ -name "vorschau2.pdf" # find file with the name "vorschau2.pdf"
find . -print # in current directory and below, show all file names
find . -newer vorschaut.pds -print # in current directory and below, show all file names
find / -name "*.doc" -print # in root directory (and below), take the files that match "*.doc" and print them
# copy a whole directory structure without the files
find ./SEPU -type d -exec mkdir -p ./SEPU_copy/{} \; # within subdirectory SEPU,
# find files of type directory and execute: mkdir [path]
## ............................................................................
## count words, lines ect. in files
wc # word, line, character and bite.
wc -l untitled # get lines for the file untitled.
wc -w untitled # get words for the file untitled.
#> 0 # if it has one line
```
*regex*
```{bash}
## ............................................................................
## search for stuff ####
grep "^df" *.do # search for a file starting with df among all files with a .do extension and list them.
grep -r "^op" . # search for files that have a line starting with op and do that
# recursively for all directoryies below current working directory and show content
grep -r -l "^op" . # only list the names of the file, don't show the content
# you can combine options. The following is the same.
## ............................................................................
## replace stuff ####
perl -pi -w -e 's/toreplace/isreplaced/g;' **.txt
# whereas toreplace is the regex string to search and isreplaced is the string
# put there.
perl -pi -w -e 's/add_ws_to_parse_data/enhance_parse_data/g;' **.R
```
**links**
You can create an alias (aka known as symbolic link) to an file or directory.
```{bash, eval = FALSE}
ln -s vim_test vim_symlink # create vim_symlink that points to vim_test
```
### Interactive usage
```{bash}
# e.g. after
git diff --help # displaying the git diff help file
# you may press
SPACE # to get to the next page
b # to go one page back
q # to get to the next page
ctrl+a # get to the beginning of a code line
ctrl+f # get to the next letter in the code line
ctrl+b # get to a letter back in the code line
ctrl+e # get to the end of a code line
```
### Auto completion
**[TAB]**
```{bash}
[TAB] #will autocomplete what you write if there is an object that matches up.
#instead of typing
cd directory1/anotherdirectory
# you might want to type
cd di[TAB]/an[TAB]
# when your directory names start with numbers, i.g.
./1-data/1-original_data/3-shapefiles/
# you can save a lot of typing by using the tab, since you can ge to this directory by typing
1 [TAB] 1 [TAB] 3 [TAB]
```
**[Stars] \\* & \\*\\* **
```{bash}
# you might want to use auto completion with the star (*). It can be leading or trailing:
git add self* # instead of
git add self_training.ado
# or
git add .ado # instad of
git add self_training.ado
# if there are multiple matching objects they are all contained in the command
```
Two consecutive asterisks ("**") in patterns matched against full pathname may have special meaning:
* A leading `**` followed by a slash means match in all directories. For example, `**/foo` matches file or directory "foo" anywhere, the same as pattern "foo". `**/foo/bar` matches file or directory "bar" anywhere that is directly under directory "foo".
* A trailing `**` matches everything inside. For example, `abc/**` matches all files inside directory "abc", relative to the location of the .gitignore file, with infinite depth.
* A slash followed by two consecutive asterisks then a slash matches zero or more directories. For example, `a/**/b` matches `a/b`, `a/x/b`, `a/x/y/b` and so on.
* Other consecutive asterisks are considered invalid.
## Files to be ignored by git
### If the file was not tracked so far
#### Basic
Explicitly list files/directories to be excluded from version control. This is the simplest and most used case.
To specify which files you don't want to be tracked by git, create a file called `.gitignore` (no extension!) in the relevant directory in which all the file characteristics that identify files that should not be tracked are stored. You can create and open the file like this
```{bash}
touch .gitignore # creates an empty file .gitignore
open .gitignore # open the file with standard text editor, which is TextEdit
```
Now, within TextEdit, use one line per specification, for example:
```{bash}
text1.txt # uniquely identifies the file text1 not to be tracked by git
*.txt # track no txt files
a/path/to/* # all files in a certain subdirectory
```
Save the file and close the window. All files starting with a dot are system files and can't bee seen by the user in the Finder. You may see them if you type `ls -la` on the command line. If you just want to see the content of the file printed on the console, type `cat .gitignore`.
#### Advanced
Instead of providing a list with files to **ignore**, you might want to include a list of files to **include**. Then there is two cases. \
\
**The file is the git directory** \
This is the easier case. Simply add the files not to be tracked precending an exclamation mark. E.g if you don't want to ignore everything except for .Rmd files in the directory. write the following in your `.ignore` file:
```{bash}
./* # ignore everything
!*.R # but .R files
```
**The file is the a git subdirectory** \
In this case, you have to manually include every directory that is a parent directory of what you want to include. For example, if you want to *unignore* (that is, add to git version control) all `.R` and `.Rmd` files in the subdirectory `r-project` in the folder `code` but nothing else, you have to go like this
```{bash}
r-project/* # exclude the folder r-project
!r-project/code/ # but include the subdirectory code
r-project/code/* # exclude the folder, but ...
!r-project/code/*.R # not .R files
!r-project/code/*.Rmd # not .Rmd files
```
Note from Stackoverflow: The trailing /\* is significant:
* The pattern dir/ excludes a directory named dir and (implicitly) everything under it.
With dir/, Git will never look at anything under dir, and thus will never apply any of the “un-exclude” patterns to anything under dir.
* The pattern dir/\* says nothing about dir itself; it just excludes everything under dir. With dir/\*, Git will process the direct contents of dir, giving other patterns a chance to “un-exclude” some bit of the content (!dir/sub/).
### If the file was previously tracked
This basically involves removing a file from the version control but keep the checked out version in the local repository.
```{bash}
git rm text.txt --cached # or rm -- cached text.txt
```
Now the file is added to the list "Untracked files", but still in the local repo. Now, you can add the file to your `.gitignore` file.
## Staging area
#### Basics
```{bash}
git init # initialize a repository
git status # show the status of files
git add text1.txt # add textfile text1 to the stagging area
```
#### Advanced
```{bash}
git reset HEAD text1.txt # remove textfile text1 from the stagging area if it
# was there
git reset HEAD~1 --soft # remove the last commit from the index and add it
# to the stagging area (green text). No local changes until you do reset
# HEAD and checkout
git reset HEAD~1 # remove the last commit from the index and don't add it
# to the stagging area (red text). No local changes until you check out
git add -A # add all unstagged files in working directory to stagging area
git add t* # add all files in the directory that start with t
git ls-files # show files tracked by git
```
#### Renaming files
If you want to rename a file, you have multiple options: \
\
**Rename the file in the finder** \
Although straight forward, this method has a fundamental disadvantage: git won't recognize this file as the pre-reamed version of the file easyily, so it thinks of it as a new file, whereas the old one has been deleted. Hence, you loose all the history if you decide to go for this option. \
\
**Rename the file from the commandline** \
An alternative is to use `git mv oldname newname` to avoid the problem described above. git will recognize the file as *renamed*, as you can see with `git status`.
```{bash}
git mv 01-preprocessing.do 01a-preprocessing_mainform.do # rename without move or
git mv 01-prepcoressing.do ./01-code/01-prepcoressing.do # ... only moving, not renaming
```
After having committed the renaming, the changes are tracked, but by default, not shown, since the file has a different name now. To show the changes, we need to add the option `-- follow 01-preprocessing.do`. Note that this is the old name in the old directory.
```{bash}
git log ./01-code/01-prepcoressing.do # won't list any commits since nothing committed after renaming
git log -- follow ./01-prepcoressing.do # won't list any commits since nothing committed after renaming
```
However, keep in mind that renaming should be avoiding when other people use your code because all references to it will be invalid, which means it can break even old versions.
## Committing
#### Basics
```{bash}
git commit -m "hi this is a commit" # commiting staged commits to current branch
git checkout -- text1.txt # dischard local changes for text1.txt and set local back to HEAD (the latest commit)
```
#### Advanced
```{bash}
# skip staging
git commit -a -m "all files" # commit all unstaged changes of tracked files in the directory
git commit * -m "all files" # the same
# amend
# change the message for the latest commit
git commit --amend -m "this is actually the correct message"
# the following only works if you have added files to the staging area. Adds these
# files to the latest commit and chnages the message.
git commit --amend -m "new message new file"
# add staged changes to latest commit without changing message
git commit --amend --no-edit
# empty commit
git commit --allow-emty -m "message" # create an empty commit
# commit description
git commit -m "Commit message" -m "Commit description" # add description to commit message
# edit commit message in vis editor
git commit
# the editor opens: write your message, hit ESC :wq (for write + quit) when you
# are done, :!q to exit without a message, i.e. to abort.
```
#### Stashing
Sometimes, you will find yourself working on something that is not yet ready to
commit, but then suddenly you need to work on something else (e.g. an urgent
bug-fix). In this case you want to save your current changes to a clipboard
without committing them yet. In this case, `git stash` is your friend. It
puts your changes aside and leaves you with a clean working directory.
```{bash}
git stash # put changes aside
git stash # nothing to commit
```
You can also get a list of your stashes, the top one being the most recently
added.
```{bash}
git stash list
```
To get a stash back, you have multiple options
```{bash}
git stash pop # restores latest stash and removes (!) it from your clipboard
git stash apply <stashname> # same, but does NOT delete stash from the clipboard.
```
You can use `git stash save 'my title for this stash'` to create a title for a
stash you saved so you can recognize it easier later.
Also, if you don't want to stash all changes, do `git stash -p` to select all
changes you want to stash with `y` and abandon changes you don't want to stash
with `n`.
If you wish to stash some of the changes, but not all, you can add the files
you want to stash to the stagging area with `git add file1 file1`.
## Cloning
You might want to join an existing project and get a local copy from a remote repository in an URL. Some repos might be protected with password. Not the one below.
```{bash}
git clone https://github.com/gittower/git-crash-course.git # clone example repo
```
## Working with branches
### show, create, delete and rename barnches
```{bash}
git branch # show all branches (green star shows current branch)
# show all branches (green star shows current branch) and some additonal information
git branch -v
git branch coffee # create a new branch "coffee"
git branch -d coffee # delete the coffee branch
git branch -m A_hotfixDropdown # rename the current bracht to A_hotfixDropdown
```
### Switching branches
```{bash}
git checkout # dischard changes in stagging area.
git checkout coffee # switch to the coffee branch
git checkout - # switching to the last branch you were at
# create a new branch called old-roject-state that is the current branch state
# at commit 0ad5a7a6
git checkout -b old-project-state 0ad5a7a6
```
## Integrating a full branch via merging
As soon as you are done with your work, merge the changes back to `master`.
Imagine the following scenario:
* You have a clean branch `master`
* You branch out to fix an issue with creating a new branch `hotfix_212` with
`git checkout -b hotfix_212`
* You are done with the fix and want to merge back.
* In the meanwhile, you changed some code lines in `master` as well.
* You committed all your changes in both `hotfix_212` and `master`
### Case 1: No conflicts
The easy case, where you don't have conflicts between the branches (i.e. you did
not change the same line of code in both branches, which you can check with `git
diff master..hotfix_212`) has the following workflow.
```{bash}
git checkout master # switch to the master branch
git merge hotfix_212 # select the branch you want to merge into master.
git branch -d hotfix_212 # after you are certain that everything works.
```
### Case 2: Conflicting changes
#### Manual resolve
It gets a bit more tricky if you have deleted one file in one of the branches
or if you changed the same line of code in both branches. However, start the same
way.
```{bash}
git checkout master # switch to the master branch
git merge hotfix_212 # select the branch you want to merge into master.
```
Now, git will tell you that there is a conflict, so the merge is put on hold.
Open the file of conflict (e.g. `conffile.txt`) and you will see that git
separated the conflicting sections with >>>>>>>>>>>>>>>>>>> and >>>>>>>>>>.
Resolve the conflicts by rewriting those lines and finally get rid of the markers.
Alternatively, you can configure a merge tool like diffuse or diffmerge.
Add the file to the stagging area and commit the changes as usual. This will
complete the merge.
```{bash}
git add conffile.txt
git commit -m "Merge conflict solved for conffile"
# now your merge is completed.
git branch -d hotfix_212 # after you are certain that everything works.
```
#### Using merge strategies
Instead of resolving by hand, you can apply merge strategies and corresponding
options. Strategies are specified with `-s`, options follow after `-X`.
The default strategy for a merge is `recursive`. So `git merge -s recursive` is
a verbose form of `git merge`. If we want to merge a branch into another and
we have conflicts (because some lines were changed in both branches) we can
specify options how to proceed. Two popular options are `ours` and `theirs`.
Let's look at an example. We are done with a new feature on our branch
devel and we want to merge it into master. In the development process, a hotfix
was committed to master, for which we accounted in devel already. Therefore, we
anticipate that there will be conficts in the merge and that we want to use
the devel version whenever there are conflicts, since we already
accounted for the problems. We would proceed as follows:
```{bash}
git checkout master
git merge devel -s recursive -X theirs # theirs refers to the other branch
# equivalent
git merge -s recursive -X theirs devel
```
We could also imagine a scenario where we want to merge changes from devel
into master, but conflicts should be solved in favor of master
```{bash}
git checkout master
# favour our (the branch we are on) over the one we integrate
git merge devel -s recursive -X ours
```
Note that above area all *options* to the *strategy* recursive.
Something you are less likely to want is to merge a branch
into another but actually discarding all changes introduced by this other
branch. This would be the *ours* merge *strategy.* Suppose there is a
devel branch and it lead to nothing, you just want to "close" the branch and
merge it into master, without actually taking any commits from devel. You would
do
```{bash}
git checkout master
git merge -s ours devel # reintegrate devel by not taking anything from it
```
Another use case is if you don't want to integrate all commits from devel
into master. Let's say you want to merge everything up to `head~3`, not
`head~3` but all commits after `head~3` again. You could do the following:
```{bash}
git checkout master
git merge devel~4
# next, mark devel~3 as integrated in master
git merge devel~3 -s ours -m "not actual merge, skip commit xyz"
# since devel~3 is marked as integrated, merging devel with master now
# will merge devel~2/1/current.
git merge devel
```
### Aborting a merge
If you want to undo a merge whilst you are in the middle of it, just use
```{bash}
git merge --abort
```
If you already finished the merge but wish to undo it, simply restore the commit
on the master branch before the merge, e.g. However, you need to have the branch
`hotfix_212` still so you can also recover this version.
```{bash}
git checkout master
git revert dsf0840sdlkj --hard
```
to restore a given hash.
### Integrating selected files via checkout
You can also just merge one file from branch x with branch y. However, this is
technically not merging, but you just checkout a version from a different branch
in your target branch. Assuming you are in branch `devel` and you want to merge
the changes from the file `reduce.R` of this branch into `master`
```{bash}
git commit files/reduce.R -m "fix if statement" # on devel
git checkout master
git checkout devel files/reduce.R # git checkout source_branch <paths>...
git status # changes already in the stagging area
git diff --cached # to see stagged chagnes
git commit files/reduce.R -m "adapt fixing if statement from devel"
```
## Rebase
### Changing the order of commits
You can change the order of commits. Have a look at the recent history using
`git log --pretty=oneline`.
```{bash}
a931ac7c808e2471b22b5bd20f0cad046b1c5d0d c
b76d157d507e819d7511132bdb5a80dd421d854f b
df239176e1a2ffac927d8b496ea00d5488481db5 a
```
Where a is the first commit, and c the last one.
Using `git rebase --interactive HEAD~2`, an interactive window opens. Note that
now, going down the list means going from old to newer commits (unilke above).
```{bash}
pick b76d157 b
pick a931ac7 c
# Rebase df23917..a931ac7 onto df23917
#
# 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
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
```
You can now change the order of the commits by c/p the second line (commit c)
to the top and save and exit.
### Squashing commits
This refers to merging multiple commits into one. Again, use `git rebase -i` as
a starting point. First, reanrange the commits as described above. Squashing
means merging multiple commits into one. Say, if you wanted to squash commit
c into b, you had to change the following:
```{bash}
pick b76d157 b
squash a931ac7 c
```
Read this from top to bottom:
Hence, first commit b is picked (applied to the code base), then commit c is
added on top of it to form one commit with b. Finally, you get to rewrite the
commit message:
```{bash}
# This is a combination of 2 commits.
# The first commit's message is:
b
# This is the 2nd commit message:
c
```
### integrate upstream changes
Lets assume there is a repo on GitHub you want to contribute to. Since you cannot push
directly to it, you need to create a fork (on GitHub) first, clone this fork
to your machine and commit your changes to the fork, push and then submit a pull
request. Let us assume you are done with your changes and you pushed to the
fork but then you can't merge with the upstream because in the meantime
(i.e. after you forked until now) there were some other people comitting to
the upstream branch and your fork is out of date. You can rebase your fork onto
the upstream, i.e. apply all commits that the upstream is ahead of your fork
to the fork and then, on the tip of this tree, apply your commits.
```{bash}
git pull --rebase # or to be explicit
git pull --rebase <remote name> <branch name>
```
## Undo things
### Go back to a previous version in the git repo
If you deleted a file in version control, please refer to the paragraph
*recover deleted files*.
**Create a new branch**
The best way to restore a previous stage is probably just by creating a new
branch with that stage indicated with a hashtag.
```{bash}
git checkout -b 123keri5
```
**Revert**
Use `revert` to create a new downstream commit that removes a specific
(upstram) commit. You are NOT destroying anything.
```{bash}
git revert 123keri5
```
**Cherry-pick**
In some sense, cherry-pick is the opposite of `git revert`. It allows you to
apply arbitrary commits from other branches to your current branch. You can also
specify multiple commits at once.
```{bash}
git cherrypick e14jd 3kg05 # apply two commits to our current branch
```
**Reset**
Use `reset` to go back to a previous commit and delete all commits between the
latest commit and the one you want to go back to.
```{bash}
# create a new commit equal to the hash of a previous commit.
git reset --hard 123keri5
```
### Recover deleted files or deleting files
In contrast to the situation where one wants to restore a previous version from
git, having deleted a file from git does not easily allow to restore it, since
it is no longer tracked.
Imagine the following situation:
* your file `example.txt` was tracked by git.
* you decided to move the file into the folder `example`. The file is no longer
in the original directory.
* now, you type `git status`and it says "deleted: .." in red, meaning that it is
not yet stagged.
* type `git checkout -- example.txt` to discard the removal and restore HEAD.
* on the other hand, if you want to confirm the removal, type
`git rm example.txt`. Note that, if you want to remove a whole directory, you
will need the `-r`flag as a double check.
* Now, after typing `git status` the "deleted: ..." is displayed in green,
saying that changes are stagged.
* To go back, you might type `git reset HEAD` (to unstage) and then
`git checkout -- example.txt` to restore everything.
## Inspecting differences
### Changes between tracked files
####`git log`
`git log` shows a log book with all commits
```{bash}
git log # show the log / history of the git repository
# show the patch (the difference to the latest revision), and only show first 3 entries
git log -p -2
# layout
git log --stat # gives statistics such as number of changed line and changed fils
git log --pretty=oneline # one commit per line. Helpful to look trough a lot of commits.
git log --name-only # gives you the names of the involved files as well
# filtering the log
git log --author=jonmcalder # only show commits from Jon
# sohw all commits for which the message started with update or Update.
git log --grep=^[Uu]pdate
git log -p --grep=^[Uu]pdate # also show the code changes
# show all commits that were committed to the development branch but not (yet)
# to the master branch. Note that for pull requests, Github shows you
# automatically the commits that differ from two branches.
git log devel ^master --no-merges
```
On a side note, you can also see the whole list of git commands (not only
commits) using `git reflog`. For example, if you switched from one branch to
the other, it shows you where you switched *to*, but not where you switched
*from*. So if you forgot the branch you are coming from, just type `git reflog`
and you will be able to see a log of all commands and a comment.
#### `git diff`
`git diff` is designed to highlight changes between two revisions or arbitrary
commits or files. \
**between HEAD and uncommitted changes**
```{bash}
git diff # inspect differences between HEAD and local file (= unstagged changes).
git diff --cached # inspect differences between HEAD and INDEX (= stagged changes).
# instead of higlighting a whole line when a character has changed, we only
hihglight words that have changed.
git diff --word-diff
```
**between committed revisions**
```{bash}
git diff HEAD~2 HEAD # difference between current HEAD and the one two commits ago.
# difference between current HEAD and the one two committs ago for text1.txt
git diff HEAD~2 HEAD -- text1.txt
git diff HEAD^ HEAD ## difference between current HEAD and the one a commit ago.
## displays names (not actual difference) for changed files between two revisions
git diff HEAD^^ HEAD --name-only
## show the names of the files where "gen" has been added or removed since the
## last 2 commits.
git diff HEAD^^ HEAD -G"gen" --name-only
# for the latest two commits that involved changing "csv" highlight all changes.
git diff HEAD~2 HEAD -G"csv"
# only highlight changed words, not lines. Plain is the default for word-diff.
# Differences are indicated using a plus and a minus and color.
git diff HEAD~2 HEAD --word-diff="plain"
# only highlight changed words, not lines. No plus / minus, only color.
git diff HEAD~2 HEAD --word-diff="color"
# same as the one above. Highlight changed words.
git diff HEAD~2 HEAD --color-words
# Now, only highlight changed words if they meet regular expression (eg. GX7).
# Not all words matching the regex are highlighted (otherwise it would not be
# very helpful), but only the ones that changed from the second latest revision
# to the latest.
git diff HEAD~2 HEAD --color-words="GX." text1.txt
```
**between branches** \
```{bash}
# inspect all the differences between HEAD oftwo branches (master and branches).
git diff master..branch1
git diff master branch1 # same as above
# inspect the differences between two branches (master and branch1) for a
# specific file
git diff master..branch1 -- text1.txt
# inspect all the changes that have occured since branch 1 was created from
# master.
git diff master...branch1
```
### Differnces between untracked files
to inspect two files and see thir difference, you can also use git. The
respective files don't even have to be tracked by git.
```{bash}
# compare the two files a.do and b.do in the working directory.
git diff --no-index a.do b.do
```
## Tagging
Tags can be used to create a reference to a certain stage of the directory,
which we can think of as verisons. There are two kinds of tags:
* lightweith tags are simple tags.
* Annotated tags are tags with attributes (such as author, tagging message and
so on.
In contrast to hashes that refer to a commit on the tree of a file, tags
typically refer to more than one file. For example if three files make up a
functioning suite, then once all three work fine together, we might want to tag
the bundle of all current versions of the three files and be able to restore
them all togehter very easily.
```{bash}
# pull tag. This happens by default with the following standard commands
git pull
git fetch
# list tags
git tag # list all tags
# list all tags starting with v3 and the first 9 lines of comment
git tag -l -n9 v3*
# create tags
git tag v1.0 # create a lightweith tag for the current HEAD.
# create annotated tag with message at HEAD
git tag -a v1.0 -m "this is my version 1"
# create an annotated tag at an arbitrary hash
git tag -a v0.0.9000 -m "tag retrospective" e4063df8558b77ba6927cbc45438cfa119b0b34b
# remove tags
git tag -d -- v1.2 # remove the tag v1.2 locally
# remove remote tags using push(requires up-to-date branch
# (including tag references))
git push --delete origin v0.2.0.901
# push tags
## by default, tags are not pushed along with commits with git push.
git push --tags # To push commits and all tags
# To push commits and tags only if they are annotated
# and reachable (that is, from related branches). Hence, use lightwithg tags
# for minor changes
git push --follow-tags
## to push specific tags, list them in after pus
git push origin v1.0.1 # v1.0.1 must be included explicitly
# push up to commit fji3wy62k to upstream master
git push upstream fji3wy62k:master
# revert
## replacing tracked files in working directory with their state when tag v1.0
## was set. Since more than one file involved, this will result in a situation
## where the files are 'detached' from HEAD and where we are not on branch. If
## we want to continue working with v1.0 (e.g. for a hotfix) we shold probably
## create a new branch with v1.0, e.g by typing
git checkout tag v.10
git checkout -b v1.0_hotfix
# switch to the latest HEAD of the branch master and discard potential changes
# made since reverted v1.0.
git checkout master
```
## Remote Repositories
### Cloned Repository
#### Get the repository
If the code you want to work with is in a remote repository, you need to get a
local copy before you can start. Here, we juse a repository called
`a_git_test_folder`.
```{bash}
# download a copy of the current code at github
git clone https://github.com/lorwal/a_git_test_folder.git
cd a_git_test_folder # change your working directory into this folder
```
#### Inspect the repository
Let's now look at the branches in this git repository. The first two commands
were introduced in this text further above.
```{bash}
git branch # list you the (local) branches
git branch -v # displays the latest commit message along the branch
# displays all branches, that is, also remote branches that are stored localy
git branch -va
```
From looking at the terminal output, you can see that
* remote branches have a name starting with `remotes`.
* by default, the cloned repository is called `origin`. `origin` is actually
just a placeholder for the full url where the repository originates from. we
can give this repository an arbitrary name.
* our cloned repository has different branches. These are the branches
available:
+ all the branches in the remote branch
+ a branch called HEAD, which is a pointer to the branch that is the
current remote head
* remote branches are displayed in red when coloring is enabled
In contrast to what you saw before, this repository is "connected" to the
remote repository. This is also visible when typing `git status`, where you
will see more information than if it was an "unconnected" local repository. The
added information for now here is that your local branch master is up to date
with the remote repository *origin/master*.
To see the remote "connections", type the following command is your friend.
```{bash}
git remote -v # show what origin points to
```
There are always two directions of a remote repository, one is the one where
you fetch (download) code from, the other one is where you push (upload) your
committed work. In simple settings, the two are the same. You can also add new
remote commections like this:
```{bash}
git remote add alternativeorigin https://github.com/lorwal/a_git_test_folder_alternative.git
```
#### Use other than the master branch locally.
By default, you will be on the master branch. However, you might want to wok on
a different branch. All other remote branches that are shown with
`git remote -va` are not local copies. To generate local copies, simply use
```{bash}
git checkout --track feature2
```
To create a local copy of the remote branch feature2. The `track` option means
that git tracks how the remote branch and the local branch differ, e.g. when
using `git status`. Such a relationship between the local and remote master is
established automatically with `git clone`. This tracking relationship also
allows us to use `git fetch`, `git pull` and `git push` without further
instructions. This is in most cases the desired behaviour.
#### Push new local branch
Assume you branched out for a new feature (feature3) locally, and you want to
push the changes to github. If you want to push an entire branch that does not
yet exist in the remote repository, a simple `git push` will fail. To add the
branch as a new remote branch, use
```{bash}
git push --set-upstream origin feature3
```
If you intend to merge this new banch with another branch at github, go to
github and open a pull request. The owner of the repository can then decide
when to merge the proposed branches on github.
Alternatively, you can merge them locally and then push to the final branch,
e.g. merge with `master` before you push to the master branch.
## Configuring
You got global git configurations saved in `~/yourusername/.gitconfig`.
They can be altered using `git config --global your_command`.
On the other hand, you can also set config options in a given directory -
without the global option. These configurations will then only apply to this
folder.
See [this link](http://stackoverflow.com/questions/4220416/can-i-specify-multiple-users-for-myself-in-gitconfig] for an extensive discussion.
## Workflow
### Basics
The basic workflow is as follows:
* you clone a repository before you start (as we just did).
* you commit changes to the local repository.
* After a few commits, the commits form a 'bundle' and you are ready to push.
Maybe, just in time, you find something else you want to change that actually
belongs to your latest commit. Use `--amend` and `no-edit` to modify the latest
commit. This is only possible before you push.
* you use `git push` (or an explicit form, e.g. `git push origin master` to
push the master branch to the corresponding remote branch). Do not wait too
long with pushing to avoid merge conflicts with other people. You need to pull
the latest version before you push.Just before you push your changes, you local
branch is "ahead" of the remote branch if the remote branch was not modified
since you cloned it. If we work on the master branch and make one commit, our
local branch will be "ahead of origin/master by n commits", as you can read
when you type `git status`.
* If other people are contributing, then your previously cloned repository gets
out of date. You have to download the latest version of the remote repository,
inspect changes and merge them with your repository before you can push them to
the remote repository. Use `git fetch` to update the copy of the remote
repository on your local disc. You can use tools like `git diff` or `git log`
to compare the versions. At that stage, your local (unmodified) copy is said to
be "behind" the remote repository. If you are ready to merge the cahnges, use
`git merge` to merge the copy of the up-to-date remote and your changes made
based on the old version of the remote. `git pull` is the fast way to do that:
It executes the fetching and merging in one step.
* Note that you cannot push code if it is based on an out-of-date repository.
### Advanced