The more you work with git to manage your code (or other stuff), the messier your directory structure becomes and after a few years you will maybe end up with something like this:
├── code
│ ├── blog
│ └── website
├── repos
│ ├── coolproject
│ └── demo
├── repositories
│ └── website
└── src
├── foo
└── project1337
At least for me this happened. Most of these directories were at least actual git repositories but some were not. Obviously this is a mess and if each of the directories contains a few dozens of repositories it is getting hard to find the right one.
So I was looking for a better way to organize my repositories and a friend told me about ghq. ghq makes it easy to manage all your git repositories and enforces an organized directory structure at the same time. If you work with go you will already know the directory structure as it is exactly the same.
ghq(1)
NAME
ghq - Manage remote repository clones
DESCRIPTION
'ghq' provides a way to organize remote repository clones, like go get does. When you clone a remote repository by ghq get, ghq makes a directory under a specific root directory (by default ~/ghq) using the remote repository URL’s host and path.
$ ghq get https://github.com/x-motemen/ghq # Runs `git clone https://github.com/x-motemen/ghq ~/ghq/github.com/x-motemen/ghq`
You can also list local repositories (ghq list).
SYNOPSIS
ghq get [-u] [-p] [--shallow] [--vcs <vcs>] [--look] [--silent] [--branch] [--no-recursive] <repository URL>|<host>/<user>/<project>|<user>/<project>|<project> ghq list [-p] [-e] [<query>] ghq create [--vcs <vcs>] <repository URL>|<host>/<user>/<project>|<user>/<project>|<project> ghq root [--all]
COMMANDS
- get
-
Clone a remote repository under ghq root directory (see DIRECTORY STRUCTURES below). If the repository is already cloned to local, nothing will happen unless '-u' ('--update') flag is supplied, in which case the local repository is updated ('git pull --ff-only' eg.) When you use '-p' option, the repository is cloned via…
Install ghq
Installing ghq is easy. If you use a Linux distribution you can check if ghq is available with your package manger. Otherwise you have a few other options. You can download a current release from GitHub or if you have go
installed you can run go get github.com/motemen/ghq
. In case you use macOS you can run brew install ghq
. To verify your installation just run:
$ ghq root
/home/max/.ghq
Change the ghq root directory
By default ghq organizes all git repositories in ~/.ghq
but this can be changed in your ~/.gitconfig
file. For example, I do not like the idea of putting all my repositories in a hidden directory like .ghq
. Instead I use ~/repos
, so the config entry in my ~/.gitconfig
file looks like this:
[ghq]
root = ~/repos
root = ~/go/src
By defining root=...
multiple times ghq uses all directories to look for repositories. This is particularly helpful if you work with go as it organizes repositories in the same way but puts them in ~/go/src
by default. If you run ghq list
to display all your repositories ghq looks in ~/repos
and ~/go/src
as well.
Clone a new repository
Instead of running git clone https://github.com/githubtraining/hellogitworld.git
to checkout a new repository you now have to run ghq get https://github.com/githubtraining/hellogitworld.git
and ghq will use the repository URL to create a proper directory structure:
repos/
└── github.com
└── githubtraining
└── hellogitworld
├── build.gradle
├── fix.txt
├── pom.xml
├── README.txt
└ ...
If you clone more repositories from different servers and from different users ghq will create directories to keep everything organized. For example an excerpt of my ~/repos
directory looks like this:
├── aur.archlinux.org
│ └── heluxup
│ └── PKGBUILD
├── git.centralserver.de
│ └── max
│ ├── bachelor-thesis
│ ├── dotfiles
│ ├── fotoallerlei
│ ├── k8s
│ ├── telegram-stickers
│ └── wiki
└ github.com
├── ekeih
│ ├── dwdpollen
│ ├── FrundenBot
│ ├── hello-github-actions
│ ├── heluxup
│ ├── hetzner-deployment
│ ├── i3-renameworkspace
│ ├── icinga2telegram
│ ├── InfiniteWisdomBot
│ ├── tado
│ ├── tado-influxdb
│ ├── taustakuva
│ └── webhook
├── fluxcd
│ └── helm-operator
└── grandchild
└── arch-cleaner
ghq look
and what's bad about it
Personally, I find this way of organization already a huge benefit of ghq but the major advantage is the way ghq helps to directly jump to the right repository:
$ pwd
/home/max
$ ghq look hello
$ pwd
/home/max/repos/github.com/githubtraining/hellogitworld
This way it is possible to switch between different repositories fast and without the need to know the full paths of them. Unfortunately, this has a downside... the current working directory of a process is part of its environment and a process environment can not be modified by its child processes. If you want to read some more technical background about this I recommend this Stack Overflow answer which explains it in more detail. In the end it means that ghq can not really change the current working directory of your shell... instead it starts a new subshell in the new directory. Technically this works but I find it very annoying that each ghq look ...
adds an additional nested shell process end exiting the shell means to return to the previous shell.
How can I write a Go program that will behave as 'cd ' does?
This is impossible on POSIX systems (even by using any other programming languages).
Because each process, including the parent shell process, has its own current working directory. Hence cd
has to be a shell…
The developer of ghq suggested to use a shell function to wrap ghq to solve this issue in a GitHub issue. This approach may seem a bit weird but it actually works great and is probably the only solution to really change the current working directory by running cd
which is a shell builtin (shell builtins run directly in the shell process and not in a new one, therefore they can modify the environment of the shell process).
Comment for #29
I'd like to, but I don't think ghq can change the shell's working directory since it is just a command, not a shell function. Any ideas to implement that?
One solution would be providing a shell function like:
ghq () {
if [ "$1" = look -a -n "$2" ]; then
cd $(command ghq list -e -p $2)
return
fi
command ghq "$@"
}
I extended the function a bit and added it to my ~/.bashrc
:
ghq () {
if [ "$1" = look -a -n "$2" ]; then
local repos=($(command ghq list -p "$2"))
case ${#repos[@]} in
0)
echo 'No repo found.'
return 1
;;
1)
cd "${repos[0]}"
return
;;
*)
local PS3="Select repo: "
select reponame in ${repos[@]}; do
cd "${reponame}"
return
done
esac
elif [ "$1" = get -a -n "$2" ]; then
command ghq "$@"
cd $(command ghq list -e -p "$2")
return
fi
command ghq "$@"
}
Remember to open a new shell after modifying your ~/.bashrc
or run source ~/.bashrc
to load the changes. So what does it do? Each time you run ghq
your shell does not run the real ghq binary (e.g. in /usr/bin/ghq
) directly, instead it runs the ghq
shell function with the same name.
Line 2-19 wrap the ghq look ...
call which means that when you call ghq look foobar
the function searches for a repository with foobar in its name and if it finds exactly one it runs cd ...
to switch to the directory of it. In case multiple repositories match foobar it offers a selection and then switches to the selected repository.
$ ghq look foobar
No repo found.
$ ghq look fotoallerlei
$ pwd
/home/max/repos/git.centralserver.de/max/fotoallerlei
$ ghq look heluxup
1) /home/max/repos/aur.archlinux.org/heluxup
2) /home/max/repos/github.com/ekeih/heluxup
Select repo: 2
$ pwd
/home/max/repos/github.com/ekeih/heluxup
Line 20-25 wrap the ghq get ...
call to switch to the new repository after it is cloned successfully. If ghq is called neither with look
nor with get
the original ghq binary is called directly.
Wrap-Up
After moving my existing repositories to the directory structure of ghq I started to use it every day. It took me a few days to remember to use ghq get ...
instead of git clone ...
and ghq look ...
instead of cd ~/repos/...
, but I like this new workflow very much and can recommend it to everyone who uses multiple git repositories. The shell function to wrap the ghq call to avoid nested shell processes makes it a lot easier to use, so please take a second and add it to your ~/.bashrc
if you start to use ghq.
Top comments (0)