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