Skip to content

Commit f7fcde9

Browse files
committed
phase 1 completed
1 parent 1585d02 commit f7fcde9

13 files changed

Lines changed: 443 additions & 13 deletions

File tree

docs/TROUBLESHOOTING.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Fix:
1212
2. Choose `Memory cache` or `Persistent store`
1313
3. Preload the current GitHub token into Git
1414

15+
Git Genius now repeats this guidance automatically after failed HTTPS pushes.
16+
1517
If the issue continues:
1618

1719
- run `Tools -> Doctor`
@@ -63,6 +65,20 @@ Fix:
6365

6466
Git Genius can initialize the directory as a Git repo during setup.
6567

68+
## First push does not happen
69+
70+
Possible causes:
71+
72+
- the repo still has no commit
73+
- there are no files to include in the first commit
74+
- the branch exists locally but has never been published
75+
76+
Fix:
77+
78+
1. Create at least one file if the repo is empty
79+
2. Run `Daily Git Operations -> Push changes`
80+
3. If auth fails, configure `Tools -> Git Auth / Credential Helper`
81+
6682
## GitHub repo checks fail
6783

6884
Possible causes:

docs/WORKFLOWS.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,21 @@ Use this when the current directory is a new project or when the repo has not be
1717
6. Optionally create the GitHub repository
1818
7. Optionally do the first commit and push
1919

20+
Brand-new repos are now called out directly in the context panel so the first run is harder to miss.
21+
2022
## Recommended Daily Flow
2123

2224
1. Review the context panel
23-
2. Run `Daily Git Operations -> Git status`
24-
3. Run `Smart Pull` if you may be behind the remote and have local edits
25-
4. Edit files outside Git Genius
26-
5. Run `Push changes`
25+
2. Check the ahead/behind line when it is shown
26+
3. Run `Daily Git Operations -> Git status`
27+
4. Run `Smart Pull` if you may be behind the remote and have local edits
28+
5. Edit files outside Git Genius
29+
6. Run `Push changes`
2730

2831
Why this order:
2932

3033
- `Status` helps you inspect what changed
34+
- the context panel can show whether the current branch is ahead of or behind the fetched remote ref
3135
- `Smart Pull` reduces pull failures caused by local edits
3236
- `Push changes` stages, commits, and pushes in one guided step
3337

genius

7.62 MB
Binary file not shown.

git-genius

4.39 KB
Binary file not shown.

internal/config/config_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,33 @@ func TestSaveSkipsRepoConfigOutsideGitRepo(t *testing.T) {
8888
t.Fatalf("expected state to keep active workdir, got %v", recent)
8989
}
9090
}
91+
92+
func TestRecentWorkDirsReordersWhenSwitchingProjects(t *testing.T) {
93+
home := t.TempDir()
94+
repoA := t.TempDir()
95+
repoB := t.TempDir()
96+
97+
if err := os.MkdirAll(filepath.Join(repoA, ".git"), 0o755); err != nil {
98+
t.Fatalf("mkdir repoA .git: %v", err)
99+
}
100+
if err := os.MkdirAll(filepath.Join(repoB, ".git"), 0o755); err != nil {
101+
t.Fatalf("mkdir repoB .git: %v", err)
102+
}
103+
104+
t.Setenv("HOME", home)
105+
106+
Save(Config{WorkDir: repoA})
107+
Save(Config{WorkDir: repoB})
108+
Save(Config{WorkDir: repoA})
109+
110+
recent := RecentWorkDirs()
111+
if len(recent) < 2 {
112+
t.Fatalf("expected at least two recent directories, got %v", recent)
113+
}
114+
if recent[0] != repoA || recent[1] != repoB {
115+
t.Fatalf("unexpected recent workdir order: %v", recent)
116+
}
117+
if got := preferredWorkDir(); got != repoA {
118+
t.Fatalf("preferredWorkDir = %q want %q", got, repoA)
119+
}
120+
}

internal/gitops/git.go

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package gitops
22

