estratégia do github para manter uma versão do arquivo privada

11

Eu sou um professor escrevendo problemas de codificação para os alunos. O que eu quero fazer é fornecer aos alunos o código padrão com espaços reservados para as funções que os alunos devem concluir. Darei aos alunos acesso a um repositório privado do github para clonar isso.

No entanto, também quero uma versão da base de código, completa com soluções de amostra. Obviamente, não quero que os alunos tenham acesso à solução (até que a tarefa termine).

Eu pensei em ramificações, mas AFAIK, não posso manter uma ramificação privada.

Talvez eu possa dividir o projeto em outro repositório particular, mas não tenho certeza de como manter os projetos em snyc (além do arquivo que contém a solução).

Existe um fluxo de trabalho para esta situação?

Ken
fonte
1
Acho que não. Mas o que você faz frio: o delcare faz interface para todos os métodos a serem implementados. No seu repositório público de alunos, crie classes implementando essas interfaces com os corpos vazios do método. Mantenha as soluções em um repositório privado separado. Isso não resolve completamente o problema de sincronização, mas o reduz ao escopo das tarefas.
marstato 14/09/16
você procurou usar a API do github para controlar o acesso às filiais?

Respostas:

8

O que poderia ser bastante factível:

  • Crie 2 repositórios: aluno e professor.
  • Clone-os na sua máquina (pode ser feito com o cliente Github)
  • Você trabalha apenas com professor , nunca toque em aluno.

Portanto, sua estrutura de diretórios é composta por 2 repositórios git clonados:

  • / student (com uma pasta .git)
  • / teacher (com uma pasta .git)

Você coloca marcadores em torno do código "privado" nos comentários para o seu idioma, exemplo javascript abaixo. Os marcadores indicam onde o código privado inicia e termina.

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

Em seguida, faça um script simples em sua máquina local:

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

Ele irá: pegar todos os seus arquivos e copiar o conteúdo para / student (sobrescrevendo) sem as partes marcadas particulares do código. Se você quiser, pode inserir linhas vazias lá, mas isso pode dar uma dica sobre que tipo de solução você espera.

É um exemplo de código não testado, portanto é provável que você precise fazer alguma depuração.

Agora, a única coisa que você precisa fazer é confirmar e enviar o repositório do aluno quando estiver satisfeito com a saída. Isso pode ser feito com um clique ao usar o cliente GitHub (para que você possa fazer uma revisão visual rápida) ou apenas manualmente na linha de comando.

O repositório de alunos é apenas um repositório de saída, para que ele fique sempre atualizado, é claro para os alunos o que mudou observando os commits (porque eles apenas mostram alterações) e é simples de manusear.

Um passo adiante seria criar um gancho de confirmação do git que executa automaticamente seu script.

Editar: veja que você fez uma edição em sua postagem:

Obviamente, não quero que os alunos tenham acesso à solução (até que a tarefa termine).

Suspeito que esteja claro, mas deve estar completo: basta remover as tags em torno do exercício final e publicará a resposta da mesma maneira que faria nas atualizações normais dos exercícios.

Luc Franken
fonte
estava esperando que eu pudesse fazer isso com algum git voodoo, no entanto, sua solução é muito prática.
Ken
@ Ken estava pensando nisso também, mas é uma ferramenta um pouco errada para o trabalho errado. O Git mescla, atualiza etc, mas em geral não é a ideia selecionar o código. É bom em manter sua base de código consistente em várias máquinas. Por isso, pensei em outra solução. O que eu também gosto nessa abordagem é que ela minimiza riscos e trabalho, por isso é fácil acompanhá-la. E, no final, você deve escrever sua mensagem de confirmação para o repo do aluno manualmente, de qualquer maneira, para dar um bom exemplo aos seus alunos;)
Luc Franken
Para ajudar o git a acompanhar as alterações que você pode fazer na ramificação de um aluno no repositório de professores, execute o script ao mesclar (ou mesclar manualmente, removendo qualquer coisa entre os marcadores). Em seguida, sincronize o ramo do aluno localmente e envie-o ao repositório do aluno, em vez da origem do professor. Dessa forma, o git estaria em melhor forma para rastrear as alterações e ter o histórico encaminhado adequadamente de um repo para o próximo. Melhor dos dois mundos. Eu não tentei essa mente, mas não vejo por que não funcionaria.
Newtopian 14/09/16
1
Gosto disso, exceto pela ideia de remover as tags de início e fim. Melhor alterá-los adicionando a palavra "solução".
Candied_orange 14/09/16
@CandiedOrange que também é legal, concorda com isso. A solução também permitiria alguma formatação diferente e diferencia claramente entre tags esquecidas e a decisão real de que a solução deve ser publicada. @ newtopian: Eu estava pensando sobre isso, mas não vi vantagens suficientes. Também decidi ver a saída do aluno como um tipo totalmente diferente de código. Não é a fonte real, então decidi não. O que eu faria com as filiais no repositório de professores é por exemplo: Trabalho nas tarefas do próximo semestre. Quando estiver pronto, mescle-os para dominar e, em seguida, execute o script.
Luc Franken
6

