Sunday 26 January 2014

More Git

G'day:
Another Git day today... this time I did the "Git Real" course on Code School (yesterday's article - "Two birds; one... stone" - was around their freebie intro course "tryGit").

I found today's course a bit superficial... TBH I think I learned more in the tryGit one than I did this one. I guess the things to learn about Git are more conceptual than "what one types at the keyboard", so the exercises in a Code School sort of environment will be a bit basic? It seemed more a test of whether I was paying attention to the videos, than whether I actually absorbed anything.

That said, it's given me another list of things to look at in more depth, which I'll do here. And I do think I have improved my understanding as to the mindset Git operates with, in contrast to how SVN (which I have a dozen years experience with) works. So this is valuable. OK, so I have this list of git commands to investigate further. And I'll document 'em here.


git config

One good thing about Git... what the commands do are pretty self explanatory. git config manages your Git config (read that one a coupla times if it doesn't sink in the first time. I know it's complex stuff ;-). here's my config in that repo I was looking at y/day:

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git config -l
core.symlinks=false
core.autocrlf=true
color.diff=auto
color.status=auto
color.branch=auto
color.interactive=true
pack.packsizelimit=2g
help.format=html
http.sslcainfo=/bin/curl-ca-bundle.crt
sendemail.smtpserver=/bin/msmtp.exe
diff.astextplain.textconv=astextplain
rebase.autosquash=true
user.name=Adam Cameron
user.email=dac.cfml@gmail.com
core.autocrlf=true
core.repositoryformatversion=0
core.filemode=false
core.bare=false
core.logallrefupdates=true
core.symlinks=false
core.ignorecase=true
core.hidedotfiles=dotGitOnly
remote.origin.url=git@github.com:daccfml/git-tutorials.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$

I dunno what much of that stuff means either, if I'm honest.

That config shows the config settings accumulated from three different files:
  • [git install dir]/etc/gitconfig (C:\apps\git\etc\gitconfig for me)
  • [user dir]/.gitconfig (C:\users\adam.cameron\.gitconfig)
  • [repo dir]/.git/config (C:\temp\octobox\.git\config)
On my rig, these files contain:

[core]
    symlinks = false
    autocrlf = true
[color]
    diff = auto
    status = auto
    branch = auto
    interactive = true
[pack]
    packSizeLimit = 2g
[help]
    format = html
[http]
    sslCAinfo = /bin/curl-ca-bundle.crt
[sendemail]
    smtpserver = /bin/msmtp.exe

[diff "astextplain"]
    textconv = astextplain
[rebase]
    autosquash = true

[user]
    name = Adam Cameron
    email = dac.cfml@gmail.com
[core]
    autocrlf = true

[core]
    repositoryformatversion = 0
    filemode = false
    bare = false
    logallrefupdates = true
    symlinks = false
    ignorecase = true
    hideDotFiles = dotGitOnly
[remote "origin"]
    url = git@github.com:daccfml/git-tutorials.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
    remote = origin
    merge = refs/heads/master
[user]

The settings in those files are loaded in turn, and the most localised of any same-named settings are used (so a local setting overrides a global setting, which itself overrides a system-wide one). And all three can be set at once:

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git config --system user.email daccfml@gmail.com
adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git config --global user.email dac.cfml+git@gmail.com
adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git config user.email dac.cfml+octobox@gmail.com
adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git config -l
core.symlinks=false
core.autocrlf=true
color.diff=auto
color.status=auto
color.branch=auto
color.interactive=true
pack.packsizelimit=2g
help.format=html
http.sslcainfo=/bin/curl-ca-bundle.crt
sendemail.smtpserver=/bin/msmtp.exe
diff.astextplain.textconv=astextplain
rebase.autosquash=true
user.email=daccfml@gmail.com
user.name=Adam Cameron
user.email=dac.cfml+git@gmail.com
core.autocrlf=true
core.repositoryformatversion=0
core.filemode=false
core.bare=false
core.logallrefupdates=true
core.symlinks=false
core.ignorecase=true
core.hidedotfiles=dotGitOnly
remote.origin.url=git@github.com:daccfml/git-tutorials.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master
user.email=dac.cfml+octobox@gmail.com

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$

Here I've set the user.email setting in each of system, global and local (I didn't specify "local", as that's what the default is). And they're shown individually in the config listing. They've also updated those files I mentioned before too. Here's the system-wide one (C:\apps\git\etc\gitconfig):


[core]
    symlinks = false
    autocrlf = true
[color]
    diff = auto
    status = auto
    branch = auto
    interactive = true
[pack]
    packSizeLimit = 2g
[help]
    format = html
[http]
    sslCAinfo = /bin/curl-ca-bundle.crt
[sendemail]
    smtpserver = /bin/msmtp.exe

[diff "astextplain"]
    textconv = astextplain
[rebase]
    autosquash = true
[user]
    email = daccfml@gmail.com

If I pop into my scratch repo and list the config there, I see the system and global setting, but not the one local to the octobox repo:

adam.cameron@ACBIGLAPTOP /c/webroots/shared/git (master)
$ git config -l
core.symlinks=false
core.autocrlf=true
color.diff=auto
color.status=auto
color.branch=auto
color.interactive=true
pack.packsizelimit=2g
help.format=html
http.sslcainfo=/bin/curl-ca-bundle.crt
sendemail.smtpserver=/bin/msmtp.exe
diff.astextplain.textconv=astextplain
rebase.autosquash=true
user.email=daccfml@gmail.com
user.name=Adam Cameron
user.email=dac.cfml+git@gmail.com
core.autocrlf=true
core.repositoryformatversion=0
core.filemode=false
core.bare=false
core.logallrefupdates=true
core.symlinks=false
core.ignorecase=true
core.hidedotfiles=dotGitOnly
remote.origin.url=git@github.com:daccfml/scratch.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*

adam.cameron@ACBIGLAPTOP /c/webroots/shared/git (master)
$

I can also just get a local-specific config listing:

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git config -l --local
core.repositoryformatversion=0
core.filemode=false
core.bare=false
core.logallrefupdates=true
core.symlinks=false
core.ignorecase=true
core.hidedotfiles=dotGitOnly
remote.origin.url=git@github.com:daccfml/git-tutorials.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master
user.email=dac.cfml+octobox@gmail.com

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$

There's also --global and --system options on the listing too (I'll spare you the output: you get the idea)

