strategi github untuk menjaga satu versi file tetap pribadi

11

Saya seorang dosen menulis masalah coding untuk mahasiswa. Yang ingin saya lakukan adalah memberi siswa kode boilerplate dengan placeholder untuk fungsi yang harus diselesaikan siswa. Saya akan memberikan siswa akses ke repo github pribadi untuk mengkloning ini.

Namun, saya juga menginginkan versi basis kode, lengkap dengan solusi sampel. Jelas saya tidak ingin siswa memiliki akses ke solusi (sampai tugas selesai).

Saya sudah memikirkan cabang, tapi AFAIK, saya tidak bisa merahasiakan satu cabang.

Mungkin saya bisa memotong proyek ke repo pribadi lain, tetapi saya tidak yakin bagaimana saya bisa menyimpan proyek di snyc (terlepas dari file yang berisi solusinya).

Apakah ada alur kerja untuk situasi ini?

Ken
sumber
1
Saya rasa tidak. Tapi apa yang Anda lakukan dingin: antarmuka delcare untuk menyimpulkan metode yang akan diterapkan. Dalam repo siswa-publik Anda, buat kelas yang mengimplementasikan antarmuka tersebut dengan badan metode kosong. Pertahankan solusi dalam repo pribadi yang terpisah. Ini tidak sepenuhnya menyelesaikan masalah sinkronisasi Anda tetapi mengurangi ke lingkup tugas.
marstato
Sudahkah Anda melihat menggunakan API github untuk mengontrol akses ke cabang?

Jawaban:

8

Apa yang bisa dilakukan:

  • Buat 2 repositori: siswa dan guru.
  • Mengkloningnya ke mesin Anda (dapat dilakukan dengan klien Github)
  • Anda hanya bekerja di guru , tidak pernah menyentuh siswa.

Jadi struktur direktori Anda adalah 2 klon git repo:

  • / siswa (dengan folder .git)
  • / guru (dengan folder .git)

Anda menaruh spidol di sekitar kode "pribadi" dalam komentar untuk bahasa Anda, contoh javascript di bawah ini. Penanda menunjukkan di mana kode pribadi mulai dan berakhir.

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

Kemudian buat skrip sederhana di mesin lokal Anda:

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

Ini akan: mengambil semua file Anda dan menyalin konten ke / siswa (menimpa) tanpa bagian kode yang ditandai pribadi. Jika mau, Anda dapat memasukkan baris kosong di sana tetapi itu mungkin memberikan petunjuk tentang solusi apa yang Anda harapkan.

Ini adalah kode contoh yang belum diuji, jadi kemungkinan Anda harus melakukan debugging.

Sekarang yang harus Anda lakukan adalah berkomitmen dan mendorong repositori siswa ketika Anda senang dengan hasilnya. Itu dapat dilakukan dalam satu klik saat menggunakan klien GitHub (sehingga Anda dapat melakukan tinjauan visual cepat) atau hanya melakukannya secara manual pada baris perintah.

Repo siswa adalah repositori keluaran saja sehingga akan selalu tetap terkini, jelas bagi siswa apa yang diubah dengan melihat komit (karena mereka hanya menunjukkan perubahan) dan mudah ditangani.

Satu langkah lebih jauh adalah membuat git commit-hook yang menjalankan skrip Anda secara otomatis.

Sunting: Lihat Anda melakukan pengeditan pada posting Anda:

Jelas saya tidak ingin siswa memiliki akses ke solusi (sampai tugas selesai).

Saya menduga itu jelas tetapi harus lengkap: Hanya menghapus tag di sekitar latihan selesai akan mempublikasikan jawabannya dengan cara yang sama seperti yang Anda lakukan untuk pembaruan normal untuk latihan.

Luc Franken
sumber
berharap saya bisa melakukan ini dengan beberapa git voodoo, namun solusi Anda sangat praktis.
Ken
@ Ken juga memikirkan hal itu, tapi itu alat yang salah untuk pekerjaan yang salah. Git menggabungkan, memperbarui dll tetapi secara umum itu bukan ide untuk memilih kode. Ini bagus untuk menjaga basis kode Anda konsisten pada beberapa mesin. Jadi itu sebabnya saya memikirkan solusi lain. Apa yang saya juga suka tentang pendekatan ini adalah meminimalkan risiko dan tenaga sehingga mudah untuk mengikutinya. Dan, pada akhirnya, Anda harus menulis pesan komit Anda ke repo siswa dengan tangan tetap untuk memberikan contoh yang baik kepada siswa Anda;)
Luc Franken
Untuk membantu git melacak perubahan, Anda dapat membuat cabang siswa di repo guru Anda, jalankan skrip saat menggabungkan (atau menggabungkan dengan tangan menghapus apa pun di antara spidol). Kemudian sinkronkan cabang siswa secara lokal dan dorong ke repo siswa alih-alih asal guru. Dengan cara ini git akan berada dalam kondisi yang lebih baik untuk melacak perubahan dan memiliki riwayat yang diteruskan dengan baik dari satu repo ke yang berikutnya. Terbaik dari kedua dunia. Saya belum mencoba pikiran ini, tetapi saya tidak mengerti mengapa itu tidak berhasil.
Newtopian
1
Saya suka ini kecuali gagasan untuk menghapus tag awal. Lebih baik memotong-motongnya dengan menambahkan kata "solusi".
candied_orange
@CandiedOrange itu bagus juga, setujui itu. Solusi juga akan memungkinkan beberapa pemformatan yang berbeda dan itu dengan jelas membedakan antara tag yang dilupakan dan keputusan nyata bahwa solusi harus dipublikasikan. @ newtopian: Saya memikirkan hal itu tetapi saya tidak melihat cukup banyak keuntungan. Saya juga memutuskan untuk melihat hasil siswa sebagai jenis kode yang sama sekali berbeda. Itu bukan sumber yang sebenarnya jadi saya memutuskan untuk tidak. Apa yang akan saya lakukan dengan cabang di repo guru adalah sebagai contoh: Kerjakan tugas untuk semester berikutnya. Saat Anda siap, Anda menggabungkan mereka untuk menguasai dan kemudian menjalankan skrip.
Luc Franken
6

Anda bisa

  • Buat repostory GitHub publik yang Anda komit kode boilerplate
  • Fork repositori ini sebagai repostory GitHub pribadi
  • Selesaikan tugas di repositori bercabang dua
  • Gabungkan setiap solusi ke dalam repositori publik ketika tugas selesai

Beginilah cara saya mengimplementasikan alur kerja ini:

  • Buat repostory publik yang di- assignmentshost di GitHub. Tambahkan kode boilerplate untuk tugas. Misalnya untuk setiap tugas Anda memperkenalkan sub-direktori baru yang berisi kode boilerplate dari tugas tersebut.
  • Buat repositori pribadi baru assignments-solveddi GitHub. Kloning assignmentsrepo pada mesin Anda, dan dorong ke assignments-solved repo (pada dasarnya garpu repositori Anda sendiri sebagai salinan pribadi): 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
  • Tambahkan assignments-solvedrepo sebagai jarak jauh ke assignmentsrepo: cd assignments # change to the assignments repo on your machine git remote add solutions https://github.com/[user]/assignments-solved
  • Laksanakan setiap tugas dalam assignments-solvedrepositori. Pastikan setiap komit hanya berisi perubahan dari satu tugas.
  • Anda mungkin ingin membuat solvedcabang di assignmentsrepo, sehingga tugas asli tidak diubah: cd assignments # change to the assignments repo on your machine git branch -b solutions git push -u origin
  • Saat Anda ingin mempublikasikan solusi ke dalam assignments, ambil solvedremote dan cherry-pickkomit yang berisi solusi. cd assignments # change to the assignments repo on your machine git checkout solved git fetch solutions git cherry-pick [commithash] Di mana [commithash]berisi komit dari solusi Anda.

Anda mungkin juga dapat menerapkan alur kerja dengan menerapkan setiap tugas di cabang terpisah dari assignments-solvedrepo dan kemudian membuat permintaan tarik di assignmentsrepo. Tetapi saya tidak yakin apakah ini akan berhasil di GitHub, karena assignments-solvedrepo itu bukan fork nyata .

Gaste
sumber
Saya telah berhasil menggunakan metode serupa untuk memisahkan tes pemrograman dari jawaban yang dikirimkan. Dalam kasus saya, solusi yang diajukan ditambahkan ke masing-masing cabang klon pribadi, dan tidak pernah digabungkan kembali ke repo publik. Ini memiliki manfaat tambahan dengan membiarkan saya melihat versi tes apa yang telah dipecahkan oleh setiap kandidat, karena tes ini berkembang seiring waktu.
axl
0

Saya hanya bisa mengusulkan Anda sebuah utilitas yang ditujukan untuk .gitignore-ing dan mengenkripsi file dalam repositori Anda. Workflow agak sulit digunakan, tetapi itu membuat rekan-rekan terenkripsi file Anda tersedia di copy pekerjaan bersama dengan file non-rahasia lainnya, yang memungkinkan untuk melacak mereka dengan git seperti biasa.

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

Untuk membuat file rahasia dengan a.txttipe nama file sshare -a a.txt. Utilitas membuat file a.txtdan file ditambahkan ke .gitignore. Kemudian ia membuat mitra "database" terenkripsi a.txt.ssharedengan menambahkan .sshareekstensi ke nama file.

Kemudian Anda dapat mengisi a.txtdengan beberapa teks. Untuk menyimpan kondisinya tepat sebelum git commitmengetik sshare -s a.txt, maka utilitas meminta Anda untuk kata sandi untuk mengenkripsi keadaan file yang baru a.txt. Kemudian menggunakan kata sandi ini menambahkan perbedaan terenkripsi antara status file sebelumnya dan saat ini a.txtke akhir a.txt.ssharefile.

Setelah mengambil / menarik repositori dengan file terenkripsi, Anda harus menjalankan sshareutilitas untuk setiap file menggunakan kunci -l("load"). Dalam hal ini utilitas mendekripsi *.ssharefile ke file teks yang tidak dilacak oleh git dalam copy pekerjaan.

Anda dapat menggunakan kata sandi yang berbeda untuk setiap file rahasia.

Utilitas memungkinkan git untuk melacak perubahan efisien ( diff dari .ssharefile hanya satu baris).

Tomilov Anatoliy
sumber