한 버전의 파일을 비공개로 유지하기위한 github 전략


11

저는 학생들에게 코딩 문제를 쓰는 강사입니다. 내가하고 싶은 것은 학생들이 완성해야 할 기능에 대한 자리 표시자가있는 상용구 코드를 학생들에게 제공하는 것입니다. 학생들에게 개인 github 저장소에 액세스하여이를 복제 할 수 있습니다.

그러나 샘플 솔루션을 갖춘 코드베이스 버전도 원합니다. 분명히 나는 ​​학생들이 솔루션에 액세스하기를 원하지 않습니다 (과제가 끝날 때까지).

지점에 대해 생각했지만 AFAIK, 한 지점을 비공개로 유지할 수 없습니다.

어쩌면 프로젝트를 다른 개인 저장소로 포크 할 수는 있지만 어떻게 프로젝트를 snyc에 보관할 수 있는지 잘 모르겠습니다 (솔루션이 포함 된 파일 제외).

이 상황에 대한 워크 플로우가 있습니까?


1
나는 그렇게 생각하지 않습니다. 그러나 가장 먼저하는 일은 구현해야 할 모든 방법을위한 delcare 인터페이스입니다. 학생-공공 리포지토리에서 빈 메서드 본문으로 해당 인터페이스를 구현하는 클래스를 만듭니다. 별도의 개인 저장소에 솔루션을 유지하십시오. 이렇게하면 동기화 문제가 완전히 해결되지는 않지만 작업 범위로 줄어 듭니다.
marstato

분기에 대한 액세스를 제어하기 위해 github API를 사용하는 것을 보셨습니까?

답변:


8

할 수있는 것 :

  • 학생과 교사라는 두 개의 저장소를 만듭니다.
  • 머신에 복제 (Github 클라이언트로 수행 가능)
  • 당신 은 교사에서만 일하고 학생을 만지지 마십시오.

따라서 디렉토리 구조는 2 개의 복제 된 git repo입니다.

  • /student(.git 폴더 포함)
  • /teacher(.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);
});

그것은 모든 파일을 가져 와서 코드의 개인 표시 부분없이 / student (덮어 쓰기)에 내용을 복사합니다. 빈 줄을 삽입 할 수 있지만 원하는 솔루션 종류에 대한 힌트를 줄 수 있습니다.

테스트되지 않은 예제 코드이므로 디버깅을 수행해야 할 것입니다.

이제 출력에 만족할 때 학생 저장소에 커밋하고 밀어 넣기 만하면됩니다. GitHub 클라이언트를 사용할 때 한 번의 클릭으로 수행 할 수 있으므로 (빠른 시각적 검토를 수행 할 수 있음) 또는 명령 줄에서 수동으로 수행 할 수 있습니다.

학생 저장소는 출력 저장소 일 뿐이므로 항상 최신 상태를 유지합니다. 커밋을보고 (변경 사항 만 표시하기 때문에) 변경 사항을 학생들에게 명확하게 다루며 처리하기 쉽습니다.

한 단계 더 나아가서 스크립트를 자동 실행하는 git commit-hook을 작성하는 것입니다.

수정 : 게시물 수정을 참조하십시오.

분명히 나는 ​​학생들이 솔루션에 액세스하기를 원하지 않습니다 (과제가 끝날 때까지).

나는 그것이 분명하지만 완전하다고 생각합니다. 완료된 운동 주위의 태그를 제거하면 운동에 대한 일반적인 업데이트와 동일한 방법으로 답변이 게시됩니다.


git 부두와 함께이 작업을 수행 할 수 있기를 희망했지만 솔루션은 매우 실용적입니다.
Ken

@ Ken도 그것에 대해 생각하고 있었지만 잘못된 일을위한 약간 잘못된 도구입니다. 힘내는 병합, 업데이트 등을 수행하지만 일반적으로 코드를 선택하는 것은 아닙니다. 여러 컴퓨터에서 코드베이스의 일관성을 유지하는 데 좋습니다. 그래서 다른 솔루션을 사용하는 것입니다. 이 접근법에 대해 내가 좋아하는 것은 위험과 노동을 최소화하여 쉽게 따라갈 수 있다는 것입니다. 그리고 마지막으로, 어쨌든 학생들에게 좋은 모범을 보이기 위해 커밋 메시지를 학생 레포에 직접 써야합니다.)
Luc Franken

git이 변경 사항을 추적 할 수 있도록 교사 리포지토리에서 학생 브랜치를 만들 수 있도록하려면 병합 할 때 스크립트를 실행하십시오 (또는 마커 사이에있는 항목을 제거하여 직접 병합). 그런 다음 학생 브랜치를 로컬로 동기화하고 교사 원점 대신 학생 리포지토리로 푸시합니다. 이 방법으로 git은 변경 사항을 추적하고 기록을 한 리포지토리에서 다음 리포지토리로 올바르게 전달할 수있는 모양이 더 좋습니다. 두 세계의 최고. 나는 당신 에게이 마음을 시도하지 않았지만 왜 그것이 효과가 없을지 모르겠습니다.
Newtopian

