mirrorcat

package module
v0.1.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 10, 2018 License: MIT Imports: 11 Imported by: 1

README

GoDoc

MirrorCat

Tired of manually keeping branches up-to-date with one another across repositories? Are Git Hooks not enough for you for some reason? Deploy your instance of MirrorCat to expose a web service which will push your commits around to where they are needed.

Acquire

Docker

You can run MirrorCat in a container, however/where ever you want, using Docker images. You can find the most up-to-date images here: https://cloud.docker.com/swarm/marstr/repository/docker/marstr/mirrorcat/general

Built from Source

Note: To do this, you'll need the most recent version of the Go Programming Language installed on your machine. It also helps to have dep installed.

The simplest, albeit least stable, way to install mirrorcat is to use go get as seen below:

go get -u github.com/Azure/mirrorcat/mirrorcat

A somewhat more stable way to install is to separately fetch the source and build it yourself, as below:

go get -d github.com/Azure/mirrorcat
cd $GOPATH/src/github.com/Azure/mirrorcat
dep ensure
go install -ldflags "-X github.com/Azure/mirrorcat/mirrorcat/cmd.commit=$(git rev-parse HEAD)" ./mirrorcat

or if you're a PowerShell-type-person:

go get -d github.com/Azure/mirrorcat
cd $env:GOPATH\src\github.com\Azure\mirrorcat
dep ensure
go install -ldflags "-X github.com/Azure/mirrorcat/mirrorcat/cmd.commit=$(git rev-parse HEAD)" .\mirrorcat

Configure

You'll need to tell MirrorCat which branches to mirror, and where. There are a couple of ways of doing that. The best part? You don't even need to choose between them. A MirrorCat looks around when it starts up to see what is available to it.

Available Options
Flag Config File Key Environment Variable Default Usage
--config N/A MIRRORCAT_CONFIG ~/.mirrorcat.yml The configuration file that should be used for all of the following settings.
--port port MIRRORCAT_PORT 8080 The TCP port that should be used to serve this instance of MirrorCat
--redis-connection redis-connection MIRRORCAT_REDIS_CONNECTION None The connection string MirrorCat to use while looking for branch mappings in a Redis cache.
--clone-depth clone-depth MIRRORCAT_CLONE_DEPTH Infinity The number of commits that should be cloned while moving commits between repositories.
N/A mirrors N/A None A mapping of which branches are to be copied from one repository to another.
Using a Config File

In addition to specifying administrative stuff, you can provide lists of where to copy each branch using either JSON or YAML. MirrorCat watches the file that was used as start-up, if the file changes MirrorCat will attempt to reconfigure itself while running.

.mirrorcat.yml
port: 8080
redis-connection: redis://localhost:6379/0
mirrors:
  https://github.com/Azure/mirrorcat.git:
    master:
      https://github.com/marstr/mirrorcat.git:
      - master
  https://github.com/Azure/azure-sdk-for-go.git:
    master:
      https://github.com/Azure/azure-sdk-for-go.git:
      - dev
.mirrorcat.json
{
  "port": 8080,
  "redis-connection": "redis://localhost:6379/0",
  "mirrors": {
    "https://github.com/Azure/mirrorcat.git": {
      "master": {
        "https://github.com/marstr/mirrorcat.git": [
          "master"
        ]
      }
    },
    "https://github.com/Azure/azure-sdk-for-go.git": {
      "master": {
        "https://github.com/Azure/azure-sdk-for-go.git": [
          "dev"
        ]
      }
    }
  }
}
Using Redis

Sometimes, you may want to introduce some dynamicism into how MirrorCat behaves. For example, you may want to have a website where users can declare a branch they've been working on in a lieutenant repository ready for the big time. Redis is a great way to enable this. Just point MirrorCat at a Redis instance by passing it a Redis connection string.

The expected schema of the Redis instance is to have Sets of mappings between branch/repository pairs separated by the rune ':'.

For example, a Redis instance where the following commands had been run would result in MirrorCat behaving the same as a MirrorCat using the config files from above.

SADD master:https://github.com/Azure/mirrorcat.git master:https://github.com/marstr/mirrorcat.git
SADD master:https://github.com/Azure/azure-sdk-for-go.git dev:https://github.com/Azure/azure-sdk-for-go.git
Using Environment Variables

Any flag that you can provide the mirrorcat start command can be provided in the config file mentioned above. However, you can also speficy it by setting an environment variable prefixed with the name "mirrorcat" and all hyphensreplaced with underscores ('-' -> '_').

Priority

MirrorCat, being a project written in Go, uses the really awesome libraries github.com/spf13/cobra and github.com/spf13/viper to easily deal with flags and configuration. As such, MirrorCat uses the following priority list when looking at the configuration handed to it:

  1. Command Line Arguments
  2. Environment Variables
  3. Configuration File
  4. Default Values

