กลยุทธ์ GitHub สำหรับเก็บไฟล์หนึ่งเวอร์ชันเป็นส่วนตัว


11

ฉันเป็นอาจารย์ที่เขียนปัญหาการเข้ารหัสสำหรับนักเรียน สิ่งที่ฉันต้องการจะทำคือให้รหัสนักเรียนพร้อมตัวยึดสำหรับฟังก์ชันที่นักเรียนต้องทำให้เสร็จ ฉันจะให้นักเรียนเข้าถึง repo GitHub ส่วนตัวเพื่อโคลนนี้

อย่างไรก็ตามฉันยังต้องการเวอร์ชันของ codebase พร้อมด้วยตัวอย่างโซลูชั่น เห็นได้ชัดว่าฉันไม่ต้องการให้นักเรียนเข้าถึงการแก้ปัญหา (จนกว่างานจะเสร็จสิ้น)

ฉันคิดเกี่ยวกับสาขา แต่ AFAIK ฉันไม่สามารถรักษาความเป็นส่วนตัวได้

บางทีฉันอาจแยกโครงการไปสู่ ​​repo ส่วนตัวอื่น แต่ไม่แน่ใจว่าฉันจะเก็บโครงการเป็น snyc ได้อย่างไร (นอกเหนือจากไฟล์ที่มีโซลูชัน)

มีเวิร์กโฟลว์สำหรับสถานการณ์นี้หรือไม่


1
ฉันไม่คิดอย่างนั้น แต่สิ่งที่คุณทำคือ: ส่วนต่อประสานสำหรับวิธีการต่าง ๆ ที่จะนำมาใช้ ใน repo นักเรียน - สาธารณะของคุณสร้างคลาสที่ใช้อินเทอร์เฟซเหล่านั้นด้วยเนื้อความของวิธีการที่ว่างเปล่า รักษาโซลูชันใน repo ส่วนตัวที่แยกต่างหาก สิ่งนี้ไม่ได้แก้ปัญหาการซิงโครไนซ์ของคุณทั้งหมด แต่จะลดลงไปในขอบเขตของงาน
marstato

คุณเคยใช้ github API เพื่อควบคุมการเข้าถึงสาขาหรือไม่?

คำตอบ:


8

สิ่งที่สามารถทำได้ค่อนข้าง:

  • สร้างที่เก็บ 2 แห่ง: นักเรียนและอาจารย์
  • โคลนพวกเขาไปยังเครื่องของคุณ (สามารถทำได้กับลูกค้า Github)
  • คุณทำงานเฉพาะครูเท่านั้นอย่าแตะต้องนักเรียน

ดังนั้นโครงสร้างไดเรกทอรีของคุณจึงเป็น 2 repo git repo:

  • / นักเรียน (พร้อมโฟลเดอร์. git)
  • / ครู (พร้อมโฟลเดอร์. git)

คุณใส่เครื่องหมายรอบรหัส "ส่วนตัว" ในความคิดเห็นสำหรับภาษาของคุณเช่นจาวาสคริปต์ด้านล่าง เครื่องหมายระบุว่ารหัสส่วนตัวเริ่มต้นและสิ้นสุดที่ใด

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

จากนั้นสร้างสคริปต์ง่าย ๆ บนเครื่องของคุณ:

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

มันจะ: นำไฟล์ทั้งหมดของคุณและคัดลอกเนื้อหาไปยัง / นักเรียน (เขียนทับ) โดยไม่มีส่วนที่ทำเครื่องหมายไว้ส่วนตัวของรหัส หากคุณต้องการคุณสามารถแทรกบรรทัดว่างเปล่าที่นั่น แต่นั่นอาจให้คำแนะนำเกี่ยวกับชนิดของการแก้ปัญหาที่คุณคาดหวัง

มันเป็นรหัสตัวอย่างที่ยังไม่ทดลองดังนั้นคุณอาจต้องทำการดีบักบ้าง

ตอนนี้สิ่งเดียวที่คุณต้องทำคือกระทำและผลักดันพื้นที่เก็บข้อมูลของนักเรียนเมื่อคุณพอใจกับผลลัพธ์ ที่สามารถทำได้ในคลิกเดียวเมื่อใช้ลูกค้า GitHub (เพื่อให้คุณสามารถตรวจสอบภาพอย่างรวดเร็ว) หรือเพียงแค่ทำด้วยตนเองในบรรทัดคำสั่ง

repo ของนักเรียนเป็นที่เก็บเอาท์พุทเท่านั้นดังนั้นมันจะอยู่เสมอที่ทันสมัยมันชัดเจนกับนักเรียนที่มีการเปลี่ยนแปลงโดยดูที่การกระทำ (เพราะพวกเขาเพียงแสดงการเปลี่ยนแปลง) และง่ายต่อการจัดการ

อีกหนึ่งขั้นตอนต่อไปคือการสร้างคอมมิชชันคอมมิชชันซึ่งรันสคริปต์ของคุณโดยอัตโนมัติ

แก้ไข: เห็นคุณทำการแก้ไขโพสต์ของคุณ:

