stratégie github pour garder une version du fichier privée

11

Je suis un conférencier qui écrit des problèmes de codage pour les étudiants. Ce que je veux faire, c'est donner aux étudiants le code passe-partout avec des espaces réservés pour les fonctions que les étudiants doivent remplir. Je vais donner aux étudiants l'accès à un dépôt github privé pour cloner cela.

Cependant, je veux également une version de la base de code, avec des exemples de solutions. Évidemment, je ne veux pas que les étudiants aient accès à la solution (jusqu'à ce que le travail soit terminé).

J'ai pensé aux succursales, mais AFAIK, je ne peux pas garder une succursale privée.

Je pourrais peut-être bifurquer le projet dans un autre dépôt privé, mais je ne sais pas comment je pourrais conserver les projets dans snyc (à l'exception du fichier qui contient la solution).

Existe-t-il un workflow pour cette situation?

Ken
la source
1
Je ne pense pas. Mais ce que vous faites à froid: les interfaces delcare pour toutes les méthodes à mettre en œuvre. Dans votre référentiel étudiant-public, créez des classes implémentant ces interfaces avec les corps de méthode vides. Conservez les solutions dans un référentiel privé séparé. Cela ne résout pas entièrement votre problème de synchronisation, mais il le réduit à l'étendue des tâches.
marstato
avez-vous envisagé d'utiliser l'API github pour contrôler l'accès aux branches?

Réponses:

8

Ce qui pourrait être tout à fait faisable:

  • Créez 2 référentiels: étudiant et enseignant.
  • Clonez-les sur votre machine (cela peut être fait avec le client Github)
  • Vous travaillez uniquement en enseignant , ne touchez jamais l'élève.

Donc, votre structure de répertoire est composée de 2 repo git clonés:

  • / étudiant (avec un dossier .git)
  • / enseignant (avec un dossier .git)

Vous mettez des marqueurs autour du code "privé" dans les commentaires de votre langue, exemple javascript ci-dessous. Les marqueurs indiquent où commence et se termine le code privé.

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

Faites ensuite un script simple sur votre machine locale:

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);
});

Il: prendra tous vos fichiers et copiera le contenu dans / student (écrasement) sans les parties marquées privées du code. Si vous le souhaitez, vous pouvez y insérer des lignes vides, mais cela pourrait vous donner une idée du type de solution que vous attendez.

Il s'agit d'un exemple de code non testé, vous devrez donc probablement effectuer un débogage.

Maintenant, la seule chose que vous avez à faire est de valider et d'introduire le référentiel des étudiants lorsque vous êtes satisfait de la sortie. Cela peut être fait en un clic lors de l'utilisation du client GitHub (vous pouvez donc faire un examen visuel rapide) ou simplement le faire manuellement en ligne de commande.

Le repo des étudiants est uniquement un référentiel de sortie, il restera donc toujours à jour, il est clair pour les étudiants ce qui a changé en regardant les commits (car ils ne montrent que les changements) et c'est simple à gérer.

Une étape supplémentaire serait de créer un crochet de validation git qui exécute automatiquement votre script.

Modifier: voyez que vous avez modifié votre message:

