Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
w-c4ds/01-learning_resources/command_line_git.Rmd
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1113 lines (812 sloc)
37.3 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
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 # cut changes away | |
git stash pop # paste last changes | |
git stash pop stash stash@{1} # pop the second most recent | |
git stash save -m "message" # add dedicated meesage to stash | |
git stash --include-untracked # to also temporarily remove untracked files | |
git stash -k # stash away files, but not the ones already staged (i.e. in the index) | |
# or equivalently | |
git stash --keep-index | |
git stash | |
``` | |
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 | |
``` | |
### Show old versions | |
You can show an old version of a file like this: | |
```{bash} | |
git show hash:path/to/file | |
``` | |
If you want to save it to a file, | |
```{bash} | |
echo "$(git show hash:path/to/file)" > file | |
``` | |
Quotation marks are needed to preserve line breaks. | |
If you want to repace a file with a certain version, just | |
```{bash} | |
git reset has -- filename | |
``` | |
### 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 | |
``` | |
Note that you need to to this every time you push a branch. Pretty annoying. | |
We can set an option for this once: | |
```{bash} | |
git config --global push.default current | |
``` | |
Now, just `git push -u` is sufficient. | |
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. | |