Você poderia

  • Crie um repositório público do GitHub em que você submeteu o código padrão
  • Bifurcar este repositório como um repositório particular do GitHub
  • Resolver as atribuições no repositório bifurcado
  • Mesclar cada solução no repositório público quando a atribuição for concluída

É assim que eu implementaria esse fluxo de trabalho:

  • Crie um repositório público assignmentshospedado no GitHub. Adicione o código padrão para as atribuições. Por exemplo, para cada tarefa, você introduz um novo subdiretório que contém o código padrão da tarefa.
  • Crie um novo repositório privadoassignments-solved no GitHub. Clone o repositório assignmentsem sua máquina e envie-o para o assignments-solvedrepositório (essencialmente bifurque seu próprio repositório como uma cópia privada): 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
  • Adicione o assignments-solvedrepositório como remoto ao assignmentsrepositório: cd assignments # change to the assignments repo on your machine git remote add solutions https://github.com/[user]/assignments-solved
  • Implemente cada atribuição no assignments-solvedrepositório. Verifique se cada confirmação contém apenas alterações de uma atribuição.
  • Convém criar uma solvedramificação no assignmentsrepositório, para que as atribuições originais não sejam alteradas: cd assignments # change to the assignments repo on your machine git branch -b solutions git push -u origin
  • Quando você deseja publicar uma solução no assignments, busque o solvedcontrole remoto e cherry-pickas confirmações que contêm as soluções. cd assignments # change to the assignments repo on your machine git checkout solved git fetch solutions git cherry-pick [commithash] Onde [commithash]contém o commit da sua solução.

Você também pode implementar o fluxo de trabalho implementando cada atribuição em uma ramificação separada do assignments-solvedrepositório e, em seguida, criando uma solicitação de assignmentsrecebimento no repositório. Mas não tenho certeza se isso funcionará no GitHub, pois o assignments-solvedrepositório não é um verdadeiro fork.

Gaste
fonte
Utilizei com sucesso um método semelhante para separar um teste de programação das respostas enviadas. No meu caso, as soluções enviadas são adicionadas a ramificações individuais de um clone privado e nunca são mescladas novamente ao repositório público. Tem o benefício adicional de me deixar ver qual versão do teste cada candidato resolveu, à medida que evolui com o tempo.
Axl
0

Apenas posso propor a você um utilitário destinado a criar .gitignoree criptografar arquivos em seu repositório. O fluxo de trabalho é um pouco difícil de usar, mas disponibiliza as cópias criptografadas de seus arquivos na cópia de trabalho junto com outros arquivos não secretos, o que permite rastreá-los pelo git, como de costume.

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

Para criar um arquivo secreto com o a.txttipo de nome de arquivo sshare -a a.txt. Utilitário criar arquivo a.txte arquivo adicionado a .gitignore. Em seguida, ele cria uma contraparte criptografada do "banco de dados" a.txt.sshareadicionando .sshareextensão ao nome do arquivo.

Então você pode preencher a.txtcom algum texto. Para salvar seu estado imediatamente antes do git committipo sshare -s a.txt, o utilitário solicita uma senha para criptografar o novo estado do arquivo a.txt. Em seguida, o utilitário usando essa senha adiciona diferenças criptografadas entre o estado anterior e o atual do arquivo a.txtno final do a.txt.ssharearquivo.

Após buscar / puxar o repositório com arquivos criptografados, você deve executar o sshareutilitário para cada arquivo usando a chave -l("load"). Nesse caso, o utilitário descriptografa *.ssharearquivos para arquivos de texto não rastreados pelo git na cópia de trabalho.

Você pode usar senhas diferentes para cada arquivo secreto.

O utilitário permite que o git rastreie as alterações com eficiência (a diferença de .ssharearquivos é simplesmente uma linha).

Tomilov Anatoliy
fonte