estrategia de github para mantener una versión de archivo privada

11

Soy un profesor que escribe problemas de codificación para estudiantes. Lo que quiero hacer es dar a los estudiantes el código repetitivo con marcadores de posición para las funciones que los estudiantes deben completar. Les daré a los estudiantes acceso a un repositorio privado de Github para clonar esto.

Sin embargo, también quiero una versión de la base de código, completa con soluciones de muestra. Obviamente no quiero que los estudiantes tengan acceso a la solución (hasta que termine la tarea).

He pensado en las sucursales, pero AFAIK, no puedo mantener una sucursal privada.

Tal vez podría bifurcar el proyecto en otro repositorio privado, pero no estoy seguro de cómo podría mantener los proyectos en snyc (aparte del archivo que contiene la solución).

¿Hay un flujo de trabajo para esta situación?

Conocer
fuente
1
No lo creo. Pero lo que hace frío: interfaces delcare para todos los métodos que se implementarán. En su repositorio público-alumno, cree clases implementando esas interfaces con los cuerpos de método vacíos. Mantenga las soluciones en un repositorio privado separado. Esto no resuelve completamente su problema de sincronización, pero lo reduce al alcance de las tareas.
marstato
¿Ha buscado utilizar la API de Github para controlar el acceso a las sucursales?

Respuestas:

8

Lo que podría ser bastante factible:

  • Crear 2 repositorios: alumno y profesor.
  • Clonarlos en su máquina (se puede hacer con el cliente Github)
  • Trabajas solo en profesor , nunca tocas estudiante.

Entonces su estructura de directorio es 2 clones de repositorios de git:

  • / estudiante (con una carpeta .git)
  • / profesor (con una carpeta .git)

Pones marcadores alrededor del código "privado" en los comentarios para tu idioma, por ejemplo, javascript a continuación. Los marcadores indican dónde comienza y termina el código privado.

function sum(a, b) {
  // -----------------------START
  return a + b; // so this is what you expect from the student
  // -----------------------END
}

console.log(sum(1,1)); // I expect 2 as a result of your homework

Luego haga un script simple en su máquina local:

files.forEach((fileContent, fileName) => {
  let newFileContent = '';
  let public = true;
  fileContent.forEach((line) => {
    switch(line) {
      case '// -----------------------START':
        public = false;
        break;
      case '// -----------------------END':
        public = true;
        break;
      default:
        if(public) {
          newFileContent = newFileContent + line + "\n";
        }
    }
  });
  writeFile('../student/' + fileName, newFileContent);
});

Tomará todos sus archivos y copiará el contenido a / student (sobrescritura) sin las partes privadas marcadas del código. Si lo desea, puede insertar líneas vacías allí, pero eso puede dar una pista sobre qué tipo de solución espera.

Es un código de ejemplo no probado, por lo que es probable que deba realizar alguna depuración.

Ahora lo único que tiene que hacer es comprometerse e ingresar al repositorio de estudiantes cuando esté satisfecho con el resultado. Eso se puede hacer con un solo clic cuando se usa el cliente GitHub (para que pueda hacer una revisión visual rápida) o simplemente hacerlo manualmente en la línea de comandos.

El repositorio de estudiantes es un repositorio de salida solo, por lo que siempre estará actualizado, para los estudiantes está claro qué ha cambiado al observar los commits (porque solo muestran cambios) y es fácil de manejar.

Un paso más allá sería crear un git commit-hook que ejecute automáticamente su script.

Editar: veo que has realizado una edición en tu publicación:

Obviamente no quiero que los estudiantes tengan acceso a la solución (hasta que termine la tarea).

Sospecho que está claro, pero para completar: simplemente elimine las etiquetas alrededor del ejercicio terminado y publicará la respuesta de la misma manera que lo haría para las actualizaciones normales de los ejercicios.

