по мотивам «Subversion in Action»

Начало работы

Установка редактора

echo $SVN_EDITOR

Основы

Рабочие копии

Рабочая копия Subversion представляет собой обычное дерево каталогов локальной файловой системы, содержащее файлы. Рабочая копия является личным рабочим пространством, и изменения станут доступны другим только в случае их явной публикации. Можно иметь несколько рабочих копий одного и того же проекта.

Публикациия изменений осуществляется с помощью их записывания в репозиторий, а получение - с помощью чтения из репозитория.

Каждая рабочая копия содержит папку с именем .svn, известную как административный каталог (administrative directory). Файлы, находящиеся в административном каталоге, необходимы для понимания Subversion того какие артефакты содержат неопубликованные изменения, а какие являются устаревшими по отношению к актуальной версии.

Обычно, Subversion-репозиторий содержит файлы нескольких проектов, которые находятся в соответствующих дочерних каталогах дерева файловой системы этого репозитория.

Получение рабочей копии поддерева репозитория выполняется следующим образом:

$ svn checkout http://svn.example.com/repos/calc
A    calc/Makefile
A    calc/integer.c
A    calc/button.c
Checked out revision 56.

$ ls -A calc
Makefile  integer.c  button.c  .svn/

Расположение Subversion-репозитория всегда задается в виде URL.

Schema Способ доступа
file:/// доступ к репозиторию в файловой системе
http:// доступ по протоколу WebDAV к преднастроенному серверу Apache
https:// то же, что и http://, но с шифрованием SSL
svn:// доступ по внутреннему протоколу к серверу svnserve
svn+ssh:// то же, что и svn://, но через SSH-туннель

Публикация изменений обычно называется фиксацией (committing) и выполняется с помощью команды commit:

$ svn commit button.c
Sending        button.c
Transmitting file data .
Committed revision 57.

Для обновления локальной рабочей копии используется команда update:

$ svn update
U    button.c
Updated to revision 57.

Ревизии

Каждый коммит (commit) в репозитории рассматривается в виде атомарной транзакции - фиксируются либо все изменения, либо ни одного.

При приеме коммита в репозитории создается новое состояние дерева файловой системы, называемое ревизией (revision). Каждой ревизии присваивается уникальный возрастающий целочисленный номер. Начальная ревизия имеет номер 0 и соответствует пустому корневому каталогу.

The repository

Номера ревизий в Subversion относятся ко всему дереву, а не отдельному файлу.

Как рабочие копии отслеживают репозиторий

Для каждого файла Subversion поддерживает два основных свойства в административном каталоге .svn/:

  • базовая ревизия рабочего файла (рабочая ревизия);
  • отметка времени последнего обновления локальной копии из репозитория.

Используя эту информацию Subversion, при взаимодействии с репозиторием, определяет одно из 4-х состояний рабочего файла:

  • неизменен и актуален - файл не изменялся в рабочем каталоге, и репозиторий не содержит для него изменений после рабочей ревизии. svn commit и svn update выполнятся без внесения изменений.

  • изменен локально и актуален - файл был изменен в рабочем каталоге, но репозиторий не содержит для него изменений после рабочей ревизии. svn commit опубликует внесенные изменения, а svn update выполнится без внесения изменений.

  • неизменен и устарел - файл не изменялся в рабочем каталоге, но был изменен в репозитории. svn commit выполниться без внесения изменений, а svn update затянет (fold) последние изменения в рабочую копию.

  • изменен локально и устарел - файл был изменен как локально, так и в репозитории. svn commit завершится с ошибкой, т.к. файл должен быть сначала обновлен. svn update попытается совместить (merge) изменения в репозитории с локальными изменениями. Если автоматическое слияния невозможно, то разрешение конфликта выполняется пользователем.

Смешивание рабочих копий разных ревизий

Одной из особенностей Subversion является возможность иметь рабочую копию включающую файлы и каталоги разных рабочих ревизий.

Обновление и комиты разделены

