Github-Strategie, um eine Version der Datei privat zu halten

11

Ich bin ein Dozent, der Codierungsprobleme für Studenten schreibt. Ich möchte den Schülern den Code mit den Platzhaltern für die Funktionen geben, die die Schüler ausführen sollen. Ich werde den Schülern Zugang zu einem privaten Github-Repo geben, um dies zu klonen.

Ich möchte jedoch auch eine Version der Codebasis mit Beispiellösungen. Natürlich möchte ich nicht, dass die Schüler Zugriff auf die Lösung haben (bis die Aufgabe beendet ist).

Ich habe über Filialen nachgedacht, aber AFAIK, ich kann nicht eine Filiale privat halten.

Vielleicht könnte ich das Projekt in ein anderes privates Repo aufteilen, bin mir aber nicht sicher, wie ich die Projekte in snyc halten könnte (abgesehen von der Datei, die die Lösung enthält).

Gibt es einen Workflow für diese Situation?

Ken
quelle
1
Das glaube ich nicht. Aber was Sie kalt machen: Delcare-Schnittstellen für alle Methoden, die implementiert werden sollen. Erstellen Sie in Ihrem studentisch-öffentlichen Repo Klassen, die diese Schnittstellen mit den leeren Methodenkörpern implementieren. Pflegen Sie die Lösungen in einem separaten privaten Repo. Dies löst Ihr Synchronisationsproblem nicht vollständig, reduziert es jedoch auf den Umfang der Aufgaben.
Marstato
Haben Sie versucht, die Github-API zu verwenden, um den Zugriff auf Zweige zu steuern?

Antworten:

8

Was könnte durchaus machbar sein:

  • Erstellen Sie 2 Repositorys: Schüler und Lehrer.
  • Klonen Sie sie auf Ihren Computer (kann mit dem Github-Client durchgeführt werden)
  • Sie arbeiten nur als Lehrer , berühren niemals Schüler.

Ihre Verzeichnisstruktur besteht also aus 2 geklonten Git-Repos:

  • / student (mit einem .git-Ordner)
  • / Lehrer (mit einem .git Ordner)

Sie setzen Markierungen um den "privaten" Code in Kommentaren für Ihre Sprache, Beispiel Javascript unten. Die Markierungen geben an, wo der private Code beginnt und endet.

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

Erstellen Sie dann ein einfaches Skript auf Ihrem lokalen Computer:

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

Es wird: alle Ihre Dateien nehmen und den Inhalt ohne die privat markierten Teile des Codes nach / student kopieren (überschreiben). Wenn Sie möchten, können Sie dort leere Zeilen einfügen. Dies kann jedoch einen Hinweis darauf geben, welche Art von Lösung Sie erwarten.

Es ist ungetesteter Beispielcode, daher müssen Sie wahrscheinlich etwas debuggen.

Jetzt müssen Sie nur noch das Schüler-Repository festschreiben und verschieben, wenn Sie mit der Ausgabe zufrieden sind. Dies kann mit einem Klick erfolgen, wenn Sie den GitHub-Client verwenden (damit Sie eine schnelle visuelle Überprüfung durchführen können) oder einfach manuell über die Befehlszeile.

Das Schüler-Repo ist nur ein Ausgabe-Repository, damit es immer auf dem neuesten Stand bleibt. Den Schülern wird durch Betrachten der Commits klar, was sich geändert hat (da sie nur Änderungen anzeigen), und es ist einfach zu handhaben.

Ein weiterer Schritt wäre, einen Git-Commit-Hook zu erstellen, der Ihr Skript automatisch ausführt.

Bearbeiten: Wir sehen, dass Sie Ihren Beitrag bearbeitet haben:

Natürlich möchte ich nicht, dass die Schüler Zugriff auf die Lösung haben (bis die Aufgabe beendet ist).