Évidemment, je ne veux pas que les étudiants aient accès à la solution (jusqu'à ce que le travail soit terminé).

Je suppose que c'est clair mais pour être complet: il suffit de supprimer les balises autour de l'exercice terminé pour publier la réponse de la même manière que pour les mises à jour normales des exercices.

Luc Franken
la source
espérais pouvoir le faire avec du git vaudou, mais votre solution est très pratique.
Ken
@Ken y pensait aussi mais c'est un peu mauvais outil pour le mauvais travail. Git fusionne, met à jour etc. mais en général ce n'est pas l'idée de sélectionner du code. C'est bon pour garder votre base de code cohérente sur plusieurs machines. C'est pourquoi j'ai pensé à une autre solution. Ce que j'aime aussi dans cette approche, c'est qu'elle minimise les risques et le travail, il est donc facile de la suivre. Et, à la fin, vous devriez de toute façon écrire votre message de validation au repo des étudiants à la main pour donner un bon exemple à vos étudiants;)
Luc Franken
Pour aider git à garder une trace des modifications que vous pourriez créer dans votre repo de professeur, exécutez le script lors de la fusion (ou fusionnez à la main en supprimant tout ce qui se trouve entre les marqueurs). Synchronisez ensuite la branche étudiante localement et poussez-la vers le référentiel étudiant au lieu de l'origine de l'enseignant. De cette façon, git serait en meilleure forme pour suivre les changements et avoir un historique correctement transmis d'un dépôt à l'autre. Le meilleur des deux mondes. Je n'ai pas essayé cet esprit, mais je ne vois pas pourquoi cela ne fonctionnerait pas.
Newtopian
1
J'aime ça, sauf pour l'idée de supprimer les balises de début et de fin. Mieux vaut les réduire en ajoutant le mot «solution».
candied_orange
@CandiedOrange qui est aussi une bonne idée, d'accord là-dessus. La solution permettrait également une mise en forme différente et différencie clairement les balises oubliées et la décision réelle de publier la solution. @ newtopian: J'y pensais mais je n'ai pas vu assez d'avantages. J'ai également décidé de voir la sortie des étudiants comme un type de code totalement différent. Ce n'est pas la vraie source alors j'ai décidé de ne pas le faire. Ce que je ferais avec les branches dans le repo des enseignants, c'est par exemple: Travailler sur les tâches du prochain semestre. Lorsque vous êtes prêt, vous les fusionnez pour les maîtriser, puis exécutez le script.
Luc Franken
6

Vous pourriez

  • Créez un répertoire GitHub public où vous avez validé le code passe-partout
  • Forkez ce référentiel en tant que dépôt GitHub privé
  • Résoudre les affectations dans le référentiel forké
  • Fusionner chaque solution dans le référentiel public lorsque l'affectation est terminée

Voici comment j'implémenterais ce workflow:

  • Créez un répertoire public assignmentshébergé sur GitHub. Ajoutez le code passe-partout pour les affectations. Par exemple, pour chaque affectation, vous introduisez un nouveau sous-répertoire contenant le code passe-partout de l'affectation.
  • Créez un nouveau référentiel privéassignments-solved sur GitHub. Clonez le assignmentsréférentiel sur votre machine et poussez-le vers le assignments-solved référentiel (essentiellement créez votre propre référentiel en tant que copie privée): 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
  • Ajoutez le assignments-solvedréférentiel en tant que télécommande au assignmentsréférentiel: cd assignments # change to the assignments repo on your machine git remote add solutions https://github.com/[user]/assignments-solved
  • Implémentez chaque affectation dans le assignments-solvedréférentiel. Assurez-vous que chaque validation ne contient que les modifications d'une affectation.
  • Vous souhaiterez peut-être créer une solvedbranche dans le assignmentsréférentiel, afin que les affectations d'origine ne soient pas modifiées: cd assignments # change to the assignments repo on your machine git branch -b solutions git push -u origin
  • Lorsque vous souhaitez publier une solution dans le assignments, récupérez la solvedtélécommande et cherry-pickles validations contenant les solutions. cd assignments # change to the assignments repo on your machine git checkout solved git fetch solutions git cherry-pick [commithash] [commithash]contient le commit de votre solution.

Vous pouvez également implémenter le workflow en implémentant chaque affectation dans une branche distincte du assignments-solvedréférentiel, puis en créant une pull-request dans le assignmentsréférentiel. Mais je ne sais pas si cela fonctionnera dans GitHub, car le assignments-solveddépôt n'est pas un vrai fork.

Gaste
la source
J'ai utilisé avec succès une méthode similaire pour séparer un test de programmation des réponses soumises. Dans mon cas, les solutions soumises sont ajoutées aux branches individuelles d'un clone privé et ne sont jamais fusionnées avec le référentiel public. Il a l'avantage supplémentaire de me permettre de voir quelle version du test chaque candidat a résolu, car elle évolue au fil du temps.
axl
0

Je peux juste vous proposer un utilitaire destiné à .gitignore-ing et crypter des fichiers dans votre référentiel. Le flux de travail est légèrement difficile à utiliser, mais il rend les homologues chiffrés de vos fichiers disponibles sur la copie de travail avec d'autres fichiers non secrets, ce qui permet de les suivre par git comme d'habitude.

#!/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

Pour créer un fichier secret avec le a.txttype de nom de fichier sshare -a a.txt. Utilitaire de création de fichier a.txtet fichier ajouté à .gitignore. Ensuite, il crée l'homologue "base de données" chiffré a.txt.sshareen ajoutant une .sshareextension au nom de fichier.

Ensuite, vous pouvez remplir a.txtavec du texte. Pour enregistrer son état juste avant le git committype sshare -s a.txt, l'utilitaire vous invite à saisir un mot de passe pour crypter le nouvel état du fichier a.txt. Ensuite, l'utilisation de ce mot de passe ajoute un diff chiffré entre l'état précédent et l'état actuel du fichier a.txtà la fin du a.txt.ssharefichier.

Après avoir récupéré / extrait le référentiel avec des fichiers chiffrés, vous devez exécuter l' sshareutilitaire pour chaque fichier à l'aide de la -lclé ("load"). Dans ce cas, l'utilitaire décrypte les *.ssharefichiers en fichiers texte non suivis par git dans la copie de travail.

Vous pouvez utiliser des mots de passe différents pour chaque fichier secret.

L'utilitaire permet à git de suivre efficacement les modifications (la différence des .ssharefichiers est simplement une ligne).

Tomilov Anatoliy
la source