git-publish

Publish local directories to remote servers with ease using git + ssh.

= git-publish documentation
:link-github: https://github.com/Rex--/git-publish
:link-docs: https://rex.mckinnon.ninja/git-publish
:icons: font
:toc: left

Publish to remote directories using `git` + `ssh`.

== Quickstart

. **Init**ialize a _publish repository_. This creates a bare repository on `host`
at `remote_path` and clones it to `local_path` (default `./<remote_name>`). It
also creates an initial commit with the message 'Init Version 0'.

 $ git publish init <host>:<remote_path> [local_path]


. Add a _publish **worktree**_. This creates a directory on the publish
server (default `origin`) that will stay in sync with a branch (default `main`
or `init.defaultBranch` if set).

 $ git publish worktree <remote_path> [local_path]


. Edit files, and *_publish_* to a remote repository (default `origin`). An optional
commit message can be used instead of the default. A custom commit message has
to be set only once, as if no message is given the last commit message is used
with an incremented version number.

 $ touch index.html
 $ git publish [-m "Custom Message"]



== Setup
Currently, the required software has to be installed manually. +
A install script is provided to install `git-publish` and it's documentation.

=== Requirements
Only a few common programs are required to use `git-publish`. They can
typically be installed with your distro's package manager.

`git`::
git is required for, well, the `git` command. It's required to be installed on
both your local machine and the remote server.

`ssh`::
ssh is required for running commands on the remote server and pulling from git
repositories. It will also need to be installed on both the local machine and
the remote server. The remote server should be running an ssh server that is
accessible from the local machine.

`bash`::
Currently, the `git-publish` script uses a few "bashism"s, it is not guaranteed
to run in other shells. Because of this, `bash` is listed as a requirement,
however it may work in others.

=== Installing git-publish
`git-publish` is available on {link-github}[github]. Clone the repo
to get started, then chose from an automatic install script or manual
installation of the software.

==== Installation script
An installation script is provided that copies the needed files to the
appropriate directories. You can run this script with make using `make install`
or run the script independently with `./install.sh`.

The script will prompt for you to choose a directory on your $PATH to copy the
`git-publish` script to. It will the copy the `git-publish.1` man page to the
first path returned from the `manpath` command. You will need write access to
both directories!

.Running the install.sh script
 $ sudo make install
 [sudo] password for user:
 Select an install directory on your $PATH:
  (1) /usr/local/bin [default]
  (2) /usr/bin
  (3) /bin
  (4) /usr/local/sbin
  (5) /usr/lib/jvm/default/bin
  (6) /opt/microchip/xc8/v2.40/bin
  (7) /home/user/.local/bin/
 Input a number (enter for default): 
 Installing 'git-publish' to '/usr/local/bin' -- success
 Installing 'docs/git-publish.1' to '/usr/local/man' -- success
 Successfully installed git-publish.


==== Manual Installation
To install the `git publish` command manually, simply place the script
somewhere it is available on your $PATH and mark it executable.

 $ cp git-publish ~/.local/bin
 $ chmod a+x ~/.local/bin/git-publish

Alternatively to stay up-to-date with the latest release, link directly to the
git repository. It's recommended to checkout a tag or a commit in this case.

.The following has not been tested
 $ git checkout --detach main
 $ ln -s git-publish ~/.local/bin
 $ chmod a+x git-publish


=== Additional Considerations

==== &#46;git
Because we create a linked worktree to an existing repo, `.git` is a file
that contains a path to the common repo. This might expose sensitive
information e.g. if serving the directory with a web server. An example `.git`
created with the publish command contains the following:

 gitdir: /git/website.git/worktrees/example


==== Linux File Permissions
The ssh user that you use to connect with git needs to have write permissions
to both the bare repository and the publish directory. An example setup is
a `publish` group that has *rwx* permissions to each parent directory.
The example below will give the `git` user appropriate permissions to push to a
repository at `<server>:/srv/git/example.git` and publish files in
`/srv/publish/example`. Additional users can be granted permissions by adding
them to the `publish` group.

 # mkdir /srv/git /srv/publish
 # groupadd publish
 # chgrp publish /srv/git /srv/publish
 # chmod g+w /srv/git /srv/publish
 # usermod -aG publish git

