Tracking FreeBSD in FreeNAS by Using Git for Newbies

Posted in Blog, Misc, Tradeshow Events on November 22, 2013

This is the second in our series of presentation recaps. In addition to Craig's presentation (which can be found here) at the FreeBSD Vendor Summit held on November 7, 2013, iXsystems was also represented by Alfred Perlstein.

Alfred Perlstein, iXsystems Vice President of Software Engineering, gave a presentation, "Following FreeBSD with a git Workflow". Alfred's presentation gave step-by-step examples of how to use git to incorporate changes from FreeBSD source code, into a third-party codebase. Alfred's examples were taken directly from the development process for FreeNAS and TrueNAS, which are based on FreeBSD source code. By using git, Alfred has been able to transform the FreeNAS development process, and save hundreds of FreeNAS development hours when integrating new changes and bugfixes from FreeBSD into FreeNAS. Afterwards, Alfred generously provided a write up of his talk so that anyone who was unable to attend could refer to the tutorial he gave. A pdf copy of his article can also be found in the downloads section.

Introduction

This article will discuss, at a high level, how git works. It will include an example where we examine some of the git meta data. The goal of this article is to dispel much of the magic and worry when doing things with git.

We will cover:

 

  • How branches are made.
  • How remote branches are handled.
  • How the rebase operation works.
  • How to save your work back to your personal github.

Many of these explanations work fine for the most common use cases, at least the use cases for FreeNAS and our commercial product, TrueNAS. There is likely some measure of inaccuracy when it comes to the actual underlying mechanisms of git as opposed to the conceptual.

So instead of going into the actual mechanisms, which can be confusing and takes quite some time to grasp, we will instead conceptually describe how git works so that you can do the same branch management as we do in our project. At the end of this article, you should have a basic idea of how to take a changeset and move it to a later version of FreeBSD, effectively "rebasing" your work.

So let's get down to the concepts that dispel a lot of the confusion about git.

Actually, first let’s set up to download the FreeBSD git repository because it’s large and you can read the first few paragraphs below while it gets started. So first find yourself a filesystem that is CASE-SENSITIVE and has at least 2GB free and run this command:

% git clone https://github.com/freebsd/freebsd.git

Now start reading, this will take a while.

What is git?

The data structures of git

In order to understand git, an abstract explanation of how git works helps. Let’s start with the statement:

git is a database of diffs with a few linked list pointers in it to keep track of things.

Here is an example of a conceptual git object:

struct git_object {
    struct git_object *go_next; // <- pointer to the NEXT object
    struct git_object *go_merge; // <- We won’t cover this here
    char go_object_hash[HASHSIZE]; // that funky 242423defegefg357 thing
    char go_commit_log, author_name, etc, etc; // annotation/meta-data
    char go_diff_data[]; // has the actual patch(1) data in it
};

Using the git_object just described, we can see that each object can be looked up by its hash. By fetching that object, one can retrieve a snapshot of the tree at any given point.

The second thing we can note is that each commit appends another git_object to the list of git objects. Essentially git is a linked list that allows you to also reference any node by hashid. With this, we can represent the history of a single branch.

The data is in go_diff_data. The funky hash is stored in go_object_hash.

How do we represent multiple branches?

Simple. We have a fan-in setup where git objects can fan-in to point at each other. For example:

one branch "4321"
NULL <- 9876 <- 6543 <- 4321