เห็นได้ชัดว่าฉันไม่ต้องการให้นักเรียนเข้าถึงการแก้ปัญหา (จนกว่างานจะเสร็จสิ้น)

ฉันสงสัยว่ามันชัดเจน แต่จะเสร็จสมบูรณ์: เพียงแค่ลบแท็กรอบการฝึกที่เสร็จแล้วออกจะเผยแพร่คำตอบในแบบเดียวกับที่คุณทำสำหรับการอัพเดทตามปกติของแบบฝึกหัด


หวังว่าฉันจะทำสิ่งนี้ได้ด้วย voodoo git บางอย่าง แต่วิธีการแก้ปัญหาของคุณใช้ได้จริงมาก
เคน

@Ken ก็กำลังคิดเช่นกัน แต่มันเป็นเครื่องมือที่ผิดเล็กน้อยสำหรับงานที่ผิด Git ทำการผสานอัปเดต ฯลฯ แต่โดยทั่วไปไม่ใช่แนวคิดในการเลือกรหัส มันเป็นการดีที่ทำให้ codebase ของคุณสอดคล้องกับหลาย ๆ เครื่อง นั่นคือสาเหตุที่ฉันคิดโซลูชันอื่น สิ่งที่ฉันชอบเกี่ยวกับวิธีการนี้ก็คือมันช่วยลดความเสี่ยงและแรงงานให้น้อยที่สุดดังนั้นจึงง่ายต่อการติดตาม และในท้ายที่สุดแล้วคุณควรจะเขียนข้อความการกระทำของคุณเพื่อ repo นักเรียนด้วยมืออยู่แล้วที่จะให้เป็นตัวอย่างที่ดีให้กับนักเรียนของคุณ)
ลัค Franken

เพื่อช่วยให้คอมไพล์ติดตามการเปลี่ยนแปลงคุณสามารถสร้างสาขาของนักเรียนใน repo ครูของคุณให้เรียกใช้สคริปต์เมื่อรวม (หรือผสานด้วยมือลบสิ่งใด ๆ ระหว่างเครื่องหมาย) จากนั้นซิงค์สาขาของนักเรียนในพื้นที่แล้วส่งไปที่ repo ของนักเรียนแทนที่มาของครู วิธีนี้คอมไพล์จะมีรูปร่างที่ดีกว่าในการติดตามการเปลี่ยนแปลงและมีการส่งต่อประวัติอย่างถูกต้องจาก repo หนึ่งไปยังอีก สุดยอดของทั้งสองโลก ฉันไม่ได้ลองใจคุณ แต่ฉันไม่เห็นว่าทำไมมันไม่ทำงาน
Newtopian

1
ฉันชอบสิ่งนี้ยกเว้นความคิดในการลบแท็กสิ้นสุดเริ่มต้น ดีกว่าที่จะทำให้ยุ่งเหยิงพวกเขาโดยการเพิ่มคำว่า "การแก้ปัญหา"
candied_orange

@CandiedOrange ที่เป็นคนดีเห็นด้วยกับที่ โซลูชันจะอนุญาตการจัดรูปแบบที่แตกต่างกันและจะทำให้เห็นความแตกต่างระหว่างแท็กที่ถูกลืมและการตัดสินใจที่แท้จริงที่ควรเผยแพร่โซลูชั่น @ newtopian: ฉันกำลังคิดเกี่ยวกับสิ่งนั้น แต่ฉันเห็นข้อดีไม่เพียงพอ ฉันตัดสินใจที่จะเห็นผลลัพธ์ของนักเรียนเป็นรหัสที่แตกต่างกันโดยสิ้นเชิง มันไม่ใช่แหล่งที่แท้จริงดังนั้นฉันจึงตัดสินใจไม่ทำ สิ่งที่ฉันจะทำกับสาขาใน repo ครูเป็นตัวอย่าง: ทำงานในภาคการศึกษาถัดไป เมื่อคุณพร้อมที่จะรวมพวกเขาเป็นหลักแล้วเรียกใช้สคริปต์
Luc Franken

6

คุณทำได้

  • สร้าง repostory สาธารณะ GitHub เมื่อคุณส่งรหัสต้นแบบ
  • แยกที่เก็บนี้เป็น repostory GitHub ส่วนตัว
  • แก้ไขการมอบหมายในพื้นที่เก็บข้อมูลที่แยกกัน
  • รวมแต่ละโซลูชันเข้ากับที่เก็บสาธารณะเมื่อการมอบหมายเสร็จสิ้น