Операции публикации не влекут обновление, и наоборот. Т.е. при изменении и последующем коммите одного файла в рабочей копии изменится только его рабочая ревизия. Рабочая ревизия всей копии изменится только после svn update.

Смешивание ревизий - это норма

Закоммиченные файлы будут иметь более поздний номер рабочей ревизии, чем все остальные. Для просмотра состояния рабочих ревизий можно воспользоваться командой svn status --verbose.

Смешивание ревизий - это полезно

Смешивание ревизий дает возможность переместить определенную часть рабочей копии в истории, позволяя исследовать более ранние изменения.

Смешивание ревизий имеет ограничения

  1. Невозможно зафиксировать (commit) удаление неактуального файла.
  2. Невозможно зафиксировать изменения метаданных неактуального каталога. Рабочая ревизия каталога имеет определенный набор свойств, и фиксация изменений свойств неактуального каталога может привести к утрате свойств, неизвестных рабочей копии.

Экскурсия по Subversion

Импорт

Для импортирования нового проекта в Subversion-репозиторий используется команда import:

$ svn import -m "New import" myproj \
            http://svn.red-bean.com/repos/trunk/misc/myproj
Adding         myproj/sample.txt
...
Transmitting file data .........
Committed revision 16.

Ревизии: номера, ключевые слова и даты

Для указания ревизии используется ключ --revision (-r):

svn --revision REV

Диапазон задается указанием номеров ревизий через двоеточие:

svn --revision REV1:REV2

Номера ревизий

Вновь созданный репозиторий начинается с нулевой ревизии, а каждый последующий коммит увеличивает номер ревизии на единицу.

$ svn commit --message "Corrected number of cheese slices."
Sending        sandwich.txt
Transmitting file data .
Committed revision 3.

Ключевые слова ревизий

Для некоторых ревизий Subversion поддерживает ключевые слова (т.е. синонимы), которые можно использовать в качестве аргументов ключа --revision:

  • HEAD - последняя (самая молодая) ревизия в репозитории.

  • BASE - номер ревизии артефакта в рабочей копии. Если артефакт был локально изменен, ревизия BASE ссылается на версию без изменений.

  • COMMITTED - последняя ревизия меньшая или равная BASE, в которой артефакт был изменен.

  • PREV - ревизия предшествующая последней ревизии в которой артефакт был изменен (технически COMMITTED - 1).

Subversion хранит оригинальную (pristine) копию каждого файла в административном каталоге .svn. Номер ревизии оригинальной копии соответствует ключевому слову BASE.

Данные ключевые слова могут использоваться только при указании локального пути, но не URL.

Примеры использования:

$ svn diff --revision PREV:COMMITTED foo.c
# покажет последние закоммиченные изменения foo.c

$ svn log --revision HEAD
# покажет сообщение лога для последнего коммита в репозитории

$ svn diff --revision HEAD
# сравнит рабочий файл (с локальными изменениями)
# с последней версией в репозитории

$ svn diff --revision BASE:HEAD foo.c
# сравнит оригинальную (pristine) версию foo.c
# с последней версией в репозитории

$ svn log --revision BASE:HEAD
# покажет логи всех коммитов с момента последнего обновления

$ svn update --revision PREV foo.c
# откатит последнее изменение foo.c
# (рабочая версия foo.c уменьшится)

Даты ревизий

Ключ --revision, также, может принимать в качестве параметра дату. В этом случае Subversion найдет наиболее позднюю ревизию на заданную дату.

$ svn checkout --revision {2002-02-17}
$ svn checkout --revision {15:30}
$ svn checkout --revision {15:30:00.200000}
$ svn checkout --revision {"2002-02-17 15:30"}
$ svn checkout --revision {"2002-02-17 15:30 +0230"}
$ svn checkout --revision {2002-02-17T15:30}
$ svn checkout --revision {2002-02-17T15:30Z}
$ svn checkout --revision {2002-02-17T15:30-04:00}
$ svn checkout --revision {20020217T1530}
$ svn checkout --revision {20020217T1530Z}
$ svn checkout --revision {20020217T1530-0500}
$ svn log --revision {2002-11-28}
------------------------------------------------------------------------
r12 | ira | 2002-11-27 12:31:51 -0600 (Wed, 27 Nov 2002) | 6 lines
...

