Browse Source

Adds revert and some of the help menu

sloum 2 years ago
  1. 6
  2. 111


@ -32,15 +32,15 @@ Format: `scbm set _program-name_ _key_ _new-value_`
Action: Will set a new value for a given key for a given program. Will request confirmation of value before updating the database.
### upgrade
Format: `scbm upgrade [program name ...]`
Format: `scbm upgrade _program- name..._`
Action: Will pull updates for each program, or optionally just the program passed in, and run the install command for each. Before doing so the current commit hash will be stored in the \*prev field for the program.
### revert
Format `scbm revert [-f] _program-name_`
Format `scbm revert _program-name_`
Action: Will move to the commit hash stored in in the \*prev field for the given program. Will run the install command for the given program. Will remove the commit hash from the \*prev slice and move it to current. If the `-f` flag is passed, it will also freeze the program (but not trash it).
### list
Format: `scbm list [-v]`
Format: `scbm list`
Action: Will output a table in the format: "program name\tdescription" for each program currently managed by scbm. The verbose flag will render a full view for all items.
### freeze


@ -13,6 +13,7 @@ import (
git ""
const VERSION string = "0.0.5"
const SCBM_FOLDER string = "/var/local/scbm"
const MANIFEST string = "/var/local/scbm/_data/manifest.txt"
@ -38,6 +39,12 @@ func (rec *record) String() string {
func (rec *record) saveRecord() error {
fp := filepath.Join(SCBM_FOLDER,
err := ioutil.WriteFile(fp, []byte(rec.String()), 0644)
return err
func getRecord(name string) (record, error) {
fp := filepath.Join(SCBM_FOLDER, "_data", fmt.Sprintf("%s.scbm", name))
if _, err := os.Stat(SCBM_FOLDER); os.IsNotExist(err) {
@ -152,7 +159,7 @@ func (m *manifest) save() error {
// Startup helpers
// Startup & helpers
// validateScbmFolder checks to make sure the necessary data storage folder(s) exist.
@ -169,6 +176,7 @@ func validateScbmFolder() {
// parseArgs is the main entry point into scbm and will run the appropriate functions
// based on the arguments passed in at run time.
func parseArgs() {
a := os.Args
if len(a) == 1 {
@ -179,7 +187,7 @@ func parseArgs() {
if len(a) == 3 {
} else if len(a) == 2 {
} else {
fmt.Fprintf(os.Stderr, "Too many items received.\nExpected: scbm help [command]\nGot: scbm help %s\n", strings.Join(a[3:], " "))
@ -199,16 +207,17 @@ func parseArgs() {
case "revert":
if len(a) != 3 {
errorExit("Incorrect number of arguments to revert. Expected 1 argument: program name.", nil)
revert(a[2], false)
case "set":
case "update":
case "upgrade":
if len(a) > 2 {
} else {
upgrade(make([]string, 0))
update(make([]string, 0))
case "view":
if len(a) < 3 {
@ -237,6 +246,10 @@ func errorExit(msg string, err error) {
func printHeader() {
fmt.Printf("\033[1mscbm - v%s\n", VERSION)
// Command entry points
@ -337,7 +350,28 @@ func get() {
// for each command, offering detailed explanations of the
// command and any available flags.
func help(c string) {
fmt.Printf("No help exists for %q...\n", c)
c = strings.ToLower(c)
switch c {
case "freeze":
fmt.Println("\033[1mFormat:\033[0m\n\tscbm freeze [-trash] \033[3mprogram-name\033[0m")
fmt.Println("\033[1mAction:\033[0m\n\tWill freeze a program (prevent it from receiving further updates). \033[1mDOES NOT\033[0m delete any files or uninstall any programs. If the `-trash` flag is passed the repo will be removed from the scbm folder; this is not recommended unless you have properly uninstalled the program beforehand as `-trash` only removes the program from management by scbm. It does not remove or alter any files outside of the scbm folder.")
case "thaw":
fmt.Println("\033[1mFormat:\033[0m\n\tscbm thaw \033[3mprogram-name\033[0m")
fmt.Println("\033[1mAction:\033[0m\n\tWill reactivate a froven program, allowing it to receive updates with the `update` command.")
case "list":
fmt.Println("\033[1mFormat:\033[0m\n\tscbm list")
fmt.Println("\033[1mAction:\033[0m\n\tWill output a table displaying all of the programs under scbm management showing the program name, the discription, and the programs freeze status.")
case "get":
fmt.Println("\033[1mFormat:\033[0m\n\tscbm get \033[3mclone-url\033[0m [name]")
fmt.Println("\033[1mAction:\033[0m\n\tWill attempt to clone the repo at the given url into the sbm staging folder and add the program to the scbm databse. The name field will default to the new repo name, but can be replaced by a name passed in as the final argument to the get command. The current HEAD of the master branch will be set as the current commit.")
fmt.Printf("No help exists for %q...\n", c)
// list loads the manifest file into memory and will print out
@ -355,13 +389,13 @@ func list() {
fmt.Printf("\033[1;4m%-8s\033[0m \033[1;4m%-15s\033[0m \033[1;4m%-35s\033[0m\n", "Active?", "Name", "Description")
fmt.Printf("\033[1;4m%-15s\033[0m \033[1;4m%-8s\033[0m \033[1;4m%-35s\033[0m\n", "Name", "Frozen", "Description")
for _, k := range keys {
r, e := getRecord(k)
if e != nil {
fmt.Printf("%-8t \033[1m%-15s\033[0m %s\n", m.m[k],, r.desc)
fmt.Printf("\033[1m%-15s\033[0m %-8t %s\n",, m.m[k], r.desc)
@ -425,10 +459,10 @@ func thaw(progs []string) {
// upgrade pulls from `origin master` for each active repository in
// update pulls from `origin master` for each active repository in
// the program list provided, or absent a list pulls `origin master`
// for each active program in the manifest.
func upgrade(progs []string) {
func update(progs []string) {
m, err := buildManifest()
if err != nil {
errorExit("Error loading manifest:", err)
@ -447,7 +481,7 @@ func upgrade(progs []string) {
for _, prog := range progs {
if !m.m[prog] {
fmt.Printf("Skipping \033[4m%s\033[0m, it is set to inactive\n", prog)
fmt.Printf("Skipping \033[4m%s\033[0m, it is currently frozen\n", prog)
r, e := getRecord(prog)
@ -486,6 +520,57 @@ func upgrade(progs []string) {
// revert moves a programs HEAD back to the previously used commit,
// if one exists. It moves the current commit ID to that commit and
// runs the install script. It then freezes the program so-as to stay
// on that commit.
func revert(prog string, swap bool) {
m, err := buildManifest()
if err != nil {
errorExit("Error loading manifest:", err)
fmt.Printf("Reverting %s...\n", prog)
if val, ok := m.m[prog]; ok && !val {
errorExit(fmt.Sprintf(" \\_%s is currently frozen. To revert or make other changes please thaw it.", prog), nil)
} else if !ok {
errorExit(fmt.Sprintf(" \\_%s does not exist\n", prog), nil)
r, e := getRecord(prog)
if e != nil {
errorExit(fmt.Sprintf("Unable to retrieve record for %s", prog), nil)
if strings.TrimSpace(r.prev) == "" {
errorExit(fmt.Sprintf(" \\_%s does not have a previous commit to revert to", prog), nil)
newCurrent := r.prev
fp := filepath.Join(SCBM_FOLDER, prog)
fmt.Printf("Reverting %s to previous commit: %s", prog, newCurrent)
err = git.ResetHEAD(fp, true, newCurrent)
if err != nil {
errorExit(fmt.Sprintf(" \\_unable to revert %s to %s", prog, newCurrent), err)
if swap {
fmt.Println("Swapping current and previous commit")
r.prev = r.current
} else {
r.prev = ""
r.current = newCurrent
fmt.Printf("Updating records for %s", prog)
err = r.saveRecord()
if err != nil {
fmt.Printf(" \\_\033[7mError saving record for %s:\033[0m\n", prog)
fmt.Println("\033[7mUndoing revert...\033[0m")
freeze(false, []string{prog})
// TODO install this version
// view outputs the details of an individual program record to stdout
func view(tail []string) {
if len(tail) != 1 {