कमिटिंग मशीन विशिष्ट कॉन्फ़िगरेशन फ़ाइलें


82

जब मैं विकसित होता हूं तो एक सामान्य परिदृश्य यह होता है कि कोडबेस में कई कॉन्फिग फाइल्स होंगी जिनमें मशीन की विशिष्ट सेटिंग्स की आवश्यकता होती है। इन फ़ाइलों को गिट में चेक किया जाएगा और अन्य डेवलपर्स हमेशा गलती से उन्हें वापस चेक करेंगे और किसी और के कॉन्फ़िगरेशन को तोड़ देंगे।

इसका एक सरल समाधान यह होगा कि उन्हें सिर्फ Git में न देखें, या उनके लिए अतिरिक्त रूप से .gitignore प्रविष्टि भी जोड़ें। हालाँकि, मुझे पता है कि फ़ाइल में कुछ समझदार चूक होना बहुत अधिक सुरुचिपूर्ण है, जिसे डेवलपर अपनी आवश्यकताओं के अनुरूप संशोधित कर सकता है।

क्या ऐसी फ़ाइलों के साथ अच्छी तरह से खेलने के लिए एक शानदार तरीका है? मैं एक मशीन-विशिष्ट कॉन्फ़िगरेशन फ़ाइल को संशोधित करने में सक्षम होना चाहता हूं और फिर उस फ़ाइल को चेक किए बिना "गिट कमिट-ए" चलाने में सक्षम हो सकता हूं।


1
ऐसा लगता है जैसे आपको अपने डिजाइन में और अपने कोलीग के दिमाग में कोई समस्या है। उन्हें यह सुनिश्चित करने के लिए कहें कि वे जानते हैं कि वे एक स्रोत नियंत्रण प्रणाली में क्या कर रहे हैं, अन्यथा वे बकवास की जाँच करेंगे जिसमें वे नहीं चाहते हैं। भी: क्यों न सिर्फ सिस्टम के लिए एक फ़ाइल, एक फ़ाइल को विभाजित करें?
पॉड

11
मुझे पूरा यकीन है कि यह काफी सामान्य परिदृश्य है? आप मशीन विशिष्ट कॉन्फ़िगरेशन का ट्रैक कैसे रखेंगे? प्रत्येक सिस्टम के लिए फ़ाइल को विभाजित करना बहुत ही गड़बड़ लगता है और वितरित नियंत्रण रखने के उद्देश्य को पराजित करता है: यदि इसे किसी नई मशीन पर चेक किया जाता है तो नई फ़ाइल की जाँच नहीं करनी चाहिए।
ghempton

1
आप कम से कम जो भी साझा भंडार आप सभी को धक्का पर एक पूर्व अद्यतन हुक का उपयोग कर तोड़ने के कमिट में लाने से रोकने में सक्षम हो सकता है। यह कुछ डेवलपर्स द्वारा बनाई गई कॉन्फ़िग फ़ाइल को संशोधित करने वाले कमिट्स की तलाश कर सकता है, या यह उस फ़ाइल को छूने वाले कमिट्स की तलाश कर सकता है जो संदेश में विशेष कीवर्ड के लिए उल्लेख नहीं करते हैं।
फिल मिलर

2
+1, इस है एक आम समस्या। @Pod: रेपो में "Joe.conf" होना व्यावहारिक नहीं है, लेकिन आप अभी भी कई बार चीजों को अपडेट करने में सक्षम होना चाहते हैं ... कभी-कभी कोड में बदलाव के कारण कॉन्फ़िगरेशन में बदलाव करना पड़ता है।
थानाटोस

जवाबों:


59

क्या आपका प्रोग्राम इसकी सेटिंग्स के लिए कॉन्फ़िगरेशन फ़ाइलों की एक जोड़ी को पढ़ा है। सबसे पहले, इसे एक config.defaultsफाइल को पढ़ना चाहिए जिसे रिपॉजिटरी में शामिल किया जाएगा। फिर, इसे एक config.localफ़ाइल को पढ़ना चाहिए जिसे सूचीबद्ध किया जाना चाहिए.gitignore

इस व्यवस्था के साथ, नई सेटिंग डिफॉल्ट फाइल में दिखाई देती हैं और अपडेट होते ही प्रभावी हो जाती हैं। यदि वे ओवरराइड हो जाते हैं तो वे केवल विशेष सिस्टम पर भिन्न होंगे।

इस पर भिन्नता के रूप में, आपके पास बस एक सामान्य configफ़ाइल हो सकती है जिसे आप संस्करण नियंत्रण में जहाज करते हैं, और यह कुछ ऐसा है जो include config.localमशीन-विशिष्ट मूल्यों में लाना पसंद करता है। यह आपके कोड में एक अधिक सामान्य तंत्र (बनाम नीति) का परिचय देता है, और परिणामस्वरूप अधिक जटिल कॉन्फ़िगरेशन (यदि यह आपके आवेदन के लिए वांछनीय है) को सक्षम करता है। इस से लोकप्रिय विस्तार, कई बड़े पैमाने पर ओपन-सोर्स सॉफ़्टवेयर में देखा जाता है include conf.d, जो एक निर्देशिका में सभी फ़ाइलों से कॉन्फ़िगरेशन पढ़ता है।

इसी तरह के प्रश्न के लिए मेरा उत्तर भी देखें


मैं यह जवाब देने वाला हूं। यह विधि वांछित प्रभाव को प्राप्त करती है, केवल नकारात्मक पक्ष के साथ कि आवेदन के हिस्से पर अतिरिक्त तर्क की आवश्यकता होती है।
किन्नर

17

आप कोशिश कर सकते हैं git update-index --skip-worktree filename। यह इस बात का ढोंग करेगा कि स्थानीय नाम फ़ाइल नाम में मौजूद नहीं है, इसलिए git commit -aइसे अनदेखा कर देंगे। इसका विरोध का अतिरिक्त लाभ भी है git reset --hard, इसलिए आप गलती से अपने स्थानीय परिवर्तनों को नहीं खोएंगे। इसके अलावा, स्वचालित रूप से मर्ज फेल हो जाएगा यदि फ़ाइल अपस्ट्रीम में बदल जाती है (जब तक कि वर्किंग डायरेक्टरी कॉपी इंडेक्स कॉपी से मेल नहीं खाती है, उस स्थिति में यह स्वतः अपडेट हो जाएगा)। नकारात्मक पक्ष यह है कि इसमें शामिल सभी मशीनों पर कमांड चलना है, और यह स्वचालित रूप से करना मुश्किल है। git update-index --assume-unchangedइस विचार के सूक्ष्म रूप से भिन्न संस्करण के लिए भी देखें । दोनों पर विवरण के साथ पाया जा सकता है git help update-index


आप इन आदेशों के बारे में अधिक जानकारी 'मान-अपरिवर्तित' और 'स्किप-वर्कट्री' के बीच अंतर पर पा सकते हैं । से शीर्ष जवाब , ऐसा लगता है कि आप चाहते हैं --skip-worktreeकि इस मामले में।
सेंसफुल

10

एक अन्य दृष्टिकोण एक और निजी शाखा में आम कॉन्फ़िगरेशन फ़ाइलों में स्थानीय परिवर्तन बनाए रखना है। मैं कुछ परियोजनाओं के लिए ऐसा करता हूं जिनमें कई स्थानीय परिवर्तनों की आवश्यकता होती है। यह तकनीक सभी स्थितियों पर लागू नहीं हो सकती है, लेकिन यह कुछ मामलों में मेरे लिए काम करती है।

पहले मैं मास्टर शाखा के आधार पर एक नई शाखा बनाता हूं (इस विशेष मामले में मैं git-svn का उपयोग कर रहा हूं इसलिए मुझे मास्टर से प्रतिबद्ध होने की आवश्यकता है लेकिन यह यहां बहुत महत्वपूर्ण नहीं है):

git checkout -b work master

अब कॉन्फ़िगरेशन फ़ाइल को आवश्यक रूप से संशोधित करें और कमिट करें। मैं आमतौर पर "NOCOMMIT" या "निजी" जैसे प्रतिबद्ध संदेश में कुछ विशिष्ट लगाता हूं (यह बाद में उपयोगी होगा)। इस बिंदु पर, आप अपनी स्वयं की कॉन्फ़िग फ़ाइल का उपयोग करके अपनी निजी शाखा पर काम कर सकते हैं।

जब आप अपने काम को वापस ऊपर की ओर धकेलना चाहते हैं, तो अपनी 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से चेरी-पिकिंग के माध्यम से पहले से लागू किए गए (s) पर लंघन कर रहा है। क्या आप के साथ छोड़ दिया जाना चाहिए केवल NOCOMMIT स्थानीय आवागमन है।

यह तकनीक पुश प्रोसेस को थोड़ा अधिक समय लेने वाली बनाती है, लेकिन इसने मेरे लिए एक समस्या को हल कर दिया, इसलिए मुझे लगा कि मैं साझा करूंगा।


2
आपको लगता है कि आप ऐसा करने के लिए विस्मृत नहीं सवाल पूछने वाले पूछ रहे हैं? वह व्यक्ति जो git commit -aदुनिया में बिना किसी परवाह के बस चलता है?
फिल मिलर

कि एक ही रणनीति के बाद, आप टैग कर सकते प्रतिबद्ध है जहां आप अपने स्थानीय config फ़ाइलों की स्थापना की और के संयोजन का उपयोग git rebase --ontoऔर git fetchएक ही बात करने के लिए
डेनिलो सूजा मोरेस

8

एक संभावना है कि आपके .gitignore में वास्तविक फाइलें हों, लेकिन एक अलग एक्सटेंशन के साथ डिफ़ॉल्ट कॉन्फ़िगरेशन में जांच करें। रेल एप्लिकेशन के लिए एक विशिष्ट उदाहरण config / database.yml फ़ाइल होगी। हम config / database.yml.sample में जांच करेंगे, और प्रत्येक डेवलपर अपना स्वयं का config / database.yml बनाता है, जो पहले से ही .OPCL है।


हाँ, यह एक वृद्धिशील सुधार है, लेकिन यह अभी भी इष्टतम नहीं है क्योंकि यदि संस्करण में चेक किया गया जानबूझकर बदला गया है, तो यह डेवलपर कॉन्फिग फाइलों पर प्रतिबिंबित नहीं होता है। कब उपयोगी होगा इसका एक उदाहरण है जब एक नई संपत्ति को जोड़ा जाता है आदि
गमपटन

यह अच्छा कमिट नोट्स और वर्णनात्मक त्रुटि संदेशों के साथ संबोधित किया जा सकता है जो एक संपत्ति सेट नहीं होने पर शिकायत करते हैं। साथ ही परिवर्तन के बारे में आपकी टीम को सूचित करने वाला एक ईमेल मदद करता है।
ब्रायन केली

इस समाधान और एक महान उदाहरण के बारे में अधिक जानकारी के लिए, इस उत्तर को देखें ।
सेंसफुल

1

एक अलग एक्सटेंशन (कहते हैं .default) के साथ एक डिफ़ॉल्ट कॉन्फ़िगरेशन में जांचें, डिफ़ॉल्ट स्थान को सही स्थान पर जोड़ने के लिए एक सिम्लिंक का उपयोग करें। .ignignore में सही स्थान जोड़ें, और .गरिग्नोर (इसलिए केवल कॉन्फ़िगरेशन से संबंधित अन्य सब कुछ जोड़ें। वह चीज़ जो चेक की जाती है config.default)।

इसके अतिरिक्त, एक त्वरित इंस्टॉल स्क्रिप्ट लिखें जो आपके एप्लिकेशन-वाइड के लिए सीमलिंक सेट करती है।

हमने पिछली कंपनी में इसी तरह के दृष्टिकोण का इस्तेमाल किया था। स्क्रिप्ट स्थापित करें कि आप (सैंडबॉक्स, विकास, क्यूए, उत्पादन) में कौन सा वातावरण चला रहे थे, और स्वचालित रूप से सही काम करेंगे। यदि आपके पास config.sandbox फ़ाइल थी, और सैंडबॉक्स से चल रहे थे, तो यह लिंक करेगा (अन्यथा यह केवल .deults फ़ाइल को लिंक करेगा)। सामान्य प्रक्रिया थी .डिफ़ॉल्ट्स को कॉपी करना और आवश्यकतानुसार सेटिंग बदलना।

इंस्टॉल स्क्रिप्ट लिखना आपके द्वारा कल्पना की गई तुलना में आसान है, और आपको बहुत अधिक लचीलापन देता है।


1

मैं सबसे अच्छे उत्तर से सहमत हूं, लेकिन कुछ जोड़ना भी चाहूंगा। मैं GIT रेपो से फ़ाइलों को हटाने और संशोधित करने के लिए एक ANT स्क्रिप्ट का उपयोग करता हूं, इसलिए मुझे यकीन है कि कोई भी उत्पादन फ़ाइलें अधिलेखित नहीं होती हैं। जावा संपत्ति फ़ाइलों को संशोधित करने के लिए ANT में एक अच्छा विकल्प है। इसका अर्थ है कि अपने स्थानीय परीक्षण चर को जावा-शैली की संपत्ति फ़ाइल में डालना और इसे संसाधित करने के लिए कुछ कोड जोड़ना, लेकिन यह आपको ऑनलाइन एफ़टीपी करने से पहले अपनी साइट बनाने का अवसर देता है। आमतौर पर आप अपनी उत्पादन जानकारी को 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_");

आपकी साइट .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 {तैनाती} आपकी तैनाती लक्ष्य निर्देशिका)

<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) मैं उदाहरण के लिए ईथ वेरिएंट का उपयोग अजगर / डेजैंगो में करता हूं, आप उनमें डिफॉल्ट भी जोड़ सकते हैं। 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बाद में जोड़ा जाता है , तो परिवर्तन अभी भी ट्रैक किए जाते हैं।
ज़ीमी

0

मुझे यह पसंद है कि यह डिफ़ॉल्ट और स्थानीय कॉन्फ़िगरेशन फ़ाइलों के साथ यहाँ अनुशंसित है। मेरे स्थानीय विन्यास फाइल को प्रबंधित करने के लिए जो कि परियोजनाओं में हैं .gitignore, मैंने एक git रेपो बनाया ~/settings। वहाँ मैं सभी परियोजनाओं से अपनी सभी स्थानीय सेटिंग प्रबंधित करता हूँ। उदाहरण के लिए एक फ़ोल्डर बनाने project1में ~/settingsऔर सभी इसे में इस परियोजना के लिए स्थानीय config सामान रख दिया। इसके बाद आप उस फाइल / फोल्डर को अपने से लिंक कर सकते हैं project1

उस दृष्टिकोण से आप अपनी स्थानीय कॉन्फिग फाइलों को ट्रैक कर सकते हैं, और उन्हें सामान्य स्रोत कोड रिपॉजिटरी में नहीं डाल सकते हैं।


0

@Greg Hewgill के उत्तर पर बिल्डिंग, आप अपने स्थानीय परिवर्तनों के साथ एक विशिष्ट प्रतिबद्धता जोड़ सकते हैं और इसे स्थानीय परिवर्तन के रूप में टैग कर सकते हैं:

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) लोकलचेंज कमिट को नजरअंदाज करते हुए अपनी सुविधा शाखा को मास्टर में रिबास करें। 2) फीचर ब्रांच को छोड़े बिना फास्ट फॉरवर्ड मास्टर) लोकलचेंज को फीचर शाखा के शीर्ष पर वापस जोड़ें ताकि आप उस पर काम करना जारी रख सकें। आप इसे किसी अन्य शाखा में कर सकते हैं, जिस पर आप काम करना जारी रखना चाहते हैं। 4) इस चेरी-पिकेड कमिट के लिए लोकलचेंज टैग को रीसेट करें ताकि हम rebase --ontoफिर से उसी तरह से उपयोग कर सकें ।

इसका मतलब स्वीकार किए गए उत्तर को सर्वश्रेष्ठ सामान्य समाधान के रूप में बदलना नहीं है, लेकिन समस्या के बारे में बॉक्स से बाहर सोचने का एक तरीका है। आप मूल रूप से गलती से केवल से रिबेसिंग द्वारा गुरु को स्थानीय परिवर्तन मर्ज होने से रोकने localchangeके लिए featureऔर तेजी से अग्रेषण मास्टर।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.