При указании только даты, например 2002-11-27, Subversion дополнит ее временем 00:00:00.

Поиск в диапазоне дат выполняется следующим образом:

$ svn log --revision {2002-11-20}:{2002-11-29}
...

Начальное получение рабочей копии

Извлечение (checkout) репозитория создает его рабочую копию на локальной машине. Созданная рабочая копия соответствует HEAD-ревизии удаленного репозитория.

$ svn checkout http://svn.collab.net/repos/svn/trunk
A  trunk/subversion.dsw
A  trunk/svn_check.dsp
A  trunk/COMMITTERS
A  trunk/configure.in
A  trunk/IDEAS
...
Checked out revision 2499.

Каталог для рабочей копии задается следующим образом:

$ svn checkout http://svn.collab.net/repos/svn/trunk subv
A  subv/subversion.dsw
A  subv/svn_check.dsp
A  subv/COMMITTERS
A  subv/configure.in
A  subv/IDEAS
...
Checked out revision 2499.

В этом случае рабочая копия будет располагаться в каталоге subv вместо trunk.

Простой рабочий цикл

Типичный рабочий цикл выглядит следующим образом:

  1. Обновление рабочей копии
    • svn update
  2. Внесение изменений
    • svn add
    • svn delete
    • svn copy
    • svn move
  3. Проверка изменений
    • svn status
    • svn diff
    • svn revert
  4. Слияние опубликованных изменений с локальной рабочей копией
    • svn update
    • svn resolved
  5. Фиксация изменений
    • svn commit

Обновление рабочей копии

Обновление рабочей копии до состояния последней ревизии в репозитории выполняется командой svn update:

$ svn update
U  foo.c
U  bar.c
Updated to revision 2.

Буква перед артефактом означает действие выполняемое Subversion для обновление рабочей копии.

  • U foo - файл foo был обновлен (на основе полученных от сервера изменений).
  • A foo - файл или каталог foo был добавлен в рабочую копию.
  • D foo - файл или каталог foo был удален из рабочей копии.
  • R foo - файл или каталог foo был замещен в рабочей копии, т.е. foo был удален, и артефакт с таким же именем был добавлен. Хотя имя осталось прежним репозиторий считает, что артефакты являются различными объектами с отдельной для каждого историей.
  • G foo - файл foo получил новые изменения из репозитория, но локальная копия также была отредактирована. Изменения либо не пересекались, либо были идентичными, и Subversion успешно слил (merGed) полученные изменения с локальной версией.
  • C foo - для файла foo от сервера были получены изменения, напрямую пересекающиеся с локальными. Данный конфликт должен быть разрешен с участием пользователя.

Внесение изменений

Возможные изменения в рабочей копии:

  • изменение файлов - Subversion автоматически отслеживает такие изменения.
  • изменение дерева - данный тип изменений осуществляется явной пометкой Subversion файлов и каталогов на запланированное удаление, добавление, копирование или перемещение. Хотя данные операции могут оказать прямой эффект на локальную копию, в репозиторий изменения попадут только после коммита.

Основные команды изменения дерева приведены ниже:

  • svn add foo - планирует добавление в репозиторий файла, каталога или символьной ссылки. При последующем коммите foo станет дочерним артефактом по отношению к родительскому каталогу. В случае если foo является каталогом все его содержимое также будет запланировано на добавление. Для добавления каталога без содержимого необходимо указать ключ --non-recursive (-N).
  • svn delete foo - планирует удаление из репозитория файла, каталога или символьной ссылки. В случае файла или символьной ссылки удаление в локальной копии происходит сразу. При последующем коммите артефакт будет удален как из рабочей копии, так и из репозитория.
  • svn copy foo bar - создает новый артефакт bar как копию foo. Артефакт bar автоматически планируется на добавление. При последующем коммите bar добавляется в репозиторий вместе с историей отражающей факт копирования.
  • svn move foo bar - данная команда эквивалентна svn copy foo bar; svn delete foo.

