44 "errors"
55 "fmt"
66 "os"
7- "os/exec"
87 "strings"
98
9+ gitundo "github.com/amberpixels/git-undo"
1010 "github.com/amberpixels/git-undo/internal/git-undo/logging"
1111 "github.com/amberpixels/git-undo/internal/git-undo/undoer"
1212 "github.com/amberpixels/git-undo/internal/githelpers"
@@ -23,23 +23,16 @@ type GitHelper interface {
2323
2424// App represents the main app.
2525type App struct {
26- verbose bool
27- dryRun bool
28-
29- git GitHelper
26+ verbose bool
27+ dryRun bool
28+ buildVersion string
3029
31- lgr * logging. Logger
30+ dir string
3231
3332 // isInternalCall is a hack, so app works OK even without GIT_UNDO_INTERNAL_HOOK env variable.
3433 // So, we can run tests without setting env vars (but just via setting this flag).
34+ // Note: here it's read-only flag, and it's only set in export_test.go
3535 isInternalCall bool
36-
37- // Embedded scripts for self-management
38- updateScript string
39- uninstallScript string
40-
41- // Build-time version info
42- buildVersion string
4336}
4437
4538// IsInternalCall checks if the hook is being called internally (either via test or zsh script).
@@ -53,20 +46,12 @@ func (a *App) IsInternalCall() bool {
5346}
5447
5548// New creates a new App instance.
56- func New (repoDir string , version string , verbose , dryRun bool ) * App {
57- gitHelper := githelpers .NewGitHelper (repoDir )
58- gitDir , err := gitHelper .GetRepoGitDir ()
59- if err != nil {
60- fmt .Fprintf (os .Stderr , redColor + "git-undo ❌: " + grayColor + "failed to get repo git dir: %v" + resetColor + "\n " , err )
61- return nil
62- }
63-
49+ func New (version string , verbose , dryRun bool ) * App {
6450 return & App {
51+ dir : "." ,
6552 buildVersion : version ,
6653 verbose : verbose ,
6754 dryRun : dryRun ,
68- git : gitHelper ,
69- lgr : logging .NewLogger (gitDir , gitHelper ),
7055 }
7156}
7257
@@ -84,77 +69,63 @@ func (a *App) logDebugf(format string, args ...interface{}) {
8469 return
8570 }
8671
87- fmt .Fprintf (os .Stderr , yellowColor + "git-undo ⚙️: " + grayColor + format + resetColor + "\n " , args ... )
72+ _ , _ = fmt .Fprintf (os .Stderr , yellowColor + "git-undo ⚙️: " + grayColor + format + resetColor + "\n " , args ... )
8873}
8974
9075// logWarnf writes error messages to stderr.
9176func (a * App ) logWarnf (format string , args ... interface {}) {
92- fmt .Fprintf (os .Stderr , redColor + "git-undo ❌: " + grayColor + format + resetColor + "\n " , args ... )
77+ _ , _ = fmt .Fprintf (os .Stderr , redColor + "git-undo ❌: " + grayColor + format + resetColor + "\n " , args ... )
9378}
9479
9580// Run executes the main app logic.
96- func (a * App ) Run (args []string ) error {
81+ func (a * App ) Run (args []string ) ( err error ) {
9782 a .logDebugf ("called in verbose mode" )
9883
99- // Handle version commands first (these don't require git repo)
100- if len (args ) >= 1 {
101- firstArg := args [0 ]
102-
103- // Handle version commands: version, --version, self-version
104- if firstArg == "version" || firstArg == "--version" || firstArg == "self-version" {
105- return a .cmdVersion ()
84+ defer func () {
85+ if recovered := recover (); recovered != nil {
86+ a .logDebugf ("git-undo panic recovery: %v" , recovered )
87+ err = fmt .Errorf ("unexpected internal failure" )
10688 }
89+ }()
10790
108- // Handle "self version"
109- //nolint:goconst // we're fine with this for now
110- if len (args ) >= 2 && firstArg == "self" && args [1 ] == "version" {
111- return a .cmdVersion ()
112- }
113- }
91+ selfCtrl := NewSelfController (a .buildVersion , a .verbose ).
92+ AddScript (CommandUpdate , gitundo .GetUpdateScript ()).
93+ AddScript (CommandUninstall , gitundo .GetUninstallScript ())
11494
115- // Handle self-management commands (these don't require git repo)
116- if len (args ) >= 2 {
117- firstArg := args [0 ]
118- secondArg := args [1 ]
95+ if err := selfCtrl .HandleSelfCommand (args ); err == nil {
96+ return nil
97+ } else if ! errors .Is (err , ErrNotSelfCommand ) {
98+ return err
99+ }
119100
120- // Handle "self update" or "self-update"
121- if (firstArg == "self" && secondArg == "update" ) || firstArg == "self-update" {
122- return a .cmdSelfUpdate ()
123- }
101+ g := githelpers .NewGitHelper (a .dir )
124102
125- // Handle "self uninstall" or "self-uninstall"
126- if (firstArg == "self" && secondArg == "uninstall" ) || firstArg == "self-uninstall" {
127- return a .cmdSelfUninstall ()
128- }
129- } else if len (args ) == 1 {
130- // Handle single argument forms
131- if args [0 ] == "self-update" {
132- return a .cmdSelfUpdate ()
133- }
134- if args [0 ] == "self-uninstall" {
135- return a .cmdSelfUninstall ()
136- }
103+ gitDir , err := g .GetRepoGitDir ()
104+ if err != nil {
105+ // Silently return for non-git repos when not using self commands
106+ a .logDebugf ("not in a git repository, ignoring command%v: %s" , args , err )
107+ return nil
137108 }
138109
139- // Ensure we're inside a Git repository for other commands
140- if _ , err := a . git . GetRepoGitDir (); err ! = nil {
141- return err
110+ lgr := logging . NewLogger ( gitDir , g )
111+ if lgr = = nil {
112+ return errors . New ( "failed to create git-undo logger" )
142113 }
143114
144115 // Custom commands are --hook and --log
145116 for _ , arg := range args {
146117 switch {
147118 case strings .HasPrefix (arg , "--hook" ):
148- return a .cmdHook (arg )
119+ return a .cmdHook (lgr , arg )
149120 case arg == "--log" :
150- return a .cmdLog ()
121+ return a .cmdLog (lgr )
151122 }
152123 }
153124
154125 // Check if this is a "git undo undo" command
155126 if len (args ) > 0 && args [0 ] == "undo" {
156127 // Get the last undoed entry (from current reference)
157- lastEntry , err := a . lgr .GetLastEntry ()
128+ lastEntry , err := lgr .GetLastEntry ()
158129 if err != nil {
159130 a .logWarnf ("something wrong with the log: %v" , err )
160131 return nil
@@ -165,7 +136,7 @@ func (a *App) Run(args []string) error {
165136 }
166137
167138 // Unmark the entry in the log
168- if err := a . lgr .ToggleEntry (lastEntry .GetIdentifier ()); err != nil {
139+ if err := lgr .ToggleEntry (lastEntry .GetIdentifier ()); err != nil {
169140 return fmt .Errorf ("failed to unmark command: %w" , err )
170141 }
171142
@@ -180,7 +151,7 @@ func (a *App) Run(args []string) error {
180151 return fmt .Errorf ("invalid last undo-ed cmd[%s]: %w" , lastEntry .Command , validationErr )
181152 }
182153
183- if err := a . git .GitRun (gitCmd .Name , gitCmd .Args ... ); err != nil {
154+ if err := g .GitRun (gitCmd .Name , gitCmd .Args ... ); err != nil {
184155 return fmt .Errorf ("failed to redo command[%s]: %w" , lastEntry .Command , err )
185156 }
186157
@@ -189,7 +160,7 @@ func (a *App) Run(args []string) error {
189160 }
190161
191162 // Get the last git command
192- lastEntry , err := a . lgr .GetLastRegularEntry ()
163+ lastEntry , err := lgr .GetLastRegularEntry ()
193164 if err != nil {
194165 return fmt .Errorf ("failed to get last git command: %w" , err )
195166 }
@@ -201,7 +172,7 @@ func (a *App) Run(args []string) error {
201172 a .logDebugf ("Last git command[%s]: %s" , lastEntry .Ref , yellowColor + lastEntry .Command + resetColor )
202173
203174 // Get the appropriate undoer
204- u := undoer .New (lastEntry .Command , a . git )
175+ u := undoer .New (lastEntry .Command , g )
205176
206177 // Get the undo command
207178 undoCmd , err := u .GetUndoCommand ()
@@ -225,7 +196,7 @@ func (a *App) Run(args []string) error {
225196 }
226197
227198 // Mark the entry as undoed in the log
228- if err := a . lgr .ToggleEntry (lastEntry .GetIdentifier ()); err != nil {
199+ if err := lgr .ToggleEntry (lastEntry .GetIdentifier ()); err != nil {
229200 a .logWarnf ("Failed to mark command as undoed: %v" , err )
230201 }
231202
@@ -238,7 +209,7 @@ func (a *App) Run(args []string) error {
238209 return nil
239210}
240211
241- func (a * App ) cmdHook (hookArg string ) error {
212+ func (a * App ) cmdHook (lgr * logging. Logger , hookArg string ) error {
242213 a .logDebugf ("hook: start" )
243214
244215 if ! a .IsInternalCall () {
@@ -261,7 +232,7 @@ func (a *App) cmdHook(hookArg string) error {
261232 return nil
262233 }
263234
264- if err := a . lgr .LogCommand (hooked ); err != nil {
235+ if err := lgr .LogCommand (hooked ); err != nil {
265236 return fmt .Errorf ("failed to log command: %w" , err )
266237 }
267238
@@ -270,69 +241,6 @@ func (a *App) cmdHook(hookArg string) error {
270241}
271242
272243// cmdLog displays the git-undo command log.
273- func (a * App ) cmdLog () error {
274- return a .lgr .Dump (os .Stdout )
275- }
276-
277- // SetEmbeddedScripts sets the embedded scripts for self-management commands.
278- func SetEmbeddedScripts (app * App , updateScript , uninstallScript string ) {
279- app .updateScript = updateScript
280- app .uninstallScript = uninstallScript
281- }
282-
283- func (a * App ) cmdSelfUpdate () error {
284- a .logDebugf ("Running embedded self-update script..." )
285- return a .runEmbeddedScript (a .updateScript , "update" )
286- }
287-
288- func (a * App ) cmdSelfUninstall () error {
289- a .logDebugf ("Running embedded self-uninstall script..." )
290- return a .runEmbeddedScript (a .uninstallScript , "uninstall" )
291- }
292-
293- // runEmbeddedScript creates a temporary script file and executes it.
294- func (a * App ) runEmbeddedScript (script , name string ) error {
295- if script == "" {
296- return fmt .Errorf ("embedded %s script not available" , name )
297- }
298-
299- // Create temp file with proper extension
300- tmpFile , err := os .CreateTemp ("" , fmt .Sprintf ("git-undo-%s-*.sh" , name ))
301- if err != nil {
302- return fmt .Errorf ("failed to create temp script: %w" , err )
303- }
304- defer func () {
305- // TODO: handle error: log warnings at least
306- _ = tmpFile .Close ()
307- _ = os .Remove (tmpFile .Name ())
308- }()
309-
310- // Write script content
311- if _ , err := tmpFile .WriteString (script ); err != nil {
312- return fmt .Errorf ("failed to write script: %w" , err )
313- }
314-
315- // Close file before making it executable and running it
316- _ = tmpFile .Close ()
317-
318- // Make executable
319- //nolint:gosec // TODO: fix me in future
320- if err := os .Chmod (tmpFile .Name (), 0755 ); err != nil {
321- return fmt .Errorf ("failed to make script executable: %w" , err )
322- }
323-
324- a .logDebugf ("Executing embedded %s script..." , name )
325-
326- // Execute script
327- //nolint:gosec // TODO: fix me in future
328- cmd := exec .Command ("bash" , tmpFile .Name ())
329- cmd .Stdout = os .Stdout
330- cmd .Stderr = os .Stderr
331-
332- return cmd .Run ()
333- }
334-
335- func (a * App ) cmdVersion () error {
336- fmt .Fprintf (os .Stdout , "git-undo %s\n " , a .buildVersion )
337- return nil
244+ func (a * App ) cmdLog (lgr * logging.Logger ) error {
245+ return lgr .Dump (os .Stdout )
338246}
0 commit comments