Ich vermute, es ist klar, aber um vollständig zu sein: Entfernen Sie einfach die Tags um die fertige Übung, um die Antwort auf die gleiche Weise zu veröffentlichen, wie Sie es für normale Aktualisierungen der Übungen tun würden.

Luc Franken
quelle
Ich hatte gehofft, ich könnte dies mit etwas Git Voodoo tun, aber Ihre Lösung ist sehr praktisch.
Ken
@ Ken hat auch darüber nachgedacht, aber es ist ein bisschen falsches Werkzeug für den falschen Job. Git führt zwar zusammen, aktualisiert usw., aber im Allgemeinen ist es nicht die Idee, Code auszuwählen. Es ist gut darin, Ihre Codebasis auf mehreren Computern konsistent zu halten. Deshalb habe ich mir eine andere Lösung ausgedacht. Was ich auch an diesem Ansatz mag, ist, dass er Risiko und Arbeit minimiert, so dass es einfach ist, mit ihm Schritt zu halten. Und am Ende sollten Sie Ihre Commit-Nachricht trotzdem von Hand an das Schüler-Repo schreiben, um Ihren Schülern ein gutes Beispiel zu geben;)
Luc Franken
Um git dabei zu unterstützen, Änderungen im Auge zu behalten, können Sie einen Schülerzweig in Ihrem Lehrer-Repo erstellen, das Skript beim Zusammenführen ausführen (oder von Hand zusammenführen, um etwas zwischen den Markierungen zu entfernen). Synchronisieren Sie dann den Schülerzweig lokal und übertragen Sie ihn auf das Schüler-Repo anstelle des Lehrerursprungs. Auf diese Weise wäre git besser in der Lage, Änderungen zu verfolgen und den Verlauf ordnungsgemäß von einem Repo zum nächsten weiterzuleiten. Beste aus beiden Welten. Ich habe es nicht versucht, aber ich verstehe nicht, warum es nicht funktionieren würde.
Newtopian
1
Ich mag das bis auf die Idee, die Start-End-Tags zu entfernen. Besser, sie zu zerfleischen, indem man das Wort "Lösung" hinzufügt.
candied_orange
@CandiedOrange das ist auch eine schöne, stimme dem zu. Die Lösung würde auch eine andere Formatierung ermöglichen und klar zwischen vergessenen Tags und der tatsächlichen Entscheidung, dass die Lösung veröffentlicht werden soll, unterscheiden. @ newtopian: Ich habe darüber nachgedacht, aber ich habe nicht genug Vorteile gesehen. Außerdem habe ich beschlossen, die Schülerausgabe als eine völlig andere Art von Code zu betrachten. Es ist nicht die wahre Quelle, also habe ich mich dagegen entschieden. Was ich mit Zweigen im Lehrer-Repo machen würde, ist zum Beispiel: Arbeiten Sie an den Aufgaben für das nächste Semester. Wenn Sie bereit sind, führen Sie sie zum Master zusammen und führen das Skript aus.
Luc Franken
6

Du könntest

  • Erstellen Sie ein öffentliches GitHub-Repostory, in dem Sie den Boilerplate-Code festschreiben
  • Fork dieses Repository als privates GitHub-Repostory
  • Lösen Sie die Zuweisungen im gegabelten Repository
  • Führen Sie jede Lösung nach Abschluss der Zuweisung in das öffentliche Repository ein

