Слияние без изменения рабочего каталога
У меня есть следующий сценарий:
* ab82147 (HEAD, topic) changes
* 8993636 changes
* 82f4426 changes
* 18be5a3 (master) first
Я хотел бы объединить (non fast-forward) topic
в master
. Это требует от меня:
-
git checkout master
-
git merge --no-ff topic
Но, проверяя мастер, а затем слияние темы с ним вызывает git изменение моего рабочего каталога (хотя конечный результат идентичен первому, прежде чем проверять мастер), и проблема, с которой я связан, связана с размер нашего проекта занимает около 30 минут, чтобы построить его (с IncrediBuild), хотя ничего не изменилось, и это просто невыносимо.
Итак, я хотел бы получить следующее:
* 9075cf4 (HEAD, master) Merge branch 'topic'
|\
| * ab82147 (topic) changes
| * 8993636 changes
| * 82f4426 changes
|/
* 18be5a3 first
Не касаясь рабочего каталога (или, по крайней мере, обманывая git как-то).
Ответы
Ответ 1
Интересно! Я не думаю, что есть встроенный способ сделать это, но вы должны уметь его использовать с помощью сантехники:
#!/bin/bash
branch=master
# or take an argument:
# if [ [email protected] eq 1 ];
# branch="$1";
# fi
# make sure the branch exists
if ! git rev-parse --verify --quiet --heads "$branch" > /dev/null; then
echo "error: branch $branch does not exist"
exit 1
fi
# make sure this could be a fast-forward
if [ "$(git merge-base HEAD $branch)" == "$(git rev-parse $branch)" ]; then
# find the branch name associated with HEAD
currentbranch=$(git symbolic-ref HEAD | sed '[email protected]*/@@')
# make the commit
newcommit=$(echo "Merge branch '$currentbranch'" | git commit-tree $(git log -n 1 --pretty=%T HEAD) -p $branch -p HEAD)
# move the branch to point to the new commit
git update-ref -m "merge $currentbranch: Merge made by simulated no-ff" "refs/heads/$branch" $newcommit
else
echo "error: merging $currentbranch into $branch would not be a fast-forward"
exit 1
fi
Интересный бит - это строка newcommit=
; он использует commit-tree для непосредственного создания фиксации слияния. Первый аргумент - это дерево для использования; что дерево HEAD, ветвь, содержимое которой вы хотите сохранить. Сообщение фиксации предоставляется на stdin, а остальные аргументы - родителям, которые должен иметь новый фиксатор. Фиксированный SHA1 печатается на stdout, поэтому, предполагая, что фиксация выполнена успешно, вы можете ее захватить, а затем слить это commit (это будет ускоренная перемотка вперед). Если вы навязчивы, вы можете убедиться, что commit-tree удалось - но это должно быть в значительной степени гарантировано.
Ограничения:
- Это работает только на слияниях, которые могли бы быть быстрыми. Очевидно, что вам действительно нужно будет проверить и слить (возможно, в клоне, чтобы сохранить вашу систему сборки) в этом случае.
- Сообщение reflog отличается. Я сделал это сознательно, потому что, когда вы используете
--no-ff
, git фактически заставит себя использовать стратегию по умолчанию (рекурсивную), но написать, что в рефлоге было бы ложью.
- Если вы находитесь в режиме ожидания HEAD, все будет плохо. Это должно быть рассмотрено специально.
И да, я тестировал это на игру-репо, и, похоже, он работает нормально! (Хотя я не пытался его сломать.)
Ответ 2
Самый простой способ, который я могу представить, - это git clone
в отдельную рабочую копию, выполнить слияние там, затем git pull
назад. Затем тяга будет быстрой перемоткой вперед и должна влиять только на файлы, которые действительно изменились.
Конечно, при таком большом проекте временные клоны не идеальны и нуждаются в достаточном количестве свободного места на жестком диске. Временная стоимость дополнительного клона может быть сведена к минимуму (в долгосрочной перспективе), сохранив вашу копию слияния, если вам не требуется дисковое пространство.
Отказ от ответственности: я не подтвердил, что это работает. Я считаю, что он должен, хотя (git не поддерживает временные метки файла версии)
Ответ 3
Кроме того, вы можете исправить симптомы напрямую, сохраняя и восстанавливая отметки времени файла. Это довольно уродливо, но было интересно написать.
Пиктограмма времени сохранения/восстановления Script
#!/usr/bin/env python
from optparse import OptionParser
import os
import subprocess
import cPickle as pickle
try:
check_output = subprocess.check_output
except AttributeError:
# check_output was added in Python 2.7, so it not always available
def check_output(*args, **kwargs):
kwargs['stdout'] = subprocess.PIPE
proc = subprocess.Popen(*args, **kwargs)
output = proc.stdout.read()
retcode = proc.wait()
if retcode != 0:
cmd = kwargs.get('args')
if cmd is None:
cmd = args[0]
err = subprocess.CalledProcessError(retcode, cmd)
err.output = output
raise err
else:
return output
def git_cmd(*args):
return check_output(['git'] + list(args), stderr=subprocess.STDOUT)
def walk_git_tree(rev):
""" Generates (sha1,path) pairs for all blobs (files) listed by git ls-tree. """
tree = git_cmd('ls-tree', '-r', '-z', rev).rstrip('\0')
for entry in tree.split('\0'):
print entry
mode, type, sha1, path = entry.split()
if type == 'blob':
yield (sha1, path)
else:
print 'WARNING: Tree contains a non-blob.'
def collect_timestamps(rev):
timestamps = {}
for sha1, path in walk_git_tree(rev):
s = os.lstat(path)
timestamps[path] = (sha1, s.st_mtime, s.st_atime)
print sha1, s.st_mtime, s.st_atime, path
return timestamps
def restore_timestamps(timestamps):
for path, v in timestamps.items():
if os.path.isfile(path):
sha1, mtime, atime = v
new_sha1 = git_cmd('hash-object', '--', path).strip()
if sha1 == new_sha1:
print 'Restoring', path
os.utime(path, (atime, mtime))
else:
print path, 'has changed (not restoring)'
elif os.path.exists(path):
print 'WARNING: File is no longer a file...'
def main():
oparse = OptionParser()
oparse.add_option('--save',
action='store_const', const='save', dest='action',
help='Save the timestamps of all git tracked files')
oparse.add_option('--restore',
action='store_const', const='restore', dest='action',
help='Restore the timestamps of git tracked files whose sha1 hashes have not changed')
oparse.add_option('--db',
action='store', dest='database',
help='Specify the path to the data file to restore/save from/to')
opts, args = oparse.parse_args()
if opts.action is None:
oparse.error('an action (--save or --restore) must be specified')
if opts.database is None:
repo = git_cmd('rev-parse', '--git-dir').strip()
dbpath = os.path.join(repo, 'TIMESTAMPS')
print 'Using default database:', dbpath
else:
dbpath = opts.database
rev = git_cmd('rev-parse', 'HEAD').strip()
print 'Working against rev', rev
if opts.action == 'save':
timestamps = collect_timestamps(rev)
data = (rev, timestamps)
pickle.dump(data, open(dbpath, 'wb'))
elif opts.action == 'restore':
rev, timestamps = pickle.load(open(dbpath, 'rb'))
restore_timestamps(timestamps)
if __name__ == '__main__':
main()
Bash Тест Script
#!/bin/bash
if [ -d working ]; then
echo "Cowardly refusing to mangle an existing 'working' dir."
exit 1
fi
mkdir working
cd working
# create the repository/working copy
git init
# add a couple of files
echo "File added in master:r1." > file-1
echo "File added in master:r1." > file-2
mkdir dir
echo "File added in master:r1." > dir/file-3
git add file-1 file-2 dir/file-3
git commit -m "r1: add-1, add-2, add-3"
git tag r1
# sleep to ensure new or changed files won't have the same timestamp
echo "Listing at r1"
ls --full-time
sleep 5
# make a change
echo "File changed in master:r2." > file-2
echo "File changed in master:r2." > dir/file-3
echo "File added in master:r2." > file-4
git add file-2 dir/file-3 file-4
git commit -m "r2: change-2, change-3, add-4"
git tag r2
# sleep to ensure new or changed files won't have the same timestamp
echo "Listing at r2"
ls --full-time
sleep 5
# create a topic branch from r1 and make some changes
git checkout -b topic r1
echo "File changed in topic:r3." > file-2
echo "File changed in topic:r3." > dir/file-3
echo "File added in topic:r3." > file-5
git add file-2 dir/file-3 file-5
git commit -m "r3: change-2, change-3, add-5"
git tag r3
# sleep to ensure new or changed files won't have the same timestamp
echo "Listing at r3"
ls --full-time
sleep 5
echo "Saving timestamps"
../save-timestamps.py --save
echo "Checking out master and merging"
# merge branch 'topic'
git checkout master
git merge topic
echo "File changed in topic:r3." > file-2 # restore file-2
echo "File merged in master:r4." > dir/file-3
git add file-2 dir/file-3
git commit -m "r4: Merge branch 'topic'"
git tag r4
echo "Listing at r4"
ls --full-time
echo "Restoring timestamps"
../save-timestamps.py --restore
ls --full-time
Я оставлю это как упражнение для чтения, чтобы очистить Python script, чтобы удалить посторонний вывод и добавить лучшую проверку ошибок.
Ответ 4
Вот какая-то изменчивая версия.
- git stash
- git tag tmptag
- git merge --no-ff topic
- git checkout tmptag (-b tha_brunch)?
- git stash pop
- git tag -D tmptag