Git Part II – The Origin of Master – Git Init



Git Part II – The Origin of Master – Git Init

1 0


GitTalk_May_2013

Git Part II: The Origin of Master

On Github keelerm84 / GitTalk_May_2013

Git Part II

The Origin of Master

Matthew M. Keeler / @keelerm84

What We'll Cover

  • The first talk covered working with git locally.
  • You can watch the first talk at http://www.youtube.com/watch?v=znilVsDph38
  • This talk will be geared towards working with remote repositories, with an eye towards team development.

Git Init

  • When working locally, we can initialize a repo with git init
  • $ git init our_repo
    $ ls -a our_repo
    .   ..  .git
    $ ls -a our_repo/.git
    .       HEAD        config      hooks       objects
    ..      branches    description info        refs
  • What if we want a remote repository that we can share with others?

Bare Repositories

  • A bare repository is one without a working checkout of the files
  • These types of repositories are setup for developers to push commits to
  • $ git init --bare
    $ ls -a our_bare_repo
    .       HEAD        config      hooks       objects
    ..      branches    description info        refs
  • The created files are the same as the contents of the .git folder in a normal repository
  • Bare repositories are not necessary to receive pushes, but they are way simpler and much safer

Git Clone

  • In order to get a copy of a repository, we have to clone it
  • $ git clone ssh://user@host/path/to/our_bare_repo local_repo_name
    Cloning into 'local_repo_name'...
    remote: Counting objects: 4287, done.
    remote: Compressing objects: 100% (2385/2385), done.
    remote: Total 4287 (delta 1791), reused 4258 (delta 1762)
    Receiving objects: 100% (4287/4287), 6.28 MiB | 875 KiB/s, done.
    Resolving deltas: 100% (1791/1791), done.
  • Note that this method requires an account on the server.
  • You can also setup a git server that will allow for cloning over https or using git's native protocol.

Remotes

  • Remotes are references to server/repo pairs you can use to push/pull from
  • When cloning a repo, you are automatically setup with a remote, called origin
  • $ git remote -v
    origin  https://github.com/git/git.git (fetch)
    origin  https://github.com/git/git.git (push)

Adding Remotes

  • Because git is a distributed system, you are not limited to the central server.
  • If you want to be able to pull from another developer's system, you just add a new remote.
  • $ git remote add keelerm https://github.com/keelerm84/git.git

Renaming / Removing Remotes

  • You can rename an existing remote with
    $ git remote rename origin parent
  • When you are done with a remote, you can delete it with
    $ git remote rm parent

Remote Branches

  • If you look at the current branches, you'll find we only have master
  • $ git branch -a
    * master
  • But we know there are more branches available than that, so where are they?

Fetch

  • We have to tell git to retrieve those remote references through use of the fetch command.
  • $ git fetch origin
    From https://github.com/git/git
     * [new branch]      maint      -> origin/maint
     * [new branch]      master     -> origin/master
     * [new branch]      next       -> origin/next
     * [new branch]      pu         -> origin/pu
     * [new branch]      todo       -> origin/todo
  • $ git branch -a
    * master
      remotes/origin/maint
      remotes/origin/master
      remotes/origin/next
      remotes/origin/pu
      remotes/origin/todo

Remote Branches

  • After cloning, you can start working on the project immediately
  • What if you want to work on another branch?
  • We can checkout a remote branch with
    $ git checkout -b next origin/next
    Branch next set up to track remote branch next from origin.
    Switched to a new branch 'next'

Pull

  • As other developers commit changes, we can pull them into our local repository.
  • $ git pull origin next
  • We can pull from any remote, from any branch, with this format.

Tracked Branches

  • Branches checked out from a remote are initially set to track that branch ( by default )
  • This allows you to pull using the shorthand notation
    $ git pull
  • To explicitly set a branch to track another,
    $ git checkout -b --track local_branch remote/branch
  • To prevent tracking, you can use the option
    --no-track
  • Default behavior can be set with
    $ git config --global branch.autosetupmerge [true|false|always]

Push

  • When we are ready to share our work, we can push our commits to a remote repository
  • Much like pull, we can explicitly push to a remote branch
  • $ git push origin local_next:next
  • If the remote branch name is the same as the current branch:
    $ git push origin next
  • You can set the branch to be tracked for pushes with
    $ git push -u origin next
  • A similar format can be used to delete a remote branch
    $ git push origin :next