Изменения, вносимые данными командами, не видны в репозитории до следующего коммита. Для внесения изменений напрямую в репозиторий можно использовать версии команд работающие с URL.

Проверка изменений

Команда svn status показывает все изменения в файлах и каталогах.

  L     some_dir            # для some_dir svn оставил блокировку в области .svn 
M       bar.c               # содержимое bar.c было изменено локально
 M      baz.z               # только зачения свойств bar.z были изменены
X       3rd_party           # 3rd_party является частью внешнего определения
?       foo.o               # svn не отслеживает foo.o
!       some_dir            # svn отслеживает артефакт, но он отсутствует или не полон
~       qux                 # версионируется как файл/каталог/ссылка, но тип был изменен
I       .screenrc           # svn игнорирует данный артефакт
A  +    moved_dir           # добавлен с историей
M  +    moved_dir/README    # добавлен с историей и имеет локальные изменения
D       stuff/fish.c        # файл запланирован на удаление
A       stuff/loot/bloo.h   # файл запланирован на добавление
C       stuff/loot/lump.c   # текст файла содержит конфликты из-за обновления
 C      stuff/loot/glub.c   # свойства файла содержат конфликты из-за обновления
R       xyz.c               # файл запланирован на замещение
    S   stuff/squawk        # файл или каталог был переведен в ветку
     K  dog.jpg             # файл заблокирован локально; метка блокирования присутствует
     O  cat.jpg             # файл заблокирован в репозитории другим пользователем
     B  bird.jpg            # файл заблокирован локально, но блокировка имеет дефект
     T  fish.jpg            # файл заблокирован локально, но блокировка перезахвачена

Вывод svn status включает пять колонок символов, после чего идут несколько пробелов, после чего идет имя файла или каталога.

В первой колонке печатается код статуса файла или каталога и/или их содержимого:

  • A item - файл, каталог или символьная ссылка запланированы на добавление в репозиторий.
  • C item - файл находится в состоянии конфликта, т.е. полученные от сервера изменения пересекаются с локальными изменениями в рабочей копии. Конфликт должен быть разрешен до отправки изменений в репозиторий.
  • D item - файл, каталог или символьная ссылка запланированы на удаление из репозитория.
  • M item - содержимое файла было изменено.
  • R item - файл, каталог или символьная ссылка запланированы на замещение соответствующего артефакта в репозитории, т.е. в рамках одной ревизии данный объект сначала будет удален, а после объект с таким же именем будет добавлен в репозиторий.
  • X item - каталог не версионируется, и относится к внешнему определению (externals definition) Subversion.
  • ? item - файл, каталог или символьная ссылка не версионируются. Вывод данных файлов можно опустить либо указав ключ --quiet (-q) для команды svn status, либо с помощью задания свойства svn:ignore для родительского каталога.
  • ! item - файл, каталог или символьная ссылка версионируются, но утрачены или каким-то образом повреждены. Артефакт может быть утрачен по причине удаления без использования команд Subversion. Каталог может быть поврежден в случае прерывания получения рабочей копии или обновления. Вызов svn update обновит файл или каталог из репозитория, а вызов svn revert file восстановит утраченный файл.
  • ~ item - файл, каталог или символьная ссылка находятся в репозитории в виде, отличающемся от того, что находится в рабочей копии. Например, в репозитории содержится файл, а в локальной копии - каталог с таким же именем, и при этом не использовались команды svn delete и svn add.
  • I item - файл, каталог или символьная ссылка не версионируются т.к. Subversion настроен на его игнорирование в ходе выполнения команд svn add, svn import и svn status. Такие файлы будут выводится только в случае указания ключа --no-ignore при вызове команды svn status.

Вторая колонка показывает статус свойств файла или каталога. В случае изменения свойств будет выведено M, иначе - пробел.

В случае если Subversion заблокировал рабочую область каталога (.svn) в третьей колонке будет выведено L, иначе - пробел. Такое возможно, например, в ходе редактирования сообщения коммита. Иначе, в случае если нет операций находящихся в процессе работы, может потребоваться вызов svn clean.

