모든 버전 관리 시스템은 동일한 근본적인 문제를 해결해야 합니다: 시스템이 사용자들이 정보를 공유하도록 허용하면서도, 서로의 작업을 실수로 덮어쓰는 것을 어떻게 방지할까요? 사용자들이 저장소에서 서로의 변경 사항을 실수로 덮어쓰는 것은 너무나 쉽습니다.
다음 시나리오를 고려해 보세요: 해리와 샐리라는 두 명의 동료가 있다고 가정해 봅시다. 각자 동일한 저장소 파일을 동시에 편집하기로 결정합니다. 해리가 먼저 자신의 변경 사항을 저장소에 저장하면, (잠시 후) 샐리가 실수로 자신의 새로운 버전의 파일로 그 내용을 덮어쓸 가능성이 있습니다. 해리의 파일 버전이 영원히 사라지지는 않겠지만 (시스템이 모든 변경 사항을 기억하기 때문에), 해리가 만든 어떤 변경 사항도 샐리의 새 버전 파일에는 존재하지 않을 것입니다. 왜냐하면 샐리는 애초에 해리의 변경 사항을 본 적이 없기 때문입니다. 해리의 작업은 여전히 실질적으로 손실된 상태입니다—최신 파일 버전에서 누락되었거나—아마도 실수로 그렇게 된 것입니다. 이것은 우리가 분명히 피하고 싶은 상황입니다!
많은 버전 관리 시스템은 이 문제를 해결하기 위해 잠금-수정-잠금해제(lock-modify-unlock) 모델을 사용하는데, 이는 매우 간단한 해결책입니다. 이러한 시스템에서는 저장소가 한 번에 한 사람만 파일을 변경하도록 허용합니다. 먼저 해리는 파일을 변경하기 시작하기 전에 파일을 잠가야 합니다. 파일을 잠그는 것은 도서관에서 책을 빌리는 것과 매우 유사합니다; 해리가 파일을 잠갔다면 샐리는 해당 파일을 변경할 수 없습니다. 만약 샐리가 파일을 잠그려고 시도하면 저장소는 요청을 거부할 것입니다. 그녀가 할 수 있는 것은 파일을 읽고, 해리가 변경을 마치고 잠금을 해제할 때까지 기다리는 것뿐입니다. 해리가 파일의 잠금을 해제한 후, 그의 차례는 끝나고 이제 샐리가 잠그고 편집함으로써 자신의 차례를 가질 수 있습니다.
잠금-수정-잠금해제 모델의 문제는 다소 제한적이며, 종종 사용자에게 걸림돌이 된다는 점입니다
잠금은 관리 문제를 일으킬 수 있습니다. 때때로 해리는 파일을 잠그고는 잊어버리곤 합니다. 그동안 샐리는 파일을 편집하기 위해 계속 기다려야 하므로, 아무것도 할 수 없습니다. 그리고 해리는 휴가를 떠납니다. 이제 샐리는 관리자에게 해리의 잠금을 해제해달라고 요청해야 합니다. 결국 이러한 상황은 많은 불필요한 지연과 시간 낭비를 초래합니다.
잠금은 불필요한 직렬화를 야기할 수 있습니다. 만약 해리가 텍스트 파일의 앞부분을 편집하고 있는데, 샐리는 단순히 같은 파일의 뒷부분을 편집하고 싶다면 어떨까요? 이러한 변경 사항은 전혀 겹치지 않습니다. 변경 사항이 제대로 병합된다고 가정하면, 그들은 파일을 동시에 쉽게 편집할 수 있고 큰 문제는 없을 것입니다. 이 상황에서는 그들이 번갈아 가며 작업할 필요가 없습니다.
잠금은 잘못된 보안 인식을 심어줄 수 있습니다. 해리가 파일 A를 잠그고 편집하는 동안, 샐리는 동시에 파일 B를 잠그고 편집한다고 가정해 봅시다. 하지만 A와 B가 서로 의존적이며, 각 파일에 적용된 변경 사항이 의미적으로 호환되지 않는다고 가정해 봅시다. 갑자기 A와 B는 더 이상 함께 작동하지 않습니다. 잠금 시스템은 이 문제를 막을 힘이 없었지만—어쨌든 잘못된 보안 인식을 제공했습니다. 해리와 샐리는 파일을 잠그면 각자 안전하고 격리된 작업을 시작하는 것이라고 쉽게 생각할 수 있으며, 이로 인해 호환되지 않는 변경 사항에 대해 조기에 논의하는 것을 방해받게 됩니다.
Subversion, CVS 및 기타 버전 관리 시스템은 잠금 방식의 대안으로 복사-수정-병합(copy-modify-merge) 모델을 사용합니다. 이 모델에서 각 사용자의 클라이언트는 저장소를 읽고 파일 또는 프로젝트의 개인 작업 복사본(working copy)을 생성합니다. 사용자들은 병렬로 작업하며 자신의 개인 복사본을 수정합니다. 마지막으로, 개인 복사본들이 병합되어 새로운 최종 버전이 됩니다. 버전 관리 시스템은 종종 병합을 돕지만, 궁극적으로는 사람이 올바르게 병합되도록 책임집니다.
다음은 한 예시입니다. 해리와 샐리가 각각 저장소에서 복사한 동일한 프로젝트의 작업 복사본을 생성한다고 해 봅시다. 그들은 동시에 작업하며, 각자의 복사본 내에서 동일한 파일 A를 변경합니다. 샐리가 먼저 자신의 변경 사항을 저장소에 저장합니다. 해리가 나중에 자신의 변경 사항을 저장하려고 시도할 때, 저장소는 그의 파일 A가 구식(out-of-date)임을 알려줍니다. 다시 말해, 저장소의 파일 A가 그가 마지막으로 복사한 이후 어떤 식으로든 변경된 것입니다. 그래서 해리는 자신의 클라이언트에게 저장소의 새로운 변경 사항을 자신의 파일 A 작업 복사본으로 병합해달라고 요청합니다. 샐리의 변경 사항이 자신의 변경 사항과 겹치지 않을 가능성이 높습니다; 따라서 두 변경 사항을 모두 통합한 후에는 자신의 작업 복사본을 저장소에 다시 저장합니다.
하지만 샐리의 변경 사항이 해리의 변경 사항과 겹친다면 어떻게 될까요? 그렇다면 어떻게 해야 할까요? 이 상황을 충돌(conflict)이라고 부르며, 일반적으로 큰 문제는 아닙니다. 해리가 자신의 클라이언트에게 최신 저장소 변경 사항을 자신의 작업 복사본으로 병합해달라고 요청할 때, 그의 파일 A 복사본은 어떤 식으로든 충돌 상태로 플래그가 지정됩니다: 그는 충돌하는 두 가지 변경 사항을 모두 볼 수 있으며, 그 중에서 수동으로 선택할 수 있습니다. 소프트웨어는 충돌을 자동으로 해결할 수 없다는 점에 유의해야 합니다; 오직 사람만이 필요한 지능적인 선택을 이해하고 내릴 수 있습니다. 해리가 겹치는 변경 사항을 수동으로 해결한 후 (어쩌면 샐리와 충돌에 대해 논의함으로써!), 병합된 파일을 안전하게 저장소에 다시 저장할 수 있습니다.
복사-수정-병합 모델은 다소 혼란스럽게 들릴 수 있지만, 실제로는 매우 원활하게 작동합니다. 사용자들은 병렬로 작업하며 서로를 기다릴 필요가 없습니다. 같은 파일에서 작업할 때, 대부분의 동시 변경 사항이 전혀 겹치지 않는 것으로 밝혀졌습니다; 충돌은 드물게 발생합니다. 그리고 충돌을 해결하는 데 걸리는 시간은 잠금 시스템으로 인해 낭비되는 시간보다 훨씬 적습니다.
결론적으로, 모든 것은 하나의 중요한 요소로 귀결됩니다: 사용자 간의 소통입니다. 사용자들이 소통을 제대로 하지 않으면, 구문적 충돌과 의미적 충돌 모두 증가합니다. 어떤 시스템도 사용자에게 완벽하게 소통하도록 강요할 수 없으며, 어떤 시스템도 의미적 충돌을 감지할 수 없습니다. 따라서 잠금 시스템이 어떻게든 충돌을 방지할 것이라는 잘못된 약속에 현혹될 필요가 없습니다; 실제로는 잠금이 다른 무엇보다 생산성을 저해하는 것으로 보입니다.
잠금-수정-잠금해제 모델이 더 나은 한 가지 일반적인 상황이 있는데, 그것은 병합할 수 없는 파일이 있는 경우입니다. 예를 들어, 저장소에 그래픽 이미지가 포함되어 있고 두 사람이 동시에 이미지를 변경하는 경우, 이러한 변경 사항을 병합할 방법이 없습니다. 해리 또는 샐리 중 한 명은 자신의 변경 사항을 잃게 될 것입니다.