Contribute

Conduct

If you would like to become an active contributor to this project please follow the instructions provided in Microsoft Azure Projects Contribution Guidelines.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.

Requirements

You'll need the following tools to build and test MirrorCat:

The easiest way to get a hold of MirrorCat's source is using go get:

go get -d -t github.com/Azure/mirrorcat
Running Tests

Once you've acquired the source, you can run MirrorCat's tests with the following command:

go test -race -cover -v github.com/Azure/mirrorcat/...

Attribution

The gopher image at the top of this README was created by Ashley McNamara. You can find this image, and many others like it, at https://github.com/ashleymcnamara/gophers

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func NormalizeRef

func NormalizeRef(ref string) string

NormalizeRef removes metadata about the reference that was passed to us, and returns just it's name. Chiefly, this removes data about which repository the references belongs to, remote or local.

Example
package main

import (
	"fmt"

	"github.com/Azure/mirrorcat"
)

func main() {
	fmt.Println(mirrorcat.NormalizeRef("myBranch"))
	fmt.Println(mirrorcat.NormalizeRef("remotes/origin/myBranch"))
	fmt.Println(mirrorcat.NormalizeRef("refs/heads/myBranch"))
}
Output:

myBranch
myBranch
myBranch

func Push

func Push(ctx context.Context, original, mirror RemoteRef, depth int) (err error)

Push clones the original repository, then pushes the branch specified to another repository.

Types

type CmdErr

type CmdErr struct {
	Output []byte
	// contains filtered or unexported fields
}

CmdErr allows the Output of a completed CMD to be included with the error itself. This is useful for capturing Failure messages communicated through /dev/stderr

func (CmdErr) Error

func (ce CmdErr) Error() string

type Commit

type Commit struct {
	ID      string   `json:"id"`
	Message string   `json:"message"`
	Author  Identity `json:"author"`
	URL     string   `json:"url"`
}

Commit is an item detailed in the PushEvent page linked above, which contains metadata about commits that were pushed and that we're being informed of by a webhook.

type DefaultMirrorFinder

type DefaultMirrorFinder struct {
	sync.RWMutex
	// contains filtered or unexported fields
}

DefaultMirrorFinder provides an in-memory location for storing information about which branches should mirror which others.

Note: This is most powerful if combined with github.com/spf13/viper and its Watch

functionality
Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/Azure/mirrorcat"
)

func main() {
	original := mirrorcat.RemoteRef{
		Repository: "https://github.com/Azure/mirrorcat",
		Ref:        "master",
	}

	mirrors := []mirrorcat.RemoteRef{
		{
			Repository: "https://github.com/marstr/mirrorcat",
			Ref:        "master",
		},
		{
			Repository: "https://github.com/haydenmc/mirrorcat",
			Ref:        "master",
		},
	}

	subject := mirrorcat.NewDefaultMirrorFinder()
	subject.AddMirrors(original, mirrors...)

	results := make(chan mirrorcat.RemoteRef)

	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	go subject.FindMirrors(ctx, original, results)

loop:
	for {
		select {
		case entry, ok := <-results:
			if ok {
				fmt.Println(entry.Repository, entry.Ref)
			} else {
				break loop
			}
		case <-ctx.Done():
			break loop
		}
	}

}
Output:

https://github.com/marstr/mirrorcat master
https://github.com/haydenmc/mirrorcat master

func NewDefaultMirrorFinder

func NewDefaultMirrorFinder() *DefaultMirrorFinder

NewDefaultMirrorFinder creates an empty instance of a MirrorFinder

func (*DefaultMirrorFinder) AddMirrors

func (dmf *DefaultMirrorFinder) AddMirrors(original RemoteRef, branches ...RemoteRef)

AddMirrors registers a new remote and branch that should be cloned whenever

func (*DefaultMirrorFinder) ClearAll

func (dmf *DefaultMirrorFinder) ClearAll()

ClearAll removes all associations between References

func (*DefaultMirrorFinder) ClearMirrors

func (dmf *DefaultMirrorFinder) ClearMirrors(original RemoteRef)

ClearMirrors removes the association between a particular `RemoteRef` and all mirrored copies.

func (*DefaultMirrorFinder) FindMirrors

func (dmf *DefaultMirrorFinder) FindMirrors(ctx context.Context, original RemoteRef, results chan<- RemoteRef) error

FindMirrors iterates through the entries that had been added and publishes them all to `results` See Also: AddMirror(url.URL, branches ...string)

type Identity

type Identity struct {
	Name     string `json:"name"`
	Email    string `json:"email"`
	Username string `json:"username"`
}

Identity holds metadata about a GitHub Author

type MergeFinder

type MergeFinder []MirrorFinder

MergeFinder allows a mechanism to find mirror mappings from multiple underlying MirrorFinders.

