Working with workspaces

NOTE

All the commands are included from the file workspaces.t which can be run with cram.

Josh really starts to shine when using workspaces.

Simply put, they are a list of files and folders, remapped from the central repository to a new repository. For example, a shared library could be used by various workspaces, each mapping it to their appropriate subdirectory.

In this chapter, we're going to set up a new git repository with a couple of libraries, and then use it to demonstrate the use of workspaces.

Test set-up

NOTE

The following section describes how to set-up a local git server with made-up content for the sake of this tutorial. You're free to follow it, or to use your own existing repository, in which case you can skip to the next section

To host the repository for this test, we need a git server. We're going to run git as a cgi program using its provided http backend, served with the test server included in the hyper_cgi crate.

Serving the git repo

First, we create a bare repository, which will be served by hyper_cgi. We enable the option http.receivepack to allow the use of git push from the clients.

  $ git init --bare ./remote/real_repo.git/
  Initialized empty Git repository in */real_repo.git/ (glob)
  $ git config -f ./remote/real_repo.git/config http.receivepack true

Then we start the server which will allow clients to access the repository through http.

  $ GIT_DIR=./remote/ GIT_PROJECT_ROOT=${TESTTMP}/remote/ GIT_HTTP_EXPORT_ALL=1 hyper-cgi-test-server\
  >  --port=8001\
  >  --dir=./remote/\
  >  --cmd=git\
  >  --args=http-backend\
  >  > ./hyper-cgi-test-server.out 2>&1 &
  $ echo $! > ./server_pid

Our server is ready, serving all the repos in the remote folder on port 8001.

  $ git clone http://localhost:8001/real_repo.git
  Cloning into 'real_repo'...
  warning: You appear to have cloned an empty repository.

Adding some content

Of course, the repository is for now empty, and we need to populate it. The populate.sh script creates a couple of libraries, as well as two applications that use them.

  $ cd real_repo
  $ sh ${TESTDIR}/populate.sh > ../populate.out

  $ git push origin HEAD
  To http://localhost:8001/real_repo.git
   * [new branch]      HEAD -> master

  $ tree
  .
  |-- application1
  |   `-- app.c
  |-- application2
  |   `-- guide.c
  |-- doc
  |   |-- guide.md
  |   |-- library1.md
  |   `-- library2.md
  |-- library1
  |   `-- lib1.h
  `-- library2
      `-- lib2.h
  
  5 directories, 7 files
  $ git log --oneline --graph
  * f65e94b Add documentation
  * f240612 Add application2
  * 0a7f473 Add library2
  * 1079ef1 Add application1
  * 6476861 Add library1

Creating our first workspace

Now that we have a git repo populated with content, let's serve it through josh:

$ docker run -d --network="host" -e JOSH_REMOTE=http://127.0.0.1:8001 -v josh-vol:$(pwd)/git_data joshproject/josh-proxy:latest > josh.out

NOTE

For the sake of this example, we run docker with --network="host" instead of publishing the port. This is so that docker can access localhost, where our ad-hoc git repository is served.

To facilitate developement on applications 1 and 2, we want to create workspaces for them. Creating a new workspace looks very similar to checking out a subfolder through josh, as explained in "Getting Started".

Instead of just the name of the subfolder, though, we also use the :workspace= filter:

  $ git clone http://127.0.0.1:8000/real_repo.git:workspace=application1.git application1
  Cloning into 'application1'...
  $ cd application1
  $ tree
  .
  `-- app.c
  
  0 directories, 1 file
  $ git log -2
  commit 50cd6112e173df4cac1aca9cb88b5c2a180bc526
  Author: Josh <josh@example.com>
  Date:   Thu Apr 7 22:13:13 2005 +0000
  
      Add application1

Looking into the newly cloned workspace, we see our expected files and the history containing the only relevant commit.

NOTE

Josh allows us to create a workspace out of any directory, even one that doesn't exist yet.

Adding workspace.josh

The workspace.josh file describes how folders from the central repository (real_repo.git) should be mapped to the workspace repository.

Since we depend on library1, let's add it to the workspace file.

  $ echo "modules/lib1 = :/library1" >> workspace.josh

  $ git add workspace.josh

  $ git commit -m "Map library1 to the application1 workspace"
  [master 06361ee] Map library1 to the application1 workspace
   1 file changed, 1 insertion(+)
   create mode 100644 workspace.josh

We decided to map library1 to modules/lib1 in the workspace. We can now sync up with the server:

  $ git sync origin HEAD
    HEAD -> refs/heads/master
  From http://127.0.0.1:8000/real_repo.git:workspace=application1
   * branch            753d62ca1af960a3d071bb3b40722471228abbf6 -> FETCH_HEAD
  HEAD is now at 753d62c Map library1 to the application1 workspace
  Pushing to http://127.0.0.1:8000/real_repo.git:workspace=application1.git
  POST git-receive-pack (477 bytes)
  remote: josh-proxy        
  remote: response from upstream:        
  remote: To http://localhost:8001/real_repo.git        
  remote:    f65e94b..37184cc  JOSH_PUSH -> master        
  remote: REWRITE(06361eedf6d6f6d7ada6000481a47363b0f0c3de -> 753d62ca1af960a3d071bb3b40722471228abbf6)        
  remote: 
  remote: 
  updating local tracking ref 'refs/remotes/origin/master'
  

let's observe the result:

  $ tree
  .
  |-- app.c
  |-- modules
  |   `-- lib1
  |       `-- lib1.h
  `-- workspace.josh
  
  2 directories, 3 files
  $ git log --graph --oneline
  *   753d62c Map library1 to the application1 workspace
  |\  
  | * 366adba Add library1
  * 50cd611 Add application1

