github strategia utrzymywania prywatności jednej wersji pliku

11

Jestem wykładowcą piszącym problemy z kodowaniem dla studentów. To, co chcę zrobić, to dać uczniom kod z kodami zastępczymi dla funkcji, które studenci mają wykonać. Dam studentom dostęp do prywatnego repozytorium github, aby to sklonować.

Jednak chcę też wersję bazy kodu wraz z przykładowymi rozwiązaniami. Oczywiście nie chcę, aby uczniowie mieli dostęp do rozwiązania (do czasu zakończenia zadania).

Myślałem o oddziałach, ale AFAIK, nie mogę zachować jednego oddziału w tajemnicy.

Może mógłbym rozwidlić projekt na inne prywatne repozytorium, ale nie jestem pewien, jak mogę zachować projekty w snyc (oprócz pliku zawierającego rozwiązanie).

Czy istnieje przepływ pracy w tej sytuacji?

Rozpoznać
źródło
1
Nie sądzę. Ale to, co robisz na zimno: interfejsy delcare dla wszystkich metod, które mają zostać zaimplementowane. W swoim repozytorium dla uczniów stwórz klasy implementujące te interfejsy za pomocą pustych treści metod. Zachowaj rozwiązania w osobnym prywatnym repozytorium. Nie rozwiązuje to całkowicie problemu synchronizacji, ale ogranicza go do zakresu zadań.
marstato
czy korzystałeś z interfejsu API github do kontrolowania dostępu do oddziałów?

Odpowiedzi:

8

Co może być całkiem wykonalne:

  • Utwórz 2 repozytoria: ucznia i nauczyciela.
  • Sklonuj je na maszynie (można to zrobić za pomocą klienta Github)
  • Państwo działa tylko w nauczyciela , nigdy dotknąć ucznia.

Więc twoja struktura katalogów składa się z 2 sklonowanych repozytoriów git:

  • / student (z folderem .git)
  • / nauczyciel (z folderem .git)

Umieszczasz znaczniki wokół „prywatnego” kodu w komentarzach dla twojego języka, na przykład javascript poniżej. Znaczniki wskazują, gdzie zaczyna się i kończy prywatny kod.

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

Następnie wykonaj prosty skrypt na komputerze lokalnym:

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

Będzie: pobierał wszystkie twoje pliki i kopiował zawartość do / student (nadpisywanie) bez zaznaczonych prywatnych części kodu. Jeśli chcesz, możesz wstawić puste wiersze, ale może to podpowiedzieć, jakiego rodzaju rozwiązania oczekujesz.

Jest to przykładowy kod, który nie został przetestowany, więc prawdopodobnie będziesz musiał przeprowadzić debugowanie.

Teraz jedyne, co musisz zrobić, to zatwierdzić i wrzucić repozytorium ucznia, gdy jesteś zadowolony z wyników. Można to zrobić jednym kliknięciem podczas korzystania z klienta GitHub (dzięki czemu można dokonać szybkiego przeglądu wizualnego) lub po prostu zrobić to ręcznie w wierszu polecenia.

Repozytorium studenckie jest tylko repozytorium wyjściowym, więc zawsze będzie aktualne, dla studentów jasne jest, co się zmieniło, patrząc na zatwierdzenia (ponieważ pokazują tylko zmiany) i jest łatwe w obsłudze.

Kolejnym krokiem byłoby utworzenie haka zatwierdzającego git, który automatycznie uruchamia twój skrypt.

Edytuj: Zobacz, czy dokonałeś edycji swojego postu:

Oczywiście nie chcę, aby uczniowie mieli dostęp do rozwiązania (do czasu zakończenia zadania).

Podejrzewam, że jest jasne, ale musi być kompletne: wystarczy usunąć tagi wokół ukończonego ćwiczenia opublikuje odpowiedź w taki sam sposób, jak w przypadku normalnych aktualizacji ćwiczeń.

Luc Franken
źródło
Miałem nadzieję, że uda mi się to zrobić za pomocą git voodoo, jednak twoje rozwiązanie jest bardzo praktyczne.
Ken
@Ken też o tym myślał, ale to trochę złe narzędzie do niewłaściwej pracy. Git łączy, aktualizuje itp., Ale ogólnie rzecz biorąc nie jest to pomysł na wybór kodu. Dobrze utrzymuje spójność bazy kodu na wielu komputerach. Właśnie dlatego wymyśliłem inne rozwiązanie. To, co podoba mi się w tym podejściu, to to, że minimalizuje ono ryzyko i robociznę, więc łatwo nadążyć za nim. I w końcu i tak powinieneś ręcznie napisać wiadomość zatwierdzającą do repozytorium studentów, aby dać swoim uczniom dobry przykład;)
Luc Franken
Aby pomóc gitowi śledzić zmiany, możesz utworzyć gałąź studenta w repozytorium nauczyciela, uruchom skrypt podczas łączenia (lub scalania ręcznie, usuwając wszystko pomiędzy znacznikami). Następnie zsynchronizuj oddział ucznia lokalnie i wypchnij go do repozytorium ucznia zamiast od początku nauczyciela. W ten sposób git byłby w lepszej formie do śledzenia zmian i prawidłowego przekazywania historii z jednego repozytorium do następnego. Najlepsze z obu światów. Nie próbowałem tego umysłu, ale nie rozumiem, dlaczego to nie zadziała.
Newtopian
1
Podoba mi się to z wyjątkiem pomysłu usunięcia tagów początkowych. Lepiej zmieszaj je, dodając słowo „rozwiązanie”.
candied_orange
@CandiedOrange też jest fajny, zgódź się na to. Rozwiązanie pozwoliłoby również na inne formatowanie i wyraźnie odróżnia zapomniane znaczniki od prawdziwej decyzji, że rozwiązanie powinno zostać opublikowane. @ newtopian: Myślałem o tym, ale nie widziałem wystarczających zalet. Postanowiłem też zobaczyć wyniki studentów jako zupełnie inny rodzaj kodu. To nie jest prawdziwe źródło, więc postanowiłem tego nie robić. To, co zrobiłbym z oddziałami w repozytorium nauczycieli, to na przykład: Praca nad zadaniami na następny semestr. Kiedy będziesz gotowy, scalisz je, aby opanować, a następnie uruchom skrypt.
Luc Franken
6