To remove a config setting, one uses the --unset option:

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git config -l --local
core.repositoryformatversion=0
core.filemode=false
core.bare=false
core.logallrefupdates=true
core.symlinks=false
core.ignorecase=true
core.hidedotfiles=dotGitOnly
remote.origin.url=git@github.com:daccfml/git-tutorials.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master
user.email=dac.cfml+octobox@gmail.com

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git config --unset user.email

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git config -l --local
core.repositoryformatversion=0
core.filemode=false
core.bare=false
core.logallrefupdates=true
core.symlinks=false
core.ignorecase=true
core.hidedotfiles=dotGitOnly
remote.origin.url=git@github.com:daccfml/git-tutorials.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master
        
adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$

More on git add / adding when committing

(this article is going to just be a brain dump of the stuff I'm gleaning from the tutorial, so it might be a great raft of stuff like above, or just some bullet points)

Yesterday I covered the subtle difference between these two statements:

git add *.txt

git add '*.txt'

The former only adds files from the current directory; the latter recursively adds files matching the pattern in subdirectories too.

One can also list multiple files in a git add statement:

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        rua.txt
        tahi.txt

nothing added to commit but untracked files present (use "git add" to track)

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git add tahi.txt rua.txt

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   rua.txt
        new file:   tahi.txt


adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$

Here I've added tahi.txt and rua.txt in one statement. One can also add all files

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git statusOn branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   rua.txt
        new file:   tahi.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        more/
        toru.txt
        wha.txt


adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git add --all
adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git statusOn branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   more/ono.txt
        new file:   more/rima.txt
        new file:   rua.txt
        new file:   tahi.txt
        new file:   toru.txt
        new file:   wha.txt


adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$

One can also do git add . to effect the same results.

One of the best things I learned today is that one can add & commit in one fell swoop with the -a switch on a git commit call:

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   rua.txt
        modified:   tahi.txt
        modified:   toru.txt
        modified:   wha.txt

no changes added to commit (use "git add" and/or "git commit -a")

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git commit -am "update files 1-4"
[master fbaddd8] update files 1-4
 4 files changed, 4 insertions(+), 4 deletions(-)

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

nothing to commit, working directory clean

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$

Notice that it's not reporting "changes to be committed" like the previous example above did,  this time it's saying everything is committed, ready to push up the remote repository.

Note two things here:
  1. this "add" operation does not add untracked files (like git add might), it only adds untracked changes to previously tracked files.
  2. I used -am here. That's the equivalent of -a -m. So the option is not "am", it's two options concatentated together. I just think that looks cleaner.

Different degrees of git resetting

soft

We had the briefest of looks at this yesterday, but I learned some more handy options today.

Firstly we can roll-back to the state files were in before an earlier commit:

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

nothing to commit, working directory clean

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git reset --soft HEAD^

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   rua.txt
        modified:   tahi.txt
        modified:   toru.txt
        modified:   wha.txt


adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$

There's two things to to note from that git reset call above. Firstly the "soft" setting means "just uncommit" (but otherwise don't change the files themselves). Secondly the "^" notation on the HEAD. Remember the HEAD is just the most up-to-date revision of the current branch in the repository. Suffixing this with "^" means "previous to ~": so we're resetting to the state the repo was in at the commit previous to the current HEAD. And we see that that was the state in which those four files had been updated, but not committed. Cool.

One can also add multiple "^" signs to skip back multiple commits, as demonstrated in this long-winded example (below). Here I am updating tahi.txt and rua.txt. This statement just appends " updated" to the file's contents:

echo " updated" >> tahi.txt

(that's just BASH, it's nothing to do with Git).

And then committing them. Then doing the same with toru.txt and wha.txt.

