strategia github per mantenere privata una versione del file

11

Sono un docente che scrive problemi di codifica per gli studenti. Quello che voglio fare è dare agli studenti il codice standard con i segnaposto per le funzioni che gli studenti devono completare. Darò agli studenti l'accesso a un repository privato di github per clonarlo.

Tuttavia, voglio anche una versione del codebase, completa di soluzioni di esempio. Ovviamente non voglio che gli studenti abbiano accesso alla soluzione (fino a quando l'assegnazione è finita).

Ho pensato ai rami, ma AFAIK, non posso mantenere privato un ramo.

Forse potrei inserire il progetto in un altro repository privato, ma non sono sicuro di come mantenere i progetti in snyc (a parte il file che contiene la soluzione).

Esiste un flusso di lavoro per questa situazione?

    
posta Ken 14.09.2016 - 13:36
fonte

3 risposte

8

Cosa potrebbe essere abbastanza fattibile:

  • Crea 2 repository: studente e insegnante.
  • Clona sul tuo computer (può essere fatto con il client Github)
  • Tu lavori solo nell'insegnante , non toccare mai lo studente.

Quindi la struttura della directory è 2 repo git clonato:

  • / studente (con una cartella .git)
  • / insegnante (con una cartella .git)

Metti dei marcatori attorno al codice "privato" nei commenti per la tua lingua, ad esempio javascript di seguito. I marcatori indicano dove inizia e finisce il codice privato.

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

Quindi crea un semplice script sul tuo computer 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);
});

Sarà: prendi tutti i tuoi file e copia i contenuti su / student (sovrascrittura) senza le parti contrassegnate private del codice. Se vuoi, puoi inserire delle linee vuote ma potrebbe dare un suggerimento sul tipo di soluzione che ti aspetti.

È un codice di esempio non testato, quindi è probabile che tu debba fare un po 'di debug.

Ora la sola cosa che devi fare è commettere e inserire nel repository degli studenti quando sei soddisfatto dell'output. Ciò può essere fatto con un clic quando si utilizza il client GitHub (in modo da poter eseguire una rapida revisione visiva) o semplicemente farlo manualmente sulla riga di comando.

Il repository per studenti è un repository di output solo per essere sempre aggiornato, agli studenti è chiaro cosa è cambiato guardando i commit (perché mostrano solo cambiamenti) ed è semplice da gestire.

Un ulteriore passo sarebbe quello di creare un git-hook-hook che esegue automaticamente lo script.

Modifica: vedi che hai apportato una modifica al tuo post:

Obviously I don't want the students to have access to the solution (until the assignment is over).

Ho il sospetto che sia chiaro ma completo: basta rimuovere i tag attorno all'esercizio finito e la risposta verrà pubblicata nello stesso modo in cui si farebbe per i normali aggiornamenti degli esercizi.

    
risposta data 14.09.2016 - 15:23
fonte
6

Potresti

  • Crea un repostory pubblico GitHub se ti impegni a scrivere il codice standard
  • Forchetta questo repository come repostory GitHub privato
  • Risolvi i compiti nel repository biforcato
  • Unisci ogni soluzione nel repository pubblico quando l'assegnazione viene eseguita

Ecco come implementerei questo flusso di lavoro:

  • Crea un repostory pubblico assignments ospitato su GitHub. Aggiungi il codice standard per i compiti. Per esempio. per ogni incarico si introduce una nuova sottodirectory contenente il codice boilerplate del file assegnazione.
  • Crea un nuovo repository privato assignments-solved su GitHub. Clona il assignments pronti contro termine sul tuo computer e spingerlo su assignments-solved repo (in pratica si biforca come repository privato): %codice%
  • Aggiungi il repository 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 come remoto al repository assignments-solved : %codice%
  • Implementa ciascun compito nel repository assignments . Assicurarsi che ogni commit contiene solo modifiche da un incarico.
  • Potresti voler creare un ramo cd assignments # change to the assignments repo on your machine git remote add solutions https://github.com/[user]/assignments-solved nel repository assignments-solved , in modo che i compiti originali non vengano modificati: %codice%
  • Quando vuoi pubblicare una soluzione in solved , scarica il file assignments remoto e cd assignments # change to the assignments repo on your machine git branch -b solutions git push -u origin i commit contenenti le soluzioni. %codice% Dove assignments contiene il commit della soluzione.

Potresti anche essere in grado di implementare il flusso di lavoro implementando ciascun compito in un ramo separato del repository solved e quindi creando una richiesta di pull nel repository cherry-pick . Ma non sono sicuro che funzionerà in GitHub, poiché cd assignments # change to the assignments repo on your machine git checkout solved git fetch solutions git cherry-pick [commithash] repo non è un fork reale .

    
risposta data 16.09.2016 - 09:48
fonte
0

Posso solo proporti un programma destinato a .gitignore -ing e crittografare i file nel tuo repository. Il flusso di lavoro è leggermente difficile da utilizzare, ma rende disponibili le copie crittografate dei file nella copia di lavoro insieme ad altri file non segreti, il che consente di rintracciarli con git come al solito.

#!/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 $'
#!/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 $'%pre%' -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
' -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

Per creare un file segreto con nomefile a.txt tipo sshare -a a.txt . Utility crea file a.txt e file aggiunti a .gitignore . Quindi crea la controparte "database" crittografata a.txt.sshare aggiungendo .sshare estensione al nome file.

Quindi puoi riempire a.txt con del testo. Per salvare il suo stato subito prima di git commit digitare sshare -s a.txt , quindi l'utilità richiede la password per crittografare il nuovo stato del file a.txt . Quindi, l'utilizzo di questa password aggiunge la differenza crittografata tra lo stato precedente e corrente del file a.txt alla fine del file a.txt.sshare .

Dopo il repository fetch / pull con i file crittografati, dovresti eseguire sshare utility per ogni file usando la chiave -l ("load"). In questo caso l'utilità decodifica *.sshare file in file di testo non tracciati da git nella copia di lavoro.

Puoi utilizzare password diverse per ogni file segreto.

L'utility consente a git di tenere traccia delle modifiche in modo efficiente ( diff di .sshare file è semplicemente una riga).

    
risposta data 20.09.2016 - 20:44
fonte

Leggi altre domande sui tag