Cam kết các tệp cấu hình cụ thể của máy


82

Một tình huống phổ biến khi tôi phát triển là cơ sở mã sẽ có một số tệp cấu hình yêu cầu cài đặt cụ thể cho máy. Các tệp này sẽ được kiểm tra trong Git và các nhà phát triển khác sẽ luôn vô tình kiểm tra lại chúng và phá vỡ cấu hình của người khác.

Một giải pháp đơn giản cho điều này là chỉ cần không đăng ký chúng vào Git, hoặc thậm chí thêm một mục .gitignore cho chúng. Tuy nhiên, tôi thấy rằng sẽ đẹp hơn nhiều nếu có một số mặc định hợp lý trong tệp mà nhà phát triển có thể sửa đổi cho phù hợp với nhu cầu của mình.

Có cách nào hữu ích để làm cho Git chơi tốt với các tệp như vậy không? Tôi muốn có thể sửa đổi tệp cấu hình dành riêng cho máy và sau đó có thể chạy "git commit -a" mà không cần kiểm tra tệp đó.


1
Điều này có vẻ như bạn có vấn đề trong thiết kế của mình và trong bộ não của đồng nghiệp. Hãy nói với họ để đảm bảo rằng họ biết những gì họ đang cam kết trong một hệ thống kiểm soát nguồn, nếu không họ sẽ kiểm tra những thứ mà bạn không muốn. Ngoài ra: Tại sao không chỉ chia nhỏ tệp, một tệp cho mỗi hệ thống?
Pod

11
Tôi khá chắc rằng đây là một kịch bản khá phổ biến? Làm thế nào để bạn theo dõi cấu hình cụ thể của máy? Tách các tập tin cho mỗi hệ thống dường như lộn xộn khá và loại đánh bại mục đích của phân phối có điều khiển phiên bản: nếu nó được kiểm tra ra trên một máy mới không nên cần phải là một tập tin mới kiểm tra trong.
ghempton

1
Bạn có thể ít nhất có thể ngăn chặn việc thực hiện các cam kết vi phạm bằng cách sử dụng móc cập nhật trước trên bất kỳ kho lưu trữ được chia sẻ nào mà tất cả bạn đẩy đến. Nó có thể tìm kiếm các cam kết sửa đổi tệp cấu hình do một số nhà phát triển nhất định thực hiện hoặc nó có thể tìm kiếm các cam kết chạm vào tệp đó mà không đề cập đến từ khóa đặc biệt trong thư.
Phil Miller

2
+1, đây một vấn đề phổ biến. @Pod: Không thực tế khi có "Joe.conf" trong repo, nhưng bạn vẫn muốn có thể cập nhật mọi thứ ... đôi khi cấu hình phải thay đổi do thay đổi trong mã.
Thanatos

Câu trả lời:


59

Yêu cầu chương trình của bạn đọc một cặp tệp cấu hình cho cài đặt của nó. Đầu tiên, nó sẽ đọc một config.defaultstệp sẽ được đưa vào kho lưu trữ. Sau đó, nó sẽ đọc một config.localtệp được liệt kê trong.gitignore

Với sự sắp xếp này, các cài đặt mới sẽ xuất hiện trong tệp mặc định và có hiệu lực ngay khi được cập nhật. Chúng sẽ chỉ thay đổi trên các hệ thống cụ thể nếu chúng bị ghi đè.

Như một biến thể của điều này, bạn có thể chỉ có một configtệp chung mà bạn gửi trong quyền kiểm soát phiên bản và yêu cầu nó làm một cái gì đó giống như include config.localmang lại các giá trị dành riêng cho máy. Điều này giới thiệu một cơ chế chung hơn (so với chính sách) trong mã của bạn, và do đó cho phép các cấu hình phức tạp hơn (nếu điều đó mong muốn cho ứng dụng của bạn). Phần mở rộng phổ biến từ này, được thấy trong nhiều phần mềm mã nguồn mở quy mô lớn, là dùng để include conf.dđọc cấu hình từ tất cả các tệp trong một thư mục.

Cũng xem câu trả lời của tôi cho một câu hỏi tương tự.


Tôi sẽ cho câu trả lời này. Phương pháp này đạt được hiệu quả mong muốn, với nhược điểm duy nhất là nó yêu cầu thêm logic trên một phần của ứng dụng.
ghempton 12/09/09

18

Bạn có thể thử git update-index --skip-worktree filename. Điều này sẽ yêu cầu git giả vờ rằng các thay đổi cục bộ đối với tên tệp không tồn tại, vì vậy git commit -asẽ bỏ qua nó. Nó có thêm lợi thế là chống lại git reset --hard, vì vậy bạn sẽ không vô tình làm mất các thay đổi cục bộ của mình. Ngoài ra, kết hợp tự động sẽ không thành công nếu tệp được thay đổi ngược dòng (trừ khi bản sao thư mục làm việc khớp với bản sao chỉ mục, trong trường hợp đó, nó sẽ được tự động cập nhật). Nhược điểm là lệnh phải được chạy trên tất cả các máy liên quan và rất khó để thực hiện điều này tự động. Xem thêm git update-index --assume-unchangedđể biết một phiên bản khác của ý tưởng này. Chi tiết về cả hai có thể được tìm thấy với git help update-index.


Bạn có thể tìm thêm thông tin về các lệnh này trong câu hỏi Sự khác biệt giữa 'giả định không thay đổi' và 'cây cối bỏ qua' . Từ câu trả lời trên cùng , có vẻ như bạn muốn --skip-worktreetrong trường hợp này.
Senseful

10

Một cách tiếp cận khác là duy trì các thay đổi cục bộ đối với các tệp cấu hình chung trong một nhánh riêng khác. Tôi làm điều này cho một số dự án yêu cầu một số thay đổi cục bộ. Kỹ thuật này có thể không áp dụng được cho mọi trường hợp, nhưng nó phù hợp với tôi trong một số trường hợp.

Đầu tiên, tôi tạo một nhánh mới dựa trên nhánh chính (trong trường hợp cụ thể này, tôi đang sử dụng git-svn nên tôi cần cam kết từ chính nhưng điều đó không quá quan trọng ở đây):

git checkout -b work master

Bây giờ hãy sửa đổi (các) tệp cấu hình nếu cần và cam kết. Tôi thường đặt một cái gì đó đặc biệt trong thông báo cam kết như "NOCOMMIT" hoặc "PRIVATE" (điều này sẽ hữu ích sau này). Tại thời điểm này, bạn có thể làm việc trên nhánh riêng của mình bằng cách sử dụng tệp cấu hình của riêng bạn.

Khi bạn muốn đẩy lùi công việc của mình ngược dòng, hãy chọn từng thay đổi từ worknhánh của bạn đến nhánh chính. Tôi có một tập lệnh để giúp thực hiện việc này, trông giống như sau:

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

Điều này đầu tiên kiểm tra để đảm bảo tôi đang ở trên masterchi nhánh (kiểm tra sự tỉnh táo). Sau đó, nó liệt kê từng cam kết work, lọc ra những cam kết có đề cập đến từ khóa NOCOMMIT, đảo ngược thứ tự và cuối cùng chọn từng cam kết (bây giờ từ cũ nhất đầu tiên) vào master.

Cuối cùng, sau khi đẩy các thay đổi trong ngược dòng chính, tôi chuyển trở lại workvà rebase:

git checkout work
git rebase master

Git sẽ áp dụng lại từng cam kết trong worknhánh, bỏ qua một cách hiệu quả (các) cam kết đã được áp dụng trong masterquá trình hái anh đào. Những gì bạn nên còn lại chỉ là các cam kết địa phương NOCOMMIT.

Kỹ thuật này làm cho quá trình đẩy tốn thời gian hơn một chút, nhưng nó đã giải quyết được một vấn đề cho tôi nên tôi nghĩ tôi sẽ chia sẻ.


2
Bạn có nhận ra rằng bạn đang yêu cầu người đặt câu hỏi không quên làm điều này? Người chỉ chạy git commit -amà không cần chăm sóc trên thế giới?
Phil Miller

Sau chiến lược tương tự, bạn có thể tag các cam kết mà bạn thiết lập file cấu hình địa phương của bạn và sử dụng một sự kết hợp của git rebase --ontogit fetchđể làm điều tương tự
Danilo Souza Moraes

8

Một khả năng là có các tệp thực trong .gitignore của bạn, nhưng hãy kiểm tra các cấu hình mặc định với một phần mở rộng khác. Ví dụ điển hình cho ứng dụng Rails sẽ là tệp config / database.yml. Chúng tôi sẽ kiểm tra trong config / database.yml.sample và mỗi nhà phát triển tạo config / database.yml của riêng họ đã được .gitignored.


Vâng, đây là một cải tiến gia tăng, nhưng nó vẫn chưa phải là tối ưu vì nếu phiên bản đã đăng ký được cố tình thay đổi, nó sẽ không được phản ánh trên các tệp cấu hình của nhà phát triển. Một ví dụ về khi điều đó sẽ có ích là khi một tài sản mới được thêm vào, vv
ghempton

Đó có thể là giải quyết bằng các ghi chú cam kết tốt và thông báo lỗi mô tả phàn nàn khi thuộc tính không được đặt. Đồng thời, một email thông báo cho nhóm của bạn về sự thay đổi sẽ hữu ích.
Brian Kelly

Để biết thêm thông tin về giải pháp này và một ví dụ tuyệt vời, hãy xem câu trả lời này .
Senseful

1

Kiểm tra cấu hình mặc định với tiện ích mở rộng khác (giả sử .default), sử dụng liên kết tượng trưng để liên kết biểu tượng mặc định đến đúng vị trí, thêm vị trí chính xác vào .gitignore và thêm mọi thứ khác liên quan đến cấu hình vào .gitignore (vì vậy chỉ thứ được đăng ký là config.default).

Ngoài ra, hãy viết một tập lệnh cài đặt nhanh để thiết lập các liên kết tượng trưng cho toàn ứng dụng của bạn.

Chúng tôi đã sử dụng một cách tiếp cận tương tự tại một công ty trước đây. Tập lệnh cài đặt tự động phát hiện bạn đang chạy trong môi trường nào (hộp cát, phát triển, QA, sản xuất) và sẽ tự động làm đúng. Nếu bạn có tệp config.sandbox và đang chạy từ hộp cát, nó sẽ liên kết tệp đó (nếu không nó sẽ chỉ liên kết tệp .defaults). Thủ tục phổ biến là sao chép .defaults và thay đổi cài đặt nếu cần.

Viết kịch bản cài đặt dễ dàng hơn bạn tưởng tượng và mang lại cho bạn sự linh hoạt.


1

Tôi đồng ý với câu trả lời tốt nhất nhưng cũng muốn thêm một cái gì đó. Tôi sử dụng tập lệnh ANT để tách và sửa đổi tệp từ kho lưu trữ GIT nên tôi chắc chắn rằng không có tệp sản xuất nào bị ghi đè. Có một tùy chọn hay trong ANT để sửa đổi các tệp thuộc tính java. Điều này có nghĩa là đặt các biến thử nghiệm cục bộ của bạn trong một tệp thuộc tính kiểu java và thêm một số mã để xử lý nó, nhưng nó mang lại cho bạn cơ hội để tự động xây dựng trang web của mình trước khi bạn FTP nó trực tuyến. Thông thường, bạn sẽ đặt thông tin sản xuất của mình vào tệp site.default.properties và để ANT quản lý cài đặt. Cài đặt cục bộ của bạn sẽ có trong 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

sau đó sử dụng nó:

  $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 của bạn trông giống như sau:

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

và site.local.properties của bạn sẽ trông như thế nào (lưu ý môi trường khác biệt và các mô-đun đã bật):

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

Và hướng dẫn ANT của bạn: ($ d {deploy} là thư mục đích triển khai của bạn)

<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

Ngày nay (2019), tôi sử dụng các vars ENV chẳng hạn trong python / django, bạn cũng có thể thêm mặc định cho chúng. Trong ngữ cảnh của docker, tôi có thể lưu các vars ENV trong một tệp docker-compos.yml hoặc một tệp bổ sung bị bỏ qua trong kiểm soát phiên bản.

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

0

Giải pháp đơn giản nhất là chỉnh sửa tệp thành mặc định, cam kết, sau đó thêm tệp vào của bạn .gitignore. Bằng cách này, các nhà phát triển sẽ không vô tình cam kết khi thực hiện git commit -a, nhưng họ vẫn có thể cam kết trong trường hợp (có lẽ là hiếm) mà bạn muốn thay đổi mặc định của mình git add --force.

Tuy nhiên, có một tệp .default.localtệp cấu hình cuối cùng là giải pháp tốt nhất, vì điều này cho phép bất kỳ ai có cấu hình máy cụ thể thay đổi giá trị mặc định mà không phải phá vỡ cấu hình của chính họ.


Điều này không hiệu quả - nếu tệp được theo dõi và thêm vào .gitignoresau này, các thay đổi vẫn được theo dõi.
Zeemee

0

Tôi làm điều đó giống như nó được đề xuất ở đây với các tệp cấu hình mặc định và cục bộ. Để quản lý các tệp cấu hình cục bộ của tôi có trong các dự án .gitignore, tôi đã tạo một git repo ~/settings. Ở đó, tôi quản lý tất cả các cài đặt cục bộ của mình từ tất cả các dự án. Bạn tạo, ví dụ như một thư mục project1trong ~/settingsvà đặt tất cả những thứ cấu hình địa phương cho dự án này vào nó. Sau đó, bạn có thể liên kết biểu tượng các tệp / thư mục đó với của bạn project1.

Với cách tiếp cận đó, bạn có thể theo dõi các tệp cấu hình cục bộ của mình và không đưa chúng vào kho lưu trữ mã nguồn thông thường.


0

Dựa trên câu trả lời của @Greg Hewgill, bạn có thể thêm một cam kết cụ thể với các thay đổi cục bộ của mình và gắn thẻ nó là localchange:

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

Sau đó, tiến hành thêm cam kết cho tính năng của bạn. Sau khi hoàn thành công việc, bạn có thể hợp nhất nhánh này lại thành chính mà không cần cam kết cục bộ bằng cách thực hiện như sau:

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

Các lệnh này sẽ:

1) Căn cứ lại nhánh tính năng của bạn để làm chủ, bỏ qua cam kết trao đổi cục bộ. 2) Tua đi nhanh chính mà không cần rời khỏi nhánh tính năng 3) Thêm cam kết cục bộ trở lại đầu nhánh tính năng để bạn có thể tiếp tục làm việc với nó. Bạn có thể thực hiện việc này với bất kỳ nhánh nào khác mà bạn muốn tiếp tục làm việc. 4) Đặt lại thẻ localchange thành cam kết được chọn từ anh đào này để chúng tôi có thể sử dụng rebase --ontolại theo cách tương tự.

Điều này không có nghĩa là để thay thế câu trả lời được chấp nhận như là giải pháp chung tốt nhất, mà là một cách suy nghĩ thấu đáo về vấn đề. Về cơ bản, bạn tránh vô tình hợp nhất các thay đổi cục bộ thành master bằng cách chỉ khôi phục từ master localchangetới featurevà chuyển tiếp nhanh.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.