Luc Franken
fuente
esperaba poder hacer esto con un poco de git vudú, sin embargo, su solución es muy práctica.
Ken
@Ken también estaba pensando en eso, pero es una herramienta un poco incorrecta para el trabajo equivocado. Git se fusiona, actualiza, etc., pero en general no es la idea seleccionar código. Es bueno para mantener su base de código consistente en múltiples máquinas. Por eso pensé en otra solución. Lo que también me gusta de este enfoque es que minimiza el riesgo y el trabajo, por lo que es fácil mantenerse al día. Y, al final, debe escribir su mensaje de compromiso al repositorio de estudiantes a mano de todos modos para dar un buen ejemplo a sus estudiantes;)
Luc Franken
Para ayudar a git a realizar un seguimiento de los cambios, puede hacer una rama de estudiante en el repositorio de su maestro, ejecute el script al fusionar (o fusionar a mano eliminando cualquier cosa entre los marcadores). Luego sincronice la rama del estudiante localmente y empújela al repositorio del estudiante en lugar del origen del maestro. De esta manera, git estaría en mejor forma para rastrear los cambios y reenviar correctamente el historial de un repositorio al siguiente. Lo mejor de ambos mundos. No lo he intentado, pero no veo por qué no funcionaría.
Newtopian
1
Me gusta esto, excepto por la idea de eliminar las etiquetas de inicio y fin. Es mejor destrozarlos agregando la palabra "solución".
candied_orange
@CandiedOrange también es agradable, de acuerdo en eso. La solución también permitiría un formato diferente y diferencia claramente entre las etiquetas olvidadas y la decisión real de que la solución debería publicarse. @ newtopian: estaba pensando en eso pero no vi suficientes ventajas. También decidí ver la salida del alumno como un tipo de código totalmente diferente. No es la fuente real, así que decidí no hacerlo. Lo que haría con ramas en el repositorio de maestros es, por ejemplo: Trabajar en las tareas para el próximo semestre. Cuando esté listo, los fusionará para dominarlos y luego ejecutará el script.
Luc Franken
6

Tú podrías

  • Cree un repositorio público de GitHub donde confirme el código repetitivo
  • Bifurca este repositorio como un repositorio privado de GitHub
  • Resolver las asignaciones en el repositorio bifurcado
  • Fusionar cada solución en el repositorio público cuando finalice la asignación

Así es como implementaría este flujo de trabajo:

  • Crea un repositorio público assignmentsalojado en GitHub. Agregue el código repetitivo para las asignaciones. Por ejemplo, para cada tarea, introduce un nuevo subdirectorio que contiene el código repetitivo de la tarea.
  • Crea un nuevo repositorio privadoassignments-solved en GitHub. Clone el assignmentsrepositorio en su máquina y empújelo al assignments-solved repositorio (esencialmente bifurque su propio repositorio como una copia privada): git clone https://github.com/[user]/assignments assignments-solved cd assignments-solved git remote set-url origin https://github.com/[user]/assignments-solved git push origin master git push --all
  • Agregue el assignments-solvedrepositorio como remoto al assignmentsrepositorio: cd assignments # change to the assignments repo on your machine git remote add solutions https://github.com/[user]/assignments-solved
  • Implemente cada tarea en el assignments-solvedrepositorio. Asegúrese de que cada confirmación contenga solo los cambios de una tarea.
  • Es posible que desee crear una solvedrama en el assignmentsrepositorio, para que las asignaciones originales no se modifiquen: cd assignments # change to the assignments repo on your machine git branch -b solutions git push -u origin
  • Cuando desee publicar una solución en assignments, busque el solvedcontrol remoto y cherry-picklas confirmaciones que contienen las soluciones. cd assignments # change to the assignments repo on your machine git checkout solved git fetch solutions git cherry-pick [commithash] Donde [commithash]contiene el compromiso de su solución.

También puede implementar el flujo de trabajo implementando cada asignación en una rama separada del assignments-solvedrepositorio y luego creando una solicitud de extracción en el assignmentsrepositorio. Pero no estoy seguro de si esto funcionará en GitHub, ya que el assignments-solvedrepositorio no es una bifurcación real .

Gaste
fuente
He utilizado con éxito un método similar para separar una prueba de programación de las respuestas enviadas. En mi caso, las soluciones enviadas se agregan a las ramas individuales de un clon privado y nunca se fusionan con el repositorio público. Tiene el beneficio adicional de permitirme ver qué versión de la prueba ha resuelto cada candidato, a medida que evoluciona con el tiempo.
axl
0

Solo puedo proponerle una utilidad destinada a .gitignorecodificar y cifrar archivos en su repositorio. El flujo de trabajo es un poco difícil de usar, pero hace que las contrapartes cifradas de sus archivos estén disponibles en la copia de trabajo junto con otros archivos no secretos, lo que permite rastrearlos por git como de costumbre.

#!/bin/bash

set -o errexit
set -o pipefail
set -o nounset

version=1
OPTIND=1
verbose=0
mode="add"
recurse=()
files=()