two branches, "4321" & "abcd"
NULL <- 9876 <- 6543 <- 4321
                  \
                   `----abcd

You can see that “abcd”’s go_next pointer points to the node “6543”. So does “4321”. Since both “abcd” and “4321” reference “6543”, we have a fan-in configuration, typical of branched development.

Naming branches

Excellent. But, wait...my branches are actually called "4321" & "abcd"?!

That's TERRIBLE!

Well, wait-- git allows you to name branches (as it should). Let's talk about how that is done.

First, let's type "git branch" to see what branch we're on.

~/git/freebsd % git branch
* master
~/git/freebsd %

Ok, "master". That is exactly where we want to be, the git name for “HEAD” or “TRUNK” that most CVS or SVN users are used to.

Now, let's go under our FreeBSD git repo and go into the HIDDEN directory and look around.

Under your freebsd/.git directory, the two things of interest for this example are the file config and the directory refs. The config file has your config in it. We'll get to that later.

Let's look under refs, we have directories heads; this is where the references to our hashes are. Have look around. There's one file called master. Inside master is the hash for the git_obj that represents the tip of the branch.

Go ahead and run "git log" on that hash. What we have here is a nice representation of a branch.

~/git/freebsd % cat .git/refs/heads/master
ce4163e4f3478c8819e2e474356811fc1c641aba
~/git/freebsd % git log ce4163e4f3478c8819e2e474356811fc1c641aba
commit ce4163e4f3478c8819e2e474356811fc1c641aba
Author: loos
Date:   Tue Nov 12 13:55:19 2013 +0000

    Move the KASSERT() check to the point before the increase of number of pins.

    Approved by:        adrian (mentor)

Notes (origin/commits):
    svn path=/head/; revision=258047
. . .

This is a ref. This is also a head. It represents the master branch. The master branch is the typical DEFAULT branch for most git setups. You can make the default branch NOT master, but for now, most git repos implicitly call the original branch master.

OK, now where is the rest of FreeBSD? Is all I have is "trunk" or "master"?

Nope! Let’s explain that.

Remote branches

Let’s go back under the .git directory. From there, go to refs, then "remotes".This is where the rest of the remote repository's branches and "things" should be. Let's take a look under here…

 

~/git/freebsd % ls -l .git/refs/remotes
total 0
drwxr-xr-x 5 alfred staff 272 Nov
12 16:32 origin ~/git/freebsd %

What is origin? Origin is the DEFAULT name that git will assign a remote repository when you clone from it. We can change the name or use different names, but for now, let's look under the hood of the directory origin:

~/git/freebsd
% ls .git/refs/remotes/origin
HEAD        projects    svn_head
master      stable      user

Wait a second… What is "HEAD"? I thought that HEAD was called master??? Well, let’s look:

~/git/freebsd
%cat .git/refs/remotes/origin/HEAD
ref: refs/remotes/origin/master
~/git/freebsd %

Oh, interesting. It’s actually a reference to refs/remotes/origin/master! This is the currently checked out branch.

Let’s look at something interesting. Look under "stable". You'll see directories for 10, 9, 6, etc. Go ahead and "cat" one of those files. You'll see a hash, so run "git log" on that hash. OK, so now we know how branches are made.

~/git/freebsd % ls -l .git/refs/remotes/origin/stable
total 12
-rw-r--r-- 1 alfred staff 41 Nov 12 16:32 10
-rw-r--r-- 1 alfred staff 41 Nov 12 06:37 8
-rw-r--r-- 1 alfred staff 41 Nov 12 16:32 9
~/git/freebsd % cat .git/refs/remotes/origin/stable/9
511ee4cd9216f25374ee3d0c8f8db4ffb6a436a0
~/git/freebsd % git log 511ee4cd9216f25374ee3d0c8f8db4ffb6a436a0 | head -15
commit 511ee4cd9216f25374ee3d0c8f8db4ffb6a436a0
Author: jhb <jhb@FreeBSD.org>
Date:   Tue Nov 12 21:33:01 2013 +0000

    MFC 254576: Stop an ipoib interface before detaching it.

    PR:           kern/181225

Notes (origin/commits): svn path=/stable/9/; revision=258072

commit f8fd5caeee892ad50c64f57787b957bff2fe9727
Author: dim <dim@FreeBSD.org>
Date:   Tue Nov 12 18:43:35 2013 +0000

Now go ahead and play around under the .git/refs directory and explore a bit. Don’t delete any files, but just run around and explore.

How we use git in FreeNAS

FreeNAS uses FreeBSD as the base OS. Our main goal is to upstream as much code as possible into FreeBSD. At the same time, we need insulation against unintended stability and performance issues in FreeBSD.

So we use git to stack diffs on top of FreeBSD.

We take a pristine copy of FreeBSD, using all of the history and just branch from it and then apply our changes to our custom branch.

Then when we feel ready, we grab a newer version of FreeBSD and "rebase" on top of it. Effectively we move all of our FreeNAS changes over to a newer version of FreeBSD and the majority of our changes are absorbed as newer FreeBSD has all of our changes.

Talking about the rebase operation

Let's walk through this process. First we need to add FreeNAS as another remote repo. What is a remote repo? Well our first remote repo is origin which happens to be...
#
#    modified: sys/boot/forth/brand.4th
#
# Unmerged paths:
#   (use "git reset HEAD ..." to unstage)
#   (use "git add ..." to mark resolution)
#
#    both modified:   sys/boot/forth/beastie.4th
#    both modified:   sys/boot/forth/menu.4th
#

How do we resolve this?

Edit sys/boot/forth/beastie.4th. Remove the second part under !HEAD(that's us) for the first conflict:

      \ Currently available:
      \
      \#   #    NAME#   #     DESCRIPTION
      \#   #    beastie   #  Color ``Helper Daemon'' mascot (19 rows x 34 columns)
      \#   #    beastiebw   B/W ``Helper Daemon'' mascot (19 rows x 34 columns)
      <<<<<<< HEAD
      \#   #    fbsdbw  #    "FreeBSD" logo in B/W (13 rows x 21 columns)
      \#   #    orb  #       Color ``Orb'' mascot (15 rows x 30 columns)
      \#   #    orbbw  #     B/W ``Orb'' mascot (15 rows x 32 columns) (default)
      =======
      \#   #    fbsdbw  #    "FreeNAS" logo in B/W (13 rows x 21 columns)
      \#   #    orb  #       Color ``Orb'' mascot (15 rows x 30 columns) (2nd default)
      \#   #    orbbw  #     B/W ``Orb'' mascot (15 rows x 32 columns)
      \#   #    tribute  #   Color ``Tribute'' (must fit 19 rows x 34 columns) (default)
      \#   #    tributebw  # B/W ``Tribute'' (must fit 19 rows x 34 columns)
      >>>>>>> Change FreeNAS text logo
      \
      \ NOTE: Setting `loader_logo' to an undefined value (such as "none") will
      \#   #    prevent beastie from being drawn.