Tags

  • Tags are markers to specific commits.
  • Prevents us from having to remember specific SHA1
  • We can review the list of existing tags
  • $ git tag
    v1.8.3-rc3
    v1.8.3-rc2
    v1.8.3-rc1
  • You can search for a specific tag with
    $ git tag -l 'regex'

Lightweight vs. Annotated

  • Lightweight tags are nothing more than simple markers, just a file containing the SHA1 of a commit
    $ git tag temp_tag HEAD
  • Annotated tags are full commit objects
  • $ git tag -a -m 'Tagging v.1.8.4' v1.8.4 HEAD
  • We can share those tags with
    $ git push --tags
  • Retrieve new tags with
    $ git fetch --tags

Merging Changes

  • When working with branches, merging is inevitable
  • Like most everything with git, there are a couple ways to accomplish this
    • git merge
    • git rebase

Merge

  • When you have done work on a separate branch, your work may look similar to the following

Merge It!

  • We can switch to the master branch and issue a
    $ git merge experiment
  • This looks for the common point between the branches, and adds a new commit that is a merge of the two paths

Pros and Cons

  • This is always an available option, so if you remember only one method, make it this one
  • Easy to understand, but difficult to review the history as commits have two parents

Rebase

  • Merging melds two branches into one, but rebase takes a different approach
  • It tries to replay your work on top of existing commits, even though they weren't developed that way.

Rebase It!

  • To rebase these commits, from the experiment branch, we issue
    $ git rebase master
  • This stashes the commits on our local branch (experiment), going back to the common point of master
  • The new commits from master are brought in, so master and experiment are identical
  • The stashed commits are applied ontop of the new master commits.

Pros and Cons

  • Rebasing gives us a nice linear history, making later review easy
  • Takes a few extra steps, but well worth it.
  • This should never be done with commits already shared

A More Complicated Rebase

  • A rebase doesn't have to only involve two branches. It may include 3!
  • This is where git rebase --onto comes in

What Are You Doing?

  • Let's say you wanted to bring your client commits back into the mainline
  • But we don't want to bring in any of the server commits yet because they aren't done
  • $ git rebase --onto master server client
  • This tells git to find the commits since the common point of server and client, and rebase those onto master

The Result?

We can finish this up by mering client into master to fast-forward master to C9'

Switching Gears

  • Sometimes you're working on an issue, and something urgent comes up.
  • With a dirty working directory, you have a couple of options
  • You can commit your current work, and then --amend it later
  • Or you can stash your current work, and restore it later.

Stash

  • To store the current working directory:
    $ git stash
  • This adds the changes to a stack
  • You can specify a message when stashing with
    $ git stash save 'Some pithy message'
  • Additional flags
    • --patch Allows you to interactively stage portions of the file
    • --keep-index Any changes already added to the index will kept after performing the stash
    • --include-tracked This will include any untracked files in the stash

Review Stashes

  • You can review your list of stashes with
    $ git stash list
    stash@{0}: WIP on next: ea353ce Sync with 1.8.3
  • Stashes are referenced by their stash name (e.g. stash@{0})
  • You can apply a stash with
    $ git stash apply stash@{0}
  • This leaves the stash on the stack. You can remove a stash with
    $ git stash drop stash@{0}
  • You can apply and drop with
    $ git stash pop stash@{0}

Convenient Applys

  • By default, applying a stash removes all files from the index, and applies them to the working directory.
  • $ git stash apply --index
    will store the working directory and index as it was during the stash
  • You can also create a branch from a stash with
    $ git stash branch branch_name

Git Rebase (Again)

  • While working on a feature branch, you are likely to make a lot of smaller commits
  • You can merge in all of those individual changes, but sometimes it can be better to only merge in one complete commit
  • This can be accomplished through a particular use of rebase
    $ git rebase -i HEAD~4
  • This will open a buffer like the following

Rebase Options

  • pick e00dd1e describe: Add --first-parent option
    pick 66fa1b2 Documentation/merge-options.txt: restore `-e` option
    pick 7370445 completion: regression fix for zsh
    pick 92c4369 remote-hg: trivial configuration note cleanup
    pick 5e49f30 remote-hg: fix order of configuration comments
    pick edca415 Git 1.8.3
    
    # Rebase aed12a7..ea353ce onto aed12a7
    #
    # 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
    #  x, exec = run command (the rest of the line) using shell
    #
    # These lines can be re-ordered; they are executed from top to bottom.
    #
    # If you remove a line here THAT COMMIT WILL BE LOST.
    # However, if you remove everything, the rebase will be aborted.

Splitting a Commit

  • Individual commits should only contain one fix or feature.
  • If you have commited too much, you can of course use reset, but that's really only efficient if it is the most recent commit
  • Rebase can be used to more easily split commits past the first one

Steps to Split Commits

  • git rebase -i SHA1
  • Change pick to edit for the commit to split
  • Issue a git reset to put the changes in the working directory.
  • Restage and commit files as appropriate.
  • When finished, git rebase --continue

Cherry Pick

  • There are times when you want to bring in specific commits from another branch ( local or remote )
  • You can pull out these commits with
    $ git cherry-pick SHA1
  • A good use case for cherry-pick is if a branch has code you want to introduce, but the branch itself is quite dated.
  • Cherry picking will take a commit, apply it to your system, and automatically make a new commit
  • If you add the -n flag, this will stage the changes, but not automatically commit them.
  • You can also cherry pick multiple commits at once by specifying individual SHAs, or a range selector

Blame

  • When tracking down a bug, you might want to find out which commit introduced it so you'll have context when you fix it.
  • You can look at which commit last touched a line with
    $ git blame /path/to/file
  • We can also look at a specific line range with
    $ git blame -L1,30 /path/to/file
$ git blame -L9,13 /path/to/file
35297089 (Jonathan Nieder         2013-03-09 14:00:11 -0800   9) #define NOLOGIN_COMMAND COMMAND_DIR "/no-interactive-login"
2dbc887e (Greg Brockman           2010-07-28 17:31:01 -0700  10) 
35eb2d36 (Linus Torvalds          2005-10-23 14:30:45 -0700  11) static int do_generic_cmd(const char *me, char *arg)
35eb2d36 (Linus Torvalds          2005-10-23 14:30:45 -0700  12) {
35eb2d36 (Linus Torvalds          2005-10-23 14:30:45 -0700  13)        const char *my_argv[4];

Bisect

  • git blame shows us the last time the line was touched, but not necessarily who introduced an error.
  • For this, we can use git bisect
  • Bisect works by performing a binary search through a range of commits, stopping at each point for you to review.
  • Steps to performing a bisect
    $ git bisect start
    $ git bisect bad HEAD
    $ git bisect good SHA1 # Known working SHA1
      # As git stops at each commit, check it for the error and mark it as good or bad
    $ git bisect [good|bad]
      # If there is a commit you cannot determine, use 
    $ git bisect skip
      # Once you have found the commit, you can exit the bisect with
    $ git bisect reset
                                

Automated git bisect

  • You can automate the process by using the run flag
  • You specify a script to run at every stopping point, and the exit status determines whether the test is good or bad
  • Common commands/scripts would include running make or executing your test suite

Purging a File

  • What do you do if you accidentally commit a file that should not be available in the repository?
  • $ git filter branch --tree-filter 'rm -f password' HEAD
  • This will completely purge the file from all commits in the current branch

Retagging Commits

  • Another common use case is fixing invalid author emails
  • $ git filter-branch --commit-filter '
    if [ "$GIT_AUTHOR_EMAIL" = "keelerm@localhost" ];
    then
            GIT_AUTHOR_NAME="Matthew M. Keeler";
            GIT_AUTHOR_EMAIL="keelerm@tortugas-llc.com";
            git commit-tree "$@";
    else
            git commit-tree "$@";
    fi' HEAD

Warning

  • You can add the --all flag to run this purge command against all branches
  • Be warned, this will change the SHAs of all prior commits, forcing you to push with force
    $ git push --force origin master
  • Other team members will have to drop their branches and pull in the re-worked branch

Hooks

  • Hooks are scripts that are executed when various "events" are triggered.
  • These scripts can be placed in the .git/hooks directory under the repository
  • Some common use cases are running the code through a linter on pre-commit or tagging information on an issue tracker on git post-receive

So In Conclusion

alias yolo="git commit -am 'Deal with it' && git push --force"

Questions?