I then roll back one commit (^) and check the status: only toru.txt and wha.txt to commit again. Then I roll back to two commits previous to the HEAD (^^), and now see that tahi.txt and rua.txt also now need committing:

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ echo " updated" >> tahi.txt

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ echo " updated" >> rua.txt

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git commit -am "updated 1+2"
[master 6308a52] updated 1+2
 2 files changed, 2 insertions(+), 2 deletions(-)

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ echo " updated" >> toru.txt

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ echo " updated" >> wha.txt

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git commit -am "updated 3+4"
[master warning bd6b933] updated 3+4
 2 files changed, 2 insertions(+), 2 deletions(-)

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

nothing to commit, working directory clean

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git reset --soft head^

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   toru.txt
        modified:   wha.txt

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git reset --soft head^^

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   rua.txt
        modified:   tahi.txt
        modified:   toru.txt
        modified:   wha.txt

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$

hard

Obviously if there's a soft option, there's also the hard way. a hard reset not only undoes the git activity, it also undoes changes in the actual files too. Here's an example. Before the output below, I reverted the reset from the example above, and made sure my working directory was clean and matched the Github repository. So there were no changes anywhere.

I then went through and repeated the exercise of updating and committing tahi.txt and rua.txt; then did the same with toru.txt and wha.txt. And pushed that up to Github. So, again, everything was married up both locally and remotely.

And here's the screen cap of an exercise showing a hard reset:

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working directory clean


So this demonstrates we're all committed and up to date ("nothing up my sleeves", basically)

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git log -n 3 --reverse --format="%cr %s"
12 minutes ago reset 1-4
11 minutes ago 1+2 updated before HARD reset test
10 minutes ago 3+4 updated before HARD reset test


This shows the commit messages of the last three commits. The latter two being the commits I did for this exercise, and the former one being just whatever I committed previous to that (as a baseline).

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ cat tahi.txt
one updated


The cat (BASH) command just outputs the contents of the specified file. Note that tahi.txt contains the "updated" value here.

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git reset --hard HEAD^^
HEAD is now at 8e064cd reset 1-4


So I do the hard reset. Note that the HEAD is now back past the two test commits, and positioned at the previous one (the first one listed above).

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ cat tahi.txt
one


And the contents of tahi.txt have been rolled back too. This is the difference between a hard and soft reset. A soft reset only resets Git activity, a hard reset rolls back the file system activity too.

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git status
On branch master
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)

nothing to commit, working directory clean


This shows that nothing is lying around to commit (so all the changes to the files are gone), and the local branch is two commits behind the remote one.

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git pull
Updating 8e064cd..a1beae4
Fast-forward
 rua.txt  | 2 +-
 tahi.txt | 2 +-
 toru.txt | 2 +-
 wha.txt  | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ cat tahi.txt
one updated

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$


Doing a pull restores the previously committed files (and their contents); showing the reset only impacts the local repository, not the remote one.

Hopefully that makes sense? It didn't to me during the course, but once I started playing around with it, it cleared itself up.

Editing a commit

This is cool. We can update a previous commit by amending it. Here's an example:

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working directory clean


Just like before, we're starting from a clean slate.

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git diff
diff --git a/tahi.txt b/tahi.txt
index 6b1c933..40090cc 100644
--- a/tahi.txt
+++ b/tahi.txt
@@ -1 +1 @@
-one updated
+one change for initial commit


Behind the scenes (I forgot to use cat this time) I have changed the contents of tahi.txt from "one updated" to "one change for initial commit".

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git commit -am "initial commit"
[master acbc224] initial commit
 1 file changed, 1 insertion(+), 1 deletion(-)

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git log -n 2 --reverse --format="%cr %s"
47 minutes ago 3+4 updated before HARD reset test
60 seconds ago initial commit


I commit that change, and list the log of the two most recent commits: the one I've just done, and the previous one.

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git diff
diff --git a/tahi.txt b/tahi.txt
index 40090cc..979d896 100644
--- a/tahi.txt
+++ b/tahi.txt
@@ -1 +1 @@
-one change for initial commit
+one change for initial commit (and a second update to amend that commit with)

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)

Again, I've updated tahi.txt behind the scenes.

$ git commit --amend -am "This comment should replace the previous commit comment"
[master 8a2f422] This comment should replace the previous commit comment
 1 file changed, 1 insertion(+), 1 deletion(-)

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

nothing to commit, working directory clean

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$ git log -n 2 --reverse --format="%cr %s"
48 minutes ago 3+4 updated before HARD reset test
25 seconds ago This comment should replace the previous commit comment

adam.cameron@ACBIGLAPTOP /c/temp/octobox (master)
$

And I use amend to amend the previous commit, and use a new message. This is borne out by looking at the log: the previous message has gone, as indeed has the entire commit record, being replaced with the updated one.



OK, that's enough for the time being. My brain is full, my fingers are tired, and I am hungry... and it's a slow cook tonight so if I start now, dinner ain't gonna be ready for another coupla hours anyhow.

Anyhow, that's a chunk of stuff for you to read through. Interestingly this lot only overs the first 1.5 videos on this course, out of seven. I've got a long way to go.

Righto.

--
Adam