github.com/ivotron/vio@v0.1.1-0.20160328072646-778e014d4dee/posix_backend.go (about)

     1  package vio
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/exec"
     9  	"strings"
    10  
    11  	"gopkg.in/ini.v1"
    12  
    13  	"github.com/tgulacsi/go-locking"
    14  )
    15  
    16  type PosixBackend struct {
    17  	snapshotsPath string
    18  	repoPath      string
    19  }
    20  
    21  func NewPosixBackend(o *ini.File) (b Backend, err error) {
    22  	if !o.Section("").HasKey("snapshots_path") {
    23  		return nil, AnError{"Expecting key 'snapshots_path' in configuration."}
    24  	}
    25  	if !o.Section("").HasKey("repo_path") {
    26  		return nil, AnError{"Expecting key 'repo_path' in configuration."}
    27  	}
    28  	return &PosixBackend{
    29  		snapshotsPath: o.Section("").Key("snapshots_path").String(),
    30  		repoPath:      o.Section("").Key("repo_path").String()}, nil
    31  }
    32  
    33  func (b PosixBackend) Init() (err error) {
    34  	if err = os.Mkdir(b.snapshotsPath, 0755); err != nil {
    35  		return
    36  	}
    37  
    38  	if _, err = os.Stat(b.snapshotsPath + "/index"); err == nil {
    39  		return AnError{"Repository already initialized"}
    40  	}
    41  
    42  	if err = ioutil.WriteFile(b.snapshotsPath+"/index", []byte(""), 0644); err != nil {
    43  		return
    44  	}
    45  
    46  	return
    47  }
    48  
    49  func (b PosixBackend) isRepoOK() (err error) {
    50  	if !b.IsInitialized() {
    51  		return AnError{"Uninitialized repository."}
    52  	}
    53  
    54  	hasUncommitted, err := HasUncommittedChanges(b.repoPath)
    55  
    56  	if err != nil {
    57  		return
    58  	}
    59  	if hasUncommitted {
    60  		return AnError{"Uncommitted changes in repo."}
    61  	}
    62  
    63  	return
    64  }
    65  
    66  func (b PosixBackend) Open() error {
    67  	return nil
    68  }
    69  
    70  func (b PosixBackend) IsInitialized() bool {
    71  	_, err := os.Stat(b.snapshotsPath + "/index")
    72  	return err == nil
    73  }
    74  
    75  func (b PosixBackend) GetStatus() (Status, error) {
    76  	return Committed, nil
    77  }
    78  
    79  func (b PosixBackend) Checkout(v *version) (err error) {
    80  	if err = b.isRepoOK(); err != nil {
    81  		return
    82  	}
    83  
    84  	// acquire a lock on the index file
    85  	flock, err := locking.NewFLock(b.snapshotsPath + "/index")
    86  	if err != nil {
    87  		return
    88  	}
    89  	if err = flock.Lock(); err != nil {
    90  		return
    91  	}
    92  	defer flock.Unlock()
    93  
    94  	idx, err := b.GetVersions()
    95  	if err != nil {
    96  		return
    97  	}
    98  
    99  	if !ContainsVersion(idx, v) {
   100  		return AnError{
   101  			fmt.Sprintf("Version %s#%d not in index", v.revision, v.timestamp.Unix())}
   102  	}
   103  
   104  	return checkoutSnapshot(b.repoPath, b.snapshotsPath, v)
   105  }
   106  
   107  func (b PosixBackend) Commit(meta map[string]string) (v *version, err error) {
   108  	if err = b.isRepoOK(); err != nil {
   109  		return
   110  	}
   111  	versionedFiles, err := GetVersionedFiles(b.repoPath)
   112  	if err != nil {
   113  		return
   114  	}
   115  
   116  	id, err := GetCurrentCommitId(b.repoPath)
   117  	if err != nil {
   118  		return
   119  	}
   120  
   121  	v = NewVersionWithMeta(id, meta)
   122  
   123  	// acquire a lock on the index file
   124  	flock, err := locking.NewFLock(b.snapshotsPath + "/index")
   125  	if err != nil {
   126  		return
   127  	}
   128  	if err = flock.Lock(); err != nil {
   129  		return
   130  	}
   131  	defer flock.Unlock()
   132  
   133  	idx, err := b.GetVersions()
   134  	if err != nil {
   135  		return
   136  	}
   137  	if ContainsVersion(idx, v) {
   138  		return nil, AnError{"Version " + fmt.Sprintf("%v", v) + " already in index."}
   139  	}
   140  
   141  	if err = createSnapshot(b.repoPath, b.snapshotsPath, v, versionedFiles); err != nil {
   142  		return
   143  	}
   144  
   145  	if err = addVersionToIndex(v, b.snapshotsPath+"/index"); err != nil {
   146  		return
   147  	}
   148  
   149  	return
   150  }
   151  
   152  func checkoutSnapshot(repoPath string, snapsPath string, v *version) (err error) {
   153  
   154  	if _, err = os.Stat(snapsPath + "/" + v.revision); err != nil {
   155  		return
   156  	}
   157  
   158  	unixTime := fmt.Sprintf("%d", v.timestamp.Unix())
   159  	if _, err = os.Stat(snapsPath + "/" + v.revision + "/" + unixTime); err != nil {
   160  		return
   161  	}
   162  
   163  	srcPath := snapsPath + "/" + v.revision + "/" + unixTime + "/"
   164  
   165  	var args []string
   166  	args = append(args, "-am")
   167  
   168  	// source
   169  	args = append(args, srcPath)
   170  
   171  	// destination
   172  	args = append(args, repoPath)
   173  
   174  	_, err = exec.Command("rsync", args...).CombinedOutput()
   175  
   176  	return
   177  }
   178  
   179  func createSnapshot(repoPath string,
   180  	snapsPath string, v *version, versionedFiles []string) (err error) {
   181  
   182  	if err = os.MkdirAll(snapsPath+"/"+v.revision, 0755); err != nil {
   183  		return
   184  	}
   185  
   186  	unixTime := fmt.Sprintf("%d", v.timestamp.Unix())
   187  	if err = os.Mkdir(snapsPath+"/"+v.revision+"/"+unixTime, 0755); err != nil {
   188  		return
   189  	}
   190  	destPath := snapsPath + "/" + v.revision + "/" + unixTime
   191  
   192  	var args []string
   193  	args = append(args, "-a")
   194  	for _, vfile := range versionedFiles {
   195  		args = append(args, "--exclude="+vfile)
   196  	}
   197  
   198  	args = append(args, "--exclude=.git/")
   199  
   200  	if _, err := os.Stat(repoPath + "/.vioignore"); err == nil {
   201  		args = append(args, "--filter=:-_/.vioignore")
   202  	}
   203  
   204  	// source
   205  	args = append(args, repoPath+"/")
   206  
   207  	// destination
   208  	args = append(args, destPath)
   209  
   210  	_, err = exec.Command("rsync", args...).CombinedOutput()
   211  
   212  	return
   213  }
   214  
   215  func addVersionToIndex(v *version, filename string) (err error) {
   216  	f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600)
   217  	if err != nil {
   218  		return
   219  	}
   220  
   221  	defer f.Close()
   222  
   223  	_, err = f.WriteString(fmt.Sprintf("%v\n", v))
   224  
   225  	return
   226  }
   227  
   228  func (b PosixBackend) GetVersions() (versions []version, err error) {
   229  	contents, err := ioutil.ReadFile(b.snapshotsPath + "/index")
   230  	if err != nil {
   231  		return
   232  	}
   233  
   234  	versions = []version{}
   235  	lines := strings.Split(string(contents), "\n")
   236  	for _, line := range lines {
   237  		if len(strings.TrimSpace(line)) == 0 {
   238  			continue
   239  		}
   240  		i := strings.Index(line, ",")
   241  		if i < 0 {
   242  			return nil, AnError{"Malformed version in index: " + line}
   243  		}
   244  		v_str := line[:i]
   245  		meta_str := line[i+1:]
   246  
   247  		var meta map[string]string
   248  
   249  		err = json.Unmarshal([]byte(meta_str), &meta)
   250  		if err != nil {
   251  			return
   252  		}
   253  
   254  		v := *NewVersionWithMeta(v_str, meta)
   255  		versions = append(versions, v)
   256  	}
   257  	return
   258  }
   259  
   260  func (b PosixBackend) Diff(v1 *version, v2 *version, obj string) (string, error) {
   261  	return "", AnError{"not yet"}
   262  }