After pushing and fetching the result, we see that it has been succesfully mapped by josh.

One suprising thing is the history: our "mapping" commit became a merge commit! This is because josh needs to merge the history of the module we want to map into the repository of the workspace. After this is done, all commits will be present in both of the histories.

NOTE

git sync is a utility provided with josh which will push contents, and, if josh tells it to, fetch the transformed result. Otherwise, it works like git push.

By the way, what does the history look like on the real_repo ?

  $ cd ../real_repo
  $ git pull origin master
  From http://localhost:8001/real_repo
   * branch            master     -> FETCH_HEAD
     f65e94b..37184cc  master     -> origin/master
  Updating f65e94b..37184cc
  Fast-forward
   application1/workspace.josh | 1 +
   1 file changed, 1 insertion(+)
   create mode 100644 application1/workspace.josh
  Current branch master is up to date.

  $ tree
  .
  |-- application1
  |   |-- app.c
  |   `-- workspace.josh
  |-- application2
  |   `-- guide.c
  |-- doc
  |   |-- guide.md
  |   |-- library1.md
  |   `-- library2.md
  |-- library1
  |   `-- lib1.h
  `-- library2
      `-- lib2.h
  
  5 directories, 8 files
  $ git log --graph --oneline
  * 37184cc Map library1 to the application1 workspace
  * f65e94b Add documentation
  * f240612 Add application2
  * 0a7f473 Add library2
  * 1079ef1 Add application1
  * 6476861 Add library1

We can see the newly added commit for workspace.josh in application1, and as expected, no merge here.

Interacting with workspaces

Let's now create a second workspce, this time for application2. It depends on library1 and library2.

  $ git clone http://127.0.0.1:8000/real_repo.git:workspace=application2.git application2
  Cloning into 'application2'...
  $ cd application2
  $ echo "libs/lib1 = :/library1" >> workspace.josh
  $ echo "libs/lib2 = :/library2" >> workspace.josh
  $ git add workspace.josh && git commit -m "Create workspace for application2"
  [master 566a489] Create workspace for application2
   1 file changed, 2 insertions(+)
   create mode 100644 workspace.josh

Syncing as before:

  $ git sync origin HEAD
    HEAD -> refs/heads/master
  From http://127.0.0.1:8000/real_repo.git:workspace=application2
   * branch            5115fd2a5374cbc799da61a228f7fece3039250b -> FETCH_HEAD
  HEAD is now at 5115fd2 Create workspace for application2
  Pushing to http://127.0.0.1:8000/real_repo.git:workspace=application2.git
  POST git-receive-pack (478 bytes)
  remote: josh-proxy        
  remote: response from upstream:        
  remote: To http://localhost:8001/real_repo.git        
  remote:    37184cc..feb3a5b  JOSH_PUSH -> master        
  remote: REWRITE(566a4899f0697d0bde1ba064ed81f0654a316332 -> 5115fd2a5374cbc799da61a228f7fece3039250b)        
  remote: 
  remote: 
  updating local tracking ref 'refs/remotes/origin/master'
  

And our local folder now contains all the files requested:

  $ tree
  .
  |-- guide.c
  |-- libs
  |   |-- lib1
  |   |   `-- lib1.h
  |   `-- lib2
  |       `-- lib2.h
  `-- workspace.josh
  
  3 directories, 4 files

And the history includes the history of both of the libraries:

  $ git log --oneline --graph
  *   5115fd2 Create workspace for application2
  |\  
  | * ffaf58d Add library2
  | * f4e4e40 Add library1
  * ee8a5d7 Add application2

Note that since we created the workspace and added the dependencies in one single commit, the history just contains this one single merge commit.

Pushing a change from a workspace

While testing application2, we noticed a typo in the library1 dependency. Let's go ahead a fix it!

  $ sed -i 's/41/42/' libs/lib1/lib1.h
  $ git commit -a -m "fix lib1 typo"
  [master 82238bf] fix lib1 typo
   1 file changed, 1 insertion(+), 1 deletion(-)

We can push this change like any normal git change:

  $ git push origin master
  remote: josh-proxy        
  remote: response from upstream:        
  remote: To http://localhost:8001/real_repo.git        
  remote:    feb3a5b..31e8fab  JOSH_PUSH -> master        
  remote: 
  remote: 
  To http://127.0.0.1:8000/real_repo.git:workspace=application2.git
     5115fd2..82238bf  master -> master

Since the change was merged in the central repository, a developper can now pull from the application1 workspace.

  $ cd ../application1
  $ git pull
  From http://127.0.0.1:8000/real_repo.git:workspace=application1
   + 06361ee...c64b765 master     -> origin/master  (forced update)
  Updating 753d62c..c64b765
  Fast-forward
   modules/lib1/lib1.h | 2 +-
   1 file changed, 1 insertion(+), 1 deletion(-)
  Current branch master is up to date.

The change has been propagated!

  $ git log --oneline --graph
  * c64b765 fix lib1 typo
  *   753d62c Map library1 to the application1 workspace
  |\  
  | * 366adba Add library1
  * 50cd611 Add application1