Command line git

global_options,include
1
knitr::opts_chunk$set(eval = F) # disable eval

Basic Commandline navigation

Key terminal commands

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

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

eval
1
ln -s vim_test vim_symlink # create vim_symlink that points to vim_test

Interactive usage

1
2
3
4
5
6
7
8
9
10
11
# 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]

1
2
3
4
5
6
7
8
9
10
[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] \ & \*\ **

1
2
3
4
5
6
7
8
9
10
11
# 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

1
2
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:

1
2
3
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:

1
2
./* # 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

1
2
3
4
5
6
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.

1
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

1
2
3
git init # initialize a repository
git status # show the status of files
git add text1.txt # add textfile text1 to the stagging area

Advanced

1
2
3
4
5
6
7
8
9
10
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.

1
2
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.

1
2
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

1
2
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 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.

1
2
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.

1
git stash list

To get a stash back, you have multiple options

1
2
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.

1
git clone https://github.com/gittower/git-crash-course.git # clone example repo

Working with branches

show, create, delete and rename barnches

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
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.

1
2
3
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.

1
2
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.

1
2
3
4
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:

1
2
3
4
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

1
2
3
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

1
2
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:

1
2
3
4
5
6
7
8
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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.

1
2
3
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).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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:

1
2
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:

1
2
3
4
5
6
7
8
# 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.

1
2
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.

1
git checkout -b 123keri5

Revert

Use revert to create a new downstream commit that removes a specific
(upstram) commit. You are NOT destroying anything.

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

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

1
2
# 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 statusand 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 -rflag 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 \

1
2
3
4
5
6
7
8
9
10
11
12
# 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.

1
2
# 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 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.

1
2
3
4
# 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.

1
2
3
4
5
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.

1
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:

1
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

1
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

1
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

From: https://github.com/lorenzwalthert/w-c4ds/blob/master/01-learning_resources/command_line_git.Rmd

Share

Top counties by vote

My previous post got me wondering about the relative number of Democratic and Republican votes in the top counties:

California, Texas, Florida stand out, with LA alone accounting for ~3% of Clinton’s vote. It would be interesting to know how many of those votes were illegal aliens, given that Obama was encouraging illegals to vote. How is requiring proof of citizenship to vote bad for us as a country? I would go further and require not only proof of citizenship, but also proof of high school graduation or the equivalent. Furthermore, lets put an age cap of 75 years on voting. After 75 years you have had 57 years to influence the political direction of our country. Time to step aside and make room for the next generation. Voting is about the future, and at 75 you don’t have much future left. Furthermore cognitive decline may be having an adverse negative effect on decision making. This will also minimize the impact of voting scams that target the elderly.

process.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
##source: https://raw.githubusercontent.com/tonmcg/US_County_Level_Election_Results_08-16/9db796f730956d0a506d51fa653f583a6eef70a3/2016_US_County_Level_Presidential_Results.csv

library(ggplot2)

rm(list=ls(all=TRUE))

# Location of WARN notice pdf file
location <- '~/syncd/prog/county_data'
filename <- paste( "data.csv", sep="")


d <- read.table (file = filename, sep = ",", dec = ".", header=TRUE, skip = 0, na.strings = "NA", strip.white = FALSE )

d$GOPexcess <- d$votes_gop - d$votes_dem
d$winner <- "Trump"
d$county <- NA

for(i in 1:nrow(d)){
if(d[i,"GOPexcess"] <0) d[i,"winner"] <- "Clinton"
d[i,"county"] <- paste(d[i,"county_name"], ", ", d[i,"state_abbr"], sep="" )
}


dem <- d[d$winner=="Clinton",]
dem <- dem[ order(dem$total_votes, decreasing=TRUE),]
toptendem <- dem[1:10,]
toptendem$xlab <- letters[1:10]


gop <- d[d$winner=="Trump",]
gop <- gop[ order(gop$total_votes, decreasing=TRUE),]
toptengop <- gop[1:10,]
toptengop$xlab <- letters[1:10]

d2 <- rbind(toptendem, toptengop)


ggplot(data=d2, aes(x=reorder(xlab, -total_votes), y=total_votes, fill=winner)) + geom_bar(stat="identity") + scale_fill_manual(values=c("blue", "red")) + facet_grid(.~winner) + scale_x_discrete(labels= d2$county) + theme(axis.text.x=element_text(angle = 90, hjust = 0))

Note that I could not figure out how to apply separate x axis labels across facets and had to resort to cut and paste.

Share

Fear by Bob Woodward

My son, concerned that I have gone down the alt-right rabbit hole, gave me Fear by Bob Woodward as a Christmas gift.

Some take aways from the book:

Trump is a liar
A weakness Mueller hopes to capitalize on (see below).

Trump is combative
As such, he antogonizes loyal supporters (Bannon, Sessions, Dowd, Porter) who eventually abandon him. Consequently his administration is in chaos.

Trump does not understand the legislative process
And does not seem interested in learning. Without the ability to build consensus, his greatest accomplishments may be behind him. (Those would be: opening the Overton window so we can discuss the harm of diversity and unrestricted immigration, sidelining the Bushs and Clintons, alerting the left that there is a formerly silent majority uninterested and even antogonistic to the leftist agendas.)

The Russian hoax
Mueller, a Clinton operative, is charged with getting Trump impeached by any means. His strategy is to entrap Trump in a lie and get him impeached for perjury. The Russian hoax serves as a pretense for the continuing investigations.

Overall interesting to read some of the conversations that occurred prior to the election up to about July 2018. I don’t think this book will dissuade Trump supporters, but will temper any hopes of further advancement during the administration.

Share

Clinton won the popular vote!??

I am familiar with the popular vote by county map shown here but never have seen it as a bar graph. I downloaded the raw data from here and plotted it myself:

So one must define “popular vote”. If you say popular vote is the candidate most popular across the country, I think I could say Trump won “by a landslide”!! Also a good argument as to why we have the Electoral college. We don’t want liberal concerns (BLM, diversity and inclusion, unconscious bias, feminism, LGBTQ) from the large metropolis’ (packed with undocumented voters) to trump the concerns of most Americans (family values, nationalism, solidarity). Also a good argument for implementing voter identification laws.

process.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
library(dplyr)
rm(list=ls(all=TRUE))

location <- '~/syncd/prog/county_data'
filename <- paste( "data.csv", sep="")
d <- read.table (file = filename, sep = ",", dec = ".", header=TRUE, skip = 0, na.strings = "NA", strip.white = FALSE )

d$GOPexcess <- d$votes_gop - d$votes_dem
d$winner <- "Trump"

for(i in 1:nrow(d)){
if(d[i,"GOPexcess"] <0) d[i,"winner"] <- "Clinton"
}

barplot(table(d$winner), col=c("blue","red"))
barplot(c("Clinton"= 65853652,"Trump"=62985134) ,col=c("blue","red"))

out.file <- "final.txt"
write.table( final, file =out.file, append = FALSE, quote = TRUE, sep = "\t", eol = "\n", na = "NA", dec = ".", row.names = FALSE, col.names = TRUE, qmethod = c("escape", "double"))

Share

Useful EMACS programming tips

Despite my many attempts at using an integrated IDE, most recently IntelliJ IDEA, I always end up going back to EMACS. The numerous generic utilities that have been developed over decades allows one to mimic just about any feature of an integrated IDE in EMACS. Furthermore, those features (in EMACS) are consistent across programming languages. Learn once, use everywhere. If you can’t find something, write the elisp code yourself and bind to a button. Here are some notes to remind me of what is available.

  • M-x occur to create a new buffer showing the occurences of a term.

  • M-3 Ctr-x $ fold code (M-[1|2|3] depending on the level of folding desired, Ctrl-x $ to undo

  • M-x imenu to jump to a method

Share

Skill aquisition

This article argues that talent is overrated, excellence can be achieved by anyone, it is a matter of mindful practice, preferably with an experienced coach or teacher. I disagree. I think endogenous talent is extremely relevant as well as is inherited behavioural attitudes, intelligence, and physique. Nonetheless, an excellent read.

The mundacity of excellence

Share

Traditional NH (Quebecois) Recipes

From the December 2018 issue of NH Magazine.

Living on Manchester’s West Side during my childhood, we lived fairly frugal, blue-collar lives. For my millworker parents, penny-pinching wasn’t just a fad, it was a necessity. To make matters even more challenging, in the late ’80s, stable piecework jobs faded away and forced our folks, and many others in our neighborhood, to reinvent themselves. Some went back to college, some started their own businesses, some stayed home and raised a family. It was a tough yet exciting time. Throughout it all the comfort foods of our era stayed constant. Cheap ground meats, filling potatoes, golden brown crusts and boiled things — all of these were at the center of our tables.

With five mouths to feed, it could have been easy to feel hungry or that we were “doing without.” Looking back, however, nothing could have been further from the truth. Whether it be space, activities or food, my parents were experts at making a lot out of a little. My mother, though, was the queen of stretching a dollar. The number of meals she could pull off with just one roasted chicken was the envy of our tenement.

Classic New Hampshire fare was probably never famous for being delicious, sought-after cuisine, but the flavors and sheer act of creation were tied to more than seasoning; there were emotions and memories bound to these foods. Sadly, I’m noticing that this type of low-cost, hearty and nutriment-filled cooking is slowly leaving our dinner tables. The sustenance of my youth, the foods that defined our region and nurtured our souls, is seemingly being replaced by prepared foods sold by the pound.

In a time of smartphones, constant connectivity and instant gratification, bringing back classic dishes enjoyed over generations can not only bring families together, but can perhaps also fix a few of the ailments that plague our country at large. Food brings people together, and it always has. Having eaten many different comfort foods in various contrasting cultural settings, I have yet to witness disaffection while sitting together, mouths full, around anyone’s table. Eating a meal together is a spiritual experience that fills more than our stomachs.

New Hampshire cuisine is the result of frugality and the melding of cultures. We can see it everywhere if we look in the right places — French Canadians, Irish, Portuguese, Italians, Greeks and more — all settled here to make a living and their culinary influence came with them.

In my family, wafts of beef stew, pot roast or my grandmother’s famous pork chop casserole warmed our spirits, especially in the cold winter months. My personal favorites were generally bubbling crocks laden with cheese and butter-cracker-crusts or staples like porcupine meatballs, orbs of beef laden with rice, simmered in a condensed tomato soup “sauce,” and American chop suey, a regional “goulash” overflowing with ground beef, elbow noodles, green peppers, onions and tomato sauce. It’s no wonder I gravitated toward making food for a living.

There was tourtière, a hefty pork pie, and poutine, hand-cut French fries sprinkled with squeaky cheese curds, both smothered with unctuous beef gravy. And how could I forget the staple of boiled dinner, corned beef, potatoes, carrots, turnips and pickled beets. My mom’s spin usually included cheap lobs of kielbasa that would end up a cold snack for the next day’s school lunches.

I fear that an amazing generation of traditional New England foods — big-flavored, inexpensive and uninhibited — are becoming, like the textiles fabricated at the old mills of Manchester, a distant memory.

Baked beans and brown bread or Chinese pie mustn’t go by the wayside. As a chef and aging member of “Generation X,” it is my obligation and duty to carry the torch for those in front of us. It’s time to revisit old favorites or, perhaps, discover some new ones.

I’ve recently had a chance to thumb through a few of my great-grandmother’s old handwritten recipes. The list of favorites is lengthy and inspiring. Yes, there were those classics that she was known for; cat-head biscuits, macaroni and cheese casserole, beef stew and pot roast, just to name a few. The ones that caught my eye, however, were her desserts. Unapologetically fattening, riddled with cholesterol and trans fats, but oh so amazing. As children, we would sit around Gram Carrie’s orange Formica kitchen table eating her shiny, hot-from-the-lard buttermilk donuts tossed lovingly in cinnamon sugar. Those long-forgotten moments came rushing back to me as I read through these perfectly kept pieces of paper, index cards or cutouts from the long-lost magazines of her time. This recipe box was a sort of T.A.R.D.I.S. that transported me back to a time where things were simpler and not yet hazed by clouds, texts or cat videos.

All these amazing morsels were more than just about the sugar, recipe boxes or frying kettles, they were about bonding, growing up and loving each other.

Perhaps our general sense of division in New Hampshire, or anywhere in the country for that matter, could be healed just by taking time on a regular basis to pull down an old cookbook on a rainy day or share a casserole together a few nights a week at the dinner table. The dinner table itself may be among the lost; either turned into a place to stash the piles of mail or sit listlessly staring at our smartwatches. Counting steps may become more important than counting the minutes until dinnertime. These recipes connect us to our past and could heal our present whilst setting us up for a very different future.

In New Hampshire, reestablishing the lost act of eating together, especially those cultural classics, every night could start a positive revolution. Grab a cookbook, call your grandmother or visit the library and make some memories together. I have included four savory dishes that were an important part of my family upbringing, as well as one dessert. These meals are the center of some of New Hampshire’s most cherished comfort foods. I hope you enjoy them.


Porcupine Meatballs

Porcupine meatballs are not only great as a nice, hearty dinner, but they make great leftovers. We make club sandwiches with them the next day.

Ingredients

1 pound ground beef
¼ cup uncooked long-grain rice
1 slightly beaten egg
1 tablespoon dried parsley flakes
¼ cup onion, finely chopped
½ teaspoon garlic powder
¼ teaspoon paprika
⅛ teaspoon ground black pepper
½ teaspoon kosher salt
1 10.75-ounce can condensed tomato soup, divided
½ cup water
2 teaspoons Worcestershire sauce

Instructions

In a medium-size bowl, combine meat, rice, egg, parsley, onion, garlic powder, paprika, pepper, salt and ¼ cup tomato soup. Mix thoroughly and shape into about 20 meatballs and place in a skillet.

Mix remaining soup, water and Worcestershire sauce. Pour over meatballs. Bring to a boil and reduce heat. Cover and simmer for about 25-30 minutes, stirring often.

Rice should be cooked and sticking out of the sides of the meatballs.


American Chop Suey With Hamburg

Similar to American goulash, this regional fare has been a staple throughout the working class and can still be found on menus of area diners.

Ingredients

3 tablespoons butter
1 medium yellow onion, chopped
1 green bell pepper, stemmed, seeded and chopped
2 garlic cloves, minced
1 pound ground beef
1 teaspoon dried oregano
1 teaspoon dried basil
1/2 teaspoon ground black pepper Kosher salt to taste
1 14.5-ounce can diced tomatoes
1 14.5-ounce can tomato sauce
1/4 cup tomato paste
2/3 cup tomato juice
Pinch of sugar
1 pound elbow macaroni

Instructions

Heat the butter in a large pot over medium heat. Add the onion and bell pepper and cook, stirring occasionally, until soft, about 5 minutes. Add the garlic and cook, stirring, for about 1 minute. Then add the ground beef and continue to cook, stirring and breaking up the chunks of meat with a spoon. Cook until the meat is no longer pink, about 7 minutes. Drain off most of the fat. Sprinkle the herbs and pepper over the meat, add salt to taste and mix in well.

Add the canned tomatoes with their juices, the tomato sauce, paste and juice. Add sugar to taste. Simmer while you cook the pasta.

Bring a large pot of salted water to a boil over high heat. Add the macaroni and cook, stirring occasionally, until al dente. Drain. Mix the macaroni into the chop suey. Serve hot.


Simple Beef Gravy

This simple gravy can be thrown together in about 10 minutes. Not only was this a staple in our house to pour over pork pie, this gravy is also a great go-to for mashed potatoes or pot roast.

Ingredients

2 cups low-sodium, good-quality beef broth
2 teaspoons garlic powder
1 teaspoon onion powder
1 tablespoon Worcestershire sauce
1/4 cup cold water plus 3 tablespoons cornstarch
Salt and pepper to taste
Splash of heavy cream (optional)

Instructions

In a medium saucepan, bring beef broth to boil over medium-high heat. Stir in garlic powder, onion powder and Worcestershire sauce.
In a small bowl, whisk together cold water and cornstarch until dissolved. Pour into boiling beef broth and reduce heat to medium-low. Stir until thickened.
Season with salt and pepper. Adjust seasoning according to preference. Add optional heavy cream for creamier gravy. Serve hot over pork pie.

Recipe Notes

This recipe makes about 2 cups of gravy and can easily be doubled or even tripled if you use a large saucepan.


Pork Pie With Gravy

Thought to have originated by French Canadian millworkers in the 19th century, this New Hampshire version of tourtière was originally served to help celebrate the New Year. This recipe is my favorite.

Ingredients

2 pounds ground pork
1 medium onion, finely chopped
1 teaspoon salt
2 cups water
1 teaspoon cinnamon
1 teaspoon ground nutmeg
2 cups mashed potatoes
Double pie crust
1 tablespoon milk

Instructions

In a medium saucepan over medium-low heat, combine pork, onion, salt and water. Simmer gently, stirring often, until all liquid evaporates, about an hour. Stir in spices. Add potatoes and mix well to combine thoroughly. Heat oven to 375F. Line a pie plate with one crust. Spoon in pork/potato mixture. Add top crust and flute the edges. Brush the top with milk and slice four, 1-inch slits into the top to allow steam to escape. Bake 30 minutes or until golden brown and internal temperature is at least 145F. Meanwhile, make beef gravy (recipe above).

Double Batch Pie Crust

Ingredients

4 cups flour, plus extra for work surface
2 teaspoons salt
1 cup lard
3/4 cup shortening
1 large egg, lightly beaten
1 tablespoon vinegar
1/2 cup ice water

Instructions

In a large bowl, combine flour and salt. Cut lard and shortening in until pieces are about the size of a pea. Add egg, vinegar and ice water. Work mixture into a soft, cohesive dough ball. Divide in half, and put one half aside for another pie (or freeze). Cut other dough mass in half. Wrap in plastic and place in refrigerator for at least 30 minutes. On a work surface dusted with flour, roll out bottom and top crusts. Yield: 2 two-crust pies

Recipe Notes

This recipe can be used to fill a very deep pie pan for a holiday centerpiece (pictured) or can make two pies where one can be frozen for later. In my family, we would usually bake one for Christmas and save the second one for Easter, but this dish can be eaten any time of the year. Regarding the crust — the addition of vinegar and egg will make the crust a bit flakier as well as alleviate the chances of getting a tough crust. 


Chinese Pie or Yankee Shepherd’s Pie

This dish is called “Chinese Pie” or “Pâté Chinois.” Thought to have been created by Canadian immigrants working on the railroad in China, Maine. There’s no doubt that this is a huge part of our New Hampshire history. It’s even better the next day.

Ingredients

1 tablespoon olive oil
1 pound ground beef
1 medium onion, chopped
Salt and pepper to taste
1 14-ounce can creamed corn
3 cups mashed potatoes (made ahead to your liking)
Paprika for dusting

Instructions

Preheat oven to 350F.

Heat oil in a large skillet on medium-high heat.

Add onion and cook 3-4 minutes, add ground beef, salt and pepper and cook until brown and breaking up any large chunks with a spoon.

Drain off most of the fat.

Place into a casserole dish making a nice layer.

Layer the creamed corn on top.

Spread the mashed potatoes on top being sure to completely cover the corn layer.

Sprinkle a thin dusting of paprika on top of the potatoes.

Bake for 35 minutes or until golden brown. The corn layer should bubble slightly around the edges of the potatoes.

Serve with a garden salad and buttery rolls.


Mayonnaise Cake

As with many of our states classic foods, this cake was born of a time where eggs and oil were scarce.

Ingredients

2 cups flour
1 teaspoon baking soda
1 teaspoon baking powder
½ teaspoon salt
¼ cup cocoa powder
¾ cup mayonnaise
1 cup sugar
1 cup water
1 teaspoon vanilla extract

Instructions

Sift together all dry ingredients and set aside.

Cream together mayonnaise and sugar.

Alternatively add dry ingredients and water until blended together.

Stir in vanilla extract and stir until everything is combined and smooth.

Spray a 9-inch round cake pan with non-stick spray.

Bake in 350F oven for about 35 minutes or until a toothpick inserted in the middle comes out clean.

Cool for 10 minutes in cake pan and carefully remove from cake pan and cool completely on a cake rack.

Ice with chocolate buttercream icing.

Simple Buttercream Icing

Ingredients

6 tablespoons unsalted butter, softened
2 1/3 cups confectioners sugar
3/4 cup unsweetened cocoa powder
1/3 cup whole milk
2 teaspoons vanilla extract
1/4 teaspoon salt

Instructions

In the bowl of a stand mixer fitted with the paddle attachment, beat the butter on medium speed until it is smooth, about 1 minute.

Add the confectioners sugar and cocoa powder to the bowl and beat until combined.

With the stand mixer running on low speed, slowly stream in the milk and vanilla extract then add the salt and continue beating until well-combined, scraping down the sides as needed, about 2 minutes.

Increase the speed to high and beat the frosting for an additional 2 minutes.

Use the frosting immediately. Can also be stored in an airtight container in the refrigerator. (If you refrigerate the frosting, you may want to re-blend it for a few seconds in the stand mixer before using it.)

Share

Behavior modification by parasites

Tapeworm-cancer-AIDS is a real thing

They found that people given a flu vaccine interacted with significantly more people, and in significantly larger groups, in the 48 hours after being exposed, compared with the 48 hours before. The infected hosts were more likely to head out to bars and parties.

Brain Evolution Through The Lens Of Parasite Manipulation

Share