func (MergeFinder) FindMirrors

func (haystack MergeFinder) FindMirrors(ctx context.Context, needle RemoteRef, results chan<- RemoteRef) (err error)

FindMirrors enumerates each MirrorFinder, and

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/Azure/mirrorcat"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	const mainRepo = "github.com/Azure/mirrorcat"
	const secondaryRepo = "github.com/marstr/mirrorcat"

	child1, child2 := mirrorcat.NewDefaultMirrorFinder(), mirrorcat.NewDefaultMirrorFinder()

	orig := mirrorcat.RemoteRef{Repository: mainRepo, Ref: "master"}

	child1.AddMirrors(orig, mirrorcat.RemoteRef{Repository: secondaryRepo, Ref: "master"})
	child2.AddMirrors(orig, mirrorcat.RemoteRef{Repository: mainRepo, Ref: "dev"})

	subject := mirrorcat.MergeFinder([]mirrorcat.MirrorFinder{child1, child2})

	results, errs := make(chan mirrorcat.RemoteRef), make(chan error, 1)

	go func() {
		select {
		case errs <- subject.FindMirrors(ctx, orig, results):
		case <-ctx.Done():
			errs <- ctx.Err()
		}
	}()

loop:
	for {
		select {
		case result, ok := <-results:
			if !ok {
				break loop
			}
			fmt.Printf("%s:%s\n", result.Repository, result.Ref)
		case <-ctx.Done():
			return
		}
	}
	fmt.Println(<-errs)

}
Output:

github.com/marstr/mirrorcat:master
github.com/Azure/mirrorcat:dev
<nil>

type MirrorFinder

type MirrorFinder interface {
	FindMirrors(context.Context, RemoteRef, chan<- RemoteRef) error
}

MirrorFinder provides an abstraction for communication which branches on which repositories are mirrors of others.

type PushEvent

type PushEvent struct {
	Ref          string     `json:"ref"`
	Before       string     `json:"before"`
	Size         int        `json:"size"`
	DistinctSize int        `json:"distinct_size"`
	Commits      []Commit   `json:"commits"`
	Head         Commit     `json:"head_commit"`
	Repository   Repository `json:"repository"`
	Pusher       Identity   `json:"pusher"`
}

PushEvent encapsulates all data that will be provided by a GitHub Webhook PushEvent. Read more at: https://developer.github.com/v3/activity/events/types/#pushevent

type RedisFinder

type RedisFinder redis.Client

RedisFinder implementes the MirrorFinder interface against a Redis Cache.

func (RedisFinder) FindMirrors

func (rf RedisFinder) FindMirrors(ctx context.Context, original RemoteRef, results chan<- RemoteRef) error

FindMirrors scrapes a Redis Cache, looking for any mirror entries.

It is expected that the Redis Cache will contain a key which is the result of `mirrorcat.RedisRemoteRef(original).String()`. At that key, MirrorCat expects to find a Set of strings matching the format of the key, but targeting other repositories and refs.

type RedisRemoteRef

type RedisRemoteRef RemoteRef

RedisRemoteRef allows easy conversion to `string` from a `mirrorcat.RemoteRef`.

func ParseRedisRemoteRef

func ParseRedisRemoteRef(input string) (RedisRemoteRef, error)

ParseRedisRemoteRef reads a string formatted as a RedisRemoteRef and reads it into a `mirrorcat.RemoteRef`.

Example
package main

import (
	"fmt"

	"github.com/Azure/mirrorcat"
)

func main() {
	unmarshaled, err := mirrorcat.ParseRedisRemoteRef("master:https://github.com/Azure/mirrorcat")
	if err != nil {
		return
	}

	fmt.Println("Repository:", unmarshaled.Repository)
	fmt.Println("Ref:", unmarshaled.Ref)

}
Output:

Repository: https://github.com/Azure/mirrorcat
Ref: master

func (RedisRemoteRef) String

func (rrr RedisRemoteRef) String() string
Example
package main

import (
	"fmt"

	"github.com/Azure/mirrorcat"
)

func main() {
	subject := mirrorcat.RemoteRef{
		Repository: "https://github.com/Azure/mirrorcat",
		Ref:        "master",
	}

	marshaled := mirrorcat.RedisRemoteRef(subject).String()
	fmt.Println(marshaled)

}
Output:

master:https://github.com/Azure/mirrorcat

type RemoteRef

type RemoteRef struct {
	Repository string `json:"repo"`
	Ref        string `json:"ref"`
}

RemoteRef combines a location with a branch name.

type Repository

type Repository struct {
	ID       int64  `json:"id"`
	Name     string `json:"name"`
	URL      string `json:"url"`
	SSHURL   string `json:"ssh_url"`
	GITURL   string `json:"git_url"`
	CloneURL string `json:"clone_url"`
}

Repository holds metadat about a GitHub URL.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL