머신 특정 구성 파일 커밋


82

개발할 때 일반적인 시나리오는 코드베이스에 시스템 특정 설정이 필요한 여러 구성 파일이 있다는 것입니다. 이러한 파일은 Git에 체크인되고 다른 개발자는 항상 실수로 다시 체크인하여 다른 사람의 구성을 손상시킵니다.

이에 대한 간단한 해결책은 Git에 체크인하지 않거나 추가로 .gitignore 항목을 추가하는 것입니다. 그러나 개발자가 필요에 맞게 수정할 수있는 파일에 합리적인 기본값을 갖는 것이 훨씬 더 우아하다는 것을 알았습니다.

이러한 파일로 Git을 멋지게 재생하는 우아한 방법이 있습니까? 컴퓨터 별 구성 파일을 수정 한 다음 해당 파일을 확인하지 않고 "git commit -a"를 실행할 수 있기를 바랍니다.


1
이것은 당신의 디자인과 동료의 두뇌에 문제가있는 것처럼 들립니다. 그들이 소스 제어 시스템에 어떤 일을하고 있는지 알고 있는지 확인하라고 말하십시오. 그렇지 않으면 그들이 원하지 않는 쓰레기를 검사 할 것입니다. 또한 : 각 시스템에 대해 파일을 하나씩 분할하지 않는 이유는 무엇입니까?
포드

11
이것이 상당히 일반적인 시나리오라고 확신합니까? 기계 별 구성을 어떻게 추적합니까? 각 시스템에 대해 파일을 분할하는 것은 매우 지저분하고 분산 된 버전 제어의 목적을
무력화하는 것 같습니다

1
모든 공유 저장소에 대해 사전 업데이트 후크를 사용하여 중단 커밋을 가져 오는 것을 최소한 방지 할 수 있습니다. 특정 개발자가 만든 구성 파일을 수정하는 커밋을 찾거나 메시지에서 특수 키워드에 대해 언급하지 않은 해당 파일에 대한 커밋을 찾을 수 있습니다.
Phil Miller

2
+1, 이것은 이다 일반적인 문제. @Pod : 저장소에 "Joe.conf"를 포함하는 것은 실용적이지 않지만 가끔 업데이트 할 수 있기를 원합니다. 때로는 코드 변경으로 인해 구성이 변경되어야합니다.
Thanatos 2010-06-08

답변:


59

프로그램이 설정을 위해 한 쌍의 구성 파일을 읽도록하십시오. 첫째, config.defaults저장소에 포함될 파일을 읽어야 합니다. 그런 다음 다음에 config.local나열되어야 하는 파일을 읽어야합니다 ..gitignore

이 배열을 사용하면 새 설정이 기본 파일에 나타나고 업데이트되는 즉시 적용됩니다. 재정의 된 경우에만 특정 시스템에서 달라집니다.

이것에 대한 변형으로, config버전 제어에서 제공 하는 일반 파일 만 가질 수 있으며 include config.local컴퓨터 특정 값을 가져 오는 것과 같은 작업을 수행 하도록 할 수 있습니다. 이는 코드에보다 일반적인 메커니즘 (정책 대비)을 도입하고 결과적으로 더 복잡한 구성을 가능하게합니다 (응용 프로그램에 적합한 경우). 많은 대규모 오픈 소스 소프트웨어에서 볼 수있는이 확장은 to입니다 include conf.d. 이는 디렉토리의 모든 파일에서 구성을 읽습니다.

비슷한 질문에 대한 내 대답참조하십시오 .


나는 이것에 답을 줄 것이다. 이 방법은 응용 프로그램 부분에 추가 논리가 필요하다는 유일한 단점을 제외하고 원하는 효과를 얻습니다.
ghempton

17

