github策略,用于保持文件的一个版本私有


11

我是一位为学生编写编码问题的讲师。我要做的是给学生提供样板代码,并带有占位符以表示学生要完成的功能。我将为学生提供访问私人github存储库的权限,以克隆它。

但是,我还想要一个版本的代码库,以及示例解决方案。显然,我不希望学生可以使用该解决方案(直到作业结束)。

我已经考虑过分支机构,但是AFAIK,我不能将一个分支机构设为私有。

也许我可以将项目分叉到另一个私有仓库中,但是不确定如何将项目保留在snyc中(除了包含解决方案的文件之外)。

有针对这种情况的工作流程吗?


1
我不这么认为。但是您要做什么呢:delcare接口可以实现所有要实现的方法。在您的学生公共仓库中,创建使用空方法主体实现这些接口的类。在单独的私人仓库中维护解决方案。这不能完全解决您的同步问题,但是可以将其缩小到任务范围。
marstato

您是否考虑过使用github API来控制对分支的访问?

Answers:


8

什么可能是完全可行的:

  • 创建2个存储库:学生和老师。
  • 将它们克隆到您的计算机上(可以使用Github客户端完成)
  • 仅在老师中工作,永远不会碰学生。

因此,您的目录结构是2个克隆的git repo:

  • /学生(带有.git文件夹)
  • / teacher(带有.git文件夹)

您可以在语言的注释中的“私有”代码周围加上标记,例如下面的javascript。标记指示私有代码的开始和结束位置。

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 voodoo做到这一点,但是您的解决方案非常实用。
肯(Ken)

@Ken也在考虑这一点,但这对于完成错误的工作来说是个错误的工具。Git确实会合并,更新等,但是总的来说,选择代码并不是一个主意。它可以使您的代码库在多台计算机上保持一致。这就是为什么我要提出另一种解决方案的原因。我也喜欢这种方法,因为它可以最大程度地减少风险和人工,因此很容易跟上它。并且,最后,您还是应该手动将提交消息写到学生回购中,为学生提供一个很好的例子;)
Luc Franken

为了帮助git跟踪更改,您可以在教师仓库中创建学生分支,在合并时运行脚本(或手动合并以删除标记之间的任何内容)。然后在本地同步学生分支,并将其推送到学生仓库,而不是教师来源。这样,git可以更好地跟踪更改,并将历史记录从一个仓库正确转发到下一个仓库。两全其美。我没有尝试过这个想法,但是我不明白为什么它不起作用。
Newtopian

1
除了删除开始结束标签的想法外,我喜欢这个。最好通过添加“解决方案”一词来操纵它们。
candied_orange

@CandiedOrange也是一个不错的选择,对此表示同意。解决方案还将允许某些不同的格式,并且可以清楚地区分被遗忘的标签和应发布解决方案的实际决定。// @ newtopian:我当时正在考虑,但是我没有看到足够的优势。我还决定将学生的输出视为完全不同的代码。它不是真正的来源,所以我决定不这样做。例如,我对教师回购中的分支的处理是:处理下学期的任务。准备就绪后,可以将它们合并到母版中,然后运行脚本。
吕克·弗兰肯

6

你可以

  • 提交样板代码时,创建一个公共的GitHub Repostory
  • 分叉此存储库作为私有GitHub重新发布记录
  • 解决分叉存储库中的分配
  • 完成分配后,将每个解决方案合并到公共存储库中

这就是我实现此工作流程的方式:

  • 创建assignments托管在GitHub上的公共目录。添加分配的样板代码。例如,对于每个作业,您都会引入一个新的子目录,其中包含该作业的样板代码。
  • 在GitHub上创建一个新的私有存储库assignments-solvedassignments在计算机上克隆 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,请获取 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-solved,然后在存储库中创建拉取请求来实现工作流assignments。但是我不确定这是否可以在GitHub上使用,因为该assignments-solved仓库不是真正的分支。


我已经成功地使用了类似的方法将编程测试与提交的答案分开。在我的情况下,提交的解决方案将添加到私有克隆的各个分支中,并且永远不会合并回公共仓库。随着时间的推移,它还使我看到了每个候选人所解决的测试版本,这具有额外的好处。
axl

0

我只是可以向您提出一个实用程序,该实用程序旨在对.gitignore存储库中的文件进行-ing和加密。工作流程有点难以使用,但是它使您的文件的加密副本与其他非机密文件一起在工作副本中可用,从而可以照常通过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。然后,使用此密码的实用程序将文件的先前状态和当前状态之间的加密差异添加到文件a.txt末尾a.txt.sshare

在获取/拉出带有加密文件的存储库之后,您应该sshare使用-l(“加载”)密钥为每个文件运行实用程序。在这种情况下,实用程序会将*.sshare文件解密为工作副本中git无法跟踪的文本文件。

您可以为每个秘密文件使用不同的密码。

此程序可允许GIT中轨道有效地改变(DIFF.sshare文件是简单的一行)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.