В случае если файл или каталог запланированы на добавление или изменение с дополнительной сопровождающей историей, в четвертой колонке будет выведен +, иначе - пробел. Обычно это связано с использованием svn move или svn copy.

Пятая колонка может содержать только пробел или S. Это означает, что файл или каталог был переключен с пути остальной рабочей копии на ветку (используя svn switch).

Шестая колонка показывает информацию о блокировках (отличных от L в третьей колонке).

Получение статуса конкрентного артефакта выполняется следующим образом:

$ svn status stuff/fish.c
D      stuff/fish.c

Вызов svn status с ключем --verbose (-v) выведет статус всех артефактов в рабочей копии, в том числе не изменившихся.

$ svn status --verbose
M               44        23    sally     README
                44        30    sally     INSTALL
M               44        20    harry     bar.c
                44        18    ira       stuff
                44        35    harry     stuff/trout.c
D               44        19    ira       stuff/fish.c
                44        21    sally     stuff/things
A                0         ?     ?        stuff/things/bloo.h
                44        36    harry     stuff/things/gloo.c

Смысл первой колонки остается таким же. Вторая колонка показывает номер рабочей ревизии артефакта. Третья и четвертая колонки показывают номер ревизии последнего изменения файла и имя пользователя внесшего эти изменения соответственно.

Все, перечисленные выше, команды работают без обращения к серверу. Для получения информации об актуальности артефактов используется ключ --show-updates (-u).

$ svn status --show-updates --verbose
M      *        44        23    sally     README
M               44        20    harry     bar.c
       *        44        35    harry     stuff/trout.c
D               44        19    ira       stuff/fish.c
A                0         ?     ?        stuff/things/bloo.h
Status against revision:   46

Звездочки говорят об имеющихся на сервере обновлениях.

Для получения информации о том, какие именно были внесены изменения используется команда svn diff:

$ svn diff
Index: bar.c
===================================================================
--- bar.c	(revision 3)
+++ bar.c	(working copy)
@@ -1,7 +1,12 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <stdio.h>

 int main(void) {
-  printf("Sixty-four slices of American Cheese...\n");
+  printf("Sixty-five slices of American Cheese...\n");
 return 0;
 }

Index: README
===================================================================
--- README	(revision 3)
+++ README	(working copy)
@@ -193,3 +193,4 @@ 
+Note to self:  pick up laundry.

Index: stuff/fish.c
===================================================================
--- stuff/fish.c	(revision 1)
+++ stuff/fish.c	(working copy)
-Welcome to the file known as 'fish'.
-Information on fish will be here soon.

Index: stuff/things/bloo.h
===================================================================
--- stuff/things/bloo.h	(revision 8)
+++ stuff/things/bloo.h	(working copy)
+Here is a new file to describe
+things about bloo.

Вывод команды представлен в универсальном формате (unified diff format).

Соответствующий патч (patch) можно получить вызвав:

$ svn diff > patchfile

Отмена изменений выполняется с помощью команды svn revert:

$ svn revert README
Reverted 'README'

В этом случае Subversion откатит файл до оригинального состояния находящегося в административной области .svn.

Концептуально, svn revert ITEM имеет тот же эффект, что и удаление артефакта из рабочей копии с последующим вызовом svn update -r BASE ITEM, за исключением отсутствия необходимости соединения с сервером.

Все три команды - svn status, svn diff и svn revert - выполняются без взаимодействия с сервером.

Разрешение конфликтов

$ svn update
U  INSTALL
G  README
C  bar.c
Updated to revision 46.

В приведенном выше выводе отметка файла символом C означает то, что локальные изменения пересекаются (overlapped) с изменениями пришедшими с сервера, и требуется ручное вмешательство.

