diff --git a/.augment/rules/ar-coding-standards.md b/.augment/rules/ar-coding-standards.md new file mode 100644 index 0000000..fb71715 --- /dev/null +++ b/.augment/rules/ar-coding-standards.md @@ -0,0 +1,7 @@ +# .augment/rules/qs-coding-standards.md +type: always + +- Use Go 1.24+ with proper module structure +- Follow qs command patterns in internal/commands/ +- Include proper error handling with context.Context +- Use github.com/untillpro/goutils/logger for all logging \ No newline at end of file diff --git a/.augment/rules/ar-github-integration.md b/.augment/rules/ar-github-integration.md new file mode 100644 index 0000000..8a4f0ea --- /dev/null +++ b/.augment/rules/ar-github-integration.md @@ -0,0 +1,7 @@ +# .augment/rules/qs-github-integration.md +type: manual + +- Use GitHub CLI (gh) for all GitHub API operations +- Implement retry mechanisms with helper.Retry() for network calls +- Include proper error messages for GitHub authentication failures +- Follow the existing patterns in gitcmds/ package \ No newline at end of file diff --git a/.augment/rules/ar-testing-framework.md b/.augment/rules/ar-testing-framework.md new file mode 100644 index 0000000..590d817 --- /dev/null +++ b/.augment/rules/ar-testing-framework.md @@ -0,0 +1,8 @@ +# .augment/rules/qs-testing-framework.md +type: auto +description: Testing guidelines for qs project + +- Use testify framework for unit tests (github.com/stretchr/testify/require) +- Write system tests in sys_test.go for new commands +- Test with real GitHub repositories using systrun framework +- Include both positive and negative test cases \ No newline at end of file diff --git a/README.md b/README.md index 13e220c..052d50a 100644 --- a/README.md +++ b/README.md @@ -40,23 +40,24 @@ go install github.com/untillpro/qs@latest ## Quick Start +`qs` automatically detects your workflow mode: +- **Single Remote Mode**: When you have direct push access (no upstream remote) +- **Fork Mode**: When contributing to external projects (with upstream remote) + ```bash -# Initialize a forked workflow -qs fork # Fork repository and configure remotes +# For repositories with direct access (single remote mode) +qs dev "feature-name" # Create branch - no fork needed! +qs u -m "commit message" # Upload changes +qs pr # Create PR to same repository -# Create a development branch -qs dev # Create branch from clipboard content -qs dev "feature-name" # Create branch with specific name +# For external projects (fork mode) +qs fork # Fork repository and configure remotes +qs dev "feature-name" # Create branch in your fork +qs u -m "commit message" # Upload changes +qs pr # Create PR to upstream -# Work with changes -qs u -m "commit message" # Upload changes (add + commit + push) +# Common commands (work in both modes) qs d # Download changes (smart sync) - -# Create pull request -qs pr # Create PR from current branch -qs pr -d # Create draft PR - -# Check status qs # Show repository status ``` @@ -82,10 +83,12 @@ qs fork # Fork repository to your account and configure upstr #### Branch Operations ```bash qs dev [branch-name] # Create development branch + # - Auto-detects workflow mode (fork vs single remote) # - Auto-detects branch name from clipboard # - Supports GitHub issue URLs # - Supports Jira ticket URLs # - Links branch to issues automatically + # - Works with or without upstream remote qs dev -d # Delete merged development branches # - Removes local and remote branches @@ -93,7 +96,6 @@ qs dev -d # Delete merged development branches # - Cleans up tracking references qs dev -i, --ignore-hook # Create branch without large file hooks -qs dev -n, --no-fork # Create branch in main repo (no fork required) ``` #### Sync Operations @@ -239,7 +241,34 @@ export JIRA_API_TOKEN="your-jira-api-token" ## Workflow Examples -### Standard Fork Workflow +### Single Remote Workflow (Direct Repository Access) +For repositories where you have direct push access and don't need a fork: + +```bash +# 1. Clone and work directly +git clone https://github.com/your-org/your-repo.git +cd your-repo + +# 2. Create feature branch (no fork needed!) +qs dev "feature-awesome-feature" +# qs automatically detects single remote mode + +# 3. Make changes and upload +# ... edit files ... +qs u -m "Add awesome feature" + +# 4. Create pull request to the same repository +qs pr + +# 5. Clean up after merge +qs dev -d +``` + +**Note**: `qs` automatically detects single remote mode when you don't have an upstream remote and works seamlessly with just the `origin` remote. + +### Fork Workflow (Contributing to External Projects) +For contributing to repositories you don't have direct access to: + ```bash # 1. Fork and setup git clone https://github.com/original/repo.git @@ -253,7 +282,7 @@ qs dev "feature-awesome-feature" # ... edit files ... qs u -m "Add awesome feature" -# 4. Create pull request +# 4. Create pull request to upstream qs pr # 5. Clean up after merge diff --git a/gitcmds/gitcmds.go b/gitcmds/gitcmds.go index 2e9e408..e7b2afd 100644 --- a/gitcmds/gitcmds.go +++ b/gitcmds/gitcmds.go @@ -1595,20 +1595,24 @@ func CreateGithubLinkToIssue(wd, parentRepo, githubIssueURL string, issueNumber // SyncMainBranch syncs the local main branch with upstream and origin // Flow: -// 1. Pull from UpstreamMain to MainBranch with rebase -// 2. If upstream exists -// - Pull from origin to MainBranch with rebase -// - Push to origin from MainBranch +// 1. If upstream exists: Pull from upstream/main to main with rebase +// 2. Pull from origin/main to main with rebase +// 3. Push to origin/main +// In single remote mode (no upstream), only syncs with origin func SyncMainBranch(wd string) error { mainBranch, err := GetMainBranch(wd) if err != nil { return fmt.Errorf(errMsgFailedToGetMainBranch, err) } - // Pull from UpstreamMain to MainBranch with rebase - remoteUpstreamURL := GetRemoteUpstreamURL(wd) + // Check if upstream remote exists + upstreamExists, err := HasRemote(wd, "upstream") + if err != nil { + return fmt.Errorf("failed to check if upstream exists: %w", err) + } - if len(remoteUpstreamURL) > 0 { + // Pull from upstream/main if upstream exists (fork workflow) + if upstreamExists { stdout, stderr, err := new(exec.PipedExec). Command(git, pull, "--rebase", "upstream", mainBranch, "--no-edit"). WorkingDir(wd). @@ -2350,13 +2354,23 @@ func createPR( repo := parentRepoName if len(repo) == 0 { + // Single remote mode: no parent repo, PR to same repo repo = forkAccount + slash + repoName } + // Determine the head reference for the PR + // In fork mode: use "forkAccount:branchName" format + // In single remote mode: use just "branchName" format + headRef := prBranchName + if len(parentRepoName) > 0 { + // Fork mode: specify the fork account + headRef = forkAccount + ":" + prBranchName + } + args := []string{ "pr", "create", - fmt.Sprintf(`--head=%s`, forkAccount+":"+prBranchName), + fmt.Sprintf(`--head=%s`, headRef), fmt.Sprintf(`--repo=%s`, repo), fmt.Sprintf(`--body=%s`, strings.TrimSpace(strBody)), fmt.Sprintf(`--title=%s`, strings.TrimSpace(prTitle)), @@ -2798,6 +2812,23 @@ func HasRemote(wd, remoteName string) (bool, error) { return false, nil } +// GetEffectiveUpstreamRemote returns the remote to use as upstream. +// If 'upstream' remote exists, returns "upstream". +// Otherwise, returns "origin" (single remote mode). +// This allows qs to work in both fork and non-fork workflows. +func GetEffectiveUpstreamRemote(wd string) (string, error) { + upstreamExists, err := HasRemote(wd, "upstream") + if err != nil { + return "", err + } + + if upstreamExists { + return "upstream", nil + } + + return "origin", nil +} + func GawkInstalled() bool { _, _, err := new(exec.PipedExec). Command("gawk", "--version"). diff --git a/gitcmds/pr.go b/gitcmds/pr.go index bcc0665..59fd8e3 100644 --- a/gitcmds/pr.go +++ b/gitcmds/pr.go @@ -63,7 +63,14 @@ func Pr(wd string, needDraft bool) error { // If we are on dev branch than we need to create pr branch if branchType == notesPkg.BranchTypeDev { var response string - if len(parentRepoName) > 0 && UpstreamNotExist(wd) { + // Only add upstream if we have a parent repo and upstream doesn't exist + // In single remote mode (no parent repo), we don't need upstream + upstreamExists, err := HasRemote(wd, "upstream") + if err != nil { + return err + } + + if len(parentRepoName) > 0 && !upstreamExists { fmt.Print("Upstream not found.\nRepository " + parentRepoName + " will be added as upstream. Agree[y/n]?") _, _ = fmt.Scanln(&response) if response != pushYes { diff --git a/internal/cmdproc/cmdproc.go b/internal/cmdproc/cmdproc.go index c991910..1aaaa93 100644 --- a/internal/cmdproc/cmdproc.go +++ b/internal/cmdproc/cmdproc.go @@ -150,7 +150,6 @@ func devCmd(_ context.Context, params *qsGlobalParams) *cobra.Command { } cmd.Flags().BoolP(devDelParamFull, devDelParam, false, devDelMsgComment) cmd.Flags().BoolP(ignorehookDelParamFull, ignorehookDelParam, false, devIgnoreHookMsgComment) - cmd.Flags().BoolP(noForkParamFull, noForkParam, false, devNoForkMsgComment) return cmd } diff --git a/internal/cmdproc/consts.go b/internal/cmdproc/consts.go index 910c287..5f6c7ae 100644 --- a/internal/cmdproc/consts.go +++ b/internal/cmdproc/consts.go @@ -1,7 +1,7 @@ package cmdproc const ( - msgOkSeeYou = "Ok, see you" + msgOkSeeYou = "Ok, see you" pushParamDesc = "Upload sources to repo" pushMessageWord = "message" pushMessageParam = "m" @@ -22,14 +22,11 @@ const ( ignorehookDelParamFull = "ignore-hook" prdraftParam = "d" prdraftParamFull = "draft" - noForkParam = "n" - noForkParamFull = "no-fork" prParamDesc = "Make pull request" devDelMsgComment = "Deletes all merged branches from forked repository" devIgnoreHookMsgComment = "Ignore creating local hook" - devNoForkMsgComment = "Allows to create branch in main repo" prdraftMsgComment = "Create draft of pull request" devParamDesc = "Create developer branch" upgradeParamDesc = "Print command to upgrade qs" diff --git a/internal/commands/consts.go b/internal/commands/consts.go index 9bf4fdf..dd3fae8 100644 --- a/internal/commands/consts.go +++ b/internal/commands/consts.go @@ -7,7 +7,6 @@ const ( pushYes = "y" devDelParamFull = "delete" - noForkParamFull = "no-fork" devConfirm = "Dev branch '$reponame' will be created. Continue(y/n)? " diff --git a/internal/commands/dev.go b/internal/commands/dev.go index bbc7737..2090741 100644 --- a/internal/commands/dev.go +++ b/internal/commands/dev.go @@ -13,7 +13,6 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/spf13/cobra" "github.com/untillpro/goutils/logger" - "github.com/untillpro/qs/gitcmds" contextpkg "github.com/untillpro/qs/internal/context" "github.com/untillpro/qs/internal/helper" @@ -45,16 +44,26 @@ func Dev(cmd *cobra.Command, wd string, args []string) error { args = append(args, clipargs) } - noForkAllowed := (cmd.Flag(noForkParamFull).Value.String() == trueStr) - if !noForkAllowed { - if len(parentRepo) == 0 { // main repository, not forked - repo, org, err := gitcmds.GetRepoAndOrgName(wd) - if err != nil { - return err - } + // Auto-detect workflow mode: + // - If parentRepo exists OR upstream remote exists -> fork workflow + // - If no parentRepo AND no upstream remote -> single remote workflow + // Check if upstream remote exists + upstreamExists, err := gitcmds.HasRemote(wd, "upstream") + if err != nil { + return err + } - return fmt.Errorf("you are in %s/%s repo\nExecute 'qs fork' first", org, repo) + // Only require fork if: + // 1. No parent repo exists (not a fork), AND + // 2. Upstream remote exists (indicating fork workflow was intended) + // This catches the edge case where someone manually added upstream but didn't fork + if len(parentRepo) == 0 && upstreamExists { + repo, org, err := gitcmds.GetRepoAndOrgName(wd) + if err != nil { + return err } + + return fmt.Errorf("you are in %s/%s repo with upstream remote but no fork detected\nExecute 'qs fork' first", org, repo) } curBranch, isMain, err := gitcmds.IamInMainBranch(wd) @@ -139,18 +148,18 @@ func Dev(cmd *cobra.Command, wd string, args []string) error { case pushYes: // Remote developer branch, linked to issue is created var response string - if len(parentRepo) > 0 { - if gitcmds.UpstreamNotExist(wd) { - fmt.Print("Upstream not found.\nRepository " + parentRepo + " will be added as upstream. Agree[y/n]?") - _, _ = fmt.Scanln(&response) - if response != pushYes { - fmt.Print(msgOkSeeYou) - return nil - } - response = "" - if err := gitcmds.MakeUpstreamForBranch(wd, parentRepo); err != nil { - return err - } + // Only add upstream if we have a parent repo and upstream doesn't exist + // In single remote mode (no parent repo), we don't need upstream + if len(parentRepo) > 0 && !upstreamExists { + fmt.Print("Upstream not found.\nRepository " + parentRepo + " will be added as upstream. Agree[y/n]?") + _, _ = fmt.Scanln(&response) + if response != pushYes { + fmt.Print(msgOkSeeYou) + return nil + } + response = "" + if err := gitcmds.MakeUpstreamForBranch(wd, parentRepo); err != nil { + return err } } diff --git a/internal/systrun/const.go b/internal/systrun/const.go index 12c8302..896ede6 100644 --- a/internal/systrun/const.go +++ b/internal/systrun/const.go @@ -33,13 +33,13 @@ type SyncState int type ClipboardContentType int const ( + // RemoteStateNull means that the remote of the clone repo is null + RemoteStateNull RemoteState = iota // RemoteStateOK means that remote of the clone repo is configured correctly - RemoteStateOK RemoteState = iota + RemoteStateOK // RemoteStateMisconfigured means that the remote of the clone repo is not configured correctly, // e.g. `qs u` should fail on permission error on `git push` (now it does not fail) RemoteStateMisconfigured - // RemoteStateNull means that the remote of the clone repo is null - RemoteStateNull ) const ( diff --git a/internal/systrun/impl.go b/internal/systrun/impl.go index 1c3c712..b35a5e8 100644 --- a/internal/systrun/impl.go +++ b/internal/systrun/impl.go @@ -167,23 +167,8 @@ func (st *SystemTest) createAnotherClone() error { return fmt.Errorf("failed to get oririn remote URL: %w", err) } - remoteUpstreamURL, err := gitcmds.GetRemoteUrlByName(st.cloneRepoPath, upstream) - if err != nil { - if !strings.Contains(err.Error(), "not found") { - remoteUpstreamURL = "" - } - - return fmt.Errorf("failed to get upstream remote URL: %w", err) - } - - // extract account, repo and token from remote url - forkAccount, _, forkToken, err := gitcmds.ParseGitRemoteURL(remoteOriginURL) - if err != nil { - return err - } - // extract account, repo and token from remote url - upstreamAccount, repo, upstreamToken, err := gitcmds.ParseGitRemoteURL(remoteUpstreamURL) + forkAccount, repo, forkToken, err := gitcmds.ParseGitRemoteURL(remoteOriginURL) if err != nil { return err } @@ -207,16 +192,33 @@ func (st *SystemTest) createAnotherClone() error { return fmt.Errorf("failed to clone repository: %w, output: %s", err, output) } - // Step 3.1: Configure remotes in temp clone - if err := gitcmds.CreateRemote( - st.anotherCloneRepoPath, - upstream, - upstreamAccount, - upstreamToken, - repo, - true, - ); err != nil { - return err + // Step 3.1: Configure upstream remote in temp clone if needed + if !st.cfg.NoUpstream() { + remoteUpstreamURL, err := gitcmds.GetRemoteUrlByName(st.cloneRepoPath, upstream) + if err != nil { + if !strings.Contains(err.Error(), "not found") { + remoteUpstreamURL = "" + } + + return fmt.Errorf("failed to get upstream remote URL: %w", err) + } + + // extract account, repo and token from remote url + upstreamAccount, repo, upstreamToken, err := gitcmds.ParseGitRemoteURL(remoteUpstreamURL) + if err != nil { + return err + } + + if err := gitcmds.CreateRemote( + st.anotherCloneRepoPath, + upstream, + upstreamAccount, + upstreamToken, + repo, + true, + ); err != nil { + return err + } } if err := gitcmds.CreateRemote( @@ -1009,18 +1011,10 @@ func (st *SystemTest) setSyncState( return err } - parentRepo, err := gitcmds.GetParentRepoName(st.cloneRepoPath) - if err != nil { - return err - } - - args := []string{} - if len(parentRepo) == 0 { - args = append(args, "--no-fork") - } + // No need to pass --no-fork flag anymore - auto-detection handles it stdout, stderr, err := st.runCommand(&CommandConfig{ Command: "dev", - Args: args, + Args: []string{}, Stdin: "y", }) if err != nil { diff --git a/internal/systrun/types.go b/internal/systrun/types.go index bebb726..76c22f6 100644 --- a/internal/systrun/types.go +++ b/internal/systrun/types.go @@ -80,6 +80,10 @@ type Expectation int type ExpectationFunc func(_ context.Context) error +func (cfg *TestConfig) NoUpstream() bool { + return cfg.UpstreamState == RemoteStateOK && cfg.ForkState == RemoteStateNull +} + // ExpectationCustomBranchIsCurrentBranch represents checker for ExpectationCurrentBranch func ExpectationCustomBranchIsCurrentBranch(ctx context.Context) error { currentBranch, err := gitcmds.GetCurrentBranchName(ctx.Value(contextCfg.CtxKeyCloneRepoPath).(string)) diff --git a/sys_test.go b/sys_test.go index d5dc107..093a344 100644 --- a/sys_test.go +++ b/sys_test.go @@ -97,34 +97,35 @@ func TestDev_CustomName(t *testing.T) { require.NoError(err) } -// TestDev_NoUpstream_CustomName tests creating a new dev branch when it doesn't exist -func TestDev_NoUpstream_CustomName(t *testing.T) { +// TestDevExistingBranch tests behavior when dev branch already exists +func TestDev_ExistingBranch(t *testing.T) { require := require.New(t) + branchName := "branch-name" testConfig := &systrun.TestConfig{ TestID: strings.ToLower(t.Name()), GHConfig: getGithubConfig(t), CommandConfig: &systrun.CommandConfig{ Command: "dev", - Args: []string{"--no-fork"}, + Args: []string{branchName}, Stdin: "y", }, - ClipboardContent: systrun.ClipboardContentCustom, - UpstreamState: systrun.RemoteStateOK, - ForkState: systrun.RemoteStateNull, - Expectations: []systrun.ExpectationFunc{ - systrun.ExpectationCustomBranchIsCurrentBranch, - systrun.ExpectationLargeFileHooksInstalled, + UpstreamState: systrun.RemoteStateOK, + ForkState: systrun.RemoteStateOK, + BranchState: &systrun.BranchState{ + DevBranchExists: true, }, + ExpectedStderr: fmt.Sprintf("dev branch %s-dev already exists", branchName), } sysTest := systrun.New(t, testConfig) err := sysTest.Run() - require.NoError(err) + + require.Error(err) } -// TestDevExistingBranch tests behavior when dev branch already exists -func TestDev_ExistingBranch(t *testing.T) { +// TestDev_ExistingBranch_NoUpstream tests behavior when dev branch already exists +func TestDev_ExistingBranch_NoUpstream(t *testing.T) { require := require.New(t) branchName := "branch-name" @@ -137,7 +138,6 @@ func TestDev_ExistingBranch(t *testing.T) { Stdin: "y", }, UpstreamState: systrun.RemoteStateOK, - ForkState: systrun.RemoteStateOK, BranchState: &systrun.BranchState{ DevBranchExists: true, }, @@ -150,8 +150,7 @@ func TestDev_ExistingBranch(t *testing.T) { require.Error(err) } -// TestDevNoForkExistingIssue tests creating a dev branch when upstream remote doesn't exist -func TestDev_NoFork_ExistingIssue(t *testing.T) { +func TestDev_GitHubIssue_NoUpstream(t *testing.T) { require := require.New(t) ghConfig := getGithubConfig(t) @@ -161,7 +160,6 @@ func TestDev_NoFork_ExistingIssue(t *testing.T) { GHConfig: ghConfig, CommandConfig: &systrun.CommandConfig{ Command: "dev", - Args: []string{"--no-fork"}, Stdin: "y", }, UpstreamState: systrun.RemoteStateOK, @@ -204,8 +202,33 @@ func TestPR_FromOtherClone(t *testing.T) { require.NoError(err) } -// TestDev_NoFork_NonExistingIssue tests creating a dev branch when upstream remote doesn't exist -func TestDev_NoFork_NonExistingIssue(t *testing.T) { +func TestPR_FromOtherClone_NoUpstream(t *testing.T) { + require := require.New(t) + + ghConfig := getGithubConfig(t) + + testConfig := &systrun.TestConfig{ + TestID: strings.ToLower(t.Name()), + GHConfig: ghConfig, + CommandConfig: &systrun.CommandConfig{ + Command: "pr", + }, + UpstreamState: systrun.RemoteStateOK, + ClipboardContent: systrun.ClipboardContentGithubIssue, + SyncState: systrun.SyncStateSynchronized, + RunCommandOnOtherClone: true, + NeedCollaboration: true, + Expectations: []systrun.ExpectationFunc{systrun.ExpectationPRCreated}, + } + + sysTest := systrun.New(t, testConfig) + err := sysTest.Run() + require.NoError(err) +} + +// TestDev_NonExistingIssue_NoUpstream tests creating a dev branch when upstream remote doesn't exist +// Auto-detection handles single remote mode (no --no-fork flag needed) +func TestDev_NonExistingIssue_NoUpstream(t *testing.T) { require := require.New(t) ghConfig := getGithubConfig(t) @@ -214,12 +237,10 @@ func TestDev_NoFork_NonExistingIssue(t *testing.T) { GHConfig: ghConfig, CommandConfig: &systrun.CommandConfig{ Command: "dev", - Args: []string{"--no-fork"}, Stdin: "y", }, ClipboardContent: systrun.ClipboardContentUnavailableGithubIssue, UpstreamState: systrun.RemoteStateOK, - ForkState: systrun.RemoteStateNull, ExpectedStderr: "invalid GitHub issue link", } @@ -228,8 +249,7 @@ func TestDev_NoFork_NonExistingIssue(t *testing.T) { require.Error(err) } -// TestDevNoForkJiraTicketURL tests creating a dev branch with a valid JIRA ticket URL -func TestDev_NoFork_JiraTicketURL(t *testing.T) { +func TestDev_JiraTicketURL_NoUpstream(t *testing.T) { require := require.New(t) testConfig := &systrun.TestConfig{ @@ -237,11 +257,9 @@ func TestDev_NoFork_JiraTicketURL(t *testing.T) { GHConfig: getGithubConfig(t), CommandConfig: &systrun.CommandConfig{ Command: "dev", - Args: []string{"--no-fork"}, Stdin: "y", }, UpstreamState: systrun.RemoteStateOK, - ForkState: systrun.RemoteStateNull, ClipboardContent: systrun.ClipboardContentJiraTicket, Expectations: []systrun.ExpectationFunc{systrun.ExpectationCurrentBranchHasPrefix}, } @@ -274,6 +292,29 @@ func TestPR_Synchronized(t *testing.T) { require.NoError(err) } +// TestPR_Synchronized_NoUpstream tests creating a basic PR +func TestPR_Synchronized_NoUpstream(t *testing.T) { + require := require.New(t) + + testConfig := &systrun.TestConfig{ + TestID: strings.ToLower(t.Name()), + GHConfig: getGithubConfig(t), + CommandConfig: &systrun.CommandConfig{ + Command: "pr", + }, + UpstreamState: systrun.RemoteStateOK, + ForkState: systrun.RemoteStateNull, + SyncState: systrun.SyncStateSynchronized, + ClipboardContent: systrun.ClipboardContentGithubIssue, + NeedCollaboration: true, + Expectations: []systrun.ExpectationFunc{systrun.ExpectationPRCreated}, + } + + sysTest := systrun.New(t, testConfig) + err := sysTest.Run() + require.NoError(err) +} + // TestPR_FromJiraTicket tests creating a PR on Jira based branch func TestPR_FromJiraTicket(t *testing.T) { require := require.New(t) @@ -319,6 +360,28 @@ func TestPR_ForkChanged(t *testing.T) { require.Error(err) } +func TestPR_UpstreamChanged_NoFork(t *testing.T) { + require := require.New(t) + + testConfig := &systrun.TestConfig{ + TestID: strings.ToLower(t.Name()), + GHConfig: getGithubConfig(t), + CommandConfig: &systrun.CommandConfig{ + Command: "pr", + }, + UpstreamState: systrun.RemoteStateOK, + ForkState: systrun.RemoteStateNull, + SyncState: systrun.SyncStateForkChanged, + ClipboardContent: systrun.ClipboardContentGithubIssue, + NeedCollaboration: true, + Expectations: []systrun.ExpectationFunc{systrun.ExpectationPRCreated}, + } + + sysTest := systrun.New(t, testConfig) + err := sysTest.Run() + require.Error(err) +} + func TestPR_NoUpstream(t *testing.T) { require := require.New(t) @@ -391,6 +454,7 @@ func TestUpload(t *testing.T) { require.NoError(err) } +// TestDevD_DevBranch_NoRT_NoPR - develop branch exists, no remote tracking branch, no pull request func TestDevD_DevBranch_NoRT_NoPR(t *testing.T) { require := require.New(t) @@ -402,7 +466,7 @@ func TestDevD_DevBranch_NoRT_NoPR(t *testing.T) { Args: []string{"-d"}, Stdin: "y", }, - ForkState: systrun.RemoteStateOK, + UpstreamState: systrun.RemoteStateOK, BranchState: &systrun.BranchState{ DevBranchExists: true, }, @@ -429,7 +493,7 @@ func TestDevD_DevBranch_RT_NoPR(t *testing.T) { Args: []string{"-d"}, Stdin: "y", }, - ForkState: systrun.RemoteStateOK, + UpstreamState: systrun.RemoteStateOK, BranchState: &systrun.BranchState{ DevBranchExists: true, DevBranchHasRtBranch: true, @@ -747,7 +811,6 @@ func TestLargeFileHook(t *testing.T) { GHConfig: getGithubConfig(t), CommandConfig: &systrun.CommandConfig{ Command: "dev", - Args: []string{"--no-fork"}, Stdin: "y", }, UpstreamState: systrun.RemoteStateOK, @@ -766,6 +829,55 @@ func TestLargeFileHook(t *testing.T) { require.NoError(err) } +// TestPR_FromJiraTicket_NoUpstream tests creating a PR in single remote mode +// with automatic workflow detection +func TestPR_FromJiraTicket_NoUpstream(t *testing.T) { + require := require.New(t) + + testConfig := &systrun.TestConfig{ + TestID: strings.ToLower(t.Name()), + GHConfig: getGithubConfig(t), + CommandConfig: &systrun.CommandConfig{ + Command: "pr", + }, + ClipboardContent: systrun.ClipboardContentJiraTicket, + UpstreamState: systrun.RemoteStateOK, + ForkState: systrun.RemoteStateNull, + SyncState: systrun.SyncStateSynchronized, + NeedCollaboration: true, + Expectations: []systrun.ExpectationFunc{systrun.ExpectationPRCreated}, + } + + sysTest := systrun.New(t, testConfig) + err := sysTest.Run() + require.NoError(err) +} + +// TestDev_CustomName_NoUpstream tests creating a dev branch in single remote mode +// with automatic workflow detection +func TestDev_CustomName_NoUpstream(t *testing.T) { + require := require.New(t) + + testConfig := &systrun.TestConfig{ + TestID: strings.ToLower(t.Name()), + GHConfig: getGithubConfig(t), + CommandConfig: &systrun.CommandConfig{ + Command: "dev", + Stdin: "y", + }, + ClipboardContent: systrun.ClipboardContentCustom, + UpstreamState: systrun.RemoteStateOK, + Expectations: []systrun.ExpectationFunc{ + systrun.ExpectationCustomBranchIsCurrentBranch, + systrun.ExpectationLargeFileHooksInstalled, + }, + } + + sysTest := systrun.New(t, testConfig) + err := sysTest.Run() + require.NoError(err) +} + // getGithubConfig retrieves GitHub credentials from environment variables // and skips the test if any credentials are missing func getGithubConfig(t *testing.T) systrun.GithubConfig { diff --git a/version b/version index 412fbc5..ef8b092 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.79.0-SNAPSHOT \ No newline at end of file +1.77.0 \ No newline at end of file