Then edit sys/boot/forth/menu.4th and change the text like so:

: menu-create ( -- )

        \ Print the frame caption at (x,y)
<<<<<<< HEAD
        s" loader_menu_title" getenv dup -1 = if
                drop s" Welcome to FreeBSD"
=======
        str_loader_menu_title getenv dup -1 = if
              drop s" Welcome to FreeNAS"
>>>>>>> Change FreeNAS text logo
        then
        24 over 2 / - 9 at-xy type

To:

: menu-create ( -- )

        \ Print the frame caption at (x,y)
        s" loader_menu_title" getenv dup -1 = if
                drop s" Welcome to FreeNAS"
        then
        24 over 2 / - 9 at-xy type

Now mark those conflicts as resolved and commit them:

~/git/freebsd % git add sys/boot/forth/beastie.4th sys/boot/forth/menu.4th
~/git/freebsd % git rebase --continue
Applying: Change FreeNAS text logo
Applying: Make txg_quiesce visible so it will work for Dtrace
Applying: Fix load of dtraceall module
. .

If you get any more conflicts (in my recent check I got another conflict at patch 0188) just skip them as we’re not doing a lesson on conflict resolution:

~/git/freebsd % git rebase --skip
Applying: Support for conditionally including files.
Using index info to reconstruct a base tree...
M   sys/boot/forth/beastie.4th
M   sys/boot/forth/brand.4th
M   sys/boot/forth/loader.4th
M   sys/boot/forth/menu.rc
M   sys/boot/forth/support.4th
:35: trailing whitespace.
       else
:88: trailing whitespace.
\ which prepends 'include' to the string to be passed to
warning: 2 lines add whitespace errors.
Falling back to patching base and 3-way merge…
. . .

Note: Since us FreeNAS/TrueOS developers push as many changes as possible upstream to FreeBSD we will typically get MANY conflicts when doing a rebase against a much later version. Luckily since almost everything is upstream, we typically wind up just skipping most of our patches. Still, we are careful and audit during this time.

We are almost done now. Our unfied_freebsd_b branch contains the changes rebased onto a later version of FreeBSD. We have one last thing to do… we have to put it somewhere!

Pushing it back to github!

You want to store your personal version of TrueOS/FreeNAS right? Well the easiest way is to use github.

If you don’t have a github account then go make one RIGHT NOW.

Then just perform the following steps:

1. Go to this url: https://github.com/freebsd/freebsd
2. Pick “fork”, right now it’s the icon at the top right of the screen:

3.Then to your copy of the fork: https://github.com/alfredperlstein/freebsd
4.Get the repository url:

In my case it’s “git@github.com:alfredperlstein/freebsd.git”

 

5. Now add it as a remote:

% git remote add mybsd git@github.com:alfredperlstein/freebsd.git

6. Push your work to it:

~/git/freebsd % git push alfred unified_freebsd_b:unified_freebsd_b
Counting objects: 2915, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (1322/1322), done.
Writing objects: 100% (2330/2330), 662.04 KiB | 0 bytes/s, done.
Total 2330 (delta 1727), reused 1195 (delta 881)
To git@github.com:alfredperlstein/freebsd.git
* [new branch]      unified_freebsd_b -> unified_freebsd_b

Now check if it’s there:

Now you’re done. You have your own copy of FreeNAS’s TrueOS to play with.

Find us on Facebook

Reviews

StatisticsReview of iXsystems, Inc.

Archive

2014
2013
2012
2011

Feeds

RSS / Atom