นี่คือวิธีที่ฉันจะใช้เวิร์กโฟลว์นี้:

  • สร้างการเผยแพร่ซ้ำสาธารณะที่assignmentsโฮสต์บน GitHub เพิ่มรหัสสำเร็จรูปสำหรับการกำหนด เช่นสำหรับการมอบหมายแต่ละครั้งคุณจะแนะนำไดเรกทอรีย่อยใหม่ที่มีรหัสสำเร็จรูปของการกำหนด
  • สร้างที่เก็บส่วนตัวใหม่assignments-solvedบน GitHub โคลน assignmentsrepo บนเครื่องของคุณและผลักดันมันไปassignments-solved repo (ส่วนแยกพื้นที่เก็บข้อมูลของคุณเองเป็นสำเนาเอกชน): 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
  • เพิ่มassignments-solvedrepo เป็นรีโมทไปยังassignmentsrepo: cd assignments # change to the assignments repo on your machine git remote add solutions https://github.com/[user]/assignments-solved
  • ใช้แต่ละการมอบหมายในที่assignments-solvedเก็บ ตรวจสอบให้แน่ใจว่าการกระทำแต่ละรายการมีการเปลี่ยนแปลงจากการมอบหมายครั้งเดียวเท่านั้น
  • คุณอาจต้องการสร้างsolvedสาขาในassignmentsrepo เพื่อที่จะไม่ทำการเปลี่ยนแปลงที่ได้รับมอบหมายเดิม: cd assignments # change to the assignments repo on your machine git branch -b solutions git push -u origin
  • เมื่อคุณต้องการเผยแพร่โซลูชันลงในassignmentsดึง solvedรีโมตและคอมcherry-pickมิตที่มีโซลูชัน cd assignments # change to the assignments repo on your machine git checkout solved git fetch solutions git cherry-pick [commithash] ที่[commithash]มีความมุ่งมั่นของการแก้ปัญหาของคุณ

คุณอาจสามารถนำเวิร์กโฟลว์ไปใช้โดยใช้การมอบหมายแต่ละรายการในสาขาแยกของassignments-solvedrepo แล้วสร้างคำขอดึงในassignmentsrepo แต่ฉันไม่แน่ใจว่าสิ่งนี้จะทำงานใน GitHub หรือไม่เนื่องจากassignments-solvedธุรกรรมซื้อคืนนั้นไม่ใช่ทางแยกที่แท้จริง


ฉันใช้วิธีที่คล้ายกันสำเร็จแล้วในการแยกการทดสอบการเขียนโปรแกรมออกจากคำตอบที่ส่ง ในกรณีของฉันโซลูชันที่ส่งจะถูกเพิ่มไปยังแต่ละสาขาของโคลนส่วนตัวและไม่รวมกลับไปที่ repo สาธารณะ มันมีประโยชน์เพิ่มเติมในการแจ้งให้ฉันเห็นว่าแบบทดสอบใดที่ผู้สมัครแต่ละคนได้แก้ไขขณะที่มันมีวิวัฒนาการตลอดเวลา
axl

0

ฉันสามารถเสนอยูทิลิตีที่ตั้งใจจะทำ.gitignoreและเข้ารหัสไฟล์ในที่เก็บของคุณ เวิร์กโฟลว์นั้นใช้งานได้ยากเพียงเล็กน้อย แต่มันทำให้คู่หูที่เข้ารหัสของไฟล์ของคุณสามารถใช้งานได้ที่สำเนาการทำงานพร้อมกับไฟล์ที่ไม่ใช่ความลับอื่น ๆ ซึ่งช่วยให้สามารถติดตามพวกเขาด้วยคอมไพล์ได้ตามปกติ

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

การสร้างแฟ้มลับกับชื่อไฟล์ประเภทa.txt sshare -a a.txtยูทิลิตี้สร้างไฟล์และแฟ้มเพิ่มไปยังa.txt .gitignoreจากนั้นจะสร้าง "ฐานข้อมูล" ที่เข้ารหัสa.txt.sshareโดยเพิ่ม.sshareส่วนขยายไปยังชื่อไฟล์

จากนั้นคุณสามารถกรอกa.txtข้อความ บันทึกสถานะทางด้านขวาก่อนที่จะgit commitประเภทแล้วยูทิลิตี้พร้อมท์คุณใส่รหัสผ่านในการเข้ารหัสรัฐใหม่ของแฟ้มsshare -s a.txt a.txtจากนั้น utilty โดยใช้รหัสผ่านนี้จะเพิ่มการเข้ารหัสที่แตกต่างกันระหว่างสถานะก่อนหน้าและสถานะปัจจุบันของไฟล์a.txtไปยังจุดสิ้นสุดa.txt.sshareไฟล์

หลังจากดึง / ดึงพื้นที่เก็บข้อมูลด้วยไฟล์ที่เข้ารหัสแล้วคุณควรเรียกใช้sshareยูทิลิตี้สำหรับแต่ละไฟล์โดยใช้ปุ่ม-l("โหลด") ในกรณีนี้ยูทิลิตี้ถอดรหัส*.sshareไฟล์เป็นไฟล์ข้อความที่ไม่ได้ติดตามโดยgitในสำเนาการทำงาน

คุณสามารถใช้รหัสผ่านที่แตกต่างกันสำหรับไฟล์ลับแต่ละไฟล์

ยูทิลิตี้นี้ช่วยให้คอมไพล์สามารถติดตามการเปลี่ยนแปลงได้อย่างมีประสิทธิภาพ (ความแตกต่างของ.sshareไฟล์เป็นเพียงบรรทัดเดียว)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.