From: Richard Hartmann Date: Sat, 1 Nov 2014 06:17:33 +0000 (+0100) Subject: Merge branch 'master' into feature/tests X-Git-Url: https://git.madduck.net/code/vcsh.git/commitdiff_plain/73658dcd2c73106375cab7f649bce7be02836ca0?hp=dd7e32f2cbc2ad60bee86d8c3f5dffd436d360be Merge branch 'master' into feature/tests --- diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 9ccbd87..d7a3394 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,17 +1,24 @@ Alphabetical list of surnames of everyone who ever committed to this repository. Auto-generated from tools/list_CONTRIBUTORS. +Skurikhin Alexander Eric Bouchut Dridi Boukelmoune Rob Cornish Vincent Demeester +Mert Dirik +Jeff Fein-Worton Thomas Ferris Nicolaisen martin f. krafft Alessandro Ghedini +Thorsten Glaser +G.raud Mikhail Gusarov Valentin Haenel Richard Hartmann Gregor Jasny +Errietta Kostala +Yuval Langer Caleb Maclennan Markus Martin mek-apelsin @@ -19,9 +26,14 @@ Evan Pitstick Dieter Plaetinck Corey Quinn Pavlos Ratis +Dewey Sasser Gernot Schulz +Aaron Schumacher +Andrew Schwartzmeyer +Dato Simó Alexander Skurikhin Jonathan Sternberg Frank Terbeck +mirabilos Aaron VonderHaar Tony diff --git a/Makefile b/Makefile index 4ae8d88..ca6ebe6 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PREFIX=/usr +PREFIX?=/usr DOCDIR_PREFIX=$(PREFIX)/share/doc DOCDIR=$(DOCDIR_PREFIX)/$(self) ZSHDIR=$(PREFIX)/share/zsh/vendor-completions @@ -47,4 +47,4 @@ test: @if which git > /dev/null ; then :; else echo "'git' not found, exiting..."; exit 1; fi moo: - @if [ -x /usr/games/cowsay ]; then /usr/games/cowsay "I hope you're happy now..."; fi + @ which cowsay >/dev/null 2>&1 && cowsay "I hope you're happy now..." diff --git a/README.md b/README.md index 57f56ed..763b7c4 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,12 @@ vcsh - Version Control System for $HOME - multiple Git repositories in $HOME # Index -1. [30 second howto](#30-second-howto) +1. [30 Second How-to](#30-second-how-to) 2. [Introduction](#introduction) -3. [Usage Exmaples](#usage-examples) -4. [Overview](#overview) -5. [Getting Started](#getting-started) -6. [Contact](#contact) +3. [Contact](#contact) -# 30 second howto +# 30 Second How-to While it may appear that there's an overwhelming amount of documentation and while the explanation of the concepts behind `vcsh` needs to touch a few gory @@ -39,6 +36,7 @@ directory. They all maintain their working trees without clobbering each other or interfering otherwise. By default, all Git repositories maintained via `vcsh` store the actual files in `$HOME` but you can override this setting if you want to. + All this means that you can have one repository per application or application family, i.e. `zsh`, `vim`, `ssh`, etc. This, in turn, allows you to clone custom sets of configurations onto different machines or even for different @@ -48,9 +46,10 @@ or available to root and you may want to maintain different configuration for `ssh` on your personal and your work machines. A lot of modern UNIX-based systems offer packages for `vcsh`. In case yours -does not read `INSTALL.md` for install instructions or `PACKAGING.md` to create -a package, yourself. If you do end up packaging `vcsh` please let us know so we -can give you your own packaging branch in the upstream repository. +does not, read [INSTALL.md](doc/INSTALL.md) for install instructions or +[PACKAGING.md](doc/PACKAGING.md) to create a package yourself. If you do end +up packaging `vcsh` please let us know so we can give you your own packaging +branch in the upstream repository. ## Talks @@ -60,372 +59,6 @@ All slides, videos, and further information can be found [on the author's talk page][talks]. -# Usage Examples - -There are three different ways to interact with `vcsh` repositories; this -section will only show the simplest and easiest way. -Certain more advanced use cases require the other two ways, but don't worry -about this for now. If you never even bother playing with the other two -modes you will still be fine. -`vcsh enter` and `vcsh run` will be covered in later sections. - - -| Task | Command | -| ----------------------------------------------------- | ------------------------------------------------- | -| _Initialize a new repository called "vim"_ | `vcsh init vim` | -| _Clone an existing repository_ | `vcsh clone ` | -| _Add files to repository "vim"_ | `vcsh vim add ~/.vimrc ~/.vim` | -| | `vcsh vim commit -m 'Update Vim configuration'` | -| _Add a remote for repository "vim"_ | `vcsh vim remote add origin ` | -| | `vcsh vim push origin master:master` | -| | `vcsh vim branch --track master origin/master` | -| _Push to remote of repository "vim"_ | `vcsh vim push` | -| _Pull from remote of repository "vim"_ | `vcsh vim pull` | -| _Show status of changed files in all repositories_ | `vcsh status` | -| _Pull from all repositories_ | `vcsh pull` | -| _Push to all repositories_ | `vcsh push` | - - -# Overview - -## From zero to vcsh - -You put a lot of effort into your configuration and want to both protect and -distribute this configuration. - -Most people who decide to put their dotfiles under version control start with a -single repository in `$HOME`, adding all their dotfiles (and possibly more) -to it. This works, of course, but can become a nuisance as soon as you try to -manage more than one host. - -The next logical step is to create single-purpose repositories in, for example, -`~/.dotfiles` and to create symbolic links into `$HOME`. This gives you the -flexibility to check out only certain repositories on different hosts. The -downsides of this approach are the necessary manual steps of cloning and -symlinking the individual repositories. - -`vcsh` takes this approach one step further. It enables single-purpose -repositories and stores them in a hidden directory. However, it does not create -symbolic links in `$HOME`; it puts the actual files right into `$HOME`. - -As `vcsh` allows you to put an arbitrary number of distinct repositories into -your `$HOME`, you will end up with a lot of repositories very quickly. - -To manage both `vcsh` and other repositories, we suggest using [mr](mr). `mr` -takes care of pulling in and pushing out new data for a variety of version -control systems. - -`vcsh` was designed with [mr][mr], a tool to manage Multiple Repositories, in -mind and the two integrate very nicely. `mr` has native support for `vcsh` -repositories and to `vcsh`, `mr` is just another configuration to track. -This make setting up any new machine a breeze. It takes literally less than -five minutes to go from standard installation to fully set up system - -This is where `mr` comes in. While the use of `mr` is technically -optional, but it will be an integral part of the proposed system that follows. - -## Default Directory Layout - -To illustrate, this is what a possible directory structure looks like. - - $HOME - |-- $XDG_CONFIG_HOME (defaults to $HOME/.config) - | |-- mr - | | |-- available.d - | | | |-- zsh.vcsh - | | | |-- gitconfigs.vcsh - | | | |-- lftp.vcsh - | | | |-- offlineimap.vcsh - | | | |-- s3cmd.vcsh - | | | |-- tmux.vcsh - | | | |-- vim.vcsh - | | | |-- vimperator.vcsh - | | | `-- snippets.git - | | `-- config.d - | | |-- zsh.vcsh -> ../available.d/zsh.vcsh - | | |-- gitconfigs.vcsh -> ../available.d/gitconfigs.vcsh - | | |-- tmux.vcsh -> ../available.d/tmux.vcsh - | | `-- vim.vcsh -> ../available.d/vim.vcsh - | `-- vcsh - | |-- config - | `-- repo.d - | |-- zsh.git -----------+ - | |-- gitconfigs.git | - | |-- tmux.git | - | `-- vim.git | - |-- [...] | - |-- .zshrc <----------------------+ - |-- .gitignore.d - | `-- zsh - |-- .mrconfig - `-- .mrtrust - -### available.d - -The files you see in $XDG\_CONFIG\_HOME/mr/available.d are mr configuration files -that contain the commands to manage (checkout, update etc.) a single -repository. vcsh repo configs end in .vcsh, git configs end in .git, etc. This -is optional and your preference. For example, this is what a zsh.vcsh -with read-only access to my zshrc repo looks likes. I.e. in this specific -example, push can not work as you will be using the author's repository. This -is for demonstration, only. Of course, you are more than welcome to clone from -this repository and fork your own. - - [$XDG_CONFIG_HOME/vcsh/repo.d/zsh.git] - checkout = vcsh clone 'git://github.com/RichiH/zshrc.git' zsh - update = vcsh zsh pull - push = vcsh zsh push - status = vcsh zsh status - gc = vcsh zsh gc - -### config.d - -$XDG\_CONFIG\_HOME/mr/available.d contains *all available* repositories. Only -files/links present in mr/config.d, however, will be used by mr. That means -that in this example, only the zsh, gitconfigs, tmux and vim repositories will -be checked out. A simple `mr update` run in $HOME will clone or update those -four repositories listed in config.d. - -### ~/.mrconfig - -Finally, ~/.mrconfig will tie together all those single files which will allow -you to conveniently run `mr up` etc. to manage all repositories. It looks like -this: - - [DEFAULT] - include = cat ${XDG_CONFIG_HOME:-$HOME/.config}/mr/config.d/* - -### repo.d - -$XDG\_CONFIG\_HOME/vcsh/repo.d is the directory where all git repositories which -are under vcsh's control are located. Since their working trees are configured -to be in $HOME, the files contained in those repositories will be put in $HOME -directly. -Of course, [mr] [mr] will work with this layout if configured according to this -document (see above). - -vcsh will check if any file it would want to create exists. If it exists, vcsh -will throw a warning and exit. Move away your old config and try again. -Optionally, merge your local and your global configs afterwards and push with -`vcsh foo push`. - -## Moving into a New Host - -To illustrate further, the following steps could move your desired -configuration to a new host. - -1. Clone the mr repository (containing available.d, config.d etc.); for - example: `vcsh clone git://github.com/RichiH/vcsh_mr_template.git mr` -2. Choose your repositories by linking them in config.d (or go with the default - you may have already configured by adding symlinks to git). -3. Run mr to clone the repositories: `cd; mr update`. -4. Done. - -Hopefully the above could help explain how this approach saves time by - -1. making it easy to manage, clone and update a large number of repositories - (thanks to mr) and -2. making it unnecessary to create symbolic links in $HOME (thanks to vcsh). - -If you want to give vcsh a try, follow the instructions below. - - -# Getting Started - -Below, you will find a few different methods for setting up vcsh: - -1. The Template Way -2. The Steal-from-Template Way -3. The Manual Way - -### The Template Way - -#### Prerequisites - -Make sure none of the following files and directories exist for your test -(user). If they do, move them away for now: - -* ~/.gitignore.d -* ~/.mrconfig -* $XDG\_CONFIG\_HOME/mr/available.d/mr.vcsh -* $XDG\_CONFIG\_HOME/mr/available.d/zsh.vcsh -* $XDG\_CONFIG\_HOME/mr/config.d/mr.vcsh -* $XDG\_CONFIG\_HOME/vcsh/repo.d/mr.git/ - -All of the files are part of the template repository, the directory is where -the template will be stored. - - apt-get install mr - -### Install vcsh - -#### Debian - -If you are using Debian Squeeze, you will need to enable backports. -From Wheezy onwards, you can install it directly: - - apt-get install vcsh - -#### Gentoo - -To install vcsh in Gentoo Linux just give the following command as root: - - emerge dev-vcs/vcsh - -#### Arch Linux - -vcsh is availabe via [AUR](https://aur.archlinux.org/packages.php?ID=54164) -and further documentation about the use of AUR is available -[on Arch's wiki](https://wiki.archlinux.org/index.php/Arch_User_Repository). - - cd /var/abs/local/ - wget https://aur.archlinux.org/packages/vc/vcsh-git/vcsh-git.tar.gz - tar xfz vcsh-git.tar.gz - cd vcsh-git - makepkg -s - pacman -U vcsh*.pkg.tar.xz - -#### From source - - # choose a location for your checkout - mkdir -p ~/work/git - cd ~/work/git - git clone git://github.com/RichiH/vcsh.git - cd vcsh - sudo ln -s vcsh /usr/local/bin # or add it to your PATH - cd - -#### Clone the Template - - vcsh clone git://github.com/RichiH/vcsh_mr_template.git mr - -#### Enable Your Test Repository - - mv ~/.zsh ~/zsh.bak - mv ~/.zshrc ~/zshrc.bak - cd $XDG_CONFIG_HOME/mr/config.d/ - ln -s ../available.d/zsh.vcsh . # link, and thereby enable, the zsh repository - cd - mr up - -#### Set Up Your Own Repositories - -Now, it's time to edit the template config and fill it with your own remotes: - - vim $XDG_CONFIG_HOME/mr/available.d/mr.vcsh - vim $XDG_CONFIG_HOME/mr/available.d/zsh.vcsh - -And then create your own stuff: - - vcsh init foo - vcsh foo add bar baz quux - vcsh foo remote add origin git://quuux - vcsh foo commit - vcsh foo push - - cp $XDG_CONFIG_HOME/mr/available.d/mr.vcsh $XDG_CONFIG_HOME/mr/available.d/foo.vcsh - vim $XDG_CONFIG_HOME/mr/available.d/foo.vcsh # add your own repo - -Done! - -### The Steal-from-Template Way - -You're welcome to clone the example repository: - - vcsh clone git://github.com/RichiH/vcsh_mr_template.git mr - # make sure 'include = cat /usr/share/mr/vcsh' points to an exiting file - vim .mrconfig - -Look around in the clone. It should be reasonably simple to understand. If not, -poke me, RichiH, on Freenode (query) or OFTC (#vcs-home). - - -### The Manual Way - -This is how my old setup procedure looked like. Adapt it to your own style or -copy mine verbatim, either is fine. - - # Create workspace - mkdir -p ~/work/git - cd !$ - - # Clone vcsh and make it available - git clone git://github.com/RichiH/vcsh.git vcsh - sudo ln -s ~/work/git/vcsh/vcsh /usr/bin/local - hash -r - -Grab my mr config. see below for details on how I set this up - - vcsh clone ssh:///mr.git - cd $XDG_CONFIG_HOME/mr/config.d/ - ln -s ../available.d/* . - - -mr is used to actually retrieve configs, etc - - ~ % cat ~/.mrconfig - [DEFAULT] - # adapt /usr/share/mr/vcsh to your system if needed - include = cat /usr/share/mr/vcsh - include = cat $XDG_CONFIG_HOME/mr/config.d/* - ~ % echo $XDG_CONFIG_HOME - /home/richih/.config - ~ % ls $XDG_CONFIG_HOME/mr/available.d # random selection of my repos - git-annex gitk.vcsh git.vcsh ikiwiki mr.vcsh reportbug.vcsh snippets.git wget.vcsh zsh.vcsh - ~ % - # then simply ln -s whatever you want on your local machine from - # $XDG_CONFIG_HOME/mr/available.d to $XDG_CONFIG_HOME/mr/config.d - ~ % cd - ~ % mr -j 5 up - - -# mr usage ; will be factored out & rewritten - -### Keeping repositories Up-to-Date - -This is the beauty of it all. Once you are set up, just run: - - mr up - mr push - -Neat. - -### Making Changes - -After you have made some changes, for which you would normally use `git add` -and `git commit`, use the vcsh wrapper (like above): - - vcsh foo add bar baz quux - vcsh foo commit - vcsh foo push - -### Using vcsh without mr - -vcsh encourages you to use [mr][mr]. It helps you manage a large number of -repositories by running the necessary vcsh commands for you. You may choose not -to use mr, in which case you will have to run those commands manually or by -other means. - - -To initialize a new repository: `vcsh init zsh` - -To clone a repository: `vcsh clone ssh:///zsh.git` - -To interact with a repository, use the regular Git commands, but prepend them -with `vcsh run $repository_name`. For example: - - vcsh zsh status - vcsh zsh add .zshrc - vcsh zsh commit - -Obviously, without mr keeping repositories up-to-date, it will have to be done -manually. Alternatively, you could try something like this: - - for repo in `vcsh list`; do - vcsh run $repo git pull; - done - - # Contact There are several ways to get in touch with the author and a small but committed @@ -438,7 +71,7 @@ community around the general idea of version controlling your (digital) life. * Pull requests or issues on [https://github.com/RichiH/vcsh][vcsh] -[mr]: http://kitenet.net/~joey/code/mr/ +[myrepos]: http://myrepos.branchable.com/ [talks]: http://richardhartmann.de/talks/ [vcsh]: https://github.com/RichiH/vcsh [vcs-home-list]: http://lists.madduck.net/listinfo/vcs-home diff --git a/_vcsh b/_vcsh index 47dbd9e..53eaada 100644 --- a/_vcsh +++ b/_vcsh @@ -43,6 +43,10 @@ function _vcsh-list-tracked-by () { (( CURRENT == 2 )) && __vcsh_repositories } +function _vcsh-list-untracked () { + _nothing +} + function _vcsh-pull () { _nothing } @@ -66,6 +70,10 @@ function _vcsh-run () { fi } +function _vcsh-status () { + (( CURRENT == 2 )) && __vcsh_repositories +} + function _vcsh-upgrade () { (( CURRENT == 2 )) && __vcsh_repositories } @@ -89,6 +97,7 @@ function _vcsh () { subcommands=( "clone:clone an existing repository" + "commit:commit in all repositories" "delete:delete an existing repository" "enter:enter repository; spawn new <\$SHELL>" "help:display help" @@ -96,9 +105,12 @@ function _vcsh () { "list:list all local vcsh repositories" "list-tracked:list all files tracked by vcsh" "list-tracked-by:list files tracked by a repository" + "list-untracked:list all files not tracked by vcsh" "pull:pull from all vcsh remotes" + "push:push to vcsh remotes" "rename:rename a repository" "run:run command with <\$GIT_DIR> and <\$GIT_WORK_TREE> set" + "status:show statuses of all/one vcsh repositories" "upgrade:upgrade repository to currently recommended settings" "version:print version information" "which:find in name of any tracked file" diff --git a/changelog b/changelog index dc578c6..9cc6c16 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,52 @@ +2014-10-26 Richard Hartmann + + * Release 1.20141026 + * FIX fatal() + * Improve error handling of clone() + +2014-10-25 Richard Hartmann + + * Release 1.20141025 + * `vcsh which dontexist` exits 1 + * `vcsh status` shows commits ahead/behind remote tracking branch + * Support overlay functions + * Support `vcsh list-untracked`, optionally recursively + * Support `vcsh list-untracked $repo` + * Improve error handling of clone() + * Rename `list-tracked-by` to `list-tracked ` + * Support repo-specific config files + * Various minor improvements + * More moo + +2014-10-09 Richard Hartmann + + * Display full paths in list-tracked* + * Lots of help improvements + +2014-05-08 Richard Hartmann + + * Revert "Introduce static manpage as part of normal repo" + +2014-05-07 Richard Hartmann + + * Increase portability + * Improve hooks + * Improve docs + * Various minor improvements + +2014-03-13 Richard Hartmann + + * Release 1.20140313 -- the "Git won a prize" edition + * Initialize repos with --shared=0600 + * Fix cloning from some spefically-named remotes + * Support custom gitattribute files + * Support filenames with whitespace in gitignores + * Pass out git exit codes + * Switch to static, pre-built manpage + * Improve documentation + * Use more portable find(1) syntax + * Various minor improvements + 2013-12-29 Richard Hartmann * Release 1.20131229 diff --git a/INSTALL.md b/doc/INSTALL.md similarity index 100% rename from INSTALL.md rename to doc/INSTALL.md diff --git a/PACKAGING.md b/doc/PACKAGING.md similarity index 100% rename from PACKAGING.md rename to doc/PACKAGING.md diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..b14d782 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,477 @@ +vcsh - Version Control System for $HOME - multiple Git repositories in $HOME + + +# Index + +1. [30 Second How-to](#30-second-how-to) +2. [Introduction](#introduction) +3. [Usage Examples](#usage-examples) +4. [Overview](#overview) +5. [Getting Started](#getting-started) +6. [Contact](#contact) + + +# 30 Second How-to + +While it may appear that there's an overwhelming amount of documentation and +while the explanation of the concepts behind `vcsh` needs to touch a few gory +details of `git` internals, getting started with `vcsh` is extremely simple. + +Let's say you want to version control your `vim` configuration: + + vcsh init vim + vcsh vim add ~/.vimrc ~/.vim + vcsh vim commit -m 'Initial commit of my Vim configuration' + # optionally push your files to a remote + vcsh vim remote add origin + vcsh vim push -u origin master + # from now on you can push additional commits like this + vcsh vim push + +If all that looks a _lot_ like standard `git`, that's no coincidence; it's +a design feature. + + +# Introduction + +[vcsh][vcsh] allows you to maintain several Git repositories in one single +directory. They all maintain their working trees without clobbering each other +or interfering otherwise. By default, all Git repositories maintained via +`vcsh` store the actual files in `$HOME` but you can override this setting if +you want to. + +All this means that you can have one repository per application or application +family, i.e. `zsh`, `vim`, `ssh`, etc. This, in turn, allows you to clone +custom sets of configurations onto different machines or even for different +users; picking and mixing which configurations you want to use where. +For example, you may not need to have your `mplayer` configuration on a server +or available to root and you may want to maintain different configuration for +`ssh` on your personal and your work machines. + +A lot of modern UNIX-based systems offer packages for `vcsh`. In case yours +does not read `INSTALL.md` for install instructions or `PACKAGING.md` to create +a package, yourself. If you do end up packaging `vcsh` please let us know so we +can give you your own packaging branch in the upstream repository. + +## Talks + +Some people found it useful to look at slides and videos explaining how `vcsh` +works instead of working through the docs. +All slides, videos, and further information can be found +[on the author's talk page][talks]. + + +# Usage Examples + +There are three different ways to interact with `vcsh` repositories; this +section will only show the simplest and easiest way. + +Certain more advanced use cases require the other two ways, but don't worry +about this for now. If you never even bother playing with the other two +modes you will still be fine. + +`vcsh enter` and `vcsh run` will be covered in later sections. + + +| Task | Command | +| ----------------------------------------------------- | ------------------------------------------------- | +| _Initialize a new repository called "vim"_ | `vcsh init vim` | +| _Clone an existing repository_ | `vcsh clone ` | +| _Add files to repository "vim"_ | `vcsh vim add ~/.vimrc ~/.vim` | +| | `vcsh vim commit -m 'Update Vim configuration'` | +| _Add a remote for repository "vim"_ | `vcsh vim remote add origin ` | +| | `vcsh vim push origin master:master` | +| | `vcsh vim branch --track master origin/master` | +| _Push to remote of repository "vim"_ | `vcsh vim push` | +| _Pull from remote of repository "vim"_ | `vcsh vim pull` | +| _Show status of changed files in all repositories_ | `vcsh status` | +| _Pull from all repositories_ | `vcsh pull` | +| _Push to all repositories_ | `vcsh push` | + + +# Overview + +## From zero to vcsh + +You put a lot of effort into your configuration and want to both protect and +distribute this configuration. + +Most people who decide to put their dotfiles under version control start with a +single repository in `$HOME`, adding all their dotfiles (and possibly more) +to it. This works, of course, but can become a nuisance as soon as you try to +manage more than one host. + +The next logical step is to create single-purpose repositories in, for example, +`~/.dotfiles` and to create symbolic links into `$HOME`. This gives you the +flexibility to check out only certain repositories on different hosts. The +downsides of this approach are the necessary manual steps of cloning and +symlinking the individual repositories. + +`vcsh` takes this approach one step further. It enables single-purpose +repositories and stores them in a hidden directory. However, it does not create +symbolic links in `$HOME`; it puts the actual files right into `$HOME`. + +As `vcsh` allows you to put an arbitrary number of distinct repositories into +your `$HOME`, you will end up with a lot of repositories very quickly. + +`vcsh` was designed with [myrepos][myrepos], a tool to manage Multiple +Repositories, in mind and the two integrate very nicely. The myrepos tool +(`mr`) has native support for `vcsh` repositories and the configuration for +myrepos is just another set of files that you can track with `vcsh` like any +other. This makes setting up any new machine a breeze. It can take literally +less than five minutes to go from standard installation to fully set up system. + +We suggest using [myrepos][myrepos] to manage both `vcsh` and other +repositories. The `mr` utility takes care of pulling in and pushing +out new data for a variety of version control systems. While the use +of myrepos is technically optional, it will be an integral part of the +proposed system that follows. For instance, you can use +[myrepos][myrepos] to track repositories in home such as `.emacs.d`, +which `mr` can clone and update for you automatically. To do this, +just add a `mr` configuration file to `available.d` with a `checkout` +command to clone the repo, and set the [title] to the desired +location, e.g. `$HOME/.emacs.d`. Try the `mr register` command in an +existing repository, then view `~/.mrconfig` for an example. + +## Default Directory Layout + +To illustrate, this is what a possible directory structure looks like. + + $HOME + |-- $XDG_CONFIG_HOME (defaults to $HOME/.config) + | |-- mr + | | |-- available.d + | | | |-- zsh.vcsh + | | | |-- gitconfigs.vcsh + | | | |-- lftp.vcsh + | | | |-- offlineimap.vcsh + | | | |-- s3cmd.vcsh + | | | |-- tmux.vcsh + | | | |-- vim.vcsh + | | | |-- vimperator.vcsh + | | | `-- snippets.git + | | `-- config.d + | | |-- zsh.vcsh -> ../available.d/zsh.vcsh + | | |-- gitconfigs.vcsh -> ../available.d/gitconfigs.vcsh + | | |-- tmux.vcsh -> ../available.d/tmux.vcsh + | | `-- vim.vcsh -> ../available.d/vim.vcsh + | `-- vcsh + | |-- config + | `-- repo.d + | |-- zsh.git -----------+ + | |-- gitconfigs.git | + | |-- tmux.git | + | `-- vim.git | + |-- [...] | + |-- .zshrc <----------------------+ + |-- .gitignore.d + | `-- zsh + |-- .mrconfig + `-- .mrtrust + +### available.d + +The files you see in $XDG\_CONFIG\_HOME/mr/available.d are myrepos +configuration files that contain the commands to manage (checkout, update +etc.) a single repository. vcsh repo configs end in .vcsh, git configs end +in .git, etc. This is optional and your preference. For example, this is +what a zsh.vcsh with read-only access to my zshrc repo looks likes. I.e. in +this specific example, push can not work as you will be using the author's +repository. This is for demonstration, only. Of course, you are more than +welcome to clone from this repository and fork your own. + + [$XDG_CONFIG_HOME/vcsh/repo.d/zsh.git] + checkout = vcsh clone 'git://github.com/RichiH/zshrc.git' 'zsh' + update = vcsh zsh pull + push = vcsh zsh push + status = vcsh zsh status + gc = vcsh zsh gc + + [$HOME/.emacs.d] + checkout = vcsh clone 'git://github.com/andschwa/emacs.git' '.emacs.d' + +### config.d + +$XDG\_CONFIG\_HOME/mr/available.d contains *all available* repositories. Only +files/links present in mr/config.d, however, will be used by myrepos. That means +that in this example, only the zsh, gitconfigs, tmux and vim repositories will +be checked out. A simple `mr update` run in $HOME will clone or update those +four repositories listed in config.d. + +### ~/.mrconfig + +Finally, ~/.mrconfig will tie together all those single files which will allow +you to conveniently run `mr up` etc. to manage all repositories. It looks like +this: + + [DEFAULT] + include = cat ${XDG_CONFIG_HOME:-$HOME/.config}/mr/config.d/* + +### repo.d + +$XDG\_CONFIG\_HOME/vcsh/repo.d is the directory where all git repositories which +are under vcsh's control are located. Since their working trees are configured +to be in $HOME, the files contained in those repositories will be put in $HOME +directly. + +Of course, [myrepos][myrepos] will work with this layout if configured according to +this document (see above). + +vcsh will check if any file it would want to create exists. If it exists, vcsh +will throw a warning and exit. Move away your old config and try again. +Optionally, merge your local and your global configs afterwards and push with +`vcsh repo_name push`. + +## Moving into a New Host + +To illustrate further, the following steps could move your desired +configuration to a new host. + +1. Clone the myrepos repository (containing available.d, config.d etc.); for + example: `vcsh clone git://github.com/RichiH/vcsh_mr_template.git mr` +2. Choose your repositories by linking them in config.d (or go with the default + you may have already configured by adding symlinks to git). +3. Run myrepos to clone the repositories: `cd; mr update`. +4. Done. + +Hopefully the above could help explain how this approach saves time by + +1. making it easy to manage, clone and update a large number of repositories + (thanks to myrepos) and +2. making it unnecessary to create symbolic links in $HOME (thanks to vcsh). + +If you want to give vcsh a try, follow the instructions below. + + +# Getting Started + +Below, you will find a few different methods for setting up vcsh: + +1. The Template Way +2. The Steal-from-Template Way +3. The Manual Way + +### The Template Way + +#### Prerequisites + +Make sure none of the following files and directories exist for your test +(user). If they do, move them away for now: + +* `~/.gitignore.d` +* `~/.mrconfig` +* `$XDG\_CONFIG\_HOME/mr/available.d/mr.vcsh` +* `$XDG\_CONFIG\_HOME/mr/available.d/zsh.vcsh` +* `$XDG\_CONFIG\_HOME/mr/config.d/mr.vcsh` +* `$XDG\_CONFIG\_HOME/vcsh/repo.d/mr.git/` + +All of the files are part of the template repository, the directory is where +the template will be stored. + +### Install vcsh + +#### Debian + +If you are using Debian Squeeze, you will need to enable backports and the +package name for myrepos will be 'mr'. + +From Wheezy onwards, you can install both directly: + + apt-get install myrepos vcsh + +#### Gentoo + +To install vcsh in Gentoo Linux just give the following command as root: + + emerge dev-vcs/vcsh + +Note the portage package for myrepos still has the old project name: + + emerge dev-vcs/mr + +#### Arch Linux + +vcsh is available via this [AUR](https://aur.archlinux.org/packages/vcsh/) +package. Likewise myrepos is available [here](https://aur.archlinux.org/packages/myrepos/). +You may install both useing your favorite AUR helper. e.g. with yaourt: + + yaourt -Sya myrepos vcsh + +Or you can do it yourself manually using the documentation on installing AUR packages +[on Arch's wiki](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages). + +If you prefer to use the devel package that installs the git HEAD version it +is available [here](https://aur.archlinux.org/packages/vcsh-git/). + +#### Mac OS X + +Formulas are available for vcsh as well as git and myrepos through [homebrew](http://brew.sh). The +vcsh formula is set to depend on myrepos, so you only need one install command: + + brew install vcsh + +#### From source + +To install the latest version from git: + + # choose a location for your checkout + mkdir -p ~/work/git + cd ~/work/git + git clone git://github.com/RichiH/vcsh.git + cd vcsh + sudo ln -s vcsh /usr/local/bin # or add it to your PATH + +For myrepos: + + # use checkout location from above + cd ~/work/git + git clone git://myrepos.branchable.com/ myrepos + cd myrepos + make install + +#### Clone the Template + + vcsh clone git://github.com/RichiH/vcsh_mr_template.git mr + +#### Enable Your Test Repository + + mv ~/.zsh ~/zsh.bak + mv ~/.zshrc ~/zshrc.bak + cd $XDG_CONFIG_HOME/mr/config.d/ + ln -s ../available.d/zsh.vcsh . # link, and thereby enable, the zsh repository + cd + mr up + +#### Set Up Your Own Repositories + +Now, it's time to edit the template config and fill it with your own remotes: + + vim $XDG_CONFIG_HOME/mr/available.d/mr.vcsh + vim $XDG_CONFIG_HOME/mr/available.d/zsh.vcsh + +And then create your own stuff: + + + vcsh init repo_name + vcsh repo_name add bar baz quux + vcsh repo_name remote add origin git://quuux + vcsh repo_name commit + vcsh repo_name push + + cp $XDG_CONFIG_HOME/mr/available.d/mr.vcsh $XDG_CONFIG_HOME/mr/available.d/repo_name.vcsh + vim $XDG_CONFIG_HOME/mr/available.d/repo_name.vcsh # add your own repo + +Done! + +### The Steal-from-Template Way + +You're welcome to clone the example repository: + + vcsh clone git://github.com/RichiH/vcsh_mr_template.git mr + # make sure 'include = cat /usr/share/mr/vcsh' points to an exiting file + vim .mrconfig + +Look around in the clone. It should be reasonably simple to understand. If not, +poke me, RichiH, on Freenode (query) or OFTC (#vcs-home). + + +### The Manual Way + +This is how my old setup procedure looked like. Adapt it to your own style or +copy mine verbatim, either is fine. + + # Create workspace + mkdir -p ~/work/git + cd !$ + + # Clone vcsh and make it available + git clone git://github.com/RichiH/vcsh.git vcsh + sudo ln -s ~/work/git/vcsh/vcsh /usr/bin/local + hash -r + +Grab my myrepos config. see below for details on how I set this up + + vcsh clone ssh:///mr.git + cd $XDG_CONFIG_HOME/mr/config.d/ + ln -s ../available.d/* . + + +[myrepos][myrepos] is used to actually retrieve configs, etc. + + ~ % cat ~/.mrconfig + [DEFAULT] + include = cat $XDG_CONFIG_HOME/mr/config.d/* + ~ % echo $XDG_CONFIG_HOME + /home/richih/.config + ~ % ls $XDG_CONFIG_HOME/mr/available.d # random selection of my repos + git-annex gitk.vcsh git.vcsh ikiwiki mr.vcsh reportbug.vcsh snippets.git wget.vcsh zsh.vcsh + ~ % + # then simply ln -s whatever you want on your local machine from + # $XDG_CONFIG_HOME/mr/available.d to $XDG_CONFIG_HOME/mr/config.d + ~ % cd + ~ % mr -j 5 up + + +# myrepos usage ; will be factored out & rewritten + +### Keeping repositories Up-to-Date + +This is the beauty of it all. Once you are set up, just run: + + mr up + mr push + +Neat. + +### Making Changes + +After you have made some changes, for which you would normally use `git add` +and `git commit`, use the vcsh wrapper (like above): + + vcsh repo_name add bar baz quux + vcsh repo_name commit + vcsh repo_name push + +### Using vcsh without myrepos + +vcsh encourages you to use [myrepos][myrepos]. It helps you manage a large number of +repositories by running the necessary vcsh commands for you. You may choose not +to use myrepos, in which case you will have to run those commands manually or by +other means. + + +To initialize a new repository: `vcsh init zsh` + +To clone a repository: `vcsh clone ssh:///zsh.git` + +To interact with a repository, use the regular Git commands, but prepend them +with `vcsh run $repository_name`. For example: + + vcsh zsh status + vcsh zsh add .zshrc + vcsh zsh commit + +Obviously, without myrepos keeping repositories up-to-date, it will have to be done +manually. Alternatively, you could try something like this: + + for repo in `vcsh list`; do + vcsh run $repo git pull; + done + + +# Contact + +There are several ways to get in touch with the author and a small but committed +community around the general idea of version controlling your (digital) life. + +* IRC: #vcs-home on irc.oftc.net + +* Mailing list: [http://lists.madduck.net/listinfo/vcs-home][vcs-home-list] + +* Pull requests or issues on [https://github.com/RichiH/vcsh][vcsh] + + +[myrepos]: http://myrepos.branchable.com/ +[talks]: http://richardhartmann.de/talks/ +[vcsh]: https://github.com/RichiH/vcsh +[vcs-home-list]: http://lists.madduck.net/listinfo/vcs-home diff --git a/doc/hooks b/doc/hooks index 54ff795..282ee86 100644 --- a/doc/hooks +++ b/doc/hooks @@ -14,6 +14,12 @@ Available hooks are: * post-enter * pre-init * post-init +* pre-merge + Use this hook to detect and handle merge conflicts before vcsh's native code + finds and errors on them. This is useful for allowing clones on top of existing + files. +* post-merge + Use this hook to finish handling any merge conflicts found in the pre-merge hook. * pre-pull * post-pull * pre-push diff --git a/doc/sample_hooks/post-init-add-origin b/doc/sample_hooks/post-init-add-origin new file mode 100755 index 0000000..993f3c7 --- /dev/null +++ b/doc/sample_hooks/post-init-add-origin @@ -0,0 +1,18 @@ +#!/bin/sh + +# This adds a remote origin at $GITURL/dotfiles/$VCSH_DIRECTORY which +# is helpful for creating new dotfile repositories that you plan to +# store on (e.g.) Github. +# +# You must set $GITURL in order to use this hook. For example, add the +# following to your .bashrc (or equivalent for your shell), replacing +# YOURUSERNAME with your github username: +# +# export GITURL="git@github.com:YOURUSERNAME" + +if [ -z $GITURL ]; then + echo "\$GITURL is not set; please see post-init-add-origin hook" + exit 1; +fi + +vcsh $VCSH_DIRECTORY remote add origin $GITURL/dotfiles/$VCSH_DIRECTORY diff --git a/doc/sample_hooks/post-init-setup-mr b/doc/sample_hooks/post-init-setup-mr new file mode 100755 index 0000000..a38c012 --- /dev/null +++ b/doc/sample_hooks/post-init-setup-mr @@ -0,0 +1,10 @@ +#!/bin/sh + +# This sets up your new repos with mr. + +cat > $HOME/.config/mr/available.d/$VCSH_DIRECTORY.vcsh << EOF +[\$HOME/.config/vcsh/repo.d/$VCSH_DIRECTORY.git] +checkout = vcsh clone $GITURL/dotfiles/$VCSH_DIRECTORY.git +EOF + +ln -s $HOME/.config/mr/available.d/$VCSH_DIRECTORY.vcsh $HOME/.config/mr/config.d/ diff --git a/doc/sample_hooks/post-merge-unclobber b/doc/sample_hooks/post-merge-unclobber new file mode 100755 index 0000000..2b8f286 --- /dev/null +++ b/doc/sample_hooks/post-merge-unclobber @@ -0,0 +1,10 @@ +#!/bin/sh + +# This finds objects that the pre-merge script moved out of the way to +# avoid conflicts when running `vcsh clone` and moves them back to their +# original places. The result is that the Git repository gets checked out +# without error and the pre-existing files end up back in the working +# directory. Git and thus vcsh now see these as un-staged changes to the +# working branch and you can deal with them as usual. + +find . -name '*.vcsh-unclobber' -execdir rename .vcsh-unclobber '' {} \; diff --git a/doc/sample_hooks/pre-merge-unclobber b/doc/sample_hooks/pre-merge-unclobber new file mode 100755 index 0000000..5261096 --- /dev/null +++ b/doc/sample_hooks/pre-merge-unclobber @@ -0,0 +1,11 @@ +#!/bin/sh + +# This code does amost exactly what the native vcsh sanity checking code +# does except that on finding a potential merge conflict, it moves existing +# files out of the way temporarily. Merging (part of `vcsh clone`) happens +# cleanly, and a post-merge hook can be used to figure out what to do with +# the now-renamed files. + +for object in $(git ls-tree -r origin/master | awk '{print $4}'); do + [ -e "$object" ] && mv "$object" "$object.vcsh-unclobber" +done diff --git a/doc/vcsh.1.ronn b/doc/vcsh.1.ronn index 56df4ed..1d432c3 100644 --- a/doc/vcsh.1.ronn +++ b/doc/vcsh.1.ronn @@ -5,7 +5,7 @@ vcsh(1) - Version Control System for $HOME - multiple Git repositories in $HOME `vcsh` [] -`vcsh` clone [] +`vcsh` clone [-b ] [] `vcsh` delete @@ -17,9 +17,9 @@ vcsh(1) - Version Control System for $HOME - multiple Git repositories in $HOME `vcsh` list -`vcsh` list-tracked +`vcsh` list-tracked [] -`vcsh` list-tracked-by +`vcsh` list-untracked [<-r>] [] `vcsh` pull @@ -86,6 +86,9 @@ an interactive user. If you need to clone a bundle of repositories, look into the `post-clone-retired` hook. + You can also use a single git repository with several branches. Use the `-b` + option to specify a branch at clone time, the default is `master`. + * commit: Commit in all repositories @@ -107,9 +110,24 @@ an interactive user. * list-tracked: List all files tracked by vcsh. + If you want to list files tracked by a specific repository, simply + append the repository's name last. + * list-tracked-by: List files tracked by a repository. + This is a legacy command; you should use `list-tracked ` instead. + +* list-untracked: + List all files NOT tracked by vcsh. + + By default, the file list is shallow and stops at directory levels where + possible. If you prefer to get a list of all files, append `-r` for + recursive mode. + + If you want to list files not tracked by a specific repository, simply + append the repository's name last. + * pull: Pull from all vcsh remotes. @@ -171,6 +189,15 @@ executed in the context of your shell. Interesting knobs you can turn: +* <$VCSH_GITATTRIBUTES>: + Can be , or any other value. + + will not maintain Git attributes in a special location. + + If set to any other value, repo-specific gitattributes files will be maintained. + + Defaults to . + * <$VCSH_GITIGNORE>: Can be , , or . @@ -245,6 +272,24 @@ Available hooks are , , , If you need more, vcsh is trivial to patch, but please let upstream know so we can ship them by default. +## OVERLAY SYSTEM + +`vcsh` also provides an overlay system. Similar to hooks, the recommended +locations are <$XDG_CONFIG_HOME/vcsh/overlays-available> and +<$XDG_CONFIG_HOME/vcsh/overlays-enabled>. + +Overlays follow the same rules as hooks and you are free to overwrite any +and all functions. Same as hooks, you can use global or repository-specific +overlays by using either <$VCSH_OVERLAY_D/$VCSH_COMMAND> or +<$VCSH_OVERLAY_D/$VCSH_REPO_NAME.$VCSH_COMMAND>. + +Please note that nothing stops you from, e.g. overwriting `status()` in +<$VCSH_OVERLAY_D/commit>. As the overlays will be sourced and you are +replacing arbitrary functions, any and all features may stop working, or you +may even lose data. + +You have been warned. + ## DETAILED HOWTO AND FURTHER READING Manpages are often short and sometimes useless to glean best practices from. diff --git a/tools/list_CONTRIBUTORS b/tools/list_CONTRIBUTORS index 2954f58..1b88451 100755 --- a/tools/list_CONTRIBUTORS +++ b/tools/list_CONTRIBUTORS @@ -1,5 +1,11 @@ #!/bin/sh +# This program is licensed under the GNU GPL version 2 or later. +# (c) Richard "RichiH" Hartmann , 2012-2014 +# For details, see LICENSE. To submit patches, you have to agree to +# license your code under the GNU GPL version 2 or later. + + echo 'Alphabetical list of surnames of everyone who ever committed to this repository. Auto-generated from tools/list_CONTRIBUTORS. ' diff --git a/vcsh b/vcsh index 5d502e7..0f2c5af 100755 --- a/vcsh +++ b/vcsh @@ -1,7 +1,7 @@ #!/bin/sh # This program is licensed under the GNU GPL version 2 or later. -# (c) Richard "RichiH" Hartmann , 2011-2013 +# (c) Richard "RichiH" Hartmann , 2011-2014 # For details, see LICENSE. To submit patches, you have to agree to # license your code under the GNU GPL version 2 or later. @@ -19,11 +19,12 @@ # If '.git-HEAD' is appended to the version, you are seeing an unreleased # version of vcsh; the master branch is supposed to be clean at all times # so you can most likely just use it nonetheless -VERSION='1.20131229.git-HEAD' +VERSION='1.20141026' SELF=$(basename $0) fatal() { echo "$SELF: fatal: $1" >&2 + [ -z $2 ] && exit 1 exit $2 } @@ -31,16 +32,16 @@ fatal() { # options that will modify our behaviour. # Commands are handled at the end of this script. while getopts "c:dv" flag; do - if [ "$1" = '-d' ] || [ "$1" = '--debug' ]; then + if [ x"$1" = x'-d' ] || [ x"$1" = x'--debug' ]; then set -vx VCSH_DEBUG=1 echo "debug mode on" echo "$SELF $VERSION" - elif [ "$1" = '-v' ]; then + elif [ x"$1" = x'-v' ]; then VCSH_VERBOSE=1 echo "verbose mode on" echo "$SELF $VERSION" - elif [ "$1" = '-c' ]; then + elif [ x"$1" = x'-c' ]; then VCSH_OPTION_CONFIG=$OPTARG fi shift 1 @@ -48,7 +49,7 @@ done source_all() { # Source file even if it's in $PWD and does not have any slashes in it - case "$1" in + case $1 in */*) . "$1";; *) . "$PWD/$1";; esac; @@ -57,7 +58,7 @@ source_all() { # Read configuration and set defaults if anything's not set [ -n "$VCSH_DEBUG" ] && set -vx -: ${XDG_CONFIG_HOME:=$HOME/.config} +: ${XDG_CONFIG_HOME:="$HOME/.config"} # Read configuration files if there are any [ -r "/etc/vcsh/config" ] && . "/etc/vcsh/config" @@ -73,10 +74,12 @@ fi [ -n "$VCSH_DEBUG" ] && set -vx # Read defaults -: ${VCSH_REPO_D:=$XDG_CONFIG_HOME/vcsh/repo.d} -: ${VCSH_HOOK_D:=$XDH_CONFIG_HOME/vcsh/hooks-enabled} -: ${VCSH_BASE:=$HOME} +: ${VCSH_REPO_D:="$XDG_CONFIG_HOME/vcsh/repo.d"} +: ${VCSH_HOOK_D:="$XDG_CONFIG_HOME/vcsh/hooks-enabled"} +: ${VCSH_OVERLAY_D:="$XDG_CONFIG_HOME/vcsh/overlays-enabled"} +: ${VCSH_BASE:="$HOME"} : ${VCSH_GITIGNORE:=exact} +: ${VCSH_GITATTRIBUTES:=none} : ${VCSH_WORKTREE:=absolute} if [ ! "x$VCSH_GITIGNORE" = 'xexact' ] && [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && [ ! "x$VCSH_GITIGNORE" = 'xrecursive' ]; then @@ -97,7 +100,8 @@ help() { -v Enable verbose mode commands: - clone \\ + clone [-b ] \\ + \\ [] Clone from an existing repository commit Commit in all repositories delete Delete an existing repository @@ -105,9 +109,10 @@ help() { help Display this help text init Initialize a new repository list List all repositories - list-tracked List all files tracked by vcsh - list-tracked-by \\ - List files tracked by a repository + list-tracked \\ + [] List all files tracked all or one repositories + list-untracked \\ + [<-r>] [] List all files not tracked by all or one repositories pull Pull from all vcsh remotes push Push to vcsh remotes rename \\ @@ -145,22 +150,26 @@ clone() { hook pre-clone init git remote add origin "$GIT_REMOTE" - git config branch.master.remote origin - git config branch.master.merge refs/heads/master - if [ $(git ls-remote origin master 2> /dev/null | wc -l ) -lt 1 ]; then - info "remote is empty, not merging anything" + git checkout -b "$VCSH_BRANCH" || return $? + git config branch."$VCSH_BRANCH".remote origin + git config branch."$VCSH_BRANCH".merge refs/heads/"$VCSH_BRANCH" + if [ $(git ls-remote origin "$VCSH_BRANCH" 2> /dev/null | wc -l ) -lt 1 ]; then + info "remote is empty, not merging anything. + You should add files to your new repository." exit fi - git fetch - for object in $(git ls-tree -r origin/master | awk '{print $4}'); do + git fetch origin "$VCSH_BRANCH" + hook pre-merge + git ls-tree -r --name-only origin/"$VCSH_BRANCH" | (while read object; do [ -e "$object" ] && error "'$object' exists." && VCSH_CONFLICT=1 done - [ "$VCSH_CONFLICT" = '1' ] && + [ x"$VCSH_CONFLICT" = x'1' ]) && fatal "will stop after fetching and not try to merge! - Once this situation has been resolved, run 'vcsh run $VCSH_REPO_NAME git pull' to finish cloning." 17 - git merge origin/master + Once this situation has been resolved, run 'vcsh $VCSH_REPO_NAME pull' to finish cloning." 17 + git merge origin/"$VCSH_BRANCH" + hook post-merge hook post-clone retire hook post-clone-retired @@ -170,9 +179,10 @@ commit() { hook pre-commit for VCSH_REPO_NAME in $(list); do echo "$VCSH_REPO_NAME: " - export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git" + GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR use git commit --untracked-files=no --quiet + VCSH_COMMAND_RETURN_CODE=$? echo done hook post-commit @@ -209,7 +219,7 @@ git_dir_exists() { } hook() { - for hook in $VCSH_HOOK_D/$1* $VCSH_HOOK_D/$VCSH_REPO_NAME.$1*; do + for hook in "$VCSH_HOOK_D/$1"* "$VCSH_HOOK_D/$VCSH_REPO_NAME.$1"*; do [ -x "$hook" ] || continue verbose "executing '$hook'" "$hook" @@ -221,40 +231,98 @@ init() { [ ! -e "$GIT_DIR" ] || fatal "'$GIT_DIR' exists" 10 mkdir -p "$VCSH_BASE" || fatal "could not create '$VCSH_BASE'" 50 cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11 - git init + git init --shared=0600 upgrade hook post-init } list() { for repo in "$VCSH_REPO_D"/*.git; do - [ -d "$repo" ] && [ -r "$repo" ] && echo $(basename "$repo" .git) + [ -d "$repo" ] && [ -r "$repo" ] && echo "$(basename "$repo" .git)" done } get_files() { - export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git" + GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR git ls-files } list_tracked() { - for VCSH_REPO_NAME in $(list); do - get_files - done | sort -u + VCSH_REPO_NAME=$2; export VCSH_REPO_NAME + if [ -n "$VCSH_REPO_NAME" ]; then + get_files | list_tracked_helper + else + for VCSH_REPO_NAME in $(list); do + get_files + done | list_tracked_helper + fi +} + +list_tracked_helper() { + sed "s,^,$(printf '%s\n' "$VCSH_BASE/" | sed 's/[,\&]/\\&/g')," | sort -u } list_tracked_by() { - use - git ls-files | sort -u + list_tracked $2 +} + +list_untracked() { + command -v 'comm' >/dev/null 2>&1 || fatal "Could not find 'comm'" + + temp_file_others=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file' + temp_file_untracked=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file' + temp_file_untracked_copy=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file' + + # Hack in support for `vcsh list-untracked -r`... + directory_opt="--directory" + shift 1 + while getopts "r" flag; do + if [ x"$1" = x'-r' ]; then + unset directory_opt + fi + shift 1 + done + # ...and parse for a potential parameter afterwards. As we shifted things out of $* in during getops, we need to look at $1 + VCSH_REPO_NAME=$1; export VCSH_REPO_NAME + + if [ -n "$VCSH_REPO_NAME" ]; then + list_untracked_helper $VCSH_REPO_NAME + else + for VCSH_REPO_NAME in $(list); do + list_untracked_helper $VCSH_REPO_NAME + done + fi + cat $temp_file_untracked + + unset directory_opt directory_component + rm -f $temp_file_others $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not delete temp files' +} + +list_untracked_helper() { + export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git" + git ls-files --others "$directory_opt" | ( + while read line; do + echo "$line" + directory_component=${line%%/*} + [ -d "$directory_component" ] && printf '%s/\n' "$directory_component" + done + ) | sort -u > $temp_file_others + if [ -z "$ran_once" ]; then + ran_once=1 + cp $temp_file_others $temp_file_untracked || fatal 'Could not copy temp file' + fi + cp $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not copy temp file' + comm -12 --nocheck-order $temp_file_others $temp_file_untracked_copy > $temp_file_untracked } pull() { hook pre-pull for VCSH_REPO_NAME in $(list); do - printf "$VCSH_REPO_NAME: " - export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git" + printf '%s: ' "$VCSH_REPO_NAME" + GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR use git pull + VCSH_COMMAND_RETURN_CODE=$? echo done hook post-pull @@ -263,10 +331,11 @@ pull() { push() { hook pre-push for VCSH_REPO_NAME in $(list); do - printf "$VCSH_REPO_NAME: " - export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git" + printf '%s: ' "$VCSH_REPO_NAME" + GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR use git push + VCSH_COMMAND_RETURN_CODE=$? echo done hook post-push @@ -283,8 +352,8 @@ rename() { # Now that the repository has been renamed, we need to fix up its configuration # Overwrite old name.. - GIT_DIR="$GIT_DIR_NEW" - VCSH_REPO_NAME="$VCSH_REPO_NAME_NEW" + GIT_DIR=$GIT_DIR_NEW + VCSH_REPO_NAME=$VCSH_REPO_NAME_NEW # ..and clobber all old configuration upgrade } @@ -293,25 +362,35 @@ run() { hook pre-run use "$@" + VCSH_COMMAND_RETURN_CODE=$? hook post-run } status() { - if [ ! "x$VCSH_REPO_NAME" = "x" ]; then - export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git" - use - git status --short --untracked-files='no' + if [ -n "$VCSH_REPO_NAME" ]; then + status_helper $VCSH_REPO_NAME else for VCSH_REPO_NAME in $(list); do echo "$VCSH_REPO_NAME:" - export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git" - use - git status --short --untracked-files='no' + status_helper $VCSH_REPO_NAME echo done fi } +status_helper() { + GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR + use + remote_tracking_branch=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2> /dev/null) && { + commits_behind=$(git log ..${remote_tracking_branch} --oneline | wc -l) + commits_ahead=$(git log ${remote_tracking_branch}.. --oneline | wc -l) + [ ${commits_behind} -ne 0 ] && echo "Behind $remote_tracking_branch by $commits_behind commits" + [ ${commits_ahead} -ne 0 ] && echo "Ahead of $remote_tracking_branch by $commits_ahead commits" + } + git status --short --untracked-files='no' + VCSH_COMMAND_RETURN_CODE=$? +} + upgrade() { hook pre-upgrade # fake-bare repositories are not bare, actually. Set this to false @@ -321,26 +400,29 @@ upgrade() { # core.worktree may be absolute or relative to $GIT_DIR, depending on # user preference if [ ! "x$VCSH_WORKTREE" = 'xabsolute' ]; then - git config core.worktree $(cd $GIT_DIR && GIT_WORK_TREE="$VCSH_BASE" git rev-parse --show-cdup) + git config core.worktree "$(cd "$GIT_DIR" && GIT_WORK_TREE=$VCSH_BASE git rev-parse --show-cdup)" elif [ ! "x$VCSH_WORKTREE" = 'xrelative' ]; then git config core.worktree "$VCSH_BASE" fi [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME" - git config vcsh.vcsh 'true' + [ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && git config core.attributesfile ".gitattributes.d/$VCSH_REPO_NAME" + git config vcsh.vcsh 'true' use [ -e "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" + [ -e "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME" ] && git add -f "$VCSH_BASE/.gitattributes.d/$VCSH_REPO_NAME" hook post-upgrade } use() { git_dir_exists - export VCSH_DIRECTORY="$VCSH_REPO_NAME" + VCSH_DIRECTORY=$VCSH_REPO_NAME; export VCSH_DIRECTORY } which() { + [ -e "$VCSH_COMMAND_PARAMETER" ] || fatal "'$VCSH_COMMAND_PARAMETER' does not exist" 1 for VCSH_REPO_NAME in $(list); do for VCSH_FILE in $(get_files); do - echo $VCSH_FILE | grep -q "$VCSH_COMMAND_PARAMETER" && echo "$VCSH_REPO_NAME: $VCSH_FILE" + echo "$VCSH_FILE" | grep -q "$VCSH_COMMAND_PARAMETER" && echo "$VCSH_REPO_NAME: $VCSH_FILE" done done | sort -u } @@ -354,11 +436,13 @@ write_gitignore() { use cd "$VCSH_BASE" || fatal "could not enter '$VCSH_BASE'" 11 + OLDIFS=$IFS + IFS=$(printf '\n\t') gitignores=$(for file in $(git ls-files); do while true; do - echo $file; new="${file%/*}" - [ "$file" = "$new" ] && break - file="$new" + echo "$file"; new=${file%/*} + [ x"$file" = x"$new" ] && break + file=$new done; done | sort -u) @@ -374,6 +458,7 @@ write_gitignore() { { echo "$gitignore/*" | sed 's@^@!/@' >> "$tempfile" || fatal "could not write to '$tempfile'" 57; } fi done + IFS=$OLDIFS if diff -N "$tempfile" "$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME" > /dev/null; then rm -f "$tempfile" || error "could not delete '$tempfile'" exit @@ -387,15 +472,15 @@ write_gitignore() { fatal "could not move '$tempfile' to '$VCSH_BASE/.gitignore.d/$VCSH_REPO_NAME'" 53 } -debug `git version` +debug $(git version) if [ ! "x$VCSH_GITIGNORE" = 'xexact' ] && [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && [ ! "x$VCSH_GITIGNORE" = 'xrecursive' ]; then fatal "'\$VCSH_GITIGNORE' must equal 'exact', 'none', or 'recursive'" 1 fi -export VCSH_COMMAND="$1" +VCSH_COMMAND=$1; export VCSH_COMMAND -case "$VCSH_COMMAND" in +case $VCSH_COMMAND in clon|clo|cl) VCSH_COMMAND=clone;; commi|comm|com|co) VCSH_COMMAND=commit;; delet|dele|del|de) VCSH_COMMAND=delete;; @@ -413,57 +498,75 @@ case "$VCSH_COMMAND" in write|writ|wri|wr) VCSH_COMMAND=write-gitignore;; esac -if [ "$VCSH_COMMAND" = 'clone' ]; then +if [ x"$VCSH_COMMAND" = x'clone' ]; then + VCSH_BRANCH= + if [ "$2" = -b ]; then + VCSH_BRANCH=$3 + shift + shift + fi [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a remote" 1 GIT_REMOTE="$2" - [ -n "$3" ] && VCSH_REPO_NAME="$3" || VCSH_REPO_NAME=$(basename "$GIT_REMOTE" .git) + [ -n "$VCSH_BRANCH" ] || if [ "$3" = -b ]; then + VCSH_BRANCH=$4 + shift + shift + fi + if [ -n "$3" ]; then + VCSH_REPO_NAME=$3 + [ -z "$VCSH_BRANCH" ] && [ "$4" = -b ] && VCSH_BRANCH=$5 + else + VCSH_REPO_NAME=$(basename "${GIT_REMOTE#*:}" .git) + fi [ -z "$VCSH_REPO_NAME" ] && fatal "$VCSH_COMMAND: could not determine repository name" 1 export VCSH_REPO_NAME - export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git" + [ -n "$VCSH_BRANCH" ] || VCSH_BRANCH=master + GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR elif [ "$VCSH_COMMAND" = 'version' ]; then echo "$SELF $VERSION" git version exit -elif [ "$VCSH_COMMAND" = 'which' ]; then +elif [ x"$VCSH_COMMAND" = x'which' ]; then [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify a filename" 1 [ -n "$3" ] && fatal "$VCSH_COMMAND: too many parameters" 1 - export VCSH_COMMAND_PARAMETER="$2" -elif [ "$VCSH_COMMAND" = 'delete' ] || - [ "$VCSH_COMMAND" = 'enter' ] || - [ "$VCSH_COMMAND" = 'init' ] || - [ "$VCSH_COMMAND" = 'list-tracked-by' ] || - [ "$VCSH_COMMAND" = 'rename' ] || - [ "$VCSH_COMMAND" = 'run' ] || - [ "$VCSH_COMMAND" = 'upgrade' ] || - [ "$VCSH_COMMAND" = 'write-gitignore' ]; then - [ -z $2 ] && fatal "$VCSH_COMMAND: please specify repository to work on" 1 - [ "$VCSH_COMMAND" = 'rename' -a -z "$3" ] && fatal "$VCSH_COMMAND: please specify a target name" 1 - [ "$VCSH_COMMAND" = 'run' -a -z "$3" ] && fatal "$VCSH_COMMAND: please specify a command" 1 - export VCSH_REPO_NAME="$2" - export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git" - [ "$VCSH_COMMAND" = 'rename' ] && { export VCSH_REPO_NAME_NEW="$3"; - export GIT_DIR_NEW="$VCSH_REPO_D/$VCSH_REPO_NAME_NEW.git"; } - [ "$VCSH_COMMAND" = 'run' ] && shift 2 -elif [ "$VCSH_COMMAND" = 'commit' ] || - [ "$VCSH_COMMAND" = 'list' ] || - [ "$VCSH_COMMAND" = 'list-tracked' ] || - [ "$VCSH_COMMAND" = 'pull' ] || - [ "$VCSH_COMMAND" = 'push' ]; then + VCSH_COMMAND_PARAMETER=$2; export VCSH_COMMAND_PARAMETER +elif [ x"$VCSH_COMMAND" = x'delete' ] || + [ x"$VCSH_COMMAND" = x'enter' ] || + [ x"$VCSH_COMMAND" = x'init' ] || + [ x"$VCSH_COMMAND" = x'list-tracked-by' ] || + [ x"$VCSH_COMMAND" = x'rename' ] || + [ x"$VCSH_COMMAND" = x'run' ] || + [ x"$VCSH_COMMAND" = x'upgrade' ] || + [ x"$VCSH_COMMAND" = x'write-gitignore' ]; then + [ -z "$2" ] && fatal "$VCSH_COMMAND: please specify repository to work on" 1 + [ x"$VCSH_COMMAND" = x'rename' ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a target name" 1 + [ x"$VCSH_COMMAND" = x'run' ] && [ -z "$3" ] && fatal "$VCSH_COMMAND: please specify a command" 1 + VCSH_REPO_NAME=$2; export VCSH_REPO_NAME + GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR + [ x"$VCSH_COMMAND" = x'rename' ] && { VCSH_REPO_NAME_NEW=$3; export VCSH_REPO_NAME_NEW; + GIT_DIR_NEW=$VCSH_REPO_D/$VCSH_REPO_NAME_NEW.git; export GIT_DIR_NEW; } + [ x"$VCSH_COMMAND" = x'run' ] && shift 2 +elif [ x"$VCSH_COMMAND" = x'commit' ] || + [ x"$VCSH_COMMAND" = x'list' ] || + [ x"$VCSH_COMMAND" = x'list-tracked' ] || + [ x"$VCSH_COMMAND" = x'list-untracked' ] || + [ x"$VCSH_COMMAND" = x'pull' ] || + [ x"$VCSH_COMMAND" = x'push' ]; then : -elif [ "$VCSH_COMMAND" = 'status' ]; then - export VCSH_REPO_NAME="$2" +elif [ x"$VCSH_COMMAND" = x'status' ]; then + VCSH_REPO_NAME=$2; export VCSH_REPO_NAME elif [ -n "$2" ]; then - export VCSH_COMMAND='run' - export VCSH_REPO_NAME="$1" - export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git" - [ -d $GIT_DIR ] || { help; exit 1; } + VCSH_COMMAND='run'; export VCSH_COMMAND + VCSH_REPO_NAME=$1; export VCSH_REPO_NAME + GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR + [ -d "$GIT_DIR" ] || { help; exit 1; } shift 1 set -- "git" "$@" elif [ -n "$VCSH_COMMAND" ]; then - export VCSH_COMMAND='enter' - export VCSH_REPO_NAME="$1" - export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git" - [ -d $GIT_DIR ] || { help; exit 1; } + VCSH_COMMAND='enter'; export VCSH_COMMAND + VCSH_REPO_NAME=$1; export VCSH_REPO_NAME + GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR + [ -d "$GIT_DIR" ] || { help; exit 1; } else # $1 is empty, or 'help' help && exit @@ -471,9 +574,9 @@ fi # Did we receive a directory instead of a name? # Mangle the input to fit normal operation. -if echo $VCSH_REPO_NAME | grep -q '/'; then - export GIT_DIR=$VCSH_REPO_NAME - export VCSH_REPO_NAME=$(basename "$VCSH_REPO_NAME" .git) +if echo "$VCSH_REPO_NAME" | grep -q '/'; then + GIT_DIR=$VCSH_REPO_NAME; export GIT_DIR + VCSH_REPO_NAME=$(basename "$VCSH_REPO_NAME" .git); export VCSH_REPO_NAME fi check_dir() { @@ -490,10 +593,23 @@ check_dir() { check_dir "$VCSH_REPO_D" [ ! "x$VCSH_GITIGNORE" = 'xnone' ] && check_dir "$VCSH_BASE/.gitignore.d" +[ ! "x$VCSH_GITATTRIBUTES" = 'xnone' ] && check_dir "$VCSH_BASE/.gitattributes.d" verbose "$VCSH_COMMAND begin" -export VCSH_COMMAND=$(echo $VCSH_COMMAND | sed 's/-/_/g') +VCSH_COMMAND=$(echo "$VCSH_COMMAND" | sed 's/-/_/g'); export VCSH_COMMAND + +# Source repo-specific configuration file +[ -r "$XDG_CONFIG_HOME/vcsh/config.d/$VCSH_REPO_NAME" ] && . "$XDG_CONFIG_HOME/vcsh/config.d/$VCSH_REPO_NAME" + +# source overlay functions +for overlay in "$VCSH_OVERLAY_D/$VCSH_COMMAND"* "$VCSH_OVERLAY_D/$VCSH_REPO_NAME.$VCSH_COMMAND"*; do + [ -r "$overlay" ] || continue + info "sourcing '$overlay'" + . "$overlay" +done + hook pre-command $VCSH_COMMAND "$@" hook post-command verbose "$VCSH_COMMAND end, exiting" +exit $VCSH_COMMAND_RETURN_CODE