Skip to content

Commit

Permalink
Add sync command (#3)
Browse files Browse the repository at this point in the history
* Add sync command

* Add release workflow
  • Loading branch information
The-Daishogun authored Sep 30, 2023
1 parent 2c30719 commit d102633
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 31 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/build-and-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# .github/workflows/release.yaml

on:
release:
types: [created]

permissions:
contents: write
packages: write

jobs:
releases-matrix:
name: Release Go Binary
runs-on: ubuntu-latest
strategy:
matrix:
# build and publish in parallel: linux/386, linux/amd64, linux/arm64, windows/386, windows/amd64, darwin/amd64, darwin/arm64
goos: [linux, windows, darwin]
goarch: ["386", amd64, arm64]
exclude:
- goarch: "386"
goos: darwin
- goarch: arm64
goos: windows
steps:
- uses: actions/checkout@v3
- uses: wangyoucao577/go-release-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
binary_name: "syncommit"
4 changes: 2 additions & 2 deletions cmd/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"log"
"os"
"os/exec"
"syncommit/utils"
"syncommit/structs"

"github.com/spf13/cobra"
)
Expand All @@ -23,7 +23,7 @@ var commitCommand = &cobra.Command{
Short: "Add a commit to the sync repo",
Long: `Add a commit to the sync repo`,
Run: func(cmd *cobra.Command, args []string) {
err := os.Chdir(utils.RepoPath)
err := os.Chdir(structs.RepoPath)
if err != nil {
log.Fatal("failed to cd into repo directory. ", err.Error())
}
Expand Down
5 changes: 3 additions & 2 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"log"
"syncommit/scripts"
"syncommit/structs"
"syncommit/utils"

"github.com/spf13/cobra"
Expand All @@ -17,11 +18,11 @@ var initCommand = &cobra.Command{
Use: "init",
Short: "setup the current repository to sync with github",
Run: func(cmd *cobra.Command, args []string) {
privateRepoFound, err := utils.SearchDir(utils.ConfigFolderPath, utils.RepoLocation)
privateRepoFound, err := utils.SearchDir(structs.ConfigFolderPath, structs.RepoLocation)
if err != nil {
log.Fatal("failed to read directory")
}
privateRepoUrlFound, err := utils.SearchDir(utils.ConfigFolderPath, utils.RepoFileName)
privateRepoUrlFound, err := utils.SearchDir(structs.ConfigFolderPath, structs.RepoFileName)
if err != nil {
log.Fatal("failed to read directory")
}
Expand Down
12 changes: 3 additions & 9 deletions cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ package cmd
import (
"fmt"
"log"
"os"
"os/exec"
"syncommit/utils"
"syncommit/structs"

"github.com/spf13/cobra"
)
Expand All @@ -19,12 +17,8 @@ var pushCommand = &cobra.Command{
Short: "Pushes all the sync commits to github",
Long: `Pushes all the sync commits to github`,
Run: func(cmd *cobra.Command, args []string) {
err := os.Chdir(utils.RepoPath)
if err != nil {
log.Fatal("failed to cd into repo directory. ", err.Error())
}
commitCmd := exec.Command("git", "push", "-fu")
err = commitCmd.Run()
syncRepo := structs.GetRepoAtPath(structs.RepoPath)
err := syncRepo.Push()
if err != nil {
log.Fatal("failed to push. ", err.Error())
}
Expand Down
38 changes: 38 additions & 0 deletions cmd/sync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package cmd

import (
"fmt"
"log"
"syncommit/structs"
"syncommit/utils"

"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(syncCommand)
}

var syncCommand = &cobra.Command{
Use: "sync",
Short: "sync all commits to private repo",
Long: `commits and pushes all your commits in the current project to github.`,
Run: func(cmd *cobra.Command, args []string) {
repo := structs.GetRepoAtPath(".")
allCommits := repo.GetRepoCommitsForCurrentAuthor()
syncRepo := structs.GetRepoAtPath(structs.RepoPath)
syncedCommits := syncRepo.GetRepoCommitsForCurrentAuthor()
commitsToSync := utils.FilterSyncedCommits(allCommits, syncedCommits)
for _, commit := range commitsToSync {
err := commit.Commit(branchName, repo.Name)
if err != nil {
log.Fatal("failed to sync repo.\nError: ", err.Error())
}
}
err := syncRepo.Push()
if err != nil {
log.Fatal("failed to push. ", err.Error())
}
fmt.Println("sync commits pushed successfully.")
},
}
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package main

import (
"log"
"syncommit/cmd"
"syncommit/utils"
)

func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
utils.RunChecks()
utils.SetupConfigFolder()
cmd.Execute()
Expand Down
4 changes: 3 additions & 1 deletion scripts/post-commit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ if [[ $(git config --get remote.origin.url) == *"github"* ]]; then
echo "skipping sync commit since remote is github."
exit 0
fi

commit_hash=$(git log -1 --format=%h)
commit_message=$(git log -n 1 HEAD --pretty=format:%s)
branch_name=$(git branch --show-current)
repo_name=$(basename -s .git `git config --get remote.origin.url`)

sync_message="commit: $commit_message on branch $branch_name on repo: $repo_name"
sync_message="hash: $commit_hash $commit_message on branch $branch_name on repo: $repo_name"
syncommit commit -m "$(echo $sync_message)"
exit 0
28 changes: 28 additions & 0 deletions structs/commit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package structs

import (
"fmt"
"log"
"os"
"os/exec"
"strings"
"time"
)

type Commit struct {
Hash string
Message string
Time time.Time
}

func (c *Commit) generateCommitMessage(repoName, branchName string) string {
return fmt.Sprintf("hash: %s %s on branch: %s on repo: %s", c.Hash, c.Message, strings.TrimSpace(repoName), strings.TrimSpace(branchName))
}

func (c *Commit) Commit(repoName, branchName string) error {
err := os.Chdir(RepoPath)
if err != nil {
log.Fatal("failed to cd into repo directory. ", err.Error())
}
return exec.Command("git", "commit", "-m", fmt.Sprintf("%q", c.generateCommitMessage(repoName, branchName)), "--allow-empty", fmt.Sprintf("--date='%s'", c.Time.Format(time.RFC1123Z))).Run()
}
118 changes: 118 additions & 0 deletions structs/repo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package structs

import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
)

const RepoFileName = ".repo"
const RepoLocation = "repo"

var HomePath = os.Getenv("HOME")

var ConfigFolderPath = filepath.Join(HomePath, "/.syncommit")
var RepoPath = filepath.Join(ConfigFolderPath, RepoLocation)

const hashPrefix = "hash: "

type Repo struct {
Name string
Path string
BranchName string
CurrentAuthorName string
CurrentAuthorEmail string
}

func (r *Repo) GetRepoCommitsForCurrentAuthor() (commits []Commit) {
cmd := exec.Command("git", "--no-pager", "log", fmt.Sprintf("--author=%s", r.CurrentAuthorEmail), "--pretty=format:%h$//%s$//%ad", "--date=unix", "--no-merges")
commitsBytes, err := cmd.Output()
if err != nil && err.Error() == "exit status 128" {
// User has no commits
return
}
if err != nil {
log.Fatal("Failed to get commits for current author.\nError: ", err.Error())
}
commitsString := strings.Split(string(commitsBytes), "\n")
for _, commitString := range commitsString {
if len(commitString) < 1 {
continue
}

var commit Commit
if r.Path != RepoPath {
commit = parseGeneralRepoCommitString(commitString)
} else {
if !strings.Contains(commitString, hashPrefix) {
continue
}
commit = parseSyncRepoCommitString(commitString)
}
commits = append(commits, commit)
}
return
}

func parseSyncRepoCommitString(commitString string) Commit {
hashWithMessageAndTime := strings.Split(commitString, "$//")
commitTimeStr, _ := strconv.Atoi(hashWithMessageAndTime[2])
originalCommitHash := strings.Replace(hashWithMessageAndTime[0], hashPrefix, "", 1)[0:7]
return Commit{Hash: originalCommitHash, Message: hashWithMessageAndTime[1], Time: time.Unix(int64(commitTimeStr), 0)}
}

func parseGeneralRepoCommitString(commitString string) Commit {
hashWithMessageAndTime := strings.Split(commitString, "$//")
commitTimeStr, _ := strconv.Atoi(hashWithMessageAndTime[2])
return Commit{Hash: hashWithMessageAndTime[0], Message: hashWithMessageAndTime[1], Time: time.Unix(int64(commitTimeStr), 0)}
}

func (r *Repo) Push() error {
cmd := exec.Command("git", "push", "-fu")
cmd.Dir = r.Path
return cmd.Run()
}

func GetRepoAtPath(path string) Repo {
err := os.Chdir(path)
if err != nil {
log.Fatal("failed to change directory,\nError: ", err.Error())
}
branchNameBytes, err := exec.Command("git", "branch", "--show-current").Output()
if err != nil {
log.Fatal("failed to get repo information.\nError: ", err.Error())
}
branchName := strings.ReplaceAll(string(branchNameBytes), "\n", "")
repoPathBytes, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
if err != nil {
log.Fatal("failed to get repo information.\nError: ", err.Error())
}
repoPath := strings.ReplaceAll(string(repoPathBytes), "\n", "")
repoNameBytes, err := exec.Command("basename", string(repoPath)).Output()
if err != nil {
log.Fatal("failed to get repo information.\nError: ", err.Error())
}
repoName := strings.ReplaceAll(string(repoNameBytes), "\n", "")
authorNameBytes, err := exec.Command("git", "config", "user.name").Output()
if err != nil {
log.Fatal("failed to get repo information.\nError: ", err.Error())
}
authorName := strings.TrimSpace(string(authorNameBytes))
authorEmailBytes, err := exec.Command("git", "config", "user.email").Output()
if err != nil {
log.Fatal("failed to get repo information.\nError:", err.Error())
}
authorEmail := strings.TrimSpace(string(string(authorEmailBytes)))
return Repo{
Name: repoName,
Path: repoPath,
BranchName: branchName,
CurrentAuthorName: authorName,
CurrentAuthorEmail: authorEmail,
}
}
35 changes: 28 additions & 7 deletions utils/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"path/filepath"
"regexp"
"strings"
"syncommit/structs"
)

func ValidateGitUrl(gitUrl string) bool {
Expand All @@ -17,16 +18,16 @@ func ValidateGitUrl(gitUrl string) bool {
}

func ClonePrivateRepo(repoUrl string) error {
dirs, err := os.ReadDir(ConfigFolderPath)
dirs, err := os.ReadDir(structs.ConfigFolderPath)
if err != nil {
log.Fatal("failed to read the contents of ConfigFolderPath. ", err)
}
for _, dir := range dirs {
if dir.Name() == RepoLocation {
if dir.Name() == structs.RepoLocation {
return nil
}
}
cmd := exec.Command("git", "clone", "-q", repoUrl, filepath.Join(ConfigFolderPath, RepoLocation))
cmd := exec.Command("git", "clone", "-q", repoUrl, filepath.Join(structs.ConfigFolderPath, structs.RepoLocation))
return cmd.Run()

}
Expand All @@ -40,23 +41,43 @@ func GetPrivateRepo() {
if !validated {
log.Fatal("invalid git url. make sure it's the ssh url and the url is correct.")
}
file, err := os.Create(filepath.Join(ConfigFolderPath, RepoFileName))
file, err := os.Create(filepath.Join(structs.ConfigFolderPath, structs.RepoFileName))
if err != nil {
log.Fatal("failed to create .repo file. ", err)
}
defer file.Close()

_, err = file.WriteString(input)
if err != nil {
os.Remove(filepath.Join(ConfigFolderPath, RepoFileName))
os.Remove(filepath.Join(structs.ConfigFolderPath, structs.RepoFileName))
log.Fatal("failed to write to .repo file. ", err)
}
fmt.Println("Starting to clone the repo.")
err = ClonePrivateRepo(input)
if err != nil {
os.Remove(filepath.Join(ConfigFolderPath, RepoFileName))
os.Remove(filepath.Join(ConfigFolderPath, RepoLocation))
os.Remove(filepath.Join(structs.ConfigFolderPath, structs.RepoFileName))
os.Remove(filepath.Join(structs.ConfigFolderPath, structs.RepoLocation))
log.Fatal("failed to clone repo. make sure repo url is correct. ", err)
}
fmt.Println("Cloning successful.")
}

func createCommitMasterString(commits []structs.Commit) string {
var masterString = ""
for _, commit := range commits {
masterString = masterString + commit.Hash + " "
}
return masterString
}

func FilterSyncedCommits(allCommits []structs.Commit, syncedCommits []structs.Commit) []structs.Commit {
var commitsToSync []structs.Commit
masterString := createCommitMasterString(syncedCommits)
for _, commit := range allCommits {
if strings.Contains(masterString, commit.Hash) {
continue
}
commitsToSync = append(commitsToSync, commit)
}
return commitsToSync
}
Loading

0 comments on commit d102633

Please sign in to comment.