diff --git a/README.md b/README.md index f8c723c..6a1ab47 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Source: https://stackoverflow.com/a/7497395 echo "" > messages.po find . -type f -iname "*.py" | xgettext -j -f - msgmerge -N locale/XX/LC_MESSAGES/messages.po messages.po > new.po -mv new.po messages.po +mv -f new.po messages.po msgfmt messages.po mv -f messages.mo messages.po locale/XX/LC_MESSAGES/ ``` diff --git a/locale/es/LC_MESSAGES/messages.po b/locale/es/LC_MESSAGES/messages.po index 11e072d..8be432f 100644 --- a/locale/es/LC_MESSAGES/messages.po +++ b/locale/es/LC_MESSAGES/messages.po @@ -5,8 +5,7 @@ msgstr "" #: submit50.py:44 msgid "You have an old version of python. Install version 2.7 or higher." -msgstr "" -"Tienes una versión antigua de python. Instala la versión 2.7 o superior." +msgstr "Tienes una versión antigua de python. Instala la versión 2.7 o superior." #: submit50.py:115 msgid "show commands being executed" @@ -16,122 +15,133 @@ msgstr "mostrar comandos que se están ejecutando" msgid "problem to submit" msgstr "problema para entregar" -#: submit50.py:177 +#: submit50.py:178 msgid "GitHub username: " msgstr "Nombre de usuario de GitHub: " -#: submit50.py:183 +#: submit50.py:186 msgid "GitHub password: " msgstr "Contraseña de GitHub: " -#: submit50.py:217 +#: submit50.py:223 msgid "Invalid username and/or password." -msgstr "Nombre de usuario y / o contraseña es inválido." +msgstr "Nombre de usuario y/o contraseña es inválido." -#: submit50.py:221 +#: submit50.py:227 msgid "Could not authenticate user." msgstr "No se pudo autenticar usuario." -#: submit50.py:268 +#: submit50.py:274 msgid "Could not connect to GitHub." -msgstr "No se pudo conectar a GitHub" +msgstr "No se pudo conectar a GitHub." -#: submit50.py:272 +#: submit50.py:278 msgid "Sorry, something's wrong! Let sysadmins@cs50.harvard.edu know!" msgstr "¡Lo siento, algo está mal! ¡Déjale saber a sysadmins@cs50.harvard.edu!" -#: submit50.py:277 submit50.py:295 +#: submit50.py:283 submit50.py:301 msgid "Submission cancelled." msgstr "La entrega se canceló." -#: submit50.py:391 +#: submit50.py:397 msgid "You don't have git. Install git, then re-run {}!" msgstr "No tienes git. ¡Instala git, y luego vuelve a ejecutar {}!" -#: submit50.py:395 +#: submit50.py:401 msgid "" "You have an old version of git. Install version 2.7 or later, then re-run {}!" msgstr "" +"Tienes una versión antigua de git. ¡Instala la versión 2.7 o superior, y " +"vuelve a ejecutar {}!" -#: submit50.py:409 +#: submit50.py:415 msgid "" "You have an unknown version of submit50. Email sysadmins@cs50.harvard.edu!" msgstr "" "Tienes una versión desconocida de submit50. ¡Escríbele a sysadmins@cs50." "harvard.edu!" -#: submit50.py:413 +#: submit50.py:419 msgid "You have an old version of submit50. Run update50, then re-run {}!" -msgstr "" -"Tienes una versión antigua de submit50. ¡Ejecuta update50, y luego vuelve a " +msgstr "Tienes una versión antigua de submit50. ¡Ejecuta update50, y luego vuelve a " "ejecutar {}!" -#: submit50.py:430 +#: submit50.py:440 msgid "Invalid problem. Did you mean to submit something else?" msgstr "El problema no es válido. ¿Quisiste entregar algo más?" -#: submit50.py:446 +#: submit50.py:456 msgid "You seem to be missing these files:" msgstr "Parece que faltan estos archivos:" -#: submit50.py:449 +#: submit50.py:459 msgid "Ensure you have the required files before submitting." msgstr "Asegúrate de tener los archivos necesarios antes de entregar." -#: submit50.py:452 +#: submit50.py:462 msgid "Authenticating" msgstr "Autenticando" -#: submit50.py:470 +#: submit50.py:480 msgid "Preparing" msgstr "Preparando" -#: submit50.py:477 +#: submit50.py:487 msgid "" "Looks like {} isn't enabled for your account yet. Log into https://cs50.me/ " "in a browser, click \"Authorize application\", and re-run {} here!" msgstr "" +"Parece que {} todavía no está habilitado para tu cuenta. ¡Inicie sesión en " +"https://cs50.me/ en un navegador, haga clic en \"Authorize application" +"\", y vuelve a ejecutar {} aquí!" -#: submit50.py:480 +#: submit50.py:490 msgid "" "Looks like you have the wrong username in ~/.gitconfig or {} isn't yet " "enabled for your account. Double-check ~/.gitconfig and then log into " "https://cs50.me/ in a browser, click \"Authorize application\" if prompted, " "and re-run {} here." msgstr "" -"Parece que tienes el nombre de usuario incorrecto en ~ /.gitconfig o {} aún " +"Parece que tienes el nombre de usuario incorrecto en ~/.gitconfig o {} aún " "no está habilitado para tu cuenta. Vuelve a chequear ~/.gitconfig y luego " -"ingresa en https://cs50.me/ en un navegador, haga clic en \"Autorizar " -"aplicación\" si se te solicita, y vuelve a ejecutar {} aquí." +"ingresa en https://cs50.me/ en un navegador, haga clic en \"Authorize " +"application\" si se te solicita, y vuelve a ejecutar {} aquí." -#: submit50.py:525 +#: submit50.py:538 msgid "" "These files are too large to be submitted:\n" "{}\n" "Remove these files from your directory and then re-run {}!" msgstr "" +"Estos archivos son demasiado grandes para ser entregados:\n" +"{}\n" +"¡Quita estos archivos de tu directorio y luego vuelve a ejecutar: {}!" -#: submit50.py:530 +#: submit50.py:543 msgid "" "These files are too large to be submitted:\n" "{}\n" "Install git-lfs (or remove these files from your directory) and then re-run " "{}!" msgstr "" +"Estos archivos son demasiado grandes para ser entregados:\n" +"{}\n" +"Instala git-lfs (o quita estos archivos de tu directorio) y luego vuelve a ejecutar " +"{}!" -#: submit50.py:541 +#: submit50.py:554 msgid "No files in this directory are expected for submission." msgstr "Ningún archivo en este directorio se espera para la entrega." -#: submit50.py:544 +#: submit50.py:559 msgid "Files that will be submitted:" msgstr "Archivos que se van a entregar:" -#: submit50.py:550 +#: submit50.py:565 msgid "Files that won't be submitted:" msgstr "Archivos que no se van a entregar:" -#: submit50.py:554 +#: submit50.py:571 msgid "" "Keeping in mind the course's policy on academic honesty, are you sure you " "want to submit these files? " @@ -139,34 +149,22 @@ msgstr "" "Teniendo en cuenta la política de honestidad académica del curso, ¿estás " "seguro que quieres enviar estos archivos? " -#: submit50.py:557 +#: submit50.py:577 msgid "No files were submitted." -msgstr "" +msgstr "No se ha entregado ningún archivo." -#: submit50.py:560 +#: submit50.py:581 msgid "Submitting" msgstr "Entregando" -#: submit50.py:568 +#: submit50.py:583 +msgid "Uploading" +msgstr "Subiendo" + +#: submit50.py:592 msgid "Submitted {}! See https://cs50.me/submissions." msgstr "¡Se entregó {}! Puedes ver https://cs50.me/submissions." -#: submit50.py:615 +#: submit50.py:640 msgid "Could not complete two-factor authentication." msgstr "No se pudo completar la autenticación de dos factores." - -#~ msgid "" -#~ "You have an old version of git. Install version 2.7 or later, then re-run " -#~ "submit50!" -#~ msgstr "" -#~ "Tienes una versión antigua de git. ¡Instala la versión 2.7 o superior, y " -#~ "vuelve a ejecutar submit50!" - -#~ msgid "" -#~ "Looks like {} isn't enabled for your account yet. Log into https://cs50." -#~ "me/ in a browser, click \"Authorize application\", and re-run submit50 " -#~ "here!" -#~ msgstr "" -#~ "Parece que {} todavía no está habilitado para tu cuenta. Inicie sesión en " -#~ "https://cs50.me/ en un navegador, haga clic en \"Autorizar la aplicación" -#~ "\", y vuelva a ejecutar submit50 ¡aquí!" diff --git a/setup.py b/setup.py index ba9cb47..27bb0fc 100644 --- a/setup.py +++ b/setup.py @@ -61,5 +61,5 @@ class CustomInstall(install): "console_scripts": ["submit50=submit50:main"] }, url="https://github.com/cs50/submit50", - version="2.4.0" + version="2.4.1" ) diff --git a/submit50.py b/submit50.py old mode 100644 new mode 100755 index 09d6c03..933dc1f --- a/submit50.py +++ b/submit50.py @@ -139,21 +139,22 @@ def authenticate(org): pass authenticate.SOCKET = os.path.join(cache, ORG) - # check cache, then config for credentials - credentials = run("git -c credential.helper='cache --socket {}' credential fill".format(authenticate.SOCKET), - lines=[""]*3, - quiet=True) - run("git credential-cache --socket {} exit".format(authenticate.SOCKET)) - matches = re.search("^username=([^\r]+)\r\npassword=([^\r]+)\r?$", credentials, re.MULTILINE) - if matches: - username = matches.group(1) - password = matches.group(2) + spawn = pexpect.spawn if sys.version_info < (3, 0) else pexpect.spawnu + child = spawn("git -c credential.helper='cache --socket {}' credential fill".format(authenticate.SOCKET)) + child.sendline("") + + if child.expect(["Username:", pexpect.EOF]): + # Credentials are already cached + clear_credentials() + username, password = re.search("username=([^\r]+)\r\npassword=([^\r]+)", child.before, re.MULTILINE).groups() else: + # No cached credentials found try: username = run("git config --global credential.https://github.com/submit50.username") - except: + except Error: username = None password = None + child.close() def rlinput(prompt, prefill=""): """ @@ -174,13 +175,17 @@ def rlinput(prompt, prefill=""): # prompt for username, prefilling if possible while True: progress(False) - username = rlinput(_("GitHub username: "), username).strip() - if username: - break + try: + username = rlinput(_("GitHub username: "), username).strip() + if username: + break + except EOFError: + print() # prompt for password while True: - print(_("GitHub password: "), end="", flush=True) + print(_("GitHub password: "), end="") + sys.stdout.flush() password = str() while True: ch = getch() @@ -190,13 +195,18 @@ def rlinput(prompt, prefill=""): elif ch == "\177": # DEL if len(password) > 0: password = password[:-1] - print("\b \b", end="", flush=True) + print("\b \b", end="") + sys.stdout.flush() elif ch == "\3": # ctrl-c print("^C", end="") os.kill(os.getpid(), signal.SIGINT) + elif ch == "\4": # ctrl-d + print() + break else: password += ch - print("*", end="", flush=True) + print("*", end="") + sys.stdout.flush() if password: break @@ -238,6 +248,16 @@ def rlinput(prompt, prefill=""): authenticate.SOCKET = None +def clear_credentials(): + """Clear git credential cache """ + run("git credential-cache --socket {} exit".format(authenticate.SOCKET)) + # OSX will sometimes store git credentials in the keyring. Try to remove them + try: + run("git credential-osxkeychain erase", lines=["host=github.com", "protocol=https", ""]) + except Error: + pass + + def cprint(text="", color=None, on_color=None, attrs=None, **kwargs): """Colorizes text (and wraps to terminal's width).""" @@ -248,10 +268,6 @@ def cprint(text="", color=None, on_color=None, attrs=None, **kwargs): columns, lines = get_terminal_size() if columns == 0: columns = 80 # because get_terminal_size's default fallback doesn't work in pipes - # only python3 supports "flush" keyword argument - if sys.version_info < (3, 0) and "flush" in kwargs: - del kwargs["flush"] - # print text termcolor.cprint(textwrap.fill(text, columns, drop_whitespace=False), color=color, on_color=on_color, attrs=attrs, **kwargs) @@ -270,25 +286,25 @@ def excepthook(type, value, tb): if run.verbose: traceback.print_exception(type, value, tb) cprint(_("Sorry, something's wrong! Let sysadmins@cs50.harvard.edu know!"), "yellow") - try: - run("git credential-cache --socket {} exit".format(authenticate.SOCKET)) - except Exception: - pass - cprint(_("Submission cancelled."), "red") - + if authenticate.SOCKET: # not set when using SSH + try: + clear_credentials() + except Exception: + pass + cprint(_("Submission cancelled."), "red") sys.excepthook = excepthook def handler(number, frame): """Handle SIGINT.""" - os.system("stty sane") # in case signalled from input_with_prefill + os.system("stty sane 2> {}".format(os.devnull)) # in case signalled from input_with_prefill if progress.progressing: progress(False) else: cprint() try: - run("git credential-cache --socket {} exit".format(authenticate.SOCKET)) + clear_credentials() except Exception: pass teardown() @@ -296,7 +312,7 @@ def handler(number, frame): os._exit(0) -def run(command, cwd=None, env=None, lines=[], password=None, quiet=False): +def run(command, cwd=None, env=None, lines=[], password=None, quiet=False, timeout=None): """Run a command.""" # echo command @@ -310,12 +326,16 @@ def run(command, cwd=None, env=None, lines=[], password=None, quiet=False): "GIT_WORK_TREE": run.GIT_WORK_TREE, "HOME": os.path.expanduser("~") } + if os.getenv("SSH_AGENT_PID"): + env["SSH_AGENT_PID"] = os.getenv("SSH_AGENT_PID") + if os.getenv("SSH_AUTH_SOCK"): + env["SSH_AUTH_SOCK"] = os.getenv("SSH_AUTH_SOCK") # spawn command if sys.version_info < (3, 0): - child = pexpect.spawn(command, cwd=cwd, env=env, ignore_sighup=True, timeout=None) + child = pexpect.spawn(command, cwd=cwd, env=env, ignore_sighup=True, timeout=timeout) else: - child = pexpect.spawnu(command, cwd=cwd, encoding="utf-8", env=env, ignore_sighup=True, timeout=None) + child = pexpect.spawnu(command, cwd=cwd, encoding="utf-8", env=env, ignore_sighup=True, timeout=timeout) # send output of command to stdout only if run with --verbose (and not quieted by caller) if run.verbose and not quiet: @@ -457,13 +477,21 @@ def submit(org, branch): # authenticate user via SSH try: + + # require ssh assert which("ssh") + + # require GitHub username in ~/.gitconfig username, password = run("git config --global credential.https://github.com/submit50.username", quiet=True), None email = "{}@users.noreply.github.com".format(username) repo = "git@github.com:{}/{}.git".format(org, username) - with open(os.devnull, "w") as DEVNULL: - progress(False) - assert subprocess.call(["ssh", "git@github.com"], stderr=DEVNULL) == 1 # successfully authenticated + progress(False) + + # require ssh-agent + child = pexpect.spawn("ssh git@github.com") + i = child.expect(["Enter passphrase for key", pexpect.EOF]) + child.close() + assert i != 0 # authenticate user via HTTPS except: @@ -547,8 +575,10 @@ def submit(org, branch): if len(files) == 0: raise Error(_("No files in this directory are expected for submission.")) + # prompts for submit50 if org == "submit50": - cprint(_("Files that will be submitted:"), "green") + if len(files) == 1: + cprint(_("Files that will be submitted:"), "green") for f in files: cprint("./{}".format(f), "green") @@ -558,13 +588,21 @@ def submit(org, branch): for f in other: cprint("./{}".format(f), "yellow") - answer = input(_("Keeping in mind the course's policy on academic honesty, " - "are you sure you want to submit these files? ")) - if not re.match("^\s*(?:y|yes)\s*$", answer, re.I): + # prompt for honesty + try: + answer = input(_("Keeping in mind the course's policy on academic honesty, " + "are you sure you want to submit these files? ")) + except EOFError: + answer = None + print() + if not answer or not re.match("^\s*(?:y|yes)\s*$", answer, re.I): raise Error(_("No files were submitted.")) # update progress - progress(_("Submitting" if org == "submit50" else "Uploading")) + if org == "submit50": + progress(_("Submitting")) + else: + progress(_("Uploading")) # push branch run("git commit --allow-empty --message='{}'".format(timestamp)) @@ -575,7 +613,6 @@ def submit(org, branch): if org == "submit50": cprint(_("Submitted {}! See https://cs50.me/submissions.").format(branch), "green") - progress(False) return username, commit_hash @@ -610,7 +647,8 @@ def two_factor(org, username, password): requests.post("https://api.github.com/authorizations", auth=(username, password)) while True: - cprint("Authentication code:", end=" ", flush=True) + cprint("Authentication code:", end=" ") + sys.stdout.flush() code = input() if code: break