При возникновении конфликта обычно происходят три вещи:

  1. Subversion выводит C при обновлении и запоминает, что файл находится в состоянии конфликта.
  2. В случае если для файла возможно контекстное, построчное слияния, в содержимое вставляются особые строки - маркеры конфлита (conflict markers), которые отделяют конфликтующие области.
  3. Для каждого конфликтующего файла Subversion создает три дополнительных неверсионируемых файла в рабочем каталоге:
    • filename.mine - файл, содержащий локальные изменения до обновления.
    • filename.rOLDREV - файл, в состоянии BASE-ревизии, т.е. перед внесением локальных изменений.
    • filename.rNEWREV - версия файла, полученная от сервера при обновлении (HEAD-ревизия репозитория).

Subversion не позволит выполнить commit при наличии данных файлов.

$ svn commit --message "Add a few more things"
svn: Commit failed (details follow):
svn: Aborting commit: '/home/sally/svn-work/sandwich.txt' remains in conflict

Разрешение конфликта можно выполнить тремя способами:

  • совместить конфликтующий текст вручную (при подсказке маркеров конфликта);
  • заменить рабочий файл на один из временных;
  • выполнить svn revert <filename> для отмены всех локальных изменений.

После разрешения конфликта необходимо уведомить об этом Subversion вызвав svn resolved:

$ svn resolved sandwich.txt
Resolved conflicted state of 'sandwich.txt'

Данная команда удалит три временных файла, и Subversion больше не будет считать, что файл находится в состоянии конфликта.

Слияние конфликта вручную

$ cat sandwich.txt
Top piece of bread
Mayonnaise
Lettuce
Tomato
Provolone
<<<<<<< .mine
Salami
Mortadella
Prosciutto
=======
Sauerkraut
Grilled Chicken
>>>>>>> .r2
Creole Mustard
Bottom piece of bread

Текст между <<<<<<< и ======= относится к локальным изменениям, а между ======= и >>>>>>> к изменениям полученным от сервера.

Замещение рабочего файла

$ svn update
C  sandwich.txt
Updated to revision 2.
$ ls sandwich.*
sandwich.txt  sandwich.txt.mine  sandwich.txt.r2  sandwich.txt.r1
$ cp sandwich.txt.r2 sandwich.txt
$ svn resolved sandwich.txt

Редактирование по-новой

$ svn revert sandwich.txt
Reverted 'sandwich.txt'
$ ls sandwich.*
sandwich.txt

В случае отката конфликтующего файла вызов svn resolved не требуется.

Фиксация изменений

Команда svn commit отправляет все изменения в репозиторий. Для указания комментария к коммиту используется опция --message (-m).

$ svn commit --message "Corrected number of cheese slices."
Sending        sandwich.txt
Transmitting file data .
Committed revision 3.

Для получения комментария из файла используется ключ --file:

$ svn commit --file logmsg 
Sending        sandwich.txt
Transmitting file data .
Committed revision 4.

В случае отсутствия --message или --file Subversion запустит редактор по-умолчанию.

В случае наличия в репозитории изменений для закомиченного файла svn commit завершится с ошибкой:

$ svn commit --message "Add another rule"
Sending        rules.txt
svn: Commit failed (details follow):
svn: Out of date: 'rules.txt' in transaction 'g'

В этом случае необходимо запустить svn update, разрешить возникшие конфликты и повторить вызов.

Итоги

Извлечение рабочей копии

svn co svn+ssh://<host or IP>/repo\\trunk [targetDir]

Обновление репозитория

svn update
svn up

Работа с файлами

svn add src/main/java/Application.java
svn delete src/main/java/Application.java
svn copy src/main/java/Service1.java src/main/java/Service2.java
svn move src/main/java/Service1.java src/main/java/Service2.java

Проверка текущего состояния

svn status
svn status --show-updates
svn diff

Фиксация изменений и отправка на сервер

svn commit -m "PROJ-0001 Description"

Разрешение конфликта

svn resolved src/main/java/Application.java

Наборы изменений

svn changelist PROJ-0001 src/main/java/Appliaction.java \
                         src/main/java/Utils.java
svn diff --changelist PROJ-0001
svn commit --changelist PROJ-0001 -m "PROJ-0001 Description"

Просмотр истории

svn log | less
svn log -r 100500 --diff | less

Откат изменений

svn merge -c -100500 .