while getopts ":vaslr:" opt
do
    case "$opt" in
        \?) echo "error: invalid option: -$OPTARG" >&2 ; exit 1
            ;;
        :)  echo "error: option -$OPTARG requires an argument" >&2 ; exit 1
            ;;
        v)  let "verbose++" ; echo "verbosity increased"
            ;;
        a)  mode="add"
            ;;
        s)  mode="save"
            ;;
        l)  mode="load"
            ;;
        r)  recurse+=("$OPTARG")
            ;;
    esac
done
shift $((OPTIND-1))
if [[ "${#recurse[@]}" != 0 ]] 
then
    for pattern in "${recurse[@]}" 
    do
        while IFS= read -d $'\0' -r file
        do
            files+=("$file")
        done < <(find . -name "$pattern" -type f -print0)
    done
else
    files=("$@")
fi

[[ "${#files[@]}" != 0 ]] || { echo "list of files to process is empty" >&2 ; exit 1 ; }

if [[ $mode == "add" ]]
then
    for file in "${files[@]}"
    do
        [[ -e $file ]] && cp "$file" "${file}.bak" || touch "$file"
        sshare_file="${file}.sshare"
        [[ -e $sshare_file ]] || { echo "$version" > "$sshare_file" ; git add --intent-to-add "$sshare_file" ; echo "$file" >> .gitignore ; echo "${file}.bak" >> .gitignore ; git add .gitignore ; }
    done
    exit 0
fi
tmp_dir=`mktemp --tmpdir -d sshare.XXXX`
read -r -s -p "enter password to $mode tracked files:" sshare_password && echo ;
for file in "${files[@]}"
do
    [[ ! -e $file ]] && touch "$file" || cp "$file" "${file}.bak"
    sshare_file="${file}.sshare"
    [[ -r $sshare_file ]] || { echo "warning: can't read file '$sshare_file' (file '$file' skipped)" >&2 ; continue ; }
    file_version=$(head -1 "$sshare_file")
    [[ "$file_version" == $version ]] || { echo "warning: version '$file_version' of '$sshare_file' file differs from version '$version' of script (file '$file' skipped)" >&2 ; continue ; }
    tmp_file="$tmp_dir/$file"
    mkdir -p "$(dirname "$tmp_file")"
    > "$tmp_file"
    line_number=0
    while IFS= read -r line
    do
        let "line_number++" || :
        [[ -n $line ]] || { echo "warning: empty line encountered at #$line_number in file '$sshare_file' (ignored)" >&2 ; continue ; }
        echo "$line" | openssl enc -d -A -base64 -aes256 -k "$sshare_password" | gunzip --to-stdout --force | patch "$tmp_file" --normal --quiet
    done < <(tail --lines=+2 "$sshare_file")
    if [[ $mode == "load" ]]
    then
        cp -f "$tmp_file" . || { echo "warning: can't write to file '$file' (file '$file' skipped)" >&2 ; continue ; }
    elif [[ $mode == "save" ]]
    then
        chunk=$(diff "$tmp_file" "$file" || :)
        [[ -n $chunk ]] || { echo "nothing to comit since last edit for file '$file'" ; continue ; }
        [[ -w $sshare_file ]] || { echo "warning: can't update sshare database '$sshare_file' (file '$file' skipped)" ; continue ; }
        echo "$chunk" | gzip --stdout | openssl enc -e -A -base64 -aes256 -k "$sshare_password" >> "$sshare_file"
        echo >> "$sshare_file"
        echo "changes encrypted for file '$file'"
    fi
done

Para crear un archivo secreto con el a.txttipo de nombre de archivo sshare -a a.txt. Utilidad crear archivo a.txty archivo agregado a .gitignore. Luego crea una contraparte de "base de datos" encriptada a.txt.sshareagregando .sshareextensión al nombre de archivo.

Entonces puedes llenar a.txtcon algo de texto. Para guardar su estado justo antes de git commitescribir sshare -s a.txt, la utilidad le pedirá una contraseña para cifrar el nuevo estado del archivo a.txt. Luego, la utilidad que utiliza esta contraseña agrega diferencias cifradas entre el estado anterior y actual del archivo a.txtal final del a.txt.ssharearchivo.

Después de buscar / extraer el repositorio con archivos cifrados, debe ejecutar la sshareutilidad para cada archivo usando la -ltecla ("cargar"). En este caso, la utilidad descifra *.ssharearchivos a archivos de texto no rastreados por git en la copia de trabajo.

Puede usar diferentes contraseñas para cada archivo secreto.

La utilidad permite que git rastree los cambios de manera eficiente (la diferencia de .ssharearchivos es simplemente una línea).

Tomilov Anatoliy
fuente