1. 72

Until recently, there were no git clients for Plan 9. Upstream git is large, included perl and bash code, and used a large number of system calls that Plan 9 does not support. In addition, it does not feel good to use, with a bewildering array of inconsistent command line arguments and ad-hoc commands.

The file formats and wire formats for git make …interesting.. design choices, but they aren’t particularly hard to implement, so I followed the documentation and implemented them.

Now, we have a native Plan 9 git implementation. It presents read only access to scripts via a file system mounted on /mnt/git, and manipulates the repository directly. The ability to access the repository directly from the shell using standard tools gives this implementation a very plan9-y feel.

There’s more care given to a consistent and minimalist interface that supports the necessary functionality, which will hopefully lead to a more pleasant user experience. Commands like checkout will not be overloaded with all functionality under the sun.

For an extra dose of irony, it’s currently hosted in mercurial, since that’s the current ‘native’ plan 9 version control system. Eventually, it will be developed using itself, but it needs to be beaten on more before I’m confident.

Enjoy!

  1.  

  2. 8

    This is actually kind of cool!

    1. 6

      Did you consider using libgit2 in your approach? Why did it not fit your needs?

      1. 20

        Porting is annoying, and ape (the ansi/posix emulation environment) is a separate world that’s no fun to use. It also comes with interoperability caveats and issues when using the native plan9 libraries. Additionally, the things that libgit2 helps with are the things I want to do in shell script. And finally:

          term% cd git9
          term% wc -l `{du -a . | awk '/.*\.[chy]$/{print $2}'}
             108 ./conf.c
             378 ./fetch.c
             839 ./fs.c
             174 ./git.h
              62 ./objset.c
             828 ./pack.c
             264 ./save.c
             489 ./send.c
             228 ./util.c
            3370 total
        

        Compared to:

          term% cd libgit2
          term% wc -l `{du -a . | awk '/.*\.[chy]$/{print $2}' }
              <elided list of ~900 files>
              270404 total
        

        Even ignoring tests, examples, and external deps:

          term% wc -l `{du -a . | \
                   awk '/.*\.[chy]$/{print $2}' | \
                   grep -v 'tests|deps|examples' }
              <elided list of files>
              138077 total
        

        In other words, depending on how you count, there’s between 1.2% and 2.4% the amount of C code to maintain, update, and understand in this implementation. And sure, this implementation will grow, but it’s likely to stay within the single digit percentages of libgit2’s size.

        1. 1

          Thank you for your response!

      2. 3

        But what about adding parts of files to commits?

        1. 1

          As in git add -p for Interactive Staging?

          You can try working on one thing at a time, or you can leverage your favourite editor’s functionality, if possible. VIM (and I bet many others) can do partial writes to files, so if you keep touching many areas but want to stage/commit them one by one, write the specific areas only and proceed as usual.

          Developers have been surviving without those bells and whistles for a long time. I do use it now and then when things got too complicated, but that’s not (necessarily) a good thing: I wish I never run into this in the first place.

          1. 1

            Yeah, but now we can use -i and -p, we don’t want to go back. But partial writes may be a good answer, I didn’t know about those.

            1. 1

              Adding parts of files to commits is fragile, because by definition you’ve never tested those staged files as a unit, and it’s quite easy to break the code as a result.

              I’d strongly prefer a partial stash, similar to git stash -p. That gives you a checkout that you can test before committing. Plan 9 already comes with tools for doing this[1], and the file system abstraction means that the only support needed is for hooking it into a stash workflow.

              [1] idiff, http://man.cat-v.org/9front/1/idiff

              1. 1

                Oh, I “want to go back” on so many things in git…

                1. 1

                  I love git, seriously. Now that I know I’m manipulating the DAG, everything just Makes Sense. I wouldn’t want to teach a new user though.

          2. 3

            the entire concept of the staging area has been dropped, as it’s both confusing and clunky. There are now only three states that files can be in: ‘untracked’, ‘dirty’, and ‘committed’.

            … Some usage examples:

            git/clone git://git.eigenstate.org/git/ori/mc.git
            git/log
            cd subdir/name
            git/add foo.c
            diff bar.c /mnt/git/HEAD/
            git/commit
            git/push
            

            After git/add foo.c, which state is foo.c: “dirty” or “committed”? It’s not committed and not untracked, so I guess it must be “dirty”. But if it’s again modified in the workspace… then what?

            Seems to me like there is still a “staging” step here, or please clarify.

            1. 5

              After git/add foo.c, which state is foo.c: “dirty” or “committed”? It’s not committed and not untracked, so I guess it must be “dirty”. But if it’s again modified in the workspace… then what?

              Naively snapshotting an entire directory will pick up in-progress files, generated outputs, and other undesirable stragglers. We need a method to mark files that should be included in a commit.

              git/add is the tool for this. It does not copy files anywhere, it just marks a file as snapshot-worthy.

              git/commit will snapshot whatever is in the working directory at the time you call it. The concept of a staging area is simply gone. This reduces the amount of state I need to track in my head, improving usability. (See, for example, the usability studies done by the gitless folks: https://spderosso.github.io/oopsla16.pdf, https://gitless.com/)

              One major missing feature that results from this decision is partial commits. This will be supported by explicitly listing the files to snapshot when committing, as in git/commit path/to/file. That will work by creating a fresh namespace from /mnt/git, and binding in just the files passed as dirty. Something like:

                   scratch=/tmp/commit.$pid
                   mkdir $scratch
                   @{ # subshell for fresh namespace, so binds are cleaned up.
                       rfork n
                       # stitch together a view of the files that we'd like to snapshot.
                       for(f in $files)
                             bind $file $scratch/$file
                       bind -ac .git $scratch/.git
                       bind -a /mnt/git/HEAD/tree $scratch
                       # commit that virtual view.
                       cd $scratch
                       git/commit
                   }
                   rm $scratch
              

              (Edited for clarity)

              1. 3

                The next commit will snapshot it: git/add does not copy it anywhere. It just marks it as a part of future snapshots. Whatever is in your current directory will be what is in your commit.

                I can appreciate the simplicity, but then why bother with git/add at all? Why not git/commit everything always (except .gitignore’d files)?

                Removing the “staging” phase implies that if one wants to divide changes into separate commits, one must find some other mechanism or discipline. But keeping the git/add step means that the opt-in habit is still required, except with less granularity. Going full opt-out (.gitignore) eliminates git/add (except when needed to override .gitignore), and eliminates “untracked” (because one must update .gitignore to avoid committing such files). Untracked-unignored files are a bad habit anyway.

                Replying to your edit:

                Naively snapshotting an entire directory will pick up in-progress files, generated outputs, and other undesirable stragglers. We need a method to mark files that should be included in a commit. git/add is the tool for this. It does not copy files anywhere, it just marks a file as snapshot-worthy.

                That is still conceptually (“head state”) a staging phase, except at file-granularity instead of chunk-granularity. Going “full opt-out” would fully eliminate the staging phase.

                1. 4

                  It’s an interesting thought. Not sure how I feel about it, but I’ll experiment and see how it works in practice.

                  1. 3

                    This idea mostly works, but does require a feature that I don’t think the Plan 9 implementation has, which is easy per-checkout ignores (i.e., the equivalent of .git/info/exclude). Otherwise, it’s pretty easy to get into a situation where you constantly need to move temp files (e.g., log output from a bug you’re trying to fix) out of the repo before the commit and then move them back in, which gets old fast.

                    1. 1

                      Otherwise, it’s pretty easy to get into a situation where you constantly need to move temp files (e.g., log output from a bug you’re trying to fix) out of the repo before the commit and then move them back in, which gets old fast.

                      I don’t see why something like .git/info/exclude would not be supported (the current git/add behavior already implies support for some sort of local tracking list), but why not add such “temp files” to .gitignore?

                      In any case you’re going to run into an analogous situation if you don’t want to commit certain modifications to your workspace. This is the “other mechanism or discipline” I mentioned above.

                    2. 1

                      Why not git/commit everything always (except .gitignore’d files)?

                      I like the concept, removing a feature and leveraging existing features.

                      1. 0

                        You sounds like a git wizard. I guess you may want to take a look at rcs, cvs, svn, and hg.

                  2. 1

                    Looks like it’s MIT-licensed. I wonder if freebsd might adopt git, then.

                    1. 1

                      I needed that.