1
시작 종료 태그를 제거한다는 아이디어를 제외하고는 이것을 좋아합니다. "solution"이라는 단어를 추가하여 엉망으로 만드는 것이 좋습니다.
candied_orange

@CandiedOrange 그것은 또한 좋은 것입니다, 그것에 동의하십시오. 솔루션은 또한 몇 가지 다른 형식을 허용하며 잊어 버린 태그와 솔루션을 게시해야한다는 실제 결정을 명확하게 구분합니다. @ newtopian : 나는 그것에 대해 생각하고 있었지만 충분한 이점을 보지 못했습니다. 또한 학생 출력이 완전히 다른 종류의 코드로보기로 결정했습니다. 실제 소스가 아니기 때문에 내가하지 않기로 결정했습니다. 교사 리포지토리의 지사와 함께 할 일은 다음과 같습니다. 준비가되면이를 마스터로 병합 한 다음 스크립트를 실행하십시오.
Luc Franken

6

넌 할 수있어

  • 상용구 코드를 커밋 한 경우 공개 GitHub 저장소를 만듭니다.
  • 이 저장소를 개인 GitHub 저장소로 포크
  • 갈래 저장소에서 과제 해결
  • 할당이 완료되면 각 솔루션을 공용 저장소에 병합

이 워크 플로를 구현하는 방법은 다음과 같습니다.

  • assignmentsGitHub에서 호스팅 되는 공개 repostory를 만듭니다 . 과제에 대한 상용구 코드를 추가하십시오. 예를 들어 각 과제에 대해 과제의 상용구 코드가 포함 된 새로운 하위 디렉토리를 소개합니다.
  • GitHub 에서 새 개인 저장소 assignments-solved를 작성하십시오 . assignments머신 에서 리포지를 복제하고 이를 assignments-solved 리포지토리로 푸시하십시오 (본질적으로 자체 저장소로 개인 사본으로 포크). 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-solved저장소에 원격으로 저장소를 추가하십시오 assignments. cd assignments # change to the assignments repo on your machine git remote add solutions https://github.com/[user]/assignments-solved
  • assignments-solved저장소 에서 각 지정을 구현하십시오 . 각 커밋에 하나의 할당 변경 사항 만 포함되어 있는지 확인하십시오.
  • 원래 할당이 변경되지 않도록 리포지토리에 solved분기 를 만들 수 있습니다 assignments. cd assignments # change to the assignments repo on your machine git branch -b solutions git push -u origin
  • 솔루션을에 게시 하려면 솔루션 및 솔루션이 포함 된 커밋을 assignments가져옵니다 . 어디 들어있는이 솔루션의 커밋합니다.solvedcherry-pick cd assignments # change to the assignments repo on your machine git checkout solved git fetch solutions git cherry-pick [commithash] [commithash]

assignments-solved리포지토리 의 별도 분기에 각 할당을 구현 한 다음 리포지토리에서 풀 요청을 만들어 워크 플로를 구현할 수도 있습니다 assignments. 그러나 assignments-solvedrepo가 실제 포크 가 아니기 때문에 이것이 GitHub에서 작동하는지 확실 하지 않습니다 .


비슷한 방법을 사용하여 프로그래밍 테스트를 제출 된 답변과 분리했습니다. 필자의 경우 제출 된 솔루션은 개인 클론의 개별 분기에 추가되고 공개 리포지토리로 다시 병합되지 않습니다. 시간이 지남에 따라 진화함에 따라 각 응시자가 해결 한 테스트 버전을 확인할 수있는 이점도 있습니다.
axl

0

.gitignore리포지토리의 파일을 암호화하고 암호화 하는 유틸리티를 제안 할 수 있습니다 . 워크 플로는 사용하기가 약간 어렵지만 파일의 암호화 된 상대방을 다른 비 비밀 파일과 함께 작업 복사본에서 사용할 수있게하여 평소처럼 git으로 파일을 추적 할 수 있습니다.

#!/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는 파일의 이전 상태와 현재 상태 사이의 암호화 된 diff를 파일 a.txt끝에 추가 a.txt.sshare합니다.

암호화 된 파일이있는 리포지토리를 가져 오거나 풀한 후에는 ( "load") 키를 sshare사용하여 각 파일에 대한 유틸리티를 실행해야합니다 -l. 이 경우 유틸리티 는 작업 복사본에서 git*.sshare의해 추적되지 않은 텍스트 파일로 파일을 해독 합니다 .

비밀 파일마다 다른 암호를 사용할 수 있습니다.

이 유틸리티는 트랙에 자식이 (효율적으로 변경 할 수 있습니다 DIFF.sshare파일은 단순히 한 줄입니다).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.