Mógłbyś

  • Utwórz publiczne repozytorium GitHub, jeśli zatwierdzisz kod płyty głównej
  • Rozwidlaj to repozytorium jako prywatne repozytorium GitHub
  • Rozwiąż zadania w rozwidlonym repozytorium
  • Po zakończeniu przypisania połącz każde rozwiązanie z publicznym repozytorium

Oto jak zaimplementowałbym ten przepływ pracy:

  • Utwórz publiczne repozytorium assignmentshostowane na GitHub. Dodaj kod płyty głównej dla zadań. Np. Do każdego zadania wprowadza się nowy podkatalog zawierający kod płyty głównej zadania.
  • Utwórz nowe prywatne repozytorium assignments-solvedna GitHub. Sklonuj assignmentsrepozytorium na swoim komputerze i wypchnij je do assignments-solved repozytorium (zasadniczo rozwidlaj własne repozytorium jako kopię prywatną): 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
  • Dodaj assignments-solvedrepozytorium jako zdalne do assignmentsrepozytorium: cd assignments # change to the assignments repo on your machine git remote add solutions https://github.com/[user]/assignments-solved
  • Zaimplementuj każde zadanie w assignments-solvedrepozytorium. Upewnij się, że każde zatwierdzenie zawiera tylko zmiany z jednego przypisania.
  • Możesz utworzyć solvedgałąź w assignmentsrepozytorium, aby oryginalne przypisania nie uległy zmianie: cd assignments # change to the assignments repo on your machine git branch -b solutions git push -u origin
  • Kiedy chcesz opublikować rozwiązanie w assignments, pobierz solvedpilota i cherry-pickzatwierdzenia zawierające rozwiązania. cd assignments # change to the assignments repo on your machine git checkout solved git fetch solutions git cherry-pick [commithash] Gdzie [commithash]zawiera zatwierdzenie twojego rozwiązania.

Możliwe jest również zaimplementowanie przepływu pracy poprzez wdrożenie każdego zadania w osobnej gałęzi assignments-solvedrepozytorium, a następnie utworzenie żądania ściągania w assignmentsrepozytorium. Ale nie jestem pewien, czy to zadziała w GitHub, ponieważ assignments-solvedrepo nie jest prawdziwym widelcem.

Smak
źródło
Z powodzeniem zastosowałem podobną metodę do oddzielenia testu programowego od przesłanych odpowiedzi. W moim przypadku przesłane rozwiązania są dodawane do poszczególnych oddziałów prywatnego klonu i nigdy nie są ponownie łączone z publicznym repozytorium. Ma tę dodatkową zaletę, że pozwala mi zobaczyć, jaką wersję testu rozwiązał każdy kandydat, ponieważ ewoluuje on z czasem.
axl
0

Mogę tylko zaproponować Ci narzędzie do obsługi .gitignorei szyfrowania plików w twoim repozytorium. Przepływ pracy jest nieco trudny w użyciu, ale sprawia, że ​​zaszyfrowane odpowiedniki plików są dostępne w kopii roboczej wraz z innymi nie-tajnymi plikami, co pozwala na śledzenie ich przez git jak zwykle.

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

Aby utworzyć tajny plik o a.txttypie pliku sshare -a a.txt. Narzędzie do tworzenia pliku a.txti pliku dodanego do .gitignore. Następnie tworzy zaszyfrowany odpowiednik „bazy danych” a.txt.ssharepoprzez dodanie .ssharerozszerzenia do nazwy pliku.

Następnie możesz wypełnić a.txttekst. Aby zapisać swój stan tuż przed git committypem sshare -s a.txt, narzędzie wyświetli monit o podanie hasła do zaszyfrowania nowego stanu pliku a.txt. Następnie użycie tego hasła dodaje zaszyfrowaną różnicę między poprzednim a bieżącym stanem pliku a.txtna końcu a.txt.ssharepliku.

Po pobraniu / ściągnięciu repozytorium z zaszyfrowanymi plikami powinieneś uruchomić ssharenarzędzie dla każdego pliku za pomocą -lklawisza („load”). W takim przypadku narzędzie odszyfrowuje *.ssharepliki do plików tekstowych nieśledzonych przez git w kopii roboczej.

Możesz użyć różnych haseł dla każdego tajnego pliku.

Narzędzie pozwala na śledzenie zmian git skutecznie ( diff z .sshareplików jest po prostu jednym wierszu).

Tomiłow Anatolij
źródło