Наиболее часто используемые команды и методы использования системы управления версиями Mercurial. Перевод части 5 "Ежедневное использование Mercurial" (Mercurial in daily use [1]) полного руководства от Bryan O'Sullivan (Mercurial: The Definitive Guide by Bryan O'Sullivan).
Для начала несколько слов о том, что такое система управления версиями, к которым относится Mercurial. Если некоторые термины будут непонятны, обращайтесь в конец статьи, в раздел Словарик. Система управления версиями предназначена для следующих целей:
1. Отслеживание истории изменения файлов в проекте. 2. Удобного просмотра истории изменений. Можно посмотреть, какие были сделаны изменения на в каждом файле. 3. Отката к нужному пункту истории. Т. е. может быть получена любая копия отслеживаемого файла на любой ранней стадии разработки. 4. Предоставление возможности нескольким разработчикам независимо друг от друга работать над одним и тем же проектом, и даже над одним и тем же файлом исходного кода. Т. е. с помощью системы контроля версий эти изменения, сделанные разными разработчиками, можно отследить, и удобным образом объединить в единый проект. 5. Для резервного копирования проекта, организации его зеркальных копий.
Системы управления версиями бывают централизованными и распределенными. У каждого из вариантов есть свои достоинства и недостатки. Централизованная система предполагает наличие единственного главного хранилища - репозитория, с которым должны работать все участники проекта. Нет главного репозитория - работа невозможна. Система Mercurial относится к распределенным системам, в этом случае каждый разработчик имеет собственную копию репозитория, и может работать независимо и от других участников проекта, и от главного репозитория. Не требуется даже постоянное подключение к общей сети. Изменениями могут обмениваться друг с другом как отдельные разработчики напрямую (действия push и pull), так же может быть и обмен с главным репозиторием.
[Скажите Mercurial, за какими файлами нужно наблюдать]
Система Mercurial не будет работать с файлами в Вашем репозитории, пока Вы не скажете ей поддерживать эти файлы. Команда hg status покажет Вам, о каких файлах Mercurial ничего не знает; для отображения таких файлов используют подкоманду “?”. Чтобы указать Mercurial отслеживать файл, используйте команду hg add. Как только Вы добавили файл, запись в выводе команды hg status поменяется с "?" на "A". Пример:
$ hg init add-example
$ cd add-example
$ echo a > myfile.txt
$ hg status
? myfile.txt
$ hg add myfile.txt
$ hg status
A myfile.txt
$ hg commit -m 'Added one file'
$ hg status
После запуска команды hg commit все файлы, которые были добавлены через commit, больше не будут отображаться в листинге команды hg status. Причина такого поведения - команда hg status говорит только о том, какие файлы могли бы Вас заинтересовать. Например, hg status сообщит о файлах, которые были изменены, удалены или переименованы, но не всех остальных файлах. Смысл тут в том, что если в Вашем репозитории находятся тысячи файлов, то Вы редко будете нуждаться что-то знать о неизменившихся файлах, которые отслеживаются Mercurial. Однако Вы все же можете получить такую информацию, об этом поговорим позже.
Как только Вы добавили файл (или файлы) командой hg add [files], Mercurial пока ничего особенного с ним не делает. Вместо этого Mercurial сделает снимок состояния этого файла в тот момент, когда Вы выполните фиксацию commit. Mercurial также будет продолжать отслеживать изменения, которые Вы сделаете в файле всякий раз, когда Вы будете производить новые фиксации (commit) - до тех пор, пока Вы не удалите файл.
Именование файла: неявное (implicit) в сравнении с явным (explicit)
Полезным свойством Mercurial является следующее: если Вы передадите в команде имя директории (папки), то такая команда будет обработана системой Mercurial так, что как будто Вы ей сказали: "Мне нужно поработать с каждым файлом в этой директории и её поддиректориях". Пример:
$ mkdir b
$ echo b > b/somefile.txt
$ echo c > b/source.cpp
$ mkdir b/d
$ echo d > b/d/test.h
$ hg add b
adding b/d/test.h
adding b/somefile.txt
adding b/source.cpp
$ hg commit -m 'Added all files in subdirectory'
Обратите внимание: в этом примере система Mercurial распечатала имена файлов, которые были добавлены, в отличие от предыдущего примера, когда мы добавили только один файл myfile.txt. Произошло следующее: в предыдущем примере мы явно указали имя файла (explicit file name) для добавления в командной строке. Предположение, которое Mercurial делает в таких случаях - мы знаем то, что мы делаем, и Mercurial не распечатывает дополнительную информацию.
Однако, когда мы неявно (imply) указываем имена файлов путем указания имени директории, Mercurial производит дополнительные действия при печати имени каждого файла, с которым она что-то делает. Это дает больше информации о том, что происходит, и уменьшает количество неприятных и неожиданных сюрпризов. Такое поведение является общим для большинства команд Mercurial.
Mercurial следит только за файлами, но не директориями
Система Mercurial никак не отслеживает информацию о директориях. Вместо этого она отслеживает путь до файла. Перед тем, как создать файл, Mercurial сначала создает недостающие директории, которые составляют путь до файла. После того, как Mercurial удаляет файл, она затем удаляет любые пустые директории, которые которые находятся в пути файла. Это отличие кажется тривиальным, но у него есть одно незначительное практическое следствие: Mercurial не видит пустых каталогов и не сохраняет информацию о них.
Пустые директории редко могут быть полезными, и есть ненавязчивые обходные решения, которые можете использовать, чтобы достигнуть надлежащего эффекта. Поэтому разработчики системы Mercurial почувствовали, что сложность реализация управления пустыми каталогами не стоит ограниченного преимущества, которое принесет эта функция.
Если Вам нужно иметь в Вашем репозитории пустую папку, то есть несколько способов добиться этого. Один из них следующий: создайте папку, и выполните команду hg add для скрытого (hidden) файла в этом каталоге. В Unix-системах принято, что любое имя файла, начинающееся с точки (“.”), считается скрытым и для большинства команд и утилит GUI. Пример использования этого метода:
$ hg init hidden-example
$ cd hidden-example
$ mkdir empty
$ touch empty/.hidden
$ hg add empty/.hidden
$ hg commit -m 'Manage an empty-looking directory'
$ ls empty
$ cd ..
$ hg clone hidden-example tmp
updating working directory
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ ls tmp
empty
$ ls tmp/empty
Другой способ для сохранения пустых директорий - создание директорий с помощью скрипта автоматической сборки перед тем, как директория потребуется (например, с помощью makefile).
[Как остановить отслеживание файла (stop tracking a file)]
После того как решите, что файл больше не должен находиться в Вашем репозитории, используйте команду hg remove. Эта команда удаляет файл, и говорит Mercurial остановить отслеживание файла (это произойдет при следующей фиксации). Удаленный файл (removed file) представлен в списке команды hg status с пометкой “R”. Пример:
$ hg init remove-example
$ cd remove-example
$ echo a > a
$ mkdir b
$ echo b > b/b
$ hg add a b
adding b/b
$ hg commit -m 'Small example for file removal'
$ hg remove a
$ hg status
R a
$ hg remove b
removing b/b
После выполнения hg remove для файла, Mercurial не будет больше отслеживать изменения файла, даже если создадите файл заново с тем же самым именем в Вашей рабочей директории. Если нужно заново создать файл и снова отслеживать его системой Mercurial, просто добавьте этот файл командой hg add. Mercurial будет знать, что заново добавленный файл не связан со старым файлом, который имеет такое же имя.
Удаление файла никак не влияет на его историю
Важно понимать, что удаление файла имеет только два эффекта:
- Из рабочей директории удаляется только текущая версия файла (файл просто удаляется с диска).
- Происходит остановка отслеживания изменений файла, начиная от последующей фиксации (commit).
Удаление файла ни в коем случае не изменяет историю этого файла.
??(Жаль, что нет возможности сделать обратное - удалить из отслеживания и историю, но оставить его рабочую копию. Иногда бывает позарез нужно полностью убрать из репозитория какой-нибудь конфиденциальный файл и его историю, например файл с паролями, но сделать это нет никакой возможности.)??
?? hg forget ??
Если вы обновите (имеется в виду команда update) рабочую директорию к changeset, который был зафиксирован, когда Вы все еще отслеживали файл, который позже удалили, то файл снова появится в рабочем каталоге с содержимым, которое файл имел при фиксации changeset-а. Если Вы обновите (update) рабочую директорию к более позднему changeset-у, в котором файл уже был удален, то Mercurial снова удалит файл из рабочей директории.
Пропавшие файлы (Missing files)
Удаленные вручную файлы (т. е. те файлы, которые были удалены не с помощью команды hg remove, а проводником или в консоли) Mercurial считает пропавшими (недостающими). Пропавшие файлы помечаются значком "!" в листинге вывода команды hg status. Команды Mercurial обычно ничего не делают с пропавшими файлами. Пример:
$ hg init missing-example
$ cd missing-example
$ echo a > a
$ hg add a
$ hg commit -m 'File about to be missing'
$ rm a
$ hg status
! a
Если Ваш репозиторий имеет файлы, которые команда hg status показывает со статусом missing, и Вы хотите чтобы такого не было, то можете в любой момент выполнить команду hg remove --after, чтобы указать Mercurial, что Вы реально хотите удалить эти файлы.
$ hg remove --after a
$ hg status
R a
Или наоборот, если файлы были удалены случайно и их нужно восстановить, воспользуйтесь командой hg revert. Файл появится снова в немодифицированном виде (с того момента, как был зафиксирован). Пример:
$ hg revert a
$ cat a
a
$ hg status
Почему системе явно говорят удалить файл?
У Вас может появиться вопрос, почему Mercurial требует, чтобы явно сказали ему, что удаляете файл. В ранних версиях Mercurial можно было удалять файл в любой момент, когда понравится; Mercurial замечал отсутствие файла автоматически, и при следующем запуске фиксации (hg commit) трекинг файла прекращался. На практике оказалось, что при таком алгоритме легко случайно удалить файл без получения оповещения об этом.
Полезное сокращение — добавление и удаление файлов за один шаг
Mercurial поддерживает комбинированную команду hg addremove, которая добавляет неотслеживаемые файлы и одновременно помечает пропавшие (missing) файлы как удаленные (removed). Пример:
$ hg init addremove-example
$ cd addremove-example
$ echo a > a
$ echo b > b
$ hg addremove
adding a
adding b
Команда hg commit также предоставляет опцию -A, которая выполняет ту же функцию add-and-remove, за которой немедленно следует фиксация (commit). Пример:
$ echo c > c
$ hg commit -A -m 'Commit with addremove'
adding c
[Копирование файлов]
Mercurial предоставляет команду hg copy, которая позволяет Вам сделать новую копию файла. Когда Вы копируете файл с использованием этой команды, Mercurial делает запись об факте, что новый файл является копией оригинального файла. Эта запись позволяет делать специальную обработку копий файлов при объединении (merge) Вашей работы с чьей-то еще.
Результаты копирования во время слияния (merge)
То, что происходит во время слияния, - это то, что изменения "следуют" за копией. Чтобы хорошо проиллюстрировать, что это означает, давайте создадим пример. Мы начнем с обычного крошечного репозитория, который содержит единственный файл.
$ hg init my-copy
$ cd my-copy
$ echo line > file
$ hg add file
$ hg commit -m 'Added a file'
Сделаем некую работу параллельно, чтобы у нас было, с чем объединиться. Поэтому давайте клонировать наш репозиторий.
$ cd ..
$ hg clone my-copy your-copy
updating working directory
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
Вернемся в первоначальную директорию, и воспользуемся командой hg copy для создания копии первого созданного файла.
$ cd my-copy
$ hg copy file new-file
Если мы посмотрим на вывод команды hg status, то скопированный файл будет выглядеть просто как обычный добавленный файл.
$ hg status
A new-file
Однако если мы передадим опцию -C команде hg status, то выведется другая строка: это - файл, с которого был скопирован наш недавно добавленный файл.
$ hg status -C
A new-file
file
$ hg commit -m 'Copied file'
Теперь, перейдя в клонированный репозиторий, сделаем параллельные изменения. Мы добавим еще строку к исходному файлу, который мы создали.
$ cd ../your-copy
$ echo 'new contents' >> file
$ hg commit -m 'Changed file'
Теперь у нас есть модифицированный файл в этом репозитории. Когда мы получим изменения (pull the changes) из первого репозитория, и объединим их (merge), Mercurial распространит изменения, которые были сделаны локально в file, в его копию, new-file.
$ hg pull ../my-copy
pulling from ../my-copy
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)
$ hg merge
merging file and new-file to new-file
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ cat new-file
line
new contents
Почему изменения должны следовать за копиями?
Такое поведение, что изменения файла распространяются на копии, может выглядеть несколько эзотерическим, однако во многих случаях оно весьма затребовано.
Прежде всего, запомните: это распространение происходит только тогда, когда делается объединение (merge). Так, если Вы делаете hg copy файла, и в последствии модифицируете оригинальный файл в процессе работы, то ничего не произойдет.
Второе, что следует знать - модификации распространяются на копии только только пока объединяемый набор изменений (changeset) еще не видел изменения копии.
Причина такого поведения Mercurial следующая. Предположим, я делаю важное исправление ошибки в исходном файле и фиксирую (commit) мои изменения. Тем временем Вы решили сделать hg copy файла в репозитории, не зная об ошибке или фиксации, и начали работать над копией файла.
Если Вы получите (pull) и сольете (merge) мои изменения, и Mercurial не распространит изменения на копии, то Ваш новый исходный файл будет содержать старую ошибку (исправленную мной в оригинале файла), и если не знали, что нужно распространить исправление ошибки вручную, то ошибка останется в Вашей копии файла.
Путем автоматического распространения изменения, в котором исправлен баг, от оригинального файла на копию, Mercurial предотвращает проблему такого рода. Насколько мне известно, из систем контроля версий только Mercurial распространяет изменения между копиями, как описано здесь.
Как только в Вашей истории изменений появится запись о копии (copy) и последующем произошедшем слиянии (merge), обычно нет никакой дальнейшей потребности распространять изменения от оригинального файла до копии файла, и именно поэтому Mercurial распространяет изменения на копию только до первого слияния (merge), но не в дальнейшем.
Как сделать так, чтобы сделанные изменения не передавались в копию
Если, по каким-то причинам решено что для Вашей работы не нужна автоматическая передача изменений между копиями, то просто сделайте копию файла с помощью команды операционной системы, а не через Mercurial (на системах типа Unix это команда cp, а на Windows это команды copy и xcopy), и затем вручную выполните команду hg add. Перед тем, как делать это, пожалуйста прочитайте еще раз секцию “Почему изменения должны следовать за копиями?”, сделайте обоснованное решение, то такое поведение не относится к Вашему случаю.
Поведение команды hg copy
Когда Вы используете команду hg copy, система Mercurial делает копию каждого исходного файла в том виде, в каком он находится в рабочей директории. Это означает, что если Вы сделали некоторые модификации в файле, и затем сделали hg copy без предварительной фиксации этих изменений (commit changes), то новая копия будет также содержать сделанные Вами модификации, которые были сделаны до этой точки (я считаю такое поведение несколько парадоксальным, поэтому здесь об этом и упоминаю).
Команда hg copy действует так же, как и команда cp операционной системы Unix (если хотите, можете использовать команду-алиас hg cp). Для команды hg copy Вы должны предоставить два или более параметра, в которых последний параметр будет обработан как место назначения (destination), и все остальные параметры как источники (sources).
Если Вы передадите команде hg copy один файл в качестве источника (source), и место назначения не существует (файл destination отсутствует), то система Mercurial создаст новую копию файла с этим именем.
$ mkdir k
$ hg copy a k
$ ls k
a
Если место назначения является директорией (папкой), то система Mercurial скопирует источники в эту директорию.
$ mkdir d
$ hg copy a b d
$ ls d
a b
Копирование директории является рекурсивным, и сохраняет структуру директорий источника.
$ hg copy z e
copying z/a/c to e/a/c
Если и source, и destination оба являются директориями, то дерево папок и файлов source будет воссоздано в папке destination.
$ hg copy z d
copying z/a/c to d/z/a/c
Как и с командой hg remove, если Вы копировали файлы вручную и затем хотите дать Mercurial информацию от том, что Вы файлы скопировали, просто используйте опцию --after в команде hg copy.
$ cp a n
$ hg copy --after a n
[Переименование файлов]
Обычно чаще нужно переименовать файл, чем сделать его копию. Однако я обсуждал сначала команду hg copy перед переименованием файлов, так как Mercurial делает обработку переименования таким же способом, как и копирование. Таким образом знания о том, что Mercurial делает при копировании скажут Вам о том, что произойдет, когда Вы будете переименовывать файл.
Когда Вы используете команду hg rename, Mercurial делает копию каждого исходного файла, затем удаляет их и помечает исходные файлы как удаленные.
$ hg rename a b
Команда hg status покажет заново скопированные (переименованные) файлы как добавленные, и файлы источники как удаленные.
$ hg status
A b
R a
Как и с результатами команды hg copy, мы должны использовать опцию -C, чтобы команда hg status видела добавленные файл отслеживаемыми системой Mercurial как копии оригинального файла, теперь уже удаленного.
$ hg status -C
A b
a
R a
С командой hg remove, как и с hg copy, Вы можете сказать Mercurial о переименовании постфактум с использованием опции --after. В большинстве других случаев поведение команды hg rename и список принимаемых ею опций те же самые, как и команды hg copy.
Если Вы знакомы с командной строкой Unix, то будете рады узнать что команда переименования Mercurial может быть вызвана как hg mv.
Переименование файлов и слияние изменений
Поскольку переименование в Mercurial реализовано как последовательное copy-и-remove, при переименовании происходит такое же распространений изменения файла, как и после копирования.
Если я модифицирую файл, и Вы переименовываете его, после чего файл получает новое имя, и затем мы делаем слияние (merge) наших соответствующих изменений, то мои модификации файла с оригинальным именем буду распространены на файл с новым именем. (Это может показаться Вам "простой работой", однако не все системы контроля версий действительно делают это.)
Принимая в внимание, что для поведения, что изменения будут следовать за копией, Вы можете кивнуть и сказать - "да, это может быть полезным", для Вас должно быть ясно, что распространение изменений на переименованный файл является действительно важным. Без такой функции полезные изменения могут просто исчезнуть без следа, когда файлы были переименованы, и никто не догадается, что изменения потерялись.
Расхождение в переименовании и слиянии (Divergent renames and merging)
Случаи расхождения (divergence) в именах происходят, когда два разработчика начинают называть файлы по-разному в своих соответствующих репозиториях.
$ hg clone orig anne
updating working directory
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg clone orig bob
updating working directory
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
Anne переименовала файл foo в bar.
$ cd anne
$ hg rename foo bar
$ hg ci -m 'Rename foo to bar'
Тем временем Bob переименовывает его в quux. (Помните, что команда hg mv является алиасом hg rename.)
$ cd ../bob
$ hg mv foo quux
$ hg ci -m 'Rename foo to quux'
Мне нравится думать об этом как о конфликте, потому что у каждого девелопера свои намерения по поводу именования файла.
Как Вы думаете, что должно произойти, когда разработчики решат слить вместе (merge) свои проекты? Фактическое поведение Mercurial состоит в том, что всегда сохраняется оба имени, когда происходит объединение набора изменений (merges changesets), которые содержат различающиеся переименования.
# See http://www.selenic.com/mercurial/bts/issue455
$ cd ../orig
$ hg pull -u ../anne
pulling from ../anne
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ hg pull ../bob
pulling from ../bob
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)
$ hg merge
warning: detected divergent renames of foo to:
bar
quux
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ ls
bar quux
Обратите внимание, что Mercurial предупреждает о различающихся переименованиях (divergent renames), и оставляет их на Ваше усмотрение - чтобы Вы что-нибудь сделали с их различиями после слияния (merge).
Конвергентное переименование и слияние
Другой проблемный случай переименования происходит, когда два человека сделали выбор переименовать разные исходные файлы (несколько source files с разными именами) в одно то же самое конечное имя файла (один destination file). В этом случае Mercurial запускает обычный процесс слияния, и позволяет Вам направить систему Mercurial к подходящему решению для слияния.
Другие частные случаи, связанные с именованием
Mercurial имеет давнюю ошибку, по которой он не может обработать слияние, в котором у одной стороны есть файл с указанным именем, в то же время у другой стороны есть каталог с таким же именем.
$ hg init issue29
$ cd issue29
$ echo a > a
$ hg ci -Ama
adding a
$ echo b > b
$ hg ci -Amb
adding b
$ hg up 0
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ mkdir b
$ echo b > b/b
$ hg ci -Amc
adding b/b
created new head
$ hg merge
abort: Is a directory: /tmp/issue29ozeAHx/issue29/b
[Восстановление из ошибочных ситуаций]
Mercurial имеет некоторые полезные команды, которые помогут Вам исправить некоторые общие ошибки.
Команда hg revert позволят откатить (undo) изменения, которые Вы сделали в рабочей директории. Например, если Вы случайно выполнили для файла команду hg add, просто запустите hg revert с именем файла, который Вы добавили, и файл в любом случае останется нетронутым, и он также не будет больше отслеживаться системой Mercurial.
Полезно помнить, что команда hg revert полезна для изменений, которые еще не зафиксированы (not committed). Если Вы заметили ошибку после фиксации, то Вы все еще можете что-то сделать для исправления положения, однако Ваши возможности будут более ограничены.
Более подробную информацию о команде hg revert, и подробности о том, что делать с уже фиксированными изменениями, см. в Главе 9 "Как найти и исправить ошибки" (Chapter 9, Finding and fixing mistakes).
[Работа с хитрыми слияниями (Dealing with tricky merges)]
В сложном большом проекте бывает непросто слить два набора изменений (merge of two changesets). Предположим, что есть большой исходный файл, который интенсивно редактировался с обеих сторон слияния: это почти неизбежно приводит к конфликтам, некоторые из которых могут быть разрешены только с нескольких попыток.
Давайте разработаем простой случай такого рода, и посмотрим, как с ним можно разобраться. Мы начнем с репозитория, содержащего один файл, и клонируем его дважды.
$ hg init conflict
$ cd conflict
$ echo first > myfile.txt
$ hg ci -A -m first
adding myfile.txt
$ cd ..
$ hg clone conflict left
updating working directory
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg clone conflict right
updating working directory
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
В одном из клонов мы модифицируем файл.
$ cd left
$ echo left >> myfile.txt
$ hg ci -m left
В другом клоне мы изменим файл по-другому.
$ cd ../right
$ echo right >> myfile.txt
$ hg ci -m right
Затем мы получим изменения в оригинальном repo.
$ cd ../conflict
$ hg pull -u ../left
pulling from ../left
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg pull -u ../right
pulling from ../right
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
not updating, since new heads added
(run 'hg heads' to see heads, 'hg merge' to merge)
Мы ожидаем, что наш репозиторий будет содержать две головы.
$ hg heads
changeset: 2:adc157afe092
tag: tip
parent: 0:b704237e4749
user: Bryan O'Sullivan <bos@serpentine.com>
date: Tue May 05 06:55:25 2009 +0000
summary: right
changeset: 1:64abc51bea49
user: Bryan O'Sullivan <bos@serpentine.com>
date: Tue May 05 06:55:25 2009 +0000
summary: left
Если мы сейчас запустим на выполнение команду hg merge, нас перекинет в интерфейс GUI который позволит нам вручную разрешить конфликт путем редактирования myfile.txt. Однако для упрощения представления ситуации, мы хотели бы, чтобы вместо этого сразу перестало бы работать слияние. Вот один способ, как мы можем это реализовать.
$ export HGMERGE=merge
Мы указали системе слияния Mercurial выполнить команду false (которая, как мы и хотим, сразу приведет к сбою) если она обнаружит слияние, с которым нельзя разобраться автоматически.
Если мы теперь запустим hg merge, то работа должна сразу завершиться с сообщением об отказе.
$ hg merge
merging myfile.txt
merge: warning: conflicts during merge
merging myfile.txt failed!
0 files updated, 0 files merged, 0 files removed, 1 files unresolved
use 'hg resolve' to retry unresolved file merges or 'hg up --clean' to abandon
Даже если мы не заметим отказ в слиянии, Mercurial предотвратит от случайной фиксации результата при ошибки слияния.
$ hg commit -m 'Attempt to commit a failed merge'
abort: unresolved merge conflicts (see hg resolve)
Когда hg commit в этом случае завершится с ошибкой, то предполагается, мы использовали незнакомую команду hg resolve. Как обычно, команда hg help resolve распечатает подходящую подсказку.
Состояния разрешения файла (File resolution states)
Когда происходит слияние (merge), большинство файлов остаются немодифицированными. Для каждого файла, который был как-то изменен системой Mercurial, будет отслежено состояние файла.
- файл называется разрешенным (resolved), если он успешно слит - либо автоматически системой Mercurial, либо вручную при вмешательстве человека - неразрешенный (unresolved) не был успешно слит, и требует к себе особого внимания.
Если Mercurial видит любой файл в неразрешенном состоянии (unresolved state) после слияния (merge), то она решает, что слияние потерпело ошибку. К счастью, мы не должны перезапустить все слияние с нуля.
Опция --list или -l в команде hg resolve распечатает состояние каждого сливаемого файла.
$ hg resolve -l
U myfile.txt
В выводе команды hg resolve разрешенные (resolved) файлы будут помечены R, а неразрешенные (unresolved) будут помечены U. Если имеются любые файлы, помеченные в списке U, мы знаем, что попытка фиксации результата слияния потерпит ошибку.
Разрешение слияния файлов (Resolving a file merge)
У нас есть несколько опций для перемещения файла из состояния unresolved в состояние resolved. Безусловно наиболее распространенный способ - повторно выполнить hg resolve. Если мы передадим индивидуальные имена файлов или каталогов, система Mercurial сделает попытку слияния любых unresolved файлов, если они имеются в этих указанных местах. Мы можем также передать опцию --all или -a, которая попытается слить все unresolved файлы.
Mercurial также позволяет нам напрямую модифицировать состояние разрешения (resolution state) файла. Мы можем вручную пометить файл как resolved с использованием опции --mark, или как unresolved с использованием опции --unmark. Это позволяет нам вручную сделать очистку особенно грязного слияния и отслеживать наш процесс продвижения слияния по каждому файлу.
[Более полезный оператор сравнения (More useful diffs)]
Вывод по умолчанию команды hg diff обратно совместим со стандартной командой diff command, но у этого есть некоторые недостатки.
Рассмотрим случай, в котором мы используем команду hg rename для переименования файла.
$ hg rename a b
$ hg diff
diff -r 4b300eaa7199 a
--- a/a Tue May 05 06:55:21 2009 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-a
diff -r 4b300eaa7199 b
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/b Tue May 05 06:55:21 2009 +0000
@@ -0,0 +1,1 @@
+a
Показанный выше вывод diff затеняет тот факт, что мы просто переименовали файл. Команда hg diff понимает опцию --git или -g для использования более новой формы формата diff, который отображает такую информацию в более читаемом виде.
$ hg diff -g
diff --git a/a b/b
rename from a
rename to b
Эта опция нам помогает, тогда как без нее мы были бы сбиты с толку: файл, который команда hg status указывает как модифицированный, но для него hg diff не выводит различий. Эта ситуация может возникнуть, если мы поменяем права доступа к файлу (file's execute permissions).
$ chmod +x a
$ hg st
M a
$ hg diff
Обычная команда diff не обратит никакого внимания на права доступа к файлу (file permissions), поэтому hg diff по умолчанию ничего не распечатает. Если мы предоставим опцию -g то команда diff покажет нам, что реально произошло.
$ hg diff -g
diff --git a/a b/a
old mode 100644
new mode 100755
[Какие файлы нужно отслеживать, а какие нежелательно]
Системы контроля версий (Revision control systems) главным образом лучше всего подходят для управления текстовыми файлами, которые были написаны людьми - такими как исходный код программы, где файлы час то не изменяются от одной ревизии до другой. Некоторые централизованные системы контроля версий могут также более-менее сносно работать с двоичными файлами, такими как растровые изображения bitmap.
Например, команда разработчиков игры будет обычно обслуживать системой контроля версий как исходный код, так и его двоичные активы (такие как данные геометрии, текстуры, разметки карт).
Поскольку обычно невозможно слить две конфликтующие модификации в двоичный файл, централизированные системы обычно предоставляют механизм блокирования файла, который позволяют пользователю сказать “Только я могу редактировать этот файл, и больше никто”.
В отличие от централизированной системы (centralized system), распределенная система контроля версий (distributed revision control system) меняет некоторые факторы, которые позволяют принять решение - какие файлы отслеживать и каким образом.
Например, распределенная система контроля версий не может по своей природе применить блокировку файла. Таким образом, нет никакого встроенного механизма, который запретит вносить конфликтные изменения разным разработчикам в один двоичный файл. Если Вы работаете в команде, где несколько людей могут часто редактировать двоичные файлы, то может быть не очень хорошей идеей использовать систему Mercurial, или даже любую другую распределенную систему контроля версий для отслеживания этих файлов. когда сохраняются изменения файла, Mercurial обычно сохраняет различия между предыдущей и текущей версиями файла. это очень эффективный метод для большинства текстовых файлов. Однако некоторые файлы (особенно двоичные файлы) размечены так, что даже мелочь логического содержания файла приводит к тому, что многие или почти все байты файла будут изменены. Например, сжатые файлы (архивы zip, rar) особенно восприимчивы к этому. Если изменения между каждой последующей версией файла будут всегда большими, то Mercurial не сможет эффективно сохранять историю ревизий файла. Это приведет как к разрастанию репозитория, так и к увеличению времени, необходимого для его клонирования.
Чтобы понять, как это может произойти практически, предположим, что вы хотите использовать Mercurial для обслуживания (отслеживания истории изменения) документа OpenOffice. OpenOffice сохраняет свои документы на диск как упакованные zip файлы. Даже изменение одной единственной буквы в документе OpenOffice приведет к изменению значения каждого байта в файле документа, когда Вы сохраните его. Теперь предположим, что файл имеет размер 2 мегабайта. Поскольку почти весь файл потерпит изменения при каждом сохранении, Mercurial будет сохранять все 2 мегабайта при каждой фиксации (commit), даже если возможно в файле каждый раз было изменено всего лишь несколько слов. Одиночные часто редактируемые файлы не дружественны к системе хранения Mercurial, из за этого может сильно разрастись размер репозитория.
Еще хуже, если кто-то одновременно с Вами редактирует тот же документ OpenOffice, в этом случае нет никакого подходящего способа объединить (merge) Вашу работу, и работу другого человека. Даже нет никакого способа узнать, какие именно различия имеются между соответствующими изменениями файла.
Таким образом, есть несколько четких рекомендаций по определенным видам файлов, чтобы быть с ними особенно осторожными.
- файлы, которые очень велики по размеру, и плохо сжимаются, например образы ISO CD-ROM. Они даже по причине просто своего размера будут делать клонирование по сети очень медленным. - Файлы, которые сильно изменяются от одной версии к другой могут быть очень дорогими на сохранение в репозитории, если Вы часто редактируете их, и разрешить конфликты из-за параллельного их редактирования может быть трудно.
[Резервные копии и зеркалирование (Backups and mirroring)]
Поскольку Mercurial обслуживает полную копию истории в каждом клоне, то любой, кто использует Mercurial для совместной работы над проектом, может быть потенциальным источником резервной копии в случае катастрофы. Если центральный репозиторий стал недоступен, Вы можете сконструировать ему замену путем клонирования копии репозитория одного из участников проекта, и получения (pulling) любых изменений которые могут быть видны у других участников проекта.
Систему Mercurial очень просто использовать как отключенные от сети резервные копии (off-site backups) и дальние зеркала (remote mirrors). Настройте периодическое ежечасное задание (например, командой cron) на удаленном сервере для передачи изменений (pull changes) от главных репозиториев. Это будет сложно сделать только в маловероятном случае, когда часто меняется количество главных поддерживаемых репозиториев, в этом случае Вам понадобится немного обновлять скрипты на предмет списка репозиториев для бекапа.
Если Вы выполняете традиционные бекапы главные репозитории на ленту или диск, и Вы хотите сделать резервную копию репозитория с именем myrepo, используйте команду hg clone -U myrepo myrepo.bak для клонирования myrepo перед началом Вашего бекапа. Опция -U не проверяет рабочий каталог после завершения клонирования, так как это было бы излишним и заняло больше времени для резервного копирования.
Если нужно восстановиться из myrepo.bak вместо myrepo, то Вы будете иметь гарантию, что имеете непротиворечивый снимок репозитория, на который не сделана команда push каким-нибудь разработчиком посередине процесса копирования.
[Словарик]
commit фиксация - Mercurial делает снимок всего состояния файлов, входящих в репозиторий, и сохраняет в своей базе.
changeset набор изменений. База данных, в которой хранится история изменений файлов и все содержимое репозитория до определенного момента фиксации.
pull получение репозитория на свой компьютер.
pull the changes получить изменения на свой компьютер.
push передача своего репозитория на удалённый компьютер.
push the changes передача изменений своего репозитория на удалённый компьютер.
merge, слияние - объединение двух репозиториев.
divergent, дивергенция - расхождение, дивергенция; противоположна конвергенции.
convergent, конвергенция - схождение, процесс сближения, схождения (в разном смысле), компромиссов; противоположна дивергенции.
repo репозиторий - база данных, где Mercurial отслеживает и сохраняет изменения файлов, плюс все файлы проекта. База данных Mercurial находится в подпапке проекта .hg.
[Ссылки]
1. Mercurial in daily use site:hgbook.red-bean.com - повседневное использование Mercurial. |