33
import (
4+
"fmt"
45
"strings"
56

67
"git-genius/internal/config"
@@ -66,13 +67,14 @@ func Status() bool {
6667
}
6768

6869
func Push(msg string) bool {
69-
7070
if !system.EnsureGitRepo() {
7171
return false
7272
}
7373

7474
ensureSafeDirectory()
7575
cfg := config.Load()
76+
stateBefore := InspectRepoState()
77+
createdCommit := false
7678

7779
// ---------- FIRST COMMIT ----------
7880
if !hasAnyCommit() {
@@ -95,10 +97,25 @@ func Push(msg string) bool {
9597
return false
9698
}
9799

100+
stagedNames, err := system.GitOutput("diff", "--cached", "--name-only")
101+
if err != nil {
102+
ui.Error("Failed to inspect staged files")
103+
return false
104+
}
105+
if stagedNames == "" {
106+
ui.Warn("This repository has no files to commit yet")
107+
ui.Info("Create at least one file, then run Push changes again")
108+
if cfg.Remote != "" {
109+
ui.Info("Your remote can stay configured now; the first push just needs a real commit")
110+
}
111+
return false
112+
}
113+
98114
if err := system.RunGit("commit", "-m", msg); err != nil {
99115
ui.Error("Initial commit failed")
100116
return false
101117
}
118+
createdCommit = true
102119
} else if isWorkingTreeDirty() {
103120
// ---------- NORMAL COMMIT ----------
104121
if msg == "" {
@@ -116,6 +133,7 @@ func Push(msg string) bool {
116133
ui.Error("Commit failed")
117134
return false
118135
}
136+
createdCommit = true
119137
} else {
120138
ui.Info("No local file changes detected")
121139
ui.Info("Attempting to push any existing local commits")
@@ -144,17 +162,22 @@ func Push(msg string) bool {
144162
return false
145163
}
146164

165+
if stateBefore.NeedsFirstPush || (!createdCommit && stateBefore.HasCommits && !stateBefore.RemoteTrackingSeen) {
166+
ui.Info("This looks like the first push for " + cfg.Remote + "/" + branch)
167+
}
168+
147169
// ---------- PUSH ----------
148-
if err := system.RunGitWithRemote(cfg.Remote, "push", "-u", cfg.Remote, branch); err != nil {
170+
stderr, err := system.RunGitWithRemoteBuffered(cfg.Remote, "push", "-u", cfg.Remote, branch)
171+
if err != nil {
149172
ui.Error("Push failed")
150-
if !system.HasGitCredentialHelper() {
151-
ui.Info("Run Tools -> Git Auth / Credential Helper to reduce repeated HTTPS auth prompts")
152-
}
173+
printPushFailureSummary(cfg.Remote, branch, stderr)
153174
ui.Info("Run Doctor if the problem persists")
154175
return false
155176
}
156177

157-
ui.Success("Changes pushed successfully")
178+
cfg.FirstPushDone = true
179+
config.Save(cfg)
180+
printPushSuccessSummary(cfg.Remote, branch, stateBefore, createdCommit)
158181
return true
159182
}
160183

@@ -194,13 +217,17 @@ func Pull() bool {
194217
}
195218
}
196219

220+
stateBefore := InspectRepoState()
221+
headBefore, _ := system.GitOutput("rev-parse", "HEAD")
222+
197223
if err := system.RunGitWithRemote(cfg.Remote, "pull", cfg.Remote, branch); err != nil {
198224
ui.Error("Pull failed")
199225
ui.Info("Try Smart Pull or run Doctor for more guidance")
200226
return false
201227
}
202228

203-
ui.Success("Pull completed")
229+
headAfter, _ := system.GitOutput("rev-parse", "HEAD")
230+
printPullSuccessSummary(cfg.Remote, branch, stateBefore, headBefore != "" && headBefore != headAfter)
204231
return true
205232
}
206233

@@ -226,3 +253,68 @@ func Fetch() bool {
226253
ui.Success("Fetched all remotes")
227254
return true
228255
}
256+
257+
func printPushSuccessSummary(remote, branch string, before RepoState, createdCommit bool) {
258+
after := InspectRepoState()
259+
260+
ui.Header("Push Summary")
261+
ui.Success("Remote : " + remote)
262+
ui.Success("Branch : " + branch)
263+
264+
switch {
265+
case !before.HasCommits && createdCommit:
266+
ui.Success("Created the first commit and published the branch")
267+
case before.NeedsFirstPush:
268+
ui.Success("Published this branch to the remote for the first time")
269+
case before.HasAheadBehind && before.Ahead > 0:
270+
ui.Success(fmt.Sprintf("Published %d local commit(s)", before.Ahead))
271+
case createdCommit:
272+
ui.Success("Created a new commit and pushed it")
273+
default:
274+
ui.Info("Remote branch was already up to date before this push")
275+
}
276+
277+
if after.HasAheadBehind {
278+
ui.Info("Ahead/Behind : " + after.AheadBehindSummary())
279+
}
280+
}
281+
282+
func printPushFailureSummary(remote, branch, stderr string) {
283+
ui.Header("Push Summary")
284+
ui.Error("Remote : " + remote)
285+
ui.Error("Branch : " + branch)
286+
287+
if system.RemoteUsesHTTPS(remote) {
288+
ui.Info("HTTPS remote detected")
289+
ui.Info("Run Tools -> Git Auth / Credential Helper and preload the current GitHub token into Git")
290+
ui.Info("Check that the token still has repo access to this repository")
291+
if !system.HasGitCredentialHelper() {
292+
ui.Info("No Git credential helper is configured yet")
293+
}
294+
}
295+
296+
if strings.Contains(strings.ToLower(stderr), "repository not found") {
297+
ui.Info("Verify the remote URL and GitHub owner/repository in Setup or Create / Link GitHub Repository")
298+
}
299+
}
300+
301+
func printPullSuccessSummary(remote, branch string, before RepoState, headChanged bool) {
302+
after := InspectRepoState()
303+
304+
ui.Header("Pull Summary")
305+
ui.Success("Remote : " + remote)
306+
ui.Success("Branch : " + branch)
307+
308+
switch {
309+
case before.HasAheadBehind && before.Behind > 0:
310+
ui.Success(fmt.Sprintf("Integrated %d remote commit(s)", before.Behind))
311+
case headChanged:
312+
ui.Success("Local branch moved forward")
313+
default:
314+
ui.Info("Local branch was already up to date")
315+
}
316+
317+
if after.HasAheadBehind {
318+
ui.Info("Ahead/Behind : " + after.AheadBehindSummary())
319+
}
320+
}

internal/gitops/repo_state.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package gitops
2+
3+
import (
4+
"fmt"
5+
6+
"git-genius/internal/config"
7+
"git-genius/internal/system"
8+
)
9+
10+
type RepoState struct {
11+
Branch string
12+
Remote string
13+
HasCommits bool
14+
WorkingTreeDirty bool
15+
RemoteConfigured bool
16+
RemoteTrackingSeen bool
17+
Ahead int
18+
Behind int
19+
HasAheadBehind bool
20+
FirstRun bool
21+
NeedsFirstPush bool
22+
}
23+
24+
func InspectRepoState() RepoState {
25+
cfg := config.Load()
26+
27+
state := RepoState{
28+
Branch: CurrentBranch(),
29+
Remote: cfg.Remote,
30+
HasCommits: hasAnyCommit(),
31+
WorkingTreeDirty: isWorkingTreeDirty(),
32+
FirstRun: !config.HasProjectConfig(cfg.GetWorkDir()) && !config.HasHistoryForWorkDir(cfg.GetWorkDir()),
33+
}
34+
35+
if state.Branch == "-" || state.Branch == "" {
36+
state.Branch = cfg.Branch
37+
}
38+
39+
if state.Remote != "" && system.HasRemote(state.Remote) {
40+
state.RemoteConfigured = true
41+
}
42+
43+
if state.Branch != "" && state.RemoteConfigured && system.HasRemoteTrackingBranch(state.Remote, state.Branch) {
44+
state.RemoteTrackingSeen = true
45+
ahead, behind, err := system.AheadBehind(state.Branch, state.Remote+"/"+state.Branch)
46+
if err == nil {
47+
state.Ahead = ahead
48+
state.Behind = behind
49+
state.HasAheadBehind = true
50+
}
51+
}
52+
53+
state.NeedsFirstPush = state.HasCommits && state.RemoteConfigured && state.Branch != "" && !state.RemoteTrackingSeen
54+
return state
55+
}
56+
57+
func (s RepoState) AheadBehindSummary() string {
58+
if !s.HasAheadBehind {
59+
return ""
60+
}
61+
return fmt.Sprintf("%d ahead / %d behind", s.Ahead, s.Behind)
62+
}

0 commit comments

Comments
 (0)