Alternatively, you could just work in the user's home directory:

 $ mkdir ~/git ~/publish

NOTE: `git publish` requires absolute paths for all arguments that take a path.


==== SSH Host
The publish server is required to be accessible over ssh. This means we have to
authenticate with it somehow, however, most of the time it is recommend to
disable password logins for security. The easiest way to authenticate using a
key pair is by installing the needed key to your `ssh-agent`. This also means
you won't have to type in any passwords to login to the publish server.

Sometimes, you may not have an ssh-agent running or maybe you are running the
server on a non-standard port. These situations require you to configure the
ssh connection. The current recommended way to do this is by editing
`~/.ssh/config` and configuring a custom host for your ssh server. An
example of one such host is given below:

 Host <custom-host>
     Hostname       <ip>
     Port           <port>
     User           <user>
     IdentityFile   <path/to/private_key>

All configuration options are optional, just use what you need to change from
the defaults. You can now use `<custom-host>` as a hostname like:

 $ git publish init <custom-host>:/path/to/repo.git


== Usage
The basic workflow this suits is to keep a remote directory in sync with
one on your local machine. It achieves this by pushing a local repository to a
remote repository over ssh. This means an intermediate git host such as Github
is not required. A git server hook that fires after a successful push is used
to trigger a force checkout of the main branch for every published worktree
on the server. These worktree directories contain a copy of the latest
committed files.

=== Initialize Publish Repository
When you initialize a repository with the `git publish init` command, it does
three main things:

1. Creates a bare repository on the given host and installs a post-receive hook.
2. Initializes a local repository and adds the remote as origin.
3. Creates an empty commit and pushes it to the configured branch.

Let's initialize a publish repository on `example.com`. We'll call it `website`.

.Initializing a publish repository
====
 $ git publish init example.com:/git/website.git
 Connecting to server: example.com
 Repo: /git/website.git
 [email protected]'s password: 
 Initialized empty Git repository in /git/website.git/
 Find this repo at: example.com:/git/website.git
 Cloning into local directory: website/
 Initialized empty Git repository in /home/user/website/.git/
 [main (root-commit) aa3d154] Init Version 0
 [email protected]'s password: 
 Enumerating objects: 2, done.
 Counting objects: 100% (2/2), done.
 Writing objects: 100% (2/2), 170 bytes | 170.00 KiB/s, done.
 Total 2 (delta 0), reused 0 (delta 0), pack-reused 0
 To example.com:/git/website.git
  * [new branch]      main -> main
====

This created two repositories: `/git/website.git` on our server and
`website/.git` on our local machine.

Inside of our local repo, if we run `git remote get-url origin` (or whatever
you set the remote to be) we can see it is set up to push/pull from
`example.com:/git/website.git`. The git-publish command just runs a `git init`
and then `git remote add` to setup the local repository. It then creates an
empty commit and pushes it to the remote with the message 'Init Version 0'.
This empty commit can be viewed as the 'creation' commit.

In our server repo, the only interesting thing you will find is a shell script
`post-receive` that gets installed in `<repo>/hooks/` e.g.
`example.git/hooks/post-receive` in the case of the example above. This script,
which runs after a successful push, simply finds all *.publish files in the
hooks/ directory and executes them. Other than this simple one-line script, the
bare repository is bog standard.

=== Create Publish Worktree
The worktree(s) that hold the actual published files are created with the
`git publish worktree` command.

Let's create a publish worktree on `example.com` at `/var/www/website/`. Note
how we don't have to specify a host like `example.com`, it is extracted from
the configured remote (default `origin`).

.Create a publish worktree
====
 $ git publish worktree /var/www/example
 Using remote 'origin' @ example.com:/git/website.git
 Creating publish worktree: /var/www/example
 [email protected]'s password: 
 Preparing worktree (detached HEAD cf7f00d)
 HEAD is now at cf7f00d Init Version 0
 Successfully created worktree: /var/www/example