So würde ich diesen Workflow implementieren:

  • Erstellen Sie ein öffentliches Repostory, assignmentsdas auf GitHub gehostet wird. Fügen Sie den Boilerplate-Code für die Zuweisungen hinzu. Zum Beispiel führen Sie für jede Aufgabe ein neues Unterverzeichnis ein, das den Boilerplate-Code der Aufgabe enthält.
  • Erstellen Sie ein neues privates Repository assignments-solvedauf GitHub. Klonen Sie das assignmentsRepo auf Ihrem Computer und verschieben Sie es in das assignments-solved Repo (im Wesentlichen teilen Sie Ihr eigenes Repository als private Kopie auf): 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
  • Fügen Sie das assignments-solvedRepo als Remote zum assignmentsRepo hinzu: cd assignments # change to the assignments repo on your machine git remote add solutions https://github.com/[user]/assignments-solved
  • Implementieren Sie jede Zuordnung im assignments-solvedRepository. Stellen Sie sicher, dass jedes Commit nur Änderungen aus einer Zuweisung enthält.
  • Möglicherweise möchten Sie eine solvedVerzweigung im assignmentsRepo erstellen , damit die ursprünglichen Zuordnungen nicht geändert werden: cd assignments # change to the assignments repo on your machine git branch -b solutions git push -u origin
  • Wenn Sie eine Lösung in veröffentlichen möchten assignments, rufen Sie die solvedFernbedienung und cherry-pickdie Commits mit den Lösungen ab. cd assignments # change to the assignments repo on your machine git checkout solved git fetch solutions git cherry-pick [commithash] Wo [commithash]enthält das Commit Ihrer Lösung.

Möglicherweise können Sie den Workflow auch implementieren, indem Sie jede Zuweisung in einem separaten Zweig des assignments-solvedRepos implementieren und dann eine Pull-Anforderung im assignmentsRepo erstellen . Ich bin mir aber nicht sicher, ob dies in GitHub funktionieren wird, da das assignments-solvedRepo keine echte Gabel ist.

Gaste
quelle
Ich habe erfolgreich eine ähnliche Methode verwendet, um einen Programmiertest von den eingereichten Antworten zu trennen. In meinem Fall werden die eingereichten Lösungen einzelnen Zweigen eines privaten Klons hinzugefügt und nie wieder in das öffentliche Repo übernommen. Es hat den zusätzlichen Vorteil, dass ich sehen kann, welche Version des Tests jeder Kandidat gelöst hat, da er sich im Laufe der Zeit weiterentwickelt.
Axl
0

Ich kann Ihnen nur ein Dienstprogramm vorschlagen, mit dem Sie .gitignoreDateien in Ihrem Repository verschlüsseln können. Der Workflow ist etwas schwierig zu verwenden, macht jedoch die verschlüsselten Gegenstücke Ihrer Dateien zusammen mit anderen nicht geheimen Dateien auf der Arbeitskopie verfügbar, sodass Sie sie wie gewohnt per Git verfolgen können.

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

So erstellen Sie eine geheime Datei mit dem Dateinamen a.txttype sshare -a a.txt. Dienstprogramm Datei erstellen a.txtund Datei hinzugefügt .gitignore. Anschließend wird ein verschlüsseltes Gegenstück zur "Datenbank" erstellt, a.txt.sshareindem .ssharedem Dateinamen eine Erweiterung hinzugefügt wird .

Dann können Sie a.txtmit etwas Text füllen . Um seinen Zustand direkt vor speichern git commitTyp sshare -s a.txt, dann Dienstprogramm Aufforderung für Sie Kennwort Neuzustand der Datei zu verschlüsseln a.txt. Wenn Sie dieses Kennwort verwenden a.txt, wird am Ende der a.txt.sshareDatei ein verschlüsselter Unterschied zwischen dem vorherigen und dem aktuellen Dateistatus hinzugefügt .

Nach dem Abrufen / Ziehen des Repositorys mit verschlüsselten Dateien sollten Sie das sshareDienstprogramm für jede Datei mit dem -lSchlüssel ("Laden") ausführen . In diesem Fall entschlüsselt das Dienstprogramm *.sshareDateien in Textdateien, die von git in der Arbeitskopie nicht verfolgt werden.

Sie können für jede geheime Datei unterschiedliche Passwörter verwenden.

Das Dienstprogramm ermöglicht es git Spur ändert effizient ( diff von .sshareDateien ist einfach eine Zeile).

Tomilov Anatoliy
quelle