[{"data":1,"prerenderedAt":1234},["ShallowReactive",2],{"/blog/how-we-automatically-fixed-hundreds-of-ruby-2-7-deprecation-warnings":3,"navigation-en-us":446,"banner-en-us":874,"footer-en-us":884,"blog-post-authors-en-us-Thong Kuah":1129,"blog-related-posts-en-us-how-we-automatically-fixed-hundreds-of-ruby-2-7-deprecation-warnings":1144,"blog-promotions-en-us":1170,"next-steps-en-us":1224},{"id":4,"title":5,"authors":6,"body":8,"category":427,"date":428,"description":429,"extension":430,"externalUrl":431,"faq":431,"featured":432,"heroImage":433,"meta":434,"navigation":99,"path":435,"seo":436,"slug":440,"stem":441,"tags":442,"template":444,"updatedDate":431,"__hash__":445},"blogPosts/en-us/blog/how-we-automatically-fixed-hundreds-of-ruby-2-7-deprecation-warnings.md","How we automatically fixed thousands of Ruby 2.7 deprecation warnings",[7],"Thong Kuah",{"type":9,"value":10,"toc":421},"minimark",[11,15,31,42,47,60,63,67,82,85,174,198,209,213,222,250,383,391,395,403,417],[12,13,14],"p",{},"Ruby 3.0 was just released on Dec. 25, 2020, with some new features and some breaking changes.\nGitLab was at Ruby 2.6, and we wanted to upgrade to Ruby 2.7 in preparation to eventually upgrade to Ruby 3.",[12,16,17,18,24,25,30],{},"In Ruby 3.0, ",[19,20,23],"a",{"href":21,"rel":22},"https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/",[],"positional and keyword arguments will be separated",". To help developers prepare for this, in Ruby 2.7,\nwarnings were added. In GitLab, we discovered we have ",[19,26,29],{"href":27,"rel":28},"https://gitlab.com/gitlab-org/gitlab/-/issues/257438",[],"thousands\nof such warnings"," across hundreds of files:",[32,33,39],"pre",{"className":34,"code":36,"language":37,"meta":38},[35],"language-text","warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call\n","text","",[40,41,36],"code",{"__ignoreMap":38},[43,44,46],"h2",{"id":45},"boring-solutions","Boring solutions",[12,48,49,50,53,54,59],{},"To address this warning, the obvious, and boring solution was to simply add ",[40,51,52],{},"**"," to the last keyword argument.\nFor the most part, this is what we did. However, while this was under way, we also developed a RuboCop check that could\ndetect, and automatically fix the keyword arguments. The benefit for this approach was that we can\n",[19,55,58],{"href":56,"rel":57},"https://docs.rubocop.org/rubocop/usage/auto_correct.html",[],"autocorrect"," any existing warnings en masse.",[12,61,62],{},"The tricky part about this is that RuboCop is designed to statically analyze Ruby code, whereas the warnings were\ngenerated by Ruby at runtime.",[43,64,66],{"id":65},"a-way-forward","A way forward",[12,68,69,70,75,76,81],{},"After some research, we found a way to utilize our comprehensive RSpec test suite to\ngather all the warnings using the ",[19,71,74],{"href":72,"rel":73},"https://github.com/shopify/deprecation_toolkit",[],"Deprecation Toolkit gem",". We also\nconsidered using the ",[19,77,80],{"href":78,"rel":79},"https://github.com/jeremyevans/ruby-warning",[],"warning gem"," at one point, but preferred Deprecation Toolkit\nas the results were easier to process.",[12,83,84],{},"Deprecation Toolkit supports RSpec out of the box, so it was really simple to configure. It also has a simple YAML-based file format to record all deprecations. We then adapted this to record deprecation warnings for Ruby 2.7\nlast keyword arguments with:",[32,86,90],{"className":87,"code":88,"language":89,"meta":38,"style":38},"language-ruby shiki shiki-themes github-light","\n  kwargs_warnings = [\n    # Taken from https://github.com/jeremyevans/ruby-warning/blob/1.1.0/lib/warning.rb#L18\n    %r{warning: (?:Using the last argument (?:for `.+' )?as keyword parameters is deprecated; maybe \\*\\* should be added to the call|Passing the keyword argument (?:for `.+' )?as the last hash parameter is deprecated|Splitting the last argument (?:for `.+' )?into positional and keyword parameters is deprecated|The called method (?:`.+' )?is defined here)\\n\\z}\n  ]\n  DeprecationToolkit::Configuration.warnings_treated_as_deprecation = kwargs_warnings\n\n","ruby",[40,91,92,101,116,123,143,149],{"__ignoreMap":38},[93,94,97],"span",{"class":95,"line":96},"line",1,[93,98,100],{"emptyLinePlaceholder":99},true,"\n",[93,102,104,108,112],{"class":95,"line":103},2,[93,105,107],{"class":106},"sqxcx","  kwargs_warnings",[93,109,111],{"class":110},"sD7c4"," =",[93,113,115],{"class":114},"sgsFI"," [\n",[93,117,119],{"class":95,"line":118},3,[93,120,122],{"class":121},"sAwPA","    # Taken from https://github.com/jeremyevans/ruby-warning/blob/1.1.0/lib/warning.rb#L18\n",[93,124,126,130,134,137,140],{"class":95,"line":125},4,[93,127,129],{"class":128},"sYBdl","    %r{warning: (?:Using the last argument (?:for `.+' )?as keyword parameters is deprecated; maybe ",[93,131,133],{"class":132},"s691h","\\*\\*",[93,135,136],{"class":128}," should be added to the call|Passing the keyword argument (?:for `.+' )?as the last hash parameter is deprecated|Splitting the last argument (?:for `.+' )?into positional and keyword parameters is deprecated|The called method (?:`.+' )?is defined here)",[93,138,139],{"class":132},"\\n\\z",[93,141,142],{"class":128},"}\n",[93,144,146],{"class":95,"line":145},5,[93,147,148],{"class":114},"  ]\n",[93,150,152,156,159,162,165,169,171],{"class":95,"line":151},6,[93,153,155],{"class":154},"sYu0t","  DeprecationToolkit",[93,157,158],{"class":114},"::",[93,160,161],{"class":154},"Configuration",[93,163,164],{"class":114},".",[93,166,168],{"class":167},"s7eDp","warnings_treated_as_deprecation",[93,170,111],{"class":110},[93,172,173],{"class":114}," kwargs_warnings\n",[12,175,176,177,184,185,194,195,197],{},"Lastly, we wrote a new RuboCop check, called\n",[19,178,181],{"href":179,"rel":180},"https://gitlab.com/gitlab-org/gitlab/-/blob/632b7768f7f9014951170a006489d66b34001c68/rubocop/cop/lint/last_keyword_argument.rb",[],[40,182,183],{},"Lint/LastKeywordArgument",",\nthat checks against the YAML files generated by Deprecation Toolkit, and\ngenerates offenses. Now we can very quickly, statically check the whole GitLab\ncodebase, and even autocorrect! You can see how ",[19,186,189,190,193],{"href":187,"rel":188},"https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47720",[],"Deprecation Toolkit and the\n",[40,191,192],{},"LastKeywordArgument"," check was put together in this merge\nrequest",". You can\nsee a sample output from running the ",[40,196,192],{}," cop check:",[12,199,200,205,206,208],{},[201,202],"img",{"alt":203,"src":204},"LastKeywordArgument RuboCop offenses","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398598/blog/Content%20Images/last-keyword-argument-rubocop-offenses.png","\nSample output from running the ",[40,207,192],{}," cop check",[43,210,212],{"id":211},"automatically-fix-everything","Automatically fix everything",[12,214,215,216,221],{},"Now we have an automatic RuboCop check, which can also autocorrect, we create merge requests to autocorrect!\nFor example, we autocorrected 62 instances across ",[19,217,220],{"href":218,"rel":219},"https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48407",[],"39 spec files",".\nAutomation for the win!",[12,223,224,225,228,229,232,233,236,237,242,243,245,246,249],{},"We then went one step further, and integrated this in our GitLab CI pipelines. Using the ",[40,226,227],{},"artifacts"," feature of GitLab CI, we\ngathered the ",[40,230,231],{},"deprecations"," directory from all RSpec jobs (we have about 400 such jobs). After all the RSpec jobs have passed, we then made a ",[40,234,235],{},"post-test"," job to\n",[19,238,241],{"href":239,"rel":240},"https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49792",[],"check the results"," with the ",[40,244,192],{}," cop. Below is a\nsnippet of the GitLab CI ",[40,247,248],{},".gitlab-ci.yml"," configuration:",[32,251,255],{"className":252,"code":253,"language":254,"meta":38,"style":38},"language-yaml shiki shiki-themes github-light","stages:\n  - test\n  - post-test\n\n# This inherited job is used by all RSpec jobs\n.rspec-base:\n  stage: test\n  artifacts:\n    - deprecations/\n\n# GitLab CI job artifacts from previous stages are passed to this job\nrspec:deprecations:\n  stage: post-test\n  script:\n    - bundle exec rubocop --only Lint/LastKeywordArgument --parallel\n  artifacts:\n    - deprecations/\n\n","yaml",[40,256,257,266,274,281,285,290,297,308,316,325,330,336,344,353,361,369,376],{"__ignoreMap":38},[93,258,259,263],{"class":95,"line":96},[93,260,262],{"class":261},"shJU0","stages",[93,264,265],{"class":114},":\n",[93,267,268,271],{"class":95,"line":103},[93,269,270],{"class":114},"  - ",[93,272,273],{"class":128},"test\n",[93,275,276,278],{"class":95,"line":118},[93,277,270],{"class":114},[93,279,280],{"class":128},"post-test\n",[93,282,283],{"class":95,"line":125},[93,284,100],{"emptyLinePlaceholder":99},[93,286,287],{"class":95,"line":145},[93,288,289],{"class":121},"# This inherited job is used by all RSpec jobs\n",[93,291,292,295],{"class":95,"line":151},[93,293,294],{"class":261},".rspec-base",[93,296,265],{"class":114},[93,298,300,303,306],{"class":95,"line":299},7,[93,301,302],{"class":261},"  stage",[93,304,305],{"class":114},": ",[93,307,273],{"class":128},[93,309,311,314],{"class":95,"line":310},8,[93,312,313],{"class":261},"  artifacts",[93,315,265],{"class":114},[93,317,319,322],{"class":95,"line":318},9,[93,320,321],{"class":114},"    - ",[93,323,324],{"class":128},"deprecations/\n",[93,326,328],{"class":95,"line":327},10,[93,329,100],{"emptyLinePlaceholder":99},[93,331,333],{"class":95,"line":332},11,[93,334,335],{"class":121},"# GitLab CI job artifacts from previous stages are passed to this job\n",[93,337,339,342],{"class":95,"line":338},12,[93,340,341],{"class":261},"rspec:deprecations",[93,343,265],{"class":114},[93,345,347,349,351],{"class":95,"line":346},13,[93,348,302],{"class":261},[93,350,305],{"class":114},[93,352,280],{"class":128},[93,354,356,359],{"class":95,"line":355},14,[93,357,358],{"class":261},"  script",[93,360,265],{"class":114},[93,362,364,366],{"class":95,"line":363},15,[93,365,321],{"class":114},[93,367,368],{"class":128},"bundle exec rubocop --only Lint/LastKeywordArgument --parallel\n",[93,370,372,374],{"class":95,"line":371},16,[93,373,313],{"class":261},[93,375,265],{"class":114},[93,377,379,381],{"class":95,"line":378},17,[93,380,321],{"class":114},[93,382,324],{"class":128},[12,384,385,386,164],{},"This enabled us to have a single job where ",[19,387,390],{"href":388,"rel":389},"https://gitlab.com/gitlab-org/gitlab/-/jobs/991299621",[],"we can see all deprecation warnings",[43,392,394],{"id":393},"conclusion","Conclusion",[12,396,397,398,402],{},"With this measure we went from about 30,000 warnings related to keyword arguments to about 800 remaining warnings, largely stemming from\ndependencies. Feel free to follow our progress in ",[19,399,401],{"href":27,"rel":400},[],"GitLab issue #257438",", and contribute to\nfix the remaining warnings if you are interested!",[12,404,405,406,411,412],{},"Cover image by ",[19,407,410],{"href":408,"rel":409},"https://unsplash.com/@epicantus",[],"Daria Nepriakhina"," on ",[19,413,416],{"href":414,"rel":415},"https://unsplash.com/photos/zNU3ErDAbAw",[],"Unsplash",[418,419,420],"style",{},"html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .s691h, html code.shiki .s691h{--shiki-default:#22863A;--shiki-default-font-weight:bold}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}",{"title":38,"searchDepth":103,"depth":103,"links":422},[423,424,425,426],{"id":45,"depth":103,"text":46},{"id":65,"depth":103,"text":66},{"id":211,"depth":103,"text":212},{"id":393,"depth":103,"text":394},"engineering","2021-02-03","The upgrade to Ruby 2.7 for GitLab involved thousands of deprecation warnings across hundreds of files. Here's how we fixed most of them.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681882/Blog/Hero%20Images/daria-nepriakhina-zNU3ErDAbAw-unsplash.jpg",{},"/en-us/blog/how-we-automatically-fixed-hundreds-of-ruby-2-7-deprecation-warnings",{"title":5,"description":429,"ogTitle":5,"ogDescription":429,"noIndex":432,"ogImage":433,"ogUrl":437,"ogSiteName":438,"ogType":439,"canonicalUrls":437},"https://about.gitlab.com/blog/how-we-automatically-fixed-hundreds-of-ruby-2-7-deprecation-warnings","https://about.gitlab.com","article","how-we-automatically-fixed-hundreds-of-ruby-2-7-deprecation-warnings","en-us/blog/how-we-automatically-fixed-hundreds-of-ruby-2-7-deprecation-warnings",[443],"open source","BlogPost","oolecZTg-Ap9iRzBWRPQtUru58qgjDSr0ul34Dniedc",{"logo":447,"freeTrial":452,"sales":457,"login":462,"items":467,"search":794,"minimal":825,"duo":844,"switchNav":853,"pricingDeployment":864},{"config":448},{"href":449,"dataGaName":450,"dataGaLocation":451},"/","gitlab logo","header",{"text":453,"config":454},"Get free trial",{"href":455,"dataGaName":456,"dataGaLocation":451},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":458,"config":459},"Request a demo",{"href":460,"dataGaName":461,"dataGaLocation":451},"/sales/?contact-topic=request-demo","sales",{"text":463,"config":464},"Sign in",{"href":465,"dataGaName":466,"dataGaLocation":451},"https://gitlab.com/users/sign_in/","sign in",[468,497,597,602,716,772],{"text":469,"config":470,"menu":472},"Platform",{"dataNavLevelOne":471},"platform",{"type":473,"columns":474},"cards",[475,481,489],{"title":469,"description":476,"link":477},"The intelligent orchestration platform for DevSecOps",{"text":478,"config":479},"Explore our Platform",{"href":480,"dataGaName":471,"dataGaLocation":451},"/platform/",{"title":482,"description":483,"link":484},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":485,"config":486},"Meet GitLab Duo",{"href":487,"dataGaName":488,"dataGaLocation":451},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":490,"description":491,"link":492},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":493,"config":494},"Learn more",{"href":495,"dataGaName":496,"dataGaLocation":451},"/why-gitlab/","why gitlab",{"text":498,"left":99,"config":499,"menu":501},"Product",{"dataNavLevelOne":500},"solutions",{"type":502,"link":503,"columns":507,"feature":576},"lists",{"text":504,"config":505},"View all Solutions",{"href":506,"dataGaName":500,"dataGaLocation":451},"/solutions/",[508,532,555],{"title":509,"description":510,"link":511,"items":516},"Automation","CI/CD and automation to accelerate deployment",{"config":512},{"icon":513,"href":514,"dataGaName":515,"dataGaLocation":451},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[517,521,524,528],{"text":518,"config":519},"CI/CD",{"href":520,"dataGaLocation":451,"dataGaName":518},"/solutions/continuous-integration/",{"text":482,"config":522},{"href":487,"dataGaLocation":451,"dataGaName":523},"gitlab duo agent platform - product menu",{"text":525,"config":526},"Source Code Management",{"href":527,"dataGaLocation":451,"dataGaName":525},"/solutions/source-code-management/",{"text":529,"config":530},"Automated Software Delivery",{"href":514,"dataGaLocation":451,"dataGaName":531},"Automated software delivery",{"title":533,"description":534,"link":535,"items":540},"Security","Deliver code faster without compromising security",{"config":536},{"href":537,"dataGaName":538,"dataGaLocation":451,"icon":539},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[541,545,550],{"text":542,"config":543},"Application Security Testing",{"href":537,"dataGaName":544,"dataGaLocation":451},"Application security testing",{"text":546,"config":547},"Software Supply Chain Security",{"href":548,"dataGaLocation":451,"dataGaName":549},"/solutions/supply-chain/","Software supply chain security",{"text":551,"config":552},"Software Compliance",{"href":553,"dataGaName":554,"dataGaLocation":451},"/solutions/software-compliance/","software compliance",{"title":556,"link":557,"items":562},"Measurement",{"config":558},{"icon":559,"href":560,"dataGaName":561,"dataGaLocation":451},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[563,567,571],{"text":564,"config":565},"Visibility & Measurement",{"href":560,"dataGaLocation":451,"dataGaName":566},"Visibility and Measurement",{"text":568,"config":569},"Value Stream Management",{"href":570,"dataGaLocation":451,"dataGaName":568},"/solutions/value-stream-management/",{"text":572,"config":573},"Analytics & Insights",{"href":574,"dataGaLocation":451,"dataGaName":575},"/solutions/analytics-and-insights/","Analytics and insights",{"title":577,"type":502,"items":578},"GitLab for",[579,585,591],{"text":580,"config":581},"Enterprise",{"icon":582,"href":583,"dataGaLocation":451,"dataGaName":584},"Building","/enterprise/","enterprise",{"text":586,"config":587},"Small Business",{"icon":588,"href":589,"dataGaLocation":451,"dataGaName":590},"Work","/small-business/","small business",{"text":592,"config":593},"Public Sector",{"icon":594,"href":595,"dataGaLocation":451,"dataGaName":596},"Organization","/solutions/public-sector/","public sector",{"text":598,"config":599},"Pricing",{"href":600,"dataGaName":601,"dataGaLocation":451,"dataNavLevelOne":601},"/pricing/","pricing",{"text":603,"config":604,"menu":606},"Resources",{"dataNavLevelOne":605},"resources",{"type":502,"link":607,"columns":611,"feature":705},{"text":608,"config":609},"View all resources",{"href":610,"dataGaName":605,"dataGaLocation":451},"/resources/",[612,645,672],{"title":613,"items":614},"Getting started",[615,620,625,630,635,640],{"text":616,"config":617},"Install",{"href":618,"dataGaName":619,"dataGaLocation":451},"/install/","install",{"text":621,"config":622},"Quick start guides",{"href":623,"dataGaName":624,"dataGaLocation":451},"/get-started/","quick setup checklists",{"text":626,"config":627},"Learn",{"href":628,"dataGaLocation":451,"dataGaName":629},"https://university.gitlab.com/","learn",{"text":631,"config":632},"Product documentation",{"href":633,"dataGaName":634,"dataGaLocation":451},"https://docs.gitlab.com/","product documentation",{"text":636,"config":637},"Best practice videos",{"href":638,"dataGaName":639,"dataGaLocation":451},"/getting-started-videos/","best practice videos",{"text":641,"config":642},"Integrations",{"href":643,"dataGaName":644,"dataGaLocation":451},"/integrations/","integrations",{"title":646,"items":647},"Discover",[648,653,658,663,667],{"text":649,"config":650},"Customer success stories",{"href":651,"dataGaName":652,"dataGaLocation":451},"/customers/","customer success stories",{"text":654,"config":655},"Blog",{"href":656,"dataGaName":657,"dataGaLocation":451},"/blog/","blog",{"text":659,"config":660},"Demo Hub",{"href":661,"dataGaName":662,"dataGaLocation":451},"/demo-hub/","demo hub",{"text":664,"config":665},"The Source",{"href":666,"dataGaName":657,"dataGaLocation":451},"/the-source/",{"text":668,"config":669},"Remote",{"href":670,"dataGaName":671,"dataGaLocation":451},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":673,"items":674},"Connect",[675,680,685,690,695,700],{"text":676,"config":677},"GitLab Services",{"href":678,"dataGaName":679,"dataGaLocation":451},"/services/","services",{"text":681,"config":682},"Contribute",{"href":683,"dataGaName":684,"dataGaLocation":451},"https://contributors.gitlab.com","contribute",{"text":686,"config":687},"Community",{"href":688,"dataGaName":689,"dataGaLocation":451},"/community/","community",{"text":691,"config":692},"Forum",{"href":693,"dataGaName":694,"dataGaLocation":451},"https://forum.gitlab.com/","forum",{"text":696,"config":697},"Events",{"href":698,"dataGaName":699,"dataGaLocation":451},"/events/","events",{"text":701,"config":702},"Partners",{"href":703,"dataGaName":704,"dataGaLocation":451},"/partners/","partners",{"config":706,"title":709,"text":710,"link":711},{"background":707,"textColor":708},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":712,"config":713},"Read the latest",{"href":714,"dataGaName":715,"dataGaLocation":451},"/whats-new/","whats new",{"text":717,"config":718,"menu":720},"Company",{"dataNavLevelOne":719},"company",{"type":502,"columns":721},[722],{"items":723},[724,729,735,737,742,747,752,757,762,767],{"text":725,"config":726},"About",{"href":727,"dataGaName":728,"dataGaLocation":451},"/company/","about",{"text":730,"config":731,"footerGa":734},"Jobs",{"href":732,"dataGaName":733,"dataGaLocation":451},"/jobs/","jobs",{"dataGaName":733},{"text":696,"config":736},{"href":698,"dataGaName":699,"dataGaLocation":451},{"text":738,"config":739},"Leadership",{"href":740,"dataGaName":741,"dataGaLocation":451},"/company/team/e-group/","leadership",{"text":743,"config":744},"Handbook",{"href":745,"dataGaName":746,"dataGaLocation":451},"https://handbook.gitlab.com/","handbook",{"text":748,"config":749},"Investor relations",{"href":750,"dataGaName":751,"dataGaLocation":451},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":753,"config":754},"Trust Center",{"href":755,"dataGaName":756,"dataGaLocation":451},"/security/","trust center",{"text":758,"config":759},"AI Transparency Center",{"href":760,"dataGaName":761,"dataGaLocation":451},"/ai-transparency-center/","ai transparency center",{"text":763,"config":764},"Newsletter",{"href":765,"dataGaName":766,"dataGaLocation":451},"/company/contact/#contact-forms","newsletter",{"text":768,"config":769},"Press",{"href":770,"dataGaName":771,"dataGaLocation":451},"/press/","press",{"text":773,"config":774,"menu":775},"Contact us",{"dataNavLevelOne":719},{"type":502,"columns":776},[777],{"items":778},[779,784,789],{"text":780,"config":781},"Talk to sales",{"href":782,"dataGaName":783,"dataGaLocation":451},"/sales/","talk to sales",{"text":785,"config":786},"Support portal",{"href":787,"dataGaName":788,"dataGaLocation":451},"https://support.gitlab.com/hc/en-us","support portal",{"text":790,"config":791},"Customer portal",{"href":792,"dataGaName":793,"dataGaLocation":451},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":795,"login":796,"suggestions":803},"Close",{"text":797,"link":798},"To search repositories and projects, login to",{"text":799,"config":800},"gitlab.com",{"href":465,"dataGaName":801,"dataGaLocation":802},"search login","search",{"text":804,"default":805},"Suggestions",[806,808,812,814,818,822],{"text":482,"config":807},{"href":487,"dataGaName":482,"dataGaLocation":802},{"text":809,"config":810},"Code Suggestions (AI)",{"href":811,"dataGaName":809,"dataGaLocation":802},"/solutions/code-suggestions/",{"text":518,"config":813},{"href":520,"dataGaName":518,"dataGaLocation":802},{"text":815,"config":816},"GitLab on AWS",{"href":817,"dataGaName":815,"dataGaLocation":802},"/partners/technology-partners/aws/",{"text":819,"config":820},"GitLab on Google Cloud",{"href":821,"dataGaName":819,"dataGaLocation":802},"/partners/technology-partners/google-cloud-platform/",{"text":823,"config":824},"Why GitLab?",{"href":495,"dataGaName":823,"dataGaLocation":802},{"freeTrial":826,"mobileIcon":831,"desktopIcon":836,"secondaryButton":839},{"text":827,"config":828},"Start free trial",{"href":829,"dataGaName":456,"dataGaLocation":830},"https://gitlab.com/-/trials/new/","nav",{"altText":832,"config":833},"Gitlab Icon",{"src":834,"dataGaName":835,"dataGaLocation":830},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":832,"config":837},{"src":838,"dataGaName":835,"dataGaLocation":830},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":840,"config":841},"Get Started",{"href":842,"dataGaName":843,"dataGaLocation":830},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":845,"mobileIcon":849,"desktopIcon":851},{"text":846,"config":847},"Learn more about GitLab Duo",{"href":487,"dataGaName":848,"dataGaLocation":830},"gitlab duo",{"altText":832,"config":850},{"src":834,"dataGaName":835,"dataGaLocation":830},{"altText":832,"config":852},{"src":838,"dataGaName":835,"dataGaLocation":830},{"button":854,"mobileIcon":859,"desktopIcon":861},{"text":855,"config":856},"/switch",{"href":857,"dataGaName":858,"dataGaLocation":830},"#contact","switch",{"altText":832,"config":860},{"src":834,"dataGaName":835,"dataGaLocation":830},{"altText":832,"config":862},{"src":863,"dataGaName":835,"dataGaLocation":830},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":865,"mobileIcon":870,"desktopIcon":872},{"text":866,"config":867},"Back to pricing",{"href":600,"dataGaName":868,"dataGaLocation":830,"icon":869},"back to pricing","GoBack",{"altText":832,"config":871},{"src":834,"dataGaName":835,"dataGaLocation":830},{"altText":832,"config":873},{"src":838,"dataGaName":835,"dataGaLocation":830},{"title":875,"titleMobile":876,"button":877,"config":882},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":493,"config":878},{"href":879,"dataGaName":880,"dataGaLocation":881},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":883,"disabled":432},"release",{"data":885},{"text":886,"source":887,"edit":893,"contribute":898,"config":903,"items":908,"minimal":1118},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":888,"config":889},"View page source",{"href":890,"dataGaName":891,"dataGaLocation":892},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":894,"config":895},"Edit this page",{"href":896,"dataGaName":897,"dataGaLocation":892},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":899,"config":900},"Please contribute",{"href":901,"dataGaName":902,"dataGaLocation":892},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":904,"facebook":905,"youtube":906,"linkedin":907},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[909,956,1010,1054,1086],{"title":598,"links":910,"subMenu":925},[911,915,920],{"text":912,"config":913},"View plans",{"href":600,"dataGaName":914,"dataGaLocation":892},"view plans",{"text":916,"config":917},"Why Premium?",{"href":918,"dataGaName":919,"dataGaLocation":892},"/pricing/premium/","why premium",{"text":921,"config":922},"Why Ultimate?",{"href":923,"dataGaName":924,"dataGaLocation":892},"/pricing/ultimate/","why ultimate",[926],{"title":927,"links":928},"Contact Us",[929,932,934,936,941,946,951],{"text":930,"config":931},"Contact sales",{"href":782,"dataGaName":461,"dataGaLocation":892},{"text":785,"config":933},{"href":787,"dataGaName":788,"dataGaLocation":892},{"text":790,"config":935},{"href":792,"dataGaName":793,"dataGaLocation":892},{"text":937,"config":938},"Status",{"href":939,"dataGaName":940,"dataGaLocation":892},"https://status.gitlab.com/","status",{"text":942,"config":943},"Terms of use",{"href":944,"dataGaName":945,"dataGaLocation":892},"/terms/","terms of use",{"text":947,"config":948},"Privacy statement",{"href":949,"dataGaName":950,"dataGaLocation":892},"/privacy/","privacy statement",{"text":952,"config":953},"Cookie preferences",{"dataGaName":954,"dataGaLocation":892,"id":955,"isOneTrustButton":99},"cookie preferences","ot-sdk-btn",{"title":498,"links":957,"subMenu":966},[958,962],{"text":959,"config":960},"DevSecOps platform",{"href":480,"dataGaName":961,"dataGaLocation":892},"devsecops platform",{"text":963,"config":964},"AI-Assisted Development",{"href":487,"dataGaName":965,"dataGaLocation":892},"ai-assisted development",[967],{"title":968,"links":969},"Topics",[970,975,980,985,990,995,1000,1005],{"text":971,"config":972},"CICD",{"href":973,"dataGaName":974,"dataGaLocation":892},"/topics/ci-cd/","cicd",{"text":976,"config":977},"GitOps",{"href":978,"dataGaName":979,"dataGaLocation":892},"/topics/gitops/","gitops",{"text":981,"config":982},"DevOps",{"href":983,"dataGaName":984,"dataGaLocation":892},"/topics/devops/","devops",{"text":986,"config":987},"Version Control",{"href":988,"dataGaName":989,"dataGaLocation":892},"/topics/version-control/","version control",{"text":991,"config":992},"DevSecOps",{"href":993,"dataGaName":994,"dataGaLocation":892},"/topics/devsecops/","devsecops",{"text":996,"config":997},"Cloud Native",{"href":998,"dataGaName":999,"dataGaLocation":892},"/topics/cloud-native/","cloud native",{"text":1001,"config":1002},"AI for Coding",{"href":1003,"dataGaName":1004,"dataGaLocation":892},"/topics/devops/ai-for-coding/","ai for coding",{"text":1006,"config":1007},"Agentic AI",{"href":1008,"dataGaName":1009,"dataGaLocation":892},"/topics/agentic-ai/","agentic ai",{"title":1011,"links":1012},"Solutions",[1013,1015,1017,1022,1026,1029,1033,1036,1038,1041,1044,1049],{"text":542,"config":1014},{"href":537,"dataGaName":542,"dataGaLocation":892},{"text":531,"config":1016},{"href":514,"dataGaName":515,"dataGaLocation":892},{"text":1018,"config":1019},"Agile development",{"href":1020,"dataGaName":1021,"dataGaLocation":892},"/solutions/agile-delivery/","agile delivery",{"text":1023,"config":1024},"SCM",{"href":527,"dataGaName":1025,"dataGaLocation":892},"source code management",{"text":971,"config":1027},{"href":520,"dataGaName":1028,"dataGaLocation":892},"continuous integration & delivery",{"text":1030,"config":1031},"Value stream management",{"href":570,"dataGaName":1032,"dataGaLocation":892},"value stream management",{"text":976,"config":1034},{"href":1035,"dataGaName":979,"dataGaLocation":892},"/solutions/gitops/",{"text":580,"config":1037},{"href":583,"dataGaName":584,"dataGaLocation":892},{"text":1039,"config":1040},"Small business",{"href":589,"dataGaName":590,"dataGaLocation":892},{"text":1042,"config":1043},"Public sector",{"href":595,"dataGaName":596,"dataGaLocation":892},{"text":1045,"config":1046},"Education",{"href":1047,"dataGaName":1048,"dataGaLocation":892},"/solutions/education/","education",{"text":1050,"config":1051},"Financial services",{"href":1052,"dataGaName":1053,"dataGaLocation":892},"/solutions/finance/","financial services",{"title":603,"links":1055},[1056,1058,1060,1062,1065,1067,1070,1072,1074,1076,1078,1080,1082,1084],{"text":616,"config":1057},{"href":618,"dataGaName":619,"dataGaLocation":892},{"text":621,"config":1059},{"href":623,"dataGaName":624,"dataGaLocation":892},{"text":626,"config":1061},{"href":628,"dataGaName":629,"dataGaLocation":892},{"text":631,"config":1063},{"href":633,"dataGaName":1064,"dataGaLocation":892},"docs",{"text":654,"config":1066},{"href":656,"dataGaName":657,"dataGaLocation":892},{"text":1068,"config":1069},"What's new",{"href":714,"dataGaName":715,"dataGaLocation":892},{"text":649,"config":1071},{"href":651,"dataGaName":652,"dataGaLocation":892},{"text":668,"config":1073},{"href":670,"dataGaName":671,"dataGaLocation":892},{"text":676,"config":1075},{"href":678,"dataGaName":679,"dataGaLocation":892},{"text":681,"config":1077},{"href":683,"dataGaName":684,"dataGaLocation":892},{"text":686,"config":1079},{"href":688,"dataGaName":689,"dataGaLocation":892},{"text":691,"config":1081},{"href":693,"dataGaName":694,"dataGaLocation":892},{"text":696,"config":1083},{"href":698,"dataGaName":699,"dataGaLocation":892},{"text":701,"config":1085},{"href":703,"dataGaName":704,"dataGaLocation":892},{"title":717,"links":1087},[1088,1090,1092,1094,1096,1098,1102,1107,1109,1111,1113],{"text":725,"config":1089},{"href":727,"dataGaName":719,"dataGaLocation":892},{"text":730,"config":1091},{"href":732,"dataGaName":733,"dataGaLocation":892},{"text":738,"config":1093},{"href":740,"dataGaName":741,"dataGaLocation":892},{"text":743,"config":1095},{"href":745,"dataGaName":746,"dataGaLocation":892},{"text":748,"config":1097},{"href":750,"dataGaName":751,"dataGaLocation":892},{"text":1099,"config":1100},"Sustainability",{"href":1101,"dataGaName":1099,"dataGaLocation":892},"/sustainability/",{"text":1103,"config":1104},"Diversity, inclusion and belonging (DIB)",{"href":1105,"dataGaName":1106,"dataGaLocation":892},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":753,"config":1108},{"href":755,"dataGaName":756,"dataGaLocation":892},{"text":763,"config":1110},{"href":765,"dataGaName":766,"dataGaLocation":892},{"text":768,"config":1112},{"href":770,"dataGaName":771,"dataGaLocation":892},{"text":1114,"config":1115},"Modern Slavery Transparency Statement",{"href":1116,"dataGaName":1117,"dataGaLocation":892},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":1119},[1120,1123,1126],{"text":1121,"config":1122},"Terms",{"href":944,"dataGaName":945,"dataGaLocation":892},{"text":1124,"config":1125},"Cookies",{"dataGaName":954,"dataGaLocation":892,"id":955,"isOneTrustButton":99},{"text":1127,"config":1128},"Privacy",{"href":949,"dataGaName":950,"dataGaLocation":892},[1130],{"id":1131,"title":7,"body":431,"config":1132,"content":1134,"description":431,"extension":1138,"meta":1139,"navigation":99,"path":1140,"seo":1141,"stem":1142,"__hash__":1143},"blogAuthors/en-us/blog/authors/thong-kuah.yml",{"template":1133},"BlogAuthor",{"name":7,"config":1135},{"headshot":1136,"ctfId":1137},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667179/Blog/Author%20Headshots/tkuah-headshot.jpg","tkuah","yml",{},"/en-us/blog/authors/thong-kuah",{},"en-us/blog/authors/thong-kuah","xJQ-LmIxnbBOQkNtnl9ht3dSkez0P1ho7K_DlEVTHgM",[1145,1154,1162],{"title":1146,"description":1147,"heroImage":1148,"category":427,"date":1149,"authors":1150,"slug":1153,"externalUrl":431},"Confidential AI for GitLab Self-Hosted","Give developers AI coding agents in GitLab Duo without source code leaving a hardware-encrypted boundary — no GPUs needed.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1773866173/vte9qh8rriznvyclhkes.png","2026-08-06",[1151,1152],"Mathias Ewald","Martin Paloncy, Edgeless Systems","confidential-ai-for-gitlab-self-hosted",{"title":1155,"description":1156,"heroImage":1157,"category":427,"date":1158,"authors":1159,"slug":1161,"externalUrl":431},"Green DevOps: Why carbon measurement belongs in your CI/CD pipeline","CI/CD pipelines have a hidden carbon cost. Here's why measuring it matters, and how you can get started with Eco CI and Carmen in GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1765809212/noh0mdfn9o94ry9ykura.png","2026-07-09",[1160],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":1163,"description":1164,"heroImage":1165,"category":427,"date":1166,"authors":1167,"slug":1169,"externalUrl":431},"How to build CI/CD observability at scale","This practical guide to GitLab pipeline analytics helps self-managed users gain operational insights using Prometheus and Grafana.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-04-28",[1168],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"promotions":1171},[1172,1186,1198,1210],{"id":1173,"categories":1174,"header":1176,"text":1177,"button":1178,"image":1183},"ai-modernization",[1175],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":1179,"config":1180},"Get your AI maturity score",{"href":1181,"dataGaName":1182,"dataGaLocation":657},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":1184},{"src":1185},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":1187,"categories":1188,"header":1190,"text":1177,"button":1191,"image":1195},"devops-modernization",[1189,994],"product","Are you just managing tools or shipping innovation?",{"text":1192,"config":1193},"Get your DevOps maturity score",{"href":1194,"dataGaName":1182,"dataGaLocation":657},"/assessments/devops-modernization-assessment/",{"config":1196},{"src":1197},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":1199,"categories":1200,"header":1202,"text":1177,"button":1203,"image":1207},"security-modernization",[1201],"security","Are you trading speed for security?",{"text":1204,"config":1205},"Get your security maturity score",{"href":1206,"dataGaName":1182,"dataGaLocation":657},"/assessments/security-modernization-assessment/",{"config":1208},{"src":1209},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":1211,"paths":1212,"header":1215,"text":1216,"button":1217,"image":1222},"github-azure-migration",[1213,1214],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":1218,"config":1219},"See how GitLab compares to GitHub",{"href":1220,"dataGaName":1221,"dataGaLocation":657},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":1223},{"src":1197},{"header":1225,"blurb":1226,"button":1227,"secondaryButton":1232},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":1228,"config":1229},"Get your free trial",{"href":1230,"dataGaName":456,"dataGaLocation":1231},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":930,"config":1233},{"href":782,"dataGaName":461,"dataGaLocation":1231},1786803750040]