====

This created a new linked worktree at `/var/www/example` and checked out the
latest commit in the configured branch. If you haven't yet published any files,
the directory will be empty except for the `.git` _file_. Refer to the 
<<_git,.git section>> in <<Additional Considerations>> for info about this file.

This command also places a `.publish` script into the common repository's
`hook/` directory. This script contains the worktree path and the branch to
stay up-to-date with. The file is named after the worktree path, so the
example worktree's script would be `var-www-example.publish`. This script gets
run on every successful push and checks out the latest commit in the configured
branch. There is currently no way to change the branch a worktree tracks.

=== Publish Files to Worktrees
Files are published to remote worktrees with the `git publish` command. Most of
the time, no arguments are required.

Let's publish an `index.html` to our `/var/www/example/`.

.Publish a file
====
 $ touch index.html
 $ git publish index.html
 Publishing files: index.html
 Using default commit message: Publish Version
 Using default version: 1
 [main 3e9a0d8] Publish Version 1
  1 file changed, 0 insertions(+), 0 deletions(-)
  create mode 100644 index.html
 [email protected]'s password: 
 Enumerating objects: 3, done.
 Counting objects: 100% (3/3), done.
 Delta compression using up to 4 threads
 Compressing objects: 100% (2/2), done.
 Writing objects: 100% (2/2), 259 bytes | 259.00 KiB/s, done.
 Total 2 (delta 0), reused 0 (delta 0), pack-reused 0
 remote: Updating worktree: /var/www/example
 remote: HEAD is now at 3e9a0d8 Publish Version 1
 To example.com:/git/website.git
    cf7f00d..3e9a0d8  main -> main
 Published Version: 1
====

This published the specified files, and then checked out the commit in the
remote worktree. Because this was our first commit (the previous commit's
version was 0) and no message was specified with `-m`, the commit message
defaults to 'Publish Version' plus the version appended to the end. Note how
this changes in the example below.

Now, let's add some error pages.

.Publish a directory
====
 $ touch 404.html 403.html
 $ git publish
 Publishing directory: /home/user/website
 Using last commit message: Publish Version 1
 Incrementing commit version: 1 + 1
 [main 3e9a0d8] Publish Version 2
  2 files changed, 0 insertions(+), 0 deletions(-)
  create mode 100644 404.html
  create mode 100644 403.html
 [email protected]'s password: 
 Enumerating objects: 3, done.
 Counting objects: 100% (3/3), done.
 Delta compression using up to 4 threads
 Compressing objects: 100% (2/2), done.
 Writing objects: 100% (2/2), 259 bytes | 259.00 KiB/s, done.
 Total 2 (delta 0), reused 0 (delta 0), pack-reused 0
 remote: Updating worktree: /var/www/example
 remote: HEAD is now at 3e9a0d8 Publish Version 2
 To example.com:/git/website.git
    cf7f00d..3e9a0d8  main -> main
 Published Version: 2
====

Since we didn't specify any files to publish, it publishes every file in the
directory. No commit message was specified, so it defaults to the last commit
message with an incremented version number.


=== Existing Repositories
The `publish` command works on any existing git repository.
The `publish worktree` command works on repositories that are configured to use
an ssh remote and you have general SSH access to the server hosting the
repository. Note this means it will not work on hosted git solutions e.g.
GitHub.

When the repository already exists on the host, you should use the `publish hook`
command to update worktrees automatically. This command installs a post-receive
hooks that gets run after every commit. After the hook has been installed, you
can commit and push as normal and any worktrees that have been created will be
updated.

.Install hook
====
 $ git publish hook example.com:/git/website.git
 Connecting to server: example.com
 Repo: /git/website.git
 Installed post-receive hook: /git/website.git/hooks/post-receive
====

// == git-publish(1)
include::git-publish-man.adoc[leveloffset=+1]



---
[.text-center]
[.big]#{link-github}[github] | {link-docs}[documentation]# +
&copy; 2022-2024 Rex McKinnon