1
+ # /*
2
+ # * EJERCICIO:
3
+ # * ¡Me voy de viaje al GitHub Universe 2024 de San Francisco!
4
+ # *
5
+ # * Desarrolla un CLI (Command Line Interface) que permita
6
+ # * interactuar con Git y GitHub de manera real desde terminal.
7
+ # *
8
+ # * El programa debe permitir las siguientes opciones:
9
+ # * 1. Establecer el directorio de trabajo
10
+ # * 2. Crear un nuevo repositorio
11
+ # * 3. Crear una nueva rama
12
+ # * 4. Cambiar de rama
13
+ # * 5. Mostrar ficheros pendientes de hacer commit
14
+ # * 6. Hacer commit (junto con un add de todos los ficheros)
15
+ # * 7. Mostrar el historial de commits
16
+ # * 8. Eliminar rama
17
+ # * 9. Establecer repositorio remoto
18
+ # * 10. Hacer pull
19
+ # * 11. Hacer push
20
+ # * 12. Salir
21
+ # *
22
+ # * Puedes intentar controlar los diferentes errores.
23
+ # */
24
+
25
+ import os
26
+ import subprocess
27
+ import http .client
28
+ from urllib .parse import urlparse
29
+
30
+
31
+ class Directory :
32
+ __PATH_DIRECTORY = None
33
+
34
+ def __init__ (self ) -> None :
35
+ self .__PATH_DIRECTORY = os .getcwd ()
36
+
37
+ def select_folder (self ) -> None :
38
+ """Selecciona una ruta válida"""
39
+ while self .__PATH_DIRECTORY is None :
40
+ route = input ("Introduce una ruta o presiona 0 para usar la carpeta actual:\n " )
41
+ self .check_folder (route )
42
+
43
+ def check_folder (self , route ) -> bool :
44
+ """Establece una carpeta por defecto si no se ha seleccionado ninguna."""
45
+ if route == '0' :
46
+ self .__PATH_DIRECTORY = os .getcwd ()
47
+ print ("Hemos asignado la carpeta por defecto." )
48
+ return True
49
+ elif os .path .isdir (route ):
50
+ self .__PATH_DIRECTORY = route
51
+ print (f"Nueva ruta establecida: { self .__PATH_DIRECTORY } " )
52
+ return True
53
+ else :
54
+ print ("La ruta no es válida." )
55
+ return False
56
+
57
+ def clear_path (self ) -> None :
58
+ self .__PATH_DIRECTORY = None
59
+
60
+ def get_path (self ) -> str :
61
+ return self .__PATH_DIRECTORY if self .__PATH_DIRECTORY else "No se ha establecido una ruta."
62
+
63
+
64
+ class CLIControl :
65
+ def __init__ (self ):
66
+ self .__DIRECTORY = Directory ()
67
+ self .__SUBPROCESS = subprocess
68
+ self .__CURRENT_BRANCH = None
69
+
70
+ def is_git_initialized (self ) -> bool :
71
+ """Verificar si .git existe en el directorio actual"""
72
+ directory_path = self .__DIRECTORY .get_path ()
73
+
74
+ # Asegurarse de que la ruta esté configurada
75
+ if not directory_path or directory_path == "Aún no se ha establecido una ruta." :
76
+ print ("No se ha establecido un directorio de trabajo válido." )
77
+ return False
78
+
79
+ git_path = os .path .join (directory_path , '.git' )
80
+
81
+ # Debug: Imprimir la ruta donde se está buscando la carpeta .git
82
+ print (f"Verificando si existe .git en la ruta: { git_path } " )
83
+
84
+ if os .path .isdir (git_path ):
85
+ return True
86
+ else :
87
+ print ("Git no está inicializado en este directorio. Utiliza un directorio con un repositorio Git." )
88
+ return False
89
+
90
+
91
+ def create_repository (self ):
92
+ """Crear un repositorio Git en el directorio actual"""
93
+ if not self .is_git_initialized ():
94
+ resultado = self .__SUBPROCESS .run (
95
+ ["git" , "init" ],
96
+ capture_output = True ,
97
+ text = True ,
98
+ cwd = self .__DIRECTORY .get_path ()
99
+ )
100
+ print (resultado .stdout )
101
+ else :
102
+ print ("Ya hay un repositorio Git en el directorio." )
103
+
104
+ def create_branch (self , branch_name ):
105
+ """Crear una nueva rama"""
106
+ if self .is_git_initialized ():
107
+ resultado = self .__SUBPROCESS .run (
108
+ ["git" , "branch" , branch_name ],
109
+ capture_output = True ,
110
+ text = True ,
111
+ cwd = self .__DIRECTORY .get_path ()
112
+ )
113
+ # Verificar si la creación de la rama fue exitosa
114
+ if resultado .returncode == 0 :
115
+ print (f"La rama '{ branch_name } ' ha sido creada con éxito." )
116
+ else :
117
+ print ("Error al crear la rama:" )
118
+ print (resultado .stderr )
119
+
120
+ def select_folder_CLI (self ):
121
+ """Limpiar todo y volver a pedir carpeta"""
122
+ self .__DIRECTORY .clear_path ()
123
+ self .__DIRECTORY .select_folder ()
124
+ self .__CURRENT_BRANCH = None
125
+
126
+ def status (self ):
127
+ """Mostrar archivos pendientes de commit"""
128
+ if self .is_git_initialized ():
129
+ resultado = self .__SUBPROCESS .run (
130
+ ["git" , "status" ],
131
+ capture_output = True ,
132
+ text = True ,
133
+ cwd = self .__DIRECTORY .get_path ()
134
+ )
135
+ print (resultado .stdout )
136
+
137
+ def commit_git (self , msg = 'Commit desde CLI' ):
138
+ """Agregar y hacer commit de todos los cambios"""
139
+ if self .is_git_initialized ():
140
+ self .__SUBPROCESS .run (["git" , "add" , "." ], cwd = self .__DIRECTORY .get_path ())
141
+ resultado = self .__SUBPROCESS .run (
142
+ ["git" , "commit" , "-m" , msg ],
143
+ capture_output = True ,
144
+ text = True ,
145
+ cwd = self .__DIRECTORY .get_path ()
146
+ )
147
+ print (resultado .stdout )
148
+
149
+ def log (self ):
150
+ """Mostrar historial de commits"""
151
+ if self .is_git_initialized ():
152
+ resultado = self .__SUBPROCESS .run (
153
+ ["git" , "log" , "--oneline" ],
154
+ capture_output = True ,
155
+ text = True ,
156
+ cwd = self .__DIRECTORY .get_path ()
157
+ )
158
+ print (resultado .stdout )
159
+
160
+ def delete_branch (self ):
161
+ """Eliminar la rama actual"""
162
+ if self .is_git_initialized ():
163
+ # Por si acaso llamamos a la función para seleccionar rama para no eliminar la rama sin querer.
164
+ self .select_remote_or_local_branch ()
165
+ if self .__CURRENT_BRANCH :
166
+ resultado = self .__SUBPROCESS .run (
167
+ ["git" , "branch" , "-d" , self .__CURRENT_BRANCH ],
168
+ capture_output = True ,
169
+ text = True ,
170
+ cwd = self .__DIRECTORY .get_path ()
171
+ )
172
+ print (resultado .stdout )
173
+ self .__CURRENT_BRANCH = None
174
+ else :
175
+ print ("Selecciona una rama primero." )
176
+
177
+ def add_remote (self , url_remote ):
178
+ """Establecer un repositorio remoto"""
179
+ if self .is_git_initialized ():
180
+ resultado = self .__SUBPROCESS .run (
181
+ ["git" , "remote" , "add" , "origin" , url_remote ],
182
+ capture_output = True ,
183
+ text = True ,
184
+ cwd = self .__DIRECTORY .get_path ()
185
+ )
186
+ print (resultado .stdout )
187
+
188
+ def select_remote_or_local_branch (self ):
189
+ """Función para seleccionar una rama local o remota"""
190
+ if self .is_git_initialized ():
191
+ resultado = self .__SUBPROCESS .run (
192
+ ["git" , "branch" , "-a" ],
193
+ capture_output = True ,
194
+ text = True ,
195
+ cwd = self .__DIRECTORY .get_path ()
196
+ )
197
+
198
+ # Obtiene la lista de ramas y elimina líneas en blanco
199
+ branches = [branch .strip () for branch in resultado .stdout .splitlines () if branch .strip ()]
200
+
201
+ # Muestra las opciones al usuario de forma numerada
202
+ print ("Selecciona la rama que deseas utilizar:" )
203
+ for idx , branch in enumerate (branches , 1 ):
204
+ print (f"{ idx } . { branch } " )
205
+
206
+ # Verifica la elección del usuario
207
+ while True :
208
+ try :
209
+ choice = int (input ("Introduce el número de la rama: " ))
210
+ if 1 <= choice <= len (branches ):
211
+ selected_branch = branches [choice - 1 ].replace ("*" , "" ).strip ()
212
+ self .__CURRENT_BRANCH = selected_branch
213
+ print (f"Has seleccionado la rama: { self .__CURRENT_BRANCH } " )
214
+ break
215
+ else :
216
+ print ("Número fuera de rango. Por favor, selecciona un número válido." )
217
+ except ValueError :
218
+ print ("Entrada no válida. Por favor, introduce un número." )
219
+
220
+ def check_url_github (self , url ) -> bool :
221
+ """Comprueba si la URL devuelve un estado 200 o un estado 301 usando http.client."""
222
+ parsed_url = urlparse (url )
223
+ connection = http .client .HTTPConnection (parsed_url .netloc )
224
+ try :
225
+ connection .request ("GET" , parsed_url .path or "/" )
226
+ response = connection .getresponse ()
227
+
228
+ # Verificar el estado
229
+ if response .status == 200 or response .status == 301 :
230
+ print (f"Conexión exitosa a { url } (Estado 200)." )
231
+ return True
232
+ else :
233
+ print (f"Error: estado { response .status } en { url } ." )
234
+ return False
235
+ except Exception as e :
236
+ print (f"Hubo un problema con la solicitud: { e } " )
237
+ return False
238
+ finally :
239
+ connection .close ()
240
+
241
+ def git_pull (self ):
242
+ """Hacer pull de la rama actual"""
243
+ if self .is_git_initialized ():
244
+ resultado = self .__SUBPROCESS .run (
245
+ ["git" , "pull" ],
246
+ capture_output = True ,
247
+ text = True ,
248
+ cwd = self .__DIRECTORY .get_path ()
249
+ )
250
+ print (resultado .stdout )
251
+
252
+ def git_push (self ):
253
+ """Hacer push de la rama actual"""
254
+ if self .is_git_initialized ():
255
+ # Asegúrate de que haya una rama seleccionada
256
+ if self .__CURRENT_BRANCH :
257
+ print (f"Intentando hacer push a la rama '{ self .__CURRENT_BRANCH } ' en el remoto 'origin'..." )
258
+
259
+ # Comprueba si el remoto 'origin' está configurado
260
+ remote_check = self .__SUBPROCESS .run (
261
+ ["git" , "remote" , "-v" ],
262
+ capture_output = True ,
263
+ text = True ,
264
+ cwd = self .__DIRECTORY .get_path ()
265
+ )
266
+ if 'origin' not in remote_check .stdout :
267
+ print ("El remoto 'origin' no está configurado. Por favor, agrega un repositorio remoto." )
268
+ return
269
+
270
+ # Realiza el push
271
+ resultado = self .__SUBPROCESS .run (
272
+ ["git" , "push" , "-u" , "origin" , self .__CURRENT_BRANCH ],
273
+ capture_output = True ,
274
+ text = True ,
275
+ cwd = self .__DIRECTORY .get_path ()
276
+ )
277
+
278
+ # Verifica la salida y los errores
279
+ if resultado .returncode == 0 :
280
+ print ("Push exitoso:" )
281
+ print (resultado .stdout )
282
+ else :
283
+ print ("Error al hacer push:" )
284
+ print (resultado .stderr )
285
+ else :
286
+ print ("Selecciona una rama primero." )
287
+
288
+
289
+ def menu (self ):
290
+ """Menú interactivo"""
291
+ while True :
292
+ print ("\n Selecciona una opción:" )
293
+ print ("1. Establecer directorio de trabajo" )
294
+ print ("2. Crear repositorio" )
295
+ print ("3. Crear rama" )
296
+ print ("4. Cambiar de rama" )
297
+ print ("5. Mostrar ficheros pendientes de commit" )
298
+ print ("6. Hacer commit" )
299
+ print ("7. Mostrar historial de commits" )
300
+ print ("8. Eliminar rama" )
301
+ print ("9. Establecer repositorio remoto" )
302
+ print ("10. Hacer pull" )
303
+ print ("11. Hacer push" )
304
+ print ("12. Salir" )
305
+
306
+ opcion = input ("Opción: " )
307
+ match opcion :
308
+ case '1' :
309
+ self .select_folder_CLI ()
310
+ case '2' :
311
+ self .create_repository ()
312
+ case '3' :
313
+ branch_name = input ("Nombre de la nueva rama: " )
314
+ self .create_branch (branch_name )
315
+ case '4' :
316
+ self .select_remote_or_local_branch ()
317
+ case '5' :
318
+ self .status ()
319
+ case '6' :
320
+ msg = input ("Mensaje de commit: " )
321
+ self .commit_git (msg )
322
+ case '7' :
323
+ self .log ()
324
+ case '8' :
325
+ self .delete_branch ()
326
+ case '9' :
327
+ url = input ("URL del repositorio remoto: " )
328
+ if self .check_url_github (url ):
329
+ self .add_remote (url )
330
+ else :
331
+ print ("URL no válida. No se ha establecido el remoto." )
332
+ case '10' :
333
+ self .git_pull ()
334
+ case '11' :
335
+ self .git_push ()
336
+ case '12' :
337
+ print ("Saliendo..." )
338
+ break
339
+ case _:
340
+ print ("Opción no válida." )
341
+
342
+ def main ():
343
+ cli = CLIControl ()
344
+ cli .menu ()
345
+
346
+ if __name__ == '__main__' :
347
+ main ()
0 commit comments