시도해 볼 수 있습니다 git update-index --skip-worktree filename. 이것은 git에게 파일 이름에 대한 로컬 변경 사항이 존재하지 않는 척하도록 지시하므로 git commit -a무시합니다. 또한 저항 git reset --hard할 수 있는 추가 이점이 있으므로 실수로 로컬 변경 사항을 잃지 않을 것입니다. 또한 파일이 업스트림으로 변경되면 자동 병합이 정상적으로 실패합니다 (작업 디렉터리 복사본이 인덱스 복사본과 일치하지 않는 경우 자동으로 업데이트 됨). 단점은 관련된 모든 컴퓨터에서 명령을 실행해야한다는 점이며이를 자동으로 수행하는 것은 어렵습니다. git update-index --assume-unchanged이 아이디어의 미묘하게 다른 버전을 참조하십시오 . 둘 다에 대한 자세한 내용은 git help update-index.


이러한 명령에 대한 자세한 내용은 'assume-unchanged'와 'skip-worktree'의 차이점 질문에서 찾을 수 있습니다 . 로부터 최고 대답 당신이 원하는 것처럼, 그것은 보이는 --skip-worktree이 경우.
Senseful

10

또 다른 접근 방식은 다른 개인 브랜치의 공통 구성 파일에 대한 로컬 변경을 유지하는 것입니다. 여러 로컬 변경이 필요한 일부 프로젝트에 대해이 작업을 수행합니다. 이 기술이 모든 상황에 적용되는 것은 아니지만 어떤 경우에는 저에게 효과적입니다.

먼저 마스터 브랜치를 기반으로 새 브랜치를 만듭니다.

git checkout -b work master

이제 필요에 따라 구성 파일을 수정하고 커밋합니다. 나는 보통 "NOCOMMIT"또는 "PRIVATE"와 같은 커밋 메시지에 독특한 것을 넣습니다 (나중에 유용 할 것입니다). 이 시점에서 자신의 구성 파일을 사용하여 비공개 브랜치에서 작업 할 수 있습니다.

작업을 다시 업스트림으로 푸시하려면 work브랜치에서 마스터로 각 변경 사항을 선택하십시오 . 이 작업을 수행하는 데 도움이되는 스크립트가 있는데 다음과 같습니다.

#!/bin/sh

BRANCH=`git branch | grep ^\\* | cut -d' ' -f2`
if [ $BRANCH != "master" ]; then
  echo "$0: Current branch is not master"
  exit 1
fi

git log --pretty=oneline work...master | grep -v NOCOMMIT: | cut -d' ' -f1 | tac | xargs -l git cherry-pick

이 첫 번째는 내가 master지점 에 있는지 확인합니다 (건전성 확인). 그런 다음에 각 커밋을 나열 work하고 NOCOMMIT 키워드를 언급하는 커밋을 필터링하고 순서를 반대로 한 다음 마지막으로 각 커밋 (이제 가장 오래된 커밋부터)을 master.

마지막으로 마스터 업스트림에서 변경 사항을 work적용한 후 다시 전환하여 리베이스합니다.

git checkout work
git rebase master

Git은 work브랜치 의 각 커밋을 다시 적용 master하여 체리 피킹 을 통해 이미 적용된 커밋을 효과적으로 건너 뜁니다 . 남은 것은 NOCOMMIT 로컬 커밋뿐입니다.

이 기술은 푸시 프로세스에 시간이 좀 더 걸리지 만 문제가 해결 되었기 때문에 공유 할 것이라고 생각했습니다.


2
질문을하지 않는 사람에게이 작업을 요청한다는 것을 알고 있습니까? git commit -a세상에 신경 쓰지 않고 달리는 사람 ?
Phil Miller

같은 전략에 따라, 당신은 당신이 당신의 지역 설정 파일을 설정 여기서 커밋 태그와의 조합을 사용할 수 git rebase --ontogit fetch같은 일을 할
다닐 수자 모라에스

8

한 가지 가능성은 .gitignore에 실제 파일이 있지만 확장자가 다른 기본 구성을 체크인하는 것입니다. Rails 앱의 일반적인 예는 config / database.yml 파일입니다. config / database.yml.sample을 확인하고 각 개발자는 이미 .gitignored 인 자체 config / database.yml을 만듭니다.


예, 이것은 점진적인 개선이지만 체크인 된 버전이 의도적으로 변경되면 개발자 구성 파일에 반영되지 않기 때문에 여전히 최적이 아닙니다. 유용 할 때의 예는 새 속성이 추가되는 경우입니다.
ghempton

그것은 좋은 커밋 노트와 속성이 설정되지 않았을 때 불평하는 설명적인 오류 메시지로 해결 될 수 있습니다. 또한 변경 사항에 대해 팀에 알리는 이메일이 도움이됩니다.
Brian Kelly

이 솔루션 및 좋은 예에 대한 자세한 내용은 이 답변을 참조하십시오 .
Senseful

1

다른 확장자 (예 : .default)로 기본 구성을 체크인하고, symlink를 사용하여 기본값을 올바른 위치로 심볼릭 링크하고, 올바른 위치를 .gitignore에 추가하고, 구성과 관련된 다른 모든 것을 .gitignore에 추가합니다 (따라서 유일한 체크인되는 것은 config.default)입니다.

또한 응용 프로그램 전체에 대한 심볼릭 링크를 설정하는 빠른 설치 스크립트를 작성하십시오.

우리는 이전 회사에서 비슷한 접근 방식을 사용했습니다. 설치 스크립트는 실행중인 환경 (샌드 박스, 개발, QA, 프로덕션)을 자동으로 감지하고 자동으로 올바른 작업을 수행합니다. config.sandbox 파일이 있고 샌드 박스에서 실행중인 경우 해당 파일이 연결됩니다 (그렇지 않으면 .defaults 파일 만 연결됨). 일반적인 절차는 .defaults를 복사하고 필요에 따라 설정을 변경하는 것이 었습니다.

설치 스크립트를 작성하는 것은 상상하는 것보다 쉬우 며 많은 유연성을 제공합니다.


1

베스트 답변에 동의하지만 추가하고 싶습니다. ANT 스크립트를 사용하여 GIT 저장소에서 파일을 제거하고 수정하므로 프로덕션 파일을 덮어 쓰지 않습니다. ANT에는 java-property 파일을 수정하는 좋은 옵션이 있습니다. 즉, 로컬 테스트 변수를 자바 스타일 속성 파일에 넣고 코드를 추가하여 처리하지만 FTP를 온라인으로 보내기 전에 사이트 구축을 자동화 할 수있는 기회를 제공합니다. 일반적으로 프로덕션 정보를 site.default.properties 파일에 넣고 ANT가 설정을 관리하도록합니다. 로컬 설정은 site.local.properties에 있습니다.

    <?php
/**
 * This class will read one or two files with JAVA style property files. For instance site.local.properties & site.default.properties
 * This will enable developers to make config files for their personal development environment, while maintaining a config file for 
 * the production site. 
 * Hint: use ANT to build the site and use the ANT <propertyfile> command to change some parameters while building.
 * @author martin
 *
 */
class javaPropertyFileReader {

    private $_properties;
    private $_validFile;

    /**
     * Constructor
     * @return javaPropertyFileReader
     */
    public function   __construct(){
        $this->_validFile = false;
        return $this;
    }//__construct

    /**
     * Reads one or both Java style property files
     * @param String $filenameDefaults
     * @param String $filenameLocal
     * @throws Exception
     * @return javaPropertyFileReader
     */
    public function readFile($filenameDefaults, $filenameLocal = ""){

        $this->handleFile($filenameDefaults);
        if ($filenameLocal != "") $this->handleFile($filenameLocal);
    }//readFile

    /**
     * This private function will do all the work of reading the file and  setting up the properties
     * @param String $filename
     * @throws Exception
     * @return javaPropertyFileReader
     */
    private function handleFile($filename){

    $file = @file_get_contents($filename);

    if ($file === false) {
         throw (New Exception("Cannot open property file: " . $filename, "01"));
    }
    else {
        # indicate a valid file was opened
        $this->_validFile = true;

        // if file is Windows style, remove the carriage returns
        $file = str_replace("\r", "", $file);

        // split file into array : one line for each record
        $lines = explode("\n", $file);

        // cycle lines from file
        foreach ($lines as $line){
            $line = trim($line);

            if (substr($line, 0,1) == "#" || $line == "") {
                #skip comment line
            }
            else{
                // create a property via an associative array
                $parts   = explode("=", $line);
                $varName = trim($parts[0]);
                $value   = trim($parts[1]);

                // assign property
                $this->_properties[$varName] = $value;
            }
        }// for each line in a file
    }
    return $this;
    }//readFile

    /**
     * This function will retrieve the value of a property from the property list.
     * @param String $propertyName
     * @throws Exception
     * @return NULL or value of requested property
     */
    function getProperty($propertyName){
        if (!$this->_validFile) throw (new Exception("No file opened", "03"));

        if (key_exists($propertyName, $this->_properties)){
            return $this->_properties[$propertyName];
        }
        else{
          return NULL;
        }
    }//getProperty

    /**
     * This function will retreive an array of properties beginning with a certain prefix.
     * @param String $propertyPrefix
     * @param Boolean $caseSensitive
     * @throws Exception
     * @return Array
     */
    function getPropertyArray($propertyPrefix, $caseSensitive = true){
        if (!$this->_validFile) throw (new Exception("No file opened", "03"));

        $res = array();

        if (! $caseSensitive) $propertyPrefix= strtolower($propertyPrefix);

        foreach ($this->_properties as $key => $prop){
            $l = strlen($propertyPrefix);

            if (! $caseSensitive) $key = strtolower($key);

            if (substr($key, 0, $l ) == $propertyPrefix) $res[$key] = $prop;
        }//for each proprty

        return $res;
    }//getPropertyArray

    function createDefineFromProperty($propertyName){
        $propValue = $this->getProperty($propertyName);
        define($propertyName, $propValue);
    }//createDefineFromProperty


    /**
     * This will create a number of 'constants' (DEFINE) from an array of properties that have a certain prefix.
     * An exception is thrown if 
     * @param  String $propertyPrefix
     * @throws Exception
     * @return Array The array of found properties is returned.
     */
    function createDefinesFromProperties($propertyPrefix){
        // find properties
        $props = $this->getPropertyArray($propertyPrefix);

        // cycle all properties 
        foreach($props as $key => $prop){

            // check for a valid define name
            if (preg_match("'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'", $key)) {
                define($key, $prop);
            }   
            else{
                throw (new Exception("Invalid entry in property file: cannot create define for {" . $key . "}", "04"));
            }   
        }// for each property found

        return $props;
    }//createDefineFromProperty

}//class javaPropertyFileReader

그런 다음 사용하십시오.

  $props = new javaPropertyFileReader();
  $props->readFile($_SERVER["DOCUMENT_ROOT"] . "/lib/site.default.properties",$_SERVER["DOCUMENT_ROOT"] . "/lib/site.local.properties");

  #create one DEFINE
  $props->createDefineFromProperty("picture-path");

  # create a number of DEFINEs for enabled modules
  $modules = $props->createDefinesFromProperties("mod_enabled_");

site.default.properties는 다음과 같습니다.

release-date=x
environment=PROD
picture-path=/images/

SITE_VERSION_PRODUCTION=PROD
SITE_VERSION_TEST=TEST
SITE_VERSION_DEVELOP=DEV

# Available Modules
mod_enabled_x=false
mod_enabled_y=true
mod_enabled_z=true

귀하의 site.local.properties는 다음과 같습니다 (차이 환경 및 활성화 된 모듈에 유의하십시오).

release-date=x
environment=TEST
picture-path=/images/

SITE_VERSION_PRODUCTION=PROD
SITE_VERSION_TEST=TEST
SITE_VERSION_DEVELOP=DEV

# Available Modules
mod_enabled_x=true
mod_enabled_y=true
mod_enabled_z=true

그리고 ANT 지침 : ($ d {deploy}가 배포 대상 디렉터리 임)

<propertyfile
    file="${deploy}/lib/site.properties"
    comment="Site properties">
    <entry  key="environment" value="PROD"/>
    <entry  key="release-date" type="date" value="now" pattern="yyyyMMddHHmm"/>
</propertyfile>

1

요즘 (2019) 저는 예를 들어 python / django에서 ENV vars를 사용하며 기본값을 추가 할 수도 있습니다. docker의 컨텍스트에서 ENV vars를 docker-compose.yml 파일 또는 버전 제어에서 무시되는 추가 파일에 저장할 수 있습니다.

# settings.py
import os
DEBUG = os.getenv('DJANGO_DEBUG') == 'True'
EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST', 'localhost')

0

가장 간단한 해결책은 파일을 기본값으로 편집하고 커밋 한 다음 .gitignore. 이런 식으로 개발자는을 할 때 실수로 커밋하지 git commit -a않지만 기본값을 변경하려는 경우 (아마도 드물게) 커밋 할 수 있습니다.git add --force .

그러나 시스템 별 구성을 가진 모든 사용자가 자신의 구성을 깨지 않고도 기본값을 변경할 수 있기 때문에 .default.local구성 파일을 갖는 것이 궁극적으로 최상의 솔루션입니다.


이것은 작동하지 않습니다. 파일을 추적하고 .gitignore나중에 추가 해도 변경 사항은 계속 추적됩니다.
Zeemee

0

기본 및 로컬 구성 파일을 사용하여 여기에서 권장하는 것처럼 수행합니다. 프로젝트에있는 로컬 설정 파일을 관리하기 위해 .gitignoregit repo를 만들었습니다 ~/settings. 거기에서 모든 프로젝트의 모든 로컬 설정을 관리합니다. 예를 들어 폴더 project1~/settings만들고이 프로젝트에 대한 모든 로컬 구성 항목을 여기에 넣습니다. 그 후 해당 파일 / 폴더를 project1.

이 방법을 사용하면 로컬 구성 파일을 추적 할 수 있으며 일반 소스 코드 저장소에 넣지 않아도됩니다.


0

@Greg Hewgill의 답변을 기반으로 로컬 변경 사항으로 특정 커밋을 추가하고 localchange로 태그를 지정할 수 있습니다.

git checkout -b feature master
vim config.local
git add -A && git commit -m "local commit" && git tag localchange

그런 다음 기능의 커밋을 추가합니다. 작업을 마친 후 다음을 수행하여 로컬 변경 커밋없이이 분기를 마스터에 다시 병합 할 수 있습니다.

git rebase --onto master localchange feature
git fetch . feature:master
git cherry-pick localchange
git tag localchange -f

이 명령은 다음을 수행합니다.

1) localchange 커밋을 무시하고 기능 브랜치를 마스터로 리베이스하십시오. 2) 기능 브랜치를 떠나지 않고 마스터 빨리 감기 3) 기능 브랜치의 맨 위에 localchange 커밋을 다시 추가하여 작업을 계속할 수 있습니다. 계속 작업하려는 다른 브랜치에 대해이 작업을 수행 할 수 있습니다. 4) localchange 태그를 체리 선택 커밋으로 재설정하여 사용할 수 있습니다.rebase --onto 같은 방식으로 다시 합니다.

이것은 가장 일반적인 해결책으로 받아 들여진 대답을 대체하기위한 것이 아니라 문제에 대해 상자 밖에서 생각하는 방법입니다. 당신은 기본적으로 실수 단지에서 리베이스에 의해 마스터에 로컬 변경 사항을 병합 방지 localchangefeature빠르게 전달 마스터.

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